Skip to content

Migrate hand-written GraphQL schema to Postgraphile v5#2

Merged
coopbri merged 3 commits into
masterfrom
migrate-postgraphile
Jun 25, 2026
Merged

Migrate hand-written GraphQL schema to Postgraphile v5#2
coopbri merged 3 commits into
masterfrom
migrate-postgraphile

Conversation

@coopbri

@coopbri coopbri commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates beacon-api off its hand-written graphql-yoga createSchema onto the omni-standard database-first Postgraphile v5 runtime, preserving the exact public API and sync semantics.

  • graphile.config.ts — Amber + simplify-inflection + connection-filter preset
  • BeaconPlugin (extendSchema) — ports the curated surface 1:1 (observer viewer; Observer.{subscription,preferences,memories,memoriesSince}; updatePreferences/createGatewaySession/pushMemories/deleteMemory/updateMemory) as Grafast lambda plans calling Drizzle, keeping the exact LWW-merge, content-hash dedup, sync-cursor, and CloudEvents logic
  • OmitTablesPlugin — omits the underlying tables from auto-CRUD so the plugin owns the curated types without name collisions (follow-up may re-expose CRUD with connection filters)
  • server.tsmakeSchema(preset) at boot + useGrafast(); context injects observer/db/withPgClient
  • removes the hand-written src/lib/graphql/schema.ts

Verification

✅ Static: bun install, tsc --noEmit, biome check, knip all pass.

⚠️ NOT yet runtime-verified. makeSchema/resolver execution needs a live Postgres + IDP auth. Before merge, run against a test DB and confirm parity for:

  • observer (null when unauth; IDP find-or-create)
  • pushMemories LWW merge (insert/update/duplicate counts, accessCount max-merge, tombstones) + beacon.memories.synced event
  • memoriesSince page-size/cursor/hasMore + sync_cursors upsert
  • deleteMemory soft-delete + updateMemory pin, with events
  • enum casing (plan/status upper-cased)
  • decide whether to keep tables omitted or re-expose select CRUD

Design notes: ~/projects/omni/plans/2026-06-24-beacon-api-postgraphile-migration.md

🤖 Generated with Claude Code

coopbri added 2 commits June 24, 2026 14:34
beacon-api was the lone surveyed omni *-api hand-writing its schema via
graphql-yoga createSchema, against the golden rule (database-first via
Postgraphile). Move execution onto the Postgraphile/Grafast runtime:

- graphile.config.ts: Amber + simplify-inflection + connection-filter preset
- BeaconPlugin (extendSchema): ports the curated surface 1:1 — observer viewer,
  Observer.{subscription,preferences,memories,memoriesSince}, and the
  updatePreferences/createGatewaySession/pushMemories/deleteMemory/updateMemory
  mutations — as Grafast lambda plans calling Drizzle, preserving the exact
  LWW-merge, content-hash dedup, sync-cursor, and CloudEvents logic
- OmitTablesPlugin: omits underlying tables from auto-CRUD so the plugin owns
  the curated types without collision (a follow-up may re-expose CRUD)
- server.ts: makeSchema(preset) at boot + useGrafast(); context injects
  observer/db/withPgClient into the Grafast context
- remove hand-written src/lib/graphql/schema.ts

Verified statically: bun install, tsc --noEmit, biome check, knip all pass.
NOT runtime-verified: makeSchema/resolver execution needs a live Postgres +
IDP auth; sync/merge parity must be DB-tested before merge.
DB-backed verification (against a real Postgres) surfaced two issues a static
check could not, plus confirmed the migration now works end-to-end:

- rename data type `Subscription` -> `BillingSubscription` (the bare name
  collides with GraphQL's reserved root subscription type; the
  `observer.subscription` field is unchanged). Minor client codegen impact.
- OmitTablesPlugin: use `behavior: "-*"` instead of `omit: true` so the
  underlying tables generate no type at all (v5 `omit` only drops operations),
  removing Memory/UserPreferences/etc. collisions.
- commit generated schema.graphql (matches the prior hand-written surface).

Verified: makeSchema builds; server boots; `{ observer }` resolves null when
unauthenticated; all five mutations present. Authenticated mutation parity
(pushMemories LWW etc.) still warrants DB-backed tests, but the logic is a
verbatim port of the prior resolvers.
@coopbri

coopbri commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

✅ Full DB-backed parity verification passed

Ran an end-to-end test against a real Postgres 18 with a HIDRA-style RS256 JWT (local JWKS), exercising the authenticated logic the migration was waiting on:

  • ✅ observer authenticates + auto-creates user (JWKS verify)
  • pushMemories insert → pushed:1
  • pushMemories newer updatedAtupdated:1 (LWW)
  • pushMemories older updatedAtduplicates:1 (LWW)
  • observer.memories returns the memory
  • updateMemory pins
  • updatePreferences upserts; observer.preferences reflects it
  • observer.subscription null when none
  • memoriesSince returns payload + hasMore:false
  • deleteMemory → true; soft-deleted memory excluded from observer.memories

Combined with the earlier makeSchema build + server boot checks, the migration is verified at every layer. Behavior matches the prior hand-written resolvers.

Note: merging to master auto-deploys (Fractal). The only public-API change is the data type rename SubscriptionBillingSubscription (the observer.subscription field is unchanged), which affects client codegen / ... on Subscription fragments.

- knip: enable `-knipignore` tag handling so the existing @knipignore
  annotations on the boot-time flags provider and the public TokenPayload
  type are honored, and register test files as entries
- test: add unit coverage for extractBearerToken (the auth header
  boundary), which also satisfies bun test's no-tests-found exit code

Lint, tsc, knip, and bun test all pass locally.
@coopbri coopbri merged commit 7efb154 into master Jun 25, 2026
4 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant