@aventara/prisma7-adapter 0.1.0-pilot.0
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/LICENSE +91 -0
- package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
- package/README.md +564 -0
- package/dist/discovery/artifact.loader.d.ts +26 -0
- package/dist/discovery/artifact.loader.js +124 -0
- package/dist/discovery/capability-surface.interface.d.ts +191 -0
- package/dist/discovery/capability-surface.interface.js +13 -0
- package/dist/discovery/capability.deriver.d.ts +92 -0
- package/dist/discovery/capability.deriver.js +350 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +210 -0
- package/dist/discovery/discovery-artifact.interface.js +1 -0
- package/dist/discovery/enum.mapper.d.ts +59 -0
- package/dist/discovery/enum.mapper.js +32 -0
- package/dist/discovery/field.mapper.d.ts +244 -0
- package/dist/discovery/field.mapper.js +387 -0
- package/dist/discovery/identifier.mapper.d.ts +188 -0
- package/dist/discovery/identifier.mapper.js +132 -0
- package/dist/discovery/model.discoverer.d.ts +108 -0
- package/dist/discovery/model.discoverer.js +341 -0
- package/dist/discovery/operations.deriver.d.ts +75 -0
- package/dist/discovery/operations.deriver.js +92 -0
- package/dist/discovery/scalar.mapper.d.ts +81 -0
- package/dist/discovery/scalar.mapper.js +51 -0
- package/dist/discovery/schema-facts.interface.d.ts +174 -0
- package/dist/discovery/schema-facts.interface.js +11 -0
- package/dist/discovery/schema.reader.d.ts +162 -0
- package/dist/discovery/schema.reader.js +692 -0
- package/dist/discovery/types.reader.d.ts +24 -0
- package/dist/discovery/types.reader.js +617 -0
- package/dist/discovery/vocabulary.dictionary.d.ts +258 -0
- package/dist/discovery/vocabulary.dictionary.js +429 -0
- package/dist/errors/error.normalizer.d.ts +250 -0
- package/dist/errors/error.normalizer.js +135 -0
- package/dist/errors/prisma-error.reader.d.ts +144 -0
- package/dist/errors/prisma-error.reader.js +167 -0
- package/dist/execution/first-variant.emulator.d.ts +64 -0
- package/dist/execution/first-variant.emulator.js +205 -0
- package/dist/execution/operation.executor.d.ts +87 -0
- package/dist/execution/operation.executor.js +210 -0
- package/dist/execution/result.shaper.d.ts +103 -0
- package/dist/execution/result.shaper.js +141 -0
- package/dist/execution/transaction.executor.d.ts +147 -0
- package/dist/execution/transaction.executor.js +13 -0
- package/dist/generate/artifact.emitter.d.ts +50 -0
- package/dist/generate/artifact.emitter.js +105 -0
- package/dist/generate/declared-support.reader.d.ts +33 -0
- package/dist/generate/declared-support.reader.js +74 -0
- package/dist/generate/discovery.bin.d.ts +2 -0
- package/dist/generate/discovery.bin.js +15 -0
- package/dist/generate/discovery.cli.d.ts +24 -0
- package/dist/generate/discovery.cli.js +163 -0
- package/dist/generate/existence.gate.d.ts +73 -0
- package/dist/generate/existence.gate.js +91 -0
- package/dist/generate/index.d.ts +81 -0
- package/dist/generate/index.js +65 -0
- package/dist/generate/node-version.guard.d.ts +8 -0
- package/dist/generate/node-version.guard.js +59 -0
- package/dist/generate/prisma-cli.resolver.d.ts +48 -0
- package/dist/generate/prisma-cli.resolver.js +95 -0
- package/dist/generate/prisma.generate.d.ts +164 -0
- package/dist/generate/prisma.generate.js +278 -0
- package/dist/generate/reader-crosscheck.d.ts +156 -0
- package/dist/generate/reader-crosscheck.js +202 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/prisma-client.interface.d.ts +48 -0
- package/dist/prisma-client.interface.js +9 -0
- package/dist/prisma.adapter.d.ts +55 -0
- package/dist/prisma.adapter.js +105 -0
- package/dist/provider/provider-profile.d.ts +53 -0
- package/dist/provider/provider-profile.js +91 -0
- package/dist/translation/identifier.addressing.d.ts +80 -0
- package/dist/translation/identifier.addressing.js +30 -0
- package/dist/translation/mutation.translator.d.ts +115 -0
- package/dist/translation/mutation.translator.js +580 -0
- package/dist/translation/operation.translator.d.ts +273 -0
- package/dist/translation/operation.translator.js +377 -0
- package/dist/translation/order-pagination.translator.d.ts +86 -0
- package/dist/translation/order-pagination.translator.js +159 -0
- package/dist/translation/projection.translator.d.ts +115 -0
- package/dist/translation/projection.translator.js +151 -0
- package/dist/translation/scalar.conversion.d.ts +101 -0
- package/dist/translation/scalar.conversion.js +221 -0
- package/dist/translation/where.translator.d.ts +79 -0
- package/dist/translation/where.translator.js +254 -0
- package/package.json +102 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PrismaIsolationLevel } from "./provider/provider-profile.js";
|
|
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.
|
|
7
|
+
*
|
|
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`:
|
|
13
|
+
*
|
|
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>'.
|
|
17
|
+
*
|
|
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.
|
|
22
|
+
*/
|
|
23
|
+
/** Adapter-owned transaction options; never Aventara public configuration (D-806). */
|
|
24
|
+
export type PrismaTransactionOptions = {
|
|
25
|
+
/** Prisma's own default is 2000 ms **[PKG, `runtime/client.d.ts`]**. */
|
|
26
|
+
readonly maxWait?: number;
|
|
27
|
+
/** Prisma's own default is 5000 ms **[PKG, `runtime/client.d.ts`]**. */
|
|
28
|
+
readonly timeout?: number;
|
|
29
|
+
readonly isolationLevel?: PrismaIsolationLevel;
|
|
30
|
+
};
|
|
31
|
+
/** One Prisma model delegate, as far as the adapter is concerned. */
|
|
32
|
+
export type PrismaDelegate = Readonly<Record<string, unknown>>;
|
|
33
|
+
/**
|
|
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).
|
|
36
|
+
*/
|
|
37
|
+
export interface PrismaDelegateHost {
|
|
38
|
+
$transaction<T>(work: (transactional: PrismaDelegateHost) => Promise<T>, options?: PrismaTransactionOptions): Promise<T>;
|
|
39
|
+
}
|
|
40
|
+
/** The delegate index view of a host. */
|
|
41
|
+
export type PrismaDelegateIndex = Readonly<Record<string, PrismaDelegate | undefined>>;
|
|
42
|
+
/**
|
|
43
|
+
* The single place the structural gap between a generated client (a class,
|
|
44
|
+
* with no index signature) and a delegate index is crossed. Every delegate
|
|
45
|
+
* lookup in the adapter goes through here, so there is exactly one assertion
|
|
46
|
+
* to audit rather than one per call site.
|
|
47
|
+
*/
|
|
48
|
+
export declare function prismaDelegates(client: PrismaDelegateHost): PrismaDelegateIndex;
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export function prismaDelegates(client) {
|
|
8
|
+
return client;
|
|
9
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Adapter } from "@aventara/core";
|
|
2
|
+
import type { PrismaAdapterModel, PrismaDiscoveryArtifact } from "./discovery/discovery-artifact.interface.js";
|
|
3
|
+
import type { PrismaDelegateHost, PrismaTransactionOptions } from "./prisma-client.interface.js";
|
|
4
|
+
import { type PrismaDriver, type PrismaProvider } from "./provider/provider-profile.js";
|
|
5
|
+
/**
|
|
6
|
+
* T-1 — the adapter's only public entry point, and the composition root.
|
|
7
|
+
*
|
|
8
|
+
* `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).
|
|
12
|
+
*/
|
|
13
|
+
export type PrismaAdapterOptions<TClient extends PrismaDelegateHost, M extends PrismaAdapterModel = PrismaAdapterModel> = {
|
|
14
|
+
/** Application-owned, already-constructed client (spec §18.2). */
|
|
15
|
+
readonly client: TClient;
|
|
16
|
+
/**
|
|
17
|
+
* The generate-time discovery artifact this adapter loads and checks
|
|
18
|
+
* (D-802, T-3.2).
|
|
19
|
+
*
|
|
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`.
|
|
27
|
+
*/
|
|
28
|
+
readonly discovery: PrismaDiscoveryArtifact<M>;
|
|
29
|
+
/** Declared, never sniffed from client internals (D-804). */
|
|
30
|
+
readonly provider: PrismaProvider;
|
|
31
|
+
readonly driver: PrismaDriver;
|
|
32
|
+
/** Adapter-owned, never Aventara public configuration (D-806). */
|
|
33
|
+
readonly transactionOptions?: PrismaTransactionOptions;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The installed client's version, resolved by the caller.
|
|
37
|
+
*
|
|
38
|
+
* `@prisma/client` is a *peer* dependency, so the version that matters is the
|
|
39
|
+
* one installed beside the application, not beside this package. The caller
|
|
40
|
+
* supplies it; `createPrismaAdapter` does not go looking, which is also what
|
|
41
|
+
* keeps the construction path free of Prisma tooling.
|
|
42
|
+
*/
|
|
43
|
+
export type PrismaAdapterConstruction<TClient extends PrismaDelegateHost, M extends PrismaAdapterModel = PrismaAdapterModel> = PrismaAdapterOptions<TClient, M> & {
|
|
44
|
+
readonly installedPrismaVersion?: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Construction is `async` because core's `Adapter.model` is a settled fact read
|
|
48
|
+
* synchronously at framework construction, while *verifying* the artifact is
|
|
49
|
+
* asynchronous: `computeModelHash` digests through `globalThis.crypto.subtle`,
|
|
50
|
+
* which is a promise-returning API. The artifact itself is already a value in
|
|
51
|
+
* hand (`options.discovery`); only its verification has to
|
|
52
|
+
* await, so it happens here, once, before an adapter exists — every fence is
|
|
53
|
+
* the same fence, moved from the first `discover()` call to construction.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createPrismaAdapter<TClient extends PrismaDelegateHost, M extends PrismaAdapterModel = PrismaAdapterModel>(options: PrismaAdapterConstruction<TClient, M>): Promise<Adapter<M>>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { loadDiscoveryArtifact } from "./discovery/artifact.loader.js";
|
|
2
|
+
import { createPrismaOperationExecutor } from "./execution/operation.executor.js";
|
|
3
|
+
import { createPrismaTransactionExecutor } from "./execution/transaction.executor.js";
|
|
4
|
+
import { resolveProviderProfile, } from "./provider/provider-profile.js";
|
|
5
|
+
/**
|
|
6
|
+
* Each Prisma referential action as core's word for it (F-806). Keyed by the
|
|
7
|
+
* closed {@link PrismaReferentialAction} union with `satisfies`, so a sixth
|
|
8
|
+
* Prisma action is a compile error here rather than an edge core never follows,
|
|
9
|
+
* and valued by core's own union, so a word core does not have is one too. The
|
|
10
|
+
* mapping is one to one (the architect's five words, 2026-10-04): `no-action`
|
|
11
|
+
* stays `noAction` although, measured on both engines (Phase 9 S0, U4), it is not
|
|
12
|
+
* `DEFERRABLE` and answers one delete exactly as `restrict` does; `set-default`
|
|
13
|
+
* stays `setDefault` although, like `setNull`, it keeps the rows.
|
|
14
|
+
*/
|
|
15
|
+
const DELETE_EFFECTS = {
|
|
16
|
+
cascade: "cascade",
|
|
17
|
+
restrict: "restrict",
|
|
18
|
+
"no-action": "noAction",
|
|
19
|
+
"set-null": "setNull",
|
|
20
|
+
"set-default": "setDefault",
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The artifact's referential actions, translated into core's vocabulary.
|
|
24
|
+
*
|
|
25
|
+
* The direction is carried over unchanged, because it is the same sentence on
|
|
26
|
+
* both sides of the seam: an edge listed under Resource `R` says **deleting a
|
|
27
|
+
* `target` row does `onDelete` to rows of `R`**. Only owning edges exist in the
|
|
28
|
+
* artifact, and `onUpdate` is not carried: core plans the delete closure only.
|
|
29
|
+
*/
|
|
30
|
+
function translateReferentialActions(table) {
|
|
31
|
+
return Object.fromEntries(Object.entries(table).map(([resource, { owningEdges }]) => [
|
|
32
|
+
resource,
|
|
33
|
+
owningEdges.map((edge) => ({
|
|
34
|
+
field: edge.field,
|
|
35
|
+
target: edge.target,
|
|
36
|
+
onDelete: DELETE_EFFECTS[edge.onDelete],
|
|
37
|
+
})),
|
|
38
|
+
]));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Construction is `async` because core's `Adapter.model` is a settled fact read
|
|
42
|
+
* synchronously at framework construction, while *verifying* the artifact is
|
|
43
|
+
* asynchronous: `computeModelHash` digests through `globalThis.crypto.subtle`,
|
|
44
|
+
* which is a promise-returning API. The artifact itself is already a value in
|
|
45
|
+
* hand (`options.discovery`); only its verification has to
|
|
46
|
+
* await, so it happens here, once, before an adapter exists — every fence is
|
|
47
|
+
* the same fence, moved from the first `discover()` call to construction.
|
|
48
|
+
*/
|
|
49
|
+
export async function createPrismaAdapter(options) {
|
|
50
|
+
const profile = resolveProviderProfile({
|
|
51
|
+
provider: options.provider,
|
|
52
|
+
driver: options.driver,
|
|
53
|
+
});
|
|
54
|
+
// The loader is a *verifier*, and its return is deliberately discarded. It
|
|
55
|
+
// takes `unknown` because it guards the runtime boundary, so its answer is
|
|
56
|
+
// the general `PrismaAdapterModel`; the literal `M` is already in hand on
|
|
57
|
+
// `options.discovery.model`, which is the very object the loader would have
|
|
58
|
+
// handed back. Reading it from the argument keeps the literal without a
|
|
59
|
+
// cast, and keeps the loader honest about validating untrusted input.
|
|
60
|
+
await loadDiscoveryArtifact({
|
|
61
|
+
artifact: options.discovery,
|
|
62
|
+
installedPrismaVersion: options.installedPrismaVersion ?? options.discovery.prismaVersion,
|
|
63
|
+
});
|
|
64
|
+
const model = options.discovery.model;
|
|
65
|
+
// Read once, like the model: a settled fact of the constructed adapter.
|
|
66
|
+
const referentialActions = translateReferentialActions(options.discovery.referentialActions);
|
|
67
|
+
// Task 10: `execute` is real. The executor is built once, at construction, and
|
|
68
|
+
// closes over the client, the discovered model and the generate-time
|
|
69
|
+
// translation table — the three facts every call needs and none of which can
|
|
70
|
+
// change afterwards.
|
|
71
|
+
//
|
|
72
|
+
// `translation` is widened by one deliberate cast. `PrismaTranslationTable<M>`
|
|
73
|
+
// is a mapped type over `M`'s own resource keys, which is exactly what makes a
|
|
74
|
+
// missing Resource a compile error at the generate step; a mapped type over a
|
|
75
|
+
// literal union carries no index signature, so the executor — which looks a
|
|
76
|
+
// Resource up by a name core validated — takes the widened view. The narrow
|
|
77
|
+
// type has already done its work by the time this line runs.
|
|
78
|
+
const { execute } = createPrismaOperationExecutor({
|
|
79
|
+
client: options.client,
|
|
80
|
+
model: options.discovery.model,
|
|
81
|
+
translation: options.discovery.translation,
|
|
82
|
+
scope: "root",
|
|
83
|
+
...(options.transactionOptions === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: { transactionOptions: options.transactionOptions }),
|
|
86
|
+
});
|
|
87
|
+
// DA-12: `transaction` is mounted exactly when the declared driver supports
|
|
88
|
+
// an interactive one. For `@prisma/adapter-d1` the profile says `"none"`
|
|
89
|
+
// [PKG], so core never calls it and the `_transactions` route is never
|
|
90
|
+
// mounted. The declaration is Task 10's (D-805) and the body is Task 11's:
|
|
91
|
+
// one interactive transaction per plan, with the transaction-scoped executor
|
|
92
|
+
// as the only thing the callback hands core (C-803, C-804).
|
|
93
|
+
if (profile.transactions === "none") {
|
|
94
|
+
return { model, referentialActions, execute };
|
|
95
|
+
}
|
|
96
|
+
const transaction = createPrismaTransactionExecutor({
|
|
97
|
+
client: options.client,
|
|
98
|
+
model: options.discovery.model,
|
|
99
|
+
translation: options.discovery.translation,
|
|
100
|
+
...(options.transactionOptions === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: { transactionOptions: options.transactionOptions }),
|
|
103
|
+
});
|
|
104
|
+
return { model, referentialActions, execute, transaction };
|
|
105
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TransactionCapability } from "@aventara/core";
|
|
2
|
+
/**
|
|
3
|
+
* T-2 / D-824 — the one closed table of provider and driver facts, carrying
|
|
4
|
+
* **only** the facts no reader can supply.
|
|
5
|
+
*
|
|
6
|
+
* What is deliberately absent: `supportsScalarLists`, `supportsOrderNulls`,
|
|
7
|
+
* `supportsCreateManyAndReturn` and `supportsUpdateManyAndReturn` are
|
|
8
|
+
* *discovered* from the generated client's own types at generate time and
|
|
9
|
+
* recorded in `PrismaDiscoveryArtifact` (T-3, T-3.2); `supportsEmulatedFirst
|
|
10
|
+
* Variants` is *derived* — it is exactly `transactions !== "none"` — and is
|
|
11
|
+
* computed where the §8.3 gate lives. A stored copy of either class would be a
|
|
12
|
+
* second source of truth, which is dictionary invariant 6.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here is inferred at runtime, because nothing here is derived at
|
|
15
|
+
* runtime at all.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The two engines Phase 8 runs — a value, so the bin's `--provider` check and
|
|
19
|
+
* the manifest's declaration drift guard (`declaration-drift.gate.spec.ts`)
|
|
20
|
+
* read the same list the type is derived from.
|
|
21
|
+
*/
|
|
22
|
+
export declare const PRISMA_PROVIDERS: readonly ["sqlite", "postgresql"];
|
|
23
|
+
export type PrismaProvider = (typeof PRISMA_PROVIDERS)[number];
|
|
24
|
+
/**
|
|
25
|
+
* The closed driver set. Neon-HTTP and PlanetScale-HTTP are **absent**, not
|
|
26
|
+
* `false`: their interactive-transaction support is genuinely UNVERIFIED for
|
|
27
|
+
* Prisma 7 (C-825 — no adapter package is installed, neither driver is in
|
|
28
|
+
* Phase 8's engine split, and all three v7 driver-adapter documentation pages
|
|
29
|
+
* returned HTTP 404), and an entry with no evidence is a guess.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PRISMA_DRIVERS: readonly ["@prisma/adapter-better-sqlite3", "@prisma/adapter-pg", "@prisma/adapter-d1"];
|
|
32
|
+
export type PrismaDriver = (typeof PRISMA_DRIVERS)[number];
|
|
33
|
+
/** The five levels `Prisma.IsolationLevel` admits **[PKG, `runtime/client.d.ts`]**. */
|
|
34
|
+
export type PrismaIsolationLevel = "ReadUncommitted" | "ReadCommitted" | "RepeatableRead" | "Snapshot" | "Serializable";
|
|
35
|
+
/** The caller's explicit declaration; never sniffed from client internals (D-804). */
|
|
36
|
+
export type PrismaProviderDeclaration = {
|
|
37
|
+
readonly provider: PrismaProvider;
|
|
38
|
+
readonly driver: PrismaDriver;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The resolved profile. Three members, and three only — see the type comment
|
|
42
|
+
* above for what is discovered or derived instead.
|
|
43
|
+
*/
|
|
44
|
+
export type PrismaProviderProfile = {
|
|
45
|
+
readonly provider: PrismaProvider;
|
|
46
|
+
readonly transactions: TransactionCapability;
|
|
47
|
+
readonly isolationLevels: readonly PrismaIsolationLevel[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Resolves the profile for a declaration. Pure, total over the closed sets,
|
|
51
|
+
* and the only place provider truth is read.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveProviderProfile(declaration: PrismaProviderDeclaration): PrismaProviderProfile;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T-2 / D-824 — the one closed table of provider and driver facts, carrying
|
|
3
|
+
* **only** the facts no reader can supply.
|
|
4
|
+
*
|
|
5
|
+
* What is deliberately absent: `supportsScalarLists`, `supportsOrderNulls`,
|
|
6
|
+
* `supportsCreateManyAndReturn` and `supportsUpdateManyAndReturn` are
|
|
7
|
+
* *discovered* from the generated client's own types at generate time and
|
|
8
|
+
* recorded in `PrismaDiscoveryArtifact` (T-3, T-3.2); `supportsEmulatedFirst
|
|
9
|
+
* Variants` is *derived* — it is exactly `transactions !== "none"` — and is
|
|
10
|
+
* computed where the §8.3 gate lives. A stored copy of either class would be a
|
|
11
|
+
* second source of truth, which is dictionary invariant 6.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here is inferred at runtime, because nothing here is derived at
|
|
14
|
+
* runtime at all.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The two engines Phase 8 runs — a value, so the bin's `--provider` check and
|
|
18
|
+
* the manifest's declaration drift guard (`declaration-drift.gate.spec.ts`)
|
|
19
|
+
* read the same list the type is derived from.
|
|
20
|
+
*/
|
|
21
|
+
export const PRISMA_PROVIDERS = ["sqlite", "postgresql"];
|
|
22
|
+
/**
|
|
23
|
+
* The closed driver set. Neon-HTTP and PlanetScale-HTTP are **absent**, not
|
|
24
|
+
* `false`: their interactive-transaction support is genuinely UNVERIFIED for
|
|
25
|
+
* Prisma 7 (C-825 — no adapter package is installed, neither driver is in
|
|
26
|
+
* Phase 8's engine split, and all three v7 driver-adapter documentation pages
|
|
27
|
+
* returned HTTP 404), and an entry with no evidence is a guess.
|
|
28
|
+
*/
|
|
29
|
+
export const PRISMA_DRIVERS = [
|
|
30
|
+
"@prisma/adapter-better-sqlite3",
|
|
31
|
+
"@prisma/adapter-pg",
|
|
32
|
+
"@prisma/adapter-d1",
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* `transactions` is a **DRIVER** fact, and each row carries its evidence.
|
|
36
|
+
*
|
|
37
|
+
* - `@prisma/adapter-d1` → `"none"` **[PKG]**: `runtime/client.js` of
|
|
38
|
+
* `@prisma/client` 7.10.0 branches on
|
|
39
|
+
* `this._engineConfig.adapter?.adapterName === "@prisma/adapter-d1"` and
|
|
40
|
+
* throws `"Cloudflare D1 does not support interactive transactions. …"`.
|
|
41
|
+
* Read from the installed package on 2026-09-23 (C-824, F-703).
|
|
42
|
+
* - `@prisma/adapter-better-sqlite3` and `@prisma/adapter-pg` →
|
|
43
|
+
* `"interactive"` **[RUN8]**: observed, not assumed. D-805 conditions this
|
|
44
|
+
* value on an interactive transaction having been *proved to run* on each
|
|
45
|
+
* driver, and `src/provider/interactive-transaction.spec.ts` runs one — a
|
|
46
|
+
* write and a read issued through the callback's `tx`, with the write
|
|
47
|
+
* visible afterwards — on both. Produced by
|
|
48
|
+
* `AVENTARA_TEST_POSTGRES_URL=postgresql://aventara:aventara_dev@127.0.0.1:5432/aventara
|
|
49
|
+
* npx vitest run --config ../../vitest.config.mts --root .
|
|
50
|
+
* src/provider/interactive-transaction.spec.ts --reporter=verbose`
|
|
51
|
+
* from `packages/prisma7-adapter`, which is what the relative `--config` path
|
|
52
|
+
* and `--root .` are relative to. Run on 2026-09-23 against `@prisma/client`
|
|
53
|
+
* 7.10.0, Node v24.20.0, SQLite through better-sqlite3 12.11.1 and
|
|
54
|
+
* PostgreSQL 18.6: 4 passed, and 2 of those 4 skip when the URL is absent, so
|
|
55
|
+
* the PostgreSQL half is not a silent pass. Re-run as written on 2026-10-02
|
|
56
|
+
* after the config's `.mts` rename (`aba66a2`): 4 passed, and 2 passed with
|
|
57
|
+
* 2 skipped without the URL.
|
|
58
|
+
*/
|
|
59
|
+
const DRIVER_TRANSACTIONS = {
|
|
60
|
+
"@prisma/adapter-better-sqlite3": "interactive",
|
|
61
|
+
"@prisma/adapter-pg": "interactive",
|
|
62
|
+
"@prisma/adapter-d1": "none",
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* `isolationLevels` is a **PROVIDER** fact **[DOC7]**, fetched 2026-09-23 from
|
|
66
|
+
* the version-pinned `prisma.io/docs/orm/v7/…/queries/transactions` page:
|
|
67
|
+
* PostgreSQL supports ReadUncommitted, ReadCommitted, RepeatableRead and
|
|
68
|
+
* Serializable; SQLite supports Serializable only. `Snapshot` is a SQL Server
|
|
69
|
+
* level and belongs to neither Phase 8 engine, so it appears in the level
|
|
70
|
+
* union (it is one of the five Prisma admits) and in no row.
|
|
71
|
+
*/
|
|
72
|
+
const PROVIDER_ISOLATION_LEVELS = {
|
|
73
|
+
sqlite: ["Serializable"],
|
|
74
|
+
postgresql: [
|
|
75
|
+
"ReadUncommitted",
|
|
76
|
+
"ReadCommitted",
|
|
77
|
+
"RepeatableRead",
|
|
78
|
+
"Serializable",
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the profile for a declaration. Pure, total over the closed sets,
|
|
83
|
+
* and the only place provider truth is read.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveProviderProfile(declaration) {
|
|
86
|
+
return {
|
|
87
|
+
provider: declaration.provider,
|
|
88
|
+
transactions: DRIVER_TRANSACTIONS[declaration.driver],
|
|
89
|
+
isolationLevels: PROVIDER_ISOLATION_LEVELS[declaration.provider],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { PrismaIdentifierAddress } from "../discovery/discovery-artifact.interface.js";
|
|
2
|
+
/**
|
|
3
|
+
* The one owner of **core's flat identifier → Prisma's addressed
|
|
4
|
+
* `<M>WhereUniqueInput`**.
|
|
5
|
+
*
|
|
6
|
+
* # Why this module exists at all
|
|
7
|
+
*
|
|
8
|
+
* The rule is three facts, every one of them measured at 7.10.0 and recorded in
|
|
9
|
+
* {@link PrismaIdentifierAddress}: the generate-time `key` is matched and never
|
|
10
|
+
* re-derived (`members.join("_")` is wrong for every `name:`-renamed criterion),
|
|
11
|
+
* a key wraps its members iff `members.length > 1`, and the identifier chosen is
|
|
12
|
+
* the first recorded criterion the caller's values complete.
|
|
13
|
+
*
|
|
14
|
+
* Task 9 flagged that the rule had landed twice — `addressCursor` in
|
|
15
|
+
* `order-pagination.translator.ts` and `addressUnique` in
|
|
16
|
+
* `mutation.translator.ts` — and Task 10 needed it a third time, for the
|
|
17
|
+
* top-level where-unique of `find.unique`, `update.unique`, `delete.unique` and
|
|
18
|
+
* `upsert.unique`. Three copies of a measured rule is three places a later
|
|
19
|
+
* maintainer can fix two of, so the rule moved here and the three call sites
|
|
20
|
+
* became three *configurations* of it.
|
|
21
|
+
*
|
|
22
|
+
* # What the call sites still own, and why that is not a leak
|
|
23
|
+
*
|
|
24
|
+
* Two things differ between them and neither can be collapsed:
|
|
25
|
+
*
|
|
26
|
+
* - **How complete the values must be.** A unique selector is `"exact"`: core's
|
|
27
|
+
* selector carries one identifier and nothing else, so a selector with a spare
|
|
28
|
+
* key is not that identifier. A cursor is `"subset"`: `validateCursor` requires
|
|
29
|
+
* the cursor's keys to equal the *active ordered scalar fields*, which include
|
|
30
|
+
* a complete identifier **plus** every other ordered key, so the identifier is
|
|
31
|
+
* a proper subset of what arrives.
|
|
32
|
+
* - **How a member's value is emitted.** `mutation.translator.ts` runs each
|
|
33
|
+
* member through C-834's outbound scalar conversion (a core `Decimal` must
|
|
34
|
+
* leave as a base-10 string); a cursor's values are already Prisma-shaped.
|
|
35
|
+
* Passing the conversion in as `emit` keeps the scalar table in the module that
|
|
36
|
+
* owns it rather than importing it here.
|
|
37
|
+
*
|
|
38
|
+
* # No spec file of its own, deliberately
|
|
39
|
+
*
|
|
40
|
+
* Every branch is exercised through its three callers' specs —
|
|
41
|
+
* `order-pagination.translator.spec.ts` (subset, compound wrapping, the
|
|
42
|
+
* "addresses no row" refusal), `mutation.translator.spec.ts` (exact, renamed
|
|
43
|
+
* keys, Decimal members) and `operation.translator.spec.ts` (exact, at the top
|
|
44
|
+
* level). A fourth spec restating them would test this module's three lines
|
|
45
|
+
* twice and the measured facts not at all.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* How completely the supplied values must cover a criterion.
|
|
49
|
+
*
|
|
50
|
+
* `"exact"` — the values are exactly one identifier: every member present and no
|
|
51
|
+
* other key. `"subset"` — the values may carry more keys than the criterion's
|
|
52
|
+
* members, and the criterion matches when all of its members are present.
|
|
53
|
+
*/
|
|
54
|
+
export type PrismaIdentifierMatch = "exact" | "subset";
|
|
55
|
+
/** One member's value, on the way out to Prisma. */
|
|
56
|
+
export type PrismaIdentifierMemberEmitter = (member: string, value: unknown) => unknown;
|
|
57
|
+
export type PrismaIdentifierAddressing = {
|
|
58
|
+
/** The recorded criterion that matched. */
|
|
59
|
+
readonly address: PrismaIdentifierAddress;
|
|
60
|
+
/**
|
|
61
|
+
* The `<M>WhereUniqueInput` fragment: `{ [key]: value }` for a single-column
|
|
62
|
+
* criterion, `{ [key]: { …members } }` for a compound one.
|
|
63
|
+
*/
|
|
64
|
+
readonly where: Readonly<Record<string, unknown>>;
|
|
65
|
+
};
|
|
66
|
+
export type PrismaIdentifierAddressingInput = {
|
|
67
|
+
/** The Resource's criteria, in discovery order — the first match wins. */
|
|
68
|
+
readonly addresses: readonly PrismaIdentifierAddress[];
|
|
69
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
70
|
+
readonly match: PrismaIdentifierMatch;
|
|
71
|
+
readonly emit: PrismaIdentifierMemberEmitter;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Addresses one row by the first recorded criterion the values complete.
|
|
75
|
+
*
|
|
76
|
+
* @returns the addressing, or `undefined` when no criterion is complete — which
|
|
77
|
+
* is a caller-specific refusal (each call site raises its own error type with
|
|
78
|
+
* its own path), never a silently unaddressed write.
|
|
79
|
+
*/
|
|
80
|
+
export declare function addressIdentifier(input: PrismaIdentifierAddressingInput): PrismaIdentifierAddressing | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Addresses one row by the first recorded criterion the values complete.
|
|
3
|
+
*
|
|
4
|
+
* @returns the addressing, or `undefined` when no criterion is complete — which
|
|
5
|
+
* is a caller-specific refusal (each call site raises its own error type with
|
|
6
|
+
* its own path), never a silently unaddressed write.
|
|
7
|
+
*/
|
|
8
|
+
export function addressIdentifier(input) {
|
|
9
|
+
const keyCount = Object.keys(input.values).length;
|
|
10
|
+
for (const address of input.addresses) {
|
|
11
|
+
if (input.match === "exact" && address.members.length !== keyCount) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (!address.members.every((member) => Object.hasOwn(input.values, member))) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const members = address.members.map((member) => [member, input.emit(member, input.values[member])]);
|
|
18
|
+
const [single] = members;
|
|
19
|
+
// A single-column criterion does NOT wrap, and a compound one does. The
|
|
20
|
+
// difference is `members.length`, read off the recorded criterion rather
|
|
21
|
+
// than guessed from the shape of the value.
|
|
22
|
+
return {
|
|
23
|
+
address,
|
|
24
|
+
where: members.length === 1 && single !== undefined
|
|
25
|
+
? { [address.key]: single[1] }
|
|
26
|
+
: { [address.key]: Object.fromEntries(members) },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type AdapterModel, type OperationIdentity } from "@aventara/core";
|
|
2
|
+
import type { PrismaIdentifierAddress } from "../discovery/discovery-artifact.interface.js";
|
|
3
|
+
/**
|
|
4
|
+
* T-7's mutation half — one validated Aventara `data` in, one Prisma write
|
|
5
|
+
* argument out.
|
|
6
|
+
*
|
|
7
|
+
* # Pure, and the tables are the module
|
|
8
|
+
*
|
|
9
|
+
* No client, no database, no provider name. Three tables carry everything this
|
|
10
|
+
* module knows, each one TOTAL over a core union by `satisfies`, so a member
|
|
11
|
+
* added to core with no row here is a compile error rather than a silently
|
|
12
|
+
* dropped argument (C-855's emission half, C-837's translator table — the one
|
|
13
|
+
* `vocabulary.dictionary.ts` explicitly left to this task):
|
|
14
|
+
*
|
|
15
|
+
* - {@link SCALAR_DIRECTIVE_TOKENS} over `ScalarMutationDirective`;
|
|
16
|
+
* - {@link RELATION_DIRECTIVE_EMISSIONS} over `RelationMutationDirective`;
|
|
17
|
+
* - {@link MUTATION_DATA_SITES} over every standard operation key, which is
|
|
18
|
+
* C-818's enforcement point.
|
|
19
|
+
*
|
|
20
|
+
* C-834's own table is NOT one of them any more: the scalar value conversion was
|
|
21
|
+
* born here, and `where.translator.ts` and `execution/result.shaper.ts` need the
|
|
22
|
+
* same answers, so it moved to `scalar.conversion.ts` and all three read it
|
|
23
|
+
* (Task 10's `identifier.addressing.ts` is the precedent). This module's
|
|
24
|
+
* emission site is {@link emitValue}, which is now a call rather than a copy.
|
|
25
|
+
*
|
|
26
|
+
* Phase 7's `RELATION_MUTATION_RECORD_SITES` is the pattern
|
|
27
|
+
* (`packages/core/src/runtime/mutation-planner.ts`): `as const satisfies
|
|
28
|
+
* Readonly<Record<Union, …>>` keeps every cell a literal while making the union
|
|
29
|
+
* coverage a compile-time obligation. Two of core's passes are already keyed
|
|
30
|
+
* that way; this is the third, and the only one that names Prisma.
|
|
31
|
+
*
|
|
32
|
+
* # Refusing is part of honouring (the `where.translator.ts` stance)
|
|
33
|
+
*
|
|
34
|
+
* The adapter assumes validated input (C-845). It still refuses to EMIT a
|
|
35
|
+
* directive the discovered model does not advertise for that field and stage,
|
|
36
|
+
* and refuses one for which Prisma's nested input at that stage and cardinality
|
|
37
|
+
* has no key at all. Both refusals are C-855 seen from the emission side: an
|
|
38
|
+
* argument is honoured or it is not sent. A directive quietly dropped here would
|
|
39
|
+
* surface as a write that silently did less than it was asked to.
|
|
40
|
+
*
|
|
41
|
+
* # C-818 cannot reach the Contract, so it is enforced here
|
|
42
|
+
*
|
|
43
|
+
* `RelationFieldCapabilities` is one record per field with a single `create`
|
|
44
|
+
* member **[CORE, adapters/adapter-field.ts]**, so "relation create directives
|
|
45
|
+
* for `create.one` and none for `create.many`" is unrepresentable in the model;
|
|
46
|
+
* `model.discoverer.ts` composes the permissive `create.one` answer and says so.
|
|
47
|
+
* The restriction therefore lives in {@link MUTATION_DATA_SITES}, which is keyed
|
|
48
|
+
* by the operation the data belongs to. Measured at 7.10.0 on both engines
|
|
49
|
+
* (2026-10-02), a nested relation key in a bulk write is a hard failure, not a
|
|
50
|
+
* no-op:
|
|
51
|
+
*
|
|
52
|
+
* - `post.createMany({ data: [{ title, author: { connect: … } }] })` →
|
|
53
|
+
* *"Argument `authorId` is missing."*
|
|
54
|
+
* - `post.updateMany({ where, data: { category: { connect: … } } })` →
|
|
55
|
+
* *"Unknown argument `category`. Available options are marked with ?."*
|
|
56
|
+
*
|
|
57
|
+
* # Nothing in the caller's arguments is touched
|
|
58
|
+
*
|
|
59
|
+
* Every object and array returned is new. The input is read, never written: a
|
|
60
|
+
* translator that mutated its argument would corrupt the second attempt of a
|
|
61
|
+
* retried operation, which is the one place the bug would not show up in a
|
|
62
|
+
* single-shot test.
|
|
63
|
+
*/
|
|
64
|
+
/** A mutation the model, the stage or the active operation does not license. */
|
|
65
|
+
export declare class PrismaMutationTranslationError extends Error {
|
|
66
|
+
readonly name = "PrismaMutationTranslationError";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A Prisma write argument, as plain data: `<M>CreateInput`, `<M>UpdateInput`,
|
|
70
|
+
* `<M>CreateManyInput` or `<M>UpdateManyMutationInput` depending on the
|
|
71
|
+
* operation the translation was asked for.
|
|
72
|
+
*/
|
|
73
|
+
export type PrismaMutationData = Readonly<Record<string, unknown>>;
|
|
74
|
+
/** The two write stages core distinguishes. */
|
|
75
|
+
export type PrismaWriteStage = "create" | "update";
|
|
76
|
+
/**
|
|
77
|
+
* Task 7's `where` translator, injected rather than imported so this module
|
|
78
|
+
* stays a pure function of its inputs and the filter grammar keeps one owner
|
|
79
|
+
* (the same shape `projection.translator.ts` uses for the same reason).
|
|
80
|
+
*/
|
|
81
|
+
export type PrismaNestedWhereTranslator = (resource: string, where: unknown, path: string) => Readonly<Record<string, unknown>>;
|
|
82
|
+
export type PrismaMutationDependencies = {
|
|
83
|
+
readonly translateWhere: PrismaNestedWhereTranslator;
|
|
84
|
+
/**
|
|
85
|
+
* Prisma addressing per Resource, from the generate-time translation table
|
|
86
|
+
* (`PrismaDiscoveryArtifact.translation`). Required, because a nested
|
|
87
|
+
* `connect` is a `<T>WhereUniqueInput` and core's unique selector is FLAT: a
|
|
88
|
+
* compound identifier must be wrapped under its recorded key. Measured on
|
|
89
|
+
* both engines (2026-10-02) — the flat form is refused outright:
|
|
90
|
+
* *"Argument `connect`: Invalid value provided. Expected
|
|
91
|
+
* MetricWhereUniqueInput[], provided Object."*
|
|
92
|
+
*/
|
|
93
|
+
readonly identifiers: Readonly<Record<string, readonly PrismaIdentifierAddress[]>>;
|
|
94
|
+
};
|
|
95
|
+
export type MutationTranslation = {
|
|
96
|
+
/** The discovered model: field kinds, cardinalities, targets and capabilities. */
|
|
97
|
+
readonly model: AdapterModel;
|
|
98
|
+
/** The Aventara Resource key the data is written against. */
|
|
99
|
+
readonly resource: string;
|
|
100
|
+
/** The operation whose argument this data becomes; it decides C-818 admission. */
|
|
101
|
+
readonly operation: OperationIdentity;
|
|
102
|
+
/** Which of that operation's write stages this data is (`upsert.unique` has two). */
|
|
103
|
+
readonly stage: PrismaWriteStage;
|
|
104
|
+
/** A validated Aventara `CreateDataInput` / `UpdateDataInput`, as plain data. */
|
|
105
|
+
readonly data: unknown;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Translates one validated Aventara mutation `data` into the Prisma write
|
|
109
|
+
* argument for the named operation and stage.
|
|
110
|
+
*
|
|
111
|
+
* @throws {PrismaMutationTranslationError} rather than emitting a directive the
|
|
112
|
+
* model does not advertise, one Prisma has no key for at this stage and
|
|
113
|
+
* cardinality, or a nested relation write inside a bulk argument (C-818).
|
|
114
|
+
*/
|
|
115
|
+
export declare function translateMutationData(input: MutationTranslation, dependencies: PrismaMutationDependencies): PrismaMutationData;
|