@prisma-idb/family-idb 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/bin/prisma-idb.d.mts +1 -0
- package/dist/bin/prisma-idb.mjs +13 -0
- package/dist/bin/prisma-idb.mjs.map +1 -0
- package/dist/chain-order-CaHXo5jz.mjs +44 -0
- package/dist/chain-order-CaHXo5jz.mjs.map +1 -0
- package/dist/cli-Dlaym_DD.mjs +342 -0
- package/dist/cli-Dlaym_DD.mjs.map +1 -0
- package/dist/contract-space-codegen--gfuyap2.mjs +128 -0
- package/dist/contract-space-codegen--gfuyap2.mjs.map +1 -0
- package/dist/exports/cli.d.mts +26 -0
- package/dist/exports/cli.d.mts.map +1 -0
- package/dist/exports/cli.mjs +2 -0
- package/dist/exports/config-types.d.mts +38 -0
- package/dist/exports/config-types.d.mts.map +1 -0
- package/dist/exports/config-types.mjs +66 -0
- package/dist/exports/config-types.mjs.map +1 -0
- package/dist/exports/contract-psl.d.mts +59 -0
- package/dist/exports/contract-psl.d.mts.map +1 -0
- package/dist/exports/contract-psl.mjs +97 -0
- package/dist/exports/contract-psl.mjs.map +1 -0
- package/dist/exports/contract-ts.d.mts +98 -0
- package/dist/exports/contract-ts.d.mts.map +1 -0
- package/dist/exports/contract-ts.mjs +275 -0
- package/dist/exports/contract-ts.mjs.map +1 -0
- package/dist/exports/control.d.mts +107 -0
- package/dist/exports/control.d.mts.map +1 -0
- package/dist/exports/control.mjs +573 -0
- package/dist/exports/control.mjs.map +1 -0
- package/dist/exports/pack.d.mts +10 -0
- package/dist/exports/pack.d.mts.map +1 -0
- package/dist/exports/pack.mjs +11 -0
- package/dist/exports/pack.mjs.map +1 -0
- package/dist/migration-plan-BWhd8BdV.mjs +330 -0
- package/dist/migration-plan-BWhd8BdV.mjs.map +1 -0
- package/dist/preflight-4jDCNvS0.mjs +119 -0
- package/dist/preflight-4jDCNvS0.mjs.map +1 -0
- package/dist/psl-interpreter-C0suaXu2.d.mts +45 -0
- package/dist/psl-interpreter-C0suaXu2.d.mts.map +1 -0
- package/dist/psl-interpreter-C6Q4okuO.mjs +809 -0
- package/dist/psl-interpreter-C6Q4okuO.mjs.map +1 -0
- package/dist/validate-BLnItEKs.mjs +48 -0
- package/dist/validate-BLnItEKs.mjs.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
import { t as validateContract } from "./validate-BLnItEKs.mjs";
|
|
2
|
+
import { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from "@prisma/orm-framework/contract/types";
|
|
3
|
+
import { computeExecutionHash, computeProfileHash, computeStorageHash } from "@prisma/orm-framework/contract/hashing";
|
|
4
|
+
import { notOk, ok } from "@prisma/orm-framework/utils/result";
|
|
5
|
+
//#region src/core/psl-interpreter.ts
|
|
6
|
+
/** The one `temporal.*` type-constructor path IDB currently understands. */
|
|
7
|
+
const TEMPORAL_UPDATED_AT_PATH = ["temporal", "updatedAt"];
|
|
8
|
+
/** Generator id shared by `temporal.updatedAt()`/bare `@updatedAt`/`@default(now())`. */
|
|
9
|
+
const TIMESTAMP_NOW_GENERATOR_ID = "timestampNow";
|
|
10
|
+
/** Synthetic generator id that just echoes `params.value` — backs `@default(<literal>)`. */
|
|
11
|
+
const LITERAL_GENERATOR_ID = "literal";
|
|
12
|
+
/** Parses `@default(...)`'s positional argument as a literal value: `true`/`false`/a number/a quoted string. */
|
|
13
|
+
function parseDefaultLiteralValue(raw) {
|
|
14
|
+
const t = raw.trim();
|
|
15
|
+
if (t === "true") return { value: true };
|
|
16
|
+
if (t === "false") return { value: false };
|
|
17
|
+
if (/^-?\d+(\.\d+)?$/.test(t)) return { value: Number(t) };
|
|
18
|
+
const str = parseStringArg(t);
|
|
19
|
+
if (str !== void 0) return { value: str };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `true` if a parsed `@default(<literal>)` value's JS type matches what
|
|
23
|
+
* `codecId` expects (a literal's JS type must match the field's declared
|
|
24
|
+
* type — a string literal on an Int field, or vice versa, is a mismatch).
|
|
25
|
+
* Declared as a function (not a `codecId → JS type` const lookup) since
|
|
26
|
+
* `SCALAR_TO_CODEC_ID` — which every `codecId` here is sourced from — is
|
|
27
|
+
* declared further down this module; a top-level const initializer would hit
|
|
28
|
+
* the temporal dead zone, but a function body only evaluates when called.
|
|
29
|
+
*/
|
|
30
|
+
function literalValueMatchesCodec(value, codecId) {
|
|
31
|
+
switch (codecId) {
|
|
32
|
+
case SCALAR_TO_CODEC_ID["String"]: return typeof value === "string";
|
|
33
|
+
case SCALAR_TO_CODEC_ID["Int"]:
|
|
34
|
+
case SCALAR_TO_CODEC_ID["Float"]:
|
|
35
|
+
case SCALAR_TO_CODEC_ID["Decimal"]: return typeof value === "number";
|
|
36
|
+
case SCALAR_TO_CODEC_ID["Boolean"]: return typeof value === "boolean";
|
|
37
|
+
default: return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Parses `@default(...)`'s positional argument as a function call: `name(...)` or bare `name()`. */
|
|
41
|
+
function parseDefaultFunctionCall(raw) {
|
|
42
|
+
const trimmed = raw.trim();
|
|
43
|
+
const openParen = trimmed.indexOf("(");
|
|
44
|
+
const closeParen = trimmed.lastIndexOf(")");
|
|
45
|
+
if (openParen <= 0 || closeParen !== trimmed.length - 1) return void 0;
|
|
46
|
+
const name = trimmed.slice(0, openParen).trim();
|
|
47
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) return void 0;
|
|
48
|
+
const arg = trimmed.slice(openParen + 1, closeParen).trim();
|
|
49
|
+
return arg ? {
|
|
50
|
+
name,
|
|
51
|
+
arg
|
|
52
|
+
} : { name };
|
|
53
|
+
}
|
|
54
|
+
/** Resolves a parsed `@default(...)` function call (excluding `autoincrement()`, handled separately as a storage-plane concern) to a generator id. */
|
|
55
|
+
function resolveDefaultFunctionGeneratorId(name, arg) {
|
|
56
|
+
switch (name) {
|
|
57
|
+
case "now": return arg === void 0 ? {
|
|
58
|
+
ok: true,
|
|
59
|
+
generatorId: TIMESTAMP_NOW_GENERATOR_ID
|
|
60
|
+
} : {
|
|
61
|
+
ok: false,
|
|
62
|
+
reason: "invalid-argument"
|
|
63
|
+
};
|
|
64
|
+
case "uuid":
|
|
65
|
+
if (arg === void 0 || arg === "4") return {
|
|
66
|
+
ok: true,
|
|
67
|
+
generatorId: "uuidv4"
|
|
68
|
+
};
|
|
69
|
+
if (arg === "7") return {
|
|
70
|
+
ok: true,
|
|
71
|
+
generatorId: "uuidv7"
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
reason: "invalid-argument"
|
|
76
|
+
};
|
|
77
|
+
case "cuid": return arg === void 0 ? {
|
|
78
|
+
ok: true,
|
|
79
|
+
generatorId: "cuid2"
|
|
80
|
+
} : {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: "invalid-argument"
|
|
83
|
+
};
|
|
84
|
+
default: return {
|
|
85
|
+
ok: false,
|
|
86
|
+
reason: "unknown-function"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const SCALAR_TO_CODEC_ID = {
|
|
91
|
+
String: "idb/string@1",
|
|
92
|
+
Int: "idb/int32@1",
|
|
93
|
+
Float: "idb/double@1",
|
|
94
|
+
Boolean: "idb/bool@1",
|
|
95
|
+
DateTime: "idb/date@1",
|
|
96
|
+
BigInt: "idb/bigint@1",
|
|
97
|
+
Decimal: "idb/decimal@1",
|
|
98
|
+
Json: "idb/json@1",
|
|
99
|
+
Bytes: "idb/bytes@1"
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Codecs excluded from IndexedDB's "valid key" algorithm
|
|
103
|
+
* (https://w3c.github.io/IndexedDB/#key-construct: number, string, Date,
|
|
104
|
+
* buffer source, or Array — nothing else).
|
|
105
|
+
*
|
|
106
|
+
* - `idb/bool@1` — boolean is not, and has never been, a valid IDB key type.
|
|
107
|
+
* - `idb/bigint@1` — bigint round-trips fine as a stored *value* (structured
|
|
108
|
+
* clone supports it), but is explicitly absent from the key-type algorithm.
|
|
109
|
+
* - `idb/json@1` — arbitrary shape (object, array, or primitive); can't be
|
|
110
|
+
* statically guaranteed to be a valid key.
|
|
111
|
+
*
|
|
112
|
+
* Using any of these as a model's `@id`/key throws on every write
|
|
113
|
+
* (`DataError` extracting the primary key). Using one as an index `keyPath`
|
|
114
|
+
* doesn't throw on write — the record is just silently omitted from that
|
|
115
|
+
* index — but throws the first time anyone queries it via `IDBKeyRange`
|
|
116
|
+
* (see ADR 016's Context: `OutboxEvent.synced`, `idb/bool@1`, exactly this).
|
|
117
|
+
*/
|
|
118
|
+
const IDB_INVALID_KEY_CODEC_IDS = /* @__PURE__ */ new Set([
|
|
119
|
+
"idb/bool@1",
|
|
120
|
+
"idb/bigint@1",
|
|
121
|
+
"idb/json@1"
|
|
122
|
+
]);
|
|
123
|
+
/** `true` if `codecId`'s runtime representation is a valid IndexedDB key. */
|
|
124
|
+
function isValidIdbKeyCodec(codecId) {
|
|
125
|
+
return !IDB_INVALID_KEY_CODEC_IDS.has(codecId);
|
|
126
|
+
}
|
|
127
|
+
const REFERENTIAL_ACTION_MAP = {
|
|
128
|
+
Cascade: "cascade",
|
|
129
|
+
SetNull: "setNull",
|
|
130
|
+
SetDefault: "setDefault",
|
|
131
|
+
Restrict: "restrict",
|
|
132
|
+
NoAction: "noAction"
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* ADR 013 — shared between this file and `contract-builder.ts` (which
|
|
136
|
+
* already imports {@link ContractProjection} from here) so the message
|
|
137
|
+
* format isn't duplicated. Called whenever a surviving model's relation is
|
|
138
|
+
* dropped because its target is excluded — any cardinality, required or
|
|
139
|
+
* not; the model itself is never excluded as a result, only the relation.
|
|
140
|
+
*/
|
|
141
|
+
function warnDroppedRelation(modelName, relationName, targetModel) {
|
|
142
|
+
console.warn(`[prisma-idb] Dropped relation "${modelName}.${relationName}" from the client contract: target model "${targetModel}" is excluded (ADR 013).`);
|
|
143
|
+
}
|
|
144
|
+
function findPositionalArg(args) {
|
|
145
|
+
return args.find((a) => a.kind === "positional")?.value;
|
|
146
|
+
}
|
|
147
|
+
function findNamedArg(args, name) {
|
|
148
|
+
return args.find((a) => a.kind === "named" && a.name === name)?.value;
|
|
149
|
+
}
|
|
150
|
+
function parseStringArg(raw) {
|
|
151
|
+
if (raw === void 0) return void 0;
|
|
152
|
+
const t = raw.trim();
|
|
153
|
+
if (t.startsWith("\"") && t.endsWith("\"") || t.startsWith("'") && t.endsWith("'")) return t.slice(1, -1);
|
|
154
|
+
}
|
|
155
|
+
function parseFieldList(raw) {
|
|
156
|
+
if (raw === void 0) return void 0;
|
|
157
|
+
const t = raw.trim();
|
|
158
|
+
if (!t.startsWith("[") || !t.endsWith("]")) return void 0;
|
|
159
|
+
const inner = t.slice(1, -1).trim();
|
|
160
|
+
if (inner === "") return [];
|
|
161
|
+
return inner.split(",").map((s) => s.trim()).filter(Boolean);
|
|
162
|
+
}
|
|
163
|
+
function lowerFirst(s) {
|
|
164
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
165
|
+
}
|
|
166
|
+
function hasFieldAttribute(field, name) {
|
|
167
|
+
return field.attributes.some((a) => a.name === name);
|
|
168
|
+
}
|
|
169
|
+
function getFieldAttribute(field, name) {
|
|
170
|
+
return field.attributes.find((a) => a.name === name);
|
|
171
|
+
}
|
|
172
|
+
function hasModelAttribute(model, name) {
|
|
173
|
+
return model.attributes.some((a) => a.name === name);
|
|
174
|
+
}
|
|
175
|
+
const IDB_EXCLUDE_ATTR = "idb.exclude";
|
|
176
|
+
function interpretModel(model, modelNames, sourceId, diagnostics, projection, excludedModelNames, excludedFieldNamesByModel) {
|
|
177
|
+
const modelFields = Object.values(model.fields);
|
|
178
|
+
const excludedFieldNames = new Set(projection === "client" ? modelFields.filter((f) => hasFieldAttribute(f, IDB_EXCLUDE_ATTR)).map((f) => f.name) : []);
|
|
179
|
+
const storeName = parseStringArg(findPositionalArg(model.attributes.find((a) => a.name === "map")?.args ?? [])) ?? lowerFirst(model.name);
|
|
180
|
+
let keyPath;
|
|
181
|
+
let idFieldName;
|
|
182
|
+
const idModelAttr = model.attributes.find((a) => a.name === "id");
|
|
183
|
+
if (idModelAttr) {
|
|
184
|
+
const fields = parseFieldList(findPositionalArg(idModelAttr.args));
|
|
185
|
+
if (!fields || fields.length === 0) {
|
|
186
|
+
diagnostics.push({
|
|
187
|
+
code: "IDB_INVALID_ID",
|
|
188
|
+
message: `Model "${model.name}" @@id([…]) is missing a field list.`,
|
|
189
|
+
sourceId,
|
|
190
|
+
span: idModelAttr.span
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (fields.length > 1) {
|
|
195
|
+
diagnostics.push({
|
|
196
|
+
code: "IDB_NO_COMPOUND_KEY",
|
|
197
|
+
message: `Model "${model.name}" @@id([${fields.join(", ")}]) declares a compound key. IDB does not support compound primary keys — use a single @id field instead.`,
|
|
198
|
+
sourceId,
|
|
199
|
+
span: idModelAttr.span
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
idFieldName = fields[0];
|
|
204
|
+
keyPath = fields[0];
|
|
205
|
+
}
|
|
206
|
+
const idFields = modelFields.filter((f) => hasFieldAttribute(f, "id"));
|
|
207
|
+
if (idFields.length > 1) {
|
|
208
|
+
diagnostics.push({
|
|
209
|
+
code: "IDB_MULTIPLE_ID_FIELDS",
|
|
210
|
+
message: `Model "${model.name}" declares @id on multiple fields (${idFields.map((f) => f.name).join(", ")}). Only one @id field is allowed.`,
|
|
211
|
+
sourceId,
|
|
212
|
+
span: model.span
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (idFields.length === 1) {
|
|
217
|
+
if (keyPath !== void 0) {
|
|
218
|
+
diagnostics.push({
|
|
219
|
+
code: "IDB_INVALID_ID",
|
|
220
|
+
message: `Model "${model.name}" cannot declare both a field-level @id and a model-level @@id.`,
|
|
221
|
+
sourceId,
|
|
222
|
+
span: model.span
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
idFieldName = idFields[0].name;
|
|
227
|
+
keyPath = idFields[0].name;
|
|
228
|
+
}
|
|
229
|
+
if (keyPath === void 0) {
|
|
230
|
+
diagnostics.push({
|
|
231
|
+
code: "IDB_MISSING_ID",
|
|
232
|
+
message: `Model "${model.name}" has no @id field. Add @id to exactly one scalar field.`,
|
|
233
|
+
sourceId,
|
|
234
|
+
span: model.span
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (excludedFieldNames.has(keyPath)) {
|
|
239
|
+
diagnostics.push({
|
|
240
|
+
code: "IDB_CANNOT_EXCLUDE_KEY_FIELD",
|
|
241
|
+
message: `Field "${model.name}.${keyPath}" is the model's @id key and cannot be marked @idb.exclude — the client contract needs a primary key for every included model.`,
|
|
242
|
+
sourceId,
|
|
243
|
+
span: model.span
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const indexes = {};
|
|
248
|
+
for (const attr of model.attributes) {
|
|
249
|
+
if (attr.name !== "index" && attr.name !== "unique") continue;
|
|
250
|
+
const isUnique = attr.name === "unique";
|
|
251
|
+
const fields = parseFieldList(findPositionalArg(attr.args));
|
|
252
|
+
if (!fields || fields.length === 0) {
|
|
253
|
+
diagnostics.push({
|
|
254
|
+
code: "IDB_INVALID_INDEX",
|
|
255
|
+
message: `Model "${model.name}" @@${attr.name} is missing a field list.`,
|
|
256
|
+
sourceId,
|
|
257
|
+
span: attr.span
|
|
258
|
+
});
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (fields.length > 1) {
|
|
262
|
+
diagnostics.push({
|
|
263
|
+
code: "IDB_COMPOUND_INDEX_UNSUPPORTED",
|
|
264
|
+
message: `Model "${model.name}" @@${attr.name}([${fields.join(", ")}]) declares a compound index. IDB compound indexes are not yet supported — use a single-field index.`,
|
|
265
|
+
sourceId,
|
|
266
|
+
span: attr.span
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const field = fields[0];
|
|
271
|
+
if (excludedFieldNames.has(field)) {
|
|
272
|
+
diagnostics.push({
|
|
273
|
+
code: "IDB_INDEX_ON_EXCLUDED_FIELD",
|
|
274
|
+
message: `Model "${model.name}" @@${attr.name}([${field}]) references "${field}", which is marked @idb.exclude. Remove the index or the exclusion.`,
|
|
275
|
+
sourceId,
|
|
276
|
+
span: attr.span
|
|
277
|
+
});
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const indexName = parseStringArg(findNamedArg(attr.args, "name") ?? findNamedArg(attr.args, "map")) ?? (isUnique ? `${field}_unique` : field);
|
|
281
|
+
indexes[indexName] = {
|
|
282
|
+
keyPath: field,
|
|
283
|
+
unique: isUnique
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const contractFields = {};
|
|
287
|
+
const relations = {};
|
|
288
|
+
const relationsStorage = {};
|
|
289
|
+
const fieldDefaults = {};
|
|
290
|
+
const fksByTarget = /* @__PURE__ */ new Map();
|
|
291
|
+
const fieldExecutionDefaults = [];
|
|
292
|
+
let autoIncrement = false;
|
|
293
|
+
for (const field of modelFields) {
|
|
294
|
+
if (field.name === idFieldName && field.optional) diagnostics.push({
|
|
295
|
+
code: "IDB_NULLABLE_ID",
|
|
296
|
+
message: `Field "${model.name}.${field.name}" is marked as @id but is optional (?). The primary key cannot be nullable.`,
|
|
297
|
+
sourceId,
|
|
298
|
+
span: field.span
|
|
299
|
+
});
|
|
300
|
+
if (field.list && modelNames.has(field.typeName)) {
|
|
301
|
+
if (excludedModelNames.has(field.typeName)) warnDroppedRelation(model.name, field.name, field.typeName);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const relationAttr = getFieldAttribute(field, "relation");
|
|
305
|
+
if (!field.list && modelNames.has(field.typeName) && relationAttr) {
|
|
306
|
+
if (excludedFieldNames.has(field.name)) {
|
|
307
|
+
diagnostics.push({
|
|
308
|
+
code: "IDB_EXCLUDE_ON_RELATION_FIELD_UNSUPPORTED",
|
|
309
|
+
message: `Field "${model.name}.${field.name}" is a relation field and cannot be marked @idb.exclude directly. Exclude the target model with @@idb.exclude to drop the relation, or exclude a plain scalar field.`,
|
|
310
|
+
sourceId,
|
|
311
|
+
span: field.span
|
|
312
|
+
});
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (excludedModelNames.has(field.typeName)) {
|
|
316
|
+
warnDroppedRelation(model.name, field.name, field.typeName);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const args = relationAttr.args;
|
|
320
|
+
const localFieldsRaw = findNamedArg(args, "fields");
|
|
321
|
+
const targetFieldsRaw = findNamedArg(args, "references");
|
|
322
|
+
const localFields = parseFieldList(localFieldsRaw);
|
|
323
|
+
const targetFields = parseFieldList(targetFieldsRaw);
|
|
324
|
+
if (!localFields || localFields.length === 0 || !targetFields || targetFields.length === 0) {
|
|
325
|
+
diagnostics.push({
|
|
326
|
+
code: "IDB_INVALID_RELATION",
|
|
327
|
+
message: `Relation field "${model.name}.${field.name}" must declare both fields and references in @relation.`,
|
|
328
|
+
sourceId,
|
|
329
|
+
span: field.span
|
|
330
|
+
});
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (localFields.length !== targetFields.length) {
|
|
334
|
+
diagnostics.push({
|
|
335
|
+
code: "IDB_INVALID_RELATION",
|
|
336
|
+
message: `Relation field "${model.name}.${field.name}" must have the same number of fields and references.`,
|
|
337
|
+
sourceId,
|
|
338
|
+
span: field.span
|
|
339
|
+
});
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const excludedLocalField = localFields.find((f) => excludedFieldNames.has(f));
|
|
343
|
+
if (excludedLocalField !== void 0) {
|
|
344
|
+
diagnostics.push({
|
|
345
|
+
code: "IDB_CANNOT_EXCLUDE_RELATION_FIELD",
|
|
346
|
+
message: `Field "${model.name}.${excludedLocalField}" backs relation "${model.name}.${field.name}" and cannot be excluded independently — field-level FK exclusion isn't supported (ADR 013's cascade only covers whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`,
|
|
347
|
+
sourceId,
|
|
348
|
+
span: field.span
|
|
349
|
+
});
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const targetExcludedFields = excludedFieldNamesByModel.get(field.typeName);
|
|
353
|
+
const excludedTargetField = targetExcludedFields ? targetFields.find((f) => targetExcludedFields.has(f)) : void 0;
|
|
354
|
+
if (excludedTargetField !== void 0) {
|
|
355
|
+
diagnostics.push({
|
|
356
|
+
code: "IDB_CANNOT_EXCLUDE_RELATION_FIELD",
|
|
357
|
+
message: `Field "${field.typeName}.${excludedTargetField}" is referenced by relation "${model.name}.${field.name}" and cannot be excluded independently — field-level FK exclusion isn't supported (ADR 013's cascade only covers whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`,
|
|
358
|
+
sourceId,
|
|
359
|
+
span: field.span
|
|
360
|
+
});
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const onDeleteRaw = findNamedArg(args, "onDelete");
|
|
364
|
+
const onDelete = onDeleteRaw ? REFERENTIAL_ACTION_MAP[onDeleteRaw.trim()] : void 0;
|
|
365
|
+
if (onDeleteRaw && onDelete === void 0) diagnostics.push({
|
|
366
|
+
code: "IDB_UNKNOWN_REFERENTIAL_ACTION",
|
|
367
|
+
message: `Relation field "${model.name}.${field.name}" has unknown onDelete value "${onDeleteRaw}". Valid values: ${Object.keys(REFERENTIAL_ACTION_MAP).join(", ")}.`,
|
|
368
|
+
sourceId,
|
|
369
|
+
span: field.span
|
|
370
|
+
});
|
|
371
|
+
const onUpdateRaw = findNamedArg(args, "onUpdate");
|
|
372
|
+
const onUpdate = onUpdateRaw ? REFERENTIAL_ACTION_MAP[onUpdateRaw.trim()] : void 0;
|
|
373
|
+
if (onUpdateRaw && onUpdate === void 0) diagnostics.push({
|
|
374
|
+
code: "IDB_UNKNOWN_REFERENTIAL_ACTION",
|
|
375
|
+
message: `Relation field "${model.name}.${field.name}" has unknown onUpdate value "${onUpdateRaw}". Valid values: ${Object.keys(REFERENTIAL_ACTION_MAP).join(", ")}.`,
|
|
376
|
+
sourceId,
|
|
377
|
+
span: field.span
|
|
378
|
+
});
|
|
379
|
+
relations[field.name] = {
|
|
380
|
+
to: crossRef(field.typeName),
|
|
381
|
+
cardinality: "N:1",
|
|
382
|
+
on: {
|
|
383
|
+
localFields,
|
|
384
|
+
targetFields
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
if (onDelete !== void 0 || onUpdate !== void 0) relationsStorage[field.name] = {
|
|
388
|
+
...onDelete !== void 0 ? { onDelete } : {},
|
|
389
|
+
...onUpdate !== void 0 ? { onUpdate } : {}
|
|
390
|
+
};
|
|
391
|
+
fksByTarget.set(field.typeName, {
|
|
392
|
+
fieldName: field.name,
|
|
393
|
+
localFields,
|
|
394
|
+
targetFields
|
|
395
|
+
});
|
|
396
|
+
for (const fkField of localFields) if (!(fkField in indexes)) indexes[fkField] = {
|
|
397
|
+
keyPath: fkField,
|
|
398
|
+
unique: false
|
|
399
|
+
};
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (!field.list && modelNames.has(field.typeName) && !relationAttr) {
|
|
403
|
+
diagnostics.push({
|
|
404
|
+
code: "IDB_MISSING_RELATION_ATTRIBUTE",
|
|
405
|
+
message: `Field "${model.name}.${field.name}" has model type "${field.typeName}" but no @relation attribute. Add @relation(fields: [...], references: [...]).`,
|
|
406
|
+
sourceId,
|
|
407
|
+
span: field.span
|
|
408
|
+
});
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (field.list) continue;
|
|
412
|
+
if (excludedFieldNames.has(field.name)) {
|
|
413
|
+
if (hasFieldAttribute(field, "unique")) diagnostics.push({
|
|
414
|
+
code: "IDB_INDEX_ON_EXCLUDED_FIELD",
|
|
415
|
+
message: `Field "${model.name}.${field.name}" is marked @unique but also @idb.exclude. Remove the @unique attribute or the exclusion.`,
|
|
416
|
+
sourceId,
|
|
417
|
+
span: field.span
|
|
418
|
+
});
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (field.typeConstructor) {
|
|
422
|
+
const path = field.typeConstructor.path;
|
|
423
|
+
if (!(path.length === TEMPORAL_UPDATED_AT_PATH.length && path.every((p, i) => p === TEMPORAL_UPDATED_AT_PATH[i]))) {
|
|
424
|
+
diagnostics.push({
|
|
425
|
+
code: "IDB_UNSUPPORTED_TYPE_CONSTRUCTOR",
|
|
426
|
+
message: `Field "${model.name}.${field.name}" uses unsupported type constructor "${path.join(".")}()". IDB currently only supports temporal.updatedAt().`,
|
|
427
|
+
sourceId,
|
|
428
|
+
span: field.typeConstructor.span
|
|
429
|
+
});
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (field.typeConstructor.args.length > 0) {
|
|
433
|
+
diagnostics.push({
|
|
434
|
+
code: "IDB_TEMPORAL_UPDATED_AT_TAKES_NO_ARGS",
|
|
435
|
+
message: `Field "${model.name}.${field.name}" — temporal.updatedAt() takes no arguments.`,
|
|
436
|
+
sourceId,
|
|
437
|
+
span: field.typeConstructor.span
|
|
438
|
+
});
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (field.name === idFieldName) {
|
|
442
|
+
diagnostics.push({
|
|
443
|
+
code: "IDB_TEMPORAL_UPDATED_AT_ON_KEY_FIELD",
|
|
444
|
+
message: `Field "${model.name}.${field.name}" is the model's @id key and cannot be temporal.updatedAt() — an auto-managed timestamp cannot also be the primary key.`,
|
|
445
|
+
sourceId,
|
|
446
|
+
span: field.span
|
|
447
|
+
});
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
contractFields[field.name] = {
|
|
451
|
+
nullable: field.optional,
|
|
452
|
+
type: {
|
|
453
|
+
kind: "scalar",
|
|
454
|
+
codecId: SCALAR_TO_CODEC_ID["DateTime"]
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
fieldExecutionDefaults.push({
|
|
458
|
+
fieldName: field.name,
|
|
459
|
+
onCreate: {
|
|
460
|
+
kind: "generator",
|
|
461
|
+
id: TIMESTAMP_NOW_GENERATOR_ID
|
|
462
|
+
},
|
|
463
|
+
onUpdate: {
|
|
464
|
+
kind: "generator",
|
|
465
|
+
id: TIMESTAMP_NOW_GENERATOR_ID
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
const codecId = SCALAR_TO_CODEC_ID[field.typeName];
|
|
471
|
+
if (codecId === void 0) {
|
|
472
|
+
diagnostics.push({
|
|
473
|
+
code: "IDB_UNSUPPORTED_FIELD_TYPE",
|
|
474
|
+
message: `Field "${model.name}.${field.name}" has unsupported type "${field.typeName}". Supported types: ${Object.keys(SCALAR_TO_CODEC_ID).join(", ")}.`,
|
|
475
|
+
sourceId,
|
|
476
|
+
span: field.span
|
|
477
|
+
});
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
contractFields[field.name] = {
|
|
481
|
+
nullable: field.optional,
|
|
482
|
+
type: {
|
|
483
|
+
kind: "scalar",
|
|
484
|
+
codecId
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
const updatedAtAttr = getFieldAttribute(field, "updatedAt");
|
|
488
|
+
const defaultAttr = getFieldAttribute(field, "default");
|
|
489
|
+
if (updatedAtAttr && defaultAttr) {
|
|
490
|
+
diagnostics.push({
|
|
491
|
+
code: "IDB_UPDATED_AT_AND_DEFAULT_CONFLICT",
|
|
492
|
+
message: `Field "${model.name}.${field.name}" cannot combine @updatedAt with @default(...).`,
|
|
493
|
+
sourceId,
|
|
494
|
+
span: defaultAttr.span
|
|
495
|
+
});
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (updatedAtAttr) {
|
|
499
|
+
if (field.name === idFieldName) {
|
|
500
|
+
diagnostics.push({
|
|
501
|
+
code: "IDB_TEMPORAL_UPDATED_AT_ON_KEY_FIELD",
|
|
502
|
+
message: `Field "${model.name}.${field.name}" is the model's @id key and cannot be @updatedAt — an auto-managed timestamp cannot also be the primary key.`,
|
|
503
|
+
sourceId,
|
|
504
|
+
span: field.span
|
|
505
|
+
});
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (field.optional) {
|
|
509
|
+
diagnostics.push({
|
|
510
|
+
code: "IDB_EXECUTION_DEFAULT_ON_OPTIONAL_FIELD",
|
|
511
|
+
message: `Field "${model.name}.${field.name}" cannot be both optional and @updatedAt — an omitted value would be ambiguous between "generate one" and "store null". Remove "?" or the attribute.`,
|
|
512
|
+
sourceId,
|
|
513
|
+
span: field.span
|
|
514
|
+
});
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (codecId !== SCALAR_TO_CODEC_ID["DateTime"]) {
|
|
518
|
+
diagnostics.push({
|
|
519
|
+
code: "IDB_UPDATED_AT_NOT_DATETIME",
|
|
520
|
+
message: `Field "${model.name}.${field.name}" is marked @updatedAt but has type "${field.typeName}", not DateTime.`,
|
|
521
|
+
sourceId,
|
|
522
|
+
span: field.span
|
|
523
|
+
});
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
fieldExecutionDefaults.push({
|
|
527
|
+
fieldName: field.name,
|
|
528
|
+
onCreate: {
|
|
529
|
+
kind: "generator",
|
|
530
|
+
id: TIMESTAMP_NOW_GENERATOR_ID
|
|
531
|
+
},
|
|
532
|
+
onUpdate: {
|
|
533
|
+
kind: "generator",
|
|
534
|
+
id: TIMESTAMP_NOW_GENERATOR_ID
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
} else if (defaultAttr) {
|
|
538
|
+
const raw = findPositionalArg(defaultAttr.args);
|
|
539
|
+
if (raw === void 0) {
|
|
540
|
+
diagnostics.push({
|
|
541
|
+
code: "IDB_INVALID_DEFAULT_VALUE",
|
|
542
|
+
message: `Field "${model.name}.${field.name}" @default(...) requires a positional argument.`,
|
|
543
|
+
sourceId,
|
|
544
|
+
span: defaultAttr.span
|
|
545
|
+
});
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
const literal = parseDefaultLiteralValue(raw);
|
|
549
|
+
if (literal) {
|
|
550
|
+
if (!literalValueMatchesCodec(literal.value, codecId)) {
|
|
551
|
+
diagnostics.push({
|
|
552
|
+
code: "IDB_INVALID_DEFAULT_VALUE",
|
|
553
|
+
message: `Field "${model.name}.${field.name}" has type "${field.typeName}" but @default(${raw}) is a ${typeof literal.value} literal.`,
|
|
554
|
+
sourceId,
|
|
555
|
+
span: defaultAttr.span
|
|
556
|
+
});
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
fieldExecutionDefaults.push({
|
|
560
|
+
fieldName: field.name,
|
|
561
|
+
onCreate: {
|
|
562
|
+
kind: "generator",
|
|
563
|
+
id: LITERAL_GENERATOR_ID,
|
|
564
|
+
params: { value: literal.value }
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
fieldDefaults[field.name] = literal.value;
|
|
568
|
+
} else {
|
|
569
|
+
const call = parseDefaultFunctionCall(raw);
|
|
570
|
+
if (!call) {
|
|
571
|
+
diagnostics.push({
|
|
572
|
+
code: "IDB_INVALID_DEFAULT_VALUE",
|
|
573
|
+
message: `Field "${model.name}.${field.name}" has unsupported default value "${raw}".`,
|
|
574
|
+
sourceId,
|
|
575
|
+
span: defaultAttr.span
|
|
576
|
+
});
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (call.name === "autoincrement") {
|
|
580
|
+
if (field.name !== idFieldName) {
|
|
581
|
+
diagnostics.push({
|
|
582
|
+
code: "IDB_AUTOINCREMENT_NOT_ON_KEY_FIELD",
|
|
583
|
+
message: `Field "${model.name}.${field.name}" uses @default(autoincrement()) but is not the model's @id key. IndexedDB only generates keys for the primary key field.`,
|
|
584
|
+
sourceId,
|
|
585
|
+
span: defaultAttr.span
|
|
586
|
+
});
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (codecId !== SCALAR_TO_CODEC_ID["Int"]) {
|
|
590
|
+
diagnostics.push({
|
|
591
|
+
code: "IDB_AUTOINCREMENT_NOT_INT",
|
|
592
|
+
message: `Field "${model.name}.${field.name}" uses @default(autoincrement()) but has type "${field.typeName}", not Int. IndexedDB key generators only produce numbers.`,
|
|
593
|
+
sourceId,
|
|
594
|
+
span: defaultAttr.span
|
|
595
|
+
});
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
autoIncrement = true;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (field.optional) {
|
|
602
|
+
diagnostics.push({
|
|
603
|
+
code: "IDB_EXECUTION_DEFAULT_ON_OPTIONAL_FIELD",
|
|
604
|
+
message: `Field "${model.name}.${field.name}" cannot be both optional and @default(${call.name}(...)) — an omitted value would be ambiguous between "generate one" and "store null". Remove "?" or the default.`,
|
|
605
|
+
sourceId,
|
|
606
|
+
span: defaultAttr.span
|
|
607
|
+
});
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const resolution = resolveDefaultFunctionGeneratorId(call.name, call.arg);
|
|
611
|
+
if (!resolution.ok) {
|
|
612
|
+
diagnostics.push({
|
|
613
|
+
code: resolution.reason === "unknown-function" ? "IDB_UNKNOWN_DEFAULT_FUNCTION" : "IDB_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
614
|
+
message: resolution.reason === "unknown-function" ? `Field "${model.name}.${field.name}" uses unsupported default function "${call.name}(...)". Supported: now(), uuid(), uuid(7), cuid(), autoincrement().` : `Field "${model.name}.${field.name}" — @default(${call.name}(${call.arg ?? ""})) has an unsupported argument.`,
|
|
615
|
+
sourceId,
|
|
616
|
+
span: defaultAttr.span
|
|
617
|
+
});
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
fieldExecutionDefaults.push({
|
|
621
|
+
fieldName: field.name,
|
|
622
|
+
onCreate: {
|
|
623
|
+
kind: "generator",
|
|
624
|
+
id: resolution.generatorId
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (hasFieldAttribute(field, "unique")) {
|
|
630
|
+
if (!Object.entries(indexes).find(([, idx]) => idx.keyPath === field.name)?.[0]) indexes[`${field.name}_unique`] = {
|
|
631
|
+
keyPath: field.name,
|
|
632
|
+
unique: true
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const scalarCodecOf = (fieldName) => {
|
|
637
|
+
const type = contractFields[fieldName]?.type;
|
|
638
|
+
return type?.kind === "scalar" ? type.codecId : void 0;
|
|
639
|
+
};
|
|
640
|
+
const keyFieldCodec = scalarCodecOf(keyPath);
|
|
641
|
+
if (keyFieldCodec !== void 0 && !isValidIdbKeyCodec(keyFieldCodec)) diagnostics.push({
|
|
642
|
+
code: "IDB_INVALID_KEY_TYPE",
|
|
643
|
+
message: `Model "${model.name}" @id field "${keyPath}" has type "${keyFieldCodec}", which IndexedDB cannot use as a key. Every write would throw (DataError extracting the primary key). Use String, Int, Float, DateTime, Decimal, or Bytes instead.`,
|
|
644
|
+
sourceId,
|
|
645
|
+
span: modelFields.find((f) => f.name === keyPath)?.span ?? model.span
|
|
646
|
+
});
|
|
647
|
+
for (const [indexName, idx] of Object.entries(indexes)) {
|
|
648
|
+
const fieldCodec = scalarCodecOf(idx.keyPath);
|
|
649
|
+
if (fieldCodec === void 0 || isValidIdbKeyCodec(fieldCodec)) continue;
|
|
650
|
+
diagnostics.push({
|
|
651
|
+
code: "IDB_INVALID_INDEX_KEY_TYPE",
|
|
652
|
+
message: `Model "${model.name}" index "${indexName}" is keyed on "${idx.keyPath}" (type "${fieldCodec}"), which IndexedDB cannot use as an index key. Records are silently omitted from the index on write, and any query against it throws at runtime. Use String, Int, Float, DateTime, Decimal, or Bytes instead.`,
|
|
653
|
+
sourceId,
|
|
654
|
+
span: modelFields.find((f) => f.name === idx.keyPath)?.span ?? model.span
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
modelName: model.name,
|
|
659
|
+
storeName,
|
|
660
|
+
keyPath,
|
|
661
|
+
indexes,
|
|
662
|
+
fields: contractFields,
|
|
663
|
+
relations,
|
|
664
|
+
relationsStorage,
|
|
665
|
+
fieldDefaults,
|
|
666
|
+
fksByTarget,
|
|
667
|
+
fieldExecutionDefaults,
|
|
668
|
+
autoIncrement
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
function interpretPslDocumentToIdbContract(table, sourceId, options) {
|
|
672
|
+
const projection = options?.projection ?? "full";
|
|
673
|
+
const diagnostics = [];
|
|
674
|
+
const explicitNamespaces = Object.values(table.topLevel.namespaces);
|
|
675
|
+
for (const ns of explicitNamespaces) diagnostics.push({
|
|
676
|
+
code: "IDB_UNSUPPORTED_NAMESPACE_BLOCK",
|
|
677
|
+
message: `IDB does not support \`namespace ${ns.name} { … }\` blocks. All models must be declared at the top level.`,
|
|
678
|
+
sourceId,
|
|
679
|
+
span: ns.span
|
|
680
|
+
});
|
|
681
|
+
const allModelsUnprojected = [...Object.values(table.topLevel.models), ...explicitNamespaces.flatMap((ns) => Object.values(ns.models))];
|
|
682
|
+
const modelNames = new Set(allModelsUnprojected.map((m) => m.name));
|
|
683
|
+
const excludedModelNames = new Set(projection === "client" ? allModelsUnprojected.filter((m) => hasModelAttribute(m, IDB_EXCLUDE_ATTR)).map((m) => m.name) : []);
|
|
684
|
+
const allModels = projection === "client" ? allModelsUnprojected.filter((m) => !excludedModelNames.has(m.name)) : allModelsUnprojected;
|
|
685
|
+
const excludedFieldNamesByModel = new Map(projection === "client" ? allModelsUnprojected.map((m) => [m.name, new Set(Object.values(m.fields).filter((f) => hasFieldAttribute(f, IDB_EXCLUDE_ATTR)).map((f) => f.name))]) : []);
|
|
686
|
+
const interpretedByName = /* @__PURE__ */ new Map();
|
|
687
|
+
for (const model of allModels) {
|
|
688
|
+
const result = interpretModel(model, modelNames, sourceId, diagnostics, projection, excludedModelNames, excludedFieldNamesByModel);
|
|
689
|
+
if (result) interpretedByName.set(model.name, result);
|
|
690
|
+
}
|
|
691
|
+
for (const model of allModels) {
|
|
692
|
+
const interp = interpretedByName.get(model.name);
|
|
693
|
+
if (!interp) continue;
|
|
694
|
+
for (const field of Object.values(model.fields)) {
|
|
695
|
+
if (!field.list || !modelNames.has(field.typeName)) continue;
|
|
696
|
+
if (excludedModelNames.has(field.typeName)) continue;
|
|
697
|
+
const targetInterp = interpretedByName.get(field.typeName);
|
|
698
|
+
if (!targetInterp) continue;
|
|
699
|
+
const fk = targetInterp.fksByTarget.get(model.name);
|
|
700
|
+
if (!fk) {
|
|
701
|
+
diagnostics.push({
|
|
702
|
+
code: "IDB_UNRESOLVED_BACKRELATION",
|
|
703
|
+
message: `Backrelation field "${model.name}.${field.name}" (list of ${field.typeName}) has no matching @relation in "${field.typeName}" pointing to "${model.name}". Add @relation(fields: [...], references: [...]) to the FK field in "${field.typeName}".`,
|
|
704
|
+
sourceId,
|
|
705
|
+
span: field.span
|
|
706
|
+
});
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
const mutableInterp = interp;
|
|
710
|
+
mutableInterp.relations[field.name] = {
|
|
711
|
+
to: crossRef(field.typeName),
|
|
712
|
+
cardinality: "1:N",
|
|
713
|
+
on: {
|
|
714
|
+
localFields: fk.targetFields,
|
|
715
|
+
targetFields: fk.localFields
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (diagnostics.length > 0) return notOk({
|
|
721
|
+
summary: "PSL to IDB contract interpretation failed",
|
|
722
|
+
diagnostics
|
|
723
|
+
});
|
|
724
|
+
const ns = UNBOUND_DOMAIN_NAMESPACE_ID;
|
|
725
|
+
const stores = {};
|
|
726
|
+
const roots = {};
|
|
727
|
+
const domainModels = {};
|
|
728
|
+
const executionDefaults = [];
|
|
729
|
+
for (const [modelName, interp] of interpretedByName) {
|
|
730
|
+
stores[interp.storeName] = {
|
|
731
|
+
keyPath: interp.keyPath,
|
|
732
|
+
...interp.autoIncrement ? { autoIncrement: true } : {},
|
|
733
|
+
...Object.keys(interp.indexes).length > 0 ? { indexes: interp.indexes } : {}
|
|
734
|
+
};
|
|
735
|
+
roots[interp.storeName] = crossRef(modelName);
|
|
736
|
+
for (const fd of interp.fieldExecutionDefaults) executionDefaults.push({
|
|
737
|
+
ref: {
|
|
738
|
+
namespace: ns,
|
|
739
|
+
table: interp.storeName,
|
|
740
|
+
column: fd.fieldName
|
|
741
|
+
},
|
|
742
|
+
...fd.onCreate ? { onCreate: fd.onCreate } : {},
|
|
743
|
+
...fd.onUpdate ? { onUpdate: fd.onUpdate } : {}
|
|
744
|
+
});
|
|
745
|
+
const modelStorage = {
|
|
746
|
+
storeName: interp.storeName,
|
|
747
|
+
keyPath: interp.keyPath,
|
|
748
|
+
...Object.keys(interp.relationsStorage).length > 0 ? { relations: interp.relationsStorage } : {},
|
|
749
|
+
...Object.keys(interp.fieldDefaults).length > 0 ? { fieldDefaults: interp.fieldDefaults } : {}
|
|
750
|
+
};
|
|
751
|
+
domainModels[modelName] = {
|
|
752
|
+
fields: interp.fields,
|
|
753
|
+
relations: interp.relations,
|
|
754
|
+
storage: modelStorage
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
const storageBlock = {
|
|
758
|
+
stores,
|
|
759
|
+
namespaces: { [ns]: {
|
|
760
|
+
id: ns,
|
|
761
|
+
entries: {}
|
|
762
|
+
} }
|
|
763
|
+
};
|
|
764
|
+
const capabilities = { idb: {
|
|
765
|
+
ddlOnlyInUpgrade: true,
|
|
766
|
+
transactionalDDL: true
|
|
767
|
+
} };
|
|
768
|
+
const storageHash = computeStorageHash({
|
|
769
|
+
target: "idb",
|
|
770
|
+
targetFamily: "idb",
|
|
771
|
+
storage: storageBlock
|
|
772
|
+
});
|
|
773
|
+
const profileHash = computeProfileHash({
|
|
774
|
+
target: "idb",
|
|
775
|
+
targetFamily: "idb",
|
|
776
|
+
capabilities
|
|
777
|
+
});
|
|
778
|
+
const storage = {
|
|
779
|
+
...storageBlock,
|
|
780
|
+
storageHash
|
|
781
|
+
};
|
|
782
|
+
const domain = { namespaces: { [ns]: { models: domainModels } } };
|
|
783
|
+
const execution = executionDefaults.length > 0 ? {
|
|
784
|
+
executionHash: computeExecutionHash({
|
|
785
|
+
target: "idb",
|
|
786
|
+
targetFamily: "idb",
|
|
787
|
+
execution: { mutations: { defaults: executionDefaults } }
|
|
788
|
+
}),
|
|
789
|
+
mutations: { defaults: executionDefaults }
|
|
790
|
+
} : void 0;
|
|
791
|
+
const contract = {
|
|
792
|
+
target: "idb",
|
|
793
|
+
targetFamily: "idb",
|
|
794
|
+
roots,
|
|
795
|
+
domain,
|
|
796
|
+
storage,
|
|
797
|
+
capabilities,
|
|
798
|
+
extensions: {},
|
|
799
|
+
meta: {},
|
|
800
|
+
profileHash,
|
|
801
|
+
...execution ? { execution } : {}
|
|
802
|
+
};
|
|
803
|
+
validateContract(contract);
|
|
804
|
+
return ok(contract);
|
|
805
|
+
}
|
|
806
|
+
//#endregion
|
|
807
|
+
export { warnDroppedRelation as i, isValidIdbKeyCodec as n, literalValueMatchesCodec as r, interpretPslDocumentToIdbContract as t };
|
|
808
|
+
|
|
809
|
+
//# sourceMappingURL=psl-interpreter-C6Q4okuO.mjs.map
|