@prisma-next/family-mongo 0.0.1 → 0.3.0-dev.162
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/README.md +81 -28
- package/dist/control.d.mts +18 -0
- package/dist/control.d.mts.map +1 -0
- package/dist/control.mjs +647 -0
- package/dist/control.mjs.map +1 -0
- package/dist/pack.d.mts +13 -0
- package/dist/pack.d.mts.map +1 -0
- package/dist/pack.mjs +12 -0
- package/dist/pack.mjs.map +1 -0
- package/package.json +13 -7
- package/src/core/control-instance.ts +290 -15
- package/src/core/mongo-target-descriptor.ts +25 -2
- package/src/core/schema-diff.ts +402 -0
- package/src/core/schema-to-view.ts +128 -0
- package/src/exports/pack.ts +10 -0
package/dist/control.mjs
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import { mongoEmission } from "@prisma-next/mongo-emitter";
|
|
2
|
+
import { MongoMigrationPlanner, MongoMigrationRunner, contractToMongoSchemaIR, initMarker, introspectSchema, readMarker, updateMarker } from "@prisma-next/adapter-mongo/control";
|
|
3
|
+
import { SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_SCHEMA_FAILURE, VERIFY_CODE_TARGET_MISMATCH } from "@prisma-next/framework-components/control";
|
|
4
|
+
import { validateMongoContract } from "@prisma-next/mongo-contract";
|
|
5
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
6
|
+
import { canonicalize, deepEqual } from "@prisma-next/mongo-schema-ir";
|
|
7
|
+
import mongoTargetDescriptorMeta from "@prisma-next/target-mongo/pack";
|
|
8
|
+
|
|
9
|
+
//#region src/core/schema-diff.ts
|
|
10
|
+
function diffMongoSchemas(live, expected, strict) {
|
|
11
|
+
const issues = [];
|
|
12
|
+
const collectionChildren = [];
|
|
13
|
+
let pass = 0;
|
|
14
|
+
let warn = 0;
|
|
15
|
+
let fail = 0;
|
|
16
|
+
const allNames = new Set([...live.collectionNames, ...expected.collectionNames]);
|
|
17
|
+
for (const name of [...allNames].sort()) {
|
|
18
|
+
const liveColl = live.collection(name);
|
|
19
|
+
const expectedColl = expected.collection(name);
|
|
20
|
+
if (!liveColl && expectedColl) {
|
|
21
|
+
issues.push({
|
|
22
|
+
kind: "missing_table",
|
|
23
|
+
table: name,
|
|
24
|
+
message: `Collection "${name}" is missing from the database`
|
|
25
|
+
});
|
|
26
|
+
collectionChildren.push({
|
|
27
|
+
status: "fail",
|
|
28
|
+
kind: "collection",
|
|
29
|
+
name,
|
|
30
|
+
contractPath: `storage.collections.${name}`,
|
|
31
|
+
code: "MISSING_COLLECTION",
|
|
32
|
+
message: `Collection "${name}" is missing`,
|
|
33
|
+
expected: name,
|
|
34
|
+
actual: null,
|
|
35
|
+
children: []
|
|
36
|
+
});
|
|
37
|
+
fail++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (liveColl && !expectedColl) {
|
|
41
|
+
const status = strict ? "fail" : "warn";
|
|
42
|
+
issues.push({
|
|
43
|
+
kind: "extra_table",
|
|
44
|
+
table: name,
|
|
45
|
+
message: `Extra collection "${name}" exists in the database but not in the contract`
|
|
46
|
+
});
|
|
47
|
+
collectionChildren.push({
|
|
48
|
+
status,
|
|
49
|
+
kind: "collection",
|
|
50
|
+
name,
|
|
51
|
+
contractPath: `storage.collections.${name}`,
|
|
52
|
+
code: "EXTRA_COLLECTION",
|
|
53
|
+
message: `Extra collection "${name}" found`,
|
|
54
|
+
expected: null,
|
|
55
|
+
actual: name,
|
|
56
|
+
children: []
|
|
57
|
+
});
|
|
58
|
+
if (status === "fail") fail++;
|
|
59
|
+
else warn++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const lc = liveColl;
|
|
63
|
+
const ec = expectedColl;
|
|
64
|
+
const indexChildren = diffIndexes(name, lc, ec, strict, issues);
|
|
65
|
+
const validatorChildren = diffValidator(name, lc, ec, strict, issues);
|
|
66
|
+
const optionsChildren = diffOptions(name, lc, ec, strict, issues);
|
|
67
|
+
const children = [
|
|
68
|
+
...indexChildren,
|
|
69
|
+
...validatorChildren,
|
|
70
|
+
...optionsChildren
|
|
71
|
+
];
|
|
72
|
+
const worstStatus = children.reduce((s, c) => c.status === "fail" ? "fail" : c.status === "warn" && s !== "fail" ? "warn" : s, "pass");
|
|
73
|
+
for (const c of children) if (c.status === "pass") pass++;
|
|
74
|
+
else if (c.status === "warn") warn++;
|
|
75
|
+
else fail++;
|
|
76
|
+
if (children.length === 0) pass++;
|
|
77
|
+
collectionChildren.push({
|
|
78
|
+
status: worstStatus,
|
|
79
|
+
kind: "collection",
|
|
80
|
+
name,
|
|
81
|
+
contractPath: `storage.collections.${name}`,
|
|
82
|
+
code: worstStatus === "pass" ? "MATCH" : "DRIFT",
|
|
83
|
+
message: worstStatus === "pass" ? `Collection "${name}" matches` : `Collection "${name}" has drift`,
|
|
84
|
+
expected: name,
|
|
85
|
+
actual: name,
|
|
86
|
+
children
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const rootStatus = fail > 0 ? "fail" : warn > 0 ? "warn" : "pass";
|
|
90
|
+
const totalNodes = pass + warn + fail + collectionChildren.length;
|
|
91
|
+
return {
|
|
92
|
+
root: {
|
|
93
|
+
status: rootStatus,
|
|
94
|
+
kind: "root",
|
|
95
|
+
name: "mongo-schema",
|
|
96
|
+
contractPath: "storage",
|
|
97
|
+
code: rootStatus === "pass" ? "MATCH" : "DRIFT",
|
|
98
|
+
message: rootStatus === "pass" ? "Schema matches" : "Schema has drift",
|
|
99
|
+
expected: null,
|
|
100
|
+
actual: null,
|
|
101
|
+
children: collectionChildren
|
|
102
|
+
},
|
|
103
|
+
issues,
|
|
104
|
+
counts: {
|
|
105
|
+
pass,
|
|
106
|
+
warn,
|
|
107
|
+
fail,
|
|
108
|
+
totalNodes
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function buildIndexLookupKey(index) {
|
|
113
|
+
const keys = index.keys.map((k) => `${k.field}:${k.direction}`).join(",");
|
|
114
|
+
const opts = [
|
|
115
|
+
index.unique ? "unique" : "",
|
|
116
|
+
index.sparse ? "sparse" : "",
|
|
117
|
+
index.expireAfterSeconds != null ? `ttl:${index.expireAfterSeconds}` : "",
|
|
118
|
+
index.partialFilterExpression ? `pfe:${canonicalize(index.partialFilterExpression)}` : "",
|
|
119
|
+
index.wildcardProjection ? `wp:${canonicalize(index.wildcardProjection)}` : "",
|
|
120
|
+
index.collation ? `col:${canonicalize(index.collation)}` : "",
|
|
121
|
+
index.weights ? `wt:${canonicalize(index.weights)}` : "",
|
|
122
|
+
index.default_language ? `dl:${index.default_language}` : "",
|
|
123
|
+
index.language_override ? `lo:${index.language_override}` : ""
|
|
124
|
+
].filter(Boolean).join(";");
|
|
125
|
+
return opts ? `${keys}|${opts}` : keys;
|
|
126
|
+
}
|
|
127
|
+
function formatIndexName(index) {
|
|
128
|
+
return index.keys.map((k) => `${k.field}:${k.direction}`).join(", ");
|
|
129
|
+
}
|
|
130
|
+
function diffIndexes(collName, live, expected, strict, issues) {
|
|
131
|
+
const nodes = [];
|
|
132
|
+
const liveLookup = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const idx of live.indexes) liveLookup.set(buildIndexLookupKey(idx), idx);
|
|
134
|
+
const expectedLookup = /* @__PURE__ */ new Map();
|
|
135
|
+
for (const idx of expected.indexes) expectedLookup.set(buildIndexLookupKey(idx), idx);
|
|
136
|
+
for (const [key, idx] of expectedLookup) if (liveLookup.has(key)) nodes.push({
|
|
137
|
+
status: "pass",
|
|
138
|
+
kind: "index",
|
|
139
|
+
name: formatIndexName(idx),
|
|
140
|
+
contractPath: `storage.collections.${collName}.indexes`,
|
|
141
|
+
code: "MATCH",
|
|
142
|
+
message: `Index ${formatIndexName(idx)} matches`,
|
|
143
|
+
expected: key,
|
|
144
|
+
actual: key,
|
|
145
|
+
children: []
|
|
146
|
+
});
|
|
147
|
+
else {
|
|
148
|
+
issues.push({
|
|
149
|
+
kind: "index_mismatch",
|
|
150
|
+
table: collName,
|
|
151
|
+
indexOrConstraint: formatIndexName(idx),
|
|
152
|
+
message: `Index ${formatIndexName(idx)} missing on collection "${collName}"`
|
|
153
|
+
});
|
|
154
|
+
nodes.push({
|
|
155
|
+
status: "fail",
|
|
156
|
+
kind: "index",
|
|
157
|
+
name: formatIndexName(idx),
|
|
158
|
+
contractPath: `storage.collections.${collName}.indexes`,
|
|
159
|
+
code: "MISSING_INDEX",
|
|
160
|
+
message: `Index ${formatIndexName(idx)} missing`,
|
|
161
|
+
expected: key,
|
|
162
|
+
actual: null,
|
|
163
|
+
children: []
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
for (const [key, idx] of liveLookup) if (!expectedLookup.has(key)) {
|
|
167
|
+
const status = strict ? "fail" : "warn";
|
|
168
|
+
issues.push({
|
|
169
|
+
kind: "extra_index",
|
|
170
|
+
table: collName,
|
|
171
|
+
indexOrConstraint: formatIndexName(idx),
|
|
172
|
+
message: `Extra index ${formatIndexName(idx)} on collection "${collName}"`
|
|
173
|
+
});
|
|
174
|
+
nodes.push({
|
|
175
|
+
status,
|
|
176
|
+
kind: "index",
|
|
177
|
+
name: formatIndexName(idx),
|
|
178
|
+
contractPath: `storage.collections.${collName}.indexes`,
|
|
179
|
+
code: "EXTRA_INDEX",
|
|
180
|
+
message: `Extra index ${formatIndexName(idx)}`,
|
|
181
|
+
expected: null,
|
|
182
|
+
actual: key,
|
|
183
|
+
children: []
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return nodes;
|
|
187
|
+
}
|
|
188
|
+
function diffValidator(collName, live, expected, strict, issues) {
|
|
189
|
+
if (!live.validator && !expected.validator) return [];
|
|
190
|
+
if (expected.validator && !live.validator) {
|
|
191
|
+
issues.push({
|
|
192
|
+
kind: "type_missing",
|
|
193
|
+
table: collName,
|
|
194
|
+
message: `Validator missing on collection "${collName}"`
|
|
195
|
+
});
|
|
196
|
+
return [{
|
|
197
|
+
status: "fail",
|
|
198
|
+
kind: "validator",
|
|
199
|
+
name: "validator",
|
|
200
|
+
contractPath: `storage.collections.${collName}.validator`,
|
|
201
|
+
code: "MISSING_VALIDATOR",
|
|
202
|
+
message: "Validator missing",
|
|
203
|
+
expected: canonicalize(expected.validator.jsonSchema),
|
|
204
|
+
actual: null,
|
|
205
|
+
children: []
|
|
206
|
+
}];
|
|
207
|
+
}
|
|
208
|
+
if (!expected.validator && live.validator) {
|
|
209
|
+
const status = strict ? "fail" : "warn";
|
|
210
|
+
issues.push({
|
|
211
|
+
kind: "extra_validator",
|
|
212
|
+
table: collName,
|
|
213
|
+
message: `Extra validator on collection "${collName}"`
|
|
214
|
+
});
|
|
215
|
+
return [{
|
|
216
|
+
status,
|
|
217
|
+
kind: "validator",
|
|
218
|
+
name: "validator",
|
|
219
|
+
contractPath: `storage.collections.${collName}.validator`,
|
|
220
|
+
code: "EXTRA_VALIDATOR",
|
|
221
|
+
message: "Extra validator found",
|
|
222
|
+
expected: null,
|
|
223
|
+
actual: canonicalize(live.validator.jsonSchema),
|
|
224
|
+
children: []
|
|
225
|
+
}];
|
|
226
|
+
}
|
|
227
|
+
const liveVal = live.validator;
|
|
228
|
+
const expectedVal = expected.validator;
|
|
229
|
+
const liveSchema = canonicalize(liveVal.jsonSchema);
|
|
230
|
+
const expectedSchema = canonicalize(expectedVal.jsonSchema);
|
|
231
|
+
if (liveSchema !== expectedSchema || liveVal.validationLevel !== expectedVal.validationLevel || liveVal.validationAction !== expectedVal.validationAction) {
|
|
232
|
+
issues.push({
|
|
233
|
+
kind: "type_mismatch",
|
|
234
|
+
table: collName,
|
|
235
|
+
expected: expectedSchema,
|
|
236
|
+
actual: liveSchema,
|
|
237
|
+
message: `Validator mismatch on collection "${collName}"`
|
|
238
|
+
});
|
|
239
|
+
return [{
|
|
240
|
+
status: "fail",
|
|
241
|
+
kind: "validator",
|
|
242
|
+
name: "validator",
|
|
243
|
+
contractPath: `storage.collections.${collName}.validator`,
|
|
244
|
+
code: "VALIDATOR_MISMATCH",
|
|
245
|
+
message: "Validator mismatch",
|
|
246
|
+
expected: {
|
|
247
|
+
jsonSchema: expectedVal.jsonSchema,
|
|
248
|
+
validationLevel: expectedVal.validationLevel,
|
|
249
|
+
validationAction: expectedVal.validationAction
|
|
250
|
+
},
|
|
251
|
+
actual: {
|
|
252
|
+
jsonSchema: liveVal.jsonSchema,
|
|
253
|
+
validationLevel: liveVal.validationLevel,
|
|
254
|
+
validationAction: liveVal.validationAction
|
|
255
|
+
},
|
|
256
|
+
children: []
|
|
257
|
+
}];
|
|
258
|
+
}
|
|
259
|
+
return [{
|
|
260
|
+
status: "pass",
|
|
261
|
+
kind: "validator",
|
|
262
|
+
name: "validator",
|
|
263
|
+
contractPath: `storage.collections.${collName}.validator`,
|
|
264
|
+
code: "MATCH",
|
|
265
|
+
message: "Validator matches",
|
|
266
|
+
expected: expectedSchema,
|
|
267
|
+
actual: liveSchema,
|
|
268
|
+
children: []
|
|
269
|
+
}];
|
|
270
|
+
}
|
|
271
|
+
function diffOptions(collName, live, expected, strict, issues) {
|
|
272
|
+
if (!live.options && !expected.options) return [];
|
|
273
|
+
if (!expected.options && live.options) {
|
|
274
|
+
const status = strict ? "fail" : "warn";
|
|
275
|
+
issues.push({
|
|
276
|
+
kind: "type_mismatch",
|
|
277
|
+
table: collName,
|
|
278
|
+
actual: canonicalize(live.options),
|
|
279
|
+
message: `Extra collection options on "${collName}"`
|
|
280
|
+
});
|
|
281
|
+
return [{
|
|
282
|
+
status,
|
|
283
|
+
kind: "options",
|
|
284
|
+
name: "options",
|
|
285
|
+
contractPath: `storage.collections.${collName}.options`,
|
|
286
|
+
code: "EXTRA_OPTIONS",
|
|
287
|
+
message: "Extra collection options found",
|
|
288
|
+
expected: null,
|
|
289
|
+
actual: live.options,
|
|
290
|
+
children: []
|
|
291
|
+
}];
|
|
292
|
+
}
|
|
293
|
+
if (deepEqual(live.options, expected.options)) return [{
|
|
294
|
+
status: "pass",
|
|
295
|
+
kind: "options",
|
|
296
|
+
name: "options",
|
|
297
|
+
contractPath: `storage.collections.${collName}.options`,
|
|
298
|
+
code: "MATCH",
|
|
299
|
+
message: "Collection options match",
|
|
300
|
+
expected: canonicalize(expected.options),
|
|
301
|
+
actual: canonicalize(live.options),
|
|
302
|
+
children: []
|
|
303
|
+
}];
|
|
304
|
+
issues.push({
|
|
305
|
+
kind: "type_mismatch",
|
|
306
|
+
table: collName,
|
|
307
|
+
expected: canonicalize(expected.options),
|
|
308
|
+
actual: canonicalize(live.options),
|
|
309
|
+
message: `Collection options mismatch on "${collName}"`
|
|
310
|
+
});
|
|
311
|
+
return [{
|
|
312
|
+
status: "fail",
|
|
313
|
+
kind: "options",
|
|
314
|
+
name: "options",
|
|
315
|
+
contractPath: `storage.collections.${collName}.options`,
|
|
316
|
+
code: "OPTIONS_MISMATCH",
|
|
317
|
+
message: "Collection options mismatch",
|
|
318
|
+
expected: expected.options,
|
|
319
|
+
actual: live.options,
|
|
320
|
+
children: []
|
|
321
|
+
}];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/core/schema-to-view.ts
|
|
326
|
+
function mongoSchemaToView(schema) {
|
|
327
|
+
const collectionNodes = schema.collections.map((collection) => collectionToSchemaNode(collection.name, collection));
|
|
328
|
+
return { root: new SchemaTreeNode({
|
|
329
|
+
kind: "root",
|
|
330
|
+
id: "mongo-schema",
|
|
331
|
+
label: "database",
|
|
332
|
+
...ifDefined("children", collectionNodes.length > 0 ? collectionNodes : void 0)
|
|
333
|
+
}) };
|
|
334
|
+
}
|
|
335
|
+
function collectionToSchemaNode(name, collection) {
|
|
336
|
+
const children = [];
|
|
337
|
+
for (const index of collection.indexes) {
|
|
338
|
+
const keysSummary = index.keys.map((k) => {
|
|
339
|
+
if (k.direction === 1) return k.field;
|
|
340
|
+
if (k.direction === -1) return `${k.field} desc`;
|
|
341
|
+
return `${k.field} ${k.direction}`;
|
|
342
|
+
}).join(", ");
|
|
343
|
+
const prefix = index.unique ? "unique index" : "index";
|
|
344
|
+
const options = [];
|
|
345
|
+
if (index.sparse) options.push("sparse");
|
|
346
|
+
if (index.expireAfterSeconds != null) options.push(`ttl: ${index.expireAfterSeconds}s`);
|
|
347
|
+
if (index.partialFilterExpression) options.push("partial");
|
|
348
|
+
const optsSuffix = options.length > 0 ? ` (${options.join(", ")})` : "";
|
|
349
|
+
children.push(new SchemaTreeNode({
|
|
350
|
+
kind: "index",
|
|
351
|
+
id: `index-${name}-${index.keys.map((k) => `${k.field}_${k.direction}`).join("_")}`,
|
|
352
|
+
label: `${prefix} (${keysSummary})${optsSuffix}`,
|
|
353
|
+
meta: {
|
|
354
|
+
keys: index.keys,
|
|
355
|
+
unique: index.unique,
|
|
356
|
+
...ifDefined("sparse", index.sparse || void 0),
|
|
357
|
+
...ifDefined("expireAfterSeconds", index.expireAfterSeconds ?? void 0),
|
|
358
|
+
...ifDefined("partialFilterExpression", index.partialFilterExpression ?? void 0)
|
|
359
|
+
}
|
|
360
|
+
}));
|
|
361
|
+
}
|
|
362
|
+
if (collection.validator) {
|
|
363
|
+
const validatorChildren = [];
|
|
364
|
+
const jsonSchema = collection.validator.jsonSchema;
|
|
365
|
+
const properties = jsonSchema["properties"];
|
|
366
|
+
const required = new Set(jsonSchema["required"] ?? []);
|
|
367
|
+
if (properties) for (const [propName, propDef] of Object.entries(properties)) {
|
|
368
|
+
const bsonType = propDef["bsonType"] ?? "unknown";
|
|
369
|
+
const suffix = required.has(propName) ? " (required)" : "";
|
|
370
|
+
validatorChildren.push(new SchemaTreeNode({
|
|
371
|
+
kind: "field",
|
|
372
|
+
id: `field-${name}-${propName}`,
|
|
373
|
+
label: `${propName}: ${bsonType}${suffix}`
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
children.push(new SchemaTreeNode({
|
|
377
|
+
kind: "field",
|
|
378
|
+
id: `validator-${name}`,
|
|
379
|
+
label: `validator (level: ${collection.validator.validationLevel}, action: ${collection.validator.validationAction})`,
|
|
380
|
+
meta: {
|
|
381
|
+
validationLevel: collection.validator.validationLevel,
|
|
382
|
+
validationAction: collection.validator.validationAction,
|
|
383
|
+
jsonSchema: collection.validator.jsonSchema
|
|
384
|
+
},
|
|
385
|
+
...ifDefined("children", validatorChildren.length > 0 ? validatorChildren : void 0)
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
if (collection.options) {
|
|
389
|
+
const opts = collection.options;
|
|
390
|
+
const optLabels = [];
|
|
391
|
+
if (opts.capped) optLabels.push("capped");
|
|
392
|
+
if (opts.timeseries) optLabels.push("timeseries");
|
|
393
|
+
if (opts.collation) optLabels.push("collation");
|
|
394
|
+
if (opts.changeStreamPreAndPostImages) optLabels.push("changeStreamPreAndPostImages");
|
|
395
|
+
if (opts.clusteredIndex) optLabels.push("clusteredIndex");
|
|
396
|
+
if (optLabels.length > 0) children.push(new SchemaTreeNode({
|
|
397
|
+
kind: "field",
|
|
398
|
+
id: `options-${name}`,
|
|
399
|
+
label: `options (${optLabels.join(", ")})`,
|
|
400
|
+
meta: {
|
|
401
|
+
...ifDefined("capped", opts.capped ?? void 0),
|
|
402
|
+
...ifDefined("timeseries", opts.timeseries ?? void 0),
|
|
403
|
+
...ifDefined("collation", opts.collation ?? void 0),
|
|
404
|
+
...ifDefined("changeStreamPreAndPostImages", opts.changeStreamPreAndPostImages ?? void 0),
|
|
405
|
+
...ifDefined("clusteredIndex", opts.clusteredIndex ?? void 0)
|
|
406
|
+
}
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
return new SchemaTreeNode({
|
|
410
|
+
kind: "collection",
|
|
411
|
+
id: `collection-${name}`,
|
|
412
|
+
label: `collection ${name}`,
|
|
413
|
+
...ifDefined("children", children.length > 0 ? children : void 0)
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region src/core/control-instance.ts
|
|
419
|
+
function extractDb(driver) {
|
|
420
|
+
const mongoDriver = driver;
|
|
421
|
+
if (!mongoDriver.db) throw new Error("Mongo control driver does not expose a db property. Use createMongoControlDriver() from @prisma-next/adapter-mongo/control.");
|
|
422
|
+
return mongoDriver.db;
|
|
423
|
+
}
|
|
424
|
+
var MongoFamilyInstance = class {
|
|
425
|
+
familyId = "mongo";
|
|
426
|
+
validateContract(contractJson) {
|
|
427
|
+
return validateMongoContract(contractJson).contract;
|
|
428
|
+
}
|
|
429
|
+
async verify(options) {
|
|
430
|
+
const { driver, contract: rawContract, expectedTargetId, contractPath, configPath } = options;
|
|
431
|
+
const startTime = Date.now();
|
|
432
|
+
const contract = validateMongoContract(rawContract).contract;
|
|
433
|
+
const contractStorageHash = contract.storage.storageHash;
|
|
434
|
+
const contractProfileHash = contract.profileHash;
|
|
435
|
+
const contractTarget = contract.target;
|
|
436
|
+
const baseOpts = {
|
|
437
|
+
contractStorageHash,
|
|
438
|
+
contractProfileHash,
|
|
439
|
+
expectedTargetId,
|
|
440
|
+
contractPath,
|
|
441
|
+
...ifDefined("configPath", configPath)
|
|
442
|
+
};
|
|
443
|
+
if (contractTarget !== expectedTargetId) return buildVerifyResult({
|
|
444
|
+
...baseOpts,
|
|
445
|
+
ok: false,
|
|
446
|
+
code: VERIFY_CODE_TARGET_MISMATCH,
|
|
447
|
+
summary: "Target mismatch",
|
|
448
|
+
actualTargetId: contractTarget,
|
|
449
|
+
totalTime: Date.now() - startTime
|
|
450
|
+
});
|
|
451
|
+
const marker = await readMarker(extractDb(driver));
|
|
452
|
+
if (!marker) return buildVerifyResult({
|
|
453
|
+
...baseOpts,
|
|
454
|
+
ok: false,
|
|
455
|
+
code: VERIFY_CODE_MARKER_MISSING,
|
|
456
|
+
summary: "Marker missing",
|
|
457
|
+
totalTime: Date.now() - startTime
|
|
458
|
+
});
|
|
459
|
+
if (marker.storageHash !== contractStorageHash) return buildVerifyResult({
|
|
460
|
+
...baseOpts,
|
|
461
|
+
ok: false,
|
|
462
|
+
code: VERIFY_CODE_HASH_MISMATCH,
|
|
463
|
+
summary: "Hash mismatch",
|
|
464
|
+
marker,
|
|
465
|
+
totalTime: Date.now() - startTime
|
|
466
|
+
});
|
|
467
|
+
if (contractProfileHash && marker.profileHash !== contractProfileHash) return buildVerifyResult({
|
|
468
|
+
...baseOpts,
|
|
469
|
+
ok: false,
|
|
470
|
+
code: VERIFY_CODE_HASH_MISMATCH,
|
|
471
|
+
summary: "Hash mismatch",
|
|
472
|
+
marker,
|
|
473
|
+
totalTime: Date.now() - startTime
|
|
474
|
+
});
|
|
475
|
+
return buildVerifyResult({
|
|
476
|
+
...baseOpts,
|
|
477
|
+
ok: true,
|
|
478
|
+
summary: "Database matches contract",
|
|
479
|
+
marker,
|
|
480
|
+
totalTime: Date.now() - startTime
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
async schemaVerify(options) {
|
|
484
|
+
const { driver, contract: rawContract, strict, contractPath, configPath } = options;
|
|
485
|
+
const startTime = Date.now();
|
|
486
|
+
const contract = validateMongoContract(rawContract).contract;
|
|
487
|
+
const { root, issues, counts } = diffMongoSchemas(await introspectSchema(extractDb(driver)), contractToMongoSchemaIR(contract), strict);
|
|
488
|
+
const ok = counts.fail === 0;
|
|
489
|
+
return {
|
|
490
|
+
ok,
|
|
491
|
+
...ifDefined("code", ok ? void 0 : VERIFY_CODE_SCHEMA_FAILURE),
|
|
492
|
+
summary: ok ? "Schema matches contract" : `Schema verification found ${counts.fail} issue(s)`,
|
|
493
|
+
contract: {
|
|
494
|
+
storageHash: contract.storage.storageHash,
|
|
495
|
+
...ifDefined("profileHash", contract.profileHash)
|
|
496
|
+
},
|
|
497
|
+
target: { expected: contract.target },
|
|
498
|
+
schema: {
|
|
499
|
+
issues,
|
|
500
|
+
root,
|
|
501
|
+
counts
|
|
502
|
+
},
|
|
503
|
+
meta: {
|
|
504
|
+
...ifDefined("contractPath", contractPath),
|
|
505
|
+
...ifDefined("configPath", configPath),
|
|
506
|
+
strict
|
|
507
|
+
},
|
|
508
|
+
timings: { total: Date.now() - startTime }
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
async sign(options) {
|
|
512
|
+
const { driver, contract: rawContract, contractPath, configPath } = options;
|
|
513
|
+
const startTime = Date.now();
|
|
514
|
+
const contract = validateMongoContract(rawContract).contract;
|
|
515
|
+
const contractStorageHash = contract.storage.storageHash;
|
|
516
|
+
const contractProfileHash = contract.profileHash;
|
|
517
|
+
const db = extractDb(driver);
|
|
518
|
+
const existingMarker = await readMarker(db);
|
|
519
|
+
let markerCreated = false;
|
|
520
|
+
let markerUpdated = false;
|
|
521
|
+
let previousHashes;
|
|
522
|
+
if (!existingMarker) {
|
|
523
|
+
await initMarker(db, {
|
|
524
|
+
storageHash: contractStorageHash,
|
|
525
|
+
profileHash: contractProfileHash
|
|
526
|
+
});
|
|
527
|
+
markerCreated = true;
|
|
528
|
+
} else {
|
|
529
|
+
const storageHashMatches = existingMarker.storageHash === contractStorageHash;
|
|
530
|
+
const profileHashMatches = existingMarker.profileHash === contractProfileHash;
|
|
531
|
+
if (!storageHashMatches || !profileHashMatches) {
|
|
532
|
+
previousHashes = {
|
|
533
|
+
storageHash: existingMarker.storageHash,
|
|
534
|
+
profileHash: existingMarker.profileHash
|
|
535
|
+
};
|
|
536
|
+
if (!await updateMarker(db, existingMarker.storageHash, {
|
|
537
|
+
storageHash: contractStorageHash,
|
|
538
|
+
profileHash: contractProfileHash
|
|
539
|
+
})) throw new Error("CAS conflict: marker was modified by another process during sign");
|
|
540
|
+
markerUpdated = true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
let summary;
|
|
544
|
+
if (markerCreated) summary = "Database signed (marker created)";
|
|
545
|
+
else if (markerUpdated) summary = `Database signed (marker updated from ${previousHashes?.storageHash ?? "unknown"})`;
|
|
546
|
+
else summary = "Database already signed with this contract";
|
|
547
|
+
return {
|
|
548
|
+
ok: true,
|
|
549
|
+
summary,
|
|
550
|
+
contract: {
|
|
551
|
+
storageHash: contractStorageHash,
|
|
552
|
+
profileHash: contractProfileHash
|
|
553
|
+
},
|
|
554
|
+
target: {
|
|
555
|
+
expected: contract.target,
|
|
556
|
+
actual: contract.target
|
|
557
|
+
},
|
|
558
|
+
marker: {
|
|
559
|
+
created: markerCreated,
|
|
560
|
+
updated: markerUpdated,
|
|
561
|
+
...ifDefined("previous", previousHashes)
|
|
562
|
+
},
|
|
563
|
+
meta: {
|
|
564
|
+
contractPath,
|
|
565
|
+
...ifDefined("configPath", configPath)
|
|
566
|
+
},
|
|
567
|
+
timings: { total: Date.now() - startTime }
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
async readMarker(options) {
|
|
571
|
+
return readMarker(extractDb(options.driver));
|
|
572
|
+
}
|
|
573
|
+
async introspect(options) {
|
|
574
|
+
return introspectSchema(extractDb(options.driver));
|
|
575
|
+
}
|
|
576
|
+
toSchemaView(schema) {
|
|
577
|
+
return mongoSchemaToView(schema);
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
function buildVerifyResult(opts) {
|
|
581
|
+
return {
|
|
582
|
+
ok: opts.ok,
|
|
583
|
+
...ifDefined("code", opts.code),
|
|
584
|
+
summary: opts.summary,
|
|
585
|
+
contract: {
|
|
586
|
+
storageHash: opts.contractStorageHash,
|
|
587
|
+
...ifDefined("profileHash", opts.contractProfileHash)
|
|
588
|
+
},
|
|
589
|
+
...ifDefined("marker", opts.marker ? {
|
|
590
|
+
storageHash: opts.marker.storageHash,
|
|
591
|
+
profileHash: opts.marker.profileHash
|
|
592
|
+
} : void 0),
|
|
593
|
+
target: {
|
|
594
|
+
expected: opts.expectedTargetId,
|
|
595
|
+
...ifDefined("actual", opts.actualTargetId)
|
|
596
|
+
},
|
|
597
|
+
meta: {
|
|
598
|
+
contractPath: opts.contractPath,
|
|
599
|
+
...ifDefined("configPath", opts.configPath)
|
|
600
|
+
},
|
|
601
|
+
timings: { total: opts.totalTime }
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function createMongoFamilyInstance(_controlStack) {
|
|
605
|
+
return new MongoFamilyInstance();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/core/control-descriptor.ts
|
|
610
|
+
var MongoFamilyDescriptor = class {
|
|
611
|
+
kind = "family";
|
|
612
|
+
id = "mongo";
|
|
613
|
+
familyId = "mongo";
|
|
614
|
+
version = "0.0.1";
|
|
615
|
+
emission = mongoEmission;
|
|
616
|
+
create(stack) {
|
|
617
|
+
return createMongoFamilyInstance(stack);
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
const mongoFamilyDescriptor = new MongoFamilyDescriptor();
|
|
621
|
+
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/core/mongo-target-descriptor.ts
|
|
624
|
+
const mongoTargetDescriptor = {
|
|
625
|
+
...mongoTargetDescriptorMeta,
|
|
626
|
+
migrations: {
|
|
627
|
+
createPlanner(_family) {
|
|
628
|
+
return new MongoMigrationPlanner();
|
|
629
|
+
},
|
|
630
|
+
createRunner(_family) {
|
|
631
|
+
return new MongoMigrationRunner();
|
|
632
|
+
},
|
|
633
|
+
contractToSchema(contract) {
|
|
634
|
+
return contractToMongoSchemaIR(contract);
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
create() {
|
|
638
|
+
return {
|
|
639
|
+
familyId: "mongo",
|
|
640
|
+
targetId: "mongo"
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
//#endregion
|
|
646
|
+
export { createMongoFamilyInstance, mongoFamilyDescriptor, mongoTargetDescriptor };
|
|
647
|
+
//# sourceMappingURL=control.mjs.map
|