@aventara/prisma7-adapter 0.1.0-pilot.1 → 0.1.0-pilot.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +33 -20
  2. package/dist/discovery/artifact.loader.d.ts +6 -2
  3. package/dist/discovery/artifact.loader.js +12 -57
  4. package/dist/discovery/capability-surface.interface.d.ts +29 -40
  5. package/dist/discovery/capability-surface.interface.js +0 -12
  6. package/dist/discovery/capability.deriver.d.ts +10 -40
  7. package/dist/discovery/capability.deriver.js +0 -179
  8. package/dist/discovery/client-shape.verifier.d.ts +13 -0
  9. package/dist/discovery/client-shape.verifier.js +93 -0
  10. package/dist/discovery/discovery-artifact.interface.d.ts +55 -30
  11. package/dist/discovery/enum.mapper.d.ts +6 -14
  12. package/dist/discovery/enum.mapper.js +1 -2
  13. package/dist/discovery/field.mapper.d.ts +49 -63
  14. package/dist/discovery/field.mapper.js +0 -161
  15. package/dist/discovery/identifier.mapper.d.ts +38 -77
  16. package/dist/discovery/identifier.mapper.js +0 -64
  17. package/dist/discovery/model.discoverer.d.ts +18 -35
  18. package/dist/discovery/model.discoverer.js +66 -141
  19. package/dist/discovery/operations.deriver.d.ts +10 -38
  20. package/dist/discovery/operations.deriver.js +0 -40
  21. package/dist/discovery/scalar.mapper.d.ts +12 -35
  22. package/dist/discovery/scalar.mapper.js +0 -8
  23. package/dist/discovery/schema-facts.interface.d.ts +26 -25
  24. package/dist/discovery/schema-facts.interface.js +0 -10
  25. package/dist/discovery/schema.reader.d.ts +53 -64
  26. package/dist/discovery/schema.reader.js +0 -174
  27. package/dist/discovery/types.reader.d.ts +0 -4
  28. package/dist/discovery/types.reader.js +2 -177
  29. package/dist/discovery/vocabulary.dictionary.d.ts +36 -75
  30. package/dist/discovery/vocabulary.dictionary.js +2 -152
  31. package/dist/errors/error.normalizer.d.ts +85 -135
  32. package/dist/errors/error.normalizer.js +1 -70
  33. package/dist/errors/prisma-error.reader.d.ts +53 -68
  34. package/dist/errors/prisma-error.reader.js +0 -91
  35. package/dist/execution/first-variant.emulator.d.ts +11 -22
  36. package/dist/execution/first-variant.emulator.js +0 -142
  37. package/dist/execution/operation.executor.d.ts +12 -19
  38. package/dist/execution/operation.executor.js +0 -92
  39. package/dist/execution/result.shaper.d.ts +22 -31
  40. package/dist/execution/result.shaper.js +0 -24
  41. package/dist/execution/transaction.executor.d.ts +36 -73
  42. package/dist/generate/artifact.emitter.d.ts +27 -27
  43. package/dist/generate/artifact.emitter.js +30 -13
  44. package/dist/generate/declared-support.reader.d.ts +11 -11
  45. package/dist/generate/declared-support.reader.js +0 -12
  46. package/dist/generate/discovery.bin.js +0 -10
  47. package/dist/generate/discovery.cli.js +0 -55
  48. package/dist/generate/existence.gate.d.ts +26 -32
  49. package/dist/generate/existence.gate.js +0 -6
  50. package/dist/generate/index.d.ts +10 -12
  51. package/dist/generate/index.js +0 -42
  52. package/dist/generate/node-version.guard.js +0 -12
  53. package/dist/generate/prisma-cli.resolver.d.ts +9 -11
  54. package/dist/generate/prisma-cli.resolver.js +0 -13
  55. package/dist/generate/prisma.generate.d.ts +49 -65
  56. package/dist/generate/prisma.generate.js +13 -123
  57. package/dist/generate/reader-crosscheck.d.ts +36 -60
  58. package/dist/generate/reader-crosscheck.js +5 -41
  59. package/dist/prisma-client.interface.d.ts +14 -19
  60. package/dist/prisma-client.interface.js +0 -6
  61. package/dist/prisma.adapter.d.ts +9 -17
  62. package/dist/prisma.adapter.js +11 -53
  63. package/dist/provider/provider-profile.d.ts +6 -23
  64. package/dist/provider/provider-profile.js +0 -64
  65. package/dist/translation/identifier.addressing.d.ts +15 -22
  66. package/dist/translation/identifier.addressing.js +0 -10
  67. package/dist/translation/mutation.translator.d.ts +19 -43
  68. package/dist/translation/mutation.translator.js +1 -209
  69. package/dist/translation/operation.translator.d.ts +36 -63
  70. package/dist/translation/operation.translator.js +1 -51
  71. package/dist/translation/order-pagination.translator.d.ts +24 -31
  72. package/dist/translation/order-pagination.translator.js +0 -43
  73. package/dist/translation/projection.translator.d.ts +25 -58
  74. package/dist/translation/projection.translator.js +0 -27
  75. package/dist/translation/scalar.conversion.d.ts +15 -26
  76. package/dist/translation/scalar.conversion.js +1 -129
  77. package/dist/translation/where.translator.d.ts +17 -28
  78. package/dist/translation/where.translator.js +0 -83
  79. package/package.json +3 -3
