tml-2994: BetterAuth extension — managed contract space, contract-typed adapter, example#968
tml-2994: BetterAuth extension — managed contract space, contract-typed adapter, example#968SevInf wants to merge 33 commits into
Conversation
📝 WalkthroughWalkthroughAdds a managed BetterAuth contract-space extension with Postgres migrations, a contract-typed adapter, shared-pool integration, branded cross-space handles, an end-to-end example, and adapter, lifecycle, and authentication tests. ChangesBetterAuth extension and contract space
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/extension-better-auth
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (4)
packages/3-extensions/better-auth/test/contract-handles.test.ts (2)
70-83: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueBrand assertions via
expectTypeOfmay pass even if branding is lost.
toEqualTypeOf<TargetFieldRef<...>>()on branded refs can produce false positives becauseexpectTypeOfcan erase brand information. Consider an identity-check (Equal<A,B>) pattern to preserve the brand in the comparison.Based on learnings, for branded types prefer a type-level identity check over
expectTypeOfsince the latter can erase brand info and produce false positives.🤖 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-extensions/better-auth/test/contract-handles.test.ts` around lines 70 - 83, Replace the branded-type assertions in the User.refs.id, Session.refs.id, Account.refs.id, and Verification.refs.id tests with an identity-check pattern such as Equal<A, B>, ensuring the comparison preserves the better-auth brand. Keep the existing runtime spaceId, namespaceId, and tableName assertions unchanged.Source: Learnings
28-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCross-package import reaches into another package's test source via a relative path.
../../../2-sql/1-core/contract/test/test-supportreaches into the2-sql/1-core/contractpackage's internals from this extension package. Prefer importingcreateTestSqlNamespacethrough that package's published export surface rather than a relative path into itstest/tree.Based on learnings, cross-package imports should use the other package's published package name/export entrypoints rather than relative paths like
../../../<other-package>/....#!/bin/bash # Find the source of createTestSqlNamespace and whether it is exported via a package entrypoint. rg -nP 'createTestSqlNamespace' -g '!**/contract-handles.test.ts' -C2 fd -t f 'package.json' packages/2-sql/1-core/contract --exec sh -c 'echo "== {} =="; jq -r ".name, (.exports|keys[]?)" {}'🤖 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-extensions/better-auth/test/contract-handles.test.ts` at line 28, Replace the relative import of createTestSqlNamespace in contract-handles.test.ts with an import from the 2-sql/1-core/contract package’s published name or export entrypoint. Use the package’s existing export surface for this symbol and do not reach into its contract/test directory.packages/3-extensions/better-auth/src/adapter/model-map.ts (1)
102-116: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueRelation resolution only reads the first local/target field, silently dropping composite keys.
localField/targetFieldtake only[0]oflocalFields/targetFieldswith an empty-string fallback. For this space's single-column FKs (userId→id) this is fine, but a future composite-key relation would silently resolve against a partial (or empty-string) key rather than failing.🤖 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-extensions/better-auth/src/adapter/model-map.ts` around lines 102 - 116, The relationsBySpaceModel mapping currently reduces localFields and targetFields to their first entries, losing composite-key relations. Update the relation construction to validate that each relation has exactly one local and target field, preserving those values for single-column foreign keys and explicitly rejecting or otherwise handling composite or empty field arrays instead of using partial or empty-string keys.examples/better-auth/migrations/app/20260713T0837_init/migration.ts (1)
24-35: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRedundant index on
userId.Adding the
UNIQUEconstraint onuserId(Lines 24-29) already creates an implicit unique b-tree index on that column in Postgres. The subsequentcreateIndexon the same single column (Lines 30-35) is redundant — it adds storage and write-maintenance cost on every insert/update without any additional query-planning benefit, since the unique index already covers equality/range lookups onuserId.♻️ Proposed fix: drop the redundant index
this.addUnique({ schema: 'public', table: 'profile', constraint: 'profile_userId_key', columns: ['userId'], }), - this.createIndex({ - schema: 'public', - table: 'profile', - index: 'profile_userId_idx', - columns: ['userId'], - }), this.addForeignKey({If this scaffold intentionally pairs FK columns with an explicit index regardless of an existing covering unique constraint (e.g. so the index survives if the unique constraint is later dropped), that convention would be worth confirming against other migrations in the repo.
🤖 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 `@examples/better-auth/migrations/app/20260713T0837_init/migration.ts` around lines 24 - 35, Remove the redundant createIndex operation for profile.userId from the migration, while preserving the addUnique constraint profile_userId_key. Do not add a replacement index unless the repository’s migration convention explicitly requires one.
🤖 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 `@architecture.config.json`:
- Around line 519-542: Add architecture entries for the missing better-auth
adapter and runtime surfaces: register src/adapter/**, src/exports/adapter.ts,
src/runtime/**, and src/exports/runtime.ts with the same extensions, adapters,
and shared classifications used by the existing pack and contract entries.
In `@packages/3-extensions/better-auth/package.json`:
- Around line 58-72: Update the "./pack" entry in the package exports map to use
explicit "types" and "import" conditions, pointing to the appropriate
declaration and runtime files, matching the structure of "./contract",
"./adapter", and "./runtime".
In `@packages/3-extensions/better-auth/src/adapter/index.ts`:
- Around line 212-216: Update updateMany to pass its update payload through the
same asRecord guard used by update before calling assertKnownFields, preserving
the typed PrismaNextAdapterError with INVALID_OPERATOR_VALUE for non-object
payloads. Keep the existing model resolution, field validation, and scoped
updateCount flow unchanged.
- Around line 112-131: Update the singular operation paths for update, delete,
and consumeOne to return early when where is undefined or empty, preventing
execution against the unscoped collection. Keep scopeToWhere’s existing
raw-collection behavior and the bulk method behavior unchanged, and anchor the
changes to the singular operation implementations near scopeToWhere.
In `@packages/3-extensions/better-auth/src/adapter/model-map.ts`:
- Around line 1-4: Validate the imported contractJson at runtime before
fieldsBySpaceModel and relationsBySpaceModel consume it. Import the established
validateContract helper and pass contractJson through
validateContract<Contract>(...) using the fully typed Contract, then use the
validated result for both model/field and relation resolution while preserving
the existing SpaceModelName typing.
In `@packages/3-extensions/better-auth/src/contract/handles.ts`:
- Around line 21-97: Update the field definitions in User, Session, and Account
to mark every contract-nullable column as optional using the existing
nullability marker, including User.image, Session.ipAddress and userAgent, and
the nullable Account fields. Leave required identifiers and timestamps
unchanged, and ensure the handles match the nullability represented in
contract.json.
In `@packages/3-extensions/better-auth/test/adapter-crud.test.ts`:
- Around line 1-2: Update the file-header descriptions in
packages/3-extensions/better-auth/test/adapter-crud.test.ts lines 1-2 and
packages/3-extensions/better-auth/test/adapter-advanced.test.ts lines 1-2 to
identify the tests as using the PostgreSQL dev server rather than PGlite; no
other test behavior changes are needed.
In `@test/integration/test/extension-better-auth.harness.helpers.ts`:
- Around line 39-112: Update setupBetterAuthTestApp to guard all setup work
after database and tempRoot creation with cleanup on failure, ensuring
database.close() is attempted and tempRoot is removed before rethrowing the
original error. In the returned teardown method, make the bounded database-close
wait non-rejecting so rmSync(tempRoot, ...) always executes even when
database.close() rejects, while preserving the existing timeout behavior.
---
Nitpick comments:
In `@examples/better-auth/migrations/app/20260713T0837_init/migration.ts`:
- Around line 24-35: Remove the redundant createIndex operation for
profile.userId from the migration, while preserving the addUnique constraint
profile_userId_key. Do not add a replacement index unless the repository’s
migration convention explicitly requires one.
In `@packages/3-extensions/better-auth/src/adapter/model-map.ts`:
- Around line 102-116: The relationsBySpaceModel mapping currently reduces
localFields and targetFields to their first entries, losing composite-key
relations. Update the relation construction to validate that each relation has
exactly one local and target field, preserving those values for single-column
foreign keys and explicitly rejecting or otherwise handling composite or empty
field arrays instead of using partial or empty-string keys.
In `@packages/3-extensions/better-auth/test/contract-handles.test.ts`:
- Around line 70-83: Replace the branded-type assertions in the User.refs.id,
Session.refs.id, Account.refs.id, and Verification.refs.id tests with an
identity-check pattern such as Equal<A, B>, ensuring the comparison preserves
the better-auth brand. Keep the existing runtime spaceId, namespaceId, and
tableName assertions unchanged.
- Line 28: Replace the relative import of createTestSqlNamespace in
contract-handles.test.ts with an import from the 2-sql/1-core/contract package’s
published name or export entrypoint. Use the package’s existing export surface
for this symbol and do not reach into its contract/test directory.
🪄 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: 9ac67805-bbc2-48e4-bdc5-3a2267614c1d
⛔ Files ignored due to path filters (24)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprojects/extension-better-auth/plan.mdis excluded by!projects/**projects/extension-better-auth/qa/manual-qa.mdis excluded by!projects/**projects/extension-better-auth/qa/qa-run-2026-07-13.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/01-scaffold-and-contract-space.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/02-managed-space-lifecycle-test.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/03-branded-contract-handles-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/03-branded-contract-handles.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/04-adapter-core-crud-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/04-adapter-core-crud.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/05-adapter-consume-transaction-join.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/06-conformance-and-e2e-integration-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/06-conformance-and-e2e-integration-r3.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/06-conformance-and-e2e-integration.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/07-example-app-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/07-example-app-r3.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/07-example-app.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/08-docs-and-close-out-prep-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/08-docs-and-close-out-prep-r3.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/08-docs-and-close-out-prep.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/plan.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/spec.mdis excluded by!projects/**projects/extension-better-auth/spec.mdis excluded by!projects/**projects/extension-better-auth/trace.jsonlis excluded by!projects/**
📒 Files selected for processing (76)
.agents/rules/contract-space-package-layout.mdcarchitecture.config.jsonbiome.jsoncdocs/architecture docs/adrs/ADR 212 - Contract spaces.mddocs/architecture docs/adrs/ADR 231 - Stringly-typed third-party interfaces adapt over contract-typed collections.mddocs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.mdexamples/better-auth/.gitignoreexamples/better-auth/README.mdexamples/better-auth/biome.jsoncexamples/better-auth/migrations/app/20260713T0837_init/end-contract.d.tsexamples/better-auth/migrations/app/20260713T0837_init/end-contract.jsonexamples/better-auth/migrations/app/20260713T0837_init/migration.jsonexamples/better-auth/migrations/app/20260713T0837_init/migration.tsexamples/better-auth/migrations/app/20260713T0837_init/ops.jsonexamples/better-auth/migrations/better-auth/20260713T0717_create_auth_tables/migration.jsonexamples/better-auth/migrations/better-auth/20260713T0717_create_auth_tables/ops.jsonexamples/better-auth/migrations/better-auth/contract.d.tsexamples/better-auth/migrations/better-auth/contract.jsonexamples/better-auth/migrations/better-auth/refs/head.jsonexamples/better-auth/package.jsonexamples/better-auth/prisma-next.config.tsexamples/better-auth/src/auth.tsexamples/better-auth/src/main.tsexamples/better-auth/src/prisma/contract.d.tsexamples/better-auth/src/prisma/contract.jsonexamples/better-auth/src/prisma/contract.tsexamples/better-auth/src/prisma/db.tsexamples/better-auth/src/server.tsexamples/better-auth/test/example.integration.test.tsexamples/better-auth/tsconfig.jsonexamples/better-auth/vitest.config.tspackages/3-extensions/better-auth/README.mdpackages/3-extensions/better-auth/biome.jsoncpackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/end-contract.d.tspackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/end-contract.jsonpackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/migration.jsonpackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/migration.tspackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/ops.jsonpackages/3-extensions/better-auth/migrations/refs/head.jsonpackages/3-extensions/better-auth/package.jsonpackages/3-extensions/better-auth/prisma-next.config.tspackages/3-extensions/better-auth/src/adapter/db-surface.tspackages/3-extensions/better-auth/src/adapter/errors.tspackages/3-extensions/better-auth/src/adapter/index.tspackages/3-extensions/better-auth/src/adapter/join.tspackages/3-extensions/better-auth/src/adapter/model-map.tspackages/3-extensions/better-auth/src/adapter/where.tspackages/3-extensions/better-auth/src/contract/contract.d.tspackages/3-extensions/better-auth/src/contract/contract.jsonpackages/3-extensions/better-auth/src/contract/contract.prismapackages/3-extensions/better-auth/src/contract/handles.tspackages/3-extensions/better-auth/src/exports/adapter.tspackages/3-extensions/better-auth/src/exports/contract.tspackages/3-extensions/better-auth/src/exports/pack.tspackages/3-extensions/better-auth/src/exports/runtime.tspackages/3-extensions/better-auth/src/pack/index.tspackages/3-extensions/better-auth/src/runtime/descriptor.tspackages/3-extensions/better-auth/test/adapter-advanced.test.tspackages/3-extensions/better-auth/test/adapter-crud.test.tspackages/3-extensions/better-auth/test/adapter-errors.test.tspackages/3-extensions/better-auth/test/adapter-types.test-d.tspackages/3-extensions/better-auth/test/contract-handles.test.tspackages/3-extensions/better-auth/test/contract-types.test-d.tspackages/3-extensions/better-auth/test/descriptor.test.tspackages/3-extensions/better-auth/test/runtime-descriptor.test.tspackages/3-extensions/better-auth/tsconfig.jsonpackages/3-extensions/better-auth/tsconfig.prod.jsonpackages/3-extensions/better-auth/tsdown.config.tspackages/3-extensions/better-auth/vitest.config.tstest/integration/package.jsontest/integration/test/extension-better-auth.betterauth-e2e.test.tstest/integration/test/extension-better-auth.conformance.e2e.test.tstest/integration/test/extension-better-auth.harness.helpers.tstest/integration/test/extension-better-auth.lifecycle.e2e.test.tstest/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/better-auth-lifecycle/contract.tstest/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/better-auth-lifecycle/prisma-next.config.with-db.ts
| /** | ||
| * Behavioural coverage for `prismaNextAdapter` over a real PGlite database: |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm createDevDatabase provisions a real Postgres dev server (not PGlite).
rg -nP 'export (async )?function createDevDatabase|pglite|prisma/dev|`@electric-sql/pglite`' test/utils -C2Repository: prisma/prisma-next
Length of output: 2646
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n-- adapter-crud.test.ts --\n'
sed -n '1,12p' packages/3-extensions/better-auth/test/adapter-crud.test.ts
printf '\n-- adapter-advanced.test.ts --\n'
sed -n '1,12p' packages/3-extensions/better-auth/test/adapter-advanced.test.ts
printf '\n-- createDevDatabase implementation --\n'
sed -n '1,120p' test/utils/src/exports/index.tsRepository: prisma/prisma-next
Length of output: 4766
File headers should say PostgreSQL dev server, not PGlite. Both suites use createDevDatabase() from @prisma-next/test-utils, which starts @prisma/dev’s Postgres dev server.
packages/3-extensions/better-auth/test/adapter-crud.test.ts#L1-L2packages/3-extensions/better-auth/test/adapter-advanced.test.ts#L1-L2
📍 Affects 2 files
packages/3-extensions/better-auth/test/adapter-crud.test.ts#L1-L2(this comment)packages/3-extensions/better-auth/test/adapter-advanced.test.ts#L1-L2
🤖 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-extensions/better-auth/test/adapter-crud.test.ts` around lines 1 -
2, Update the file-header descriptions in
packages/3-extensions/better-auth/test/adapter-crud.test.ts lines 1-2 and
packages/3-extensions/better-auth/test/adapter-advanced.test.ts lines 1-2 to
identify the tests as using the PostgreSQL dev server rather than PGlite; no
other test behavior changes are needed.
Source: Learnings
| * lowers to a real cross-space FK onto `"public"."user"(id)` — created by | ||
| * `db init` alongside the pack's own tables. | ||
| */ | ||
| const Profile = model('Profile', { |
| * Contract-space view for the BetterAuth adapter (`User`, `Session`, | ||
| * `Account`, `Verification` collections). | ||
| */ | ||
| readonly authDb: AuthDb; |
There was a problem hiding this comment.
Shouldn't AuthDb already be a subset of Db? Why do we need a separate one? We shouldn't need a separate insteance of DB for that
…he shared pool Operator direction on PR #968, both design comments: PSL: the app contract moves from TS builders to contract.prisma using the cross-space reference grammar (user better-auth:public.User @relation(..., onDelete: Cascade), supabase example precedent). The lowering is identical — same storage hash, byte-identical emitted contract.json — so the committed migrations and mirrors stay valid. One client: db.ts constructs a single postgres() client over the aggregate on an app-owned pool; auth.ts hands the same pool to prismaNextAdapter({ pg }); the space view is no longer user-visible ceremony. /api/me returns the session and its user through BetterAuth plus the Profile through the ORM; the cascade proof observes the cross-space FK at the database level through the shared pool. READMEs retell the one-client story with the aggregate-no-folding explanation moved to background. Harness setup now releases the dev database and temp dir on mid-setup failure, and teardown never rejects (bounded close, rmSync in finally). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ntract space PSL-authored contract space defining the four BetterAuth core models — User, Session, Account, Verification — as singular tables in the public namespace: text ids, timestamptz timestamps, unique(user.email), unique(session.token), and navigable N:1 relations Session.user / Account.user. Managed control policy (contract default): the framework owns the tables DDL via the planner-emitted baseline migration (Path A, ADR 212), head pinned in migrations/refs/head.json. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…xports The pack JSON-imports the emitted artefacts via contractSpaceFromJson (pgvector precedent) and publishes the managed space under spaceId better-auth. Descriptor tests pin the four core tables, uniques, FKs, navigable N:1 relations, baseline migration reachability, and descriptor self-consistency; type-level tests pin the contract.d.ts model surface. /pack stays tree-shaken: its only runtime import is contractSpaceFromJson. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Same extensions/adapters slots as the supabase extension: pack + contract sources and their exports files, all on the shared plane. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…Glite In-process CLI journey for an app that lists the better-auth pack in extensionPacks: contract emit → migration plan (seeds the descriptor- shipped baseline into migrations/better-auth/) → db init creates user/session/account/verification with the unique constraints (user.email, session.token) and FKs (session.userId → user.id, account.userId → user.id) asserted via catalog introspection, with the space marker at the published head → db update at head plans zero operations and reports no-op → db verify clean, including --strict. No manual SQL participates in the lifecycle; raw queries are read-only catalog introspection. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
User, Session, Account, Verification handles branded spaceId better-auth with public-namespace singular-table coordinates (supabase handles.ts precedent). Tests pin the brand/coordinates (incl. the type-level TargetFieldRef brand), prove cross-space FK lowering through the public authoring surface (defineContract + rel.belongsTo + constraints.foreignKey onto User.refs.id → storage FK with spaceId/namespace/table/column resolved and the cross-space domain relation), and lock handle↔contract.json consistency so handle drift against the emitted space fails the suite. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The consistency loop now compares whole per-column codecId maps between each handle and the shipped contract.json domain, not just column-name sets — a handle column whose codec disagrees with the emitted space (e.g. emailVerified declared pg/text@1 instead of pg/bool@1) fails the suite. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ollections /adapter exposes prismaNextAdapter(db) on createAdapterFactory (better-auth ^1.5 peer, 1.6.23 dev): create/findOne/findMany/update/ updateMany/delete/deleteMany/count execute exclusively through the better-auth space ORM collections, so values cross the seam through contract codecs (Date↔timestamptz, boolean↔bool proven against PGlite). BetterAuth where operators (eq/ne/lt/lte/gt/gte/in/not_in/contains/ starts_with/ends_with with escaped LIKE patterns) and AND/OR connector folding translate onto typed accessor comparators; sortBy/limit/offset map to orderBy/take/skip. The model map is satisfies-bound to Record<SpaceModelName, string>, so a contract model without a mapping (or vice versa) fails pnpm typecheck. BetterAuthDb is the minimal structural collection surface — an ordinary PostgresClient over an aggregate including the space is assignable (pinned by type-level test). Factory-admitted surfaces the space does not define (plugin tables, additionalFields, relation names as fields, trait-unsupported operators, insensitive mode) fail fast with PrismaNextAdapterError naming the surface. Config is honest: supportsNumericIds false, transaction false until the native consumeOne/transaction wiring lands. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
src/adapter/index.ts no longer re-exports sibling modules (golden rule: no reexports outside exports/ folders); src/exports/adapter.ts now aggregates errors, db-surface, model-map, and the factory directly. Tests consume the public exports surface. No behaviour change. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…cked joins consumeOne rides Collection.delete() — the atomic find-first + identity-narrowed DELETE…RETURNING — so concurrent consumers of one verification row get exactly one winner (8-way race over PGlite, repeated rounds). The transaction config opens db.transaction() and rebinds the adapter to the transaction scope collections via a nested factory instance (reference-adapter pattern), so a flow that throws after writes rolls back atomically; config no longer declares transaction: false. join on findOne/findMany resolves each JoinConfig entry against the contract space navigable relations (joined model + from/to columns must all match) and chains Collection.include(), so with experimental joins the factory receives natively-joined rows — collection spies prove the joined model collection is never queried separately — and a join the contract cannot express throws a typed UNKNOWN_JOIN_RELATION error. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
CARRY-OVER 2 (pre-authorized): BetterAuth conformance join coverage demands reverse one-to-many joins (user → session/account). The space PSL gains list backrelations named after BetterAuth model names so the native join path lands joined rows under the keys the factory expects; storage hash is unchanged (domain-only change — no migration churn). The adapter honours JoinConfig.limit on to-many joins via the include refinement take() (BetterAuth default cap 100); to-one joins ignore limit per the factory contract. The unmapped-join typed error is now pinned at the join-resolution seam (user → verification), since the factory-admitted core joins are all contract-expressible after this change. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ntegration The real consumer path is green and always-on: a betterAuth() instance over prismaNextAdapter and a PGlite db migrated via the framework CLI path signs up with email/password, persists user/account/session rows readable through the contract-typed collections, retrieves the session from the sign-up cookie, and signs back in. The official conformance harness (normal/joins/auth-flow/transactions suites, non-goal tests disabled via the harness disableTests mechanism with per-test reasons) is wired but gated behind BETTER_AUTH_CONFORMANCE=1: three surfaced findings block a green run — missing ON DELETE CASCADE FK semantics in the space (harness cleanup relies on BetterAuth canonical DDL), undecoded codec values in the ORM to-many include payloads (framework decode boundary), and an upstream @better-auth/test-utils wrapper bug that drops provided transaction implementations. Findings documented in the file header; 136→9 of 225 harness tests were driven green during triage before the remaining blockers were isolated as out-of-dispatch-scope. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
BetterAuth canonical schema declares ON DELETE CASCADE on both user references (session.userId, account.userId); the space shipped plain FKs, so deleting a user with live sessions/accounts violated the constraints instead of cascading. Add onDelete: Cascade to the PSL, re-emit the space contract (storage hash changes — the authorized space exception), and regenerate the baseline migration + head ref. The lifecycle test now asserts the referential action in its whole-shape pg_constraint introspection; the descriptor test pins onDelete: cascade on both FK entries. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…sable categories Remove the BETTER_AUTH_CONFORMANCE=1 env gate: the suite now runs in every pnpm test:integration sweep. Disabled tests fall into exactly four documented categories — non-goal surfaces (plugin tables, additionalFields, renamed models/fields), the harness generateId leak, decode-dependent native joins (TML-3015; the same-named normal-suite tests pass through the fallback join path), and the upstream transaction-wrapper bug (rollback coverage lives in the extension package tests). 156 tests pass, 69 skip, none fail. Harness stability for the long run: keep dev-server idle timeout at 600s so the 1s default stops reaping pooled connections between tests (9 async "Connection terminated unexpectedly" errors), and bound database.close() in teardown — after the full suite volume the dev server close never resolves (small-run teardown returns promptly; tracked as a TML-2995 finding), and the per-file worker exit reclaims the in-memory PGlite either way. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…unMigrations is a no-op The teardown comment cited TML-2995 for the @prisma/dev close-hang; the dedicated ticket is TML-3017. The top docblock claimed runMigrations re-runs db init — it is an intentional no-op because db init already walked the space to head at module scope; say what the code does. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…n construct
An app whose aggregate contract lists the better-auth pack could not
construct a postgres() client: the runtime requirement check rejects
the contract ("Contract requires extension pack better-auth") and the
control-plane pack descriptor is not assignable to the extensions
option (no codecs slot). Every sibling schema-contributing pack
(supabase, pgvector, postgis, paradedb) ships a /runtime export for
exactly this; add the better-auth equivalent — descriptor only, no
client facade (apps use plain postgres({ extensions })). The package
test pins the fails-iff surface: construction over an aggregate
requiring the pack throws without the descriptor and succeeds with it.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
A minimal real app: better-auth pack in the config, an app Profile model with a cross-space cascading FK onto the branded User handle, betterAuth() over prismaNextAdapter, and a node:http server exposing the auth handler plus one authenticated endpoint reading the session and traversing Profile → user. Client construction resolves the D7 flag through the public surface: two typed views over one shared pool — the aggregate client passes the pack /runtime descriptor via extensions (without it postgres() rejects the contract), and the adapter rides the contract-space view with marker verification left to the aggregate client. The README documents why: the aggregate records pack models as cross-space references, not navigable domain models, so db.orm.public.User does not exist on the aggregate and Profile.user is not include()-able across spaces today. The integration test is the CI surface for the README: emit determinism against the committed aggregate, offline integrity of both committed migration spaces, real CLI db init on a fresh dev database, the FK whole-shape in pg_constraint, sign-up → authenticated /api/me through the real server, and the FK cascade observed through the ORM. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The docblock and test name said "ORM join through the aggregate client", contradicting server.ts and the README: cross-space relations are not include()-able today; the server follows the FK explicitly across the two typed views. Strings only, no behaviour change. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…, precedent references Package README leads with the two-views consumer architecture (the aggregate does not fold pack domain models; cross-space relations are typed never; hence aggregate db + space-view authDb with verifyMarker false), then the four subpath surfaces, the space schema, the three-step flow, and the adapter posture — every claim written from the shipped source. ADR 212 gains an amendment (house inline-amendment convention): the src/contract/ grouped layout is an accepted variant for PSL-authored spaces (supabase/better-auth shape; the regen script resolves both layouts as the documented rule), and managed spaces may ship application-visible table DDL — better-auth is the worked example. The layout rule and the ecosystem subsystem doc name better-auth as the managed-space precedent alongside pgvector/paradedb. New ADR 231 records the adapter pattern: stringly-typed third-party interfaces adapt over contract-typed collections, with five obligations — exhaustive typed model map, codec-boundary crossing, fail-fast typed errors, native capability mapping (atomic consume, transaction rebinding, include-backed joins), and two-views consumption. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ample The example test runs db init in-place against an ephemeral dev database; the CLI writes migrations/app/refs/db.* describing that database, so every test run rewrote the tracked copies and dirtied the tree. Only head pins and migration packages are meaningful to commit (the demo example tracks no db refs either); gitignore the db.* refs. Verified: the example test passes from a fresh-clone state without them. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The snippet invented a decodeInput() helper that does not exist and contradicted the ADR's own obligation 2 (codecs cross inside the collection). Replace with the shipped shape from the adapter source: resolveSpaceModel → assertKnownFields → collection.create(data). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…equirement Raw defineContract calls now require an explicit createNamespace; follow the supabase handles-test precedent (createTestSqlNamespace via relative test-support import, package rootDir widened to the workspace root like supabase and sql-runtime). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The emitter now stamps scalarList: true into sql capabilities; the committed example aggregate is emit output and must stay byte-stable under fixtures:check. Hashes unchanged (capabilities do not feed the storage hash), so the committed migrations and head refs stay valid. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…emit for scalarList prismaContract() now requires createNamespace (supabase precedent: postgresCreateNamespace), and fixtures:emit runs this package's build:contract-space, so the shipped space contract must re-emit byte-stable. The re-emit adds only the scalarList capability; storage hash unchanged, so the shipped baseline migration and head ref stay valid (descriptor self-consistency test green). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…error handler, README copy fidelity F-1: migration plan at head rewrites the pinned better-auth space mirrors even when it plans nothing; the committed mirrors were stale (missing the scalarList capability stamp; head.json in pretty-printed form where the rewrite path emits minified), so README step 2 dirtied a fresh clone. Commit the mirrors as the rewrite path emits them, exempt consuming-app mirror head refs (migrations/*/refs/head.json) from the biome formatter, sync the pack baseline end-contract from the shipped space contract, and extend the example test to assert plan-at-head leaves the mirrors byte-identical (assertion proven to fire against the stale bytes). F-3: the db.ts template installed no pg pool error handler, so an idle-client disconnect (pgbouncer restart, serverless reaping, network blip) crashed the whole process via unhandled error event. Add the logged handler (react-router-demo precedent). F-4 (+F-7): package README copy fidelity — extensions vs extensionPacks for the postgres defineConfig, pnpm exec prefixes on the schema-flow commands, and the requirement-check rejection quoted verbatim from the runtime (also in the example README, db.ts, and the runtime descriptor docblock); example README now states profile is null until the app creates a row, before promising the joined shape. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ger, QA script + run Transient per projects/README.md; deleted at project close-out. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The spec reserved the durability judgment for the operator; it was never put to them. No shipped file referenced the ADR. The adapter-contract content remains in the package README; the operator-decreed ADR 212 amendment stays. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
BetterAuth >=1.6.17 standardizes update/consumeOne as null-returning no-ops and delete as a plain no-op when called with an empty or absent where; scopeToWhere returned the unscoped collection instead, so a forwarded empty where became a whole-table write. The installed 1.6.23 factory guards its own update path but forwards delete/consumeOne unguarded (verified against @better-auth/core factory.mjs). Tests were red for exactly those two before the guards landed. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ullability, brand identity checks - updateMany routes its payload through asRecord like the singular path (object-shape guard before field validation). - model-map deserializes contract.json through PostgresContractSerializer at module load (Type Parameter Pattern) instead of trusting the JSON import type, and rejects relations that do not declare exactly one local + target field instead of silently taking the first. - Handles mark every contract-nullable column .optional() (10 columns across User/Session/Account); the handle-contract consistency loop now compares per-column nullability whole-map style alongside codec ids (red for the three models before the handles fix). - Brand assertions use an Equal<A,B> mutual-assignability check that preserves phantom brands; ./pack export gains types/import conditions; architecture.config.json covers the adapter and runtime globs; test headers name the @prisma/dev Postgres dev server instead of claiming bare PGlite. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ew internally The two-client ceremony was user-visible plumbing: apps had to construct a second postgres() client over the pack space contract just to hand the adapter typed collections. The adapter now accepts the app's shared pg.Pool and constructs that view itself (verifyMarker false, with the rationale moved into adapter source: the marker names the aggregate; this is a partial view of the same database). Pool only by design — no url form — so the adapter never owns an uncloseable pool. The structural BetterAuthDb form stays for tests and advanced injection. Covered by shared-pool CRUD, rollback-through-the-pool, and pool-visibility tests. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…he shared pool Operator direction on PR #968, both design comments: PSL: the app contract moves from TS builders to contract.prisma using the cross-space reference grammar (user better-auth:public.User @relation(..., onDelete: Cascade), supabase example precedent). The lowering is identical — same storage hash, byte-identical emitted contract.json — so the committed migrations and mirrors stay valid. One client: db.ts constructs a single postgres() client over the aggregate on an app-owned pool; auth.ts hands the same pool to prismaNextAdapter({ pg }); the space view is no longer user-visible ceremony. /api/me returns the session and its user through BetterAuth plus the Profile through the ORM; the cascade proof observes the cross-space FK at the database level through the shared pool. READMEs retell the one-client story with the aggregate-no-folding explanation moved to background. Harness setup now releases the dev database and temp dir on mid-setup failure, and teardown never rejects (bounded close, rmSync in finally). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The include-decode codec boundary is fixed on main (TML-2990): include cells cross codec decodeJson with the timestamptz offset rendering accepted, which is exactly what these eight joins-suite tests needed. All eight pass un-gated; the disable categories drop from four to three (non-goals, generateId leak, upstream transaction wrapper). Conformance: 164 passed / 61 skipped. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Our importer entries carried pre-rebase peer resolutions (pg 8.21, vitest 4.1.8, mongodb 7.2) and the removed sql-contract-ts devDep; plain pnpm install realigns them with main catalog pins. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
e6c506d to
78de265
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@examples/better-auth/test/example.integration.test.ts`:
- Around line 118-128: Update the FK query in the integration test to verify the
documented public.profile(userId) → public.user(id) relationship, not merely a
cascade to a relation named user. Join pg_namespace and pg_attribute for both
tables, filter the public schema and the profile/user column names, and retain
the on_delete assertion.
- Around line 186-194: Make the test named “cascades profile deletion when the
user is deleted” self-contained by creating the Ada user and profile records
within the test or an existing shared setup helper before querying Profile.
Preserve the current database-level DELETE and cascade assertions, and ensure
the setup does not depend on the preceding test.
🪄 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: cc0a0f92-0034-48e2-9e3f-d01cf092b228
⛔ Files ignored due to path filters (24)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprojects/extension-better-auth/plan.mdis excluded by!projects/**projects/extension-better-auth/qa/manual-qa.mdis excluded by!projects/**projects/extension-better-auth/qa/qa-run-2026-07-13.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/01-scaffold-and-contract-space.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/02-managed-space-lifecycle-test.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/03-branded-contract-handles-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/03-branded-contract-handles.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/04-adapter-core-crud-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/04-adapter-core-crud.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/05-adapter-consume-transaction-join.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/06-conformance-and-e2e-integration-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/06-conformance-and-e2e-integration-r3.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/06-conformance-and-e2e-integration.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/07-example-app-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/07-example-app-r3.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/07-example-app.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/08-docs-and-close-out-prep-r2.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/08-docs-and-close-out-prep-r3.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/dispatches/08-docs-and-close-out-prep.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/plan.mdis excluded by!projects/**projects/extension-better-auth/slices/better-auth-extension/spec.mdis excluded by!projects/**projects/extension-better-auth/spec.mdis excluded by!projects/**projects/extension-better-auth/trace.jsonlis excluded by!projects/**
📒 Files selected for processing (69)
.agents/rules/contract-space-package-layout.mdcarchitecture.config.jsonbiome.jsoncdocs/architecture docs/adrs/ADR 212 - Contract spaces.mddocs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.mdexamples/better-auth/.gitignoreexamples/better-auth/README.mdexamples/better-auth/biome.jsoncexamples/better-auth/migrations/app/20260713T0837_init/end-contract.d.tsexamples/better-auth/migrations/app/20260713T0837_init/end-contract.jsonexamples/better-auth/migrations/app/20260713T0837_init/migration.jsonexamples/better-auth/migrations/app/20260713T0837_init/migration.tsexamples/better-auth/migrations/app/20260713T0837_init/ops.jsonexamples/better-auth/migrations/better-auth/20260713T0717_create_auth_tables/migration.jsonexamples/better-auth/migrations/better-auth/20260713T0717_create_auth_tables/ops.jsonexamples/better-auth/migrations/better-auth/contract.d.tsexamples/better-auth/migrations/better-auth/contract.jsonexamples/better-auth/migrations/better-auth/refs/head.jsonexamples/better-auth/package.jsonexamples/better-auth/prisma-next.config.tsexamples/better-auth/src/auth.tsexamples/better-auth/src/main.tsexamples/better-auth/src/prisma/contract.d.tsexamples/better-auth/src/prisma/contract.jsonexamples/better-auth/src/prisma/contract.prismaexamples/better-auth/src/prisma/db.tsexamples/better-auth/src/server.tsexamples/better-auth/test/example.integration.test.tsexamples/better-auth/tsconfig.jsonexamples/better-auth/vitest.config.tspackages/3-extensions/better-auth/README.mdpackages/3-extensions/better-auth/biome.jsoncpackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/end-contract.d.tspackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/end-contract.jsonpackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/migration.jsonpackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/migration.tspackages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/ops.jsonpackages/3-extensions/better-auth/migrations/refs/head.jsonpackages/3-extensions/better-auth/package.jsonpackages/3-extensions/better-auth/prisma-next.config.tspackages/3-extensions/better-auth/src/adapter/db-surface.tspackages/3-extensions/better-auth/src/adapter/errors.tspackages/3-extensions/better-auth/src/adapter/index.tspackages/3-extensions/better-auth/src/adapter/join.tspackages/3-extensions/better-auth/src/adapter/model-map.tspackages/3-extensions/better-auth/src/adapter/where.tspackages/3-extensions/better-auth/src/contract/contract.d.tspackages/3-extensions/better-auth/src/contract/contract.jsonpackages/3-extensions/better-auth/src/contract/contract.prismapackages/3-extensions/better-auth/src/contract/handles.tspackages/3-extensions/better-auth/src/exports/adapter.tspackages/3-extensions/better-auth/src/exports/contract.tspackages/3-extensions/better-auth/src/exports/pack.tspackages/3-extensions/better-auth/src/exports/runtime.tspackages/3-extensions/better-auth/src/pack/index.tspackages/3-extensions/better-auth/src/runtime/descriptor.tspackages/3-extensions/better-auth/test/adapter-advanced.test.tspackages/3-extensions/better-auth/test/adapter-crud.test.tspackages/3-extensions/better-auth/test/adapter-errors.test.tspackages/3-extensions/better-auth/test/adapter-pool-form.test.tspackages/3-extensions/better-auth/test/adapter-types.test-d.tspackages/3-extensions/better-auth/test/contract-handles.test.tspackages/3-extensions/better-auth/test/contract-types.test-d.tspackages/3-extensions/better-auth/test/descriptor.test.tspackages/3-extensions/better-auth/test/runtime-descriptor.test.tspackages/3-extensions/better-auth/tsconfig.jsonpackages/3-extensions/better-auth/tsconfig.prod.jsonpackages/3-extensions/better-auth/tsdown.config.tspackages/3-extensions/better-auth/vitest.config.ts
🚧 Files skipped from review as they are similar to previous changes (63)
- packages/3-extensions/better-auth/biome.jsonc
- packages/3-extensions/better-auth/migrations/refs/head.json
- examples/better-auth/biome.jsonc
- examples/better-auth/migrations/better-auth/contract.d.ts
- examples/better-auth/.gitignore
- examples/better-auth/prisma-next.config.ts
- packages/3-extensions/better-auth/src/exports/contract.ts
- packages/3-extensions/better-auth/tsdown.config.ts
- examples/better-auth/src/prisma/contract.prisma
- packages/3-extensions/better-auth/tsconfig.prod.json
- examples/better-auth/migrations/better-auth/20260713T0717_create_auth_tables/migration.json
- examples/better-auth/migrations/app/20260713T0837_init/end-contract.json
- packages/3-extensions/better-auth/tsconfig.json
- examples/better-auth/migrations/better-auth/contract.json
- examples/better-auth/vitest.config.ts
- packages/3-extensions/better-auth/src/runtime/descriptor.ts
- biome.jsonc
- examples/better-auth/tsconfig.json
- packages/3-extensions/better-auth/src/exports/pack.ts
- packages/3-extensions/better-auth/vitest.config.ts
- examples/better-auth/migrations/better-auth/20260713T0717_create_auth_tables/ops.json
- packages/3-extensions/better-auth/README.md
- .agents/rules/contract-space-package-layout.mdc
- packages/3-extensions/better-auth/src/exports/runtime.ts
- examples/better-auth/src/auth.ts
- examples/better-auth/package.json
- packages/3-extensions/better-auth/src/contract/contract.prisma
- packages/3-extensions/better-auth/src/exports/adapter.ts
- examples/better-auth/migrations/app/20260713T0837_init/migration.json
- packages/3-extensions/better-auth/package.json
- packages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/ops.json
- examples/better-auth/src/server.ts
- architecture.config.json
- packages/3-extensions/better-auth/src/pack/index.ts
- packages/3-extensions/better-auth/test/contract-handles.test.ts
- packages/3-extensions/better-auth/test/adapter-errors.test.ts
- packages/3-extensions/better-auth/src/adapter/errors.ts
- examples/better-auth/migrations/app/20260713T0837_init/migration.ts
- packages/3-extensions/better-auth/test/contract-types.test-d.ts
- packages/3-extensions/better-auth/src/contract/handles.ts
- examples/better-auth/migrations/app/20260713T0837_init/ops.json
- packages/3-extensions/better-auth/src/contract/contract.json
- examples/better-auth/src/main.ts
- docs/architecture docs/adrs/ADR 212 - Contract spaces.md
- packages/3-extensions/better-auth/src/adapter/where.ts
- examples/better-auth/migrations/app/20260713T0837_init/end-contract.d.ts
- packages/3-extensions/better-auth/test/runtime-descriptor.test.ts
- packages/3-extensions/better-auth/src/adapter/db-surface.ts
- packages/3-extensions/better-auth/prisma-next.config.ts
- examples/better-auth/src/prisma/db.ts
- packages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/migration.ts
- packages/3-extensions/better-auth/src/adapter/join.ts
- packages/3-extensions/better-auth/test/descriptor.test.ts
- packages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/migration.json
- packages/3-extensions/better-auth/src/contract/contract.d.ts
- packages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/end-contract.json
- packages/3-extensions/better-auth/src/adapter/index.ts
- packages/3-extensions/better-auth/test/adapter-types.test-d.ts
- packages/3-extensions/better-auth/src/adapter/model-map.ts
- examples/better-auth/src/prisma/contract.d.ts
- packages/3-extensions/better-auth/migrations/20260713T0717_create_auth_tables/end-contract.d.ts
- docs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.md
- examples/better-auth/README.md
| const rows = await withClient(database.connectionString, async (client) => { | ||
| const result = await client.query( | ||
| `SELECT confrel.relname AS references_table, con.confdeltype::text AS on_delete | ||
| FROM pg_constraint con | ||
| JOIN pg_class rel ON rel.oid = con.conrelid | ||
| JOIN pg_class confrel ON confrel.oid = con.confrelid | ||
| WHERE rel.relname = 'profile' AND con.contype = 'f'`, | ||
| ); | ||
| return result.rows; | ||
| }); | ||
| expect(rows).toEqual([{ references_table: 'user', on_delete: 'c' }]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Verify the schema and referenced column in the FK assertion.
This query can pass for any cascading profile FK targeting a relation named user; it does not prove the documented "public"."profile"(userId) → "public"."user"(id) contract. Include namespace and attribute joins in the query.
🤖 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 `@examples/better-auth/test/example.integration.test.ts` around lines 118 -
128, Update the FK query in the integration test to verify the documented
public.profile(userId) → public.user(id) relationship, not merely a cascade to a
relation named user. Join pg_namespace and pg_attribute for both tables, filter
the public schema and the profile/user column names, and retain the on_delete
assertion.
| it('cascades profile deletion when the user is deleted', { | ||
| timeout: timeouts.databaseOperation, | ||
| }, async () => { | ||
| const before = await appDb.db.orm.public.Profile.where({ id: 'profile-ada' }).first(); | ||
| expect(before).not.toBeNull(); | ||
|
|
||
| // Delete the user at the database level: the cross-space FK's | ||
| // ON DELETE CASCADE removes the dependent profile row. | ||
| await appDb.pool.query('DELETE FROM "public"."user" WHERE email = $1', ['ada@example.com']); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the cascade test self-contained.
This test relies on the preceding test to create ada@example.com and profile-ada. Running it alone—or after the preceding test fails—does not exercise the cascade. Seed its user and profile within this test or a shared setup helper.
🤖 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 `@examples/better-auth/test/example.integration.test.ts` around lines 186 -
194, Make the test named “cascades profile deletion when the user is deleted”
self-contained by creating the Ada user and profile records within the test or
an existing shared setup helper before querying Profile. Preserve the current
database-level DELETE and cascade assertions, and ensure the setup does not
depend on the preceding test.
Adds
@prisma-next/extension-better-auth: a BetterAuth database adapter backed by Prisma Next's contract machinery. The auth schema ships as a managed contract space (the first extension space that ships table DDL — the framework ownsdb init/db update/verification for it), and BetterAuth's stringly-typed persistence interface is translated onto contract-typed ORM collections, so model/field mismatches are compile-time errors inside the package rather than runtime surprises in apps.Closes TML-2994, TML-2995, TML-2996 (single-PR delivery of the BetterAuth Extension project; spec + review ledger under
projects/extension-better-auth/).Changes
/pack,/contract) —spaceId: 'better-auth'definesUser/Session/Account/Verification(BetterAuth's canonical core schema: default table names inpublic, text ids,onDelete: CascadeFKs,User.email/Session.tokenuniques,User → Session[]/Account[]backrelations) from PSL source, with a baseline migration andrefs/head.json.managedcontrol — unlike the supabase space'sexternalposture,prisma-next db initcreates these tables anddb updatewalks the space's migration graph./contractshipsextensionModel-branded handles so app contracts can declare cross-space FKs ontoUser(demonstrated by the example'sProfile)./adapter) —prismaNextAdapter(db)built on better-auth v1.5+createAdapterFactory. The BetterAuth-model→collection map isas const satisfies Record<SpaceModelName, …>against the emittedcontract.d.ts, so contract/adapter divergence failspnpm typecheckin both directions; field/operator validation reads the shipped contract; unknown surfaces throw typed errors (UNKNOWN_MODEL/UNKNOWN_FIELD/UNSUPPORTED_OPERATOR/…). NativeconsumeOneridesCollection.delete()'s atomic find-first + narrowedDELETE … RETURNING(single-winner proven by an 8-way parallel race test);transactionrebinds a nested adapter onto the runtime's transaction scope (rollback proven); thejoinparameter translates ontoCollection.include()with typed failure for unmapped targets (native path proven by collection spies — the fallback separate-queries path is never taken)./runtime) — descriptor-onlySqlRuntimeExtensionDescriptor(no facade), required for constructing a client over an aggregate contract that includes the space; discovered as a product gap when building the example (plainpostgres({ contractJson: aggregate })rejects pack-bearing contracts without it).test/integration) — BetterAuth's official conformance suite (@better-auth/test-utils/adapter) over PGlite: 156 passed / 0 failed, with 69 skips in four documented categories (project non-goals; 8 decode-dependent join tests pending TML-3015 — see below; one upstream test-utils bug that voids providedtransactionimplementations; one upstream test that leaksgenerateIdstate). Plus a managed-space lifecycle journey (emit → plan → init → update-no-op → verify, catalog-asserted) and a realbetterAuth()sign-up → session e2e.examples/better-auth) — full consumer story: three-step schema flow, sign-up → authenticated request over a minimal server, cross-spaceProfile → userFK (cascade observed live), and a self-testing suite that re-emits byte-identical artifacts, integrity-checks both spaces, and runsdb initthrough the real CLI. Documents the two-views architecture (aggregate client + space view over one shared pool) that adapter consumers need because the emitted aggregate does not fold pack domain models.src/contract/PSL-authored layout and the managed table-DDL space variant; extension-precedent references updated (docs/architecture docs/subsystems/6.,.agents/rules/contract-space-package-layout.mdc).Why
contract emit → migration plan → db initas the only setup path (no manual SQL anywhere, including tests and the example) and proves extension spaces can carry table DDL through the existingcomputeExtensionSpaceApplyPathmachinery — no framework changes were needed.testAdaptersuites (including join coverage with native joins enabled) are the acceptance test, not hand-rolled approximations. The run surfaced real product gaps now fixed here (cascade semantics,/runtimedescriptor,Userbackrelations) — and one genuine ORM bug, split out as TML-3015 / PR tml-3015: decode to-many include() payloads through the codec boundary #966 (to-manyinclude()payloads bypass the codec decode boundary); the 8 conformance tests blocked on it are disabled with per-test reasons and re-enable when it merges.@prisma/devclose-hang under churn), TML-3021 (migration serializer pretty/minified mismatch), TML-3020 (duplicate ADR numbers).The
projects/extension-better-auth/folder (spec, plan, dispatch briefs, review ledger, QA script + run report) is transient perprojects/README.mdand is deleted at project close-out.Summary by CodeRabbit