Skip to content

feat: integrate RealtimeManager into constructive preset and cache lifecycle#1119

Merged
pyramation merged 9 commits intomainfrom
feat/realtime-preset-integration
May 11, 2026
Merged

feat: integrate RealtimeManager into constructive preset and cache lifecycle#1119
pyramation merged 9 commits intomainfrom
feat/realtime-preset-integration

Conversation

@pyramation
Copy link
Copy Markdown
Contributor

@pyramation pyramation commented May 11, 2026

Summary

Wires the RealtimeManager (from graphile-realtime-subscriptions, merged in #1111) into the PostGraphile preset system and cache lifecycle, gated behind a per-tenant enableRealtime database feature flag (default: false).

Changes across 4 layers:

  1. Feature flag — Adds enableRealtime?: boolean to ConstructivePresetOptions (default false) and DatabaseSettings, following the existing pattern for enableAggregates, enableLlm, etc.

  2. Preset — When enableRealtime is true, RealtimeSubscriptionsPreset() is included in createConstructivePreset, which adds per-table subscription fields for @realtime-tagged tables.

  3. Cache lifecycleGraphileCacheEntry gains an optional realtimeManager field. createGraphileInstance creates and starts a RealtimeManager when enabled; disposeEntry stops the manager on cache eviction. Both pgSubscriber and the pg pool are extracted from the resolved preset's pgServices[0] — no separate pool parameter is needed, since the pool is already embedded by makePgService({ pool, schemas }) (managed by pg-cache).

  4. Server middlewaregraphile.ts passes enableRealtime through to createGraphileInstance. api.ts adds enableRealtime to DatabaseSettings but hardcodes it to false because enable_realtime does not yet exist as a column in services_public.database_settings or services_public.api_settings. The column migration is in constructive-db #1105; once merged, a follow-up commit will wire the COALESCE cascade.

  5. Error observability — Replaced bare catch { return undefined; } with catch (e: any) { log.warn(...); return undefined; } in 5 settings query functions (queryRlsSettings, queryCorsSettings, queryPubkeySettings, queryWebauthnSettings, queryDatabaseSettings). Previously these silently swallowed SQL errors, masking real issues like missing columns or connection failures.

Updates since last revision

  • Removed withPgClientFromPool — CursorTracker/RealtimeManager now use pool.query() directly. The previous WithPgClient callback abstraction (acquire client → callback → release) was unnecessary since every usage was a single one-shot query — exactly what pool.query() does internally. Replaced the WithPgClient/PgClient types with a single Queryable interface ({ query() }), and create-instance.ts now passes the pool straight to RealtimeManager with no wrapper. This removes connection-management logic from graphile-cache where it didn't belong.
  • Pool no longer passed as a separate parameter. createGraphileInstance previously accepted an optional pool from graphile.ts, duplicating pool management that pg-cache already handles. The pool is now extracted from resolvedPreset.pgServices[0].adaptorSettings.pool — the same instance threaded into the preset by makePgService({ pool, schemas }). This makes pg-cache the single source of truth for pool lifecycle.
  • Previous updates (still in effect): replaced silent catch blocks with log.warn; removed premature enable_realtime column references from DATABASE_SETTINGS_SQL.

Review & Testing Checklist for Human

  • (resolvedPreset as any).pgServices?.[0] in create-instance.ts — Accesses PostGraphile v5 internals via as any. Confirm the resolved preset actually exposes pgSubscriber and adaptorSettings.pool on the first pg service at this point in the lifecycle (after serv.ready()). A null subscriber or null pool logs a warning and skips gracefully.
  • Pool extraction path: pgService?.adaptorSettings?.pool — This relies on @dataplan/pg's makePgService storing the pool at this path. Verify that the pool object returned here satisfies the Queryable interface (i.e. has a .query(text, values) method). Since it's a pg.Pool, it should, but confirm this is stable across PostGraphile v5 releases.
  • log.warn noise level — The 5 catch blocks now log at warn level. Verify that in normal production operation (where modules like rls_module or webauthn_module may not be deployed for every tenant) these queries don't fail routinely. If they do, some should use log.debug instead (like queryAuthSettings already does). The intent is that these core settings tables always exist, so a SQL error here is genuinely unexpected.
  • Hardcoded schema: 'realtime_public' in create-instance.ts — Verify this is correct for all tenant configurations, or whether it should be derived from the preset/settings.
  • Follow-up migration requiredenableRealtime is permanently false until constructive-db #1105 is merged, the enable_realtime column exists, and api.ts SQL is updated to include the COALESCE column.

Suggested test plan: Deploy to a test environment, verify existing feature-flag gating still works (e.g. presigned uploads enabled/disabled per API), then enable enableRealtime manually in code to confirm RealtimeManager starts and connects to PgSubscriber.

Notes

  • The RealtimeManager is dynamically imported and wrapped in try/catch, so if the import fails the PostGraphile instance still works — realtime just won't be active.
  • The explorer's usage of createGraphileInstance is unaffected since enableRealtime is optional with a safe default.
  • This is part of the broader realtime subscriptions effort tracked in #825.

Link to Devin session: https://app.devin.ai/sessions/19485cf5cc58416a9f86068563d512f5
Requested by: @pyramation

…fecycle

- Add enableRealtime flag to ConstructivePresetOptions (default: false)
- Add enableRealtime to DatabaseSettings and database_settings SQL query
- Conditionally include RealtimeSubscriptionsPreset when enableRealtime is true
- Extend GraphileCacheEntry with optional realtimeManager field
- Create and start RealtimeManager in createGraphileInstance when enabled
- Stop RealtimeManager in disposeEntry on cache eviction
- Wire enableRealtime + pool from server middleware to createGraphileInstance
- Extract PgSubscriber from resolved preset for cursor event dispatch
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@blacksmith-sh

This comment has been minimized.

The enable_realtime column doesn't exist yet in services_public.database_settings
or services_public.api_settings. Referencing it causes the entire query to fail,
which silently drops ALL feature flags (caught by try/catch returning undefined).
This broke the Bob restricted API test because enablePresignedUploads fell back
to the default (true) instead of the api_settings override (false).

Default enableRealtime to false in toDatabaseSettings until the migration in
constructive-db adds the column.
Now that constructive-db#1105 adds the enable_realtime column to both
services_public.database_settings and services_public.api_settings,
restore the SQL query to read it via the standard COALESCE cascade
(api_settings override → database_settings default).

Removes the temporary hardcoded enableRealtime: false fallback.
@blacksmith-sh

This comment has been minimized.

…tions

All 5 settings query functions (queryRlsSettings, queryCorsSettings,
queryPubkeySettings, queryWebauthnSettings, queryDatabaseSettings)
previously used bare catch { return undefined } which silently
swallowed SQL errors — including schema mismatches like missing columns.

Now each catch logs with log.warn including the function name and error
message, making query failures immediately visible in server logs while
still gracefully degrading to defaults.
…merged

The CI database doesn't have the enable_realtime column yet, so the
COALESCE query fails. Keep the hardcoded enableRealtime: false for now;
the SQL wiring will be a follow-up commit once the DB migration lands.

The log.warn fixes for silent catch blocks are retained.
Remove the WithPgClient/PgClient callback abstraction. CursorTracker and
RealtimeManager now accept a Queryable (typically pg.Pool) directly and
call pool.query() for one-shot queries — the same pattern api.ts uses.

This removes connection-management logic from graphile-cache (where it
didn't belong) and eliminates an unnecessary abstraction layer.
@pyramation pyramation merged commit 90fe6d4 into main May 11, 2026
54 checks passed
@pyramation pyramation deleted the feat/realtime-preset-integration branch May 11, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant