@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.
- package/README.md +33 -20
- package/dist/discovery/artifact.loader.d.ts +6 -2
- package/dist/discovery/artifact.loader.js +12 -57
- package/dist/discovery/capability-surface.interface.d.ts +29 -40
- package/dist/discovery/capability-surface.interface.js +0 -12
- package/dist/discovery/capability.deriver.d.ts +10 -40
- package/dist/discovery/capability.deriver.js +0 -179
- package/dist/discovery/client-shape.verifier.d.ts +13 -0
- package/dist/discovery/client-shape.verifier.js +93 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +55 -30
- package/dist/discovery/enum.mapper.d.ts +6 -14
- package/dist/discovery/enum.mapper.js +1 -2
- package/dist/discovery/field.mapper.d.ts +49 -63
- package/dist/discovery/field.mapper.js +0 -161
- package/dist/discovery/identifier.mapper.d.ts +38 -77
- package/dist/discovery/identifier.mapper.js +0 -64
- package/dist/discovery/model.discoverer.d.ts +18 -35
- package/dist/discovery/model.discoverer.js +66 -141
- package/dist/discovery/operations.deriver.d.ts +10 -38
- package/dist/discovery/operations.deriver.js +0 -40
- package/dist/discovery/scalar.mapper.d.ts +12 -35
- package/dist/discovery/scalar.mapper.js +0 -8
- package/dist/discovery/schema-facts.interface.d.ts +26 -25
- package/dist/discovery/schema-facts.interface.js +0 -10
- package/dist/discovery/schema.reader.d.ts +53 -64
- package/dist/discovery/schema.reader.js +0 -174
- package/dist/discovery/types.reader.d.ts +0 -4
- package/dist/discovery/types.reader.js +2 -177
- package/dist/discovery/vocabulary.dictionary.d.ts +36 -75
- package/dist/discovery/vocabulary.dictionary.js +2 -152
- package/dist/errors/error.normalizer.d.ts +85 -135
- package/dist/errors/error.normalizer.js +1 -70
- package/dist/errors/prisma-error.reader.d.ts +53 -68
- package/dist/errors/prisma-error.reader.js +0 -91
- package/dist/execution/first-variant.emulator.d.ts +11 -22
- package/dist/execution/first-variant.emulator.js +0 -142
- package/dist/execution/operation.executor.d.ts +12 -19
- package/dist/execution/operation.executor.js +0 -92
- package/dist/execution/result.shaper.d.ts +22 -31
- package/dist/execution/result.shaper.js +0 -24
- package/dist/execution/transaction.executor.d.ts +36 -73
- package/dist/generate/artifact.emitter.d.ts +27 -27
- package/dist/generate/artifact.emitter.js +30 -13
- package/dist/generate/declared-support.reader.d.ts +11 -11
- package/dist/generate/declared-support.reader.js +0 -12
- package/dist/generate/discovery.bin.js +0 -10
- package/dist/generate/discovery.cli.js +0 -55
- package/dist/generate/existence.gate.d.ts +26 -32
- package/dist/generate/existence.gate.js +0 -6
- package/dist/generate/index.d.ts +10 -12
- package/dist/generate/index.js +0 -42
- package/dist/generate/node-version.guard.js +0 -12
- package/dist/generate/prisma-cli.resolver.d.ts +9 -11
- package/dist/generate/prisma-cli.resolver.js +0 -13
- package/dist/generate/prisma.generate.d.ts +49 -65
- package/dist/generate/prisma.generate.js +13 -123
- package/dist/generate/reader-crosscheck.d.ts +36 -60
- package/dist/generate/reader-crosscheck.js +5 -41
- package/dist/prisma-client.interface.d.ts +14 -19
- package/dist/prisma-client.interface.js +0 -6
- package/dist/prisma.adapter.d.ts +9 -17
- package/dist/prisma.adapter.js +11 -53
- package/dist/provider/provider-profile.d.ts +6 -23
- package/dist/provider/provider-profile.js +0 -64
- package/dist/translation/identifier.addressing.d.ts +15 -22
- package/dist/translation/identifier.addressing.js +0 -10
- package/dist/translation/mutation.translator.d.ts +19 -43
- package/dist/translation/mutation.translator.js +1 -209
- package/dist/translation/operation.translator.d.ts +36 -63
- package/dist/translation/operation.translator.js +1 -51
- package/dist/translation/order-pagination.translator.d.ts +24 -31
- package/dist/translation/order-pagination.translator.js +0 -43
- package/dist/translation/projection.translator.d.ts +25 -58
- package/dist/translation/projection.translator.js +0 -27
- package/dist/translation/scalar.conversion.d.ts +15 -26
- package/dist/translation/scalar.conversion.js +1 -129
- package/dist/translation/where.translator.d.ts +17 -28
- package/dist/translation/where.translator.js +0 -83
- 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}
|
|
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}
|
|
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
|
-
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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
|
-
*
|
|
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`.
|
|
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
|
|
5
|
+
"declares required. Nullability therefore comes from PSL's `?` and nothing else.";
|
|
6
6
|
function nullableParticipantReason(key, nullableMembers) {
|
|
7
|
-
return (`
|
|
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.
|
|
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
|
|
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}
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
}
|
package/dist/prisma.adapter.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
22
|
-
*
|
|
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
|
|
21
|
+
/** Declared, never sniffed from client internals. */
|
|
30
22
|
readonly provider: PrismaProvider;
|
|
31
23
|
readonly driver: PrismaDriver;
|
|
32
|
-
/** Adapter-owned, never Aventara public configuration
|
|
24
|
+
/** Adapter-owned, never Aventara public configuration. */
|
|
33
25
|
readonly transactionOptions?: PrismaTransactionOptions;
|
|
34
26
|
};
|
|
35
27
|
/**
|