@aventara/prisma7-adapter 1.0.0-rc.2 → 1.0.0-rc.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @aventara/prisma7-adapter
2
2
 
3
+ ## 1.0.0-rc.3
4
+
5
+ ### Patch Changes
6
+
7
+ - b38034d: `aventara-prisma7-generate` refuses a `--provider` that the schema's `datasource` block contradicts, or a block naming
8
+ a provider the adapter does not support, before `prisma generate` runs, so neither the Prisma client nor the artifact
9
+ is written:
10
+
11
+ ```text
12
+ aventara-prisma7-generate: --provider postgresql contradicts the schema's datasource `db` (prisma/schema.prisma:12), which names provider "sqlite"; pass --provider sqlite.
13
+ ```
14
+
15
+ PSL's `postgres` spelling counts as `postgresql`, and a provider that is not a string literal is not compared.
16
+ Before, the flag was taken on trust, and a wrong one wrote the wrong provider into the artifact and the wrong filter
17
+ modes into the Contract.
18
+
19
+ When the file system refuses the artifact file (`--out`), the step says so in one sentence naming the file and why,
20
+ such as
21
+ `the artifact file /app/src/generated/aventara/discovery.artifact.ts (--out) cannot be written: permission denied (EACCES).`,
22
+ and leaves no staged file behind. Before, every such failure except a directory printed a stack trace.
23
+
24
+ - b38034d: On PostgreSQL, a `@unique` value too long for its index (an index row past the B-tree's 8191 bytes, SQLSTATE
25
+ `54000`) is answered `422 A2004`, not `500 A3001`: one `V1004` issue at `["arguments"]`, as a value too long for its
26
+ column already was. The adapter reads the SQLSTATE, never the message. The issue's message for a value too long now
27
+ reads `The database refused this value: it is longer than the database can hold for its column.`
28
+ (it said `longer than its column can hold`, which an index refusal is not).
29
+ - Updated dependencies [b38034d]
30
+ - Updated dependencies [b38034d]
31
+ - Updated dependencies [b38034d]
32
+ - Updated dependencies [b38034d]
33
+ - Updated dependencies [b38034d]
34
+ - Updated dependencies [b38034d]
35
+ - @aventara/core@1.0.0-rc.3
36
+
3
37
  ## 1.0.0-rc.2
4
38
 
5
39
  ### Patch Changes
package/README.md CHANGED
@@ -86,8 +86,9 @@ npx aventara-prisma7-generate \
86
86
  --config src/aventara.config.ts
