Skip to content

feat(tml-2892): contract-JSON-driven Migration base + typed contract views#879

Merged
wmadden merged 12 commits into
mainfrom
tml-2892-migration-author-accessor-for-contract-entities-kill-the
Jul 2, 2026
Merged

feat(tml-2892): contract-JSON-driven Migration base + typed contract views#879
wmadden merged 12 commits into
mainfrom
tml-2892-migration-author-accessor-for-contract-entities-kill-the

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

At a glance

A migration, before and after this PR:

// before — a hand-written describe() with copied hashes, and reaching into the
// contract through internal coordinates:
class M extends Migration {
  override describe() {
    return { from: null, to: 'sha256:8ee1…' };   // hand-copied hash
  }
  override get operations() {
    const COLLECTIONS = endContract.storage.namespaces.__unbound__.entries.collection;
    return [ createCollection('carts', { validator: { $jsonSchema: COLLECTIONS.carts.validator.jsonSchema } }),  ];
  }
}
// after — the base takes the migration's own start/end contract JSON, derives
// describe() from it, and exposes typed views; schema ops are plain generator output:
import endContract from './end-contract.json' with { type: 'json' };
import type { Contract as End } from './end-contract';

class M extends Migration<never, End> {
  override readonly endContractJson = endContract;
  override get operations() {
    return [ createCollection('carts', { validator: { $jsonSchema: {} } }),  ];
  }
}

For a migration that needs the contract (a data backfill), the view is right there, fully typed:

this.dataTransform('backfill', () => { const v = this.endContract.collection.products.validator;  });

The decision

Extend the Migration base to take the migration's start/end contract JSON as typed inputs instead of hand-written from/to hashes. The base:

  • derives describe()to = endContractJson.storage.storageHash, from = startContractJson?.storage.storageHash ?? null (byte-identical to the hashes that were previously copied in by hand), and
  • exposes typed viewsthis.startContract / this.endContract, each a ContractView: a superset of the contract that lets you reach entities by name (this.endContract.collection.carts) without spelling the __unbound__ sentinel, the entries wrapper, or the kind key.

migration plan emits this shape; every example migration is regenerated to it. Contract itself stays a raw, low-level mirror of the serialized form — the ergonomics live in the view.

How it got here

The ticket started as "add an accessor so migration authors stop writing storage.namespaces.__unbound__.entries.collection." Building the accessor surfaced the real question: generated migrations inline their values and never read the contract at all — so the only place that leak appeared was hand-authored migrations. An accessor alone would have been a solution looking for a problem.

The fix that earns its place: make the contract a first-class input to every migration. The Migration base reads the migration's own committed contract snapshot — which also lets it derive the from/to hashes (no more hand-copied sha256:…) — and hands the author a typed view over it for the one thing that is hand-authored: data transforms. Schema content stays 100% generator output.

The view

Per target, ContractView unwraps the target's default namespace and mirrors the established db.enums projection (shared unboundNamespace helper, NamespacedEntities alongside NamespacedEnums):

Target Access
Mongo (one namespace) this.endContract.collection.carts
SQLite (one namespace) this.endContract.table.users
Postgres (named schemas) this.endContract.namespace.public.table.users

The view is a superset of the contract (so this.endContract is usable as a contract and an accessor), built via a { from, fromJson } factory — no class-that-doesn't-instantiate.

What changed

  • Migration base (migration-tools) — generics <Start, End>, optional startContractJson/endContractJson, concrete derived describe() (extension migrations that override describe() and carry no contract keep working).
  • Family/target basesMongoMigration, SqliteMigration, PostgresMigration expose lazy typed startContract/endContract views.
  • ContractViewframework-components shared projection + per-target views (@prisma-next/family-mongo, @prisma-next/target-{sqlite,postgres}).
  • The three render-typescript generators — emit the new shape, drop the describe() emission.
  • All 70 example migrations — regenerated to the new shape (schema content = generator output; the one data backfill keeps its hand-authored transform). ops.json/migration.json are byte-identical.
  • Supporting: the no-bare-cast Biome plugin no longer misreads import type { X as Y } aliases as casts.

Testing