@@ -11,70 +11,11 @@ import { readDeclaredSupport } from "./declared-support.reader.js";
11
11
  import { applyExistenceGate } from "./existence.gate.js";
12
12
  import { resolvePrismaCli } from "./prisma-cli.resolver.js";
13
13
  import { crossCheckReaders, describeReaderFindings, } from "./reader-crosscheck.js";
14
- /**
15
- * Task 3 — the adapter's **generate step**: a devDependency-only executable and
16
- * the only module that runs `prisma` or spawns a process. Nothing under
17
- * `execution/`, and nothing in `discovery/artifact.loader.ts`, imports it; the
18
- * package's build `exclude` keeps `src/generate/**` out of `dist` entirely, so
19
- * a deployed application never carries `typescript`, a PSL reader or a child
20
- * process.
21
- *
22
- * **The order is fixed, and each step earns its place:**
23
- *
24
- * 1. **The pinned `prisma` major** (C-846). PSL's grammar and the generated
25
- * client's shape are version-bound; a major this reader has never seen must
26
- * stop the build rather than produce a wrong `AdapterModel` from a grammar
27
- * nobody read.
28
- * 2. **`@prisma/client/runtime/client` resolves** (§1.2 item 16). This one is
29
- * first among equals, because its failure is *silent*: every generated file
30
- * opens `// @ts-nocheck`, so with the runtime unresolvable a TypeScript
31
- * program over the generated tree reports **zero** diagnostics while
32
- * `FieldRef` degrades to its uninstantiated form and `JsonValue` stops being
33
- * a union — flipping a types-derived nullability from `true` to `false` with
34
- * nothing anywhere to say so **[RUN8]**. The guard is therefore a module
35
- * *resolution*, never a diagnostic count: a diagnostic count is exactly the
36
- * instrument that reads clean in this failure.
37
- * 3. **`prisma generate`**, run by this step itself. The two sources must
38
- * describe one instant; a client generated by some earlier command may
39
- * describe an older schema, and nothing downstream could tell.
40
- * 4. **Both sources read in that same run** — PSL for the semantic model, the
41
- * 🟢 generated types for the capability surface and for existence.
42
- * 5. **The structural checks, in that order.** The existence gate first (T-3.1
43
- * invariant 3), because it decides *which* entities exist and a cross-check
44
- * of an entity nobody admitted is noise; then the reader-versus-reader
45
- * cross-check (`reader-crosscheck.ts`), which answers the reverse direction
46
- * the gate hands it and every other fact both sources carry. Its findings
47
- * stop the step; its two documented exemptions are reported and carried out
48
- * on the result.
49
- * 6. **Emit** one TypeScript module (ADR 0009).
50
- *
51
- * The plan's §9 sketch puts the runtime check *after* `prisma generate`; the
52
- * order above is the brief's, and it is the better one — module resolution
53
- * walks `node_modules` upward from the client directory and does not need the
54
- * directory populated, so nothing is gained by generating output first that the
55
- * step is about to refuse to read.
56
- *
57
- * **The two readers fail differently, and the step keeps them different.** A
58
- * PSL `unhandled` entry means *I located a token I do not model*: there is a
59
- * construct, a spelling and a site to report, so they are aggregated and
60
- * reported together. A types-reader failure means *I cannot read the surface at
61
- * all*: there is nothing to record, so it throws. Inventing an `unhandled`
62
- * vocabulary for the type surface would be a second parallel representation of
63
- * "something went wrong" for no gain.
64
- */
65
- /** The `prisma` major this reader's grammar was written against (C-846). */
66
14
  export const PINNED_PRISMA_MAJOR = 7;
67
15
  export class PrismaGenerateStepError extends Error {
68
16
  name = "PrismaGenerateStepError";
69
17
  }
70
18
  const run = promisify(execFile);