87
87
  ```
88
88
 
89
- `--provider` and `--driver` are declared, never sniffed. The provider is recorded in the artifact. The
90
- driver decides `AvAdapterModel.transactions`, a driver fact the schema cannot state. Declare the driver
89
+ `--provider` and `--driver` are declared, never sniffed. The provider is recorded in the artifact, and the
90
+ step refuses a `--provider` that the schema's `datasource` block contradicts (`provider = "sqlite"` with
91
+ `--provider postgresql`), naming the flag to pass. The driver decides `AvAdapterModel.transactions`, a driver fact the schema cannot state. Declare the driver
91
92
  the application will actually construct the adapter with. The two cannot drift silently, but they can
92
93
  disagree, and core refuses construction when they do.
93
94
 
@@ -26,8 +26,16 @@ export type PrismaSchemaUnknownKind =
26
26
  | "block"
27
27
  /** A line the reader could not classify at all. */
28
28
  | "line";
29
+ /** A `datasource` block: the provider it names. */
30
+ export type PrismaSchemaDatasource = {
31
+ readonly name: string;
32
+ readonly provider: string | null;
33
+ readonly site: PrismaSchemaSite;
34
+ };
29
35
  export type PrismaSchemaFacts = {
30
36
  readonly enums: readonly PrismaSchemaEnum[];
37
+ /** Every `datasource` block, in reading order (Prisma admits one). */
38
+ readonly datasources: readonly PrismaSchemaDatasource[];
31
39
  readonly models: readonly PrismaSchemaModel[];
32
40
  /**
33
41
  * Attributes and blocks the reader does not recognise at all (a `type`/`view`
@@ -63,12 +63,15 @@ export function readPrismaSchemaSources(sources) {
63
63
  const enums = [];
64
64
  const models = [];
65
65
  const unhandled = [];
66
+ const datasources = [];
66
67
  for (const source of sources) {
67
- readOneSource(source, enums, models, unhandled);
68
+ readOneSource(source, enums, models, unhandled, datasources);
68
69
  }
69
- return { enums, models, unhandled };
70
+ return { enums, datasources, models, unhandled };
70
71
  }
71
- function readOneSource(source, enums, models, unhandled) {
72
+ const DATASOURCE_PROVIDER = /^provider\s*=\s*(.*)$/;
73
+ const STRING_LITERAL = /^"([^"\\]*)"$/;
74
+ function readOneSource(source, enums, models, unhandled, datasources) {
72
75
  let open = null;
73
76
  const lines = source.text.split("\n");
74
77
  const unreadableActions = new Set();
@@ -105,12 +108,26 @@ function readOneSource(source, enums, models, unhandled) {
105
108
  site: open.site,
106
109
  });
107
110
  }
111
+ else if (open.kind === "datasource") {
112
+ datasources.push({
113
+ name: open.name,
114
+ provider: open.provider,
115
+ site: open.site,
116
+ });
117
+ }
108
118
  open = null;
109
119
  continue;
110
120
  }
111
121
  if (open.kind === "skip") {
112
122
  continue;
113
123
  }
124
+ if (open.kind === "datasource") {
125
+ const assigned = DATASOURCE_PROVIDER.exec(line)?.[1]?.trim();
126
+ if (assigned !== undefined) {
127
+ open.provider = STRING_LITERAL.exec(assigned)?.[1] ?? null;
128
+ }
129
+ continue;
130
+ }
114
131
  if (open.kind === "enum") {
115
132
  if (line.startsWith("@@")) {
116
133
  for (const attribute of splitAttributes(line)) {
@@ -175,6 +192,9 @@ function openBlock(line, site, record) {
175
192
  if (keyword === "enum") {
176
193
  return { kind: "enum", name, site, values: [] };
177
194
  }
195
+ if (keyword === "datasource") {
196
+ return { kind: "datasource", name, site, provider: null };
197
+ }
178
198
  if (keyword === "type" || keyword === "view") {
179
199
  record("block", keyword, line);
180
200
  }
@@ -29,6 +29,7 @@ export const PRISMA_DATABASE_VALUE_TABLE = {
29
29
  "22025": "trailingEscape",
30
30
  "22009": "datetimeOutOfRange",
31
31
  "22012": "divisionByZero",
32
+ "54000": "tooLong",
32
33
  };
33
34
  function refusedValue(facts) {
34
35
  if (facts.code === undefined) {
@@ -5,7 +5,7 @@ import { promisify } from "node:util";
5
5
  import ts from "typescript";
6
6
  import { computeModelHash } from "../discovery/artifact.loader.js";
7
7
  import { DISCOVERY_ARTIFACT_FORMAT } from "../discovery/discovery-artifact.interface.js";
8
- import { readPrismaSchema } from "../discovery/schema.reader.js";
8
+ import { AvPrismaSchemaReaderError, readPrismaSchema, } from "../discovery/schema.reader.js";
9
9
  import { readPrismaCapabilitySurface } from "../discovery/types.reader.js";
10
10
  import { emitDiscoveryArtifactModule } from "./artifact.emitter.js";
11
11
  import { readConfigComputedFields, } from "./computed-fields.reader.js";
@@ -76,16 +76,38 @@ async function writeArtifactAtomically(file, source) {
76
76
  if (current === source) {
77
77
  return;
78
78
  }
79
- await mkdir(path.dirname(file), { recursive: true });
80
79
  const staged = `${file}.${process.pid}.${Date.now()}.tmp`;
81
80
  try {
81
+ await mkdir(path.dirname(file), { recursive: true });
82
82
  await writeFile(staged, source, "utf8");
83
83
  await rename(staged, file);
84
84
  }
85
85
  catch (error) {
86
- await rm(staged, { force: true });
87
- throw error;
86
+ await rm(staged, { force: true }).catch(() => undefined);
87
+ throw unwritableArtifact(file, error);
88
+ }
89
+ }
90
+ const UNWRITABLE_BECAUSE = {
91
+ EACCES: "permission denied",
92
+ EPERM: "permission denied",
93
+ ENOTDIR: "a part of its path is a file, not a directory",
94
+ EEXIST: "a part of its path is a file, not a directory",
95
+ ENOSPC: "no space is left on the device",
96
+ EROFS: "the file system is read-only",
97
+ EISDIR: "it is a directory",
98
+ ENAMETOOLONG: "its path is too long",
99
+ };
100
+ function unwritableArtifact(file, error) {
101
+ const code = error instanceof Error && "code" in error && typeof error.code === "string"
102
+ ? error.code
103
+ : undefined;
104
+ if (code === undefined) {
105
+ return error;
88
106
  }
107
+ const because = Object.hasOwn(UNWRITABLE_BECAUSE, code)
108
+ ? `${UNWRITABLE_BECAUSE[code]} (${code})`
109
+ : code;
110
+ return new AvPrismaGenerateStepError(`the artifact file ${path.resolve(file)} (--out) cannot be written: ${because}.`);
89
111
  }
90
112
  async function describesModel(file, modelHash) {
91
113
  const current = await readFile(file, "utf8").catch(() => undefined);
@@ -126,6 +148,28 @@ async function assertArtifactPlacement(artifactFile, clientDirectory) {
126
148
  throw new AvPrismaGenerateStepError(`the artifact file ${file} (--out) is a directory; name the .ts file to write, such as ${path.join(file, "discovery.artifact.ts")}.`);
127
149
  }
128
150
  }
151
+ const PSL_PROVIDERS = {
152
+ sqlite: "sqlite",
153
+ postgresql: "postgresql",
154
+ postgres: "postgresql",
155
+ };
156
+ function assertDatasourceProvider(facts, provider) {
157
+ for (const datasource of facts.datasources) {
158
+ if (datasource.provider === null) {
159
+ continue;
160
+ }
161
+ const named = Object.hasOwn(PSL_PROVIDERS, datasource.provider)
162
+ ? PSL_PROVIDERS[datasource.provider]
163
+ : undefined;
164
+ if (named === provider) {
165
+ continue;
166
+ }
167
+ const where = `${datasource.site.file}:${datasource.site.line}`;
168
+ throw new AvPrismaGenerateStepError(named === undefined
169
+ ? `the schema's datasource \`${datasource.name}\` (${where}) names provider "${datasource.provider}", which this adapter does not support (${Object.keys(PSL_PROVIDERS).join(", ")}).`
170
+ : `--provider ${provider} contradicts the schema's datasource \`${datasource.name}\` (${where}), which names provider "${datasource.provider}"; pass --provider ${named}.`);
171
+ }
172
+ }
129
173
  function assertNothingUnhandled(facts) {
130
174
  if (facts.unhandled.length === 0) {
131
175
  return;
@@ -141,6 +185,17 @@ export async function runPrismaGenerateStep(request) {
141
185
  const prismaVersion = await toolchain.installedPrismaVersion();
142
186
  assertPinnedMajor(prismaVersion);
143
187
  assertClientRuntimeResolves(toolchain, request.clientDirectory);
188
+ const declared = await toolchain
189
+ .readSchema(request.schema)
190
+ .catch((error) => {
191
+ if (error instanceof AvPrismaSchemaReaderError) {
192
+ return undefined;
193
+ }
194
+ throw error;
195
+ });
196
+ if (declared !== undefined) {
197
+ assertDatasourceProvider(declared, request.provider);
198
+ }
144
199
  await toolchain.runPrismaGenerate(request.schema);
145
200
  const facts = await toolchain.readSchema(request.schema);
146
201
  assertNothingUnhandled(facts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aventara/prisma7-adapter",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.3",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "The Prisma 7 adapter for Aventara.",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "CHANGELOG.md"
31
31
  ],
32
32
  "dependencies": {
33
- "@aventara/core": "1.0.0-rc.2"
33
+ "@aventara/core": "1.0.0-rc.3"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@prisma/adapter-better-sqlite3": "^7.10.0",