@aventara/prisma7-adapter 0.1.0-pilot.2 → 0.1.0-pilot.4
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 +22 -22
- package/dist/discovery/artifact.loader.d.ts +2 -7
- package/dist/discovery/artifact.loader.js +4 -0
- package/dist/discovery/capability-surface.interface.d.ts +2 -66
- package/dist/discovery/capability.deriver.d.ts +2 -47
- package/dist/discovery/discovery-artifact.interface.d.ts +34 -155
- package/dist/discovery/discovery-artifact.interface.js +1 -1
- package/dist/discovery/enum.mapper.d.ts +0 -24
- package/dist/discovery/field.mapper.d.ts +3 -114
- package/dist/discovery/field.mapper.js +6 -6
- package/dist/discovery/identifier.mapper.d.ts +4 -84
- package/dist/discovery/model.discoverer.d.ts +2 -69
- package/dist/discovery/operations.deriver.d.ts +0 -36
- package/dist/discovery/scalar.mapper.d.ts +1 -32
- package/dist/discovery/scalar.mapper.js +2 -2
- package/dist/discovery/schema-facts.interface.d.ts +3 -83
- package/dist/discovery/schema.reader.d.ts +0 -133
- package/dist/discovery/types.reader.d.ts +1 -9
- package/dist/discovery/types.reader.js +7 -7
- package/dist/discovery/vocabulary.dictionary.d.ts +6 -133
- package/dist/discovery/vocabulary.dictionary.js +2 -2
- package/dist/errors/error.normalizer.d.ts +5 -168
- package/dist/errors/prisma-error.reader.d.ts +1 -93
- package/dist/execution/first-variant.emulator.d.ts +5 -28
- package/dist/execution/operation.executor.d.ts +1 -62
- package/dist/execution/result.shaper.d.ts +0 -56
- package/dist/execution/transaction.executor.d.ts +0 -98
- package/dist/generate/artifact.emitter.d.ts +1 -35
- package/dist/generate/artifact.emitter.js +26 -43
- package/dist/generate/declared-support.reader.d.ts +0 -20
- package/dist/generate/discovery.cli.d.ts +0 -4
- package/dist/generate/existence.gate.d.ts +3 -37
- package/dist/generate/index.d.ts +4 -57
- package/dist/generate/prisma-cli.resolver.d.ts +0 -22
- package/dist/generate/prisma.generate.d.ts +14 -83
- package/dist/generate/reader-crosscheck.d.ts +3 -88
- package/dist/generate/reader-crosscheck.js +4 -4
- package/dist/prisma-client.interface.d.ts +5 -25
- package/dist/prisma.adapter.d.ts +21 -22
- package/dist/provider/provider-profile.d.ts +6 -14
- package/dist/translation/identifier.addressing.d.ts +0 -38
- package/dist/translation/mutation.translator.d.ts +5 -51
- package/dist/translation/operation.translator.d.ts +1 -102
- package/dist/translation/order-pagination.translator.d.ts +1 -47
- package/dist/translation/projection.translator.d.ts +1 -46
- package/dist/translation/scalar.conversion.d.ts +0 -64
- package/dist/translation/where.translator.d.ts +0 -52
- package/package.json +3 -2
|
@@ -2,74 +2,13 @@ import { type TransactionCapability } from "@aventara/core";
|
|
|
2
2
|
import type { PrismaComposedArtifact, PrismaModelComposer } from "../generate/prisma.generate.js";
|
|
3
3
|
import type { PrismaCapabilitySurface } from "./capability-surface.interface.js";
|
|
4
4
|
import type { PrismaSchemaFacts } from "./schema-facts.interface.js";
|
|
5
|
-
/**
|
|
6
|
-
* Every rule it applies belongs to somebody else. This module **orders and
|
|
7
|
-
* assembles**, and it owns exactly two judgements of its own:
|
|
8
|
-
*
|
|
9
|
-
* 1. **Which diagnostics are fatal.** A mapper answers with a diagnostic; whether
|
|
10
|
-
* the build stops is composition's call, because only composition knows that a
|
|
11
|
-
* model with an unmapped column cannot produce a Contract at all.
|
|
12
|
-
* 2. **The scalar registry.** `ScalarRegistry` is the set of Aventara scalar
|
|
13
|
-
* identities the discovered fields actually use — a fact no single field can
|
|
14
|
-
* state and only the whole pass can.
|
|
15
|
-
*
|
|
16
|
-
* # Three halves, from one pass over one field list
|
|
17
|
-
*
|
|
18
|
-
* `model` is core's: canonicalized, hashed, compiled into the Contract.
|
|
19
|
-
* `translation` is the adapter's Prisma addressing, and `referentialActions` is
|
|
20
|
-
* what the resolved `@relation(onDelete:/onUpdate:)` arguments say. All three are
|
|
21
|
-
* built from the SAME pass: the translation table from the same
|
|
22
|
-
* `mapPrismaIdentifiers` answer the model's `identifiers` come from — `.members`
|
|
23
|
-
* into the model, the whole address into the table — and the action table from the
|
|
24
|
-
* same `projectPrismaField` answer the model's relation fields come from. Two
|
|
25
|
-
* parallel passes would have to be kept in step; one pass cannot drift.
|
|
26
|
-
*
|
|
27
|
-
* `PrismaTranslationTable<M>` and `PrismaReferentialActionTable<M>` are mapped
|
|
28
|
-
* types over the model's resources, so a resource this module adds to the model
|
|
29
|
-
* and forgets to address — or forgets to answer the actions for — is a **compile
|
|
30
|
-
* error** rather than a runtime `undefined` at the first unique read or the first
|
|
31
|
-
* cascade question.
|
|
32
|
-
*
|
|
33
|
-
* The action table carries **only owning edges**, because only an owning edge has
|
|
34
|
-
* an action: PSL forbids the back-reference from declaring one, so the
|
|
35
|
-
* back-reference's answer is its partner's. Its direction is the one documented on
|
|
36
|
-
* `PrismaReferentialEdge` and on `field.mapper.ts`'s `PrismaRelationEdge` —
|
|
37
|
-
* *deleting a `target` does `onDelete` to rows of the enclosing resource* — and it
|
|
38
|
-
* is carried out of field projection unchanged rather than re-derived here.
|
|
39
|
-
*
|
|
40
|
-
* # No provider reaches this module, and that is enforced by the input
|
|
41
|
-
*
|
|
42
|
-
* The composer is created with a {@link TransactionCapability} and nothing else.
|
|
43
|
-
* `provider` is not a member anywhere on the path, so `if (provider === …)` is a
|
|
44
|
-
* compile error rather than a convention. The generate step keeps `provider` only
|
|
45
|
-
* to stamp the artifact, which is a recorded fact and not a branch.
|
|
46
|
-
*
|
|
47
|
-
* `transactions` is a **driver** fact and the driver is a runtime choice, so it is
|
|
48
|
-
* declared to the composer at generate time. The two cannot silently disagree: an
|
|
49
|
-
* artifact that says `"interactive"` loaded by an adapter whose declared driver
|
|
50
|
-
* says `"none"` mounts no `transaction`, and core refuses construction with
|
|
51
|
-
* `DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH` **[CORE, framework.factory.ts]** — a
|
|
52
|
-
* hard, legible failure rather than a silent one.
|
|
53
|
-
*
|
|
54
|
-
* The capability members are **scope-qualified** now, so the one record holds both
|
|
55
|
-
* answers: the create stage is advertised at the `nested` write scope and not at
|
|
56
|
-
* the bulk one. Composition asks for no variant, passes no `operation`, and
|
|
57
|
-
* advertises nothing it cannot honour. The mutation translator keeps its refusal —
|
|
58
|
-
* it is the belt to core's braces, not the only guard — but it is no longer the
|
|
59
|
-
* ONLY place the restriction exists.
|
|
60
|
-
*/
|
|
61
|
-
/** Composition refuses: the collected diagnostics, named and reported together. */
|
|
5
|
+
/** Thrown when the schema cannot be composed into a model; lists every problem. */
|
|
62
6
|
export declare class PrismaModelCompositionError extends Error {
|
|
63
7
|
readonly name = "PrismaModelCompositionError";
|
|
64
8
|
readonly diagnostics: readonly string[];
|
|
65
9
|
constructor(diagnostics: readonly string[]);
|
|
66
10
|
}
|
|
67
11
|
export type PrismaModelCompositionOptions = {
|
|
68
|
-
/**
|
|
69
|
-
* The driver's transaction capability. The ONLY declared fact composition accepts,
|
|
70
|
-
* and deliberately not the whole profile: a `PrismaProviderProfile` carries
|
|
71
|
-
* `provider`.
|
|
72
|
-
*/
|
|
73
12
|
readonly transactions: TransactionCapability;
|
|
74
13
|
};
|
|
75
14
|
export type PrismaModelDiscoveryInput = PrismaModelCompositionOptions & {
|
|
@@ -80,12 +19,6 @@ export type PrismaModelDiscoveryInput = PrismaModelCompositionOptions & {
|
|
|
80
19
|
/** Resource names the existence gate admitted, in PSL declaration order. */
|
|
81
20
|
readonly admitted: readonly string[];
|
|
82
21
|
};
|
|
83
|
-
/**
|
|
84
|
-
* The generate step's `composeModel` seam, filled.
|
|
85
|
-
*
|
|
86
|
-
* A factory rather than a bare function because the step's seam takes only the
|
|
87
|
-
* readers' output — order and refusal over external effects is the step's job, and
|
|
88
|
-
* the declared driver fact is the caller's.
|
|
89
|
-
*/
|
|
22
|
+
/** Creates the composer `runPrismaGenerateStep` needs, for one transaction capability. */
|
|
90
23
|
export declare function createPrismaModelComposer(options: PrismaModelCompositionOptions): PrismaModelComposer;
|
|
91
24
|
export declare function discoverPrismaModel(input: PrismaModelDiscoveryInput): PrismaComposedArtifact;
|
|
@@ -1,46 +1,10 @@
|
|
|
1
1
|
import type { AdapterResource, ResourceOperations, TransactionCapability } from "@aventara/core";
|
|
2
2
|
import type { PrismaAdapterModel } from "./discovery-artifact.interface.js";
|
|
3
|
-
/**
|
|
4
|
-
* No mapper, no discoverer, no emitter and no executor may drop a variant, because
|
|
5
|
-
* an omission in two places is an omission nobody can audit.
|
|
6
|
-
*
|
|
7
|
-
* # Three inputs, and `provider` is not one of them
|
|
8
|
-
*
|
|
9
|
-
* ```text
|
|
10
|
-
* findUnique, findUniqueOrThrow, findFirst, findFirstOrThrow, findMany, create,
|
|
11
|
-
* createMany, createManyAndReturn, delete, update, deleteMany, updateMany,
|
|
12
|
-
* updateManyAndReturn, upsert, count, aggregate, groupBy
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* The second input is the model's identifiers, because `find.unique`,
|
|
16
|
-
* `update.unique`, `delete.unique` and `upsert.unique` are meaningless without
|
|
17
|
-
* one. The third is `transactions` — the **only** declared profile row this gate
|
|
18
|
-
* needs. The whole `PrismaProviderProfile` is deliberately NOT accepted: it
|
|
19
|
-
* carries `provider`, and a module that cannot be handed a provider cannot branch
|
|
20
|
-
* on one (dictionary invariant 6). `isolationLevels` is not a precondition of any
|
|
21
|
-
* variant.
|
|
22
|
-
*
|
|
23
|
-
* `supportsEmulatedFirstVariants` is **derived, exactly `transactions !== "none"`,
|
|
24
|
-
* and stored nowhere**. A stored copy would be a second source of truth for a
|
|
25
|
-
* one-line derivation.
|
|
26
|
-
*
|
|
27
|
-
* # What is NOT gated here, and why that is deliberate
|
|
28
|
-
*
|
|
29
|
-
* So `update.first` reads `emulatedFirst && exposes("update")` and `delete.first`
|
|
30
|
-
* reads `emulatedFirst && exposes("delete")`. Under Route B those cells would read
|
|
31
|
-
* `updateManyAndReturn` and `deleteMany` instead — which is exactly why the write
|
|
32
|
-
* call is NOT folded into `emulatedFirst`: a shared precondition naming one
|
|
33
|
-
* route's call would assert a gate the other route does not have, an assertion
|
|
34
|
-
* that cannot fail for the right reason.
|
|
35
|
-
*/
|
|
36
|
-
/** Core's own identifier tuples for a discovered Resource. */
|
|
37
3
|
type DiscoveredIdentifiers = AdapterResource<PrismaAdapterModel>["identifiers"];
|
|
38
4
|
export type ResourceOperationsInput = {
|
|
39
|
-
/** `<M>Delegate`'s own methods, as the types reader read them. */
|
|
40
5
|
readonly operations: readonly string[];
|
|
41
6
|
/** The model's usable identifiers; empty means no unique addressing exists. */
|
|
42
7
|
readonly identifiers: DiscoveredIdentifiers;
|
|
43
|
-
/** The one declared profile row this gate reads. */
|
|
44
8
|
readonly transactions: TransactionCapability;
|
|
45
9
|
};
|
|
46
10
|
export declare function deriveResourceOperations(input: ResourceOperationsInput): ResourceOperations;
|
|
@@ -1,43 +1,12 @@
|
|
|
1
1
|
import type { PrismaResourceSurface } from "./capability-surface.interface.js";
|
|
2
2
|
import type { PrismaSchemaField } from "./schema-facts.interface.js";
|
|
3
3
|
import { type PrismaDiscoveryDiagnostic, type PrismaScalarMapping } from "./vocabulary.dictionary.js";
|
|
4
|
-
/**
|
|
5
|
-
* # Four answers, no fifth, and no guess
|
|
6
|
-
*
|
|
7
|
-
* The outcome is a closed union because every way this can end is a decision
|
|
8
|
-
* somebody has to be able to read back:
|
|
9
|
-
*
|
|
10
|
-
* - `scalar` — the Prisma type name had a row in the dictionary's total table.
|
|
11
|
-
* - `enum` — the written type is an enum `enums.ts` confirms. `enum.mapper.ts`'s
|
|
12
|
-
* `projectEnumBackedColumn` is where that becomes a field type ref.
|
|
13
|
-
* - `excluded` — PSL declares the column and the 🟢 types do not expose it.
|
|
14
|
-
* - `unmapped` — a type name in neither table. A **diagnostic**, never a silent
|
|
15
|
-
* drop and never a nearest-neighbour guess.
|
|
16
|
-
*
|
|
17
|
-
* # Why `format` is emitted here at all — and why so little of it is
|
|
18
|
-
*
|
|
19
|
-
* What the plan row was *right* about is that most of what looks like a format is
|
|
20
|
-
* policy. `format` is **enforced**: `normalizeScalarFieldValue` turns `format:
|
|
21
|
-
* "uuid"` into a `V1003` rejection of request input **[CORE,
|
|
22
|
-
* `runtime/scalar-runtime.ts`]**. So the bar for discovering one is not "the
|
|
23
|
-
* schema suggests it" but "the column itself rejects what Aventara would then
|
|
24
|
-
* reject". That intent belongs in `FrameworkScalarFieldConfig.format` **[CORE]**,
|
|
25
|
-
* which the Contract compiler already layers over the adapter's value.
|
|
26
|
-
*
|
|
27
|
-
* That is the correct answer, not a gap.
|
|
28
|
-
*
|
|
29
|
-
* # Nothing here branches on a provider
|
|
30
|
-
*
|
|
31
|
-
* `@db.Uuid` is unwritable on SQLite, so the SQLite schema simply never carries it
|
|
32
|
-
* and the same table gives the same right answer on both engines without a
|
|
33
|
-
* provider arm.
|
|
34
|
-
*/
|
|
35
4
|
export type ScalarMappingInput = {
|
|
36
5
|
/** The PSL model name, carried so a diagnostic can name the column in full. */
|
|
37
6
|
readonly resource: string;
|
|
38
7
|
/** PSL — what the column IS. */
|
|
39
8
|
readonly field: PrismaSchemaField;
|
|
40
|
-
/** TYPE — what this client exposes.
|
|
9
|
+
/** TYPE — what this client exposes. */
|
|
41
10
|
readonly surface: PrismaResourceSurface;
|
|
42
11
|
/** TYPE — `enums.ts`, which is what makes an enum name an enum name. */
|
|
43
12
|
readonly enums: Readonly<Record<string, readonly string[]>>;
|
|
@@ -7,7 +7,7 @@ export function mapPrismaScalarField(input) {
|
|
|
7
7
|
outcome: "excluded",
|
|
8
8
|
diagnostic: {
|
|
9
9
|
code: "not-exposed",
|
|
10
|
-
message: `${column} is declared in schema.prisma and absent from the generated
|
|
10
|
+
message: `${column} is declared in schema.prisma and absent from the generated client's types, so it is omitted: ${explainAbsence(field, surface)}`,
|
|
11
11
|
},
|
|
12
12
|
};
|
|
13
13
|
}
|
|
@@ -37,7 +37,7 @@ function explainAbsence(field, surface) {
|
|
|
37
37
|
return "it carries `@ignore`";
|
|
38
38
|
}
|
|
39
39
|
if (Object.hasOwn(surface.relationFields, field.name)) {
|
|
40
|
-
return "the
|
|
40
|
+
return "the generated client's types expose it as a relation edge, not a scalar column";
|
|
41
41
|
}
|
|
42
42
|
return "no schema attribute explains it — a provider drop, an `Unsupported(…)` column or a preview-feature exclusion";
|
|
43
43
|
}
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nothing here is imported from `@prisma/client`: these are this adapter's own
|
|
3
|
-
* facts over schema text, which is what keeps discovery a pure function of plain
|
|
4
|
-
* data and unit-testable without a generated client.
|
|
5
|
-
*
|
|
6
|
-
* Read `schema.reader.ts`'s header for the boundary these facts are drawn on: the
|
|
7
|
-
* reader **locates** tokens and never **evaluates** them.
|
|
8
|
-
*/
|
|
9
1
|
import type { PrismaReferentialAction } from "./discovery-artifact.interface.js";
|
|
10
2
|
/** Where a fact — or an unhandled construct — was found. One-based lines, as `prisma validate` reports them. */
|
|
11
3
|
export type PrismaSchemaSite = {
|
|
12
|
-
/** The schema file as the caller named it; a folder read names each member file. */
|
|
13
4
|
readonly file: string;
|
|
14
5
|
readonly line: number;
|
|
15
6
|
};
|
|
@@ -17,17 +8,6 @@ export type PrismaSchemaSite = {
|
|
|
17
8
|
* A construct the reader **does not recognise at all** — bucket 3 of the reader's
|
|
18
9
|
* three-way classification: a `type`/`view` block, an unrecognised `@relation`
|
|
19
10
|
* argument, or any attribute spelling added after 7.10.0.
|
|
20
|
-
*
|
|
21
|
-
* `@map`, `@@map`, `@@index` and `@@id`/`@@unique`'s `map:` are **not** here: they
|
|
22
|
-
* are recognised and deliberately discarded (bucket 2, the reader's explicit
|
|
23
|
-
* `DISCARDED_ATTRIBUTES` list), because their values appear nowhere on the 🟢
|
|
24
|
-
* generated surface. Recording them would fail the generate step on any schema
|
|
25
|
-
* that maps a column name. `name:` is **not** discarded — see {@link
|
|
26
|
-
* PrismaSchemaBlockIdentifier}.
|
|
27
|
-
*
|
|
28
|
-
* Bucket 3 is recorded rather than dropped because silently dropping an
|
|
29
|
-
* unrecognised attribute is how a grammar change becomes wrong output instead of a
|
|
30
|
-
* build stop. A non-empty `PrismaSchemaFacts.unhandled` FAILS the generate step.
|
|
31
11
|
*/
|
|
32
12
|
export type PrismaSchemaUnknown = {
|
|
33
13
|
readonly kind: PrismaSchemaUnknownKind;
|
|
@@ -41,11 +21,7 @@ export type PrismaSchemaUnknown = {
|
|
|
41
21
|
};
|
|
42
22
|
export type PrismaSchemaUnknownKind =
|
|
43
23
|
/** An `@…` on a field declaration. */
|
|
44
|
-
"field-attribute"
|
|
45
|
-
/** An `@@…` on its own line inside a block. */
|
|
46
|
-
| "block-attribute"
|
|
47
|
-
/** A named argument of a recognised attribute that the reader neither models nor deliberately discards. */
|
|
48
|
-
| "attribute-argument"
|
|
24
|
+
"field-attribute" | "block-attribute" | "attribute-argument"
|
|
49
25
|
/** A top-level block whose keyword is neither `model`, `enum`, `generator` nor `datasource`. */
|
|
50
26
|
| "block"
|
|
51
27
|
/** A line the reader could not classify at all. */
|
|
@@ -55,48 +31,15 @@ export type PrismaSchemaFacts = {
|
|
|
55
31
|
readonly models: readonly PrismaSchemaModel[];
|
|
56
32
|
/**
|
|
57
33
|
* Attributes and blocks the reader does not recognise at all (a `type`/`view`
|
|
58
|
-
* block, an
|
|
59
|
-
* `@map`/`@@map`/`@@index` are recognised and deliberately discarded instead, so
|
|
60
|
-
* they never appear here. Never silently dropped: a non-empty list FAILS the
|
|
61
|
-
* generate step.
|
|
34
|
+
* block, an unrecognised argument, any spelling added after 7.10.0).
|
|
62
35
|
*/
|
|
63
36
|
readonly unhandled: readonly PrismaSchemaUnknown[];
|
|
64
37
|
};
|
|
65
38
|
export type PrismaSchemaEnum = {
|
|
66
39
|
readonly name: string;
|
|
67
|
-
/**
|
|
68
|
-
* Declaration order is semantic; a member-level `@map` is discarded (bucket 2),
|
|
69
|
-
* not recorded.
|
|
70
|
-
*/
|
|
71
40
|
readonly values: readonly string[];
|
|
72
41
|
readonly site: PrismaSchemaSite;
|
|
73
42
|
};
|
|
74
|
-
/**
|
|
75
|
-
* A block-level uniqueness criterion — `@@id([…])` or `@@unique([…])` — as PSL
|
|
76
|
-
* states it: the members in **the attribute's own order**, and `name:`'s value
|
|
77
|
-
* when the attribute carries one.
|
|
78
|
-
*
|
|
79
|
-
* `name:` is carried rather than discarded because bucket 2's rule is that an
|
|
80
|
-
* attribute argument is discardable **iff its value appears nowhere on the 🟢
|
|
81
|
-
* surface**, and this one appears there twice. Measured at 7.10.0 (regenerated
|
|
82
|
-
* 2026-09-29): `@@unique([b, a], name: "altKey")` emits
|
|
83
|
-
* `altKey?: Prisma.NamedUniqueAltKeyCompoundUniqueInput` in `WhereUniqueInput`
|
|
84
|
-
* and keys `}, "id" | "altKey">`, and `@@id([a, b], name: "pk")` keys `}, "pk">`
|
|
85
|
-
* — the custom name **replaces** the positional `b_a` / `a_b` alias. `map:` does
|
|
86
|
-
* stay discarded: `named_idx` appears in the whole generated tree only in the 🛑
|
|
87
|
-
* `internal/class.ts` inline-schema blob.
|
|
88
|
-
*
|
|
89
|
-
* The list itself is read from the positional argument or from `fields:`,
|
|
90
|
-
* whichever the schema writes, and in whatever order the other arguments follow:
|
|
91
|
-
* `@@unique([b, a])`, `@@unique(fields: [b, a])` and
|
|
92
|
-
* `@@unique(name: "k", map: "m", fields: [b, a])` are all valid at 7.10.0 and the
|
|
93
|
-
* first two produce the identical generated key `b_a`.
|
|
94
|
-
*
|
|
95
|
-
* `null` means the attribute wrote no `name:`, not "the positional form": the
|
|
96
|
-
* positional key is derivable from `members`, and a name is derivable from
|
|
97
|
-
* nothing, so only the undecidable half is recorded. What derives the addressing
|
|
98
|
-
* key from the pair is `identifier.mapper.ts`.
|
|
99
|
-
*/
|
|
100
43
|
export type PrismaSchemaBlockIdentifier = {
|
|
101
44
|
/** Member order as written in the attribute's list. Never sorted. */
|
|
102
45
|
readonly members: readonly string[];
|
|
@@ -120,7 +63,6 @@ export type PrismaSchemaField = {
|
|
|
120
63
|
readonly type: string;
|
|
121
64
|
/** `[]` */
|
|
122
65
|
readonly list: boolean;
|
|
123
|
-
/** `?` — the ONLY source of nullability. */
|
|
124
66
|
readonly optional: boolean;
|
|
125
67
|
/** `@id` */
|
|
126
68
|
readonly id: boolean;
|
|
@@ -128,15 +70,9 @@ export type PrismaSchemaField = {
|
|
|
128
70
|
readonly unique: boolean;
|
|
129
71
|
/** `@updatedAt` */
|
|
130
72
|
readonly updatedAt: boolean;
|
|
131
|
-
/** `@default(…)`'s argument as RAW TEXT — matched against a table, never parsed. */
|
|
132
73
|
readonly defaultArgument: string | null;
|
|
133
74
|
readonly relation: PrismaSchemaRelation | null;
|
|
134
|
-
/**
|
|
135
|
-
* `@db.*` as written, e.g. `@db.Decimal(10, 2)`. Kept verbatim and never parsed. A
|
|
136
|
-
* native type is the **only** proof discovery accepts for `format`; configuration
|
|
137
|
-
* may then override it, since `AdapterScalarField.format` and
|
|
138
|
-
* `FrameworkScalarFieldConfig.format` are separate members the compiler layers.
|
|
139
|
-
*/
|
|
75
|
+
/** `@db.*` as written, e.g. `@db.Decimal(10, 2)`. */
|
|
140
76
|
readonly native: readonly string[];
|
|
141
77
|
/** `@ignore` */
|
|
142
78
|
readonly ignored: boolean;
|
|
@@ -145,7 +81,6 @@ export type PrismaSchemaField = {
|
|
|
145
81
|
export type PrismaSchemaRelation = {
|
|
146
82
|
/** `@relation("Name")` — the ONLY evidence pairing a back-reference with its forward reference. */
|
|
147
83
|
readonly name: string | null;
|
|
148
|
-
/** The FK scalar(s); read, used, then discarded. */
|
|
149
84
|
readonly fields: readonly string[] | null;
|
|
150
85
|
readonly references: readonly string[] | null;
|
|
151
86
|
/**
|
|
@@ -153,21 +88,6 @@ export type PrismaSchemaRelation = {
|
|
|
153
88
|
* absent argument carries Prisma's effective default rather than a third
|
|
154
89
|
* state, because an absent action and the explicitly-written default are one
|
|
155
90
|
* fact about what a delete does.
|
|
156
|
-
*
|
|
157
|
-
* `null` means **this edge is not the owning side** — it declares no
|
|
158
|
-
* `fields:`, so no foreign key and no action belong to it. PSL enforces that
|
|
159
|
-
* itself: `prisma validate` 7.10.0 answers *"The relation field `reqs` on
|
|
160
|
-
* Model `Alpha` must not specify the `onDelete` or `onUpdate` argument in the
|
|
161
|
-
* @relation attribute. You must only specify it on the opposite field `alpha`
|
|
162
|
-
* on model `Req`"*. `null` therefore reads as "ask the other edge", never as
|
|
163
|
-
* "no action applies". It is also what an unreadable action spelling leaves
|
|
164
|
-
* behind, and that case is recorded in `unhandled` and fails the generate
|
|
165
|
-
* step, so no consumer sees it.
|
|
166
|
-
*
|
|
167
|
-
* The inversion matters to every consumer: the action is declared on the
|
|
168
|
-
* CHILD and fires when the PARENT is deleted. Read with
|
|
169
|
-
* `PrismaSchemaField.type` — the target model — one owning edge says
|
|
170
|
-
* "deleting a `<type>` does `<onDelete>` to a row of the enclosing model".
|
|
171
91
|
*/
|
|
172
92
|
readonly onDelete: PrismaReferentialAction | null;
|
|
173
93
|
/** `@relation(onUpdate: …)`, resolved the same way. Read independently of `onDelete`. */
|
|
@@ -1,137 +1,4 @@
|
|
|
1
1
|
import type { PrismaSchemaFacts } from "./schema-facts.interface.js";
|
|
2
|
-
/**
|
|
3
|
-
* # The boundary, and why this file is a reader and not a parser
|
|
4
|
-
*
|
|
5
|
-
* It **locates** tokens and never **evaluates** them.
|
|
6
|
-
*
|
|
7
|
-
* PSL's model body is line-oriented by the language's own rule, not by this
|
|
8
|
-
* reader's assumption. Re-measured against `prisma` 7.10.0 on 2026-09-28, all four
|
|
9
|
-
* constructs that would let a declaration legally span lines are **rejected** with
|
|
10
|
-
* `Error code: P1012` — so the classic regex-parser failure cannot occur:
|
|
11
|
-
*
|
|
12
|
-
* 1. a wrapped `@@unique([…])` — the error COUNT is layout-dependent, the
|
|
13
|
-
* rejection is not. A two-line wrap (`@@unique([x,` / `y])`) gives 2 errors,
|
|
14
|
-
* both `error: Error validating: This line is not a valid field or attribute
|
|
15
|
-
* definition.`; a four-line wrap gives 4 — three of that sentence plus one
|
|
16
|
-
* `Error validating model "A": This field declaration is invalid. It is either
|
|
17
|
-
* missing a name or a type.`
|
|
18
|
-
* 2. a wrapped field attribute (`@default(uuid())` on the line below `id String
|
|
19
|
-
* @id`) — 1 error, `This line is not a valid field or attribute definition.`
|
|
20
|
-
* 3. two block attributes on one line (`@@unique([x]) @@index([y])`) — 1 error,
|
|
21
|
-
* `This line is not a valid field or attribute definition.`
|
|
22
|
-
* 4. a split field declaration (`id` then `String @id`) — 2 errors, **both**
|
|
23
|
-
* `Error validating model "A": This field declaration is invalid. It is either
|
|
24
|
-
* missing a name or a type.`
|
|
25
|
-
*
|
|
26
|
-
* All four are indeed `P1012`, **and the line-oriented conclusion holds
|
|
27
|
-
* unchanged** — the design did not move. But case 4 never emits that sentence at
|
|
28
|
-
* all (it emits *"This field declaration is invalid. It is either missing a name
|
|
29
|
-
* or a type."* twice), and case 1 emits a second, different sentence besides it.
|
|
30
|
-
* The evidence supports the rule; the single-sentence attribution does not, and
|
|
31
|
-
* the plan now says so.
|
|
32
|
-
*
|
|
33
|
-
* Re-running it: `prisma` lives at `packages/prisma7-adapter/node_modules/.bin/
|
|
34
|
-
* prisma`, not the worktree root. Invoked from the root it silently produces no
|
|
35
|
-
* output, which looks exactly like "no errors".
|
|
36
|
-
*
|
|
37
|
-
* # What crossing the boundary would mean
|
|
38
|
-
*
|
|
39
|
-
* The day this reader **evaluates** — resolving `env()`, interpreting
|
|
40
|
-
* `dbgenerated(…)`, modelling `type`/`view` blocks, following `@@schema`, or
|
|
41
|
-
* reconstructing relations without `@relation` names — it has become a parser and
|
|
42
|
-
* needs a different decision. Hence:
|
|
43
|
-
*
|
|
44
|
-
* - `@default`'s argument is kept as **raw text** and matched against a table
|
|
45
|
-
* downstream, never parsed.
|
|
46
|
-
* - `@db.*` is kept **as written** and never parsed. It is the only proof
|
|
47
|
-
* discovery accepts for `format`; configuration overrides it later.
|
|
48
|
-
* - a field's `type` stays the written token — `Grade` is not resolved to the
|
|
49
|
-
* enum, which is also why a schema **folder** needs no linker (below).
|
|
50
|
-
* - an attribute is classified three ways, not two (below), and only the third
|
|
51
|
-
* class is recorded in `unhandled`. A non-empty `unhandled` fails the generate
|
|
52
|
-
* step: silently dropping an **unrecognised** attribute is how a grammar change
|
|
53
|
-
* becomes wrong output instead of a build stop. The version fence is what
|
|
54
|
-
* converts a grammar change from silent wrongness into a build stop.
|
|
55
|
-
*
|
|
56
|
-
* # The three-way classification (corrected 2026-09-28)
|
|
57
|
-
*
|
|
58
|
-
* 1. **recognised and modelled** — `FIELD_ATTRIBUTES`, `BLOCK_ATTRIBUTES`, `@db.*`
|
|
59
|
-
* and `RELATION_ARGUMENTS`.
|
|
60
|
-
* 2. **recognised and deliberately discarded**, via the explicit
|
|
61
|
-
* `DISCARDED_ATTRIBUTES` / `DISCARDED_BLOCK_ATTRIBUTE_ARGUMENTS` lists —
|
|
62
|
-
* `@map`, `@@map`, `@@index`, and `@@id`/`@@unique`'s `map:` argument. The rule
|
|
63
|
-
* is one line long: an argument is discardable **iff its value appears nowhere
|
|
64
|
-
* on the 🟢 surface**. The list being explicit is the point: the discard is a
|
|
65
|
-
* decision a reader can see and audit, not an omission. That rule is also what
|
|
66
|
-
* took `name:` back OFF this list (2026-09-29): its value is a
|
|
67
|
-
* `WhereUniqueInput` key and an `AtLeast` key, so it is carried on
|
|
68
|
-
* `PrismaSchemaBlockIdentifier`.
|
|
69
|
-
* 3. **unrecognised** — on neither list, therefore `unhandled`, therefore a build
|
|
70
|
-
* stop.
|
|
71
|
-
*
|
|
72
|
-
* # Surface-invisibility was EVIDENCE, not the meaning (2026-09-29)
|
|
73
|
-
*
|
|
74
|
-
* Bucket 2's rule reads "discardable iff the value appears nowhere on the 🟢
|
|
75
|
-
* surface", and `@relation(onDelete: Cascade)` passes that test: measured at
|
|
76
|
-
* 7.10.0, `Cascade` reaches nothing but the 🛑 `internal/class.ts` inline-schema
|
|
77
|
-
* blob. It is still **not** discardable, and this is the first construct where the
|
|
78
|
-
* test and the rule it stands for come apart.
|
|
79
|
-
*
|
|
80
|
-
* The rule's *reason* was that such attributes configure the database below the
|
|
81
|
-
* ORM entrypoint and therefore carry no framework meaning. A referential action
|
|
82
|
-
* fails that reason while passing the test: it changes what a delete **does** —
|
|
83
|
-
* rows disappear that the caller never named — which is exactly what a transaction
|
|
84
|
-
* planner must reason about before it agrees to run a plan. So invisibility on the
|
|
85
|
-
* generated types is *evidence* for "no framework meaning", and evidence can be
|
|
86
|
-
* outweighed; it was never the meaning itself. `@map` and `@@index` remain
|
|
87
|
-
* discarded because they fail the test AND the reason. `@relation`'s `map:` joins
|
|
88
|
-
* them: a foreign-key constraint name is genuinely DB-side, and it was bucket 3
|
|
89
|
-
* until now, which made `@relation(map: …)` — valid PSL — a build stop on its own.
|
|
90
|
-
*
|
|
91
|
-
* Actions are read on both arguments and carried **resolved**: an absent argument
|
|
92
|
-
* is not a third state, it is Prisma's effective default, measured rather than
|
|
93
|
-
* assumed (see `REFERENTIAL_ACTIONS` and `resolvePrismaReferentialDefaults`).
|
|
94
|
-
*
|
|
95
|
-
* Bucket 2 previously did not exist, and a field-level `@map` landed in
|
|
96
|
-
* `unhandled`: the framework would have refused to build against any schema that
|
|
97
|
-
* maps a column name, which is most real Prisma schemas.
|
|
98
|
-
*
|
|
99
|
-
* # Arguments are located by NAME, never by position (added 2026-09-29)
|
|
100
|
-
*
|
|
101
|
-
* PSL lets the same argument list arrive positionally or named, and lets the named
|
|
102
|
-
* arguments come in any order. Measured against `prisma validate` 7.10.0 and
|
|
103
|
-
* against a generated client on 2026-09-29: `@@unique([b, a])`, `@@unique(fields:
|
|
104
|
-
* [b, a])`, `@@unique(name: "k", map: "m", fields: [b, a])` and every other
|
|
105
|
-
* permutation are accepted, and the client keys the unnamed ones `b_a` and the
|
|
106
|
-
* named ones `k` regardless of the order they were written in. The spelling is
|
|
107
|
-
* therefore not a fact about the schema, and a reader that took the list to be the
|
|
108
|
-
* FIRST argument read `@@unique(fields: [b, a])` as one member called `fields: [b,
|
|
109
|
-
* a]` — silently, with `unhandled` empty — so the generate step refused a schema
|
|
110
|
-
* Prisma accepts. ARGUMENT order does not matter; MEMBER order inside the list
|
|
111
|
-
* still does, because it is part of the identity and of the addressing key.
|
|
112
|
-
*
|
|
113
|
-
* The same rule applies to `@relation`, which mixes a positional argument (the
|
|
114
|
-
* relation name) with named ones; it was already order-independent and its FK read
|
|
115
|
-
* was unaffected.
|
|
116
|
-
*
|
|
117
|
-
* A list whose members are not plain field names — `@@unique([a(sort: Desc)])`,
|
|
118
|
-
* valid PSL at 7.10.0 — cannot be located without evaluating, so it is bucket 3
|
|
119
|
-
* rather than a guess.
|
|
120
|
-
*
|
|
121
|
-
* # One deliberate non-recording beyond the buckets
|
|
122
|
-
*
|
|
123
|
-
* `generator` and `datasource` block bodies are skipped, not recorded: they are
|
|
124
|
-
* tooling configuration, not model content, and their contents are exactly what
|
|
125
|
-
* this reader must not evaluate (`env()` lives there).
|
|
126
|
-
*
|
|
127
|
-
* # A schema folder is readdir plus merge
|
|
128
|
-
*
|
|
129
|
-
* Prisma 7 accepts a folder (`prisma validate --schema <folder>` verified at
|
|
130
|
-
* 7.10.0). Blocks are file-local and references are by name, so there is no
|
|
131
|
-
* cross-file grammar and **no linker**: reading a folder appends each file's facts
|
|
132
|
-
* in filename order and nothing is rewritten. Every fact keeps the file and line
|
|
133
|
-
* it was written on.
|
|
134
|
-
*/
|
|
135
2
|
export type PrismaSchemaSource = {
|
|
136
3
|
readonly file: string;
|
|
137
4
|
readonly text: string;
|
|
@@ -2,19 +2,11 @@ import ts from "typescript";
|
|
|
2
2
|
import type { PrismaCapabilitySurface } from "./capability-surface.interface.js";
|
|
3
3
|
export type PrismaTypesReadResult = {
|
|
4
4
|
readonly surface: PrismaCapabilitySurface;
|
|
5
|
-
/**
|
|
6
|
-
* Every file under the client directory the reader actually opened. Reported
|
|
7
|
-
* rather than assumed: the 🛑 files are absent by refusal, not by luck, and a
|
|
8
|
-
* caller — or a spec — can assert the file **set** and not only the output.
|
|
9
|
-
*/
|
|
5
|
+
/** Every file under the client directory the reader actually opened. */
|
|
10
6
|
readonly filesRead: readonly string[];
|
|
11
7
|
};
|
|
12
8
|
export declare class PrismaTypesReaderError extends Error {
|
|
13
9
|
readonly name = "PrismaTypesReaderError";
|
|
14
10
|
}
|
|
15
|
-
/**
|
|
16
|
-
* The reader's one checker question, exported because it is the distinction the
|
|
17
|
-
* whole reader rests on and therefore has to be testable on its own.
|
|
18
|
-
*/
|
|
19
11
|
export declare function readWrittenTypeArguments(checker: ts.TypeChecker, reference: ts.TypeReferenceNode): readonly string[];
|
|
20
12
|
export declare function readPrismaCapabilitySurface(clientDirectory: string): PrismaTypesReadResult;
|
|
@@ -21,7 +21,7 @@ export function readPrismaCapabilitySurface(clientDirectory) {
|
|
|
21
21
|
const root = path.resolve(clientDirectory);
|
|
22
22
|
const publicFiles = listPublicFiles(root);
|
|
23
23
|
if (publicFiles.length === 0) {
|
|
24
|
-
throw new PrismaTypesReaderError(`no generated
|
|
24
|
+
throw new PrismaTypesReaderError(`no generated Prisma client file was found under ${root}`);
|
|
25
25
|
}
|
|
26
26
|
const filesRead = new Set();
|
|
27
27
|
const host = createPublicOnlyHost(root, filesRead);
|
|
@@ -63,11 +63,11 @@ function createPublicOnlyHost(root, filesRead) {
|
|
|
63
63
|
return undefined;
|
|
64
64
|
}
|
|
65
65
|
if (isPrivatePath(root, resolved)) {
|
|
66
|
-
throw new PrismaTypesReaderError(`the types reader asked for ${path.relative(root, resolved)}, which is
|
|
66
|
+
throw new PrismaTypesReaderError(`the types reader asked for ${path.relative(root, resolved)}, which is an internal file of the generated client that must not be imported`);
|
|
67
67
|
}
|
|
68
68
|
const text = readFileSync(resolved, "utf8");
|
|
69
69
|
if (text.includes(RED_BANNER) || !text.includes(GREEN_BANNER)) {
|
|
70
|
-
throw new PrismaTypesReaderError(`${path.relative(root, resolved)} does not carry
|
|
70
|
+
throw new PrismaTypesReaderError(`${path.relative(root, resolved)} does not carry Prisma's "${GREEN_BANNER}" banner`);
|
|
71
71
|
}
|
|
72
72
|
filesRead.add(resolved);
|
|
73
73
|
return text;
|
|
@@ -128,7 +128,7 @@ function projectionMembers(index, name) {
|
|
|
128
128
|
? written.typeArguments?.[0]
|
|
129
129
|
: written;
|
|
130
130
|
if (literal === undefined || !ts.isTypeLiteralNode(literal)) {
|
|
131
|
-
throw new PrismaTypesReaderError(`${name} is declared by the
|
|
131
|
+
throw new PrismaTypesReaderError(`${name} is declared by the generated client's types and is not a written object type, so its projection members cannot be read`);
|
|
132
132
|
}
|
|
133
133
|
return new Set(memberNames(literal.members.filter(ts.isPropertySignature)));
|
|
134
134
|
}
|
|
@@ -197,7 +197,7 @@ function keysOfReferenced(index, node, accept) {
|
|
|
197
197
|
}
|
|
198
198
|
const members = membersOf(index, referencedName(reference));
|
|
199
199
|
if (members === null) {
|
|
200
|
-
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by the
|
|
200
|
+
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by the generated client's types but declared nowhere in them`);
|
|
201
201
|
}
|
|
202
202
|
return memberNames(members);
|
|
203
203
|
}
|
|
@@ -325,7 +325,7 @@ function readMutationOperations(index, model, stage, field, member) {
|
|
|
325
325
|
}
|
|
326
326
|
const members = membersOf(index, referencedName(reference));
|
|
327
327
|
if (members === null) {
|
|
328
|
-
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by ${model}Unchecked${stage}Input.${field} and declared nowhere in the
|
|
328
|
+
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by ${model}Unchecked${stage}Input.${field} and declared nowhere in the generated client's types`);
|
|
329
329
|
}
|
|
330
330
|
return memberNames(members);
|
|
331
331
|
}
|
|
@@ -371,7 +371,7 @@ function readRelationFields(checker, index, payload, where, create, update, incl
|
|
|
371
371
|
}
|
|
372
372
|
const targetMembers = membersOf(index, referencedName(targetPayload));
|
|
373
373
|
if (targetMembers === null) {
|
|
374
|
-
throw new PrismaTypesReaderError(`${referencedName(targetPayload)} is declared nowhere in the
|
|
374
|
+
throw new PrismaTypesReaderError(`${referencedName(targetPayload)} is declared nowhere in the generated client's types`);
|
|
375
375
|
}
|
|
376
376
|
const edge = findReference(include.get(name)?.type, (n) => n.endsWith("Args"));
|
|
377
377
|
const edgeArguments = edge === null
|