@aventara/prisma7-adapter 1.0.0-rc.2 → 1.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -0
- package/README.md +40 -12
- package/dist/discovery/discovery-artifact.interface.d.ts +7 -5
- package/dist/discovery/schema-facts.interface.d.ts +8 -0
- package/dist/discovery/schema.reader.js +23 -3
- package/dist/errors/error.normalizer.js +1 -0
- package/dist/generate/artifact.emitter.d.ts +8 -4
- package/dist/generate/artifact.emitter.js +9 -9
- package/dist/generate/computed-fields.import.d.ts +5 -0
- package/dist/generate/computed-fields.import.js +52 -0
- package/dist/generate/discovery.cli.js +7 -11
- package/dist/generate/index.d.ts +3 -5
- package/dist/generate/index.js +0 -1
- package/dist/generate/prisma.generate.d.ts +5 -13
- package/dist/generate/prisma.generate.js +67 -21
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/prisma-adapter.description.d.ts +42 -0
- package/dist/prisma-adapter.description.js +36 -0
- package/dist/prisma.adapter.js +10 -30
- package/dist/translation/operation.translator.js +22 -5
- package/package.json +2 -2
- package/dist/generate/computed-fields.reader.d.ts +0 -19
- package/dist/generate/computed-fields.reader.js +0 -210
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,131 @@
|
|
|
1
1
|
# @aventara/prisma7-adapter
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7c3b65f: Build-time Contracts for serverless and edge cold starts.
|
|
8
|
+
|
|
9
|
+
- New package `@aventara/build`, bin `aventara-build`, a development dependency. It runs `src/aventara.config.ts` and
|
|
10
|
+
`src/aventara.adapter.ts` without a database client, compiles the Contracts with the project's `@aventara/core` and
|
|
11
|
+
writes `src/generated/aventara/contracts.generated.ts` (`aventaraContracts`), which a serverless or edge entry passes
|
|
12
|
+
to `createFramework` as `prebuilt`. A configuration that startup would refuse fails the build with the same
|
|
13
|
+
`CONFIG_…`/`COMPILER_…` diagnostics. `aventara-build --check` writes nothing and exits 1 when the file is missing or
|
|
14
|
+
stale, naming the Resources that changed.
|
|
15
|
+
- `@aventara/prisma7-adapter`: `describePrismaAdapter({ discovery, provider, driver })` states the adapter's passive
|
|
16
|
+
facts (the model, the driver's transaction capability, the referential actions, the computed fields) with no client.
|
|
17
|
+
`createPrismaAdapter({ ...description, client })` builds the adapter from the same facts, and still checks the client
|
|
18
|
+
against the discovery artifact.
|
|
19
|
+
- `@aventara/cli`: the scaffold's `src/aventara.config.ts` exports the configuration as a plain value
|
|
20
|
+
(`export const aventaraConfig = { … } as const`) with no `adapter`. A new `src/aventara.adapter.ts` default-exports
|
|
21
|
+
`describePrismaAdapter(…)`, and `app.module.ts` joins the two:
|
|
22
|
+
`config: { ...aventaraConfig, adapter: await createPrismaAdapter({ ...adapter, client: prisma }) }`. The Nest server
|
|
23
|
+
still compiles its Contracts at startup and passes no `prebuilt`. `aventara:prepare` ends with `&& aventara-build`,
|
|
24
|
+
and `@aventara/build` is an exact development dependency.
|
|
25
|
+
|
|
26
|
+
- 2b1b17e: `aventara-build`'s files no longer land inside a Prisma client generated into `src/generated`.
|
|
27
|
+
|
|
28
|
+
When `aventara init` reuses a project whose Prisma client is generated into `src/generated`, it already puts the
|
|
29
|
+
discovery artifact in `src/aventara/`, because `prisma generate` deletes every file in its output directory that it did
|
|
30
|
+
not write. `aventara-build` still wrote its three files to `src/generated/aventara/`, so each `prisma generate` deleted
|
|
31
|
+
them and the project stopped compiling until the next `aventara:prepare`. Now all of Aventara's generated files are
|
|
32
|
+
in one place.
|
|
33
|
+
|
|
34
|
+
- `@aventara/build`: `aventara-build` writes `resources.generated.ts`, `computed-fields.generated.ts` and
|
|
35
|
+
`contracts.generated.ts` to the directory the configuration imports `aventaraResources` from, so the tree is always
|
|
36
|
+
written where the configuration reads it. A configuration that imports no tree keeps `src/generated/aventara/`.
|
|
37
|
+
The files of a project whose client is generated anywhere else are the same bytes, in the same place.
|
|
38
|
+
- `@aventara/prisma7-adapter`: with `--config`, the artifact imports the computed fields from its sibling,
|
|
39
|
+
`./computed-fields.generated`, wherever `--out` puts it. `--config` is still required for that import and the step
|
|
40
|
+
still checks that the file exists, but the step no longer uses its location. A scaffold's artifact is unchanged.
|
|
41
|
+
- `@aventara/cli`: in that project, `src/aventara.config.ts` imports the tree from `./aventara/resources.generated`,
|
|
42
|
+
and `.gitignore` gets the one `/src/aventara/` line that covers all four files (no separate
|
|
43
|
+
`/src/generated/aventara/contracts.generated.ts` line). The agent skill says where the generated files are in
|
|
44
|
+
both cases.
|
|
45
|
+
|
|
46
|
+
- 3a0dfca: A write the policy checks costs fewer database round trips. What each check decides and refuses is unchanged.
|
|
47
|
+
|
|
48
|
+
- A single-row update or delete carries the caller's scopes inside its own `where` and runs as one statement, with no
|
|
49
|
+
read of its row before it. One that matches nothing still answers `A4002` when the caller can read the row and
|
|
50
|
+
`A2003` when it cannot, with one lookup on that miss only. The Prisma adapter sends it as
|
|
51
|
+
`update({ where: { id, AND: [scope] } })`.
|
|
52
|
+
- A create asks `can({ action: "create", row })` and its `exists` lookups before the insert, without a transaction. A
|
|
53
|
+
transaction around them never closed the race between the lookup and the insert at the database's default
|
|
54
|
+
isolation, and cost a BEGIN and a COMMIT.
|
|
55
|
+
- A transaction opens only when a check after the write may roll it back: an update's after check or move-out, a
|
|
56
|
+
created row held to a write grant, a nested write, or returned rows checked by `exists`. A plain checked create or
|
|
57
|
+
delete is one statement, its interceptors and hooks outside any transaction.
|
|
58
|
+
- The after check asks `can` about the row the update returned, so it reads nothing again. Only a move-out reads the
|
|
59
|
+
changed rows again. An `update.count` runs as the returning `update.many` when the Contract offers it.
|
|
60
|
+
|
|
61
|
+
Round trips per request on PostgreSQL: `create.one` 3 → 1, `delete.unique` 4 → 1, `update.unique` 5 → 3,
|
|
62
|
+
`update.many` 4 → 3, `update.count` 5 → 3. Reads, and a Framework without a policy, are unchanged.
|
|
63
|
+
|
|
64
|
+
- 3813207: The Resource tree and the computed fields come from `aventara-build`.
|
|
65
|
+
|
|
66
|
+
- `@aventara/build`: before it compiles the Contracts, `aventara-build` writes two more files under
|
|
67
|
+
`src/generated/aventara/`. `resources.generated.ts` exports `aventaraResources`: every `defineResource(…)` result
|
|
68
|
+
that the files `src/resources/**/*.resource.ts` export, found by its type (never run), one `resources` tree per
|
|
69
|
+
layer, written as the plain spreads you wrote by hand (`root` always, `application`, `client` and `agent` when some
|
|
70
|
+
Resource has a block for them). Two definitions of one Resource stop the build, naming both. Imports are spelled as
|
|
71
|
+
the project spells its own. `computed-fields.generated.ts` exports `computedFields` and its type `AvComputedFields`:
|
|
72
|
+
which fields the configuration's write compute fills, read off its type after the tree exists, so a fresh install is
|
|
73
|
+
typed right on its first `aventara:prepare`. `--check` covers all three files. `typescript` (`>=5.7.0 <7`) is now a
|
|
74
|
+
peer dependency.
|
|
75
|
+
- `@aventara/prisma7-adapter`: the generate step no longer reads the configuration. With `--config`, the discovery
|
|
76
|
+
artifact imports `computedFields` and `AvComputedFields` from the file `aventara-build` writes beside the
|
|
77
|
+
configuration (`generated/aventara/computed-fields.generated.ts`); a `--config` file that does not exist is still
|
|
78
|
+
refused. `readConfigComputedFields`, `AvPrismaConfigReaderError` and `ConfigComputedFieldsReading` are removed from
|
|
79
|
+
`@aventara/prisma7-adapter/generate`, as are `PrismaGenerateToolchain.readComputedFields` and
|
|
80
|
+
`PrismaGenerateResult.computedFields`.
|
|
81
|
+
- `@aventara/cli`: the scaffold's `src/aventara.config.ts` takes its Resources from the tree,
|
|
82
|
+
`resources: aventaraResources.root`, so a new Resource file needs no configuration edit; the agent skill says so.
|
|
83
|
+
|
|
84
|
+
- Updated dependencies [e3d03a7]
|
|
85
|
+
- Updated dependencies [d10639a]
|
|
86
|
+
- Updated dependencies [f67e68c]
|
|
87
|
+
- Updated dependencies [708556c]
|
|
88
|
+
- Updated dependencies [3a0dfca]
|
|
89
|
+
- Updated dependencies [99169d2]
|
|
90
|
+
- Updated dependencies [0fd2608]
|
|
91
|
+
- Updated dependencies [154e440]
|
|
92
|
+
- Updated dependencies [2fcffdb]
|
|
93
|
+
- @aventara/core@1.0.0-rc.4
|
|
94
|
+
|
|
95
|
+
## 1.0.0-rc.3
|
|
96
|
+
|
|
97
|
+
### Patch Changes
|
|
98
|
+
|
|
99
|
+
- b38034d: `aventara-prisma7-generate` refuses a `--provider` that the schema's `datasource` block contradicts, or a block naming
|
|
100
|
+
a provider the adapter does not support, before `prisma generate` runs, so neither the Prisma client nor the artifact
|
|
101
|
+
is written:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
aventara-prisma7-generate: --provider postgresql contradicts the schema's datasource `db` (prisma/schema.prisma:12), which names provider "sqlite"; pass --provider sqlite.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
PSL's `postgres` spelling counts as `postgresql`, and a provider that is not a string literal is not compared.
|
|
108
|
+
Before, the flag was taken on trust, and a wrong one wrote the wrong provider into the artifact and the wrong filter
|
|
109
|
+
modes into the Contract.
|
|
110
|
+
|
|
111
|
+
When the file system refuses the artifact file (`--out`), the step says so in one sentence naming the file and why,
|
|
112
|
+
such as
|
|
113
|
+
`the artifact file /app/src/generated/aventara/discovery.artifact.ts (--out) cannot be written: permission denied (EACCES).`,
|
|
114
|
+
and leaves no staged file behind. Before, every such failure except a directory printed a stack trace.
|
|
115
|
+
|
|
116
|
+
- b38034d: On PostgreSQL, a `@unique` value too long for its index (an index row past the B-tree's limit of about 2704 bytes, SQLSTATE
|
|
117
|
+
`54000`) is answered `422 A2004`, not `500 A3001`: one `V1004` issue at `["arguments"]`, as a value too long for its
|
|
118
|
+
column already was. The adapter reads the SQLSTATE, never the message. The issue's message for a value too long now
|
|
119
|
+
reads `The database refused this value: it is longer than the database can hold for its column.`
|
|
120
|
+
(it said `longer than its column can hold`, which an index refusal is not).
|
|
121
|
+
- Updated dependencies [b38034d]
|
|
122
|
+
- Updated dependencies [b38034d]
|
|
123
|
+
- Updated dependencies [b38034d]
|
|
124
|
+
- Updated dependencies [b38034d]
|
|
125
|
+
- Updated dependencies [b38034d]
|
|
126
|
+
- Updated dependencies [b38034d]
|
|
127
|
+
- @aventara/core@1.0.0-rc.3
|
|
128
|
+
|
|
3
129
|
## 1.0.0-rc.2
|
|
4
130
|
|
|
5
131
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -55,6 +55,31 @@ than a declaration that is checked.
|
|
|
55
55
|
`createPrismaAdapter` is `async`. Construction verifies the discovery artifact, and the digest it
|
|
56
56
|
checks is a `crypto.subtle` promise, so every check runs once, here, before an adapter exists.
|
|
57
57
|
|
|
58
|
+
### `describePrismaAdapter`: the same facts, with no client
|
|
59
|
+
|
|
60
|
+
`describePrismaAdapter({ discovery, provider, driver })` returns the adapter's passive facts: the model,
|
|
61
|
+
the driver's transaction capability, the referential actions in Aventara's words and the computed
|
|
62
|
+
fields the generated types were written with. It constructs nothing and connects to nothing, so
|
|
63
|
+
`aventara-build` (`@aventara/build`) compiles the Contracts from it at build time. Spread into
|
|
64
|
+
`createPrismaAdapter` with the client, it is the adapter's options, and the adapter states exactly the
|
|
65
|
+
facts the description does:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// src/aventara.adapter.ts
|
|
69
|
+
export default describePrismaAdapter({
|
|
70
|
+
discovery,
|
|
71
|
+
provider: "postgresql",
|
|
72
|
+
driver: "@prisma/adapter-pg",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// where the framework is created
|
|
76
|
+
import description from "./aventara.adapter.js";
|
|
77
|
+
const adapter = await createPrismaAdapter({ ...description, client });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`createPrismaAdapter` still verifies the artifact and checks the client against it; the description
|
|
81
|
+
does neither, because it has no client to check.
|
|
82
|
+
|
|
58
83
|
### `maxWait`, `timeout` and `isolationLevel` are adapter-owned
|
|
59
84
|
|
|
60
85
|
They are provider concepts. Core's `Adapter.transaction(work)` interface carries no options and will
|
|
@@ -86,8 +111,9 @@ npx aventara-prisma7-generate \
|
|
|
86
111
|
--config src/aventara.config.ts
|
|
87
112
|
```
|
|
88
113
|
|
|
89
|
-
`--provider` and `--driver` are declared, never sniffed. The provider is recorded in the artifact
|
|
90
|
-
|
|
114
|
+
`--provider` and `--driver` are declared, never sniffed. The provider is recorded in the artifact, and the
|
|
115
|
+
step refuses a `--provider` that the schema's `datasource` block contradicts (`provider = "sqlite"` with
|
|
116
|
+
`--provider postgresql`), naming the flag to pass. The driver decides `AvAdapterModel.transactions`, a driver fact the schema cannot state. Declare the driver
|
|
91
117
|
the application will actually construct the adapter with. The two cannot drift silently, but they can
|
|
92
118
|
disagree, and core refuses construction when they do.
|
|
93
119
|
|
|
@@ -98,16 +124,18 @@ installed keeps `@prisma/prisma7` beside it. With neither usable, the step refus
|
|
|
98
124
|
both. TypeScript 6 is supported; TypeScript 7, which has no classic compiler API, is refused in one
|
|
99
125
|
sentence.
|
|
100
126
|
|
|
101
|
-
`--config` is optional. It names your Aventara configuration module
|
|
102
|
-
|
|
103
|
-
configuration's TypeScript type
|
|
104
|
-
`compute.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
127
|
+
`--config` is optional. It names your Aventara configuration module (`src/aventara.config.ts`). The step
|
|
128
|
+
neither reads nor runs it: `aventara-build` (`@aventara/build`), which `aventara:prepare` runs right after
|
|
129
|
+
this step, reads the configuration's TypeScript type and writes which fields its `compute.create` and
|
|
130
|
+
`compute.update` stages fill, per layer, to `computed-fields.generated.ts` beside the artifact (the
|
|
131
|
+
directory the configuration imports its Resource tree from). The artifact imports that sibling file: `computedFields` (also `discovery.computedFields`) and its type, which the
|
|
132
|
+
model's type carries. A variant's guards, pipes and filters, which run before write compute, may then
|
|
133
|
+
leave exactly those fields out of a create; every other required field stays required. On a fresh install
|
|
134
|
+
the file is not there until `aventara-build` has run. Without `--config`, or when the configuration is not
|
|
135
|
+
a literal where a compute stage is written (`aventara-build` prints where), the artifact states nothing and
|
|
136
|
+
those stages may leave any create field out. When the framework starts, it compares the statement with
|
|
137
|
+
the configuration and warns (`AVENTARA_STALE_GENERATED_TYPES`) if you changed a compute stage without
|
|
138
|
+
running `aventara:prepare` again.
|
|
111
139
|
|
|
112
140
|
`--out` must not be inside `--client`: Prisma's next `prisma generate` clears that directory and would
|
|
113
141
|
delete the artifact. The step refuses such an `--out`, and one that names a directory, in one sentence
|
|
@@ -107,11 +107,13 @@ export type AvPrismaDiscoveryArtifact<M extends AvPrismaAdapterModel = AvPrismaA
|
|
|
107
107
|
*/
|
|
108
108
|
readonly client: PrismaClientShape;
|
|
109
109
|
/**
|
|
110
|
-
* Which fields the configuration's write compute fills, per layer, as
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
110
|
+
* Which fields the configuration's write compute fills, per layer, as
|
|
111
|
+
* `aventara-build` read them off the configuration's type (the artifact
|
|
112
|
+
* imports them when the step was given `--config`); `null` when it was
|
|
113
|
+
* given none, or the build could not read them. The model's type carries
|
|
114
|
+
* the same statement for the pipeline stages' types; this value is compared
|
|
115
|
+
* with the configuration when the framework starts. Not part of
|
|
116
|
+
* `modelHash`.
|
|
115
117
|
*/
|
|
116
118
|
readonly computedFields: AvConfigComputedFields | null;
|
|
117
119
|
};
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AvConfigComputedFields } from "@aventara/core";
|
|
2
1
|
import { type AvPrismaAdapterModel, type AvPrismaReferentialActionTable, type PrismaClientShape, type PrismaTranslationTable } from "../discovery/discovery-artifact.interface.js";
|
|
3
2
|
import type { AvPrismaProvider } from "../provider/provider-profile.js";
|
|
4
3
|
export type PrismaArtifactEmission = {
|
|
@@ -12,9 +11,14 @@ export type PrismaArtifactEmission = {
|
|
|
12
11
|
/** The generated client's models and field references — beside `model`, outside the hash. */
|
|
13
12
|
readonly client: PrismaClientShape;
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* The specifier the module imports the configuration's computed fields by:
|
|
15
|
+
* the file `aventara-build` writes beside the configuration
|
|
16
|
+
* (`computed-fields.generated.ts`). `null` when the step was given no
|
|
17
|
+
* configuration: the module then states none. Beside `model`, outside the
|
|
18
|
+
* hash.
|
|
17
19
|
*/
|
|
18
|
-
readonly computedFields:
|
|
20
|
+
readonly computedFields: {
|
|
21
|
+
readonly specifier: string;
|
|
22
|
+
} | null;
|
|
19
23
|
};
|
|
20
24
|
export declare function emitDiscoveryArtifactModule(emission: PrismaArtifactEmission): Promise<string>;
|
|
@@ -60,8 +60,8 @@ const MODEL_TYPE_DOC = [
|
|
|
60
60
|
const COMPUTED_FIELDS_DOC = [
|
|
61
61
|
"/**",
|
|
62
62
|
" * Which fields the configuration's write compute fills (`compute.create`,",
|
|
63
|
-
" * `compute.update`), per layer and Resource,
|
|
64
|
-
" *
|
|
63
|
+
" * `compute.update`), per layer and Resource, as aventara-build read them off",
|
|
64
|
+
" * the configuration's type. A variant's guards, pipes and filters may leave a",
|
|
65
65
|
" * create's computed fields out; every other required field stays required.",
|
|
66
66
|
" * The framework warns at startup when the configuration says otherwise: run",
|
|
67
67
|
" * `aventara:prepare` again. Not part of `modelHash`.",
|
|
@@ -90,10 +90,7 @@ function computedFieldsLines(computedFields) {
|
|
|
90
90
|
}
|
|
91
91
|
return [
|
|
92
92
|
...COMPUTED_FIELDS_DOC,
|
|
93
|
-
|
|
94
|
-
"",
|
|
95
|
-
"/** The type of {@link computedFields}: what `AvModel` carries to the stages' types. */",
|
|
96
|
-
"export type AvComputedFields = typeof computedFields;",
|
|
93
|
+
"export { type AvComputedFields, computedFields };",
|
|
97
94
|
"",
|
|
98
95
|
];
|
|
99
96
|
}
|
|
@@ -102,9 +99,12 @@ export async function emitDiscoveryArtifactModule(emission) {
|
|
|
102
99
|
return `${[
|
|
103
100
|
...BANNER,
|
|
104
101
|
"",
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
'import type { AvAdapterModel } from "@aventara/core";',
|
|
103
|
+
...(emission.computedFields === null
|
|
104
|
+
? []
|
|
105
|
+
: [
|
|
106
|
+
`import { type AvComputedFields, computedFields } from ${JSON.stringify(emission.computedFields.specifier)};`,
|
|
107
|
+
]),
|
|
108
108
|
"",
|
|
109
109
|
`const discoveredModel = ${canonicalLiteral(emission.model)} as const satisfies AvAdapterModel;`,
|
|
110
110
|
"",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
function projectOptions(directory) {
|
|
5
|
+
const tsconfig = ts.findConfigFile(directory, ts.sys.fileExists);
|
|
6
|
+
if (tsconfig === undefined) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
const read = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
10
|
+
return read.error !== undefined
|
|
11
|
+
? {}
|
|
12
|
+
: ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(tsconfig))
|
|
13
|
+
.options;
|
|
14
|
+
}
|
|
15
|
+
function packageType(directory) {
|
|
16
|
+
for (let current = path.resolve(directory);;) {
|
|
17
|
+
const manifest = path.join(current, "package.json");
|
|
18
|
+
if (existsSync(manifest)) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(manifest, "utf8")).type;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const parent = path.dirname(current);
|
|
27
|
+
if (parent === current) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
current = parent;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function importSuffix(directory) {
|
|
34
|
+
const options = projectOptions(directory);
|
|
35
|
+
if (options.allowImportingTsExtensions === true ||
|
|
36
|
+
options
|
|
37
|
+
.rewriteRelativeImportExtensions === true) {
|
|
38
|
+
return ".ts";
|
|
39
|
+
}
|
|
40
|
+
if (options.module === ts.ModuleKind.CommonJS ||
|
|
41
|
+
options.moduleResolution === ts.ModuleResolutionKind.Bundler) {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
if (options.module === ts.ModuleKind.Node16 ||
|
|
45
|
+
options.module === ts.ModuleKind.NodeNext) {
|
|
46
|
+
return packageType(directory) === "module" ? ".js" : "";
|
|
47
|
+
}
|
|
48
|
+
return ".js";
|
|
49
|
+
}
|
|
50
|
+
export function computedFieldsSpecifier(artifactFile) {
|
|
51
|
+
return `./computed-fields.generated${importSuffix(path.dirname(path.resolve(artifactFile)))}`;
|
|
52
|
+
}
|
|
@@ -14,10 +14,13 @@ Options:
|
|
|
14
14
|
--driver <name> ${PRISMA_DRIVERS.join(" | ")}
|
|
15
15
|
--out <file> Where to write the emitted artifact module (a .ts file).
|
|
16
16
|
--config <file> Optional: your Aventara configuration module, such as
|
|
17
|
-
src/aventara.config.ts
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
src/aventara.config.ts. aventara-build reads which fields
|
|
18
|
+
it computes and writes them to
|
|
19
|
+
computed-fields.generated.ts beside the artifact; the
|
|
20
|
+
artifact imports them from there, so the stages that
|
|
21
|
+
run before compute may leave exactly those out of a
|
|
22
|
+
create. This step never reads the configuration; it
|
|
23
|
+
only checks that it exists.
|
|
21
24
|
-h, --help Print this and exit 0.
|
|
22
25
|
|
|
23
26
|
The artifact must NOT be written inside --client: the next \`prisma generate\`
|
|
@@ -94,12 +97,6 @@ async function main(argv) {
|
|
|
94
97
|
for (const absence of result.excluded) {
|
|
95
98
|
process.stdout.write(`excluded: ${absence.message}\n`);
|
|
96
99
|
}
|
|
97
|
-
if (result.computedFields?.kind === "read") {
|
|
98
|
-
process.stdout.write(`computed fields: read from ${options.config ?? "the configuration"}\n`);
|
|
99
|
-
}
|
|
100
|
-
else if (result.computedFields?.kind === "unread") {
|
|
101
|
-
process.stdout.write(`computed fields: not read, ${result.computedFields.reason}; the stages before compute may leave any create field out. Write the configuration \`as const\` there.\n`);
|
|
102
|
-
}
|
|
103
100
|
for (const exemption of result.exemptions) {
|
|
104
101
|
process.stdout.write(`exempt (${exemption.kind}) ${exemption.entity}: ${exemption.reason}\n`);
|
|
105
102
|
}
|
|
@@ -114,7 +111,6 @@ export async function runFromProcess() {
|
|
|
114
111
|
(error instanceof Error &&
|
|
115
112
|
(error.name === "AvPrismaGenerateStepError" ||
|
|
116
113
|
error.name === "AvPrismaModelCompositionError" ||
|
|
117
|
-
error.name === "AvPrismaConfigReaderError" ||
|
|
118
114
|
error.name === "AvPrismaSchemaReaderError"));
|
|
119
115
|
process.stderr.write(named
|
|
120
116
|
? `aventara-prisma7-generate: ${error.message}\n`
|
package/dist/generate/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { AvPrismaDriver, AvPrismaProvider } from "../provider/provider-profile.js";
|
|
2
2
|
import type { PrismaGenerateResult, PrismaGenerateToolchain } from "./prisma.generate.js";
|
|
3
3
|
export { AvPrismaModelCompositionError, createPrismaModelComposer, } from "../discovery/model.discoverer.js";
|
|
4
|
-
export type { ConfigComputedFieldsReading } from "./computed-fields.reader.js";
|
|
5
|
-
export { AvPrismaConfigReaderError, readConfigComputedFields, } from "./computed-fields.reader.js";
|
|
6
4
|
export type { PrismaEntityAbsence } from "./existence.gate.js";
|
|
7
5
|
export type { PrismaComposedArtifact, PrismaGenerateRequest, PrismaGenerateResult, PrismaGenerateToolchain, PrismaModelComposer, } from "./prisma.generate.js";
|
|
8
6
|
export { AvPrismaGenerateStepError, createPrismaGenerateToolchain, PINNED_PRISMA_MAJOR, runPrismaGenerateStep, } from "./prisma.generate.js";
|
|
@@ -19,9 +17,9 @@ export type PrismaDiscoveryGenerateRequest = {
|
|
|
19
17
|
/** Where the emitted TypeScript artifact module is written. */
|
|
20
18
|
readonly artifactFile: string;
|
|
21
19
|
/**
|
|
22
|
-
* The Aventara configuration module
|
|
23
|
-
*
|
|
24
|
-
* states none.
|
|
20
|
+
* The Aventara configuration module (`src/aventara.config.ts` in a
|
|
21
|
+
* scaffolded project): the artifact imports the fields it computes from
|
|
22
|
+
* the file `aventara-build` writes beside it. Left out, it states none.
|
|
25
23
|
*/
|
|
26
24
|
readonly config?: string;
|
|
27
25
|
/** Replaces the step's external effects; omit it to use the installed Prisma and TypeScript. */
|
package/dist/generate/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { createPrismaModelComposer } from "../discovery/model.discoverer.js";
|
|
|
2
2
|
import { resolveProviderProfile } from "../provider/provider-profile.js";
|
|
3
3
|
import { runPrismaGenerateStep } from "./prisma.generate.js";
|
|
4
4
|
export { AvPrismaModelCompositionError, createPrismaModelComposer, } from "../discovery/model.discoverer.js";
|
|
5
|
-
export { AvPrismaConfigReaderError, readConfigComputedFields, } from "./computed-fields.reader.js";
|
|
6
5
|
export { AvPrismaGenerateStepError, createPrismaGenerateToolchain, PINNED_PRISMA_MAJOR, runPrismaGenerateStep, } from "./prisma.generate.js";
|
|
7
6
|
export async function generatePrismaDiscoveryArtifact(request) {
|
|
8
7
|
const profile = resolveProviderProfile({
|
|
@@ -2,7 +2,6 @@ import type { PrismaCapabilitySurface } from "../discovery/capability-surface.in
|
|
|
2
2
|
import type { AvPrismaAdapterModel, AvPrismaReferentialActionTable, PrismaTranslationTable } from "../discovery/discovery-artifact.interface.js";
|
|
3
3
|
import type { PrismaSchemaFacts } from "../discovery/schema-facts.interface.js";
|
|
4
4
|
import type { AvPrismaProvider } from "../provider/provider-profile.js";
|
|
5
|
-
import { type ConfigComputedFieldsReading } from "./computed-fields.reader.js";
|
|
6
5
|
import type { PrismaEntityAbsence } from "./existence.gate.js";
|
|
7
6
|
import { type PrismaCli } from "./prisma-cli.resolver.js";
|
|
8
7
|
import type { PrismaReaderExemption } from "./reader-crosscheck.js";
|
|
@@ -38,11 +37,6 @@ export type PrismaGenerateToolchain = {
|
|
|
38
37
|
readonly readSchema: (location: string) => Promise<PrismaSchemaFacts>;
|
|
39
38
|
readonly readSurface: (clientDirectory: string) => PrismaCapabilitySurface;
|
|
40
39
|
readonly writeArtifact: (file: string, source: string) => Promise<void>;
|
|
41
|
-
/**
|
|
42
|
-
* Reads which fields a configuration computes off its type. Optional: the
|
|
43
|
-
* TypeScript reader is used when it is left out.
|
|
44
|
-
*/
|
|
45
|
-
readonly readComputedFields?: (configFile: string) => ConfigComputedFieldsReading;
|
|
46
40
|
};
|
|
47
41
|
/** What {@link runPrismaGenerateStep} needs. */
|
|
48
42
|
export type PrismaGenerateRequest = {
|
|
@@ -55,8 +49,11 @@ export type PrismaGenerateRequest = {
|
|
|
55
49
|
readonly composeModel: PrismaModelComposer;
|
|
56
50
|
/**
|
|
57
51
|
* The application's Aventara configuration module (`src/aventara.config.ts`
|
|
58
|
-
* in a scaffolded project)
|
|
59
|
-
*
|
|
52
|
+
* in a scaffolded project). The artifact imports the fields it computes
|
|
53
|
+
* from the file `aventara-build` writes beside the artifact
|
|
54
|
+
* (`computed-fields.generated.ts`, a sibling); the step never reads the
|
|
55
|
+
* configuration, and only checks it exists. Left out, the artifact states
|
|
56
|
+
* none (`computedFields: null`).
|
|
60
57
|
*/
|
|
61
58
|
readonly config?: string;
|
|
62
59
|
readonly toolchain?: PrismaGenerateToolchain;
|
|
@@ -71,11 +68,6 @@ export type PrismaGenerateResult = {
|
|
|
71
68
|
/** PSL-only entities the schema itself explained away. */
|
|
72
69
|
readonly excluded: readonly PrismaEntityAbsence[];
|
|
73
70
|
readonly exemptions: readonly PrismaReaderExemption[];
|
|
74
|
-
/**
|
|
75
|
-
* What the step made of the configuration's computed fields: read, or why
|
|
76
|
-
* not. Absent when it was given no configuration.
|
|
77
|
-
*/
|
|
78
|
-
readonly computedFields?: ConfigComputedFieldsReading;
|
|
79
71
|
};
|
|
80
72
|
export declare function resolveInstalledPrismaCli(): PrismaCli;
|
|
81
73
|
/**
|
|
@@ -4,11 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import ts from "typescript";
|
|
6
6
|
import { computeModelHash } from "../discovery/artifact.loader.js";
|
|
7
|
-
import {
|
|
8
|
-
import { readPrismaSchema } from "../discovery/schema.reader.js";
|
|
7
|
+
import { AvPrismaSchemaReaderError, readPrismaSchema, } from "../discovery/schema.reader.js";
|
|
9
8
|
import { readPrismaCapabilitySurface } from "../discovery/types.reader.js";
|
|
10
9
|
import { emitDiscoveryArtifactModule } from "./artifact.emitter.js";
|
|
11
|
-
import {
|
|
10
|
+
import { computedFieldsSpecifier } from "./computed-fields.import.js";
|
|
12
11
|
import { readDeclaredSupport } from "./declared-support.reader.js";
|
|
13
12
|
import { applyExistenceGate } from "./existence.gate.js";
|
|
14
13
|
import { resolvePrismaCli } from "./prisma-cli.resolver.js";
|
|
@@ -76,21 +75,38 @@ async function writeArtifactAtomically(file, source) {
|
|
|
76
75
|
if (current === source) {
|
|
77
76
|
return;
|
|
78
77
|
}
|
|
79
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
80
78
|
const staged = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
81
79
|
try {
|
|
80
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
82
81
|
await writeFile(staged, source, "utf8");
|
|
83
82
|
await rename(staged, file);
|
|
84
83
|
}
|
|
85
84
|
catch (error) {
|
|
86
|
-
await rm(staged, { force: true });
|
|
87
|
-
throw error;
|
|
85
|
+
await rm(staged, { force: true }).catch(() => undefined);
|
|
86
|
+
throw unwritableArtifact(file, error);
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
const UNWRITABLE_BECAUSE = {
|
|
90
|
+
EACCES: "permission denied",
|
|
91
|
+
EPERM: "permission denied",
|
|
92
|
+
ENOTDIR: "a part of its path is a file, not a directory",
|
|
93
|
+
EEXIST: "a part of its path is a file, not a directory",
|
|
94
|
+
ENOSPC: "no space is left on the device",
|
|
95
|
+
EROFS: "the file system is read-only",
|
|
96
|
+
EISDIR: "it is a directory",
|
|
97
|
+
ENAMETOOLONG: "its path is too long",
|
|
98
|
+
};
|
|
99
|
+
function unwritableArtifact(file, error) {
|
|
100
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string"
|
|
101
|
+
? error.code
|
|
102
|
+
: undefined;
|
|
103
|
+
if (code === undefined) {
|
|
104
|
+
return error;
|
|
105
|
+
}
|
|
106
|
+
const because = Object.hasOwn(UNWRITABLE_BECAUSE, code)
|
|
107
|
+
? `${UNWRITABLE_BECAUSE[code]} (${code})`
|
|
108
|
+
: code;
|
|
109
|
+
return new AvPrismaGenerateStepError(`the artifact file ${path.resolve(file)} (--out) cannot be written: ${because}.`);
|
|
94
110
|
}
|
|
95
111
|
function majorOf(version) {
|
|
96
112
|
const major = /^(\d+)\./.exec(version)?.[1];
|
|
@@ -126,6 +142,28 @@ async function assertArtifactPlacement(artifactFile, clientDirectory) {
|
|
|
126
142
|
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
143
|
}
|
|
128
144
|
}
|
|
145
|
+
const PSL_PROVIDERS = {
|
|
146
|
+
sqlite: "sqlite",
|
|
147
|
+
postgresql: "postgresql",
|
|
148
|
+
postgres: "postgresql",
|
|
149
|
+
};
|
|
150
|
+
function assertDatasourceProvider(facts, provider) {
|
|
151
|
+
for (const datasource of facts.datasources) {
|
|
152
|
+
if (datasource.provider === null) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const named = Object.hasOwn(PSL_PROVIDERS, datasource.provider)
|
|
156
|
+
? PSL_PROVIDERS[datasource.provider]
|
|
157
|
+
: undefined;
|
|
158
|
+
if (named === provider) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const where = `${datasource.site.file}:${datasource.site.line}`;
|
|
162
|
+
throw new AvPrismaGenerateStepError(named === undefined
|
|
163
|
+
? `the schema's datasource \`${datasource.name}\` (${where}) names provider "${datasource.provider}", which this adapter does not support (${Object.keys(PSL_PROVIDERS).join(", ")}).`
|
|
164
|
+
: `--provider ${provider} contradicts the schema's datasource \`${datasource.name}\` (${where}), which names provider "${datasource.provider}"; pass --provider ${named}.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
129
167
|
function assertNothingUnhandled(facts) {
|
|
130
168
|
if (facts.unhandled.length === 0) {
|
|
131
169
|
return;
|
|
@@ -141,6 +179,17 @@ export async function runPrismaGenerateStep(request) {
|
|
|
141
179
|
const prismaVersion = await toolchain.installedPrismaVersion();
|
|
142
180
|
assertPinnedMajor(prismaVersion);
|
|
143
181
|
assertClientRuntimeResolves(toolchain, request.clientDirectory);
|
|
182
|
+
const declared = await toolchain
|
|
183
|
+
.readSchema(request.schema)
|
|
184
|
+
.catch((error) => {
|
|
185
|
+
if (error instanceof AvPrismaSchemaReaderError) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
});
|
|
190
|
+
if (declared !== undefined) {
|
|
191
|
+
assertDatasourceProvider(declared, request.provider);
|
|
192
|
+
}
|
|
144
193
|
await toolchain.runPrismaGenerate(request.schema);
|
|
145
194
|
const facts = await toolchain.readSchema(request.schema);
|
|
146
195
|
assertNothingUnhandled(facts);
|
|
@@ -170,19 +219,17 @@ export async function runPrismaGenerateStep(request) {
|
|
|
170
219
|
client: clientShapeOf(surface),
|
|
171
220
|
};
|
|
172
221
|
const modelHash = await computeModelHash(model);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
await toolchain.writeArtifact(request.artifactFile, await emitDiscoveryArtifactModule({
|
|
177
|
-
...emission,
|
|
178
|
-
computedFields: null,
|
|
179
|
-
}));
|
|
180
|
-
}
|
|
181
|
-
computedFields = (toolchain.readComputedFields ?? readConfigComputedFields)(request.config);
|
|
222
|
+
if (request.config !== undefined &&
|
|
223
|
+
(await stat(request.config).catch(() => undefined))?.isFile() !== true) {
|
|
224
|
+
throw new AvPrismaGenerateStepError(`the configuration \`${request.config}\` does not exist (--config).`);
|
|
182
225
|
}
|
|
183
226
|
const source = await emitDiscoveryArtifactModule({
|
|
184
227
|
...emission,
|
|
185
|
-
computedFields:
|
|
228
|
+
computedFields: request.config === undefined
|
|
229
|
+
? null
|
|
230
|
+
: {
|
|
231
|
+
specifier: computedFieldsSpecifier(request.artifactFile),
|
|
232
|
+
},
|
|
186
233
|
});
|
|
187
234
|
await toolchain.writeArtifact(request.artifactFile, source);
|
|
188
235
|
return {
|
|
@@ -192,7 +239,6 @@ export async function runPrismaGenerateStep(request) {
|
|
|
192
239
|
admitted,
|
|
193
240
|
excluded,
|
|
194
241
|
exemptions,
|
|
195
|
-
...(computedFields === undefined ? {} : { computedFields }),
|
|
196
242
|
};
|
|
197
243
|
}
|
|
198
244
|
function clientShapeOf(surface) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { AvPrismaAdapterModel, AvPrismaDiscoveryArtifact, AvPrismaReferentialAction, AvPrismaReferentialActionTable, AvPrismaReferentialEdge, AvPrismaResourceReferentialActions, } from "./discovery/discovery-artifact.interface.js";
|
|
2
2
|
export type { AvPrismaAdapterOptions } from "./prisma.adapter.js";
|
|
3
3
|
export { createPrismaAdapter } from "./prisma.adapter.js";
|
|
4
|
+
export { describePrismaAdapter } from "./prisma-adapter.description.js";
|
|
4
5
|
export type { AvPrismaDelegate, AvPrismaDelegateHost, AvPrismaDelegateIndex, AvPrismaTransactionOptions, } from "./prisma-client.interface.js";
|
|
5
6
|
export type { AvPrismaDriver, AvPrismaIsolationLevel, AvPrismaProvider, AvPrismaProviderProfile, } from "./provider/provider-profile.js";
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AvAdapter, AvTransactionCapability } from "@aventara/core";
|
|
2
|
+
import type { AvPrismaAdapterModel, AvPrismaDiscoveryArtifact } from "./discovery/discovery-artifact.interface.js";
|
|
3
|
+
import { type AvPrismaDriver, type AvPrismaProvider } from "./provider/provider-profile.js";
|
|
4
|
+
/** What {@link describePrismaAdapter} takes: the artifact and the declared provider and driver. */
|
|
5
|
+
type PrismaAdapterDescriptionInput<M extends AvPrismaAdapterModel> = {
|
|
6
|
+
/** The `discovery` export of the generated discovery artifact. */
|
|
7
|
+
readonly discovery: AvPrismaDiscoveryArtifact<M>;
|
|
8
|
+
/** The database provider the client was generated for. */
|
|
9
|
+
readonly provider: AvPrismaProvider;
|
|
10
|
+
/** The Prisma driver adapter the client is constructed with. */
|
|
11
|
+
readonly driver: AvPrismaDriver;
|
|
12
|
+
};
|
|
13
|
+
/** The adapter's passive facts, as {@link describePrismaAdapter} states them. */
|
|
14
|
+
type PrismaAdapterDescription<M extends AvPrismaAdapterModel> = PrismaAdapterDescriptionInput<M> & Required<Pick<AvAdapter<M>, "model" | "referentialActions">> & Pick<AvAdapter<M>, "computedFields"> & {
|
|
15
|
+
/**
|
|
16
|
+
* The driver's transaction capability: `"interactive"` when the
|
|
17
|
+
* constructed Adapter runs transactions, `"none"` when it carries no
|
|
18
|
+
* `transaction` (`@prisma/adapter-d1`).
|
|
19
|
+
*/
|
|
20
|
+
readonly transactions: AvTransactionCapability;
|
|
21
|
+
/** Every Prisma driver runs developer SQL statements. */
|
|
22
|
+
readonly sqlStatements: true;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The Prisma 7 adapter's passive description: the model, the driver's
|
|
26
|
+
* transaction capability, the referential actions and the computed fields, read
|
|
27
|
+
* from the discovery artifact with no client. `aventara-build` compiles the
|
|
28
|
+
* Contracts from it; `createPrismaAdapter({ ...description, client })` builds
|
|
29
|
+
* the adapter from it.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // src/aventara.adapter.ts
|
|
34
|
+
* export default describePrismaAdapter({
|
|
35
|
+
* discovery,
|
|
36
|
+
* provider: "postgresql",
|
|
37
|
+
* driver: "@prisma/adapter-pg",
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function describePrismaAdapter<M extends AvPrismaAdapterModel>(input: PrismaAdapterDescriptionInput<M>): PrismaAdapterDescription<M>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveProviderProfile, } from "./provider/provider-profile.js";
|
|
2
|
+
const DELETE_EFFECTS = {
|
|
3
|
+
cascade: "cascade",
|
|
4
|
+
restrict: "restrict",
|
|
5
|
+
"no-action": "noAction",
|
|
6
|
+
"set-null": "setNull",
|
|
7
|
+
"set-default": "setDefault",
|
|
8
|
+
};
|
|
9
|
+
function translateReferentialActions(table) {
|
|
10
|
+
return Object.fromEntries(Object.entries(table).map(([resource, { owningEdges }]) => [
|
|
11
|
+
resource,
|
|
12
|
+
owningEdges.map((edge) => ({
|
|
13
|
+
field: edge.field,
|
|
14
|
+
target: edge.target,
|
|
15
|
+
references: edge.references,
|
|
16
|
+
onDelete: DELETE_EFFECTS[edge.onDelete],
|
|
17
|
+
})),
|
|
18
|
+
]));
|
|
19
|
+
}
|
|
20
|
+
export function describePrismaAdapter(input) {
|
|
21
|
+
const profile = resolveProviderProfile({
|
|
22
|
+
provider: input.provider,
|
|
23
|
+
driver: input.driver,
|
|
24
|
+
});
|
|
25
|
+
const computedFields = input.discovery.computedFields;
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
discovery: input.discovery,
|
|
28
|
+
provider: input.provider,
|
|
29
|
+
driver: input.driver,
|
|
30
|
+
model: input.discovery.model,
|
|
31
|
+
transactions: profile.transactions,
|
|
32
|
+
referentialActions: translateReferentialActions(input.discovery.referentialActions),
|
|
33
|
+
...(computedFields === null ? {} : { computedFields }),
|
|
34
|
+
sqlStatements: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
package/dist/prisma.adapter.js
CHANGED
|
@@ -5,25 +5,8 @@ import { derivePrismaBackReferences } from "./errors/back-reference.locator.js";
|
|
|
5
5
|
import { createPrismaOperationExecutor } from "./execution/operation.executor.js";
|
|
6
6
|
import { createPrismaSqlStatementExecutor } from "./execution/sql-statement.executor.js";
|
|
7
7
|
import { createPrismaTransactionExecutor } from "./execution/transaction.executor.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
cascade: "cascade",
|
|
11
|
-
restrict: "restrict",
|
|
12
|
-
"no-action": "noAction",
|
|
13
|
-
"set-null": "setNull",
|
|
14
|
-
"set-default": "setDefault",
|
|
15
|
-
};
|
|
16
|
-
function translateReferentialActions(table) {
|
|
17
|
-
return Object.fromEntries(Object.entries(table).map(([resource, { owningEdges }]) => [
|
|
18
|
-
resource,
|
|
19
|
-
owningEdges.map((edge) => ({
|
|
20
|
-
field: edge.field,
|
|
21
|
-
target: edge.target,
|
|
22
|
-
references: edge.references,
|
|
23
|
-
onDelete: DELETE_EFFECTS[edge.onDelete],
|
|
24
|
-
})),
|
|
25
|
-
]));
|
|
26
|
-
}
|
|
8
|
+
import { describePrismaAdapter } from "./prisma-adapter.description.js";
|
|
9
|
+
import { resolveBigintBinding, } from "./provider/provider-profile.js";
|
|
27
10
|
const PRISMA_VERSION_UNCHECKED_WARNING = "AVENTARA_PRISMA_VERSION_UNCHECKED";
|
|
28
11
|
function installedClientVersion(discovery, override) {
|
|
29
12
|
if (override !== undefined) {
|
|
@@ -47,22 +30,19 @@ function installedClientVersion(discovery, override) {
|
|
|
47
30
|
return undefined;
|
|
48
31
|
}
|
|
49
32
|
export async function createPrismaAdapter(options) {
|
|
50
|
-
const
|
|
51
|
-
provider: options.provider,
|
|
52
|
-
driver: options.driver,
|
|
53
|
-
});
|
|
33
|
+
const description = describePrismaAdapter(options);
|
|
54
34
|
await loadDiscoveryArtifact({
|
|
55
35
|
artifact: options.discovery,
|
|
56
36
|
installedPrismaVersion: installedClientVersion(options.discovery, options.installedPrismaVersion),
|
|
57
37
|
});
|
|
58
38
|
assertClientMatchesArtifact(options.discovery.client, options.client);
|
|
59
|
-
const model =
|
|
60
|
-
const referentialActions =
|
|
39
|
+
const model = description.model;
|
|
40
|
+
const referentialActions = description.referentialActions;
|
|
61
41
|
const backReferences = derivePrismaBackReferences(options.discovery.model);
|
|
62
42
|
const bigintBinding = resolveBigintBinding(options.driver);
|
|
63
|
-
const computedFields =
|
|
43
|
+
const computedFields = description.computedFields === undefined
|
|
64
44
|
? {}
|
|
65
|
-
: { computedFields:
|
|
45
|
+
: { computedFields: description.computedFields };
|
|
66
46
|
const { execute } = createPrismaOperationExecutor({
|
|
67
47
|
client: options.client,
|
|
68
48
|
model: options.discovery.model,
|
|
@@ -75,13 +55,13 @@ export async function createPrismaAdapter(options) {
|
|
|
75
55
|
: { transactionOptions: options.transactionOptions }),
|
|
76
56
|
});
|
|
77
57
|
const executeSql = createPrismaSqlStatementExecutor(options.client);
|
|
78
|
-
if (
|
|
58
|
+
if (description.transactions === "none") {
|
|
79
59
|
return {
|
|
80
60
|
model,
|
|
81
61
|
referentialActions,
|
|
82
62
|
...computedFields,
|
|
83
63
|
execute,
|
|
84
|
-
sqlStatements:
|
|
64
|
+
sqlStatements: description.sqlStatements,
|
|
85
65
|
executeSql,
|
|
86
66
|
};
|
|
87
67
|
}
|
|
@@ -101,7 +81,7 @@ export async function createPrismaAdapter(options) {
|
|
|
101
81
|
...computedFields,
|
|
102
82
|
execute,
|
|
103
83
|
transaction,
|
|
104
|
-
sqlStatements:
|
|
84
|
+
sqlStatements: description.sqlStatements,
|
|
105
85
|
executeSql,
|
|
106
86
|
};
|
|
107
87
|
}
|
|
@@ -131,7 +131,7 @@ function isUniqueSelectorVariant(key) {
|
|
|
131
131
|
function singleCallArguments(context, key, predicate, projectionArgs) {
|
|
132
132
|
const unique = isUniqueSelectorVariant(key);
|
|
133
133
|
const where = unique
|
|
134
|
-
? { where: uniqueWhere(context) }
|
|
134
|
+
? { where: uniqueWhere(context, key) }
|
|
135
135
|
: predicate === undefined
|
|
136
136
|
? {}
|
|
137
137
|
: { where: predicate };
|
|
@@ -189,11 +189,18 @@ function singleCallArguments(context, key, predicate, projectionArgs) {
|
|
|
189
189
|
throw new AvPrismaOperationTranslationError(`"${key}" has a dispatch entry and no argument shape, which is a translator defect`);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
const SCOPED_UNIQUE_VARIANTS = new Set([
|
|
193
|
+
"update.unique",
|
|
194
|
+
"delete.unique",
|
|
195
|
+
]);
|
|
196
|
+
function uniqueWhere(context, key) {
|
|
197
|
+
const where = context.args.where;
|
|
198
|
+
if (!isRecord(where)) {
|
|
195
199
|
throw new AvPrismaOperationTranslationError("where must be a unique identifier object for a unique variant");
|
|
196
200
|
}
|
|
201
|
+
const { AND: conditions, ...rest } = where;
|
|
202
|
+
const scoped = conditions !== undefined && SCOPED_UNIQUE_VARIANTS.has(key);
|
|
203
|
+
const selector = scoped ? rest : where;
|
|
197
204
|
const addressed = addressIdentifier({
|
|
198
205
|
addresses: identifiersOf(context, context.resource),
|
|
199
206
|
values: selector,
|
|
@@ -203,7 +210,17 @@ function uniqueWhere(context) {
|
|
|
203
210
|
if (addressed === undefined) {
|
|
204
211
|
throw new AvPrismaOperationTranslationError(`where: { ${Object.keys(selector).join(", ")} } is no identifier of Resource "${context.resource}", so it addresses no row`);
|
|
205
212
|
}
|
|
206
|
-
|
|
213
|
+
if (!scoped) {
|
|
214
|
+
return addressed.where;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
...addressed.where,
|
|
218
|
+
...translateWhere({
|
|
219
|
+
model: context.model,
|
|
220
|
+
resource: context.resource,
|
|
221
|
+
where: { AND: conditions },
|
|
222
|
+
}),
|
|
223
|
+
};
|
|
207
224
|
}
|
|
208
225
|
function identifierSelect(context) {
|
|
209
226
|
const [address] = identifiersOf(context, context.resource);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aventara/prisma7-adapter",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.4",
|
|
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.
|
|
33
|
+
"@aventara/core": "1.0.0-rc.4"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@prisma/adapter-better-sqlite3": "^7.10.0",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { AvConfigComputedFields } from "@aventara/core";
|
|
2
|
-
/** What the reader made of a configuration: its computed fields, or why it could not tell. */
|
|
3
|
-
export type ConfigComputedFieldsReading = {
|
|
4
|
-
readonly kind: "read";
|
|
5
|
-
readonly computedFields: AvConfigComputedFields;
|
|
6
|
-
} | {
|
|
7
|
-
readonly kind: "unread";
|
|
8
|
-
readonly reason: string;
|
|
9
|
-
};
|
|
10
|
-
/** Thrown when the configuration the step was told to read cannot be found. */
|
|
11
|
-
export declare class AvPrismaConfigReaderError extends Error {
|
|
12
|
-
readonly name = "AvPrismaConfigReaderError";
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Reads which fields the configuration in `configFile` computes, from the type
|
|
16
|
-
* of its `aventaraConfig` export (a function's awaited return, or a value), or
|
|
17
|
-
* of its default export.
|
|
18
|
-
*/
|
|
19
|
-
export declare function readConfigComputedFields(configFile: string): ConfigComputedFieldsReading;
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import ts from "typescript";
|
|
4
|
-
export class AvPrismaConfigReaderError extends Error {
|
|
5
|
-
name = "AvPrismaConfigReaderError";
|
|
6
|
-
}
|
|
7
|
-
const CONFIG_EXPORTS = ["aventaraConfig", "default"];
|
|
8
|
-
const LAYERS = ["application", "client", "agent"];
|
|
9
|
-
const AGENT_PARENTS = ["root", "application", "client"];
|
|
10
|
-
const PHASES = ["create", "update"];
|
|
11
|
-
class NotExact extends Error {
|
|
12
|
-
}
|
|
13
|
-
function defaultOptions() {
|
|
14
|
-
return {
|
|
15
|
-
target: ts.ScriptTarget.ES2022,
|
|
16
|
-
module: ts.ModuleKind.NodeNext,
|
|
17
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
18
|
-
strict: true,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
function compilerOptionsFor(configFile) {
|
|
22
|
-
const tsconfig = ts.findConfigFile(path.dirname(configFile), ts.sys.fileExists, "tsconfig.json");
|
|
23
|
-
if (tsconfig === undefined) {
|
|
24
|
-
return defaultOptions();
|
|
25
|
-
}
|
|
26
|
-
const read = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
27
|
-
if (read.error !== undefined) {
|
|
28
|
-
return defaultOptions();
|
|
29
|
-
}
|
|
30
|
-
return ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(tsconfig)).options;
|
|
31
|
-
}
|
|
32
|
-
export function readConfigComputedFields(configFile) {
|
|
33
|
-
const file = path.resolve(configFile);
|
|
34
|
-
if (!existsSync(file)) {
|
|
35
|
-
throw new AvPrismaConfigReaderError(`the configuration \`${configFile}\` does not exist (--config).`);
|
|
36
|
-
}
|
|
37
|
-
const program = ts.createProgram({
|
|
38
|
-
rootNames: [file],
|
|
39
|
-
options: {
|
|
40
|
-
...compilerOptionsFor(file),
|
|
41
|
-
noEmit: true,
|
|
42
|
-
skipLibCheck: true,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
const checker = program.getTypeChecker();
|
|
46
|
-
const source = program.getSourceFile(file);
|
|
47
|
-
const module = source === undefined ? undefined : checker.getSymbolAtLocation(source);
|
|
48
|
-
if (module === undefined) {
|
|
49
|
-
throw new AvPrismaConfigReaderError(`the configuration \`${configFile}\` is not a module: it exports nothing to read.`);
|
|
50
|
-
}
|
|
51
|
-
const exports = checker.getExportsOfModule(module);
|
|
52
|
-
const exported = CONFIG_EXPORTS.map((name) => exports.find((symbol) => symbol.escapedName === name)).find((symbol) => symbol !== undefined);
|
|
53
|
-
if (exported === undefined) {
|
|
54
|
-
throw new AvPrismaConfigReaderError(`the configuration \`${configFile}\` exports neither \`aventaraConfig\` nor a default.`);
|
|
55
|
-
}
|
|
56
|
-
const target = exported.flags & ts.SymbolFlags.Alias
|
|
57
|
-
? checker.getAliasedSymbol(exported)
|
|
58
|
-
: exported;
|
|
59
|
-
const declared = checker.getTypeOfSymbol(target);
|
|
60
|
-
const [signature] = declared.getCallSignatures();
|
|
61
|
-
const config = signature === undefined
|
|
62
|
-
? declared
|
|
63
|
-
: (checker.getAwaitedType(signature.getReturnType()) ??
|
|
64
|
-
signature.getReturnType());
|
|
65
|
-
try {
|
|
66
|
-
return {
|
|
67
|
-
kind: "read",
|
|
68
|
-
computedFields: computedFieldsOfType(checker, config),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
if (error instanceof NotExact) {
|
|
73
|
-
return { kind: "unread", reason: error.message };
|
|
74
|
-
}
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function computedFieldsOfType(checker, config) {
|
|
79
|
-
assertExact(checker, config, "the configuration");
|
|
80
|
-
const overlays = Object.fromEntries(LAYERS.map((layer) => {
|
|
81
|
-
const overlay = stated(checker, config, layer, "the configuration");
|
|
82
|
-
return [
|
|
83
|
-
layer,
|
|
84
|
-
overlay === undefined
|
|
85
|
-
? {}
|
|
86
|
-
: layerComputedFields(checker, overlay, layer),
|
|
87
|
-
];
|
|
88
|
-
}));
|
|
89
|
-
return {
|
|
90
|
-
agent: overlays.agent,
|
|
91
|
-
agentExtends: agentParent(checker, config),
|
|
92
|
-
application: overlays.application,
|
|
93
|
-
client: overlays.client,
|
|
94
|
-
root: layerComputedFields(checker, config, "the root"),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function agentParent(checker, config) {
|
|
98
|
-
const agent = stated(checker, config, "agent", "the configuration");
|
|
99
|
-
const property = agent?.getProperty("extends");
|
|
100
|
-
if (property !== undefined && property.flags & ts.SymbolFlags.Optional) {
|
|
101
|
-
throw new NotExact("`extends` in agent may be absent");
|
|
102
|
-
}
|
|
103
|
-
const parent = property === undefined ? undefined : checker.getTypeOfSymbol(property);
|
|
104
|
-
if (parent === undefined) {
|
|
105
|
-
return "client";
|
|
106
|
-
}
|
|
107
|
-
const named = AGENT_PARENTS.find((candidate) => parent.isStringLiteral() && parent.value === candidate);
|
|
108
|
-
if (named === undefined) {
|
|
109
|
-
throw new NotExact(`\`agent.extends\` is \`${checker.typeToString(parent)}\`, not one layer`);
|
|
110
|
-
}
|
|
111
|
-
return named;
|
|
112
|
-
}
|
|
113
|
-
function layerComputedFields(checker, layer, where) {
|
|
114
|
-
const resources = stated(checker, layer, "resources", where);
|
|
115
|
-
if (resources === undefined) {
|
|
116
|
-
return {};
|
|
117
|
-
}
|
|
118
|
-
const result = {};
|
|
119
|
-
for (const [resource, block] of members(checker, resources, `${where}'s resources`)) {
|
|
120
|
-
const fields = stated(checker, block, "fields", `resources.${resource}`);
|
|
121
|
-
if (fields === undefined) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const phases = {
|
|
125
|
-
create: [],
|
|
126
|
-
update: [],
|
|
127
|
-
};
|
|
128
|
-
for (const [field, entry] of members(checker, fields, `resources.${resource}.fields`)) {
|
|
129
|
-
const at = `resources.${resource}.fields.${field}`;
|
|
130
|
-
const pipelines = stated(checker, entry, "pipelines", at);
|
|
131
|
-
const compute = pipelines === undefined
|
|
132
|
-
? undefined
|
|
133
|
-
: stated(checker, pipelines, "compute", `${at}.pipelines`);
|
|
134
|
-
if (compute === undefined) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
for (const phase of PHASES) {
|
|
138
|
-
if (stagePresent(checker, compute, phase, `${at}.pipelines.compute`)) {
|
|
139
|
-
phases[phase].push(field);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
const create = phases.create.sort();
|
|
144
|
-
const update = phases.update.sort();
|
|
145
|
-
if (create.length > 0 || update.length > 0) {
|
|
146
|
-
result[resource] = {
|
|
147
|
-
...(create.length === 0 ? {} : { create }),
|
|
148
|
-
...(update.length === 0 ? {} : { update }),
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return Object.fromEntries(Object.entries(result).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
|
|
153
|
-
}
|
|
154
|
-
function assertExact(checker, type, where) {
|
|
155
|
-
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
|
|
156
|
-
throw new NotExact(`${where} has no type (\`${checker.typeToString(type)}\`)`);
|
|
157
|
-
}
|
|
158
|
-
if (type.isUnion()) {
|
|
159
|
-
throw new NotExact(`${where} is one of several shapes (\`${checker.typeToString(type)}\`)`);
|
|
160
|
-
}
|
|
161
|
-
if (checker.getIndexInfosOfType(type).length > 0) {
|
|
162
|
-
throw new NotExact(`${where} is not a literal: it has an index signature`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
function stated(checker, type, name, where) {
|
|
166
|
-
const property = type.getProperty(name);
|
|
167
|
-
if (property === undefined) {
|
|
168
|
-
return undefined;
|
|
169
|
-
}
|
|
170
|
-
if (property.flags & ts.SymbolFlags.Optional) {
|
|
171
|
-
throw new NotExact(`\`${name}\` in ${where} may be absent`);
|
|
172
|
-
}
|
|
173
|
-
const value = checker.getTypeOfSymbol(property);
|
|
174
|
-
if (value.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Never)) {
|
|
175
|
-
return undefined;
|
|
176
|
-
}
|
|
177
|
-
assertExact(checker, value, `\`${name}\` in ${where}`);
|
|
178
|
-
return value;
|
|
179
|
-
}
|
|
180
|
-
function stagePresent(checker, compute, phase, where) {
|
|
181
|
-
const property = compute.getProperty(phase);
|
|
182
|
-
if (property === undefined) {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
if (property.flags & ts.SymbolFlags.Optional) {
|
|
186
|
-
throw new NotExact(`\`${phase}\` in ${where} may be absent`);
|
|
187
|
-
}
|
|
188
|
-
const stage = checker.getTypeOfSymbol(property);
|
|
189
|
-
if (stage.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Never)) {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
const undefinedMember = (candidate) => (candidate.flags & ts.TypeFlags.Undefined) !== 0;
|
|
193
|
-
if (stage.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown) ||
|
|
194
|
-
(stage.isUnion() && stage.types.some(undefinedMember))) {
|
|
195
|
-
throw new NotExact(`\`${phase}\` in ${where} may be undefined (\`${checker.typeToString(stage)}\`)`);
|
|
196
|
-
}
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
function members(checker, type, where) {
|
|
200
|
-
return type
|
|
201
|
-
.getProperties()
|
|
202
|
-
.map((property) => {
|
|
203
|
-
const name = property.getName();
|
|
204
|
-
const value = stated(checker, type, name, where);
|
|
205
|
-
return value === undefined
|
|
206
|
-
? undefined
|
|
207
|
-
: [name, value];
|
|
208
|
-
})
|
|
209
|
-
.filter((entry) => entry !== undefined);
|
|
210
|
-
}
|