@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,573 @@
|
|
|
1
|
+
import { t as validateContract } from "../validate-BLnItEKs.mjs";
|
|
2
|
+
import { VERIFY_CODE_SCHEMA_FAILURE, VERIFY_CODE_TARGET_MISMATCH } from "@prisma/orm-framework/components/control";
|
|
3
|
+
//#region src/core/emission.ts
|
|
4
|
+
/**
|
|
5
|
+
* Converts a JavaScript primitive or plain object into a TypeScript type-literal
|
|
6
|
+
* string. Arrays become `readonly [...]` tuples; objects become `{ readonly k: v }`
|
|
7
|
+
* type literals; strings are single-quoted with escaping.
|
|
8
|
+
*/
|
|
9
|
+
function serializeValue(value) {
|
|
10
|
+
if (value === null) return "null";
|
|
11
|
+
if (value === void 0) return "undefined";
|
|
12
|
+
if (typeof value === "string") return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
13
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
14
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
15
|
+
if (Array.isArray(value)) return `readonly [${value.map((v) => serializeValue(v)).join(", ")}]`;
|
|
16
|
+
if (typeof value === "object") {
|
|
17
|
+
const entries = [];
|
|
18
|
+
for (const [k, v] of Object.entries(value)) entries.push(`readonly ${serializeObjectKey(k)}: ${serializeValue(v)}`);
|
|
19
|
+
return `{ ${entries.join("; ")} }`;
|
|
20
|
+
}
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Converts an object key to a safe TypeScript identifier. If the key is already a
|
|
25
|
+
* valid identifier it is returned as-is; otherwise it is wrapped in single-quoted
|
|
26
|
+
* string literal form.
|
|
27
|
+
*/
|
|
28
|
+
function serializeObjectKey(key) {
|
|
29
|
+
if (/^[$A-Z_a-z][$\w]*$/.test(key)) return key;
|
|
30
|
+
return serializeValue(key);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* IDB family emission plugin.
|
|
34
|
+
*
|
|
35
|
+
* Registered as `emission` on {@link IdbFamilyDescriptor}. The Prisma 8 emitter
|
|
36
|
+
* calls these methods when generating `contract.d.ts` for any schema that targets the
|
|
37
|
+
* IDB family. Each method returns a raw TypeScript source string that the emitter
|
|
38
|
+
* splices into the generated file at the appropriate location.
|
|
39
|
+
*
|
|
40
|
+
* @see `generateContractDts` in `@prisma/orm-toolchain`.
|
|
41
|
+
*/
|
|
42
|
+
const idbEmission = {
|
|
43
|
+
id: "idb",
|
|
44
|
+
/**
|
|
45
|
+
* Serializes the full `storage` section of the contract into a TypeScript type
|
|
46
|
+
* literal. The result is used as the first generic argument of `ContractType<...>`
|
|
47
|
+
* in the generated `contract.d.ts`.
|
|
48
|
+
*
|
|
49
|
+
* For IDB the storage shape is:
|
|
50
|
+
* ```ts
|
|
51
|
+
* { readonly stores: { readonly <storeName>: { readonly keyPath: '...'; ... }; ... };
|
|
52
|
+
* readonly storageHash: <storageHashTypeName> }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @param contract - The resolved Prisma contract.
|
|
56
|
+
* @param storageHashTypeName - Emitter-provided name of the `StorageHash` type alias
|
|
57
|
+
* to embed (e.g. `'StorageHash'`).
|
|
58
|
+
*/
|
|
59
|
+
generateStorageType(contract, storageHashTypeName) {
|
|
60
|
+
const storage = contract.storage;
|
|
61
|
+
const stores = [];
|
|
62
|
+
for (const [storeName, store] of Object.entries(storage.stores).sort(([a], [b]) => a.localeCompare(b))) {
|
|
63
|
+
const storeParts = [`readonly keyPath: ${serializeValue(store.keyPath)}`];
|
|
64
|
+
if (store.autoIncrement !== void 0) storeParts.push(`readonly autoIncrement: ${store.autoIncrement}`);
|
|
65
|
+
const indexes = store.indexes ?? {};
|
|
66
|
+
if (Object.keys(indexes).length > 0) {
|
|
67
|
+
const indexEntries = [];
|
|
68
|
+
for (const [indexName, index] of Object.entries(indexes).sort(([a], [b]) => a.localeCompare(b))) {
|
|
69
|
+
const indexParts = [`readonly keyPath: ${serializeValue(index.keyPath)}`, `readonly unique: ${index.unique}`];
|
|
70
|
+
if (index.multiEntry !== void 0) indexParts.push(`readonly multiEntry: ${index.multiEntry}`);
|
|
71
|
+
indexEntries.push(`readonly ${serializeObjectKey(indexName)}: { ${indexParts.join("; ")} }`);
|
|
72
|
+
}
|
|
73
|
+
storeParts.push(`readonly indexes: { ${indexEntries.join("; ")} }`);
|
|
74
|
+
} else storeParts.push(`readonly indexes: Record<string, never>`);
|
|
75
|
+
stores.push(`readonly ${serializeObjectKey(storeName)}: { ${storeParts.join("; ")} }`);
|
|
76
|
+
}
|
|
77
|
+
const storesType = stores.length > 0 ? `{ ${stores.join("; ")} }` : "Record<string, never>";
|
|
78
|
+
const nsEntries = [];
|
|
79
|
+
for (const [nsId, ns] of Object.entries(storage.namespaces ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
80
|
+
const entriesObj = ns.entries ?? {};
|
|
81
|
+
const entriesType = Object.keys(entriesObj).length === 0 ? "Record<string, never>" : serializeValue(entriesObj);
|
|
82
|
+
nsEntries.push(`readonly ${serializeObjectKey(nsId)}: { readonly id: ${serializeValue(ns.id)}; readonly entries: ${entriesType} }`);
|
|
83
|
+
}
|
|
84
|
+
return `{ readonly stores: ${storesType}; readonly namespaces: ${nsEntries.length > 0 ? `{ ${nsEntries.join("; ")} }` : "Record<string, never>"}; readonly storageHash: ${storageHashTypeName} }`;
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* Serializes the per-model storage metadata into a TypeScript type literal.
|
|
88
|
+
* The result is used as the `storage` field of the model's entry in the
|
|
89
|
+
* generated `models` type.
|
|
90
|
+
*
|
|
91
|
+
* For IDB, each model maps to exactly one object store:
|
|
92
|
+
* ```ts
|
|
93
|
+
* { readonly storeName: '<name>'; readonly keyPath: '<field>' }
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @param _modelName - The Prisma model name (unused; provided for API symmetry).
|
|
97
|
+
* @param model - The resolved `ContractModel` whose `storage` is cast to
|
|
98
|
+
* {@link IdbModelStorage}.
|
|
99
|
+
*/
|
|
100
|
+
generateModelStorageType(_modelName, model) {
|
|
101
|
+
const idbModel = model;
|
|
102
|
+
return [`{ readonly storeName: ${serializeValue(idbModel.storage.storeName)}`, `readonly keyPath: ${serializeValue(idbModel.storage.keyPath)} }`].join("; ");
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* Returns the `import type` lines prepended to `contract.d.ts` that bring
|
|
106
|
+
* IDB-family types into scope.
|
|
107
|
+
*
|
|
108
|
+
* Imports {@link IdbContractWithTypeMaps} (the phantom wrapper used by
|
|
109
|
+
* {@link getContractWrapper}) and {@link IdbTypeMaps} (used by
|
|
110
|
+
* {@link getTypeMapsExpression}).
|
|
111
|
+
*/
|
|
112
|
+
getFamilyImports() {
|
|
113
|
+
return ["import type { IdbContractWithTypeMaps, IdbTypeMaps } from '@prisma-idb/target-idb/pack';"];
|
|
114
|
+
},
|
|
115
|
+
/**
|
|
116
|
+
* Returns additional type alias declarations appended to `contract.d.ts`
|
|
117
|
+
* after the codec and operation type aliases.
|
|
118
|
+
*
|
|
119
|
+
* For IDB this exports `LaneCodecTypes` (used by the generated client to
|
|
120
|
+
* look up codec input/output pairs) as a simple alias of the assembled
|
|
121
|
+
* `CodecTypes`. IDB has no query-operation types, so no further aliases
|
|
122
|
+
* are needed.
|
|
123
|
+
*/
|
|
124
|
+
getFamilyTypeAliases() {
|
|
125
|
+
return "export type LaneCodecTypes = CodecTypes;";
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* Returns the TypeScript expression for the `TypeMaps<...>` generic
|
|
129
|
+
* instantiation written into `contract.d.ts`.
|
|
130
|
+
*
|
|
131
|
+
* IDB has no operation types or query-operation types, so the expression
|
|
132
|
+
* uses the three-argument form of {@link IdbTypeMaps}.
|
|
133
|
+
*/
|
|
134
|
+
getTypeMapsExpression() {
|
|
135
|
+
return "IdbTypeMaps<CodecTypes, FieldOutputTypes, FieldInputTypes>";
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Returns the final `export type Contract = ...` declaration and convenience
|
|
139
|
+
* re-exports derived from it.
|
|
140
|
+
*
|
|
141
|
+
* @param contractBaseName - Emitter-provided name of the base contract type
|
|
142
|
+
* (always `'ContractBase'`).
|
|
143
|
+
* @param typeMapsName - Emitter-provided name of the assembled TypeMaps type
|
|
144
|
+
* (always `'TypeMaps'`).
|
|
145
|
+
*/
|
|
146
|
+
getContractWrapper(contractBaseName, typeMapsName) {
|
|
147
|
+
return [
|
|
148
|
+
`export type Contract = IdbContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`,
|
|
149
|
+
"",
|
|
150
|
+
"export type Stores = Contract['storage']['stores'];"
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/core/schema-verify.ts
|
|
156
|
+
function passNode(kind, name, contractPath, children = []) {
|
|
157
|
+
return {
|
|
158
|
+
status: "pass",
|
|
159
|
+
kind,
|
|
160
|
+
name,
|
|
161
|
+
contractPath,
|
|
162
|
+
code: "ok",
|
|
163
|
+
message: `${name} matches`,
|
|
164
|
+
expected: null,
|
|
165
|
+
actual: null,
|
|
166
|
+
children
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function failNode(kind, name, contractPath, code, message, expected, actual, children = []) {
|
|
170
|
+
return {
|
|
171
|
+
status: "fail",
|
|
172
|
+
kind,
|
|
173
|
+
name,
|
|
174
|
+
contractPath,
|
|
175
|
+
code,
|
|
176
|
+
message,
|
|
177
|
+
expected,
|
|
178
|
+
actual,
|
|
179
|
+
children
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function warnNode(kind, name, contractPath, code, message, expected, actual, children = []) {
|
|
183
|
+
return {
|
|
184
|
+
status: "warn",
|
|
185
|
+
kind,
|
|
186
|
+
name,
|
|
187
|
+
contractPath,
|
|
188
|
+
code,
|
|
189
|
+
message,
|
|
190
|
+
expected,
|
|
191
|
+
actual,
|
|
192
|
+
children
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function countStatuses(nodes) {
|
|
196
|
+
let pass = 0, warn = 0, fail = 0, totalNodes = 0;
|
|
197
|
+
const stack = [...nodes];
|
|
198
|
+
while (stack.length > 0) {
|
|
199
|
+
const node = stack.pop();
|
|
200
|
+
totalNodes++;
|
|
201
|
+
if (node.status === "pass") pass++;
|
|
202
|
+
else if (node.status === "warn") warn++;
|
|
203
|
+
else fail++;
|
|
204
|
+
stack.push(...node.children);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
pass,
|
|
208
|
+
warn,
|
|
209
|
+
fail,
|
|
210
|
+
totalNodes
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function verifyIndex(indexName, contractIndex, actualIndex, storeName, storePath, issues) {
|
|
214
|
+
const indexPath = `${storePath}.indexes.${indexName}`;
|
|
215
|
+
if (actualIndex === void 0) {
|
|
216
|
+
const msg = `Index "${indexName}" defined in contract is missing from manifest schema`;
|
|
217
|
+
issues.push({
|
|
218
|
+
kind: "missing_column",
|
|
219
|
+
table: storeName,
|
|
220
|
+
column: indexName,
|
|
221
|
+
message: msg,
|
|
222
|
+
path: [storeName, indexName]
|
|
223
|
+
});
|
|
224
|
+
return failNode("index", indexName, indexPath, "missing_column", msg, contractIndex, void 0);
|
|
225
|
+
}
|
|
226
|
+
const children = [];
|
|
227
|
+
if (contractIndex.keyPath !== actualIndex.keyPath) {
|
|
228
|
+
const msg = `Index "${indexName}" keyPath mismatch: expected "${contractIndex.keyPath}", got "${actualIndex.keyPath}"`;
|
|
229
|
+
issues.push({
|
|
230
|
+
kind: "index_mismatch",
|
|
231
|
+
table: storeName,
|
|
232
|
+
indexOrConstraint: indexName,
|
|
233
|
+
message: msg,
|
|
234
|
+
path: [storeName, indexName]
|
|
235
|
+
});
|
|
236
|
+
children.push(failNode("field", "keyPath", `${indexPath}.keyPath`, "index_mismatch", msg, contractIndex.keyPath, actualIndex.keyPath));
|
|
237
|
+
} else children.push(passNode("field", "keyPath", `${indexPath}.keyPath`));
|
|
238
|
+
if ((contractIndex.unique ?? false) !== (actualIndex.unique ?? false)) {
|
|
239
|
+
const msg = `Index "${indexName}" unique mismatch: expected ${contractIndex.unique}, got ${actualIndex.unique}`;
|
|
240
|
+
issues.push({
|
|
241
|
+
kind: "index_mismatch",
|
|
242
|
+
table: storeName,
|
|
243
|
+
indexOrConstraint: indexName,
|
|
244
|
+
message: msg,
|
|
245
|
+
path: [storeName, indexName]
|
|
246
|
+
});
|
|
247
|
+
children.push(failNode("field", "unique", `${indexPath}.unique`, "index_mismatch", msg, contractIndex.unique, actualIndex.unique));
|
|
248
|
+
} else children.push(passNode("field", "unique", `${indexPath}.unique`));
|
|
249
|
+
const contractME = contractIndex.multiEntry ?? false;
|
|
250
|
+
const actualME = actualIndex.multiEntry ?? false;
|
|
251
|
+
if (contractME !== actualME) {
|
|
252
|
+
const msg = `Index "${indexName}" multiEntry mismatch: expected ${contractME}, got ${actualME}`;
|
|
253
|
+
issues.push({
|
|
254
|
+
kind: "index_mismatch",
|
|
255
|
+
table: storeName,
|
|
256
|
+
indexOrConstraint: indexName,
|
|
257
|
+
message: msg,
|
|
258
|
+
path: [storeName, indexName]
|
|
259
|
+
});
|
|
260
|
+
children.push(failNode("field", "multiEntry", `${indexPath}.multiEntry`, "index_mismatch", msg, contractME, actualME));
|
|
261
|
+
} else if (contractME) children.push(passNode("field", "multiEntry", `${indexPath}.multiEntry`));
|
|
262
|
+
if (children.some((c) => c.status === "fail")) return failNode("index", indexName, indexPath, "index_mismatch", `Index "${indexName}" has mismatches`, null, null, children);
|
|
263
|
+
return passNode("index", indexName, indexPath, children);
|
|
264
|
+
}
|
|
265
|
+
function verifyStore(storeName, contractStore, actualStore, strict, issues) {
|
|
266
|
+
const storePath = `storage.stores.${storeName}`;
|
|
267
|
+
if (actualStore === void 0) {
|
|
268
|
+
const msg = `Object store "${storeName}" defined in contract is missing from manifest schema`;
|
|
269
|
+
issues.push({
|
|
270
|
+
kind: "missing_table",
|
|
271
|
+
table: storeName,
|
|
272
|
+
message: msg,
|
|
273
|
+
path: [storeName]
|
|
274
|
+
});
|
|
275
|
+
return failNode("collection", storeName, storePath, "missing_table", msg, contractStore, void 0);
|
|
276
|
+
}
|
|
277
|
+
const children = [];
|
|
278
|
+
if (contractStore.keyPath !== actualStore.keyPath) {
|
|
279
|
+
const msg = `Store "${storeName}" keyPath mismatch: expected "${contractStore.keyPath}", got "${actualStore.keyPath}"`;
|
|
280
|
+
issues.push({
|
|
281
|
+
kind: "primary_key_mismatch",
|
|
282
|
+
table: storeName,
|
|
283
|
+
message: msg,
|
|
284
|
+
path: [storeName]
|
|
285
|
+
});
|
|
286
|
+
children.push(failNode("field", "keyPath", `${storePath}.keyPath`, "primary_key_mismatch", msg, contractStore.keyPath, actualStore.keyPath));
|
|
287
|
+
} else children.push(passNode("field", "keyPath", `${storePath}.keyPath`));
|
|
288
|
+
const contractAI = contractStore.autoIncrement ?? false;
|
|
289
|
+
const actualAI = actualStore.autoIncrement ?? false;
|
|
290
|
+
if (contractAI !== actualAI) {
|
|
291
|
+
const msg = `Store "${storeName}" autoIncrement mismatch: expected ${contractAI}, got ${actualAI}`;
|
|
292
|
+
issues.push({
|
|
293
|
+
kind: "type_mismatch",
|
|
294
|
+
table: storeName,
|
|
295
|
+
message: msg,
|
|
296
|
+
path: [storeName]
|
|
297
|
+
});
|
|
298
|
+
children.push(failNode("field", "autoIncrement", `${storePath}.autoIncrement`, "type_mismatch", msg, contractAI, actualAI));
|
|
299
|
+
} else if (contractAI) children.push(passNode("field", "autoIncrement", `${storePath}.autoIncrement`));
|
|
300
|
+
const contractIndexes = contractStore.indexes ?? {};
|
|
301
|
+
const actualIndexes = actualStore.indexes ?? {};
|
|
302
|
+
for (const [indexName, contractIndex] of Object.entries(contractIndexes)) {
|
|
303
|
+
const actualIndex = actualIndexes[indexName];
|
|
304
|
+
children.push(verifyIndex(indexName, contractIndex, actualIndex, storeName, storePath, issues));
|
|
305
|
+
}
|
|
306
|
+
for (const indexName of Object.keys(actualIndexes)) if (!(indexName in contractIndexes)) {
|
|
307
|
+
const msg = `Index "${indexName}" exists in manifest but is not in contract`;
|
|
308
|
+
if (strict) {
|
|
309
|
+
issues.push({
|
|
310
|
+
kind: "extra_index",
|
|
311
|
+
table: storeName,
|
|
312
|
+
indexOrConstraint: indexName,
|
|
313
|
+
message: msg,
|
|
314
|
+
path: [storeName, indexName]
|
|
315
|
+
});
|
|
316
|
+
children.push(failNode("index", indexName, `${storePath}.indexes.${indexName}`, "extra_index", msg, void 0, actualIndexes[indexName]));
|
|
317
|
+
} else children.push(warnNode("index", indexName, `${storePath}.indexes.${indexName}`, "extra_index", msg, void 0, actualIndexes[indexName]));
|
|
318
|
+
}
|
|
319
|
+
if (children.some((c) => c.status === "fail")) return failNode("collection", storeName, storePath, "schema_mismatch", `Store "${storeName}" has schema issues`, null, null, children);
|
|
320
|
+
if (children.some((c) => c.status === "warn")) return {
|
|
321
|
+
status: "warn",
|
|
322
|
+
kind: "collection",
|
|
323
|
+
name: storeName,
|
|
324
|
+
contractPath: storePath,
|
|
325
|
+
code: "ok",
|
|
326
|
+
message: `Store "${storeName}" has warnings`,
|
|
327
|
+
expected: null,
|
|
328
|
+
actual: null,
|
|
329
|
+
children
|
|
330
|
+
};
|
|
331
|
+
return passNode("collection", storeName, storePath, children);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Pure, synchronous schema verification for IndexedDB.
|
|
335
|
+
*
|
|
336
|
+
* Compares the stores and indexes defined in the contract against the manifest
|
|
337
|
+
* schema (which represents the actual IndexedDB database). Returns a
|
|
338
|
+
* {@link VerifyDatabaseSchemaResult} that callers can inspect for issues.
|
|
339
|
+
*
|
|
340
|
+
* Does not perform any I/O — all inputs must already be available.
|
|
341
|
+
*
|
|
342
|
+
* @param contract - Validated IDB contract (what we expect the schema to be).
|
|
343
|
+
* @param schema - Schema IR from the manifest (what the schema actually is).
|
|
344
|
+
* @param strict - When `true`, extra stores/indexes in the manifest are failures.
|
|
345
|
+
* @param options - Optional context paths for metadata / error reporting.
|
|
346
|
+
*/
|
|
347
|
+
function verifyIdbSchema(contract, schema, strict) {
|
|
348
|
+
const start = Date.now();
|
|
349
|
+
const issues = [];
|
|
350
|
+
const storeNodes = [];
|
|
351
|
+
const contractStores = contract.storage.stores;
|
|
352
|
+
const actualStores = schema.stores;
|
|
353
|
+
for (const [storeName, contractStore] of Object.entries(contractStores)) {
|
|
354
|
+
const actualStore = actualStores[storeName];
|
|
355
|
+
storeNodes.push(verifyStore(storeName, contractStore, actualStore, strict, issues));
|
|
356
|
+
}
|
|
357
|
+
for (const [storeName, actualStore] of Object.entries(actualStores)) if (!(storeName in contractStores)) {
|
|
358
|
+
const msg = `Object store "${storeName}" exists in manifest but is not in contract`;
|
|
359
|
+
if (strict) {
|
|
360
|
+
issues.push({
|
|
361
|
+
kind: "extra_table",
|
|
362
|
+
table: storeName,
|
|
363
|
+
message: msg,
|
|
364
|
+
path: [storeName]
|
|
365
|
+
});
|
|
366
|
+
storeNodes.push(failNode("collection", storeName, `storage.stores.${storeName}`, "extra_table", msg, void 0, actualStore));
|
|
367
|
+
} else storeNodes.push(warnNode("collection", storeName, `storage.stores.${storeName}`, "extra_table", msg, void 0, actualStore));
|
|
368
|
+
}
|
|
369
|
+
const root = {
|
|
370
|
+
status: storeNodes.some((n) => n.status === "fail") ? "fail" : storeNodes.some((n) => n.status === "warn") ? "warn" : "pass",
|
|
371
|
+
kind: "root",
|
|
372
|
+
name: "idb",
|
|
373
|
+
contractPath: "storage.stores",
|
|
374
|
+
code: issues.length === 0 ? "ok" : "schema_mismatch",
|
|
375
|
+
message: issues.length === 0 ? "All stores and indexes match" : `${issues.length} schema issue(s) found`,
|
|
376
|
+
expected: null,
|
|
377
|
+
actual: null,
|
|
378
|
+
children: storeNodes
|
|
379
|
+
};
|
|
380
|
+
const counts = countStatuses([root, ...storeNodes]);
|
|
381
|
+
const ok = root.status !== "fail";
|
|
382
|
+
const storageHash = contract.storage.storageHash;
|
|
383
|
+
const profileHash = contract.profileHash;
|
|
384
|
+
const contractField = profileHash !== void 0 ? {
|
|
385
|
+
storageHash,
|
|
386
|
+
profileHash
|
|
387
|
+
} : { storageHash };
|
|
388
|
+
const metaField = { strict };
|
|
389
|
+
const sharedResult = {
|
|
390
|
+
summary: ok ? "Schema verification passed" : `Schema verification failed: ${issues.length} issue(s)`,
|
|
391
|
+
contract: contractField,
|
|
392
|
+
target: { expected: "idb" },
|
|
393
|
+
schema: {
|
|
394
|
+
issues,
|
|
395
|
+
root,
|
|
396
|
+
counts
|
|
397
|
+
},
|
|
398
|
+
meta: metaField,
|
|
399
|
+
timings: { total: Date.now() - start }
|
|
400
|
+
};
|
|
401
|
+
if (ok) return {
|
|
402
|
+
ok: true,
|
|
403
|
+
...sharedResult
|
|
404
|
+
};
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
code: VERIFY_CODE_SCHEMA_FAILURE,
|
|
408
|
+
...sharedResult
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/core/control-instance.ts
|
|
413
|
+
const REFUSAL_CODE = "IDB-CLI-UNSUPPORTED";
|
|
414
|
+
/**
|
|
415
|
+
* Single-string refusal message baked into `summary` because the framework
|
|
416
|
+
* `VerifyDatabaseResult` / `SignDatabaseResult` `meta` field is strict-shaped
|
|
417
|
+
* (`{ contractPath, configPath? }`) and doesn't accept extra explanation
|
|
418
|
+
* fields. The summary is the only free-form text field on every result.
|
|
419
|
+
*/
|
|
420
|
+
function refusalSummary(action) {
|
|
421
|
+
return `IndexedDB cannot be ${action} from the CLI. IndexedDB only exists in the browser; the CLI runs in Node.js, so there is no live database for it to inspect or update. Author migrations with \`prisma-idb migration plan\`, validate the chain with \`prisma-idb migration preflight\`, and let \`createAutoMigratingIdbClient\` apply them the next time the app opens in a browser.`;
|
|
422
|
+
}
|
|
423
|
+
function contractInfo(storageHash, profileHash) {
|
|
424
|
+
return profileHash !== void 0 ? {
|
|
425
|
+
storageHash,
|
|
426
|
+
profileHash
|
|
427
|
+
} : { storageHash };
|
|
428
|
+
}
|
|
429
|
+
function verifyMeta(contractPath, configPath) {
|
|
430
|
+
return configPath !== void 0 ? {
|
|
431
|
+
contractPath,
|
|
432
|
+
configPath
|
|
433
|
+
} : { contractPath };
|
|
434
|
+
}
|
|
435
|
+
function signMeta(contractPath, configPath) {
|
|
436
|
+
return configPath !== void 0 ? {
|
|
437
|
+
contractPath,
|
|
438
|
+
configPath
|
|
439
|
+
} : { contractPath };
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Creates an IDB control family instance for the given control stack.
|
|
443
|
+
*
|
|
444
|
+
* **CLI surface — refusals**: IndexedDB is a browser API, so the CLI cannot
|
|
445
|
+
* read or write the live database. Every method that would normally talk to a
|
|
446
|
+
* database (`verify`, `sign`, `readMarker`, `readAllMarkers`, `introspect`)
|
|
447
|
+
* returns a structured refusal pointing the user at the contract-space
|
|
448
|
+
* authoring + preflight workflow.
|
|
449
|
+
*
|
|
450
|
+
* The CLI-side `db init`, `db update`, and `db verify` commands therefore
|
|
451
|
+
* surface a uniform `IDB-CLI-UNSUPPORTED` envelope rather than silently
|
|
452
|
+
* succeeding.
|
|
453
|
+
*
|
|
454
|
+
* **Active surface**: `deserializeContract` (pure) and `verifySchema` (pure
|
|
455
|
+
* function over an in-memory `IdbSchemaIR`) continue to work — neither
|
|
456
|
+
* needs a live database.
|
|
457
|
+
*
|
|
458
|
+
* @param _stack - The assembled control stack (unused; IDB has no adapter/extension layer).
|
|
459
|
+
*/
|
|
460
|
+
function createIdbFamilyInstance(_stack) {
|
|
461
|
+
return {
|
|
462
|
+
familyId: "idb",
|
|
463
|
+
deserializeContract(contractJson) {
|
|
464
|
+
return validateContract(contractJson);
|
|
465
|
+
},
|
|
466
|
+
async verify(options) {
|
|
467
|
+
const start = Date.now();
|
|
468
|
+
const contract = validateContract(options.contract);
|
|
469
|
+
const storageHash = contract.storage.storageHash;
|
|
470
|
+
const profileHash = contract.profileHash;
|
|
471
|
+
if (options.expectedTargetId !== "idb") return {
|
|
472
|
+
ok: false,
|
|
473
|
+
code: VERIFY_CODE_TARGET_MISMATCH,
|
|
474
|
+
summary: `Target mismatch: expected "idb", got "${options.expectedTargetId}"`,
|
|
475
|
+
contract: contractInfo(storageHash, profileHash),
|
|
476
|
+
target: {
|
|
477
|
+
expected: options.expectedTargetId,
|
|
478
|
+
actual: "idb"
|
|
479
|
+
},
|
|
480
|
+
meta: verifyMeta(options.contractPath, options.configPath),
|
|
481
|
+
timings: { total: Date.now() - start }
|
|
482
|
+
};
|
|
483
|
+
return {
|
|
484
|
+
ok: false,
|
|
485
|
+
code: REFUSAL_CODE,
|
|
486
|
+
summary: refusalSummary("verified"),
|
|
487
|
+
contract: contractInfo(storageHash, profileHash),
|
|
488
|
+
target: {
|
|
489
|
+
expected: "idb",
|
|
490
|
+
actual: "idb"
|
|
491
|
+
},
|
|
492
|
+
meta: verifyMeta(options.contractPath, options.configPath),
|
|
493
|
+
timings: { total: Date.now() - start }
|
|
494
|
+
};
|
|
495
|
+
},
|
|
496
|
+
verifySchema(options) {
|
|
497
|
+
return verifyIdbSchema(validateContract(options.contract), options.schema, options.strict);
|
|
498
|
+
},
|
|
499
|
+
async sign(options) {
|
|
500
|
+
const start = Date.now();
|
|
501
|
+
const contract = validateContract(options.contract);
|
|
502
|
+
const storageHash = contract.storage.storageHash;
|
|
503
|
+
const profileHash = contract.profileHash ?? "";
|
|
504
|
+
return {
|
|
505
|
+
ok: false,
|
|
506
|
+
summary: refusalSummary("signed"),
|
|
507
|
+
contract: contractInfo(storageHash, profileHash),
|
|
508
|
+
target: {
|
|
509
|
+
expected: "idb",
|
|
510
|
+
actual: "idb"
|
|
511
|
+
},
|
|
512
|
+
marker: {
|
|
513
|
+
created: false,
|
|
514
|
+
updated: false
|
|
515
|
+
},
|
|
516
|
+
meta: signMeta(options.contractPath, options.configPath),
|
|
517
|
+
timings: { total: Date.now() - start }
|
|
518
|
+
};
|
|
519
|
+
},
|
|
520
|
+
async readMarker(_options) {
|
|
521
|
+
return null;
|
|
522
|
+
},
|
|
523
|
+
async readAllMarkers(_options) {
|
|
524
|
+
return /* @__PURE__ */ new Map();
|
|
525
|
+
},
|
|
526
|
+
async readLedger(_options) {
|
|
527
|
+
return [];
|
|
528
|
+
},
|
|
529
|
+
async introspect(_options) {
|
|
530
|
+
return { stores: {} };
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region src/core/control-descriptor.ts
|
|
536
|
+
/**
|
|
537
|
+
* IDB family descriptor — the control-plane entry point for the IDB family.
|
|
538
|
+
*
|
|
539
|
+
* Registered in a Prisma 8 config file under the `family` key:
|
|
540
|
+
* ```ts
|
|
541
|
+
* import idb from '@prisma-idb/family-idb/control';
|
|
542
|
+
* export default defineConfig({ family: idb, ... });
|
|
543
|
+
* ```
|
|
544
|
+
*
|
|
545
|
+
* **Responsibilities:**
|
|
546
|
+
* - Carries the {@link idbEmission} plugin used by `prisma contract emit`
|
|
547
|
+
* to generate `contract.d.ts`.
|
|
548
|
+
* - Acts as a factory: `create(stack)` returns an {@link IdbControlFamilyInstance}
|
|
549
|
+
* with domain-action methods consumed by the CLI.
|
|
550
|
+
*/
|
|
551
|
+
var IdbFamilyDescriptor = class {
|
|
552
|
+
kind = "family";
|
|
553
|
+
id = "idb";
|
|
554
|
+
familyId = "idb";
|
|
555
|
+
version = "0.0.1";
|
|
556
|
+
emission = idbEmission;
|
|
557
|
+
/**
|
|
558
|
+
* Creates an {@link IdbControlFamilyInstance} for the given control stack.
|
|
559
|
+
*
|
|
560
|
+
* The instance is a plain object whose methods implement the CLI domain
|
|
561
|
+
* actions (validateContract, verify, sign, etc.).
|
|
562
|
+
*/
|
|
563
|
+
create(stack) {
|
|
564
|
+
return createIdbFamilyInstance(stack);
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region src/exports/control.ts
|
|
569
|
+
var control_default = new IdbFamilyDescriptor();
|
|
570
|
+
//#endregion
|
|
571
|
+
export { control_default as default };
|
|
572
|
+
|
|
573
|
+
//# sourceMappingURL=control.mjs.map
|