@prisma-next-idb/family-idb 0.1.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/dist/bin/prisma-next-idb.d.mts +1 -0
- package/dist/bin/prisma-next-idb.mjs +281 -0
- package/dist/bin/prisma-next-idb.mjs.map +1 -0
- package/dist/exports/config-types.d.mts +39 -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 +48 -0
- package/dist/exports/contract-psl.d.mts.map +1 -0
- package/dist/exports/contract-psl.mjs +463 -0
- package/dist/exports/contract-psl.mjs.map +1 -0
- package/dist/exports/contract-ts.d.mts +73 -0
- package/dist/exports/contract-ts.d.mts.map +1 -0
- package/dist/exports/contract-ts.mjs +162 -0
- package/dist/exports/contract-ts.mjs.map +1 -0
- package/dist/exports/control.d.mts +79 -0
- package/dist/exports/control.d.mts.map +1 -0
- package/dist/exports/control.mjs +566 -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/generate-baseline-Dg3vfBpB.mjs +130 -0
- package/dist/generate-baseline-Dg3vfBpB.mjs.map +1 -0
- package/dist/generate-migration-D8bDx9jo.mjs +150 -0
- package/dist/generate-migration-D8bDx9jo.mjs.map +1 -0
- package/dist/preflight-D8GLQXy3.mjs +112 -0
- package/dist/preflight-D8GLQXy3.mjs.map +1 -0
- package/dist/validate-DL9NthnR.mjs +48 -0
- package/dist/validate-DL9NthnR.mjs.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { t as validateContract } from "../validate-DL9NthnR.mjs";
|
|
2
|
+
import { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from "@prisma-next/contract/types";
|
|
3
|
+
import { computeProfileHash, computeStorageHash } from "@prisma-next/contract/hashing";
|
|
4
|
+
import { flatPslModels } from "@prisma-next/framework-components/psl-ast";
|
|
5
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { parsePslDocument } from "@prisma-next/psl-parser";
|
|
8
|
+
import { basename, extname } from "pathe";
|
|
9
|
+
//#region src/core/psl-interpreter.ts
|
|
10
|
+
const SCALAR_TO_CODEC_ID = {
|
|
11
|
+
String: "idb/string@1",
|
|
12
|
+
Int: "idb/int32@1",
|
|
13
|
+
Float: "idb/double@1",
|
|
14
|
+
Boolean: "idb/bool@1",
|
|
15
|
+
DateTime: "idb/date@1",
|
|
16
|
+
BigInt: "idb/bigint@1",
|
|
17
|
+
Decimal: "idb/decimal@1",
|
|
18
|
+
Json: "idb/json@1",
|
|
19
|
+
Bytes: "idb/bytes@1"
|
|
20
|
+
};
|
|
21
|
+
const REFERENTIAL_ACTION_MAP = {
|
|
22
|
+
Cascade: "cascade",
|
|
23
|
+
SetNull: "setNull",
|
|
24
|
+
SetDefault: "setDefault",
|
|
25
|
+
Restrict: "restrict",
|
|
26
|
+
NoAction: "noAction"
|
|
27
|
+
};
|
|
28
|
+
function findPositionalArg(args) {
|
|
29
|
+
return args.find((a) => a.kind === "positional")?.value;
|
|
30
|
+
}
|
|
31
|
+
function findNamedArg(args, name) {
|
|
32
|
+
return args.find((a) => a.kind === "named" && a.name === name)?.value;
|
|
33
|
+
}
|
|
34
|
+
function parseStringArg(raw) {
|
|
35
|
+
if (raw === void 0) return void 0;
|
|
36
|
+
const t = raw.trim();
|
|
37
|
+
if (t.startsWith("\"") && t.endsWith("\"") || t.startsWith("'") && t.endsWith("'")) return t.slice(1, -1);
|
|
38
|
+
}
|
|
39
|
+
function parseFieldList(raw) {
|
|
40
|
+
if (raw === void 0) return void 0;
|
|
41
|
+
const t = raw.trim();
|
|
42
|
+
if (!t.startsWith("[") || !t.endsWith("]")) return void 0;
|
|
43
|
+
const inner = t.slice(1, -1).trim();
|
|
44
|
+
if (inner === "") return [];
|
|
45
|
+
return inner.split(",").map((s) => s.trim()).filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
function lowerFirst(s) {
|
|
48
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
49
|
+
}
|
|
50
|
+
function hasFieldAttribute(field, name) {
|
|
51
|
+
return field.attributes.some((a) => a.name === name);
|
|
52
|
+
}
|
|
53
|
+
function getFieldAttribute(field, name) {
|
|
54
|
+
return field.attributes.find((a) => a.name === name);
|
|
55
|
+
}
|
|
56
|
+
function interpretModel(model, modelNames, sourceId, diagnostics) {
|
|
57
|
+
const storeName = parseStringArg(findPositionalArg(model.attributes.find((a) => a.name === "map")?.args ?? [])) ?? lowerFirst(model.name);
|
|
58
|
+
let keyPath;
|
|
59
|
+
let idFieldName;
|
|
60
|
+
const idModelAttr = model.attributes.find((a) => a.name === "id");
|
|
61
|
+
if (idModelAttr) {
|
|
62
|
+
const fields = parseFieldList(findPositionalArg(idModelAttr.args));
|
|
63
|
+
if (!fields || fields.length === 0) {
|
|
64
|
+
diagnostics.push({
|
|
65
|
+
code: "IDB_INVALID_ID",
|
|
66
|
+
message: `Model "${model.name}" @@id([…]) is missing a field list.`,
|
|
67
|
+
sourceId,
|
|
68
|
+
span: idModelAttr.span
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (fields.length > 1) {
|
|
73
|
+
diagnostics.push({
|
|
74
|
+
code: "IDB_NO_COMPOUND_KEY",
|
|
75
|
+
message: `Model "${model.name}" @@id([${fields.join(", ")}]) declares a compound key. IDB does not support compound primary keys — use a single @id field instead.`,
|
|
76
|
+
sourceId,
|
|
77
|
+
span: idModelAttr.span
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
idFieldName = fields[0];
|
|
82
|
+
keyPath = fields[0];
|
|
83
|
+
}
|
|
84
|
+
const idFields = model.fields.filter((f) => hasFieldAttribute(f, "id"));
|
|
85
|
+
if (idFields.length > 1) {
|
|
86
|
+
diagnostics.push({
|
|
87
|
+
code: "IDB_MULTIPLE_ID_FIELDS",
|
|
88
|
+
message: `Model "${model.name}" declares @id on multiple fields (${idFields.map((f) => f.name).join(", ")}). Only one @id field is allowed.`,
|
|
89
|
+
sourceId,
|
|
90
|
+
span: model.span
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (idFields.length === 1) {
|
|
95
|
+
if (keyPath !== void 0) {
|
|
96
|
+
diagnostics.push({
|
|
97
|
+
code: "IDB_INVALID_ID",
|
|
98
|
+
message: `Model "${model.name}" cannot declare both a field-level @id and a model-level @@id.`,
|
|
99
|
+
sourceId,
|
|
100
|
+
span: model.span
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
idFieldName = idFields[0].name;
|
|
105
|
+
keyPath = idFields[0].name;
|
|
106
|
+
}
|
|
107
|
+
if (keyPath === void 0) {
|
|
108
|
+
diagnostics.push({
|
|
109
|
+
code: "IDB_MISSING_ID",
|
|
110
|
+
message: `Model "${model.name}" has no @id field. Add @id to exactly one scalar field.`,
|
|
111
|
+
sourceId,
|
|
112
|
+
span: model.span
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const indexes = {};
|
|
117
|
+
for (const attr of model.attributes) {
|
|
118
|
+
if (attr.name !== "index" && attr.name !== "unique") continue;
|
|
119
|
+
const isUnique = attr.name === "unique";
|
|
120
|
+
const fields = parseFieldList(findPositionalArg(attr.args));
|
|
121
|
+
if (!fields || fields.length === 0) {
|
|
122
|
+
diagnostics.push({
|
|
123
|
+
code: "IDB_INVALID_INDEX",
|
|
124
|
+
message: `Model "${model.name}" @@${attr.name} is missing a field list.`,
|
|
125
|
+
sourceId,
|
|
126
|
+
span: attr.span
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (fields.length > 1) {
|
|
131
|
+
diagnostics.push({
|
|
132
|
+
code: "IDB_COMPOUND_INDEX_UNSUPPORTED",
|
|
133
|
+
message: `Model "${model.name}" @@${attr.name}([${fields.join(", ")}]) declares a compound index. IDB compound indexes are not yet supported — use a single-field index.`,
|
|
134
|
+
sourceId,
|
|
135
|
+
span: attr.span
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const field = fields[0];
|
|
140
|
+
const indexName = parseStringArg(findNamedArg(attr.args, "name") ?? findNamedArg(attr.args, "map")) ?? (isUnique ? `${field}_unique` : field);
|
|
141
|
+
indexes[indexName] = {
|
|
142
|
+
keyPath: field,
|
|
143
|
+
unique: isUnique
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const contractFields = {};
|
|
147
|
+
const relations = {};
|
|
148
|
+
const relationsStorage = {};
|
|
149
|
+
const fksByTarget = /* @__PURE__ */ new Map();
|
|
150
|
+
for (const field of model.fields) {
|
|
151
|
+
if (field.name === idFieldName && field.optional) diagnostics.push({
|
|
152
|
+
code: "IDB_NULLABLE_ID",
|
|
153
|
+
message: `Field "${model.name}.${field.name}" is marked as @id but is optional (?). The primary key cannot be nullable.`,
|
|
154
|
+
sourceId,
|
|
155
|
+
span: field.span
|
|
156
|
+
});
|
|
157
|
+
if (field.list && modelNames.has(field.typeName)) continue;
|
|
158
|
+
const relationAttr = getFieldAttribute(field, "relation");
|
|
159
|
+
if (!field.list && modelNames.has(field.typeName) && relationAttr) {
|
|
160
|
+
const args = relationAttr.args;
|
|
161
|
+
const localFieldsRaw = findNamedArg(args, "fields");
|
|
162
|
+
const targetFieldsRaw = findNamedArg(args, "references");
|
|
163
|
+
const localFields = parseFieldList(localFieldsRaw);
|
|
164
|
+
const targetFields = parseFieldList(targetFieldsRaw);
|
|
165
|
+
if (!localFields || localFields.length === 0 || !targetFields || targetFields.length === 0) {
|
|
166
|
+
diagnostics.push({
|
|
167
|
+
code: "IDB_INVALID_RELATION",
|
|
168
|
+
message: `Relation field "${model.name}.${field.name}" must declare both fields and references in @relation.`,
|
|
169
|
+
sourceId,
|
|
170
|
+
span: field.span
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (localFields.length !== targetFields.length) {
|
|
175
|
+
diagnostics.push({
|
|
176
|
+
code: "IDB_INVALID_RELATION",
|
|
177
|
+
message: `Relation field "${model.name}.${field.name}" must have the same number of fields and references.`,
|
|
178
|
+
sourceId,
|
|
179
|
+
span: field.span
|
|
180
|
+
});
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const onDeleteRaw = findNamedArg(args, "onDelete");
|
|
184
|
+
const onDelete = onDeleteRaw ? REFERENTIAL_ACTION_MAP[onDeleteRaw.trim()] : void 0;
|
|
185
|
+
if (onDeleteRaw && onDelete === void 0) diagnostics.push({
|
|
186
|
+
code: "IDB_UNKNOWN_REFERENTIAL_ACTION",
|
|
187
|
+
message: `Relation field "${model.name}.${field.name}" has unknown onDelete value "${onDeleteRaw}". Valid values: ${Object.keys(REFERENTIAL_ACTION_MAP).join(", ")}.`,
|
|
188
|
+
sourceId,
|
|
189
|
+
span: field.span
|
|
190
|
+
});
|
|
191
|
+
relations[field.name] = {
|
|
192
|
+
to: crossRef(field.typeName),
|
|
193
|
+
cardinality: "N:1",
|
|
194
|
+
on: {
|
|
195
|
+
localFields,
|
|
196
|
+
targetFields
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
if (onDelete !== void 0) relationsStorage[field.name] = { onDelete };
|
|
200
|
+
fksByTarget.set(field.typeName, {
|
|
201
|
+
fieldName: field.name,
|
|
202
|
+
localFields,
|
|
203
|
+
targetFields
|
|
204
|
+
});
|
|
205
|
+
for (const fkField of localFields) if (!(fkField in indexes)) indexes[fkField] = {
|
|
206
|
+
keyPath: fkField,
|
|
207
|
+
unique: false
|
|
208
|
+
};
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!field.list && modelNames.has(field.typeName) && !relationAttr) {
|
|
212
|
+
diagnostics.push({
|
|
213
|
+
code: "IDB_MISSING_RELATION_ATTRIBUTE",
|
|
214
|
+
message: `Field "${model.name}.${field.name}" has model type "${field.typeName}" but no @relation attribute. Add @relation(fields: [...], references: [...]).`,
|
|
215
|
+
sourceId,
|
|
216
|
+
span: field.span
|
|
217
|
+
});
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (field.list) continue;
|
|
221
|
+
const codecId = SCALAR_TO_CODEC_ID[field.typeName];
|
|
222
|
+
if (codecId === void 0) {
|
|
223
|
+
diagnostics.push({
|
|
224
|
+
code: "IDB_UNSUPPORTED_FIELD_TYPE",
|
|
225
|
+
message: `Field "${model.name}.${field.name}" has unsupported type "${field.typeName}". Supported types: ${Object.keys(SCALAR_TO_CODEC_ID).join(", ")}.`,
|
|
226
|
+
sourceId,
|
|
227
|
+
span: field.span
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
contractFields[field.name] = {
|
|
232
|
+
nullable: field.optional,
|
|
233
|
+
type: {
|
|
234
|
+
kind: "scalar",
|
|
235
|
+
codecId
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
if (hasFieldAttribute(field, "unique")) {
|
|
239
|
+
if (!Object.entries(indexes).find(([, idx]) => idx.keyPath === field.name)?.[0]) indexes[`${field.name}_unique`] = {
|
|
240
|
+
keyPath: field.name,
|
|
241
|
+
unique: true
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
modelName: model.name,
|
|
247
|
+
storeName,
|
|
248
|
+
keyPath,
|
|
249
|
+
indexes,
|
|
250
|
+
fields: contractFields,
|
|
251
|
+
relations,
|
|
252
|
+
relationsStorage,
|
|
253
|
+
fksByTarget
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Interprets a parsed PSL document AST and produces an IDB `Contract`.
|
|
258
|
+
*
|
|
259
|
+
* This is the IDB equivalent of `interpretPslDocumentToSqlContract` from the
|
|
260
|
+
* SQL family. It handles IDB-specific constraints:
|
|
261
|
+
*
|
|
262
|
+
* - No namespace blocks (IDB has a single implicit `__unbound__` namespace)
|
|
263
|
+
* - No compound primary keys (IDB `keyPath` must be a single field)
|
|
264
|
+
* - Relations are FK-side (`@relation`) + backrelation list fields
|
|
265
|
+
* - Indexes map directly to `IDBObjectStore.createIndex()` calls
|
|
266
|
+
*/
|
|
267
|
+
function interpretPslDocumentToIdbContract(ast, sourceId) {
|
|
268
|
+
const diagnostics = [];
|
|
269
|
+
const explicitNamespaces = ast.namespaces.filter((ns) => ns.name !== "__unspecified__");
|
|
270
|
+
for (const ns of explicitNamespaces) diagnostics.push({
|
|
271
|
+
code: "IDB_UNSUPPORTED_NAMESPACE_BLOCK",
|
|
272
|
+
message: `IDB does not support \`namespace ${ns.name} { … }\` blocks. All models must be declared at the top level.`,
|
|
273
|
+
sourceId,
|
|
274
|
+
span: ns.span
|
|
275
|
+
});
|
|
276
|
+
const allModels = flatPslModels(ast);
|
|
277
|
+
const modelNames = new Set(allModels.map((m) => m.name));
|
|
278
|
+
const interpretedByName = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const model of allModels) {
|
|
280
|
+
const result = interpretModel(model, modelNames, sourceId, diagnostics);
|
|
281
|
+
if (result) interpretedByName.set(model.name, result);
|
|
282
|
+
}
|
|
283
|
+
for (const model of allModels) {
|
|
284
|
+
const interp = interpretedByName.get(model.name);
|
|
285
|
+
if (!interp) continue;
|
|
286
|
+
for (const field of model.fields) {
|
|
287
|
+
if (!field.list || !modelNames.has(field.typeName)) continue;
|
|
288
|
+
const targetInterp = interpretedByName.get(field.typeName);
|
|
289
|
+
if (!targetInterp) continue;
|
|
290
|
+
const fk = targetInterp.fksByTarget.get(model.name);
|
|
291
|
+
if (!fk) {
|
|
292
|
+
diagnostics.push({
|
|
293
|
+
code: "IDB_UNRESOLVED_BACKRELATION",
|
|
294
|
+
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}".`,
|
|
295
|
+
sourceId,
|
|
296
|
+
span: field.span
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const mutableInterp = interp;
|
|
301
|
+
mutableInterp.relations[field.name] = {
|
|
302
|
+
to: crossRef(field.typeName),
|
|
303
|
+
cardinality: "1:N",
|
|
304
|
+
on: {
|
|
305
|
+
localFields: fk.targetFields,
|
|
306
|
+
targetFields: fk.localFields
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (diagnostics.length > 0) return notOk({
|
|
312
|
+
summary: "PSL to IDB contract interpretation failed",
|
|
313
|
+
diagnostics
|
|
314
|
+
});
|
|
315
|
+
const ns = UNBOUND_DOMAIN_NAMESPACE_ID;
|
|
316
|
+
const stores = {};
|
|
317
|
+
const roots = {};
|
|
318
|
+
const domainModels = {};
|
|
319
|
+
for (const [modelName, interp] of interpretedByName) {
|
|
320
|
+
stores[interp.storeName] = {
|
|
321
|
+
keyPath: interp.keyPath,
|
|
322
|
+
...Object.keys(interp.indexes).length > 0 ? { indexes: interp.indexes } : {}
|
|
323
|
+
};
|
|
324
|
+
roots[interp.storeName] = crossRef(modelName);
|
|
325
|
+
const modelStorage = Object.keys(interp.relationsStorage).length > 0 ? {
|
|
326
|
+
storeName: interp.storeName,
|
|
327
|
+
keyPath: interp.keyPath,
|
|
328
|
+
relations: interp.relationsStorage
|
|
329
|
+
} : {
|
|
330
|
+
storeName: interp.storeName,
|
|
331
|
+
keyPath: interp.keyPath
|
|
332
|
+
};
|
|
333
|
+
domainModels[modelName] = {
|
|
334
|
+
fields: interp.fields,
|
|
335
|
+
relations: interp.relations,
|
|
336
|
+
storage: modelStorage
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const storageBlock = {
|
|
340
|
+
stores,
|
|
341
|
+
namespaces: { [ns]: {
|
|
342
|
+
id: ns,
|
|
343
|
+
entries: {}
|
|
344
|
+
} }
|
|
345
|
+
};
|
|
346
|
+
const capabilities = { idb: {
|
|
347
|
+
ddlOnlyInUpgrade: true,
|
|
348
|
+
transactionalDDL: true
|
|
349
|
+
} };
|
|
350
|
+
const storageHash = computeStorageHash({
|
|
351
|
+
target: "idb",
|
|
352
|
+
targetFamily: "idb",
|
|
353
|
+
storage: storageBlock
|
|
354
|
+
});
|
|
355
|
+
const profileHash = computeProfileHash({
|
|
356
|
+
target: "idb",
|
|
357
|
+
targetFamily: "idb",
|
|
358
|
+
capabilities
|
|
359
|
+
});
|
|
360
|
+
const storage = {
|
|
361
|
+
...storageBlock,
|
|
362
|
+
storageHash
|
|
363
|
+
};
|
|
364
|
+
const contract = {
|
|
365
|
+
target: "idb",
|
|
366
|
+
targetFamily: "idb",
|
|
367
|
+
roots,
|
|
368
|
+
domain: { namespaces: { [ns]: { models: domainModels } } },
|
|
369
|
+
storage,
|
|
370
|
+
capabilities,
|
|
371
|
+
extensionPacks: {},
|
|
372
|
+
meta: {},
|
|
373
|
+
profileHash
|
|
374
|
+
};
|
|
375
|
+
validateContract(contract);
|
|
376
|
+
return ok(contract);
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/core/psl-provider.ts
|
|
380
|
+
function defaultOutputFromSchemaPath(schemaPath) {
|
|
381
|
+
const ext = extname(schemaPath);
|
|
382
|
+
if (ext.length === 0) return `${schemaPath}.json`;
|
|
383
|
+
const base = schemaPath.slice(0, -ext.length);
|
|
384
|
+
if (basename(base) === "schema") return `${base.slice(0, -6)}contract.json`;
|
|
385
|
+
return `${base}.json`;
|
|
386
|
+
}
|
|
387
|
+
function mapPslDiagnostics(diagnostics, sourceId) {
|
|
388
|
+
return diagnostics.map((d) => ({
|
|
389
|
+
code: d.code,
|
|
390
|
+
message: d.message,
|
|
391
|
+
sourceId,
|
|
392
|
+
...d.span !== void 0 ? { span: d.span } : {}
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Creates a `ContractConfig` that reads an IDB schema from a `.prisma` file.
|
|
397
|
+
*
|
|
398
|
+
* Use this in `prisma-next.config.ts` as the `contract:` value when you prefer
|
|
399
|
+
* PSL authoring over the TypeScript-first `defineContract()` helper.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* import { defineConfig } from '@prisma-next-idb/family-idb/config-types';
|
|
404
|
+
* import { prismaIdbContract } from '@prisma-next-idb/family-idb/contract-psl';
|
|
405
|
+
*
|
|
406
|
+
* export default defineConfig({
|
|
407
|
+
* // ...
|
|
408
|
+
* contract: prismaIdbContract('./src/prisma/schema.prisma'),
|
|
409
|
+
* });
|
|
410
|
+
* ```
|
|
411
|
+
*
|
|
412
|
+
* The emitted `contract.json` lands next to the schema file by default
|
|
413
|
+
* (`schema.prisma` → `contract.json`). Override with `options.output`.
|
|
414
|
+
*/
|
|
415
|
+
function prismaIdbContract(schemaPath, options) {
|
|
416
|
+
return {
|
|
417
|
+
source: {
|
|
418
|
+
inputs: [schemaPath],
|
|
419
|
+
load: async (context) => {
|
|
420
|
+
const [absoluteSchemaPath] = context.resolvedInputs;
|
|
421
|
+
if (absoluteSchemaPath === void 0) throw new Error("prismaIdbContract: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.");
|
|
422
|
+
let schema;
|
|
423
|
+
try {
|
|
424
|
+
schema = await readFile(absoluteSchemaPath, "utf-8");
|
|
425
|
+
} catch (error) {
|
|
426
|
+
const message = String(error);
|
|
427
|
+
return notOk({
|
|
428
|
+
summary: `Failed to read Prisma schema at "${schemaPath}"`,
|
|
429
|
+
diagnostics: [{
|
|
430
|
+
code: "PSL_SCHEMA_READ_FAILED",
|
|
431
|
+
message,
|
|
432
|
+
sourceId: schemaPath
|
|
433
|
+
}]
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
const { ast, diagnostics: parseDiagnostics, ok: parseOk } = parsePslDocument({
|
|
437
|
+
schema,
|
|
438
|
+
sourceId: schemaPath
|
|
439
|
+
});
|
|
440
|
+
const seedDiagnostics = mapPslDiagnostics(parseDiagnostics, schemaPath);
|
|
441
|
+
if (!parseOk) return notOk({
|
|
442
|
+
summary: `Failed to parse Prisma schema at "${schemaPath}"`,
|
|
443
|
+
diagnostics: seedDiagnostics
|
|
444
|
+
});
|
|
445
|
+
const interpreted = interpretPslDocumentToIdbContract(ast, schemaPath);
|
|
446
|
+
if (!interpreted.ok) return notOk({
|
|
447
|
+
...interpreted.failure,
|
|
448
|
+
diagnostics: [...seedDiagnostics, ...interpreted.failure.diagnostics]
|
|
449
|
+
});
|
|
450
|
+
if (seedDiagnostics.length > 0) return notOk({
|
|
451
|
+
summary: `PSL parse warnings in "${schemaPath}"`,
|
|
452
|
+
diagnostics: seedDiagnostics
|
|
453
|
+
});
|
|
454
|
+
return ok(interpreted.value);
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
output: options?.output ?? defaultOutputFromSchemaPath(schemaPath)
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
461
|
+
export { interpretPslDocumentToIdbContract, prismaIdbContract };
|
|
462
|
+
|
|
463
|
+
//# sourceMappingURL=contract-psl.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-psl.mjs","names":[],"sources":["../../src/core/psl-interpreter.ts","../../src/core/psl-provider.ts"],"sourcesContent":["import type { ContractSourceDiagnostic, ContractSourceDiagnostics } from \"@prisma-next/config/config-types\";\nimport { computeProfileHash, computeStorageHash } from \"@prisma-next/contract/hashing\";\nimport type { ApplicationDomain, Contract, ContractField } from \"@prisma-next/contract/types\";\nimport { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from \"@prisma-next/contract/types\";\nimport type { PslDocumentAst, PslField, PslModel } from \"@prisma-next/framework-components/psl-ast\";\nimport { flatPslModels } from \"@prisma-next/framework-components/psl-ast\";\nimport type {\n IdbIndexDefinition,\n IdbModelStorage,\n IdbReferentialAction,\n IdbStorage,\n IdbStoreDefinition,\n} from \"@prisma-next-idb/target-idb/pack\";\nimport { notOk, ok } from \"@prisma-next/utils/result\";\nimport type { Result } from \"@prisma-next/utils/result\";\nimport { validateContract } from \"./validate\";\n\n// ── Scalar type → codec ID mapping ────────────────────────────────────────────\n\nconst SCALAR_TO_CODEC_ID: Record<string, string> = {\n String: \"idb/string@1\",\n Int: \"idb/int32@1\",\n Float: \"idb/double@1\",\n Boolean: \"idb/bool@1\",\n DateTime: \"idb/date@1\",\n BigInt: \"idb/bigint@1\",\n Decimal: \"idb/decimal@1\",\n Json: \"idb/json@1\",\n Bytes: \"idb/bytes@1\",\n};\n\n// PSL PascalCase referential actions → IDB lowercase\nconst REFERENTIAL_ACTION_MAP: Record<string, IdbReferentialAction> = {\n Cascade: \"cascade\",\n SetNull: \"setNull\",\n SetDefault: \"setDefault\",\n Restrict: \"restrict\",\n NoAction: \"noAction\",\n};\n\n// ── Attribute arg helpers ──────────────────────────────────────────────────────\n\ntype AttributeArg = { kind: string; name?: string; value: string };\n\nfunction findPositionalArg(args: readonly AttributeArg[]): string | undefined {\n return args.find((a) => a.kind === \"positional\")?.value;\n}\n\nfunction findNamedArg(args: readonly AttributeArg[], name: string): string | undefined {\n return (args.find((a) => a.kind === \"named\" && a.name === name) as AttributeArg | undefined)?.value;\n}\n\nfunction parseStringArg(raw: string | undefined): string | undefined {\n if (raw === undefined) return undefined;\n const t = raw.trim();\n if ((t.startsWith('\"') && t.endsWith('\"')) || (t.startsWith(\"'\") && t.endsWith(\"'\"))) {\n return t.slice(1, -1);\n }\n return undefined;\n}\n\nfunction parseFieldList(raw: string | undefined): string[] | undefined {\n if (raw === undefined) return undefined;\n const t = raw.trim();\n if (!t.startsWith(\"[\") || !t.endsWith(\"]\")) return undefined;\n const inner = t.slice(1, -1).trim();\n if (inner === \"\") return [];\n return inner\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\nfunction lowerFirst(s: string): string {\n return s.charAt(0).toLowerCase() + s.slice(1);\n}\n\n// ── Field helpers ──────────────────────────────────────────────────────────────\n\nfunction hasFieldAttribute(field: PslField, name: string): boolean {\n return field.attributes.some((a) => a.name === name);\n}\n\nfunction getFieldAttribute(field: PslField, name: string) {\n return field.attributes.find((a) => a.name === name);\n}\n\n// ── Per-model interpretation result ───────────────────────────────────────────\n\ninterface InterpretedModel {\n readonly modelName: string;\n readonly storeName: string;\n readonly keyPath: string;\n readonly indexes: Record<string, IdbIndexDefinition>;\n readonly fields: Record<string, ContractField>;\n readonly relations: Record<\n string,\n {\n readonly to: ReturnType<typeof crossRef>;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: { readonly localFields: readonly string[]; readonly targetFields: readonly string[] };\n }\n >;\n readonly relationsStorage: Record<string, { onDelete?: IdbReferentialAction }>;\n /** FK-side declarations keyed by targetModelName for back-relation resolution. */\n readonly fksByTarget: ReadonlyMap<string, { fieldName: string; localFields: string[]; targetFields: string[] }>;\n}\n\n// ── Core interpreter ───────────────────────────────────────────────────────────\n\nfunction interpretModel(\n model: PslModel,\n modelNames: ReadonlySet<string>,\n sourceId: string,\n diagnostics: ContractSourceDiagnostic[]\n): InterpretedModel | undefined {\n // Derive store name from @@map or lowerFirst(modelName)\n const mapAttr = model.attributes.find((a) => a.name === \"map\");\n const storeName = parseStringArg(findPositionalArg(mapAttr?.args ?? [])) ?? lowerFirst(model.name);\n\n // Find the keyPath: @id field-level attribute OR @@id([field]) model-level\n let keyPath: string | undefined;\n let idFieldName: string | undefined;\n\n const idModelAttr = model.attributes.find((a) => a.name === \"id\");\n if (idModelAttr) {\n const fields = parseFieldList(findPositionalArg(idModelAttr.args));\n if (!fields || fields.length === 0) {\n diagnostics.push({\n code: \"IDB_INVALID_ID\",\n message: `Model \"${model.name}\" @@id([…]) is missing a field list.`,\n sourceId,\n span: idModelAttr.span,\n });\n return undefined;\n }\n if (fields.length > 1) {\n diagnostics.push({\n code: \"IDB_NO_COMPOUND_KEY\",\n message: `Model \"${model.name}\" @@id([${fields.join(\", \")}]) declares a compound key. IDB does not support compound primary keys — use a single @id field instead.`,\n sourceId,\n span: idModelAttr.span,\n });\n return undefined;\n }\n idFieldName = fields[0];\n keyPath = fields[0];\n }\n\n const idFields = model.fields.filter((f) => hasFieldAttribute(f, \"id\"));\n if (idFields.length > 1) {\n diagnostics.push({\n code: \"IDB_MULTIPLE_ID_FIELDS\",\n message: `Model \"${model.name}\" declares @id on multiple fields (${idFields.map((f) => f.name).join(\", \")}). Only one @id field is allowed.`,\n sourceId,\n span: model.span,\n });\n return undefined;\n }\n if (idFields.length === 1) {\n if (keyPath !== undefined) {\n diagnostics.push({\n code: \"IDB_INVALID_ID\",\n message: `Model \"${model.name}\" cannot declare both a field-level @id and a model-level @@id.`,\n sourceId,\n span: model.span,\n });\n return undefined;\n }\n idFieldName = idFields[0]!.name;\n keyPath = idFields[0]!.name;\n }\n\n if (keyPath === undefined) {\n diagnostics.push({\n code: \"IDB_MISSING_ID\",\n message: `Model \"${model.name}\" has no @id field. Add @id to exactly one scalar field.`,\n sourceId,\n span: model.span,\n });\n return undefined;\n }\n\n // ── Build indexes ────────────────────────────────────────────────────────────\n const indexes: Record<string, IdbIndexDefinition> = {};\n\n // @@index([fields]) model attribute\n for (const attr of model.attributes) {\n if (attr.name !== \"index\" && attr.name !== \"unique\") continue;\n const isUnique = attr.name === \"unique\";\n const fieldListRaw = findPositionalArg(attr.args);\n const fields = parseFieldList(fieldListRaw);\n if (!fields || fields.length === 0) {\n diagnostics.push({\n code: \"IDB_INVALID_INDEX\",\n message: `Model \"${model.name}\" @@${attr.name} is missing a field list.`,\n sourceId,\n span: attr.span,\n });\n continue;\n }\n if (fields.length > 1) {\n diagnostics.push({\n code: \"IDB_COMPOUND_INDEX_UNSUPPORTED\",\n message: `Model \"${model.name}\" @@${attr.name}([${fields.join(\", \")}]) declares a compound index. IDB compound indexes are not yet supported — use a single-field index.`,\n sourceId,\n span: attr.span,\n });\n continue;\n }\n const field = fields[0]!;\n const nameRaw = findNamedArg(attr.args, \"name\") ?? findNamedArg(attr.args, \"map\");\n const indexName = parseStringArg(nameRaw) ?? (isUnique ? `${field}_unique` : field);\n indexes[indexName] = { keyPath: field, unique: isUnique };\n }\n\n // ── Walk fields ──────────────────────────────────────────────────────────────\n const contractFields: Record<string, ContractField> = {};\n const relations: InterpretedModel[\"relations\"] = {};\n const relationsStorage: Record<string, { onDelete?: IdbReferentialAction }> = {};\n const fksByTarget = new Map<string, { fieldName: string; localFields: string[]; targetFields: string[] }>();\n\n for (const field of model.fields) {\n // Skip the @id field's optional marker — keyPath fields cannot be nullable in IDB\n if (field.name === idFieldName && field.optional) {\n diagnostics.push({\n code: \"IDB_NULLABLE_ID\",\n message: `Field \"${model.name}.${field.name}\" is marked as @id but is optional (?). The primary key cannot be nullable.`,\n sourceId,\n span: field.span,\n });\n }\n\n // Relation list field (backrelation) — skip for now, resolved in second pass\n if (field.list && modelNames.has(field.typeName)) {\n continue;\n }\n\n // FK-side relation field: non-list, type is a model, has @relation\n const relationAttr = getFieldAttribute(field, \"relation\");\n if (!field.list && modelNames.has(field.typeName) && relationAttr) {\n const args = relationAttr.args;\n const localFieldsRaw = findNamedArg(args, \"fields\");\n const targetFieldsRaw = findNamedArg(args, \"references\");\n const localFields = parseFieldList(localFieldsRaw);\n const targetFields = parseFieldList(targetFieldsRaw);\n\n if (!localFields || localFields.length === 0 || !targetFields || targetFields.length === 0) {\n diagnostics.push({\n code: \"IDB_INVALID_RELATION\",\n message: `Relation field \"${model.name}.${field.name}\" must declare both fields and references in @relation.`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n if (localFields.length !== targetFields.length) {\n diagnostics.push({\n code: \"IDB_INVALID_RELATION\",\n message: `Relation field \"${model.name}.${field.name}\" must have the same number of fields and references.`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n const onDeleteRaw = findNamedArg(args, \"onDelete\");\n const onDelete = onDeleteRaw ? REFERENTIAL_ACTION_MAP[onDeleteRaw.trim()] : undefined;\n\n if (onDeleteRaw && onDelete === undefined) {\n diagnostics.push({\n code: \"IDB_UNKNOWN_REFERENTIAL_ACTION\",\n message: `Relation field \"${model.name}.${field.name}\" has unknown onDelete value \"${onDeleteRaw}\". Valid values: ${Object.keys(REFERENTIAL_ACTION_MAP).join(\", \")}.`,\n sourceId,\n span: field.span,\n });\n }\n\n relations[field.name] = {\n to: crossRef(field.typeName),\n cardinality: \"N:1\",\n on: { localFields, targetFields },\n };\n if (onDelete !== undefined) {\n relationsStorage[field.name] = { onDelete };\n }\n fksByTarget.set(field.typeName, {\n fieldName: field.name,\n localFields,\n targetFields,\n });\n\n // Also create a default index on the FK field(s) if not already indexed\n for (const fkField of localFields) {\n if (!(fkField in indexes)) {\n indexes[fkField] = { keyPath: fkField, unique: false };\n }\n }\n continue;\n }\n\n // Non-model relation field without @relation — not a scalar, skip with error\n if (!field.list && modelNames.has(field.typeName) && !relationAttr) {\n diagnostics.push({\n code: \"IDB_MISSING_RELATION_ATTRIBUTE\",\n message: `Field \"${model.name}.${field.name}\" has model type \"${field.typeName}\" but no @relation attribute. Add @relation(fields: [...], references: [...]).`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n // Skip list fields of non-model types (JSON arrays etc. are handled as Json codec)\n if (field.list) {\n // Only model-type lists are backrelations; non-model lists are not supported in IDB.\n continue;\n }\n\n // Scalar field\n const codecId = SCALAR_TO_CODEC_ID[field.typeName];\n if (codecId === undefined) {\n diagnostics.push({\n code: \"IDB_UNSUPPORTED_FIELD_TYPE\",\n message: `Field \"${model.name}.${field.name}\" has unsupported type \"${field.typeName}\". Supported types: ${Object.keys(SCALAR_TO_CODEC_ID).join(\", \")}.`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n contractFields[field.name] = {\n nullable: field.optional,\n type: { kind: \"scalar\", codecId },\n };\n\n // @unique field attribute → unique index\n if (hasFieldAttribute(field, \"unique\")) {\n const existingKey = Object.entries(indexes).find(([, idx]) => idx.keyPath === field.name)?.[0];\n if (!existingKey) {\n indexes[`${field.name}_unique`] = { keyPath: field.name, unique: true };\n }\n }\n }\n\n return {\n modelName: model.name,\n storeName,\n keyPath,\n indexes,\n fields: contractFields,\n relations,\n relationsStorage,\n fksByTarget,\n };\n}\n\n// ── Main export ────────────────────────────────────────────────────────────────\n\n/**\n * Interprets a parsed PSL document AST and produces an IDB `Contract`.\n *\n * This is the IDB equivalent of `interpretPslDocumentToSqlContract` from the\n * SQL family. It handles IDB-specific constraints:\n *\n * - No namespace blocks (IDB has a single implicit `__unbound__` namespace)\n * - No compound primary keys (IDB `keyPath` must be a single field)\n * - Relations are FK-side (`@relation`) + backrelation list fields\n * - Indexes map directly to `IDBObjectStore.createIndex()` calls\n */\nexport function interpretPslDocumentToIdbContract(\n ast: PslDocumentAst,\n sourceId: string\n): Result<Contract<IdbStorage>, ContractSourceDiagnostics> {\n const diagnostics: ContractSourceDiagnostic[] = [];\n\n // IDB does not support namespace blocks\n const explicitNamespaces = ast.namespaces.filter((ns) => ns.name !== \"__unspecified__\");\n for (const ns of explicitNamespaces) {\n diagnostics.push({\n code: \"IDB_UNSUPPORTED_NAMESPACE_BLOCK\",\n message: `IDB does not support \\`namespace ${ns.name} { … }\\` blocks. All models must be declared at the top level.`,\n sourceId,\n span: ns.span,\n });\n }\n\n const allModels = flatPslModels(ast);\n const modelNames = new Set(allModels.map((m) => m.name));\n const interpretedByName = new Map<string, InterpretedModel>();\n\n // First pass: interpret each model individually\n for (const model of allModels) {\n const result = interpretModel(model, modelNames, sourceId, diagnostics);\n if (result) {\n interpretedByName.set(model.name, result);\n }\n }\n\n // Second pass: resolve backrelation list fields\n for (const model of allModels) {\n const interp = interpretedByName.get(model.name);\n if (!interp) continue;\n\n for (const field of model.fields) {\n if (!field.list || !modelNames.has(field.typeName)) continue;\n\n const targetInterp = interpretedByName.get(field.typeName);\n if (!targetInterp) continue;\n\n // Find the FK in the target model that points back to this model\n const fk = targetInterp.fksByTarget.get(model.name);\n if (!fk) {\n diagnostics.push({\n code: \"IDB_UNRESOLVED_BACKRELATION\",\n 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}\".`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n // The 1:N side: localFields are the PK fields of this model, targetFields are the FK fields in the target\n const mutableInterp = interp as { relations: Record<string, unknown> };\n mutableInterp.relations[field.name] = {\n to: crossRef(field.typeName),\n cardinality: \"1:N\",\n on: { localFields: fk.targetFields, targetFields: fk.localFields },\n };\n }\n }\n\n if (diagnostics.length > 0) {\n return notOk({\n summary: \"PSL to IDB contract interpretation failed\",\n diagnostics,\n });\n }\n\n // ── Build the contract ────────────────────────────────────────────────────────\n\n const ns = UNBOUND_DOMAIN_NAMESPACE_ID;\n const stores: Record<string, IdbStoreDefinition> = {};\n const roots: Record<string, ReturnType<typeof crossRef>> = {};\n const domainModels: Record<string, unknown> = {};\n\n for (const [modelName, interp] of interpretedByName) {\n stores[interp.storeName] = {\n keyPath: interp.keyPath,\n ...(Object.keys(interp.indexes).length > 0 ? { indexes: interp.indexes } : {}),\n };\n\n roots[interp.storeName] = crossRef(modelName);\n\n const modelStorage: IdbModelStorage =\n Object.keys(interp.relationsStorage).length > 0\n ? { storeName: interp.storeName, keyPath: interp.keyPath, relations: interp.relationsStorage }\n : { storeName: interp.storeName, keyPath: interp.keyPath };\n\n domainModels[modelName] = {\n fields: interp.fields,\n relations: interp.relations,\n storage: modelStorage,\n };\n }\n\n const storageBlock = {\n stores,\n namespaces: { [ns]: { id: ns, entries: {} } },\n };\n\n const capabilities = {\n idb: { ddlOnlyInUpgrade: true, transactionalDDL: true },\n };\n\n const storageHash = computeStorageHash({\n target: \"idb\",\n targetFamily: \"idb\",\n storage: storageBlock,\n });\n\n const profileHash = computeProfileHash({\n target: \"idb\",\n targetFamily: \"idb\",\n capabilities,\n });\n\n const storage: IdbStorage = { ...storageBlock, storageHash };\n\n const domain = {\n namespaces: { [ns]: { models: domainModels } },\n } as unknown as ApplicationDomain;\n\n const contract: Contract<IdbStorage> = {\n target: \"idb\",\n targetFamily: \"idb\",\n roots,\n domain,\n storage,\n capabilities,\n extensionPacks: {},\n meta: {},\n profileHash,\n };\n\n validateContract(contract);\n return ok(contract);\n}\n","import { readFile } from \"node:fs/promises\";\nimport type { ContractConfig, ContractSourceDiagnostic } from \"@prisma-next/config/config-types\";\nimport type { PslDiagnostic } from \"@prisma-next/framework-components/psl-ast\";\nimport { parsePslDocument } from \"@prisma-next/psl-parser\";\nimport { notOk, ok } from \"@prisma-next/utils/result\";\nimport { extname, basename } from \"pathe\";\nimport { interpretPslDocumentToIdbContract } from \"./psl-interpreter\";\n\nfunction defaultOutputFromSchemaPath(schemaPath: string): string {\n const ext = extname(schemaPath);\n if (ext.length === 0) return `${schemaPath}.json`;\n const base = schemaPath.slice(0, -ext.length);\n if (basename(base) === \"schema\") {\n return `${base.slice(0, -\"schema\".length)}contract.json`;\n }\n return `${base}.json`;\n}\n\nfunction mapPslDiagnostics(diagnostics: readonly PslDiagnostic[], sourceId: string): ContractSourceDiagnostic[] {\n return diagnostics.map((d) => ({\n code: d.code as string,\n message: d.message,\n sourceId,\n ...(d.span !== undefined ? { span: d.span } : {}),\n }));\n}\n\nexport interface PrismaIdbContractOptions {\n readonly output?: string;\n}\n\n/**\n * Creates a `ContractConfig` that reads an IDB schema from a `.prisma` file.\n *\n * Use this in `prisma-next.config.ts` as the `contract:` value when you prefer\n * PSL authoring over the TypeScript-first `defineContract()` helper.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@prisma-next-idb/family-idb/config-types';\n * import { prismaIdbContract } from '@prisma-next-idb/family-idb/contract-psl';\n *\n * export default defineConfig({\n * // ...\n * contract: prismaIdbContract('./src/prisma/schema.prisma'),\n * });\n * ```\n *\n * The emitted `contract.json` lands next to the schema file by default\n * (`schema.prisma` → `contract.json`). Override with `options.output`.\n */\nexport function prismaIdbContract(schemaPath: string, options?: PrismaIdbContractOptions): ContractConfig {\n return {\n source: {\n inputs: [schemaPath],\n load: async (context) => {\n const [absoluteSchemaPath] = context.resolvedInputs;\n if (absoluteSchemaPath === undefined) {\n throw new Error(\n \"prismaIdbContract: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.\"\n );\n }\n\n let schema: string;\n try {\n schema = await readFile(absoluteSchemaPath, \"utf-8\");\n } catch (error) {\n const message = String(error);\n return notOk({\n summary: `Failed to read Prisma schema at \"${schemaPath}\"`,\n diagnostics: [\n {\n code: \"PSL_SCHEMA_READ_FAILED\",\n message,\n sourceId: schemaPath,\n },\n ],\n });\n }\n\n const {\n ast,\n diagnostics: parseDiagnostics,\n ok: parseOk,\n } = parsePslDocument({\n schema,\n sourceId: schemaPath,\n });\n\n const seedDiagnostics = mapPslDiagnostics(parseDiagnostics, schemaPath);\n\n if (!parseOk) {\n return notOk({\n summary: `Failed to parse Prisma schema at \"${schemaPath}\"`,\n diagnostics: seedDiagnostics,\n });\n }\n\n const interpreted = interpretPslDocumentToIdbContract(ast, schemaPath);\n if (!interpreted.ok) {\n return notOk({\n ...interpreted.failure,\n diagnostics: [...seedDiagnostics, ...interpreted.failure.diagnostics],\n });\n }\n\n if (seedDiagnostics.length > 0) {\n return notOk({\n summary: `PSL parse warnings in \"${schemaPath}\"`,\n diagnostics: seedDiagnostics,\n });\n }\n\n return ok(interpreted.value);\n },\n },\n output: options?.output ?? defaultOutputFromSchemaPath(schemaPath),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Contract } from "@prisma-next/contract/types";
|
|
2
|
+
import { IdbReferentialAction, IdbStorage } from "@prisma-next-idb/target-idb/pack";
|
|
3
|
+
|
|
4
|
+
//#region src/core/contract-builder.d.ts
|
|
5
|
+
type PrismaScalarType = "String" | "Int" | "Float" | "Boolean" | "DateTime" | "BigInt" | "Decimal" | "Json" | "Bytes";
|
|
6
|
+
/**
|
|
7
|
+
* A field spec string: the Prisma scalar type name, optionally suffixed with
|
|
8
|
+
* `?` to indicate the field is nullable (e.g. `"String"`, `"Int?"`, `"DateTime?"`).
|
|
9
|
+
*/
|
|
10
|
+
type FieldSpec = PrismaScalarType | `${PrismaScalarType}?`;
|
|
11
|
+
type RelationDef = {
|
|
12
|
+
readonly to: string;
|
|
13
|
+
readonly cardinality: "1:1" | "1:N" | "N:1";
|
|
14
|
+
readonly on: {
|
|
15
|
+
readonly local: readonly string[];
|
|
16
|
+
readonly target: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
readonly onDelete?: IdbReferentialAction;
|
|
19
|
+
};
|
|
20
|
+
type IndexDef = {
|
|
21
|
+
readonly keyPath: string;
|
|
22
|
+
readonly unique?: boolean;
|
|
23
|
+
readonly multiEntry?: boolean;
|
|
24
|
+
};
|
|
25
|
+
type ModelDef = {
|
|
26
|
+
readonly store: string;
|
|
27
|
+
readonly key: string; /** All scalar fields on the model. Use `"Type"` for non-nullable, `"Type?"` for nullable. */
|
|
28
|
+
readonly fields: Record<string, FieldSpec>;
|
|
29
|
+
readonly indexes?: Record<string, IndexDef>;
|
|
30
|
+
readonly relations?: Record<string, RelationDef>;
|
|
31
|
+
};
|
|
32
|
+
type DefineContractInput = {
|
|
33
|
+
/** Pass the default export of `@prisma-next-idb/family-idb/pack`. */readonly family: {
|
|
34
|
+
readonly familyId: "idb";
|
|
35
|
+
readonly id: string;
|
|
36
|
+
}; /** Pass the default export of `@prisma-next-idb/target-idb/pack`. */
|
|
37
|
+
readonly target: {
|
|
38
|
+
readonly targetId: string;
|
|
39
|
+
readonly id: string;
|
|
40
|
+
};
|
|
41
|
+
readonly models: Record<string, ModelDef>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Builds a typed IDB contract from a developer-friendly model definition.
|
|
45
|
+
*
|
|
46
|
+
* This is the TypeScript-first (no-emit) authoring path per ADR 006. The
|
|
47
|
+
* returned contract object can be passed directly to `createIdbClient()` or
|
|
48
|
+
* to `typescriptContract()` for config-file usage.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { defineContract } from '@prisma-next-idb/family-idb/contract-ts';
|
|
53
|
+
* import idbFamily from '@prisma-next-idb/family-idb/pack';
|
|
54
|
+
* import idbTarget from '@prisma-next-idb/target-idb/pack';
|
|
55
|
+
*
|
|
56
|
+
* export default defineContract({
|
|
57
|
+
* family: idbFamily,
|
|
58
|
+
* target: idbTarget,
|
|
59
|
+
* models: {
|
|
60
|
+
* User: {
|
|
61
|
+
* store: 'users',
|
|
62
|
+
* key: 'id',
|
|
63
|
+
* fields: { id: 'String', name: 'String?', email: 'String' },
|
|
64
|
+
* indexes: { byEmail: { keyPath: 'email', unique: true } },
|
|
65
|
+
* },
|
|
66
|
+
* },
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function defineContract(input: DefineContractInput): Contract<IdbStorage>;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { type DefineContractInput, type FieldSpec, type IndexDef, type ModelDef, type RelationDef, defineContract };
|
|
73
|
+
//# sourceMappingURL=contract-ts.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-ts.d.mts","names":[],"sources":["../../src/core/contract-builder.ts"],"mappings":";;;;KAcK,gBAAA;;AALqC;;;KAW9B,SAAA,GAAY,gBAAA,MAAsB,gBAAgB;AAAA,KAgBlD,WAAA;EAAA,SACD,EAAA;EAAA,SACA,WAAA;EAAA,SACA,EAAA;IAAA,SACE,KAAA;IAAA,SACA,MAAA;EAAA;EAAA,SAEF,QAAA,GAAW,oBAAoB;AAAA;AAAA,KAG9B,QAAA;EAAA,SACD,OAAA;EAAA,SACA,MAAA;EAAA,SACA,UAAA;AAAA;AAAA,KAGC,QAAA;EAAA,SACD,KAAA;EAAA,SACA,GAAA,UAX+B;EAAA,SAa/B,MAAA,EAAQ,MAAA,SAAe,SAAA;EAAA,SACvB,OAAA,GAAU,MAAA,SAAe,QAAA;EAAA,SACzB,SAAA,GAAY,MAAA,SAAe,WAAA;AAAA;AAAA,KAG1B,mBAAA;EAdD,8EAgBA,MAAA;IAAA,SAAmB,QAAA;IAAA,SAA0B,EAAA;EAAA,GAX5C;EAAA,SAaD,MAAA;IAAA,SAAmB,QAAA;IAAA,SAA2B,EAAA;EAAA;EAAA,SAC9C,MAAA,EAAQ,MAAM,SAAS,QAAA;AAAA;;;;;;;;;;;;;;;;;AARe;AAGjD;;;;;;;;;;iBA+HgB,cAAA,CAAe,KAAA,EAAO,mBAAA,GAAsB,QAAA,CAAS,UAAA"}
|