71
- /**
72
- * The Prisma CLI this step runs, chosen as D11 states (`prisma-cli.resolver.ts`):
73
- * `@prisma/prisma7`'s `prisma7` when it is installed, else `prisma` when its
74
- * version is in the adapter's declared range, else a refusal naming both. Looked
75
- * up from this package — where an optional peer is linked — and then from the
76
- * project the step runs in.
77
- */
78
19
  export function resolveInstalledPrismaCli() {
79
20
  const resolution = resolvePrismaCli({
80
21
  searchFrom: [import.meta.url, path.join(process.cwd(), "package.json")],
@@ -85,26 +26,10 @@ export function resolveInstalledPrismaCli() {
85
26
  }
86
27
  return resolution.cli;
87
28
  }
88
- /**
89
- * The version fence's input: the version of the Prisma CLI this step runs, which
90
- * is the tool that produces the client and the grammar the PSL reader was
91
- * written against. (`@prisma/client`'s version is a different fact, fenced at
92
- * load time against the artifact — see `discovery/artifact.loader.ts`.)
93
- */
94
29
  export async function readInstalledPrismaVersion() {
95
30
  return resolveInstalledPrismaCli().version;
96
31
  }
97
- /**
98
- * D4 — the types reader and the runtime-resolution fence use TypeScript's
99
- * classic compiler API. TypeScript 7 has none (plan B3: `createProgram` and
100
- * `resolveModuleName` are absent; the package exports its version and nothing
101
- * else), and before this check the step died on it with a `TypeError` stack.
102
- * Asked of the API itself rather than of the version, because the API is what
103
- * the step needs.
104
- */
105
32
  function assertClassicCompilerApi() {
106
- // Three members, not `Partial<typeof ts>`: a mapped type over the whole
107
- // compiler namespace costs thousands of instantiations to ask three questions.
108
33
  const compiler = ts;
109
34
  if (typeof compiler?.createProgram === "function" &&
110
35
  typeof compiler.resolveModuleName === "function") {
@@ -112,15 +37,6 @@ function assertClassicCompilerApi() {
112
37
  }
113
38
  throw new PrismaGenerateStepError(`the installed \`typescript\` ${compiler?.version ?? "(no version)"} has no classic compiler API (\`createProgram\`), which this step reads the generated client's types with; install \`typescript\` in this adapter's declared range ${readDeclaredSupport().typescript}.`);
114
39
  }
115
- /**
116
- * Whether `@prisma/client/runtime/client` resolves from inside the generated
117
- * tree — the single question §1.2 item 16 turns on.
118
- *
119
- * Asked as a module resolution, never as a diagnostic count, and asked from a
120
- * file path *inside* `clientDirectory` because resolution walks `node_modules`
121
- * upward from the importer. The directory need not exist yet: resolution is a
122
- * path question, which is what lets this fence run before `prisma generate`.
123
- */
124
40
  export function clientRuntimeResolves(clientDirectory) {
125
41
  const containing = path.join(path.resolve(clientDirectory), "models", "_.ts");
126
42
  const resolved = ts.resolveModuleName("@prisma/client/runtime/client", containing, {
@@ -132,8 +48,6 @@ export function clientRuntimeResolves(clientDirectory) {
132
48
  }
133
49
  export function createPrismaGenerateToolchain() {
134
50
  assertClassicCompilerApi();
135
- // Resolved once per toolchain, so the version the fence reads and the CLI the
136
- // step runs cannot be two different installs.
137
51
  let cli;
138
52
  const prismaCli = () => {
139
53
  cli ??= resolveInstalledPrismaCli();
@@ -143,10 +57,6 @@ export function createPrismaGenerateToolchain() {
143
57
  installedPrismaVersion: async () => prismaCli().version,
144
58
  clientRuntimeResolves,
145
59
  runPrismaGenerate: async (schema) => {
146
- // The CLI is the one resolved beside THIS package (or the project), run
147
- // by path with this `node`: invoking whatever `prisma` is on PATH, or at
148
- // a workspace root, can be a different install — or produce no output at
149
- // all, which looks exactly like success.
150
60
  await run(process.execPath, [
151
61
  prismaCli().entry,
152
62
  "generate",
@@ -159,21 +69,6 @@ export function createPrismaGenerateToolchain() {
159
69
  writeArtifact: writeArtifactAtomically,
160
70
  };
161
71
  }
162
- /**
163
- * Writes the artifact so that no reader can ever open a partial file.
164
- *
165
- * Other packages' tests import the artifact while this package's `test` script
166
- * regenerates it, and turbo runs the two in parallel. `writeFile` truncates in
167
- * place, so a concurrent `import` could read an empty module — measured: the
168
- * client's pilot fixture failed with ``Cannot read properties of undefined
169
- * (reading 'enums')``. Two rules close it:
170
- *
171
- * - **unchanged bytes are not written** — generation is deterministic, so the
172
- * common re-run touches nothing (and keeps the file's mtime for incremental
173
- * tooling);
174
- * - **changed bytes land by `rename`** from a sibling temporary file, which POSIX
175
- * makes atomic within one directory: a reader sees the old file or the new one.
176
- */
177
72
  async function writeArtifactAtomically(file, source) {
178
73
  const current = await readFile(file, "utf8").catch(() => undefined);
179
74
  if (current === source) {
@@ -197,10 +92,10 @@ function majorOf(version) {
197
92
  function assertPinnedMajor(version) {
198
93
  const major = majorOf(version);
199
94
  if (major === undefined) {
200
- throw new PrismaGenerateStepError(`the installed \`prisma\` reports \`${version}\`, which is not a major.minor.patch version, so its major cannot be fenced against the pinned major ${PINNED_PRISMA_MAJOR} (C-846).`);
95
+ throw new PrismaGenerateStepError(`the installed \`prisma\` reports \`${version}\`, which is not a major.minor.patch version, so its major cannot be fenced against the pinned major ${PINNED_PRISMA_MAJOR}.`);
201
96
  }
202
97
  if (major !== PINNED_PRISMA_MAJOR) {
203
- throw new PrismaGenerateStepError(`the installed \`prisma\` is major ${major} and this adapter's readers are written against pinned major ${PINNED_PRISMA_MAJOR} (C-846). PSL's grammar and the generated client's shape are version-bound, so a new major stops the build here rather than producing an AdapterModel from a grammar nobody has read.`);
98
+ throw new PrismaGenerateStepError(`the installed \`prisma\` is major ${major} and this adapter's readers are written against pinned major ${PINNED_PRISMA_MAJOR}. PSL's grammar and the generated client's shape are version-bound, so a new major stops the build here rather than producing an AdapterModel from a grammar nobody has read.`);
204
99
  }
205
100
  }
206
101
  function assertClientRuntimeResolves(toolchain, clientDirectory) {
@@ -220,22 +115,12 @@ function assertNothingUnhandled(facts) {
220
115
  }
221
116
  export async function runPrismaGenerateStep(request) {
222
117
  const toolchain = request.toolchain ?? createPrismaGenerateToolchain();
223
- // 1. The version fence, before anything is run or read. The version is read
224
- // ONCE and carried to the emit: a second read could answer differently, and
225
- // an artifact stamped with a version the fence never saw is exactly the
226
- // artifact the fence exists to prevent.
227
118
  const prismaVersion = await toolchain.installedPrismaVersion();
228
119
  assertPinnedMajor(prismaVersion);
229
- // 2. The silent one.
230
120
  assertClientRuntimeResolves(toolchain, request.clientDirectory);
231
- // 3. This step generates the client it is about to read.
232
121
  await toolchain.runPrismaGenerate(request.schema);
233
- // 4. Both sources, in that same run.
234
122
  const facts = await toolchain.readSchema(request.schema);
235
123
  assertNothingUnhandled(facts);
236
- // The types reader's failure mode is a throw with nothing to record, so it
237
- // is re-raised as this step's own error with the cause preserved rather than
238
- // aggregated into a vocabulary invented for it.
239
124
  let surface;
240
125
  try {
241
126
  surface = toolchain.readSurface(request.clientDirectory);
@@ -243,17 +128,12 @@ export async function runPrismaGenerateStep(request) {
243
128
  catch (cause) {
244
129
  throw new PrismaGenerateStepError(`the capability surface at ${request.clientDirectory} could not be read: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
245
130
  }
246
- // 5. The structural checks. The gate decides existence; the cross-check then
247
- // compares every fact both readers carry. A finding means one of the two
248
- // READERS is wrong, never that the schema is — so the step stops rather than
249
- // emitting a model it cannot justify.
250
131
  const { admitted, excluded } = applyExistenceGate(facts, surface);
251
132
  const { findings, exemptions } = crossCheckReaders(facts, surface);
252
133
  if (findings.length > 0) {
253
134
  throw new PrismaGenerateStepError(describeReaderFindings(findings));
254
135
  }
255
- // 6. Emit.
256
- const { model, translation, referentialActions } = request.composeModel({
136
+ const { model, translation, referentialActions, createRequirements } = request.composeModel({
257
137
  facts,
258
138
  surface,
259
139
  admitted,
@@ -264,6 +144,8 @@ export async function runPrismaGenerateStep(request) {
264
144
  model,
265
145
  translation,
266
146
  referentialActions,
147
+ client: clientShapeOf(surface),
148
+ createRequirements,
267
149
  });
268
150
  await toolchain.writeArtifact(request.artifactFile, source);
269
151
  const modelHash = await computeModelHash(model);
@@ -276,3 +158,11 @@ export async function runPrismaGenerateStep(request) {
276
158
  exemptions,
277
159
  };
278
160
  }
161
+ function clientShapeOf(surface) {
162
+ return Object.fromEntries(Object.keys(surface.resources)
163
+ .sort()
164
+ .map((model) => [
165
+ model,
166
+ Object.keys(surface.resources[model]?.scalarFields ?? {}).sort(),
167
+ ]));
168
+ }
@@ -2,73 +2,56 @@ import type { PrismaCapabilitySurface } from "../discovery/capability-surface.in
2
2
  import type { PrismaIdentifierMapping } from "../discovery/identifier.mapper.js";
3
3
  import type { PrismaSchemaFacts, PrismaSchemaModel } from "../discovery/schema-facts.interface.js";
4
4
  /**
5
- * Task 6 — the **reader-versus-reader cross-check**: PSL's facts against the 🟢
6
- * generated types' facts, at the one instant the generate step reads both.
7
- *
8
5
  * # It is a self-test of the two readers, not a check on the developer
9
6
  *
10
7
  * That framing decides every case in this module. A finding here does not mean
11
- * *your schema is wrong*; it means **one of this adapter's two readers is
12
- * wrong**, or the client it read was generated from a different schema than the
13
- * one PSL was read from. The remedy is never "edit the schema" — it is
14
- * regenerate, or fix a reader.
8
+ * *your schema is wrong*; it means **one of this adapter's two readers is wrong**,
9
+ * or the client it read was generated from a different schema than the one PSL was
10
+ * read from. The remedy is never "edit the schema" — it is regenerate, or fix a
11
+ * reader.
15
12
  *
16
- * It follows that the check can only see facts **both** sources carry. That is
17
- * not a gap to be closed by trying harder; it is arithmetic. The honest limit is
13
+ * It follows that the check can only see facts **both** sources carry. That is not
14
+ * a gap to be closed by trying harder; it is arithmetic. The honest limit is
18
15
  * measured below.
19
16
  *
20
17
  * # What it cannot see, measured rather than described
21
18
  *
22
- * **Re-measured 2026-10-02 on BOTH Phase 8 engines (prisma 7.10.0), by
23
- * `reader-crosscheck.spec.ts`'s "what the cross-check cannot see, by arithmetic"
24
- * case.** Four *real semantic* schema edits, each applied to the schema text
25
- * alone with the generated client left where it was, produce **zero findings
26
- * between them — eight measurements, eight zeros**:
27
- *
28
- * | edit | sqlite | postgres |
29
- * | --------------------------------------------------- | ------ | -------- |
30
- * | `@default(uuid())` → `@default(cuid())` | 0 | 0 |
31
- * | `@default(now())` → `@default(dbgenerated("…"))` | 0 | 0 |
32
- * | `@default(BRONZE)` → `@default(GOLD)` | 0 | 0 |
33
- * | `@relation("Mentorship")` → `@relation("Coaching")` | 0 | 0 |
19
+ * | edit | sqlite | postgres | |
20
+ * --------------------------------------------------- | ------ | -------- | |
21
+ * `@default(uuid())` → `@default(cuid())` | 0 | 0 | | `@default(now())` →
22
+ * `@default(dbgenerated("…"))` | 0 | 0 | | `@default(BRONZE)` → `@default(GOLD)` |
23
+ * 0 | 0 | | `@relation("Mentorship")` → `@relation("Coaching")` | 0 | 0 |
34
24
  *
35
25
  * Every one of them changes what the database does. None of them is visible to a
36
26
  * cross-check, because none of them reaches the 🟢 surface at all: a `@default`
37
- * argument's *content* and a relation's *name* are PSL-exclusive (the name
38
- * appears nowhere on the generated surface, which is why `field.mapper.ts` pairs
39
- * edges from PSL alone). A fifth class is the mirror image: which filter
40
- * operators a `<Kind>Filter` exposes is 🟢-exclusive, so a reader that
41
- * under-reads one is equally invisible — seeded as the sixth edit in
27
+ * argument's *content* and a relation's *name* are PSL-exclusive (the name appears
28
+ * nowhere on the generated surface, which is why `field.mapper.ts` pairs edges
29
+ * from PSL alone). A fifth class is the mirror image: which filter operators a
30
+ * `<Kind>Filter` exposes is 🟢-exclusive, so a reader that under-reads one is
31
+ * equally invisible — seeded as the sixth edit in
42
32
  * `__fixtures__/diverged-surface.ts` and caught by nothing.
43
33
  *
44
34
  * **So this module is not a schema-drift detector and must not be trusted as
45
- * one.** Staleness — a developer who edits `schema.prisma` and does not re-run
46
- * the generate step — is prevented by the step *running `prisma generate`
47
- * itself*, so that both sources describe one instant (T-3.2, the architect's
48
- * 2026-09-27 ruling). What survives that ruling catches a different failure
49
- * again: `modelHash` proves the artifact is internally intact and the
50
- * `prismaVersion` major fence catches a client that moved underneath it. Neither
51
- * notices a schema that moved, and neither does this.
35
+ * one.** Staleness — a developer who edits `schema.prisma` and does not re-run the
36
+ * generate step — is prevented by the step *running `prisma generate` itself*, so
37
+ * that both sources describe one instant. What survives that ruling catches a
38
+ * different failure again: `modelHash` proves the artifact is internally intact
39
+ * and the `prismaVersion` major fence catches a client that moved underneath it.
40
+ * Neither notices a schema that moved, and neither does this.
52
41
  *
53
42
  * # Two exemptions, and both are explanations rather than allowances
54
43
  *
55
44
  * A freshly generated pair is not silent, and the two cases where it speaks are
56
45
  * *understood*:
57
46
  *
58
- * 1. **`json-null-is-a-value`** — the one disagreement a pristine pair produces
59
- * on both engines. JSON null is a *value*, so `JsonFilterBase` writes `|
60
- * null` operands (`array_starts_with`, `array_ends_with`, and
61
- * `array_contains` on PostgreSQL) on a column PSL declares **required**. A
62
- * plausible "the operands mention null, therefore the column is nullable"
63
- * rule is right for every other scalar and wrong for every `Json` one, which
64
- * is why nullability comes from PSL's `?` and nothing else (T-3.1 invariant
65
- * 4) and why this one is exempted by PSL *type* rather than waved through.
66
- * 2. **`nullable-participant-key`** — `Author`'s `tenantId_handle` `AtLeast` key
67
- * has no identifier behind it, because C-815 omits a tuple with a nullable
68
- * participant (`handle String?`) while the generator still emits a key for
69
- * it. C-814's rule is therefore a **subset relation with one closed
70
- * explanation, never an equality**: equality is unsatisfiable by
71
- * construction.
47
+ * 1. **`json-null-is-a-value`** — the one disagreement a pristine pair produces on
48
+ * both engines. JSON null is a *value*, so `JsonFilterBase` writes `| null`
49
+ * operands (`array_starts_with`, `array_ends_with`, and `array_contains` on
50
+ * PostgreSQL) on a column PSL declares **required**. A plausible "the operands
51
+ * mention null, therefore the column is nullable" rule is right for every other
52
+ * scalar and wrong for every `Json` one, which is why nullability comes from
53
+ * PSL's `?` and nothing else and why this one is exempted by PSL *type* rather
54
+ * than waved through.
72
55
  *
73
56
  * Each exemption is **recorded with its reason**, not dropped. A spec can then
74
57
  * assert both by name and by reason, so an unexplained third disagreement fails
@@ -77,17 +60,17 @@ import type { PrismaSchemaFacts, PrismaSchemaModel } from "../discovery/schema-f
77
60
  * # What belongs to other modules, and is deliberately not restated here
78
61
  *
79
62
  * - **A PSL-declared entity the 🟢 types do not expose** is the EXISTENCE GATE's
80
- * answer (`existence.gate.ts`, C-808): it decides admission and whether the
81
- * absence is explained. This module answers only the reverse direction — a
82
- * type-exclusive resource or field — which the gate's own header hands to it.
63
+ * answer: it decides admission and whether the absence is explained. This module
64
+ * answers only the reverse direction — a type-exclusive resource or field —
65
+ * which the gate's own header hands to it.
83
66
  * - **A `FieldRef` token that disagrees with the written PSL type** is
84
67
  * `field.mapper.ts`'s, where it *throws*: the two readers describing one field
85
68
  * two ways is not a schema a Contract can be compiled from. Restating the rule
86
69
  * here would be two owners for one fact.
87
70
  * - **A create-optional marker with no PSL cause** is `field.mapper.ts`'s
88
71
  * `crossCheckCreateOptional`, run by `model.discoverer.ts` where the field
89
- * projections already exist. It needs the projected lifecycle, which this
90
- * module does not compute.
72
+ * projections already exist. It needs the projected lifecycle, which this module
73
+ * does not compute.
91
74
  */
92
75
  /** A disagreement about a fact both sources carry: one of the readers is wrong. */
93
76
  export type PrismaReaderFindingKind =
@@ -97,7 +80,6 @@ export type PrismaReaderFindingKind =
97
80
  | "field-exclusive-to-types"
98
81
  /** PSL's `?` and the operands the 🟢 filter admits disagree. */
99
82
  | "nullability"
100
- /** C-814's subset rule is broken in one of its two directions. */
101
83
  | "identifier-key"
102
84
  /** An enum is exposed and not declared, declared and not exposed, or its members differ. */
103
85
  | "enum";
@@ -105,10 +87,7 @@ export type PrismaReaderFinding = {
105
87
  readonly kind: PrismaReaderFindingKind;
106
88
  /**
107
89
  * What the finding is about: `Model`, `Model.field`, `Enum`, or
108
- * `Model:whereUniqueKey`. The **colon** marks an addressing key rather than a
109
- * field path, because a `@@unique(name:)` key may be spelled like a column and
110
- * reading one as the other is how C-814's cross-check once refused a schema
111
- * Prisma accepts.
90
+ * `Model:whereUniqueKey`.
112
91
  */
113
92
  readonly entity: string;
114
93
  readonly message: string;
@@ -127,14 +106,11 @@ export type PrismaReaderCrossCheck = {
127
106
  /** Reported, never fatal. */
128
107
  readonly exemptions: readonly PrismaReaderExemption[];
129
108
  };
130
- /** One direction of C-814's subset rule, as a finding a spec can match on. */
131
109
  export type PrismaIdentifierKeyFinding = {
132
110
  readonly key: string;
133
111
  readonly reason: string;
134
112
  };
135
113
  /**
136
- * C-814's two directions, in one total function.
137
- *
138
114
  * Exported because it has **two callers that must check one rule**: the generate
139
115
  * step, through {@link crossCheckReaders}, and `identifier.mapper.spec.ts`, whose
140
116
  * own cases were written against a local copy while this file did not exist. The
@@ -2,29 +2,17 @@ import { mapPrismaEnum } from "../discovery/enum.mapper.js";
2
2
  import { mapPrismaIdentifiers } from "../discovery/identifier.mapper.js";
3
3
  const JSON_EXEMPTION_REASON = "`Json` admits JSON null as a VALUE, so the generated `JsonFilterBase` writes `| null` operands " +
4
4
  "(`array_starts_with`, `array_ends_with`, and `array_contains` on PostgreSQL) on a column PSL " +
5
- "declares required. Nullability therefore comes from PSL's `?` and nothing else (T-3.1 invariant 4).";
5
+ "declares required. Nullability therefore comes from PSL's `?` and nothing else.";
6
6
  function nullableParticipantReason(key, nullableMembers) {
7
- return (`C-815 omits the uniqueness criterion behind \`${key}\` because [${nullableMembers.join(", ")}] ` +
7
+ return (`The model omits the uniqueness criterion behind \`${key}\` because [${nullableMembers.join(", ")}] ` +
8
8
  "is nullable and SQL uniqueness does not constrain NULLs, while the generator still emits an " +
9
- "`AtLeast` key for it. C-814's rule is a subset relation with this one closed explanation, " +
9
+ "`AtLeast` key for it. The rule is a subset relation with this one closed explanation, " +
10
10
  "never an equality: equality is unsatisfiable by construction.");
11
11
  }
12
- /**
13
- * C-814's two directions, in one total function.
14
- *
15
- * Exported because it has **two callers that must check one rule**: the generate
16
- * step, through {@link crossCheckReaders}, and `identifier.mapper.spec.ts`, whose
17
- * own cases were written against a local copy while this file did not exist. The
18
- * explanation is re-derived **from PSL**, never taken on the mapping's word —
19
- * otherwise a mapper that invented a nullable participant would clear its own
20
- * exemption.
21
- */
22
12
  export function crossCheckIdentifierKeys(declared, mapping, uniqueKeys) {
23
13
  const findings = [];
24
14
  const nullableInPsl = new Set(declared.fields.filter((f) => f.optional).map((f) => f.name));
25
15
  const identifierKeys = mapping.identifiers.map((address) => address.key);
26
- // (a) An identifier the types do not key means the PSL reader invented a
27
- // constraint.
28
16
  for (const key of identifierKeys) {
29
17
  if (!uniqueKeys.includes(key)) {
30
18
  findings.push({
@@ -33,8 +21,6 @@ export function crossCheckIdentifierKeys(declared, mapping, uniqueKeys) {
33
21
  });
34
22
  }
35
23
  }
36
- // (b) A key with no identifier must be explained by C-815's nullable
37
- // participant and by nothing else.
38
24
  for (const key of uniqueKeys) {
39
25
  if (identifierKeys.includes(key)) {
40
26
  continue;
@@ -50,7 +36,7 @@ export function crossCheckIdentifierKeys(declared, mapping, uniqueKeys) {
50
36
  if (omission.reason !== "nullable-participant") {
51
37
  findings.push({
52
38
  key,
53
- reason: `omitted as ${omission.reason}, which C-815 does not license`,
39
+ reason: `omitted as ${omission.reason}, which no nullable participant explains`,
54
40
  });
55
41
  continue;
56
42
  }
@@ -64,14 +50,6 @@ export function crossCheckIdentifierKeys(declared, mapping, uniqueKeys) {
64
50
  }
65
51
  return findings;
66
52
  }
67
- /**
68
- * The whole cross-check: a pure function of the two readers' output.
69
- *
70
- * The order is a property of the model and not of how the readers happened to
71
- * build their records: resources in PSL declaration order, and within one
72
- * resource the type-exclusive fields, then nullability in PSL field order, then
73
- * the identifier keys; enums last.
74
- */
75
53
  export function crossCheckReaders(facts, surface) {
76
54
  const findings = [];
77
55
  const exemptions = [];
@@ -89,8 +67,6 @@ export function crossCheckReaders(facts, surface) {
89
67
  for (const model of facts.models) {
90
68
  const exposed = surface.resources[model.name];
91
69
  if (exposed === undefined) {
92
- // A PSL-declared model the types do not expose is the existence gate's
93
- // answer, with its own explained/unexplained split. Not restated here.
94
70
  continue;
95
71
  }
96
72
  const declaredFields = new Set(model.fields.map((f) => f.name));
@@ -110,16 +86,8 @@ export function crossCheckReaders(facts, surface) {
110
86
  for (const field of model.fields) {
111
87
  const scalar = exposed.scalarFields[field.name];
112
88
  if (scalar === undefined) {
113
- // A relation edge's nullability is checked by `field.mapper.ts`, which
114
- // has the payload's own `nullable` and throws on a disagreement.
115
89
  continue;
116
90
  }
117
- // A scalar LIST is out of scope, and the exclusion is a scoping decision
118
- // rather than a measurement: `StringNullableListFilter.equals` is
119
- // `string[] | null` on a `String[]` column PSL declares required
120
- // (`Post.tags` on PostgreSQL), because the `null` there is about the
121
- // absence of the whole list and not about an element. The rule below is a
122
- // per-column nullability signal only where the column holds one value.
123
91
  if (field.list) {
124
92
  continue;
125
93
  }
@@ -149,7 +117,7 @@ export function crossCheckReaders(facts, surface) {
149
117
  findings.push({
150
118
  kind: "identifier-key",
151
119
  entity: `${model.name}:${finding.key}`,
152
- message: `\`${model.name}\`'s \`WhereUniqueInput\` key \`${finding.key}\` is ${finding.reason} (C-814).`,
120
+ message: `\`${model.name}\`'s \`WhereUniqueInput\` key \`${finding.key}\` is ${finding.reason}.`,
153
121
  });
154
122
  }
155
123
  for (const omission of mapping.omitted) {
@@ -175,9 +143,6 @@ export function crossCheckReaders(facts, surface) {
175
143
  });
176
144
  }
177
145
  for (const declared of facts.enums) {
178
- // The member comparison is the ENUM MAPPER's own rule, invoked rather than
179
- // restated: declaration order is semantic (§19.2) and enters the Contract
180
- // hash, so "diverged" is the same judgement in both callers.
181
146
  const mapped = mapPrismaEnum({
182
147
  declared,
183
148
  exposed: surface.enums[declared.name],
@@ -193,7 +158,6 @@ export function crossCheckReaders(facts, surface) {
193
158
  }
194
159
  return { findings, exemptions };
195
160
  }
196
- /** The sentence the generate step refuses with. Built here, so one wording exists. */
197
161
  export function describeReaderFindings(findings) {
198
162
  return [
199
163
  "the two discovery readers disagree about facts they both carry, which means one of THEM is wrong — not the schema:",
@@ -1,26 +1,21 @@
1
1
  import type { PrismaIsolationLevel } from "./provider/provider-profile.js";
2
2
  /**
3
- * T-1 — the minimal structural shape the adapter actually uses, rather than
4
- * the full generated client type. This is the seam that keeps the adapter
5
- * compilable with no generated client in scope and testable with a recording
6
- * double.
3
+ * This is the seam that keeps the adapter compilable with no generated client in
4
+ * scope and testable with a recording double.
7
5
  *
8
- * **Why there is no `[model: string]` index signature here, though T-1
9
- * describes the host as "an index of model delegates plus `$transaction`":**
10
- * a generated `PrismaClient` is a *class*, and TypeScript grants an implicit
11
- * index signature only to object-literal type aliases. Observed on 2026-09-23
12
- * with the real generated client and `tsc -p tsconfig.typecheck.json`:
6
+ * Observed on 2026-09-23 with the real generated client and `tsc -p
7
+ * tsconfig.typecheck.json`:
13
8
  *
14
- * error TS2322: Type 'PrismaClient' is not assignable to type 'HostWithIndex'.
15
- * Index signature for type 'string' is missing in type
16
- * 'PrismaClient<never, GlobalOmitConfig | undefined, DefaultArgs>'.
9
+ * error TS2322: Type 'PrismaClient' is not assignable to type 'HostWithIndex'.
10
+ * Index signature for type 'string' is missing in type 'PrismaClient<never,
11
+ * GlobalOmitConfig | undefined, DefaultArgs>'.
17
12
  *
18
- * The same probe with the index signature removed compiled clean. So the
19
- * public constraint declares only `$transaction`, and the delegate index is
20
- * reached through `prismaDelegates()` below — one named seam, in one place,
21
- * instead of a constraint no real client can satisfy.
13
+ * The same probe with the index signature removed compiled clean. So the public
14
+ * constraint declares only `$transaction`, and the delegate index is reached
15
+ * through `prismaDelegates()` below — one named seam, in one place, instead of a
16
+ * constraint no real client can satisfy.
22
17
  */
23
- /** Adapter-owned transaction options; never Aventara public configuration (D-806). */
18
+ /** Adapter-owned transaction options; never Aventara public configuration. */
24
19
  export type PrismaTransactionOptions = {
25
20
  /** Prisma's own default is 2000 ms **[PKG, `runtime/client.d.ts`]**. */
26
21
  readonly maxWait?: number;
@@ -31,8 +26,8 @@ export type PrismaTransactionOptions = {
31
26
  /** One Prisma model delegate, as far as the adapter is concerned. */
32
27
  export type PrismaDelegate = Readonly<Record<string, unknown>>;
33
28
  /**
34
- * The host the adapter is handed. `$transaction` is declared in its callback
35
- * form only: the array overload is never reached (C-803, spec §14.5).
29
+ * The host the adapter is handed. `$transaction` is declared in its callback form
30
+ * only: the array overload is never reached.
36
31
  */
37
32
  export interface PrismaDelegateHost {
38
33
  $transaction<T>(work: (transactional: PrismaDelegateHost) => Promise<T>, options?: PrismaTransactionOptions): Promise<T>;
@@ -1,9 +1,3 @@
1
- /**
2
- * The single place the structural gap between a generated client (a class,
3
- * with no index signature) and a delegate index is crossed. Every delegate
4
- * lookup in the adapter goes through here, so there is exactly one assertion
5
- * to audit rather than one per call site.
6
- */
7
1
  export function prismaDelegates(client) {
8
2
  return client;
9
3
  }
@@ -3,33 +3,25 @@ import type { PrismaAdapterModel, PrismaDiscoveryArtifact } from "./discovery/di
3
3
  import type { PrismaDelegateHost, PrismaTransactionOptions } from "./prisma-client.interface.js";
4
4
  import { type PrismaDriver, type PrismaProvider } from "./provider/provider-profile.js";
5
5
  /**
6
- * T-1 — the adapter's only public entry point, and the composition root.
7
- *
8
6
  * `TClient` stays generic so a caller's generated client type flows through
9
- * without this package naming it: `client` is the one and only place a Prisma
10
- * type enters the public surface, and it enters as the caller's own parameter
11
- * (C-802).
7
+ * without this package naming it: `client` is the one and only place a Prisma type
8
+ * enters the public surface, and it enters as the caller's own parameter.
12
9
  */
13
10
  export type PrismaAdapterOptions<TClient extends PrismaDelegateHost, M extends PrismaAdapterModel = PrismaAdapterModel> = {
14
- /** Application-owned, already-constructed client (spec §18.2). */
11
+ /** Application-owned, already-constructed client. */
15
12
  readonly client: TClient;
16
13
  /**
17
- * The generate-time discovery artifact this adapter loads and checks
18
- * (D-802, T-3.2).
14
+ * The generate-time discovery artifact this adapter loads and checks.
19
15
  *
20
- * This is the one and only inference site for `M`: the emitted artifact
21
- * module's `model` is a literal type, and `PrismaDiscoveryArtifact<M>`
22
- * expands to a bare `model: M` property so the compiler reads that literal
23
- * off the argument. Route `M` through a conditional or a mapped type here
24
- * and it stops inferring, falls back to its default with **no compile
25
- * error**, and every narrowing downstream of the model quietly widens to
26
- * `string` — the trap core hit at S2 with `Cfg`.
16
+ * This is the one and only inference site for `M`: the emitted artifact module's
17
+ * `model` is a literal type, and `PrismaDiscoveryArtifact<M>` expands to a bare
18
+ * `model: M` property so the compiler reads that literal off the argument.
27
19
  */
28
20
  readonly discovery: PrismaDiscoveryArtifact<M>;
29
- /** Declared, never sniffed from client internals (D-804). */
21
+ /** Declared, never sniffed from client internals. */
30
22
  readonly provider: PrismaProvider;
31
23
  readonly driver: PrismaDriver;
32
- /** Adapter-owned, never Aventara public configuration (D-806). */
24
+ /** Adapter-owned, never Aventara public configuration. */
33
25
  readonly transactionOptions?: PrismaTransactionOptions;
34
26
  };
35
27
  /**