TML-2852: enums become first-class in application code — typed I/O, db.enums, declaration-order ORDER BY#769
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughParameterizes enum generics in contract authoring, widens enum member/value typing and encodes values during contract build, exposes typed enum accessors on the ORM client, adds enum-aware ORDER BY lowering in Postgres renderer, and adds type/runtime tests and demo integration across the stack. ChangesEnum Support Across the Stack
Sequence Diagram (high-level flow) sequenceDiagram
participant Authoring as ContractAuthoring
participant Builder as ContractBuilder
participant Runtime as ORMRuntime
participant Postgres as PostgresRenderer
ContractAuthoring->>ContractBuilder: defineContract/enums + enumType
ContractBuilder->>ContractBuilder: encodeEnumValue, merge scaffold+factory enums
ContractBuilder->>ORMRuntime: produce SqlContractResult (enumAccessors)
ORMRuntime->>ORMRuntime: buildEnumsMap/createEnumAccessor (lazy on db.enums)
ORMRuntime->>PostgresRenderer: query AST with enum-backed columns
PostgresRenderer->>PostgresRenderer: renderOrderByExpr -> array_position over valueSet
🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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/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/emitter
@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 📦
|
…ut types (TML-2852 Dispatch 1) FieldOutputType and FieldInputType in contract-types.ts now resolve to the literal value union (e.g. 'user' | 'admin') when the field was authored with field.namedType(enumHandle), instead of falling back to the codec's string output. The key fix was replacing the ModelStorageColumn conditional path (which TypeScript defers because it contains a type-variable extends check) with a direct ModelFieldState index access for the nullable and codec-id lookups. ExtractOutputType in query-builder/selection.ts now consults ExtractFieldOutputTypes via a FindModelForTable/FindFieldForColumn lookup before falling back to the codec type, so enum columns propagate the literal union through TableToSelection as well. Type-tests added in all four affected packages (contract-ts, sql-orm-client, sql-builder, query-builder) covering both read output and write input, plus a negative case that rejects out-of-union literals. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…ient Adds `db.enums.<Name>` to the sql-orm-client: a runtime accessor map built from the contract domain enum entity, plus the type-level connection so `db.enums.Role.values` resolves to the literal value tuple and `.members.User` to the value literal. - enum-accessor.ts: `createEnumAccessor` / `buildEnumsMap` (no bare casts; `Object.freeze` already yields `readonly`). - orm.ts: Proxy `enums` branch + `enums` typed off the contract`s `enumAccessors`. - contract-builder.ts: thread a `const Enums` generic through `defineContract` so authored enum handles keep their literal types. - contract-types.ts: `BuiltEnumAccessors` derives the accessor shape from the authored `EnumTypeHandle`; widened (enum-less) contracts stay empty. - Tests: runtime accessors + Proxy branch; a type-test asserting the literal `values` tuple (not `string[]`) and `.members.User === 'user'`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
… BY (TML-2852 Dispatch 3) An ORDER BY on an enum-restricted column now renders array_position(ARRAY[v1, v2, …]::text[], <col>) over the value-set's ordered values, so rows sort by declaration order rather than lexically. The Postgres sql-renderer builds an alias→storage-coordinate map from the SELECT's FROM and JOIN table sources, resolves an ORDER BY column-ref to its storage column, and — when that column carries a valueSet — emits the array_position expression from the value-set's ordered values. Non-enum columns render unchanged. A PGlite integration test inserts rows out of declaration order and asserts the declaration-order result, plus a nullable-column case (array_position returns NULL for NULL rows, which sort last under the default ASC NULL handling). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
d33e6a2 to
c011d3a
Compare
wmadden
left a comment
There was a problem hiding this comment.
Only a few small comments. Overall looks good.
…solution (TML-2852) D1 rewrote FieldOutputType/FieldInputType with an inline ResolveFieldDescriptor path that re-widened non-enum field result types to string (id, createdAt), regressing the query-builder ResultType inference covered by integration-tests contract-builder.test.ts and .types.test-d.ts. Restore mains codec-based resolution verbatim as FieldCodecOutputType and FieldCodecInputType, and layer the enum value-union narrowing on top via EnumNarrowedType, which only narrows when the field carries an enum typeRef. The non-enum widening came from EnumValueUnion structurally matching the default EnumTypeHandle and inferring Values as string[]. Guard it with a tuple-wrapped extends check plus a string[] extends Values bail-out so only fields with a concrete enum handle narrow; everything else falls through to mains codec output. In query-builder selection.ts, ExtractOutputType consults the field-output typemap only when it carries a concrete literal override, otherwise falling back to mains exact ExtractCodecTypes codec output expression. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…(TML-2852)
D2 added enums to OrmClient, so the loose () => ({ lane }) mocks no longer
overlap typeof orm and the as typeof ormMock cast failed with TS2352. Route the
mock implementations through unknown so the consumer stubs satisfy the richer
client type without weakening OrmClient.enums.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…rder sort (TML-2852) The Postgres ORDER BY enum hook only matched qualified column-ref order items. The sql-builders .orderBy(col) string and callback forms both emit an identifier-ref (bare column, no table qualifier), so a natural ORDER BY on an enum column fell through to a plain column reference and sorted lexically. Resolve identifier-ref order columns against the SELECTs FROM/JOIN sources and render array_position over the value-sets ordered values when exactly one source carries that enum column; an ambiguous name across joined tables falls through to the bare column. Adds a PGlite test for the identifier-ref form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
… end to end (TML-2852) A focused example app authored with the TypeScript enumType API (not PSL enum) that demonstrates all three slice surfaces against PGlite: - Typed I/O: the enum field reads as the value union (not string) and a write only accepts the union; an out-of-union literal is a compile error (enum-demo.types.test-d.ts). - db.enums: the runtime accessor exposes values/names/members/has/ordinalOf in declaration order (enum-demo.integration.test.ts). - Declaration-order ORDER BY via array_position, plus the slice-2 CHECK constraint rejecting out-of-union values (enum-demo.integration.test.ts). The runtime passes the TypeScript-authored contract directly so the value-union types flow from the authored definition. The emitted contract.json / contract.d.ts are committed and kept current via emit:check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/3-targets/6-adapters/postgres/src/core/sql-renderer.ts`:
- Around line 282-297: The current resolution loop over sourcesByRef only treats
ambiguity when multiple enum-backed columns match, allowing a rewrite to
array_position(...) when the same column name exists on multiple sources but
only one is enum; change the logic in sql-renderer.ts so that before setting
resolved (and before performing the array_position rewrite) you first count
matches of the identifier across all sources (use sourcesByRef and
contract.storage.namespaces lookups used in the existing loop), and only proceed
to set resolved/valueSet and perform the enum rewrite when the identifier
matches exactly one source column in total (not just one enum column); apply the
same uniqueness check to the other matching block that leads to the
array_position rewrite (the code around the existing resolved variable usage and
the 317-321 region) so ambiguous column names are not rewritten.
🪄 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: 7c1e650a-68d2-4642-b876-c4cedb51f2f4
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
examples/enum-demo/README.mdexamples/enum-demo/biome.jsoncexamples/enum-demo/package.jsonexamples/enum-demo/prisma-next.config.tsexamples/enum-demo/src/contract.d.tsexamples/enum-demo/src/contract.jsonexamples/enum-demo/src/contract.tsexamples/enum-demo/src/db.tsexamples/enum-demo/test/enum-demo.integration.test.tsexamples/enum-demo/test/enum-demo.types.test-d.tsexamples/enum-demo/test/init-db.tsexamples/enum-demo/tsconfig.jsonexamples/enum-demo/vitest.config.tspackages/2-sql/2-authoring/contract-ts/src/contract-types.tspackages/2-sql/4-lanes/query-builder/src/selection.tspackages/3-extensions/postgres/test/postgres.test.tspackages/3-extensions/sqlite/test/transaction.test.tspackages/3-targets/6-adapters/postgres/src/core/sql-renderer.tspackages/3-targets/6-adapters/postgres/test/migrations/order-by-enum.integration.test.ts
✅ Files skipped from review due to trivial changes (8)
- examples/enum-demo/tsconfig.json
- examples/enum-demo/prisma-next.config.ts
- examples/enum-demo/src/contract.ts
- packages/3-extensions/sqlite/test/transaction.test.ts
- examples/enum-demo/src/contract.json
- examples/enum-demo/biome.jsonc
- examples/enum-demo/README.md
- examples/enum-demo/src/contract.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/2-sql/4-lanes/query-builder/src/selection.ts
- packages/2-sql/2-authoring/contract-ts/src/contract-types.ts
…L-2852) Reduce the enum value-union narrowing to a shallow, single-pass form and fix a non-null enum column reading as `union | null`. Behavior spec (TDD, tests first): - A non-null enum field reads/writes as exactly its value union (no `| null`). - A nullable enum field reads/writes as `union | null`. - A non-text (int-backed) enum narrows to its int value union (e.g. `1 | 10`). - Non-enum fields keep their codec output unchanged. Implementation: - contract-types.ts: compute a field`s non-null base type once (enum value union when the field is enum-typed, else the codec channel), apply nullability once. Replaces the deeply nested FieldCodecOutputType / EnumNarrowedType pair (max conditional depth ~10 -> ~4). - contract-types.ts: resolve an enum-handle field`s storage descriptor and type-ref from the handle, so an enum column is a real StorageColumn with the correct `nullable: false` instead of collapsing to `never` (the `never extends true` vacuous-truth that injected the spurious `| null` in the select lane). - query-builder selection.ts: delete FindModelForTable / FindFieldForColumn / EnumOutputOverride / FieldOutputTypeFor; read the FieldOutputTypes override in one flat mapped-type pass (max depth ~7 -> ~3). - enum-type.ts + build-contract.ts: enum member values may be string or number literals (preserved in the handle for narrowing); encode to strings when lowering to the storage value-set / domain enum members. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…enum-demo (TML-2852)
Move the enum read-surface example out of a standalone `examples/enum-demo`
app and into the existing `prisma-next-demo`, and fix the Postgres value-set
materialization gap the move surfaced.
- Postgres value-set materialization: `postgresCreateNamespace` and the
Postgres contract serializer dropped the `entries.valueSet` slot, so an
`enumType`-authored enum built through the Postgres builder lost its
storage value-set. The CHECK constraint and the declaration-order ORDER BY
both reference that value-set, so `db init` failed ("value-set not found")
and ORDER BY fell through to a bare column. `PostgresSchema` now carries
`valueSet`, and the serializer round-trips it.
- postgres `defineContract`: thread an `enums` field through the wrapper so a
Postgres contract can declare `enumType` enums; re-export `enumType` /
`member` / `EnumTypeHandle` from `@prisma-next/postgres/contract-builder`.
- prisma-next-demo: add a TS-authored `enumType` enum (`Priority`) on a small
definition-form contract and demonstrate the three surfaces with tests —
typed read/write narrowing (`*.types.test-d.ts`), `db.enums` accessors, and
declaration-order ORDER BY against a dev database. PSL `enum` stays native.
- Delete `examples/enum-demo` entirely (workspace + lockfile).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…any joined source shares the name (TML-2852) resolveEnumOrderValuesForIdentifier only fell through when two enum-backed columns matched. A join where the same column name exists on multiple sources but only one is enum-backed still rewrote to array_position over a bare identifier — ambiguous SQL. Now count every source column with that name (enum or not); rewrite only when exactly one source has it and it carries a value-set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/2-sql/2-authoring/contract-ts/src/enum-type.ts (1)
188-203:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate uniqueness on the lowered storage value.
seenValuesnow treats1and'1'as distinct, butbuild-contract.tslater normalizes both to"1". That meansenumType('E', ..., member('One', 1), member('TextOne', '1'))passes here and then emits duplicate enum/value-set entries downstream. Please compare uniqueness onString(m.value)(or otherwise on the lowered representation) so authored enums stay one-to-one after serialization.🛠️ Suggested fix
- const seenValues = new Set<string | number>(); + const seenLoweredValues = new Set<string>(); for (const m of members) { if (seenNames.has(m.name)) { throw new Error( `enumType("${name}"): duplicate member name "${m.name}". Member names must be unique.`, ); } seenNames.add(m.name); - if (seenValues.has(m.value)) { + const loweredValue = String(m.value); + if (seenLoweredValues.has(loweredValue)) { throw new Error( - `enumType("${name}"): duplicate member value "${m.value}". Member values must be unique.`, + `enumType("${name}"): duplicate member value "${loweredValue}" after storage encoding. Member values must be unique.`, ); } - seenValues.add(m.value); + seenLoweredValues.add(loweredValue); }🤖 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-sql/2-authoring/contract-ts/src/enum-type.ts` around lines 188 - 203, The uniqueness check for enum member values in the members loop treats numeric 1 and string "1" as distinct; change the logic in enum-type.ts (inside the members iteration where seenValues is used) to compare and store the lowered/serialized representation (e.g., String(m.value)) so duplication is detected after serialization; update seenValues to be a Set<string>, perform seenValues.has(String(m.value)) for the duplicate check, and then seenValues.add(String(m.value)) when storing the value.packages/2-sql/2-authoring/contract-ts/src/contract-types.ts (1)
594-603:⚠️ Potential issue | 🟠 MajorFix
enumAccessorsaccessor method parameter types to use the authored enum value union
EnumHandleAccessorTypestill typeshas/nameOf/ordinalOfasv: string(even though enum handles arestring | numberat runtime and carry literal value tuples), so int-backed enums will incorrectly rejectdb.enums.<Enum>.has(1)/nameOf(10)/ordinalOf(1)at compile time.🛠️ Suggested fix
type EnumHandleAccessorType<Handle> = Handle extends EnumTypeHandle<infer _Name, infer Values, infer Names, infer MembersMap> ? { readonly values: Values; readonly names: Names; readonly members: MembersMap; - has(v: string): boolean; - nameOf(v: string): string | undefined; - ordinalOf(v: string): number; + has(v: Values[number]): boolean; + nameOf(v: Values[number]): string | undefined; + ordinalOf(v: Values[number]): number; } : never;🤖 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-sql/2-authoring/contract-ts/src/contract-types.ts` around lines 594 - 603, EnumHandleAccessorType currently types the has/nameOf/ordinalOf parameters as v: string which prevents numeric enum values from compiling; update the parameter types for has, nameOf, and ordinalOf to accept the authored enum value union (use the inferred Values tuple to derive the value type, e.g. Values[number] or the equivalent union) in the EnumHandleAccessorType definition so these methods accept both string and numeric enum members.
🧹 Nitpick comments (1)
examples/prisma-next-demo/test/utils/enum-control-client.ts (1)
27-29: 💤 Low valueOptional: remove redundant
mkdirSyncaftermkdtempSync.
mkdtempSyncat line 27 already creates the directory atomically, so themkdirSynccall at line 29 is redundant. While therecursive: trueflag makes this a harmless no-op, removing it would simplify the code.♻️ Simplify by removing the redundant mkdir
const migrationsDir = mkdtempSync(join(tmpdir(), 'prisma-next-demo-enum-migrations-')); try { - mkdirSync(migrationsDir, { recursive: true }); const initResult = await client.dbInit({🤖 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/prisma-next-demo/test/utils/enum-control-client.ts` around lines 27 - 29, Remove the redundant mkdirSync call: after creating migrationsDir with mkdtempSync (symbol migrationsDir), delete the subsequent mkdirSync(migrationsDir, { recursive: true }) invocation since mkdtempSync already creates the directory atomically; ensure any surrounding try/catch or error handling still makes sense after removing the mkdirSync line.
🤖 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.
Outside diff comments:
In `@packages/2-sql/2-authoring/contract-ts/src/contract-types.ts`:
- Around line 594-603: EnumHandleAccessorType currently types the
has/nameOf/ordinalOf parameters as v: string which prevents numeric enum values
from compiling; update the parameter types for has, nameOf, and ordinalOf to
accept the authored enum value union (use the inferred Values tuple to derive
the value type, e.g. Values[number] or the equivalent union) in the
EnumHandleAccessorType definition so these methods accept both string and
numeric enum members.
In `@packages/2-sql/2-authoring/contract-ts/src/enum-type.ts`:
- Around line 188-203: The uniqueness check for enum member values in the
members loop treats numeric 1 and string "1" as distinct; change the logic in
enum-type.ts (inside the members iteration where seenValues is used) to compare
and store the lowered/serialized representation (e.g., String(m.value)) so
duplication is detected after serialization; update seenValues to be a
Set<string>, perform seenValues.has(String(m.value)) for the duplicate check,
and then seenValues.add(String(m.value)) when storing the value.
---
Nitpick comments:
In `@examples/prisma-next-demo/test/utils/enum-control-client.ts`:
- Around line 27-29: Remove the redundant mkdirSync call: after creating
migrationsDir with mkdtempSync (symbol migrationsDir), delete the subsequent
mkdirSync(migrationsDir, { recursive: true }) invocation since mkdtempSync
already creates the directory atomically; ensure any surrounding try/catch or
error handling still makes sense after removing the mkdirSync line.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: a209978a-97a7-42f7-9907-30108d7a8de5
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (15)
examples/prisma-next-demo/prisma/enum-contract.tsexamples/prisma-next-demo/prisma/enum-db.tsexamples/prisma-next-demo/test/enum-surface.integration.test.tsexamples/prisma-next-demo/test/enum-surface.types.test-d.tsexamples/prisma-next-demo/test/utils/enum-control-client.tspackages/2-sql/2-authoring/contract-ts/src/build-contract.tspackages/2-sql/2-authoring/contract-ts/src/contract-types.tspackages/2-sql/2-authoring/contract-ts/src/enum-type.tspackages/2-sql/2-authoring/contract-ts/test/enum-type.field-output.test.tspackages/2-sql/4-lanes/query-builder/src/selection.tspackages/3-extensions/postgres/src/contract/define-contract.tspackages/3-extensions/postgres/src/exports/contract-builder.tspackages/3-targets/3-targets/postgres/src/core/postgres-contract-serializer.tspackages/3-targets/3-targets/postgres/src/core/postgres-schema.tstest/integration/test/contract-builder.types.test-d.ts
✅ Files skipped from review due to trivial changes (1)
- packages/3-extensions/postgres/src/exports/contract-builder.ts
Collapse the enum read/write field-type helpers to the flattest shape the behavior tests require, without changing non-enum resolution: - Replace the `IsEnumTypeRef` boolean threaded through three descriptor resolvers with a single `EnumFieldHandle<FieldState>` that yields the handle (or never). `ResolveFieldDescriptor` and `ResolveFieldColumnTypeRef` now short-circuit on it; for non-enum fields both reduce to mains exact bodies. `ResolveFieldColumnTypeParams` reverts to byte-identical-to-main (it already produced undefined for enum fields via the column-type-ref). - Merge `FieldOutputType` + `FieldInputType` + `FieldNullable` + `FieldBaseType` into one channel-parameterised `FieldChannelType` and one `FieldChannelTypes` map; nullability is a flat union member, not an outer conditional. Net -27 lines; no behavior change (enum field-output type-tests and the full-lane ResultType spec stay green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…852) The factory form of defineContract dropped a top-level `enums` key returned from the factory callback: its `Built` generic carried only `types`/`models` and the impl spread only those, so an enum authored through the factory never reached `SqlContractResult` -> field narrowing / db.enums. Mirror the definition form. The factory `Built` type now carries `readonly enums?: Enums` captured with a `const Enums` generic, the impl spreads `built.enums`, and the public factory overload merges scaffold-declared and factory-returned enums into the narrowing `Enums` slot. A parity type-test asserts the factory form narrows field reads/writes to the value union and surfaces the same `enumAccessors` shape as the definition form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…L-2852) Thread a factory-returned `enums` key through the Postgres `defineContract` wrapper so its factory form carries enum handles (previously only the scaffold arg did), mirroring the base builder. The demo contract (factory form) now declares a `Priority` enum (declaration order low -> high -> urgent, which differs from lexical) and references it on `Post.priority` via `field.namedType`. The no-emit app code consumes it for real: `getPostsByPriority` reads the narrowed value union and orders by declaration order, and `getPriorityEnum` surfaces `db.enums.Priority`. The rewritten enum-surface tests prove the read narrows to the union (not string), `db.enums` exposes the declaration-ordered runtime surface, declaration-order ORDER BY sorts low/low/high/urgent, and the CHECK constraint rejects out-of-union writes. Removes the side-contract (prisma/enum-contract.ts, prisma/enum-db.ts, test/utils/enum-control-client.ts) it replaces. The PSL schema is untouched (PSL enum repoint is a later slice). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/2-sql/2-authoring/contract-ts/src/contract-types.ts (1)
596-605:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAccept numeric enum values in accessor lookups.
Lines 602-604 hard-code
stringlookup inputs, but this PR also introduces int-backed enums (Priorityin the new factory-form test is1 | 10). That meansenumAccessors.Priority.has(1),nameOf(1), andordinalOf(1)are rejected by the public types even though numeric enum values are now first-class elsewhere in the surface.Suggested fix
type EnumHandleAccessorType<Handle> = Handle extends EnumTypeHandle<infer _Name, infer Values, infer Names, infer MembersMap> ? { readonly values: Values; readonly names: Names; readonly members: MembersMap; - has(v: string): boolean; - nameOf(v: string): string | undefined; - ordinalOf(v: string): number; + has(v: string | number): boolean; + nameOf(v: string | number): Names[number] | undefined; + ordinalOf(v: string | number): number; } : never;🤖 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-sql/2-authoring/contract-ts/src/contract-types.ts` around lines 596 - 605, The accessor type EnumHandleAccessorType currently restricts lookup inputs to string, which breaks int-backed enums; update the method signatures on EnumHandleAccessorType (the has, nameOf, and ordinalOf members) to accept both string and number (e.g., change parameter type from string to string | number) so numeric enum values like 1 or 10 are allowed in lookups while keeping return types the same.packages/2-sql/2-authoring/contract-ts/src/contract-builder.ts (1)
435-439:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMerge scaffold and factory enums before building the contract.
Lines 435-439 currently overwrite
definition.enumswithbuilt.enums. That disagrees with the factory-form type on Lines 504-529, which advertisesMergeEnums<ScaffoldEnums, FactoryEnums>. In a mixed contract, any scaffold-authored enum disappears from the built contract as soon as the factory returns anenumsobject, so the runtime enum surface no longer matches the published type.Suggested fix
return buildContractFromDsl({ ...full, ...ifDefined('types', built.types), ...ifDefined('models', built.models), - ...ifDefined('enums', built.enums), + ...ifDefined( + 'enums', + built.enums === undefined + ? undefined + : { + ...(definition.enums ?? {}), + ...built.enums, + }, + ), });🤖 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-sql/2-authoring/contract-ts/src/contract-builder.ts` around lines 435 - 439, The returned contract currently overwrites scaffold enums with factory enums; change the merge so scaffold and factory enums are combined before calling buildContractFromDsl by merging full.enums and built.enums into a single enums object (respecting scaffold keys and letting factory keys override where intended) instead of using ifDefined('enums', built.enums); update the return in the function that calls buildContractFromDsl to pass the merged enums (referencing buildContractFromDsl, ifDefined, definition.enums, built.enums and the MergeEnums<ScaffoldEnums, FactoryEnums> contract type) so runtime enums match the published MergeEnums type.packages/3-extensions/postgres/src/contract/define-contract.ts (1)
24-40:⚠️ Potential issue | 🟠 Major | ⚡ Quick winKeep the Postgres wrapper's enum generics split between scaffold and factory.
This wrapper collapses both enum sources into one
Enumsgeneric. Unlike the coredefineContractoverload inpackages/2-sql/2-authoring/contract-ts/src/contract-builder.ts(Lines 489-530), that meansdefineContract({ enums: { A } }, () => ({ enums: { B } }))cannot model the combined shape through the Postgres API. The extension surface ends up narrower than the builder it delegates to.Suggested fix
export function defineContract< const Types extends TypesConstraint = Record<never, never>, const Models extends ModelsConstraint = Record<never, never>, const ExtensionPacks extends | Record<string, ExtensionPackRef<'sql', string>> | undefined = undefined, - const Enums extends EnumsConstraint = Record<never, never>, + const ScaffoldEnums extends EnumsConstraint = Record<never, never>, + const FactoryEnums extends EnumsConstraint = Record<never, never>, >( - scaffold: PostgresScaffold<ExtensionPacks, Enums>, + scaffold: PostgresScaffold<ExtensionPacks, ScaffoldEnums>, factory: (helpers: ComposedAuthoringHelpers<SqlFamily, PostgresPack, ExtensionPacks>) => { readonly types?: Types; readonly models?: Models; - readonly enums?: Enums; + readonly enums?: FactoryEnums; }, -): PostgresResult<Types, Models, ExtensionPacks, Enums>; +): PostgresResult< + Types, + Models, + ExtensionPacks, + MergeEnums<ScaffoldEnums, FactoryEnums> +>;Also applies to: 84-98
🤖 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/postgres/src/contract/define-contract.ts` around lines 24 - 40, The Postgres wrapper collapses scaffold and factory enums into one Enums generic; update type PostgresResult to accept two enum generics (e.g. EnumsScaffold and EnumsFactory) and pass them through to buildBoundContract so both the scaffold-provided enums and the factory-provided enums are preserved. Concretely: replace the single Enums generic with separate EnumsScaffold and EnumsFactory generics, change the scaffold-side shape to use enums?: EnumsScaffold and ensure the factory-side shape (the buildBoundContract return/input) is typed to accept enums?: EnumsFactory so the wrapper delegates both enum sources to buildBoundContract (referencing the PostgresResult type and buildBoundContract, SqlFamily, and PostgresPack symbols).
🤖 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.
Outside diff comments:
In `@packages/2-sql/2-authoring/contract-ts/src/contract-builder.ts`:
- Around line 435-439: The returned contract currently overwrites scaffold enums
with factory enums; change the merge so scaffold and factory enums are combined
before calling buildContractFromDsl by merging full.enums and built.enums into a
single enums object (respecting scaffold keys and letting factory keys override
where intended) instead of using ifDefined('enums', built.enums); update the
return in the function that calls buildContractFromDsl to pass the merged enums
(referencing buildContractFromDsl, ifDefined, definition.enums, built.enums and
the MergeEnums<ScaffoldEnums, FactoryEnums> contract type) so runtime enums
match the published MergeEnums type.
In `@packages/2-sql/2-authoring/contract-ts/src/contract-types.ts`:
- Around line 596-605: The accessor type EnumHandleAccessorType currently
restricts lookup inputs to string, which breaks int-backed enums; update the
method signatures on EnumHandleAccessorType (the has, nameOf, and ordinalOf
members) to accept both string and number (e.g., change parameter type from
string to string | number) so numeric enum values like 1 or 10 are allowed in
lookups while keeping return types the same.
In `@packages/3-extensions/postgres/src/contract/define-contract.ts`:
- Around line 24-40: The Postgres wrapper collapses scaffold and factory enums
into one Enums generic; update type PostgresResult to accept two enum generics
(e.g. EnumsScaffold and EnumsFactory) and pass them through to
buildBoundContract so both the scaffold-provided enums and the factory-provided
enums are preserved. Concretely: replace the single Enums generic with separate
EnumsScaffold and EnumsFactory generics, change the scaffold-side shape to use
enums?: EnumsScaffold and ensure the factory-side shape (the buildBoundContract
return/input) is typed to accept enums?: EnumsFactory so the wrapper delegates
both enum sources to buildBoundContract (referencing the PostgresResult type and
buildBoundContract, SqlFamily, and PostgresPack symbols).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 62792a6c-5926-4b5e-85c2-4e0ed53c2b8e
📒 Files selected for processing (10)
examples/prisma-next-demo/prisma/contract.tsexamples/prisma-next-demo/src/prisma-no-emit/priority-feed.tsexamples/prisma-next-demo/test/enum-surface.integration.test.tsexamples/prisma-next-demo/test/enum-surface.types.test-d.tspackages/2-sql/2-authoring/contract-ts/src/contract-builder.tspackages/2-sql/2-authoring/contract-ts/src/contract-types.tspackages/2-sql/2-authoring/contract-ts/test/enum-type.factory-form.test.tspackages/3-extensions/postgres/src/contract/define-contract.tspackages/3-targets/6-adapters/postgres/src/core/sql-renderer.tspackages/3-targets/6-adapters/postgres/test/migrations/order-by-enum.integration.test.ts
✅ Files skipped from review due to trivial changes (1)
- examples/prisma-next-demo/test/enum-surface.types.test-d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
…g|number (TML-2852) member<N, V> stays generic over any literal V; enumType constrains member values to the codec descriptors input type via CodecInput<C> (falls back to unknown when the descriptor carries no input phantom). EnumTypeHandle and the db.enums accessor (has/nameOf/ordinalOf) now accept the value union, so int-backed enums accept numeric values. Uniqueness now compares the lowered String(value) form so 1 and "1" collide as build-contract later normalizes them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
buildBoundContract previously spread built.enums into the DSL input, overwriting any scaffold-authored definition.enums. The factory-form type advertises MergeEnums<ScaffoldEnums, FactoryEnums>, so a mixed contract silently lost the scaffold enums at runtime. Merge both sides instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…ntract (TML-2852)
The postgres wrapper collapsed scaffold + factory enums into one Enums generic,
so it could not model defineContract({ enums: { A } }, () => ({ enums: { B } }))
— narrower than the core defineContract it delegates to. Split into
ScaffoldEnums / FactoryEnums and return MergeEnums<ScaffoldEnums, FactoryEnums>,
mirroring the core overload. Exposes MergeEnums from the core builder for reuse.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
… demo test (TML-2852) Remove the redundant comment on contract.ts. The enum accessor probe with a non-member value now annotates the value union, since has() is typed to the codec value union after making member values codec-driven. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Conflict resolutions (done before this session): - packages/3-extensions/sql-orm-client/src/orm.ts: took main's namespace-facet structure; layered our enums root accessor on top (prop === 'enums' guard before the namespace check; OrmClient intersection adds `readonly enums: ContractEnumAccessors<TContract>`). - packages/3-extensions/postgres/test/postgres.test.ts: took main's cast-free namespaced mock shape. - packages/3-extensions/sqlite/test/transaction.test.ts: took main's cast-free namespaced mock shape. TML-2816 adaptations (done in this session): - examples/prisma-next-demo/src/prisma-no-emit/context.ts: createOrmClient now returns the full ORM client (not just client['public']) so that getPriorityEnum can reach db.enums.Priority at the root. - examples/prisma-next-demo/src/queries/get-users-with-posts-no-emit.ts: updated to access the public namespace via db['public'] with a runtime guard, matching the new namespace-qualified ORM API. - packages/2-sql/4-lanes/sql-builder/test/enum-type.field-output.test-d.ts: updated db.User → db.__unbound__.User; Db<C> is now namespace-qualified and the test contract uses __unbound__. - packages/3-extensions/sql-orm-client/test/enum-type.field-output.test-d.ts: passed namespaceId: '__unbound__' to the Collection constructor; the constructor now requires the third options argument. - packages/3-extensions/sqlite/src/runtime/sqlite.ts: broadened unboundNamespace parameter from the narrow index-signature type to object (using blindCast to index); the OrmClient intersection adding enums made the old parameter type incompatible. - test/integration/test/contract-builder.types.test-d.ts: updated enumDb.account → enumDb.public.account; defineContract with flat models places them in the public namespace, which is now the required access path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
The flat root `db.enums` accessor violated the namespace-keyed client root (TML-2816) and last-write-wins collapsed same-named enums across namespaces. Move enums into each namespace facet under a reserved `enums` key: `db.<ns>.enums.<Name>` (postgres `db.orm.public.enums`, sqlite `db.orm.enums` via the unbound projection). `buildEnumsMapForNamespace` resolves only that namespace's `domain.namespaces[ns].enum`, so same-named enums in two namespaces resolve independently. A domain model named `enums` is rejected at facet construction rather than silently shadowed. The facet enum accessor type derives from the emitted `domain.namespaces[ns].enum` entries (literal members) and, for the no-emit built contract, from its flat `enumAccessors` carrier; both preserve the ordered literal `values` tuple. Revert the sqlite `unboundNamespace` widening now that the removed root intersection no longer breaks its narrower param type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Enums move from a flat root `db.enums` into the namespace facet under a reserved `enums` key (`db.<ns>.enums.<Name>`). Record the decision, its rationale (TML-2816 namespace-keyed root, IR alignment, cross-namespace collision), the reserved-name rule (runtime guard now, authoring diagnostic at cutover), and that this is the template for future client-side entity-accessor maps. Repoint the slice spec's `db.enums` references to the facet path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…facet (TML-2852) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…→0.14) The slice is purely additive — enumType-authored enums gain typed I/O, db.<ns>.enums, and declaration-order ORDER BY; PSL enum stays native and fixtures:check is byte-identical for existing contracts. Records the per-PR upgrade-coverage declaration (no consumer action) for both the user and extension-author skills. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…cade (TML-2852) Add buildNamespacedEnums(domain) + the NamespacedEnums<TContract> type to enum-accessor.ts, relocating the per-namespace accessor types out of the orm facet. Enums are contract metadata, the same whether reached through the sql or orm lane, so they belong on the db facade, not inside the orm namespace facet. Remove the orm facet enums key and its reserved-name guard; the orm client returns to a pure namespace map. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Add a top-level db.enums member to the postgres and sqlite clients: a namespace-keyed map projected per target exactly like db.sql / db.orm. Postgres exposes db.enums.<ns>.<Name>; sqlite projects the unbound namespace so users write db.enums.<Name>. Built once at construction from contract.domain via buildNamespacedEnums and frozen; the same map is reused on the transaction context. Tests cover the same-named-enum-in-two-namespaces collision fix on the facade map. Mongo is untouched (no SQL enum surface). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…L-2852) The no-emit demo has no facade, so build the namespace-keyed enum map directly from the validated contract (the same surface the postgres facade exposes as db.enums) and read enums.public.Priority. getPriorityEnum no longer takes a runtime — enums are lane-agnostic contract metadata. The types test derives PriorityValue from the enums surface and asserts the inferred read type with no annotation, preserving the review-comment fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Update design-notes, slice spec, plan, and the 0.13-to-0.14 upgrade-instruction comments from db.<ns>.enums to db.enums.<ns>. Record the decision: enums are lane-agnostic contract metadata, so they live on the db facade as a namespace-keyed map projected per target like db.sql / db.orm; the reserved-name guard is no longer needed because enums are not adjacent to models. This is the template for future client-side entity-accessor maps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…-2852) Use a literal-keyed two-namespace contract so db.enums.public.Role resolves as a static facade proof rather than index-signature access, fixing the test-file typecheck. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
Mongo enums move from a non-goal to scope. The domain enum is framework-level and target-agnostic; Mongo realizes the value-set restriction as a $jsonSchema collection validator (validationLevel: strict) instead of a CHECK constraint, needing no value-set storage IR or migration-ops parallel. Mongo has no native enum and no prior PSL enum, so it skips the transitional keyword and the cutover entirely. Planned as one complete vertical slice (author -> enforce -> read, proven end-to-end against mongodb-memory-server) rather than split, since a non-vertical slice cannot be shown to work. Independent of the SQL track. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…aCodec encodeEnumValue and encodeDefaultLiteralValue both did the same codec-or-fallback dance. Merge them into one encodeViaCodec helper used for both the column-default and enum-lowering paths. Folds the former bare `as JsonValue` on the default path into the unified blindCast. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
…ndation The enum-accessor builders and types are built purely from contract.domain.namespaces[ns].enum (framework ContractEnum), with nothing ORM-specific. Living in sql-orm-client forced the postgres and sqlite facade tests to wholesale-mock @prisma-next/sql-orm-client and pass buildNamespacedEnums through the mock to avoid a construction crash. Move the module to @prisma-next/contract (the 0-foundation layer both SQL facades and a future Mongo facade already depend on) under a new @prisma-next/contract/enum-accessor entrypoint, and relax the type constraint from Contract<SqlStorage> to Contract so it stays lane-neutral. Update the postgres/sqlite facades, the postgres read- surface type test, and the demo no-emit context to the new path; drop the now-unneeded buildNamespacedEnums pass-through from all four facade test mocks. The facade tests now run against the real, unmocked accessor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
The no-emit context hand-wired stack/context/sql and hand-built the enums map with two blindCasts, behind a comment claiming "the no-emit path has no facade to hang db.enums on" — which is wrong: postgres() accepts a TypeScript-authored contract with deferred binding and exposes sql/enums/context/stack directly. Source those from the facade (db.sql.public, db.enums, db.context, db.stack), dropping both blindCasts, the misleading comment, and the custom rawCodecInferer stub (the facade uses the adapter inferer). createOrmClient still uses the orm() builder because the demo binds an externally-created runtime; everything else now comes from db. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
The merge pulled in three enum tests from main (PR #769) that construct runtimes through the deleted createRuntime / mocked-deserializer path. Port them to this branch's design: - postgres.test.ts: the two db.enums facade tests passed contractJson:{} with a mocked deserializeContract and asserted mocks.createRuntime. This branch's postgres.test.ts mocks only the pg boundary, so pass the real twoNamespaceDomain as contract and assert the pool never connects instead of inspecting a (deleted) createRuntime mock. - enum-surface.integration.test.ts: openRuntime built the runtime via instantiateExecutionStack + createRuntime; rebuild it through the postgres({ contract, url, extensions }).connect() facade, matching repositories.integration.test.ts. - runtime.ts: context.ts no longer exports validatedContract (took main's db-facade form), so pass the raw contract; the facade deserializes it. Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mains enum work (#769) renders enum value unions into the field-type maps; our slice nests those maps by namespace id. Reconcile the enum type-tests to the nested shape: index ExtractFieldOutputTypes/InputTypes through the default namespace (public) or the fixtures __unbound__ coordinate, and pass the NsId coordinate to ContractToQC. Enum value unions still resolve per-namespace; no flat-map read reintroduced. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
At a glance
In the demo's own contract,
Postcarries aPriorityenum — and the column is typed as its value union everywhere you touch it:This runs end-to-end against PGlite in
examples/prisma-next-demo— typed read,db.enums.<ns>, declaration-orderORDER BY, and the slice-2CHECKrejecting out-of-union writes.What this decides
An enum becomes a first-class application concept: its value union flows into the static read/write types of both query lanes,
db.enums.<ns>.<Name>exposes it at runtime, andORDER BYon an enum column sorts by declaration order. It works for text and int-backed enums and in both authoring forms (definition and factorydefineContract). Built on the merged substrate (slice 1) and check-constraint enforcement (slice 2), and lands additively — PSLenumstays native until the cutover, so onlyenumType-authored contracts exercise it.How it builds up
valueSetto the value union, on both paths: the authoredDefinition(no-emit) and the emittedcontract.d.ts— the emitter resolves a field'svalueSetref to the enum's member-value union, codec-agnostically (text and int). Both lanes inherit it through the field-output typemap; non-enum fields are unchanged.db.enums.<ns>.<Name>(R6) — a runtime, literal-typed accessor (.values/.members.X/.has/.nameOf/.ordinalOf) built from that namespace's domain enums. Enums are lane-agnostic contract metadata, sodb.enumslives on thedbfacade alongsidetransaction/prepare/raw/context(decided with the query team) — a namespace-keyed map projected per target exactly likedb.sql/db.orm. It matches the IR (domain.namespaces[ns].enum) and lets the same enum name in two namespaces resolve independently. Unbound-namespace targets (sqlite/mongo) getdb.enums.<Name>via the existing per-facade projection. Because enums sit on the facade rather than adjacent to models, no reserved-name guard is needed — a model namedenumsno longer collides.ORDER BY(R8, Postgres) — rendersarray_position(ARRAY[…]::text[], col)over the value-set's ordered values.enumskey threaded through the factory overload, mirroring the definition form), not only the definition form.Scope
Additive / dark. PSL
enumkeeps lowering to native (the repoint is the cutover, TML-2853); member defaults are TML-2855; non-PostgresORDER BY(MySQLFIELD(...)/ SQLiteCASE) is future. Existing fixtures are byte-identical apart from the demo.CI note
The repo-wide
pnpm typecheckis red on inheritedCannot find modulesubpath errors (/contract-builder,/migration,/adapter,/aggregate,/constants) that reproduce on cleanorigin/main— a separate main-health issue, not introduced here.Alternatives considered
Definition— rejected: emission widens the value-set tostring[], erasing the literals.valueSetrestriction layered on top.orderByDeclarationOrder()API — rejected: ordering an enum column by declaration order should just work; the renderer handles it implicitly.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
'user' | 'admin'instead of genericstring) in both read and write operations, with compile-time type checking.db.enums.<namespace>.<EnumName>(lane-agnostic namespace-keyed map).