Types are proven emit-then-consume against real emitted contract .d.ts fixtures (including a multi-schema Postgres contract where reaching the wrong schema's column is a compile error). The decisive check: test:integration (1125 tests) executes the regenerated migration scaffolds end-to-end through migration apply, and ops.json/migration.json are byte-identical across the whole regeneration — the scaffold changed, no migration's behavior did. Full CI-parity gates pass locally (build, force-typecheck, the whole Lint job incl. a delta-0 cast ratchet and upgrade-coverage, fixtures:check, and all three test suites).

Alternatives considered

  • A getter on Contract itself. Rejected: the author-facing contract is data-only (the emitted .d.ts has no methods), so a getter there is invisible — and it would couple the emitter to a convenience concern.
  • Emit the accessor as denormalized data. Rejected: duplicates every entity in the canonical artifact.
  • A view flattened onto the contract root (per-schema keys at the top level). Rejected: a Postgres schema named like a contract field (storage, domain) would silently shadow it. Schemas live under .namespace instead — matching db.enums.
  • Keep hand-authoring the example migrations in the new shape. Rejected — that's the exact "generated content, hand-written" smell this set out to remove. The generator is the source of truth; examples are regenerated.

Refs: TML-2892

🤖 Generated with Claude Code

@wmadden-electric wmadden-electric requested a review from a team as a code owner June 26, 2026 16:38
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Too many files!

This PR contains 186 files, which is 36 over the limit of 150.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 170ae71f-e533-47d6-aab2-d295ffb169df

📥 Commits

Reviewing files that changed from the base of the PR and between 5c04541 and eb2c369.

📒 Files selected for processing (186)
  • biome-plugins/no-bare-cast.grit
  • docs/architecture docs/adrs/ADR 232 - A migration is authored against its start and end contract snapshots.md
  • docs/architecture docs/adrs/ADR 233 - ContractView is a typed by-name accessor over a contract.md
  • examples/mongo-demo/migrations/app/20260409T1030_migration/migration.ts
  • examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.ts
  • examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.ts
  • examples/multi-extension-monorepo/packages/audit/migrations/20260601T0000_create_audit_event/migration.ts
  • examples/multi-extension-monorepo/packages/feature-flags/migrations/20260601T0000_create_feature_flag/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260301T1000_init/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1000_add_phone/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1000_add_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1000_add_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1100_add_posts/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1100_add_posts/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1100_add_posts/start-contract.json
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1200_add_avatar/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1200_add_avatar/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260302T1200_add_avatar/start-contract.json
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1000_merge_phone/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1000_merge_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1000_merge_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1100_merge_posts/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1100_merge_posts/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1100_merge_posts/start-contract.json
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1200_merge_avatar/migration.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1200_merge_avatar/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/converging-branches/migrations/app/20260303T1200_merge_avatar/start-contract.json
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260301T1000_init/migration.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260302T1000_alice_add_phone/migration.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260302T1000_alice_add_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260302T1000_alice_add_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260302T1100_bob_add_avatar/migration.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260302T1100_bob_add_avatar/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260302T1100_bob_add_avatar/start-contract.json
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260303T1000_merge_alice/migration.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260303T1000_merge_alice/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260303T1000_merge_alice/start-contract.json
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260303T1100_merge_bob/migration.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260303T1100_merge_bob/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/diamond/migrations/app/20260303T1100_merge_bob/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260301T1000_init/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260302T1000_add_phone/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260302T1000_add_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260302T1000_add_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260303T1000_add_bio/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260303T1000_add_bio/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260303T1000_add_bio/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260304T1000_add_posts/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260304T1000_add_posts/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260304T1000_add_posts/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260305T1000_add_avatar/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260305T1000_add_avatar/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260305T1000_add_avatar/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260306T1000_add_comments/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260306T1000_add_comments/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260306T1000_add_comments/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260307T1000_add_tags/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260307T1000_add_tags/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260307T1000_add_tags/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260307T1100_late_branch/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260307T1100_late_branch/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260307T1100_late_branch/start-contract.json
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260308T1000_add_everything/migration.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260308T1000_add_everything/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/long-spine/migrations/app/20260308T1000_add_everything/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260301T1000_init/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1000_add_phone/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1000_add_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1000_add_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1100_add_posts/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1100_add_posts/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1100_add_posts/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1200_add_avatar/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1200_add_avatar/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260302T1200_add_avatar/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260303T1000_add_bio/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260303T1000_add_bio/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260303T1000_add_bio/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_a/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_a/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_a/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_b/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_b/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_b/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_c/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_c/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_c/start-contract.json
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_d/migration.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_d/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/multi-branch/migrations/app/20260304T1000_parallel_d/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0719_init/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_add_name/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_add_name/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_add_name/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_alice_phone/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_alice_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_alice_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_bob_avatar/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_bob_avatar/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0725_bob_avatar/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_add_bio/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_add_bio/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_add_bio/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_add_locale/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_add_locale/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_add_locale/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_fast_forward/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_fast_forward/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_fast_forward/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_merge_alice/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_merge_alice/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_merge_alice/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_merge_bob/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_merge_bob/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0726_merge_bob/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_hotfix/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_hotfix/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_hotfix/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_alice/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_alice/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_alice/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_locale/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_locale/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_locale/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_users/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_users/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0727_rollback_users/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0728_promote_bob/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0728_promote_bob/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0728_promote_bob/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0729_reapply_noop/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0729_reapply_noop/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0730_experiment/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0730_experiment/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0730_experiment/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0730_revert_experiment/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0730_revert_experiment/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260601T0730_revert_experiment/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260602T1624_rollback_to_users_from_tip/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260602T1624_rollback_to_users_from_tip/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260602T1624_rollback_to_users_from_tip/start-contract.json
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260602T1626_rollback_to_users_from_bio/migration.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260602T1626_rollback_to_users_from_bio/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/showcase/migrations/app/20260602T1626_rollback_to_users_from_bio/start-contract.json
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260301T1000_init/migration.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260302T1000_add_phone/migration.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260302T1000_add_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260302T1000_add_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260303T1000_add_bio/migration.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260303T1000_add_bio/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260303T1000_add_bio/start-contract.json
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260304T1000_add_posts/migration.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260304T1000_add_posts/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260304T1000_add_posts/start-contract.json
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260305T1000_rollback_to_phone/migration.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260305T1000_rollback_to_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260305T1000_rollback_to_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260306T1000_rollback_to_init/migration.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260306T1000_rollback_to_init/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/skip-rollback/migrations/app/20260306T1000_rollback_to_init/start-contract.json
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260301T1000_init/migration.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1000_add_phone/migration.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1000_add_phone/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1000_add_phone/start-contract.json
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1100_add_posts/migration.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1100_add_posts/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1100_add_posts/start-contract.json
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1200_add_avatar/migration.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1200_add_avatar/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1200_add_avatar/start-contract.json
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1300_add_category/migration.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1300_add_category/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1300_add_category/start-contract.json
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1400_add_settings/migration.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1400_add_settings/start-contract.d.ts
  • examples/prisma-next-demo/fixtures/wide-fan/migrations/app/20260302T1400_add_settings/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/migration.ts
  • examples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/migration.ts
  • examples/retail-store/migrations/app/20260513T0505_initial/migration.ts
  • examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.ts
  • examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts
  • examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.ts
  • examples/retail-store/test/migration.test.ts
  • packages/1-framework/1-core/framework-components/src/exports/ir.ts
  • packages/1-framework/1-core/framework-components/src/ir/contract-view.ts
  • packages/1-framework/1-core/framework-components/test/contract-view.test.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Contract-view helpers now promote built-in kinds from default namespaces, add Mongo/SQL/Postgres/SQLite view wrappers, expand fixtures and tests, and update retail-store migrations to read collections and validators from MongoContractView.

Changes

Contract view projections

Layer / File(s) Summary
Single-namespace helper
packages/1-framework/1-core/framework-components/src/ir/contract-view.ts, packages/1-framework/1-core/framework-components/src/exports/ir.ts, packages/1-framework/1-core/framework-components/test/contract-view.test.ts
Adds default-namespace extraction, built-in promotion, namespace accessor composition, and framework tests for single-namespace projections.
SQL view builders
packages/2-sql/1-core/contract/src/contract-view.ts, packages/2-sql/1-core/contract/src/exports/contract-view.ts, packages/2-sql/1-core/contract/package.json, packages/2-sql/1-core/contract/tsdown.config.ts
Defines SQL built-in kinds, single-namespace and schema-qualified views, and the new contract-view export path.
Mongo view surface
packages/2-mongo-family/1-foundation/mongo-contract/src/contract-view.ts, packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts, packages/2-mongo-family/9-family/src/core/ir/mongo-contract-view.ts, packages/2-mongo-family/9-family/src/exports/ir.ts
Adds Mongo accessors, wrapper factories, and runtime exports on top of the shared builder.
Mongo fixtures and tests
packages/2-mongo-family/9-family/test/fixtures/orm-contract.*, packages/2-mongo-family/9-family/test/mongo-contract-view.test.*, packages/2-mongo-family/9-family/package.json, packages/2-mongo-family/9-family/tsconfig.test.json
Adds generated Mongo fixtures, a test tsconfig, and type/runtime tests for collection access and pack entries.
Postgres view surface
packages/3-targets/3-targets/postgres/src/core/postgres-contract-view.ts, packages/3-targets/3-targets/postgres/src/exports/runtime.ts
Switches Postgres to a schema-qualified view object with from and fromJson factories.
Postgres fixtures and tests
packages/3-targets/3-targets/postgres/test/fixtures/namespaced-contract.*, packages/3-targets/3-targets/postgres/test/fixtures/collision-contract.ts, packages/3-targets/3-targets/postgres/test/postgres-contract-view.test.*
Adds namespaced and collision Postgres fixtures plus compile-time coverage for schema separation and raw namespace access.
SQLite view surface
packages/3-targets/3-targets/sqlite/src/core/sqlite-contract-view.ts, packages/3-targets/3-targets/sqlite/src/exports/runtime.ts
Adds SQLite single-namespace view factories and a runtime re-export.
SQLite fixtures and tests
packages/3-targets/3-targets/sqlite/test/fixtures/sqlite-contract.*, packages/3-targets/3-targets/sqlite/test/sqlite-contract-view.test.*
Adds generated SQLite fixtures and type/runtime tests for table access, default namespace lookup, and empty valueSet.
Retail-store consumers
examples/retail-store/migrations/app/20260513T0505_initial/migration.ts, examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts, examples/retail-store/test/migration.test.ts, skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
Updates retail-store migrations and tests to read Mongo collections and validators from MongoContractView, and adds the upgrade note.

Sequence Diagram(s)

sequenceDiagram
  participant MongoContractView_fromJson as MongoContractView.fromJson
  participant MongoContractSerializer
  participant buildMongoContractView
  participant buildSingleNamespaceView
  participant buildNamespaceAccessor
  participant composeContractView
  MongoContractView_fromJson->>MongoContractSerializer: deserializeContract(json)
  MongoContractSerializer-->>MongoContractView_fromJson: contract
  MongoContractView_fromJson->>buildMongoContractView: wrap(contract)
  buildMongoContractView->>buildSingleNamespaceView: default namespace entries
  buildMongoContractView->>buildNamespaceAccessor: storage.namespaces
  buildMongoContractView->>composeContractView: accessors + namespace map
  composeContractView-->>MongoContractView_fromJson: projected view
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • prisma/prisma-next#534: Reworks Mongo contract storage and namespace access into the namespace-based shape that this PR's MongoContractView consumers read.

Suggested reviewers

  • wmadden
  • aqrln

Poem

🐰 I hopped through namespaces, neat and sly,
Built-in kinds now pop up high.
fromJson beams the view in place,
Collections bloom with rabbit grace.
Thump-thump — the stack looks tidy tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the core change: introducing typed contract views, along with related migration JSON usage.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2892-migration-author-accessor-for-contract-entities-kill-the

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jun 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@879

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@879

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@879

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@879

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@879

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@879

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@879

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@879

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@879

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@879

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@879

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@879

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@879

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@879

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@879

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@879

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@879

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@879

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@879

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@879

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@879

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@879

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@879

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@879

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@879

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@879

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@879

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@879

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@879

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@879

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@879

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@879

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@879

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@879

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@879

prisma-next

npm i https://pkg.pr.new/prisma-next@879

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@879

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@879

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@879

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@879

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@879

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@879

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@879

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@879

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@879

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@879

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@879

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@879

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@879

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@879

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@879

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@879

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@879

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@879

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@879

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@879

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@879

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@879

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@879

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@879

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@879

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@879

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@879

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@879

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@879

commit: eb2c369

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.2 KB (-0.09% 🔽)
postgres / emit 147.39 KB (-0.07% 🔽)
mongo / no-emit 96.52 KB (0%)
mongo / emit 89.23 KB (0%)
cf-worker / no-emit 188.25 KB (-0.05% 🔽)
cf-worker / emit 173.69 KB (-0.05% 🔽)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/1-framework/1-core/framework-components/src/ir/contract-view.ts`:
- Around line 47-50: The helper currently returns an unconstrained TView while
accepting builtinKinds as a plain readonly string array, so the returned view
type can promise materialized builtin properties that may actually be undefined.
Update promoteBuiltinKinds and the related helper in contract-view.ts to take
the builtin kinds as a tuple literal type and derive the return type from that
tuple instead of casting to TView; keep the type relationship aligned so callers
can only request view properties that are guaranteed by the provided
builtinKinds.

In `@packages/3-targets/3-targets/postgres/test/postgres-contract-view.test-d.ts`:
- Line 2: The import in postgres-contract-view.test-d.ts is currently type-only,
but PostgresContractView is used in a value position via typeof
PostgresContractView.from<Contract>. Change the import to a normal import so the
value binding exists at compile time, and keep the existing usage of
PostgresContractView in the type query unchanged.

In `@packages/3-targets/3-targets/sqlite/test/fixtures/sqlite-contract.json`:
- Around line 558-565: The SQLite contract JSON fixture is out of sync with the
paired emitted type fixture, since its sql capabilities are missing the enums
and lateral flags that the sqlite-contract.d.ts fixture still expects.
Regenerate both sqlite-contract.json and sqlite-contract.d.ts from the same
emitted Contract output, or update both fixtures together so the Contract shape
matches in both runtime and type fixtures. Keep the sql.capabilities object
consistent across the sqlite contract fixtures, especially around sql.enums and
sql.lateral.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b6669857-bd63-4f5f-8ed3-8a07ea5c38e4

📥 Commits

Reviewing files that changed from the base of the PR and between 9532c5f and ed61436.

📒 Files selected for processing (27)
  • examples/retail-store/migrations/app/20260513T0505_initial/migration.ts
  • examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts
  • examples/retail-store/test/migration.test.ts
  • packages/1-framework/1-core/framework-components/src/exports/ir.ts
  • packages/1-framework/1-core/framework-components/src/ir/contract-view.ts
  • packages/1-framework/1-core/framework-components/test/contract-view.test.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/src/contract-view.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/test/contract-view.test-d.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/test/contract-view.test.ts
  • packages/2-mongo-family/9-family/src/exports/ir.ts
  • packages/2-sql/1-core/contract/package.json
  • packages/2-sql/1-core/contract/src/contract-view.ts
  • packages/2-sql/1-core/contract/src/exports/contract-view.ts
  • packages/2-sql/1-core/contract/tsdown.config.ts
  • packages/3-targets/3-targets/postgres/src/core/postgres-contract-view.ts
  • packages/3-targets/3-targets/postgres/src/exports/runtime.ts
  • packages/3-targets/3-targets/postgres/test/fixtures/namespaced-contract.d.ts
  • packages/3-targets/3-targets/postgres/test/fixtures/namespaced-contract.json
  • packages/3-targets/3-targets/postgres/test/postgres-contract-view.test-d.ts
  • packages/3-targets/3-targets/postgres/test/postgres-contract-view.test.ts
  • packages/3-targets/3-targets/sqlite/src/core/sqlite-contract-view.ts
  • packages/3-targets/3-targets/sqlite/src/exports/runtime.ts
  • packages/3-targets/3-targets/sqlite/test/fixtures/sqlite-contract.d.ts
  • packages/3-targets/3-targets/sqlite/test/fixtures/sqlite-contract.json
  • packages/3-targets/3-targets/sqlite/test/sqlite-contract-view.test-d.ts
  • packages/3-targets/3-targets/sqlite/test/sqlite-contract-view.test.ts

Comment on lines +47 to +50
export function promoteBuiltinKinds<TView>(
entries: Readonly<Record<string, unknown>>,
builtinKinds: readonly string[],
): TView {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Tie builtinKinds to the returned view type.

These helpers currently blind-cast to an arbitrary TView, but builtinKinds is just readonly string[]. Because this module is now exported publicly, a caller can request a view type that guarantees valueSet/collection while passing a list that does not include that kind, and the helper will return undefined at runtime for a property the type says is always materialized. Make the built-in kind tuple part of the function signature and derive the return type from it instead of accepting an unconstrained TView.

Proposed direction
-export function promoteBuiltinKinds<TView>(
-  entries: Readonly<Record<string, unknown>>,
-  builtinKinds: readonly string[],
-): TView {
+export function promoteBuiltinKinds<
+  TEntries extends Readonly<Record<string, unknown>>,
+  const TBuiltinKinds extends readonly string[],
+>(
+  entries: TEntries,
+  builtinKinds: TBuiltinKinds,
+): SingleNamespaceView<TEntries, TBuiltinKinds[number]> {
   ...
-  return blindCast<TView, 'view is built to the SingleNamespaceView shape the caller parametrizes'>(
+  return blindCast<
+    SingleNamespaceView<TEntries, TBuiltinKinds[number]>,
+    'view is built from entries and builtinKinds'
+  >(
     view,
   );
 }

Also applies to: 79-91

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/1-framework/1-core/framework-components/src/ir/contract-view.ts`
around lines 47 - 50, The helper currently returns an unconstrained TView while
accepting builtinKinds as a plain readonly string array, so the returned view
type can promise materialized builtin properties that may actually be undefined.
Update promoteBuiltinKinds and the related helper in contract-view.ts to take
the builtin kinds as a tuple literal type and derive the return type from that
tuple instead of casting to TView; keep the type relationship aligned so callers
can only request view properties that are guaranteed by the provided
builtinKinds.

Comment thread packages/3-targets/3-targets/postgres/test/postgres-contract-view.test-d.ts Outdated
Comment thread examples/retail-store/migrations/app/20260513T0505_initial/migration.ts Outdated
@wmadden-electric wmadden-electric changed the title feat(tml-2892): add ContractView migration-author accessor feat(tml-2892): read contract tables & collections by name in migrations (ContractView) Jun 26, 2026
Comment thread packages/3-targets/3-targets/postgres/src/core/postgres-contract-view.ts Outdated
Comment thread packages/3-targets/3-targets/postgres/src/core/postgres-contract-view.ts Outdated
Comment thread packages/3-targets/3-targets/sqlite/src/core/sqlite-contract-view.ts Outdated
wmadden-electric added a commit that referenced this pull request Jun 27, 2026
Address review on #879. The view was a class with a private constructor and
a static `from()` returning a separate mapped type — a namespace cosplaying
as a class, and a narrow projection that was not substitutable for Contract.

- Each `<Target>ContractView` is now a plain `{ from, fromJson }` factory;
  the classes are deleted. `fromJson` deserializes via the serializer and
  wraps in one step, so a migration declares the view once.
- View type is now `Contract & accessors` (a superset) — one value serves as
  both the contract and the accessor surface.
- Collision-safe namespace access: contract envelope fields always win at the
  root; every namespace is reachable under `contract.namespace.<id>`;
  non-colliding namespace names are promoted to the root, with a type-level
  exclusion so a schema named like a contract field never shadows it.
- Sync the SQLite test fixture pair on `capabilities.sql`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/1-framework/1-core/framework-components/src/ir/contract-view.ts`:
- Around line 132-145: `buildNamespaceAccessor` is using a plain object for raw
namespace ids, which can drop special keys like `__proto__`, and
`composeContractView` is checking namespace membership with `in`, which can be
confused by inherited properties like `constructor`. Update
`buildNamespaceAccessor` to use a null-prototype dictionary for the `out`
accumulator, and change the namespace lookup in `composeContractView` to
`Object.hasOwn(...)` so only real storage namespaces are considered before
promotion. Keep the existing `promoteBuiltinKinds` and `blindCast` flow intact.

In `@packages/2-mongo-family/9-family/src/core/ir/mongo-contract-view.ts`:
- Around line 15-18: The MongoContractView.fromJson() helper is hardcoded to new
MongoContractSerializer(), which prevents pack-contributed kinds and custom
validator fragments from being hydrated. Update MongoContractView.fromJson() to
accept and pass through a serializer/config parameter (or otherwise make the
method explicitly base-only) so callers can supply the correct serializer for
their contract shape, and keep the generic TContract aligned with the actual
hydration path.

In `@packages/2-mongo-family/9-family/test/mongo-contract-view.test.ts`:
- Around line 45-49: The `MongoContractView.fromJson()` test is asserting the
wrong storage hash location, so update the assertion to check the contract root
field instead of `view.storage.storageHash`. Use the `fromJson()` call in
`mongo-contract-view.test.ts` and compare `view.storageHash` against
`contract.storageHash` to verify the raw round-trip shape is preserved.

In `@packages/3-targets/3-targets/postgres/src/core/postgres-contract-view.ts`:
- Around line 38-43: The fromJson() method in PostgresContractView is hardcoding
a new PostgresContractSerializer(), which drops the serializer extension point
needed for pack-backed contracts. Update PostgresContractView.fromJson() so
callers can provide a configured serializer or equivalent options, and use that
injected serializer for deserializeContract() instead of always constructing the
default one. Keep buildSqlSchemaQualifiedView() as the final wrapping step after
hydration succeeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 292ababd-0d94-4c8b-91ec-3355a35b01bd

📥 Commits

Reviewing files that changed from the base of the PR and between 3a98367 and 5c04541.

📒 Files selected for processing (28)
  • examples/retail-store/migrations/app/20260513T0505_initial/migration.ts
  • examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts
  • examples/retail-store/test/migration.test.ts
  • packages/1-framework/1-core/framework-components/src/exports/ir.ts
  • packages/1-framework/1-core/framework-components/src/ir/contract-view.ts
  • packages/1-framework/1-core/framework-components/test/contract-view.test.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/src/contract-view.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts
  • packages/2-mongo-family/9-family/package.json
  • packages/2-mongo-family/9-family/src/core/ir/mongo-contract-view.ts
  • packages/2-mongo-family/9-family/src/exports/ir.ts
  • packages/2-mongo-family/9-family/test/fixtures/orm-contract.d.ts
  • packages/2-mongo-family/9-family/test/fixtures/orm-contract.json
  • packages/2-mongo-family/9-family/test/mongo-contract-view.test-d.ts
  • packages/2-mongo-family/9-family/test/mongo-contract-view.test.ts
  • packages/2-mongo-family/9-family/tsconfig.test.json
  • packages/2-sql/1-core/contract/src/contract-view.ts
  • packages/2-sql/1-core/contract/src/exports/contract-view.ts
  • packages/3-targets/3-targets/postgres/src/core/postgres-contract-view.ts
  • packages/3-targets/3-targets/postgres/src/exports/runtime.ts
  • packages/3-targets/3-targets/postgres/test/fixtures/collision-contract.ts
  • packages/3-targets/3-targets/postgres/test/postgres-contract-view.test-d.ts
  • packages/3-targets/3-targets/postgres/test/postgres-contract-view.test.ts
  • packages/3-targets/3-targets/sqlite/src/core/sqlite-contract-view.ts
  • packages/3-targets/3-targets/sqlite/src/exports/runtime.ts
  • packages/3-targets/3-targets/sqlite/test/fixtures/sqlite-contract.json
  • packages/3-targets/3-targets/sqlite/test/sqlite-contract-view.test-d.ts
  • packages/3-targets/3-targets/sqlite/test/sqlite-contract-view.test.ts
💤 Files with no reviewable changes (2)
  • packages/3-targets/3-targets/postgres/src/exports/runtime.ts
  • packages/3-targets/3-targets/sqlite/src/exports/runtime.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/2-mongo-family/9-family/tsconfig.test.json
  • packages/2-sql/1-core/contract/src/exports/contract-view.ts
  • packages/2-mongo-family/9-family/test/fixtures/orm-contract.d.ts
  • packages/3-targets/3-targets/sqlite/test/fixtures/sqlite-contract.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts
  • examples/retail-store/migrations/app/20260513T0505_initial/migration.ts

Comment on lines +132 to +145
const out: Record<string, unknown> = {};
for (const [nsId, ns] of Object.entries(storage.namespaces)) {
out[nsId] = promoteBuiltinKinds(
blindCast<
Readonly<Record<string, unknown>>,
'Namespace.entries is the open ADR 224 dictionary Record<string, Record<string, unknown>>'
>(ns.entries),
builtinKinds,
);
}
return blindCast<
TAccessor,
'each namespace projected to its SingleNamespaceView; keys mirror the storage namespace ids'
>(out);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '128,187p' packages/1-framework/1-core/framework-components/src/ir/contract-view.ts

node <<'NODE'
const out = {};
out['__proto__'] = { polluted: true };
console.log('own keys:', Object.keys(out));
console.log('proto polluted:', Object.getPrototypeOf(out).polluted === true);

const root = {};
console.log('constructor via in:', 'constructor' in root);
console.log('constructor own:', Object.hasOwn(root, 'constructor'));
NODE

Repository: prisma/prisma-next

Length of output: 2599


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' packages/1-framework/1-core/framework-components/src/ir/contract-view.ts

printf '\n--- search ---\n'
rg -n "Object\.hasOwn| in root|Object\.create\(null\)|namespaceAccessor|buildNamespaceAccessor|composeContractView" packages/1-framework/1-core/framework-components/src/ir/contract-view.ts

Repository: prisma/prisma-next

Length of output: 7906


Use null-prototype dictionaries for raw namespace ids.

  • buildNamespaceAccessor drops keys like __proto__ on a plain {}.
  • composeContractView should use Object.hasOwn(...) instead of in, otherwise inherited names like constructor block promotion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/1-framework/1-core/framework-components/src/ir/contract-view.ts`
around lines 132 - 145, `buildNamespaceAccessor` is using a plain object for raw
namespace ids, which can drop special keys like `__proto__`, and
`composeContractView` is checking namespace membership with `in`, which can be
confused by inherited properties like `constructor`. Update
`buildNamespaceAccessor` to use a null-prototype dictionary for the `out`
accumulator, and change the namespace lookup in `composeContractView` to
`Object.hasOwn(...)` so only real storage namespaces are considered before
promotion. Keep the existing `promoteBuiltinKinds` and `blindCast` flow intact.

Comment on lines +15 to +18
* const view = MongoContractView.fromJson<Contract>(contractJson);
* view.collection.carts.validator // typed MongoCollection
* view.entries.policy.X // pack-contributed kind (singular key)
* view.storage // the full contract is still present

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== MongoContractSerializer definition =="
fd 'mongo-contract-serializer.ts' packages/2-mongo-family/9-family/src --exec nl -ba

echo
echo "== Serializer base constructor / deserializeContract =="
nl -ba packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts | sed -n '1,140p'

echo
echo "== MongoContractView.fromJson call sites =="
rg -n --type=ts 'MongoContractView\.fromJson<|new MongoContractSerializer\(' packages examples

Repository: prisma/prisma-next

Length of output: 231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files 'packages/2-mongo-family/9-family/src/core/ir/mongo-contract-view.ts' \
             'packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts' \
             'packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer.ts'

echo
echo "== mongo-contract-view.ts =="
sed -n '1,220p' packages/2-mongo-family/9-family/src/core/ir/mongo-contract-view.ts | cat -n

echo
echo "== mongo-contract-serializer-base.ts =="
sed -n '1,240p' packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts | cat -n

echo
echo "== mongo-contract-serializer.ts =="
sed -n '1,220p' packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer.ts | cat -n

echo
echo "== fromJson and serializer construction usages =="
rg -n --type=ts 'fromJson<|new MongoContractSerializer\(' packages/2-mongo-family/9-family/src

Repository: prisma/prisma-next

Length of output: 12658


fromJson() should accept serializer config

It always instantiates new MongoContractSerializer(), so pack-contributed kinds or custom validator fragments cannot be hydrated here even though the type parameter suggests any TContract. Either make this base-only or thread the serializer/config through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/2-mongo-family/9-family/src/core/ir/mongo-contract-view.ts` around
lines 15 - 18, The MongoContractView.fromJson() helper is hardcoded to new
MongoContractSerializer(), which prevents pack-contributed kinds and custom
validator fragments from being hydrated. Update MongoContractView.fromJson() to
accept and pass through a serializer/config parameter (or otherwise make the
method explicitly base-only) so callers can supply the correct serializer for
their contract shape, and keep the generic TContract aligned with the actual
hydration path.

Comment on lines +45 to +49
it('fromJson() deserializes and wraps in one call', () => {
const view = MongoContractView.fromJson<Contract>(contractJson);
expect(view.collection.tasks).toBeDefined();
expect(view.storage.storageHash).toBe(contract.storage.storageHash);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

This fromJson() assertion is checking the wrong storageHash path.

The emitted fixture stores storageHash at the contract root (packages/2-mongo-family/9-family/test/fixtures/orm-contract.json, Line 3), and the PR objective says Contract stays the raw round-trip shape. As written, Line 48 does not verify that fromJson() preserved the actual hash field. Assert view.storageHash against contract.storageHash instead.

Suggested fix
   it('fromJson() deserializes and wraps in one call', () => {
     const view = MongoContractView.fromJson<Contract>(contractJson);
     expect(view.collection.tasks).toBeDefined();
-    expect(view.storage.storageHash).toBe(contract.storage.storageHash);
+    expect(view.storageHash).toBe(contract.storageHash);
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('fromJson() deserializes and wraps in one call', () => {
const view = MongoContractView.fromJson<Contract>(contractJson);
expect(view.collection.tasks).toBeDefined();
expect(view.storage.storageHash).toBe(contract.storage.storageHash);
});
it('fromJson() deserializes and wraps in one call', () => {
const view = MongoContractView.fromJson<Contract>(contractJson);
expect(view.collection.tasks).toBeDefined();
expect(view.storageHash).toBe(contract.storageHash);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/2-mongo-family/9-family/test/mongo-contract-view.test.ts` around
lines 45 - 49, The `MongoContractView.fromJson()` test is asserting the wrong
storage hash location, so update the assertion to check the contract root field
instead of `view.storage.storageHash`. Use the `fromJson()` call in
`mongo-contract-view.test.ts` and compare `view.storageHash` against
`contract.storageHash` to verify the raw round-trip shape is preserved.

Comment on lines +38 to +43
/** Deserialize a Postgres contract JSON envelope and wrap it in a view. */
fromJson<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>(
json: unknown,
): PostgresContractView<TContract> {
const contract = new PostgresContractSerializer().deserializeContract<TContract>(json);
return buildSqlSchemaQualifiedView(contract);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

fromJson() drops the serializer extension point.

This always builds a default PostgresContractSerializer(), so any emitted contract JSON that needs extraPackEntityKinds will fail during hydration before it ever reaches the view layer. Please let callers pass a configured serializer (or equivalent options) here.

Proposed fix
 export const PostgresContractView = {
   /** Wrap an already-deserialized Postgres contract in a schema-qualified view. */
   from<TContract extends Contract<SqlStorage>>(
     contract: TContract,
   ): PostgresContractView<TContract> {
     return buildSqlSchemaQualifiedView(contract);
   },

   /** Deserialize a Postgres contract JSON envelope and wrap it in a view. */
   fromJson<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>(
     json: unknown,
+    serializer: PostgresContractSerializer = new PostgresContractSerializer(),
   ): PostgresContractView<TContract> {
-    const contract = new PostgresContractSerializer().deserializeContract<TContract>(json);
+    const contract = serializer.deserializeContract<TContract>(json);
     return buildSqlSchemaQualifiedView(contract);
   },
 };

Based on learnings, unknown kinds are supposed to fail closed in serializer hydration, and the serializer constructor’s extra-kind hook is the intended extension point for pack-backed contracts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Deserialize a Postgres contract JSON envelope and wrap it in a view. */
fromJson<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>(
json: unknown,
): PostgresContractView<TContract> {
const contract = new PostgresContractSerializer().deserializeContract<TContract>(json);
return buildSqlSchemaQualifiedView(contract);
/** Deserialize a Postgres contract JSON envelope and wrap it in a view. */
fromJson<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>(
json: unknown,
serializer: PostgresContractSerializer = new PostgresContractSerializer(),
): PostgresContractView<TContract> {
const contract = serializer.deserializeContract<TContract>(json);
return buildSqlSchemaQualifiedView(contract);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/3-targets/3-targets/postgres/src/core/postgres-contract-view.ts`
around lines 38 - 43, The fromJson() method in PostgresContractView is
hardcoding a new PostgresContractSerializer(), which drops the serializer
extension point needed for pack-backed contracts. Update
PostgresContractView.fromJson() so callers can provide a configured serializer
or equivalent options, and use that injected serializer for
deserializeContract() instead of always constructing the default one. Keep
buildSqlSchemaQualifiedView() as the final wrapping step after hydration
succeeds.

Source: Learnings

wmadden-electric added a commit that referenced this pull request Jul 1, 2026
Address review on #879. The view was a class with a private constructor and
a static `from()` returning a separate mapped type — a namespace cosplaying
as a class, and a narrow projection that was not substitutable for Contract.

- Each `<Target>ContractView` is now a plain `{ from, fromJson }` factory;
  the classes are deleted. `fromJson` deserializes via the serializer and
  wraps in one step, so a migration declares the view once.
- View type is now `Contract & accessors` (a superset) — one value serves as
  both the contract and the accessor surface.
- Collision-safe namespace access: contract envelope fields always win at the
  root; every namespace is reachable under `contract.namespace.<id>`;
  non-colliding namespace names are promoted to the root, with a type-level
  exclusion so a schema named like a contract field never shadows it.
- Sync the SQLite test fixture pair on `capabilities.sql`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric force-pushed the tml-2892-migration-author-accessor-for-contract-entities-kill-the branch from 5c04541 to d55131d Compare July 1, 2026 12:48
@wmadden-electric wmadden-electric changed the title feat(tml-2892): read contract tables & collections by name in migrations (ContractView) feat(tml-2892): contract-JSON-driven Migration base + typed contract views Jul 1, 2026
wmadden-electric added a commit that referenced this pull request Jul 1, 2026
Address review on #879. The view was a class with a private constructor and
a static `from()` returning a separate mapped type — a namespace cosplaying
as a class, and a narrow projection that was not substitutable for Contract.

- Each `<Target>ContractView` is now a plain `{ from, fromJson }` factory;
  the classes are deleted. `fromJson` deserializes via the serializer and
  wraps in one step, so a migration declares the view once.
- View type is now `Contract & accessors` (a superset) — one value serves as
  both the contract and the accessor surface.
- Collision-safe namespace access: contract envelope fields always win at the
  root; every namespace is reachable under `contract.namespace.<id>`;
  non-colliding namespace names are promoted to the root, with a type-level
  exclusion so a schema named like a contract field never shadows it.
- Sync the SQLite test fixture pair on `capabilities.sql`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric force-pushed the tml-2892-migration-author-accessor-for-contract-entities-kill-the branch from d55131d to 0f9e4ef Compare July 1, 2026 12:53
Comment thread packages/1-framework/3-tooling/migration/src/migration-base.ts Outdated
Comment thread packages/1-framework/3-tooling/migration/test/migration-base.test.ts Outdated
Comment thread packages/2-mongo-family/9-family/tsconfig.test.json Outdated
Comment thread packages/3-mongo-target/1-mongo-target/src/core/render-typescript.ts Outdated
Comment thread packages/3-targets/3-targets/sqlite/src/core/migrations/sqlite-migration.ts Outdated
Comment thread scripts/codemod-migration-shape.mjs Outdated
wmadden-electric added a commit that referenced this pull request Jul 1, 2026
…rop codemod

Address wmadden's review on #879:
- inline the minimal `{ storage: { storageHash } }` field shape (drop the vague
  `ContractHashInput` name), matching the existing repo idiom
- drop stray `async` from `describe()` test blocks
- typecheck family-mongo's tests via its main tsconfig like sibling packages
  (fix the pre-existing fixture type errors that surfaced); delete the
  special-case `tsconfig.test.json`
- rewrite the mongo `render-typescript` doc comment to current behavior only
- extract the lazy view-getter machinery into a shared `MigrationContractViews`
  helper (one copy, not three) with a structured `MIGRATION.CONTRACT_VIEW_MISSING`
  error instead of a bare `Error`
- remove the one-shot `codemod-migration-shape.mjs`

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric force-pushed the tml-2892-migration-author-accessor-for-contract-entities-kill-the branch from 91a1a4d to 1ab9dc1 Compare July 1, 2026 14:53
@wmadden wmadden enabled auto-merge July 1, 2026 14:54
@wmadden-electric wmadden-electric force-pushed the tml-2892-migration-author-accessor-for-contract-entities-kill-the branch from 1ab9dc1 to 781350d Compare July 2, 2026 05:45
wmadden-electric and others added 9 commits July 2, 2026 07:59
Migration authors reached contract entities through internal coordinates
(`storage.namespaces.__unbound__.entries.collection.<name>`), leaking the
`__unbound__` sentinel, the `entries` wrapper, and the kind key. Add a
per-target `ContractView` that presents entities by name with the default
namespace unwrapped, while keeping `Contract` itself a faithful low-level
mirror of the serialized form (no emitter/serializer/Contract-type changes).

- Shared generic projection core in `framework-components`
  (`SingleNamespaceView` + `buildSingleNamespaceView` + `promoteBuiltinKinds`):
  promotes a family's statically-named built-in kind slots to typed
  top-level accessors, leaving pack-contributed kinds under `.entries`
  keyed by the registered singular kind string.
- `MongoContractView.from(c)` — single namespace unwrapped: `cv.collection.<name>`.
- `SqliteContractView.from(c)` — single namespace unwrapped: `cv.table.<name>`,
  `cv.valueSet.<name>`.
- `PostgresContractView.from(c)` — schema-qualified, mirroring the `sql.<ns>`
  facade: `cv.<ns>.table.<name>`, `cv.<ns>.entries.policy.<name>`.

Each view's factory is generic over the contract type, so access is fully
typed against the emitted contract. The retail-store example migrations are
repointed off the leaked path. Types are proven via emit-then-consume tests
against real emitted contract `.d.ts` fixtures (incl. a multi-schema Postgres
contract with a colliding bare table name).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The retail-store example repoint triggers check:upgrade-coverage. The
ContractView accessor is additive and the old raw path still works, so no
consumer migration is required; record an incidental-diff note with
changes: [] in the 0.14-to-0.15 transition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Address review on #879. The view was a class with a private constructor and
a static `from()` returning a separate mapped type — a namespace cosplaying
as a class, and a narrow projection that was not substitutable for Contract.

- Each `<Target>ContractView` is now a plain `{ from, fromJson }` factory;
  the classes are deleted. `fromJson` deserializes via the serializer and
  wraps in one step, so a migration declares the view once.
- View type is now `Contract & accessors` (a superset) — one value serves as
  both the contract and the accessor surface.
- Collision-safe namespace access: contract envelope fields always win at the
  root; every namespace is reachable under `contract.namespace.<id>`;
  non-colliding namespace names are promoted to the root, with a type-level
  exclusion so a schema named like a contract field never shadows it.
- Sync the SQLite test fixture pair on `capabilities.sql`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ction (#834)

Retire the bespoke collision design and reuse the established namespace
projection from #834: lift the duplicated `unboundNamespace` helper into
framework-components and repoint the sqlite/mongo runtimes onto it; expose
schemas under a single `namespace` member (`NamespacedEntities`, mirroring
`NamespacedEnums`) so a schema named like a contract field can never shadow
it. Single-namespace targets keep flat root kind accessors; Postgres is
reached via `view.namespace.<schema>`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…+ exposes views

The `Migration` base now accepts the start/end contract JSON as typed inputs
(`startContractJson`/`endContractJson`) instead of hand-written from/to hashes:
`describe()` derives `{from, to}` from each contract's `storage.storageHash`
(byte-identical to the previous literals). The Mongo family base exposes lazy,
typed `this.startContract`/`this.endContract` ContractViews built from those
JSON inputs — so a migration gets typed, convenient contract access for free.

`describe()` becomes concrete-with-derive (throws if no JSON and no override),
so extension migrations that override it and carry no contract keep working.
retail-store converted to the new shape; migration.json/ops.json unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ract views

Mirror D-A for the SQL family: thread `<Start, End>` generics through
`SqlMigration` to the framework base, and add lazy memoized `startContract`/
`endContract` getters on `SqliteMigration` and `PostgresMigration`, each
returning its own ContractView shape (`SqliteContractView` flat;
`PostgresContractView` schema-qualified via `.namespace.<schema>`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…op describe()

The three `render-typescript` generators now emit migrations that import their
start/end contract JSON + types and extend `Migration<Start, End>` (baseline
`Migration<never, End>`), instead of a hand-written `describe()` with inlined
hashes. The base derives `describe()` from the JSON; the typed `startContract`/
`endContract` views are available on every generated migration.

Inlined operations are unchanged. The JSON default-import reuses the same
symbol the dataTransform op already imports, so the two merge into one import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…e new migration shape

Close out the migration-shape redesign:
- correct the stale Mongo `render-typescript` doc comment (describe() is now
  derived from contract JSON, not rendered)
- `regen-example-migrations` tolerates the new shape (no from/to hash literals
  to rewrite — they derive from the imported JSON)
- `no-bare-cast` Biome plugin anchors on `TsAsExpression` so `import type {
  Contract as End }` aliases the generator emits are no longer flagged as casts
  (repo cast count 1135 -> 1062; branch delta 0)
- integration journey-helper matches the new `Migration<Start,End>` scaffold
- record the extension-author upgrade-coverage entry for the unboundNamespace lift

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…N shape

Every example migration (70) now uses the generator's contract-JSON shape —
contract-JSON imports, `Migration<Start, End>` (baseline `<never, End>`), and a
base-derived `describe()` — replacing the hand-written `describe()` hashes. The
`operations` bodies are preserved verbatim, so `ops.json`/`migration.json` are
byte-identical; the regeneration changes only the authored scaffold.

The one data migration (retail-store backfill) keeps its hand-authored
`dataTransform` reading `this.endContract` — the typed view's real consumer.
Fixture migrations that lacked a `start-contract` gain one derived from their
DAG predecessor's end-contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric and others added 3 commits July 2, 2026 08:02
…import symbol

Post-rebase reconciliation onto main's #880:
- convert its new `20260628T0931` retail-store migration (pure-schema) to the
  contract-JSON shape (ops.json/migration.json byte-identical)
- rename the JSON-import symbol `endContractJson` -> `endContract` on the two
  original retail-store migrations to match the generator's exact emission

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…rop codemod

Address wmadden's review on #879:
- inline the minimal `{ storage: { storageHash } }` field shape (drop the vague
  `ContractHashInput` name), matching the existing repo idiom
- drop stray `async` from `describe()` test blocks
- typecheck family-mongo's tests via its main tsconfig like sibling packages
  (fix the pre-existing fixture type errors that surfaced); delete the
  special-case `tsconfig.test.json`
- rewrite the mongo `render-typescript` doc comment to current behavior only
- extract the lazy view-getter machinery into a shared `MigrationContractViews`
  helper (one copy, not three) with a structured `MIGRATION.CONTRACT_VIEW_MISSING`
  error instead of a bare `Error`
- remove the one-shot `codemod-migration-shape.mjs`

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
ADR 232 records that a migration is authored against its start/end contract
snapshots (identity derived from storage.storageHash; schema generated, data
transforms hand-authored). ADR 233 records ContractView as a standalone typed,
by-name accessor over a contract (superset via from/fromJson; schemas under a
`namespace` member; projection shared with the runtime enums surface). The two
cross-reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric force-pushed the tml-2892-migration-author-accessor-for-contract-entities-kill-the branch from 781350d to eb2c369 Compare July 2, 2026 06:16
@wmadden wmadden added this pull request to the merge queue Jul 2, 2026
Merged via the queue into main with commit 5c898e7 Jul 2, 2026
21 checks passed
@wmadden wmadden deleted the tml-2892-migration-author-accessor-for-contract-entities-kill-the branch July 2, 2026 06:43
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