@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,105 @@
1
+ import { canonicalizeJson } from "@aventara/core";
2
+ import { computeModelHash } from "../discovery/artifact.loader.js";
3
+ /**
4
+ * Biome formats and lints checked-in `.ts`, and this file's bytes are the
5
+ * contract. The suppressions are emitted rather than configured so the artifact
6
+ * stays correct wherever a consumer drops it, including a repository whose
7
+ * formatter nobody here chose.
8
+ */
9
+ const BANNER = [
10
+ "// biome-ignore-all format: generated output; these bytes are the artifact",
11
+ "// biome-ignore-all lint: generated output",
12
+ "/* !!! Generated by @aventara/prisma7-adapter's generate step. Do not edit. !!! */",
13
+ "/* Regenerate with `aventara-prisma7-generate`. */",
14
+ ];
15
+ const TRANSLATION_DOC = [
16
+ "/**",
17
+ " * Prisma addressing, beside the model and never inside it.",
18
+ " *",
19
+ " * `key` is the key a `WhereUniqueInput` accepts for that identifier: the field",
20
+ " * name for a single column, the members joined with `_` in ATTRIBUTE order for",
21
+ " * a compound, or `@@id`/`@@unique`'s `name:` where one replaces it. Computed",
22
+ ' * once here and MATCHED at runtime, never re-derived — `members.join("_")` is',
23
+ " * wrong for every named criterion. A key wraps its members iff `members.length",
24
+ " * > 1`.",
25
+ " *",
26
+ " * This half is adapter-private. It is excluded from `modelHash` by",
27
+ " * construction, so a DB-side rename never moves the Contract identity.",
28
+ " */",
29
+ ];
30
+ const REFERENTIAL_ACTIONS_DOC = [
31
+ "/**",
32
+ " * The resolved referential actions, beside the model and never inside it.",
33
+ " *",
34
+ " * One entry per Resource, listing that Resource's OWNING relation edges — the",
35
+ " * ones that declare `@relation(fields: …)`, which is the only side PSL lets",
36
+ " * carry an action. Each edge reads in ONE direction, and reversing it is",
37
+ " * silent:",
38
+ " *",
39
+ " * deleting a `target` row does `onDelete` to rows of the ENCLOSING resource.",
40
+ " *",
41
+ ' * So "what does deleting an `X` do?" is answered by scanning every',
42
+ ' * Resource\'s `owningEdges` for `target === "X"` and reading the enclosing',
43
+ " * Resource key — never by looking under `X` itself.",
44
+ " *",
45
+ " * Both members are resolved: an absent PSL argument is not a third state but",
46
+ " * Prisma's measured effective default (`set-null` where every foreign-key",
47
+ " * scalar is optional, `restrict` otherwise, `cascade` on update). An action the",
48
+ " * reader cannot name fails the generate step instead of reaching this table.",
49
+ " *",
50
+ " * Excluded from `modelHash` by construction: the hash digests the model, and a",
51
+ " * referential action is not part of the model.",
52
+ " */",
53
+ ];
54
+ const MODEL_DOC = [
55
+ "/**",
56
+ " * The Base Contract: what the schema and the generated client agreed on,",
57
+ " * read once at generate time and never re-derived.",
58
+ " *",
59
+ " * `as const satisfies AdapterModel` is load-bearing. `as const` keeps every",
60
+ " * resource key, field key and enum member a literal type, which is what",
61
+ " * `EffectiveFieldKey` narrows on downstream; `satisfies` checks the shape",
62
+ " * without widening it. Drop either half and the model still *runs* while",
63
+ " * every narrowing keyed on it silently degrades to `string`.",
64
+ " */",
65
+ ];
66
+ const DISCOVERY_DOC = [
67
+ "/** Pass this to `createPrismaAdapter({ discovery })`. */",
68
+ ];
69
+ /**
70
+ * Canonical JSON, re-indented as a TypeScript object literal.
71
+ *
72
+ * Both halves go through this one function, so C-843 holds over the whole file
73
+ * rather than over the model alone: whatever key order the readers happened to
74
+ * build, two runs over one schema emit byte-identical bytes.
75
+ */
76
+ function canonicalLiteral(value) {
77
+ return JSON.stringify(JSON.parse(canonicalizeJson(value)), null, "\t");
78
+ }
79
+ export async function emitDiscoveryArtifactModule(emission) {
80
+ const modelHash = await computeModelHash(emission.model);
81
+ return `${[
82
+ ...BANNER,
83
+ "",
84
+ 'import type { AdapterModel } from "@aventara/core";',
85
+ "",
86
+ ...MODEL_DOC,
87
+ `export const model = ${canonicalLiteral(emission.model)} as const satisfies AdapterModel;`,
88
+ "",
89
+ ...TRANSLATION_DOC,
90
+ `export const translation = ${canonicalLiteral(emission.translation)} as const;`,
91
+ "",
92
+ ...REFERENTIAL_ACTIONS_DOC,
93
+ `export const referentialActions = ${canonicalLiteral(emission.referentialActions)} as const;`,
94
+ "",
95
+ ...DISCOVERY_DOC,
96
+ "export const discovery = {",
97
+ `\tprismaVersion: ${JSON.stringify(emission.prismaVersion)},`,
98
+ `\tprovider: ${JSON.stringify(emission.provider)},`,
99
+ `\tmodelHash: ${JSON.stringify(modelHash)},`,
100
+ "\tmodel,",
101
+ "\ttranslation,",
102
+ "\treferentialActions,",
103
+ "} as const;",
104
+ ].join("\n")}\n`;
105
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What this adapter **declares** it supports, read from its own manifest — the
3
+ * one place the fact is stated (R7: support is the adapter's to declare).
4
+ *
5
+ * The generate step needs two of those facts at run time: the `prisma` range,
6
+ * to decide whether a `prisma` CLI it found may run (D11), and the `typescript`
7
+ * range, to name in the sentence that refuses a compiler with no classic API
8
+ * (D4). Both are `peerDependencies` entries (D8). Reading them here rather than
9
+ * restating them as literals is what keeps the refusal sentences, the version
10
+ * check and the published manifest from drifting apart.
11
+ *
12
+ * The manifest is two directories up from this module wherever it runs: `src/generate/`
13
+ * under the specs, `dist/generate/` in a consumer's install, `.generate/generate/`
14
+ * under this package's own `generate` script.
15
+ */
16
+ export type DeclaredSupport = {
17
+ /** `peerDependencies.prisma`: the Prisma CLI versions the readers were written against. */
18
+ readonly prisma: string;
19
+ /** `peerDependencies.typescript`: the compilers the types reader runs on. */
20
+ readonly typescript: string;
21
+ };
22
+ export declare function readDeclaredSupport(): DeclaredSupport;
23
+ /**
24
+ * Whether `version` satisfies `range`, for the range forms a peer declaration
25
+ * here uses: space-separated comparators (`>=5.5.0 <7`), each `^`, `>=`, `>`,
26
+ * `<=`, `<`, `=` or bare, with missing minor and patch read as `0`.
27
+ *
28
+ * Only a plain release can satisfy: a pre-release (`8.0.0-rc.20`) never does,
29
+ * which is semver's own rule for a range whose comparators carry no pre-release
30
+ * tag. A range in any other form is a defect in this package's manifest, so it
31
+ * throws rather than answering a guess.
32
+ */
33
+ export declare function satisfiesDeclaredRange(version: string, range: string): boolean;
@@ -0,0 +1,74 @@
1
+ import { readFileSync } from "node:fs";
2
+ const MANIFEST = new URL("../../package.json", import.meta.url);
3
+ export function readDeclaredSupport() {
4
+ const manifest = JSON.parse(readFileSync(MANIFEST, "utf8"));
5
+ const prisma = manifest.peerDependencies?.prisma;
6
+ const typescript = manifest.peerDependencies?.typescript;
7
+ if (prisma === undefined || typescript === undefined) {
8
+ throw new Error(`${MANIFEST.pathname} declares no \`prisma\` or no \`typescript\` peer range, so the generate step cannot tell which toolchain it supports`);
9
+ }
10
+ return { prisma, typescript };
11
+ }
12
+ const RELEASE = /^(\d+)\.(\d+)\.(\d+)$/;
13
+ const COMPARATOR = /^(\^|>=|>|<=|<|=)?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/;
14
+ function compare(left, right) {
15
+ for (let index = 0; index < 3; index += 1) {
16
+ const difference = left[index] - right[index];
17
+ if (difference !== 0) {
18
+ return difference;
19
+ }
20
+ }
21
+ return 0;
22
+ }
23
+ /**
24
+ * Whether `version` satisfies `range`, for the range forms a peer declaration
25
+ * here uses: space-separated comparators (`>=5.5.0 <7`), each `^`, `>=`, `>`,
26
+ * `<=`, `<`, `=` or bare, with missing minor and patch read as `0`.
27
+ *
28
+ * Only a plain release can satisfy: a pre-release (`8.0.0-rc.20`) never does,
29
+ * which is semver's own rule for a range whose comparators carry no pre-release
30
+ * tag. A range in any other form is a defect in this package's manifest, so it
31
+ * throws rather than answering a guess.
32
+ */
33
+ export function satisfiesDeclaredRange(version, range) {
34
+ const release = RELEASE.exec(version);
35
+ if (release === null) {
36
+ return false;
37
+ }
38
+ const candidate = [
39
+ Number(release[1]),
40
+ Number(release[2]),
41
+ Number(release[3]),
42
+ ];
43
+ return range
44
+ .trim()
45
+ .split(/\s+/)
46
+ .every((comparator) => {
47
+ const parsed = COMPARATOR.exec(comparator);
48
+ // `^0.x` means something narrower than "same major", and no peer here
49
+ // declares one, so it is refused with the forms this does not read.
50
+ if (parsed === null || (parsed[1] === "^" && parsed[2] === "0")) {
51
+ throw new Error(`the declared range \`${range}\` has a comparator, \`${comparator}\`, that the generate step does not read`);
52
+ }
53
+ const bound = [
54
+ Number(parsed[2]),
55
+ Number(parsed[3] ?? 0),
56
+ Number(parsed[4] ?? 0),
57
+ ];
58
+ const order = compare(candidate, bound);
59
+ switch (parsed[1]) {
60
+ case "^":
61
+ return order >= 0 && candidate[0] === bound[0];
62
+ case ">=":
63
+ return order >= 0;
64
+ case ">":
65
+ return order > 0;
66
+ case "<=":
67
+ return order <= 0;
68
+ case "<":
69
+ return order < 0;
70
+ default:
71
+ return order === 0;
72
+ }
73
+ });
74
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { refuseUnsupportedNode } from "./node-version.guard.js";
3
+ /**
4
+ * `aventara-prisma7-generate`'s entry, what the manifest's `bin` names (F-855). It always runs — no
5
+ * `import.meta.main` — and checks this Node against the package's
6
+ * `engines.node` before it loads anything else: the program is imported only
7
+ * once the guard admits this Node, so an older Node meets one sentence and exit
8
+ * 1, never a silent exit 0 or a parse error from a module it cannot run.
9
+ *
10
+ * A promise chain rather than a top-level `await`, which Node 12 cannot parse:
11
+ * this file is read by the Nodes the package does not support.
12
+ */
13
+ if (!refuseUnsupportedNode("aventara-prisma7-generate", new URL("../../package.json", import.meta.url))) {
14
+ void import("./discovery.cli.js").then((program) => program.runFromProcess());
15
+ }
@@ -0,0 +1,24 @@
1
+ import { type PrismaDriver, type PrismaProvider } from "../provider/provider-profile.js";
2
+ export type PrismaGenerateCliOptions = {
3
+ readonly schema: string;
4
+ readonly clientDirectory: string;
5
+ readonly provider: PrismaProvider;
6
+ readonly driver: PrismaDriver;
7
+ readonly artifactFile: string;
8
+ };
9
+ /**
10
+ * The flags, parsed. Exported so a case can drive it without a child process:
11
+ * the parse is the only judgement this file makes, and the rest is the step's.
12
+ *
13
+ * `--flag value` only. `--flag=value` is deliberately not accepted, because
14
+ * accepting both spellings means two code paths over one fact for a binary whose
15
+ * entire surface is five required flags.
16
+ */
17
+ export declare function parsePrismaGenerateArguments(argv: readonly string[]): PrismaGenerateCliOptions;
18
+ /**
19
+ * Runs `aventara-prisma7-generate` over this process — its arguments, its
20
+ * output, its exit code. Called by the bin's entry (`discovery.bin.ts`) once the
21
+ * Node guard has admitted this Node; importing this module runs nothing, which
22
+ * is what lets the parse be tested without the process exiting.
23
+ */
24
+ export declare function runFromProcess(): Promise<void>;
@@ -0,0 +1,163 @@
1
+ import path from "node:path";
2
+ import { PRISMA_DRIVERS, PRISMA_PROVIDERS, } from "../provider/provider-profile.js";
3
+ import { generatePrismaDiscoveryArtifact } from "./index.js";
4
+ /**
5
+ * F-805 — `aventara-prisma7-generate`: the published binary that produces the
6
+ * discovery artifact `createPrismaAdapter({ discovery })` requires.
7
+ *
8
+ * Before this existed, `package.json` declared no `bin`, one `"."` export and
9
+ * `files: ["dist"]` over a build that excluded `src/generate/**` — so a consumer
10
+ * of the published package could not produce an artifact for their own schema at
11
+ * all, and the only artifacts in existence were the two this repository generates
12
+ * for its own tests. The package was unusable as published.
13
+ *
14
+ * # It reads nothing it was not told
15
+ *
16
+ * Every input is an argument. There is no repository path, no fixture schema, no
17
+ * convention-over-configuration search and no tsconfig of this package's: the
18
+ * types reader builds its own program over the client directory it is given. That
19
+ * is the whole reason this file is a flag parser and `discovery.generate.ts` — the
20
+ * repository's own two-engine script — is a separate file that happens to call the
21
+ * same function.
22
+ *
23
+ * `--provider` and `--driver` are required rather than sniffed. The provider is
24
+ * stamped into the artifact as a recorded fact, and the driver decides
25
+ * `AdapterModel.transactions`, which is a **driver** fact the schema cannot state:
26
+ * a `datasource` block names `postgresql`, not whether the application will drive
27
+ * it through `@prisma/adapter-pg`. Guessing either would put a guess in the
28
+ * artifact; D-804's rule is that the caller declares.
29
+ *
30
+ * # Failure is a message and an exit code, never a stack
31
+ *
32
+ * The step's refusals are deliberate, legible and aimed at the person running the
33
+ * build — a `prisma` major this reader has never seen, a generated tree whose
34
+ * runtime does not resolve, a schema construct the reader does not model. Printing
35
+ * a V8 stack trace over them would bury the sentence that says what to do. An
36
+ * unexpected error keeps its stack, because that one is a defect.
37
+ */
38
+ const USAGE = `aventara-prisma7-generate — emit a @aventara/prisma7-adapter discovery artifact.
39
+
40
+ Usage:
41
+ aventara-prisma7-generate --schema <path> --client <dir> --provider <name> \\
42
+ --driver <name> --out <file>
43
+
44
+ Options:
45
+ --schema <path> The schema.prisma file, or a Prisma 7 schema folder.
46
+ --client <dir> The generated client's root: your generator block's \`output\`.
47
+ --provider <name> ${PRISMA_PROVIDERS.join(" | ")}
48
+ --driver <name> ${PRISMA_DRIVERS.join(" | ")}
49
+ --out <file> Where to write the emitted artifact module (a .ts file).
50
+ -h, --help Print this and exit 0.
51
+
52
+ The artifact must NOT be written inside --client: the types reader reads every
53
+ .ts file under that root and refuses one that is not generated-client output.
54
+
55
+ Requires a Prisma 7 CLI and \`typescript\` to be resolvable from your project.
56
+ Both are optional peer dependencies of this package, so neither is installed on
57
+ your behalf and neither is reachable from the package's runtime entry. The CLI
58
+ is \`@prisma/prisma7\` (bin \`prisma7\`) when it is installed, else \`prisma\`
59
+ when its version is in this package's declared range; TypeScript 7, which has
60
+ no classic compiler API, is refused.`;
61
+ const PROVIDERS = PRISMA_PROVIDERS;
62
+ class PrismaGenerateCliError extends Error {
63
+ name = "PrismaGenerateCliError";
64
+ }
65
+ const FLAGS = {
66
+ "--schema": "schema",
67
+ "--client": "clientDirectory",
68
+ "--provider": "provider",
69
+ "--driver": "driver",
70
+ "--out": "artifactFile",
71
+ };
72
+ /**
73
+ * The flags, parsed. Exported so a case can drive it without a child process:
74
+ * the parse is the only judgement this file makes, and the rest is the step's.
75
+ *
76
+ * `--flag value` only. `--flag=value` is deliberately not accepted, because
77
+ * accepting both spellings means two code paths over one fact for a binary whose
78
+ * entire surface is five required flags.
79
+ */
80
+ export function parsePrismaGenerateArguments(argv) {
81
+ const seen = {};
82
+ for (let index = 0; index < argv.length; index += 1) {
83
+ const flag = argv[index];
84
+ const member = FLAGS[flag];
85
+ if (member === undefined) {
86
+ throw new PrismaGenerateCliError(`unknown argument \`${flag}\`.\n\n${USAGE}`);
87
+ }
88
+ const value = argv[index + 1];
89
+ if (value === undefined || value.startsWith("--")) {
90
+ throw new PrismaGenerateCliError(`\`${flag}\` takes a value.\n\n${USAGE}`);
91
+ }
92
+ seen[member] = value;
93
+ index += 1;
94
+ }
95
+ const missing = Object.values(FLAGS).filter((member) => seen[member] === undefined);
96
+ if (missing.length > 0) {
97
+ throw new PrismaGenerateCliError(`missing required argument(s): ${missing
98
+ .map((member) => Object.entries(FLAGS).find(([, name]) => name === member)?.[0] ??
99
+ member)
100
+ .join(", ")}.\n\n${USAGE}`);
101
+ }
102
+ const provider = seen.provider;
103
+ if (!PROVIDERS.includes(provider)) {
104
+ throw new PrismaGenerateCliError(`\`--provider ${seen.provider}\` is not one of ${PROVIDERS.join(", ")}.`);
105
+ }
106
+ const driver = seen.driver;
107
+ if (!PRISMA_DRIVERS.includes(driver)) {
108
+ throw new PrismaGenerateCliError(`\`--driver ${seen.driver}\` is not one of ${PRISMA_DRIVERS.join(", ")}.`);
109
+ }
110
+ // Resolved against the process's own directory, so every path in the output
111
+ // names one place and a relative `--out` lands where the caller meant.
112
+ return {
113
+ schema: path.resolve(seen.schema),
114
+ clientDirectory: path.resolve(seen.clientDirectory),
115
+ provider,
116
+ driver,
117
+ artifactFile: path.resolve(seen.artifactFile),
118
+ };
119
+ }
120
+ async function main(argv) {
121
+ if (argv.includes("-h") || argv.includes("--help")) {
122
+ process.stdout.write(`${USAGE}\n`);
123
+ return 0;
124
+ }
125
+ const options = parsePrismaGenerateArguments(argv);
126
+ const result = await generatePrismaDiscoveryArtifact(options);
127
+ process.stdout.write(`${result.artifactFile} — ${result.modelHash}\n`);
128
+ process.stdout.write(`resources: ${result.admitted.join(", ")}\n`);
129
+ for (const absence of result.excluded) {
130
+ process.stdout.write(`excluded: ${absence.message}\n`);
131
+ }
132
+ // Printed every run, because a cross-check whose exemptions are invisible is a
133
+ // cross-check nobody can audit.
134
+ for (const exemption of result.exemptions) {
135
+ process.stdout.write(`exempt (${exemption.kind}) ${exemption.entity}: ${exemption.reason}\n`);
136
+ }
137
+ return 0;
138
+ }
139
+ /**
140
+ * Runs `aventara-prisma7-generate` over this process — its arguments, its
141
+ * output, its exit code. Called by the bin's entry (`discovery.bin.ts`) once the
142
+ * Node guard has admitted this Node; importing this module runs nothing, which
143
+ * is what lets the parse be tested without the process exiting.
144
+ */
145
+ export async function runFromProcess() {
146
+ try {
147
+ process.exitCode = await main(process.argv.slice(2));
148
+ }
149
+ catch (error) {
150
+ // A refusal from the step or this parser is a sentence the person running the
151
+ // build has to read; a stack trace in front of it is noise. Anything else is
152
+ // a defect and keeps its stack.
153
+ const named = error instanceof PrismaGenerateCliError ||
154
+ (error instanceof Error &&
155
+ (error.name === "PrismaGenerateStepError" ||
156
+ error.name === "PrismaModelCompositionError" ||
157
+ error.name === "PrismaSchemaReaderError"));
158
+ process.stderr.write(named
159
+ ? `aventara-prisma7-generate: ${error.message}\n`
160
+ : `${String(error instanceof Error ? error.stack : error)}\n`);
161
+ process.exitCode = 1;
162
+ }
163
+ }
@@ -0,0 +1,73 @@
1
+ import type { PrismaCapabilitySurface } from "../discovery/capability-surface.interface.js";
2
+ import type { PrismaSchemaFacts, PrismaSchemaSite } from "../discovery/schema-facts.interface.js";
3
+ /**
4
+ * T-3.1 invariant 3 / C-808 — **existence is gated by TYPE; attributes come
5
+ * from PSL.**
6
+ *
7
+ * An entity enters the `AdapterModel` iff the 🟢 generated types expose it.
8
+ * The gate is strictly stronger than any attribute list: it catches `@ignore`,
9
+ * `@@ignore`, preview-feature exclusions, provider drops and `Unsupported(…)`
10
+ * columns with no special case for any of them, because all of them show up the
11
+ * same way — as a name PSL declares and the client does not carry
12
+ * (`Unsupported(…)` confirmed absent from `$Payload.scalars` and from
13
+ * `FieldRefs` at 7.10.0 **[RUN8]**).
14
+ *
15
+ * `@ignore` and `@@ignore` are parsed **anyway**, and that is this module's
16
+ * whole subtlety. They do not drive the gate — the types do. They exist so the
17
+ * gate's report can say *why* an entity is absent rather than emitting the
18
+ * useless "present in schema, absent from client", and so the two outcomes can
19
+ * be told apart:
20
+ *
21
+ * - **Explained** — PSL itself instructed the exclusion. Recorded, reported,
22
+ * and **not fatal**. `@ignore` is a documented Prisma instruction with a
23
+ * deliberate author behind it; stopping the build on it would leave the
24
+ * developer no remedy but to delete the instruction, and an error with no
25
+ * remedy is a defect rather than a fence.
26
+ * - **Unexplained** — nothing in the schema accounts for the absence.
27
+ * **Fatal.** Emitting the entity would hand the framework a Resource the
28
+ * adapter can never execute against; omitting it silently would ship a model
29
+ * that is quietly missing something the developer wrote.
30
+ *
31
+ * (Recorded conflict, for whoever reconciles the plan: Task 3's checklist reads
32
+ * "a PSL-only entity is a generate-time error" flatly, while C-808's own proof
33
+ * line reads "absent, with the exclusion explained". This module implements the
34
+ * split above, which is the only reading under which the stated *reason* for
35
+ * the error — "otherwise the adapter hands the framework a Resource it can
36
+ * never execute against" — survives: once the entity is excluded, no such
37
+ * Resource is handed over, and what remains to decide is only whether to stop
38
+ * the build. It is worth stopping for the absence nobody asked for.)
39
+ *
40
+ * The reverse direction — a resource the types expose and PSL does not declare
41
+ * — is a reader inconsistency rather than an existence question, and belongs to
42
+ * `reader-crosscheck.ts` (Task 6). This module does not answer it.
43
+ */
44
+ export type PrismaAbsenceKind = "model" | "field";
45
+ /** What PSL said, when PSL said anything. */
46
+ export type PrismaAbsenceExplanation = "@ignore" | "@@ignore";
47
+ export type PrismaEntityAbsence = {
48
+ readonly kind: PrismaAbsenceKind;
49
+ /** `Skipped`, or `Kept.hidden` — as a developer would name it. */
50
+ readonly entity: string;
51
+ readonly explanation: PrismaAbsenceExplanation | null;
52
+ readonly site: PrismaSchemaSite;
53
+ /** The sentence reported or thrown. Composed here so both paths read alike. */
54
+ readonly message: string;
55
+ };
56
+ export type PrismaExistenceGateResult = {
57
+ /** Resource names admitted into the `AdapterModel`, in PSL declaration order. */
58
+ readonly admitted: readonly string[];
59
+ /** Absences PSL itself explains. Reported; never fatal. */
60
+ readonly excluded: readonly PrismaEntityAbsence[];
61
+ };
62
+ export declare class PrismaExistenceGateError extends Error {
63
+ readonly name = "PrismaExistenceGateError";
64
+ readonly absences: readonly PrismaEntityAbsence[];
65
+ constructor(absences: readonly PrismaEntityAbsence[]);
66
+ }
67
+ /**
68
+ * Runs the gate. Returns what was admitted and what PSL explained away; throws
69
+ * {@link PrismaExistenceGateError} listing every unexplained absence at once,
70
+ * because fixing them one build at a time is how a schema migration becomes a
71
+ * week.
72
+ */
73
+ export declare function applyExistenceGate(facts: PrismaSchemaFacts, surface: PrismaCapabilitySurface): PrismaExistenceGateResult;
@@ -0,0 +1,91 @@
1
+ export class PrismaExistenceGateError extends Error {
2
+ name = "PrismaExistenceGateError";
3
+ absences;
4
+ constructor(absences) {
5
+ super([
6
+ "the generated client does not expose everything the schema declares, and the schema does not say why:",
7
+ ...absences.map((absence) => ` - ${absence.message}`),
8
+ ].join("\n"));
9
+ this.absences = absences;
10
+ }
11
+ }
12
+ function where(site) {
13
+ return `${site.file}:${site.line}`;
14
+ }
15
+ const CAUSES = "A provider drop, a preview-feature gate, or an `Unsupported(...)` column are the usual causes";
16
+ function surfaceSays(entity, kind) {
17
+ return kind === "model"
18
+ ? "the generated client exposes no such model"
19
+ : `the generated surface for \`${entity.split(".")[0]}\` does not expose it`;
20
+ }
21
+ function explainedMessage(entity, kind, explanation, site) {
22
+ return `\`${entity}\` is declared at ${where(site)} and ${surfaceSays(entity, kind)}, because the schema marks it \`${explanation}\`. It is excluded from the AdapterModel, which an entity enters only where the generated types expose it.`;
23
+ }
24
+ function unexplainedMessage(entity, kind, site) {
25
+ const instruction = kind === "model" ? "`@@ignore`" : "`@ignore`";
26
+ const cost = kind === "model"
27
+ ? "Admitting it would hand the framework a Resource the adapter can never execute against"
28
+ : "Admitting it would put a field in the Contract that no query, filter or write can ever name";
29
+ return `\`${entity}\` is declared at ${where(site)} but ${surfaceSays(entity, kind)}, and nothing in the schema explains the absence — there is no ${instruction} on it. ${cost}, and dropping it silently would ship a model quietly missing something the schema declares. ${CAUSES}.`;
30
+ }
31
+ /**
32
+ * Runs the gate. Returns what was admitted and what PSL explained away; throws
33
+ * {@link PrismaExistenceGateError} listing every unexplained absence at once,
34
+ * because fixing them one build at a time is how a schema migration becomes a
35
+ * week.
36
+ */
37
+ export function applyExistenceGate(facts, surface) {
38
+ const admitted = [];
39
+ const explained = [];
40
+ const unexplained = [];
41
+ for (const model of facts.models) {
42
+ const exposed = surface.resources[model.name];
43
+ if (exposed === undefined) {
44
+ const absence = model.ignored
45
+ ? {
46
+ kind: "model",
47
+ entity: model.name,
48
+ explanation: "@@ignore",
49
+ site: model.site,
50
+ message: explainedMessage(model.name, "model", "@@ignore", model.site),
51
+ }
52
+ : {
53
+ kind: "model",
54
+ entity: model.name,
55
+ explanation: null,
56
+ site: model.site,
57
+ message: unexplainedMessage(model.name, "model", model.site),
58
+ };
59
+ (absence.explanation === null ? unexplained : explained).push(absence);
60
+ continue;
61
+ }
62
+ admitted.push(model.name);
63
+ for (const field of model.fields) {
64
+ if (field.name in exposed.scalarFields ||
65
+ field.name in exposed.relationFields) {
66
+ continue;
67
+ }
68
+ const entity = `${model.name}.${field.name}`;
69
+ const absence = field.ignored
70
+ ? {
71
+ kind: "field",
72
+ entity,
73
+ explanation: "@ignore",
74
+ site: field.site,
75
+ message: explainedMessage(entity, "field", "@ignore", field.site),
76
+ }
77
+ : {
78
+ kind: "field",
79
+ entity,
80
+ explanation: null,
81
+ site: field.site,
82
+ message: unexplainedMessage(entity, "field", field.site),
83
+ };
84
+ (absence.explanation === null ? unexplained : explained).push(absence);
85
+ }
86
+ }
87
+ if (unexplained.length > 0) {
88
+ throw new PrismaExistenceGateError(unexplained);
89
+ }
90
+ return { admitted, excluded: explained };
91
+ }
@@ -0,0 +1,81 @@
1
+ import type { PrismaDriver, PrismaProvider } from "../provider/provider-profile.js";
2
+ import type { PrismaGenerateResult, PrismaGenerateToolchain } from "./prisma.generate.js";
3
+ /**
4
+ * F-805 — the package's **second published entry**: `@aventara/prisma7-adapter/generate`.
5
+ *
6
+ * # Why a second entry rather than a second package or a wider `"."`
7
+ *
8
+ * The runtime entry (`"."`) depends on `@aventara/core` and the caller's own
9
+ * generated client, and on nothing else. The generate path needs `typescript`
10
+ * (the types reader builds a real TypeScript program over the generated tree) and
11
+ * `prisma` (the step runs `prisma generate` itself, so that both sources describe
12
+ * one instant). Those two are declared as **optional peer dependencies**, not as
13
+ * dependencies, and the split is what keeps the plan's guarantee that *no
14
+ * compiler, no parser and no Prisma tooling enters the deployed runtime*: a
15
+ * consumer who imports only `"."` never loads a module that names either, and an
16
+ * optional peer is not installed on their behalf.
17
+ *
18
+ * The published build therefore carries both halves and the `exports` map is what
19
+ * separates them. Nothing reachable from `"."` imports anything under
20
+ * `generate/`; `artifact.loader.spec.ts` asserts that from the import graph
21
+ * rather than from this paragraph.
22
+ *
23
+ * # What this entry is for
24
+ *
25
+ * Producing the discovery artifact `createPrismaAdapter({ discovery })` requires,
26
+ * from the consumer's own `schema.prisma` and their own generated client. The
27
+ * `aventara-prisma7-generate` binary (`discovery.cli.ts`) is this function with an
28
+ * argument parser in front of it; this module is the same thing for a consumer who
29
+ * already has a build script and would rather call it.
30
+ *
31
+ * Everything the step itself exposes is re-exported, because the seams are the
32
+ * point: a consumer with an unusual toolchain can supply their own
33
+ * {@link PrismaGenerateToolchain}, and one with their own composition rules can
34
+ * call {@link runPrismaGenerateStep} directly.
35
+ */
36
+ export { createPrismaModelComposer, PrismaModelCompositionError, } from "../discovery/model.discoverer.js";
37
+ export type { PrismaEntityAbsence } from "./existence.gate.js";
38
+ export type { PrismaComposedArtifact, PrismaGenerateRequest, PrismaGenerateResult, PrismaGenerateToolchain, PrismaModelComposer, } from "./prisma.generate.js";
39
+ export { createPrismaGenerateToolchain, PINNED_PRISMA_MAJOR, PrismaGenerateStepError, runPrismaGenerateStep, } from "./prisma.generate.js";
40
+ export type { PrismaReaderExemption } from "./reader-crosscheck.js";
41
+ /**
42
+ * One engine's generate request, as a consumer states it.
43
+ *
44
+ * `driver` is here and is not on {@link PrismaGenerateRequest} for a reason the
45
+ * step's own header gives: `AdapterModel.transactions` is a **driver** fact (T-2)
46
+ * that is stamped into the artifact at generate time, while the step itself is
47
+ * forbidden to know a provider or a driver at all — composition is created with a
48
+ * transaction capability and nothing else, so `if (provider === …)` inside
49
+ * discovery is a compile error rather than a convention. This function is the one
50
+ * place the two meet: it resolves the profile and hands composition the single
51
+ * fact it accepts.
52
+ *
53
+ * The declared driver must be the driver the application will construct the
54
+ * adapter with. The two cannot drift silently: an artifact that says
55
+ * `"interactive"` loaded by an adapter whose driver says `"none"` mounts no
56
+ * `transaction`, and core refuses construction with
57
+ * `DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH`.
58
+ */
59
+ export type PrismaDiscoveryGenerateRequest = {
60
+ /** The `schema.prisma` file, or a Prisma 7 schema folder. */
61
+ readonly schema: string;
62
+ /** The generator's `output` directory: the generated client's root. */
63
+ readonly clientDirectory: string;
64
+ readonly provider: PrismaProvider;
65
+ /** The driver the application will pass to `createPrismaAdapter`. */
66
+ readonly driver: PrismaDriver;
67
+ /** Where the emitted TypeScript artifact module is written. */
68
+ readonly artifactFile: string;
69
+ /** Injectable only so a caller can replace the step's external effects. */
70
+ readonly toolchain?: PrismaGenerateToolchain;
71
+ };
72
+ /**
73
+ * The generate step, wired: provider profile → transaction capability →
74
+ * composition → the step.
75
+ *
76
+ * Every decision stays where it already lived. This function resolves the profile
77
+ * and builds the composer; it makes no judgement of its own, which is why there is
78
+ * nothing here to test beyond that it is the wiring the CLI and the repository's
79
+ * own `generate` script both use.
80
+ */
81
+ export declare function generatePrismaDiscoveryArtifact(request: PrismaDiscoveryGenerateRequest): Promise<PrismaGenerateResult>;