@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.
Files changed (86) hide show
  1. package/LICENSE +91 -0
  2. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  3. package/README.md +564 -0
  4. package/dist/discovery/artifact.loader.d.ts +26 -0
  5. package/dist/discovery/artifact.loader.js +124 -0
  6. package/dist/discovery/capability-surface.interface.d.ts +191 -0
  7. package/dist/discovery/capability-surface.interface.js +13 -0
  8. package/dist/discovery/capability.deriver.d.ts +92 -0
  9. package/dist/discovery/capability.deriver.js +350 -0
  10. package/dist/discovery/discovery-artifact.interface.d.ts +210 -0
  11. package/dist/discovery/discovery-artifact.interface.js +1 -0
  12. package/dist/discovery/enum.mapper.d.ts +59 -0
  13. package/dist/discovery/enum.mapper.js +32 -0
  14. package/dist/discovery/field.mapper.d.ts +244 -0
  15. package/dist/discovery/field.mapper.js +387 -0
  16. package/dist/discovery/identifier.mapper.d.ts +188 -0
  17. package/dist/discovery/identifier.mapper.js +132 -0
  18. package/dist/discovery/model.discoverer.d.ts +108 -0
  19. package/dist/discovery/model.discoverer.js +341 -0
  20. package/dist/discovery/operations.deriver.d.ts +75 -0
  21. package/dist/discovery/operations.deriver.js +92 -0
  22. package/dist/discovery/scalar.mapper.d.ts +81 -0
  23. package/dist/discovery/scalar.mapper.js +51 -0
  24. package/dist/discovery/schema-facts.interface.d.ts +174 -0
  25. package/dist/discovery/schema-facts.interface.js +11 -0
  26. package/dist/discovery/schema.reader.d.ts +162 -0
  27. package/dist/discovery/schema.reader.js +692 -0
  28. package/dist/discovery/types.reader.d.ts +24 -0
  29. package/dist/discovery/types.reader.js +617 -0
  30. package/dist/discovery/vocabulary.dictionary.d.ts +258 -0
  31. package/dist/discovery/vocabulary.dictionary.js +429 -0
  32. package/dist/errors/error.normalizer.d.ts +250 -0
  33. package/dist/errors/error.normalizer.js +135 -0
  34. package/dist/errors/prisma-error.reader.d.ts +144 -0
  35. package/dist/errors/prisma-error.reader.js +167 -0
  36. package/dist/execution/first-variant.emulator.d.ts +64 -0
  37. package/dist/execution/first-variant.emulator.js +205 -0
  38. package/dist/execution/operation.executor.d.ts +87 -0
  39. package/dist/execution/operation.executor.js +210 -0
  40. package/dist/execution/result.shaper.d.ts +103 -0
  41. package/dist/execution/result.shaper.js +141 -0
  42. package/dist/execution/transaction.executor.d.ts +147 -0
  43. package/dist/execution/transaction.executor.js +13 -0
  44. package/dist/generate/artifact.emitter.d.ts +50 -0
  45. package/dist/generate/artifact.emitter.js +105 -0
  46. package/dist/generate/declared-support.reader.d.ts +33 -0
  47. package/dist/generate/declared-support.reader.js +74 -0
  48. package/dist/generate/discovery.bin.d.ts +2 -0
  49. package/dist/generate/discovery.bin.js +15 -0
  50. package/dist/generate/discovery.cli.d.ts +24 -0
  51. package/dist/generate/discovery.cli.js +163 -0
  52. package/dist/generate/existence.gate.d.ts +73 -0
  53. package/dist/generate/existence.gate.js +91 -0
  54. package/dist/generate/index.d.ts +81 -0
  55. package/dist/generate/index.js +65 -0
  56. package/dist/generate/node-version.guard.d.ts +8 -0
  57. package/dist/generate/node-version.guard.js +59 -0
  58. package/dist/generate/prisma-cli.resolver.d.ts +48 -0
  59. package/dist/generate/prisma-cli.resolver.js +95 -0
  60. package/dist/generate/prisma.generate.d.ts +164 -0
  61. package/dist/generate/prisma.generate.js +278 -0
  62. package/dist/generate/reader-crosscheck.d.ts +156 -0
  63. package/dist/generate/reader-crosscheck.js +202 -0
  64. package/dist/index.d.ts +5 -0
  65. package/dist/index.js +1 -0
  66. package/dist/prisma-client.interface.d.ts +48 -0
  67. package/dist/prisma-client.interface.js +9 -0
  68. package/dist/prisma.adapter.d.ts +55 -0
  69. package/dist/prisma.adapter.js +105 -0
  70. package/dist/provider/provider-profile.d.ts +53 -0
  71. package/dist/provider/provider-profile.js +91 -0
  72. package/dist/translation/identifier.addressing.d.ts +80 -0
  73. package/dist/translation/identifier.addressing.js +30 -0
  74. package/dist/translation/mutation.translator.d.ts +115 -0
  75. package/dist/translation/mutation.translator.js +580 -0
  76. package/dist/translation/operation.translator.d.ts +273 -0
  77. package/dist/translation/operation.translator.js +377 -0
  78. package/dist/translation/order-pagination.translator.d.ts +86 -0
  79. package/dist/translation/order-pagination.translator.js +159 -0
  80. package/dist/translation/projection.translator.d.ts +115 -0
  81. package/dist/translation/projection.translator.js +151 -0
  82. package/dist/translation/scalar.conversion.d.ts +101 -0
  83. package/dist/translation/scalar.conversion.js +221 -0
  84. package/dist/translation/where.translator.d.ts +79 -0
  85. package/dist/translation/where.translator.js +254 -0
  86. 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;