Skip to content

Add Relation.belongsTo / Relation.has for FK edge ergonomics#89

Merged
ryanrasti merged 1 commit into
mainfrom
ryan_belongs_to_ergo
Jul 18, 2026
Merged

Add Relation.belongsTo / Relation.has for FK edge ergonomics#89
ryanrasti merged 1 commit into
mainfrom
ryan_belongs_to_ergo

Conversation

@ryanrasti

Copy link
Copy Markdown
Owner

Collapse the codegen boilerplate
Target.scope(Current.contextOf(this)).where(...).cardinality(...)
into:
Relation.belongsTo(this, Target, { pk: this.fk }[, { card }])
Relation.has(this, Target, { fk: this.pk }[, { card }])

Helpers live as free functions under a Relation namespace (not on TableBase) so row/query autocomplete stays about columns and @expose methods. Substrate is unchanged: scope + contextOf + where + cardinality. Context threads through including undefined (same as today).

Card is an opts object for debugability and later attenuation params. Defaults: belongsTo → "one", has → "many"; codegen only emits { card } when non-default. Single-column FK maps only.

Codegen emits the helpers, ensures Relation is imported in update mode, and stamps direction on derived relations. Examples (basic, sqlite) regenerated. Unit tests cover SQL parity, hydrate context, and card typing.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Relation helper API to reduce codegen boilerplate for FK edges while preserving existing query semantics (scope/context threading, where predicate, and cardinality), then updates codegen snapshots, examples, and tests accordingly.

Changes:

  • Introduces Relation.belongsTo / Relation.has helpers and exports Relation from the package entrypoint.
  • Updates table codegen to emit Relation.*(...) calls (and auto-add Relation to typegres imports in update mode), including relation direction metadata.
  • Regenerates example tables and adjusts codegen e2e/unit tests; adds focused unit tests for SQL parity, context threading, and card typing.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/tables/sqlite.test.ts Updates sqlite codegen e2e snapshots to expect Relation.* helpers + imports.
src/tables/postgres.test.ts Updates postgres codegen e2e snapshots/assertions for Relation.* helpers + imports.
src/tables/generate.ts Emits Relation.belongsTo/Relation.has, adds relation direction, and ensures Relation is imported in update mode.
src/tables/generate.test.ts Updates generator snapshots and adds coverage for default/non-default card opt emission + update-mode import behavior.
src/relation.ts Adds the new relation helper implementation (context threading + FK predicate + cardinality).
src/relation.test.ts Adds unit tests covering SQL parity, hydrate context propagation, card typing, and multi-column FK runtime rejection.
src/index.ts Exports Relation from the root package surface.
examples/sqlite/src/tables/teams.ts Regenerated example to use Relation.has.
examples/sqlite/src/tables/dogs.ts Regenerated example to use Relation.belongsTo.
examples/basic/src/tables/toys.ts Regenerated example to use Relation.belongsTo.
examples/basic/src/tables/teams.ts Regenerated example to use Relation.has.
examples/basic/src/tables/microchips.ts Regenerated example to use Relation.belongsTo with { card: "maybe" }.
examples/basic/src/tables/dogs.ts Regenerated example relations to use Relation.* (including card overrides).
examples/basic/src/tables/collars.ts Regenerated example to use Relation.belongsTo.
.gitignore Ignores a local planning-notes file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/relation.ts Outdated
@ryanrasti
ryanrasti force-pushed the ryan_belongs_to_ergo branch from 77da6dc to 2d9ff50 Compare July 18, 2026 02:42
@ryanrasti
ryanrasti force-pushed the ryan_belongs_to_ergo branch from f953dba to 7190e0d Compare July 18, 2026 22:48
@ryanrasti
ryanrasti merged commit 8d8998b into main Jul 18, 2026
3 checks passed
ryanrasti added a commit that referenced this pull request Jul 18, 2026
Extend .live to sqlite (better-sqlite3, Durable Objects' SqlStorage) with
a minimal delta from the pg design: the Bus (reverse index, subscribe +
backfill, lifecycle) is dialect-shared; only the event source differs.

- Event source: pg keeps the shadow-events table + polling loop. sqlite
  has NO shadow table and no polling — the runtime is a synchronous
  single writer, so capture pushes events into the new `Bus.ingest()` in
  the same tick as the mutation, entirely in memory. Cursors are integer
  seqs embedded in the snapshot Cursor shape ({seq+1, seq+1, ∅}), so the
  existing MVCC `visible()` test doubles as the seq comparison and the
  subscribe-time backfill check precisely covers the register race.
- Capture (live/sqlite/capture.ts): opted-in tables (`transformer:
  SqliteLiveCapture.makeTransformer()`) get json_object image columns
  appended to RETURNING — insert→after, delete→before, update→after —
  plus a pre-SELECT for update before-images (sqlite RETURNING can't see
  OLD). Runs via a new optional `Driver.executeSync` in one synchronous
  block with the mutation: no awaits between statements, so single-writer
  atomicity replaces the transaction.
- Extractor portability: accept bare `Param` literals as anchors (sqlite
  binds primitives as `?`, not pg's `CAST($n AS T)`); dialect-aware text
  casts; decoy aliases so extractor CTEs never shadow their own table
  (sqlite resolves a CTE name inside its body — "circular reference").
- live/canonical.ts: shared value-rendering contract; collapses integral
  REALs so `'1.0'`-bound anchors match `'1'` stored INTEGERs (drivers
  bind every JS number as REAL). A parity test hangs on divergence.
- Transport intentionally deferred: `.live()` stays a local AsyncIterable
  on both dialects (already streams over exoeval). Cap'n Web prototype
  learnings (by-ref callbacks vs map recorder, dup()/dispose lifecycle,
  parked-await teardown) are recorded in ISSUES.md #13.

Tested end-to-end on better-sqlite3 (zero timers — dispatch is sync) and
via a fake SqlStorage through DoSqliteDriver; pg suite unchanged. Note:
examples/basic typecheck fails identically on main (8d8998b) — inherited
from #89, not this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants