@prisma-next/target-mongo 0.4.0-dev.8 → 0.4.1
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/dist/control.d.mts +25 -12
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +354 -108
- package/dist/control.mjs.map +1 -1
- package/dist/{migration-factories-Brzz-QGG.mjs → migration-factories-gwi81C8u.mjs} +43 -3
- package/dist/migration-factories-gwi81C8u.mjs.map +1 -0
- package/dist/migration.d.mts +16 -3
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +2 -2
- package/dist/{op-factory-call-CfPGebEH.d.mts → op-factory-call-BjNAcPSF.d.mts} +26 -25
- package/dist/op-factory-call-BjNAcPSF.d.mts.map +1 -0
- package/package.json +12 -10
- package/src/core/migration-factories.ts +69 -1
- package/src/core/mongo-ops-serializer.ts +324 -5
- package/src/core/mongo-planner.ts +5 -6
- package/src/core/mongo-runner.ts +144 -8
- package/src/core/op-factory-call.ts +81 -26
- package/src/core/render-ops.ts +2 -35
- package/src/core/render-typescript.ts +25 -78
- package/src/exports/control.ts +1 -1
- package/src/exports/migration.ts +1 -0
- package/dist/migration-factories-Brzz-QGG.mjs.map +0 -1
- package/dist/op-factory-call-CfPGebEH.d.mts.map +0 -1
package/dist/control.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as dropCollection, n as createCollection, o as dropIndex, r as createIndex, t as collMod } from "./migration-factories-gwi81C8u.mjs";
|
|
2
2
|
import { MongoSchemaCollection, MongoSchemaCollectionOptions, MongoSchemaIR, MongoSchemaIndex, MongoSchemaValidator, canonicalize, deepEqual } from "@prisma-next/mongo-schema-ir";
|
|
3
|
-
import { RawAggregateCommand, RawFindOneAndUpdateCommand, RawInsertOneCommand } from "@prisma-next/mongo-query-ast/execution";
|
|
3
|
+
import { AggregateCommand, MongoAddFieldsStage, MongoLimitStage, MongoLookupStage, MongoMatchStage, MongoMergeStage, MongoProjectStage, MongoSortStage, RawAggregateCommand, RawDeleteManyCommand, RawDeleteOneCommand, RawFindOneAndDeleteCommand, RawFindOneAndUpdateCommand, RawInsertManyCommand, RawInsertOneCommand, RawUpdateManyCommand, RawUpdateOneCommand } from "@prisma-next/mongo-query-ast/execution";
|
|
4
4
|
import { CollModCommand, CreateCollectionCommand, CreateIndexCommand, DropCollectionCommand, DropIndexCommand, ListCollectionsCommand, ListIndexesCommand, MongoAndExpr, MongoExistsExpr, MongoFieldFilter, MongoNotExpr, MongoOrExpr } from "@prisma-next/mongo-query-ast/control";
|
|
5
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
5
6
|
import { type } from "arktype";
|
|
7
|
+
import { TsExpression, jsonToTsSource, renderImports } from "@prisma-next/ts-render";
|
|
6
8
|
import { Migration } from "@prisma-next/migration-tools/migration";
|
|
7
|
-
import { ifDefined } from "@prisma-next/utils/defined";
|
|
8
9
|
import { detectScaffoldRuntime, shebangLineFor } from "@prisma-next/migration-tools/migration-ts";
|
|
10
|
+
import { errorRunnerFailed } from "@prisma-next/errors/execution";
|
|
9
11
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
10
12
|
|
|
11
13
|
//#region src/core/contract-to-schema.ts
|
|
@@ -295,6 +297,76 @@ const ExistsFilterJson = type({
|
|
|
295
297
|
field: "string",
|
|
296
298
|
exists: "boolean"
|
|
297
299
|
});
|
|
300
|
+
const RawInsertOneJson = type({
|
|
301
|
+
kind: "\"rawInsertOne\"",
|
|
302
|
+
collection: "string",
|
|
303
|
+
document: "Record<string, unknown>"
|
|
304
|
+
});
|
|
305
|
+
const RawInsertManyJson = type({
|
|
306
|
+
kind: "\"rawInsertMany\"",
|
|
307
|
+
collection: "string",
|
|
308
|
+
documents: "Record<string, unknown>[]"
|
|
309
|
+
});
|
|
310
|
+
const RawUpdateOneJson = type({
|
|
311
|
+
kind: "\"rawUpdateOne\"",
|
|
312
|
+
collection: "string",
|
|
313
|
+
filter: "Record<string, unknown>",
|
|
314
|
+
update: "Record<string, unknown> | Record<string, unknown>[]"
|
|
315
|
+
});
|
|
316
|
+
const RawUpdateManyJson = type({
|
|
317
|
+
kind: "\"rawUpdateMany\"",
|
|
318
|
+
collection: "string",
|
|
319
|
+
filter: "Record<string, unknown>",
|
|
320
|
+
update: "Record<string, unknown> | Record<string, unknown>[]"
|
|
321
|
+
});
|
|
322
|
+
const RawDeleteOneJson = type({
|
|
323
|
+
kind: "\"rawDeleteOne\"",
|
|
324
|
+
collection: "string",
|
|
325
|
+
filter: "Record<string, unknown>"
|
|
326
|
+
});
|
|
327
|
+
const RawDeleteManyJson = type({
|
|
328
|
+
kind: "\"rawDeleteMany\"",
|
|
329
|
+
collection: "string",
|
|
330
|
+
filter: "Record<string, unknown>"
|
|
331
|
+
});
|
|
332
|
+
const RawAggregateJson = type({
|
|
333
|
+
kind: "\"rawAggregate\"",
|
|
334
|
+
collection: "string",
|
|
335
|
+
pipeline: "Record<string, unknown>[]"
|
|
336
|
+
});
|
|
337
|
+
const RawFindOneAndUpdateJson = type({
|
|
338
|
+
kind: "\"rawFindOneAndUpdate\"",
|
|
339
|
+
collection: "string",
|
|
340
|
+
filter: "Record<string, unknown>",
|
|
341
|
+
update: "Record<string, unknown> | Record<string, unknown>[]",
|
|
342
|
+
upsert: "boolean"
|
|
343
|
+
});
|
|
344
|
+
const RawFindOneAndDeleteJson = type({
|
|
345
|
+
kind: "\"rawFindOneAndDelete\"",
|
|
346
|
+
collection: "string",
|
|
347
|
+
filter: "Record<string, unknown>"
|
|
348
|
+
});
|
|
349
|
+
const TypedAggregateJson = type({
|
|
350
|
+
kind: "\"aggregate\"",
|
|
351
|
+
collection: "string",
|
|
352
|
+
pipeline: "Record<string, unknown>[]"
|
|
353
|
+
});
|
|
354
|
+
const QueryPlanJson = type({
|
|
355
|
+
collection: "string",
|
|
356
|
+
command: "Record<string, unknown>",
|
|
357
|
+
meta: type({
|
|
358
|
+
target: "string",
|
|
359
|
+
storageHash: "string",
|
|
360
|
+
lane: "string",
|
|
361
|
+
paramDescriptors: "unknown[]",
|
|
362
|
+
"targetFamily?": "string",
|
|
363
|
+
"profileHash?": "string",
|
|
364
|
+
"annotations?": "Record<string, unknown>",
|
|
365
|
+
"refs?": "Record<string, unknown>",
|
|
366
|
+
"projection?": "Record<string, string> | string[]",
|
|
367
|
+
"projectionTypes?": "Record<string, string>"
|
|
368
|
+
})
|
|
369
|
+
});
|
|
298
370
|
const CheckJson = type({
|
|
299
371
|
description: "string",
|
|
300
372
|
source: "Record<string, unknown>",
|
|
@@ -305,7 +377,7 @@ const StepJson = type({
|
|
|
305
377
|
description: "string",
|
|
306
378
|
command: "Record<string, unknown>"
|
|
307
379
|
});
|
|
308
|
-
const
|
|
380
|
+
const DdlOperationJson = type({
|
|
309
381
|
id: "string",
|
|
310
382
|
label: "string",
|
|
311
383
|
operationClass: "\"additive\" | \"widening\" | \"destructive\"",
|
|
@@ -313,6 +385,21 @@ const OperationJson = type({
|
|
|
313
385
|
execute: "Record<string, unknown>[]",
|
|
314
386
|
postcheck: "Record<string, unknown>[]"
|
|
315
387
|
});
|
|
388
|
+
const DataTransformCheckJson = type({
|
|
389
|
+
description: "string",
|
|
390
|
+
source: "Record<string, unknown>",
|
|
391
|
+
filter: "Record<string, unknown>",
|
|
392
|
+
expect: "\"exists\" | \"notExists\""
|
|
393
|
+
});
|
|
394
|
+
const DataTransformOperationJson = type({
|
|
395
|
+
id: "string",
|
|
396
|
+
label: "string",
|
|
397
|
+
operationClass: "\"data\"",
|
|
398
|
+
name: "string",
|
|
399
|
+
precheck: "Record<string, unknown>[]",
|
|
400
|
+
run: "Record<string, unknown>[]",
|
|
401
|
+
postcheck: "Record<string, unknown>[]"
|
|
402
|
+
});
|
|
316
403
|
function validate(schema, data, context) {
|
|
317
404
|
try {
|
|
318
405
|
return schema.assert(data);
|
|
@@ -353,6 +440,108 @@ function deserializeFilterExpr(json) {
|
|
|
353
440
|
default: throw new Error(`Unknown filter expression kind: ${kind}`);
|
|
354
441
|
}
|
|
355
442
|
}
|
|
443
|
+
function deserializePipelineStage(json) {
|
|
444
|
+
const record = json;
|
|
445
|
+
const kind = record["kind"];
|
|
446
|
+
switch (kind) {
|
|
447
|
+
case "match": return new MongoMatchStage(deserializeFilterExpr(record["filter"]));
|
|
448
|
+
case "limit": return new MongoLimitStage(record["limit"]);
|
|
449
|
+
case "sort": return new MongoSortStage(record["sort"]);
|
|
450
|
+
case "project": return new MongoProjectStage(record["projection"]);
|
|
451
|
+
case "addFields": return new MongoAddFieldsStage(record["fields"]);
|
|
452
|
+
case "lookup": {
|
|
453
|
+
const opts = {
|
|
454
|
+
from: record["from"],
|
|
455
|
+
as: record["as"]
|
|
456
|
+
};
|
|
457
|
+
if (record["localField"] !== void 0) opts.localField = record["localField"];
|
|
458
|
+
if (record["foreignField"] !== void 0) opts.foreignField = record["foreignField"];
|
|
459
|
+
if (record["pipeline"] !== void 0) opts.pipeline = record["pipeline"].map(deserializePipelineStage);
|
|
460
|
+
if (record["let_"] !== void 0) opts.let_ = record["let_"];
|
|
461
|
+
return new MongoLookupStage(opts);
|
|
462
|
+
}
|
|
463
|
+
case "merge": {
|
|
464
|
+
const opts = { into: record["into"] };
|
|
465
|
+
if (record["on"] !== void 0) opts.on = record["on"];
|
|
466
|
+
if (record["whenMatched"] !== void 0) {
|
|
467
|
+
const wm = record["whenMatched"];
|
|
468
|
+
opts.whenMatched = typeof wm === "string" ? wm : wm.map(deserializePipelineStage);
|
|
469
|
+
}
|
|
470
|
+
if (record["whenNotMatched"] !== void 0) opts.whenNotMatched = record["whenNotMatched"];
|
|
471
|
+
return new MongoMergeStage(opts);
|
|
472
|
+
}
|
|
473
|
+
default: throw new Error(`Unknown pipeline stage kind: ${kind}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function deserializeDmlCommand(json) {
|
|
477
|
+
const kind = json["kind"];
|
|
478
|
+
switch (kind) {
|
|
479
|
+
case "rawInsertOne": {
|
|
480
|
+
const data = validate(RawInsertOneJson, json, "rawInsertOne command");
|
|
481
|
+
return new RawInsertOneCommand(data.collection, data.document);
|
|
482
|
+
}
|
|
483
|
+
case "rawInsertMany": {
|
|
484
|
+
const data = validate(RawInsertManyJson, json, "rawInsertMany command");
|
|
485
|
+
return new RawInsertManyCommand(data.collection, data.documents);
|
|
486
|
+
}
|
|
487
|
+
case "rawUpdateOne": {
|
|
488
|
+
const data = validate(RawUpdateOneJson, json, "rawUpdateOne command");
|
|
489
|
+
return new RawUpdateOneCommand(data.collection, data.filter, data.update);
|
|
490
|
+
}
|
|
491
|
+
case "rawUpdateMany": {
|
|
492
|
+
const data = validate(RawUpdateManyJson, json, "rawUpdateMany command");
|
|
493
|
+
return new RawUpdateManyCommand(data.collection, data.filter, data.update);
|
|
494
|
+
}
|
|
495
|
+
case "rawDeleteOne": {
|
|
496
|
+
const data = validate(RawDeleteOneJson, json, "rawDeleteOne command");
|
|
497
|
+
return new RawDeleteOneCommand(data.collection, data.filter);
|
|
498
|
+
}
|
|
499
|
+
case "rawDeleteMany": {
|
|
500
|
+
const data = validate(RawDeleteManyJson, json, "rawDeleteMany command");
|
|
501
|
+
return new RawDeleteManyCommand(data.collection, data.filter);
|
|
502
|
+
}
|
|
503
|
+
case "rawAggregate": {
|
|
504
|
+
const data = validate(RawAggregateJson, json, "rawAggregate command");
|
|
505
|
+
return new RawAggregateCommand(data.collection, data.pipeline);
|
|
506
|
+
}
|
|
507
|
+
case "rawFindOneAndUpdate": {
|
|
508
|
+
const data = validate(RawFindOneAndUpdateJson, json, "rawFindOneAndUpdate command");
|
|
509
|
+
return new RawFindOneAndUpdateCommand(data.collection, data.filter, data.update, data.upsert);
|
|
510
|
+
}
|
|
511
|
+
case "rawFindOneAndDelete": {
|
|
512
|
+
const data = validate(RawFindOneAndDeleteJson, json, "rawFindOneAndDelete command");
|
|
513
|
+
return new RawFindOneAndDeleteCommand(data.collection, data.filter);
|
|
514
|
+
}
|
|
515
|
+
case "aggregate": {
|
|
516
|
+
const data = validate(TypedAggregateJson, json, "aggregate command");
|
|
517
|
+
const pipeline = data.pipeline.map(deserializePipelineStage);
|
|
518
|
+
return new AggregateCommand(data.collection, pipeline);
|
|
519
|
+
}
|
|
520
|
+
default: throw new Error(`Unknown DML command kind: ${kind}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function deserializeMongoQueryPlan(json) {
|
|
524
|
+
const data = validate(QueryPlanJson, json, "Mongo query plan");
|
|
525
|
+
const command = deserializeDmlCommand(data.command);
|
|
526
|
+
const m = data.meta;
|
|
527
|
+
const meta = {
|
|
528
|
+
target: m.target,
|
|
529
|
+
storageHash: m.storageHash,
|
|
530
|
+
lane: m.lane,
|
|
531
|
+
paramDescriptors: m.paramDescriptors,
|
|
532
|
+
...ifDefined("targetFamily", m.targetFamily),
|
|
533
|
+
...ifDefined("profileHash", m.profileHash),
|
|
534
|
+
...ifDefined("annotations", m.annotations),
|
|
535
|
+
...ifDefined("refs", m.refs),
|
|
536
|
+
...ifDefined("projection", m.projection),
|
|
537
|
+
...ifDefined("projectionTypes", m.projectionTypes)
|
|
538
|
+
};
|
|
539
|
+
return {
|
|
540
|
+
collection: data.collection,
|
|
541
|
+
command,
|
|
542
|
+
meta
|
|
543
|
+
};
|
|
544
|
+
}
|
|
356
545
|
function deserializeDdlCommand(json) {
|
|
357
546
|
const kind = json["kind"];
|
|
358
547
|
switch (kind) {
|
|
@@ -429,8 +618,11 @@ function deserializeStep(json) {
|
|
|
429
618
|
command: deserializeDdlCommand(data.command)
|
|
430
619
|
};
|
|
431
620
|
}
|
|
432
|
-
function
|
|
433
|
-
|
|
621
|
+
function isDataTransformJson(json) {
|
|
622
|
+
return typeof json === "object" && json !== null && json["operationClass"] === "data";
|
|
623
|
+
}
|
|
624
|
+
function deserializeDdlOp(json) {
|
|
625
|
+
const data = validate(DdlOperationJson, json, "migration operation");
|
|
434
626
|
return {
|
|
435
627
|
id: data.id,
|
|
436
628
|
label: data.label,
|
|
@@ -440,6 +632,31 @@ function deserializeMongoOp(json) {
|
|
|
440
632
|
postcheck: data.postcheck.map(deserializeCheck)
|
|
441
633
|
};
|
|
442
634
|
}
|
|
635
|
+
function deserializeDataTransformCheck(json) {
|
|
636
|
+
const data = validate(DataTransformCheckJson, json, "data transform check");
|
|
637
|
+
return {
|
|
638
|
+
description: data.description,
|
|
639
|
+
source: deserializeMongoQueryPlan(data.source),
|
|
640
|
+
filter: deserializeFilterExpr(data.filter),
|
|
641
|
+
expect: data.expect
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function deserializeDataTransformOp(json) {
|
|
645
|
+
const data = validate(DataTransformOperationJson, json, "data transform operation");
|
|
646
|
+
return {
|
|
647
|
+
id: data.id,
|
|
648
|
+
label: data.label,
|
|
649
|
+
operationClass: "data",
|
|
650
|
+
name: data.name,
|
|
651
|
+
precheck: data.precheck.map(deserializeDataTransformCheck),
|
|
652
|
+
run: data.run.map(deserializeMongoQueryPlan),
|
|
653
|
+
postcheck: data.postcheck.map(deserializeDataTransformCheck)
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function deserializeMongoOp(json) {
|
|
657
|
+
if (isDataTransformJson(json)) return deserializeDataTransformOp(json);
|
|
658
|
+
return deserializeDdlOp(json);
|
|
659
|
+
}
|
|
443
660
|
function deserializeMongoOps(json) {
|
|
444
661
|
return json.map(deserializeMongoOp);
|
|
445
662
|
}
|
|
@@ -449,7 +666,14 @@ function serializeMongoOps(ops) {
|
|
|
449
666
|
|
|
450
667
|
//#endregion
|
|
451
668
|
//#region src/core/op-factory-call.ts
|
|
452
|
-
|
|
669
|
+
const TARGET_MIGRATION_MODULE = "@prisma-next/target-mongo/migration";
|
|
670
|
+
var OpFactoryCallNode = class extends TsExpression {
|
|
671
|
+
importRequirements() {
|
|
672
|
+
return [{
|
|
673
|
+
moduleSpecifier: TARGET_MIGRATION_MODULE,
|
|
674
|
+
symbol: this.factoryName
|
|
675
|
+
}];
|
|
676
|
+
}
|
|
453
677
|
freeze() {
|
|
454
678
|
Object.freeze(this);
|
|
455
679
|
}
|
|
@@ -458,7 +682,7 @@ function formatKeys(keys) {
|
|
|
458
682
|
return keys.map((k) => `${k.field}:${k.direction}`).join(", ");
|
|
459
683
|
}
|
|
460
684
|
var CreateIndexCall = class extends OpFactoryCallNode {
|
|
461
|
-
|
|
685
|
+
factoryName = "createIndex";
|
|
462
686
|
operationClass = "additive";
|
|
463
687
|
collection;
|
|
464
688
|
keys;
|
|
@@ -472,12 +696,15 @@ var CreateIndexCall = class extends OpFactoryCallNode {
|
|
|
472
696
|
this.label = `Create index on ${collection} (${formatKeys(keys)})`;
|
|
473
697
|
this.freeze();
|
|
474
698
|
}
|
|
475
|
-
|
|
476
|
-
return
|
|
699
|
+
toOp() {
|
|
700
|
+
return createIndex(this.collection, this.keys, this.options);
|
|
701
|
+
}
|
|
702
|
+
renderTypeScript() {
|
|
703
|
+
return this.options ? `createIndex(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.keys)}, ${jsonToTsSource(this.options)})` : `createIndex(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.keys)})`;
|
|
477
704
|
}
|
|
478
705
|
};
|
|
479
706
|
var DropIndexCall = class extends OpFactoryCallNode {
|
|
480
|
-
|
|
707
|
+
factoryName = "dropIndex";
|
|
481
708
|
operationClass = "destructive";
|
|
482
709
|
collection;
|
|
483
710
|
keys;
|
|
@@ -489,12 +716,15 @@ var DropIndexCall = class extends OpFactoryCallNode {
|
|
|
489
716
|
this.label = `Drop index on ${collection} (${formatKeys(keys)})`;
|
|
490
717
|
this.freeze();
|
|
491
718
|
}
|
|
492
|
-
|
|
493
|
-
return
|
|
719
|
+
toOp() {
|
|
720
|
+
return dropIndex(this.collection, this.keys);
|
|
721
|
+
}
|
|
722
|
+
renderTypeScript() {
|
|
723
|
+
return `dropIndex(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.keys)})`;
|
|
494
724
|
}
|
|
495
725
|
};
|
|
496
726
|
var CreateCollectionCall = class extends OpFactoryCallNode {
|
|
497
|
-
|
|
727
|
+
factoryName = "createCollection";
|
|
498
728
|
operationClass = "additive";
|
|
499
729
|
collection;
|
|
500
730
|
options;
|
|
@@ -506,12 +736,15 @@ var CreateCollectionCall = class extends OpFactoryCallNode {
|
|
|
506
736
|
this.label = `Create collection ${collection}`;
|
|
507
737
|
this.freeze();
|
|
508
738
|
}
|
|
509
|
-
|
|
510
|
-
return
|
|
739
|
+
toOp() {
|
|
740
|
+
return createCollection(this.collection, this.options);
|
|
741
|
+
}
|
|
742
|
+
renderTypeScript() {
|
|
743
|
+
return this.options ? `createCollection(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.options)})` : `createCollection(${jsonToTsSource(this.collection)})`;
|
|
511
744
|
}
|
|
512
745
|
};
|
|
513
746
|
var DropCollectionCall = class extends OpFactoryCallNode {
|
|
514
|
-
|
|
747
|
+
factoryName = "dropCollection";
|
|
515
748
|
operationClass = "destructive";
|
|
516
749
|
collection;
|
|
517
750
|
label;
|
|
@@ -521,12 +754,15 @@ var DropCollectionCall = class extends OpFactoryCallNode {
|
|
|
521
754
|
this.label = `Drop collection ${collection}`;
|
|
522
755
|
this.freeze();
|
|
523
756
|
}
|
|
524
|
-
|
|
525
|
-
return
|
|
757
|
+
toOp() {
|
|
758
|
+
return dropCollection(this.collection);
|
|
759
|
+
}
|
|
760
|
+
renderTypeScript() {
|
|
761
|
+
return `dropCollection(${jsonToTsSource(this.collection)})`;
|
|
526
762
|
}
|
|
527
763
|
};
|
|
528
764
|
var CollModCall = class extends OpFactoryCallNode {
|
|
529
|
-
|
|
765
|
+
factoryName = "collMod";
|
|
530
766
|
collection;
|
|
531
767
|
options;
|
|
532
768
|
meta;
|
|
@@ -541,8 +777,11 @@ var CollModCall = class extends OpFactoryCallNode {
|
|
|
541
777
|
this.label = meta?.label ?? `Modify collection ${collection}`;
|
|
542
778
|
this.freeze();
|
|
543
779
|
}
|
|
544
|
-
|
|
545
|
-
return
|
|
780
|
+
toOp() {
|
|
781
|
+
return collMod(this.collection, this.options, this.meta);
|
|
782
|
+
}
|
|
783
|
+
renderTypeScript() {
|
|
784
|
+
return this.meta ? `collMod(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.options)}, ${jsonToTsSource(this.meta)})` : `collMod(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.options)})`;
|
|
546
785
|
}
|
|
547
786
|
};
|
|
548
787
|
function schemaIndexToCreateIndexOptions(index) {
|
|
@@ -582,41 +821,36 @@ function schemaCollectionToCreateCollectionOptions(coll) {
|
|
|
582
821
|
|
|
583
822
|
//#endregion
|
|
584
823
|
//#region src/core/render-ops.ts
|
|
585
|
-
const renderVisitor = {
|
|
586
|
-
createIndex(call) {
|
|
587
|
-
return createIndex(call.collection, call.keys, call.options);
|
|
588
|
-
},
|
|
589
|
-
dropIndex(call) {
|
|
590
|
-
return dropIndex(call.collection, call.keys);
|
|
591
|
-
},
|
|
592
|
-
createCollection(call) {
|
|
593
|
-
return createCollection(call.collection, call.options);
|
|
594
|
-
},
|
|
595
|
-
dropCollection(call) {
|
|
596
|
-
return dropCollection(call.collection);
|
|
597
|
-
},
|
|
598
|
-
collMod(call) {
|
|
599
|
-
return collMod(call.collection, call.options, call.meta);
|
|
600
|
-
}
|
|
601
|
-
};
|
|
602
824
|
function renderOps(calls) {
|
|
603
|
-
return calls.map((call) => call.
|
|
825
|
+
return calls.map((call) => call.toOp());
|
|
604
826
|
}
|
|
605
827
|
|
|
606
828
|
//#endregion
|
|
607
829
|
//#region src/core/render-typescript.ts
|
|
830
|
+
const BASE_IMPORT = {
|
|
831
|
+
moduleSpecifier: "@prisma-next/family-mongo/migration",
|
|
832
|
+
symbol: "Migration"
|
|
833
|
+
};
|
|
608
834
|
/**
|
|
609
|
-
* Render a list of Mongo `OpFactoryCall`s as a
|
|
835
|
+
* Render a list of Mongo `OpFactoryCall`s as a `migration.ts`
|
|
610
836
|
* source string. The result is shebanged, extends the user-facing
|
|
611
837
|
* `Migration` (i.e. `MongoMigration`) from `@prisma-next/family-mongo`, and
|
|
612
838
|
* implements the abstract `operations` and `describe` members. `meta` is
|
|
613
839
|
* always rendered — `describe()` is part of the `Migration` contract, so
|
|
614
840
|
* even an empty stub must satisfy it; callers pass empty strings for a
|
|
615
841
|
* migration-new scaffold.
|
|
842
|
+
*
|
|
843
|
+
* The walk is polymorphic: each call node contributes its own
|
|
844
|
+
* `renderTypeScript()` expression and declares its own
|
|
845
|
+
* `importRequirements()`. The top-level renderer aggregates imports
|
|
846
|
+
* across all nodes and emits one `import { … } from "…"` line per module.
|
|
847
|
+
* The `Migration` import from `@prisma-next/family-mongo/migration` is
|
|
848
|
+
* always emitted — it's driven by `meta` (the rendered scaffold always
|
|
849
|
+
* extends `Migration`), not by any node.
|
|
616
850
|
*/
|
|
617
851
|
function renderCallsToTypeScript(calls, meta) {
|
|
618
|
-
const imports = buildImports(
|
|
619
|
-
const operationsBody = calls.map((c) => c.
|
|
852
|
+
const imports = buildImports(calls);
|
|
853
|
+
const operationsBody = calls.map((c) => c.renderTypeScript()).join(",\n");
|
|
620
854
|
return [
|
|
621
855
|
shebangLineFor(detectScaffoldRuntime()),
|
|
622
856
|
imports,
|
|
@@ -635,15 +869,10 @@ function renderCallsToTypeScript(calls, meta) {
|
|
|
635
869
|
""
|
|
636
870
|
].join("\n");
|
|
637
871
|
}
|
|
638
|
-
function
|
|
639
|
-
const
|
|
640
|
-
for (const call of calls)
|
|
641
|
-
return
|
|
642
|
-
}
|
|
643
|
-
function buildImports(factoryNames) {
|
|
644
|
-
const lines = ["import { Migration } from '@prisma-next/family-mongo/migration';"];
|
|
645
|
-
if (factoryNames.length > 0) lines.push(`import { ${factoryNames.join(", ")} } from '@prisma-next/target-mongo/migration';`);
|
|
646
|
-
return lines.join("\n");
|
|
872
|
+
function buildImports(calls) {
|
|
873
|
+
const requirements = [BASE_IMPORT];
|
|
874
|
+
for (const call of calls) for (const req of call.importRequirements()) requirements.push(req);
|
|
875
|
+
return renderImports(requirements);
|
|
647
876
|
}
|
|
648
877
|
function buildDescribeMethod(meta) {
|
|
649
878
|
const lines = [];
|
|
@@ -652,55 +881,12 @@ function buildDescribeMethod(meta) {
|
|
|
652
881
|
lines.push(` from: ${JSON.stringify(meta.from)},`);
|
|
653
882
|
lines.push(` to: ${JSON.stringify(meta.to)},`);
|
|
654
883
|
if (meta.kind) lines.push(` kind: ${JSON.stringify(meta.kind)},`);
|
|
655
|
-
if (meta.labels && meta.labels.length > 0) lines.push(` labels: ${
|
|
884
|
+
if (meta.labels && meta.labels.length > 0) lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
|
|
656
885
|
lines.push(" };");
|
|
657
886
|
lines.push(" }");
|
|
658
887
|
lines.push("");
|
|
659
888
|
return lines.join("\n");
|
|
660
889
|
}
|
|
661
|
-
const renderCallVisitor = {
|
|
662
|
-
createIndex(call) {
|
|
663
|
-
return call.options ? `createIndex(${renderLiteral(call.collection)}, ${renderLiteral(call.keys)}, ${renderLiteral(call.options)})` : `createIndex(${renderLiteral(call.collection)}, ${renderLiteral(call.keys)})`;
|
|
664
|
-
},
|
|
665
|
-
dropIndex(call) {
|
|
666
|
-
return `dropIndex(${renderLiteral(call.collection)}, ${renderLiteral(call.keys)})`;
|
|
667
|
-
},
|
|
668
|
-
createCollection(call) {
|
|
669
|
-
return call.options ? `createCollection(${renderLiteral(call.collection)}, ${renderLiteral(call.options)})` : `createCollection(${renderLiteral(call.collection)})`;
|
|
670
|
-
},
|
|
671
|
-
dropCollection(call) {
|
|
672
|
-
return `dropCollection(${renderLiteral(call.collection)})`;
|
|
673
|
-
},
|
|
674
|
-
collMod(call) {
|
|
675
|
-
return call.meta ? `collMod(${renderLiteral(call.collection)}, ${renderLiteral(call.options)}, ${renderLiteral(call.meta)})` : `collMod(${renderLiteral(call.collection)}, ${renderLiteral(call.options)})`;
|
|
676
|
-
}
|
|
677
|
-
};
|
|
678
|
-
function renderLiteral(value) {
|
|
679
|
-
if (value === void 0) return "undefined";
|
|
680
|
-
if (value === null) return "null";
|
|
681
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
682
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
683
|
-
if (Array.isArray(value)) {
|
|
684
|
-
if (value.length === 0) return "[]";
|
|
685
|
-
const items = value.map((v) => renderLiteral(v));
|
|
686
|
-
const singleLine = `[${items.join(", ")}]`;
|
|
687
|
-
if (singleLine.length <= 80) return singleLine;
|
|
688
|
-
return `[\n${items.map((i) => ` ${i}`).join(",\n")},\n]`;
|
|
689
|
-
}
|
|
690
|
-
if (typeof value === "object") {
|
|
691
|
-
const entries = Object.entries(value).filter(([, v]) => v !== void 0);
|
|
692
|
-
if (entries.length === 0) return "{}";
|
|
693
|
-
const items = entries.map(([k, v]) => `${renderKey(k)}: ${renderLiteral(v)}`);
|
|
694
|
-
const singleLine = `{ ${items.join(", ")} }`;
|
|
695
|
-
if (singleLine.length <= 80) return singleLine;
|
|
696
|
-
return `{\n${items.map((i) => ` ${i}`).join(",\n")},\n}`;
|
|
697
|
-
}
|
|
698
|
-
return String(value);
|
|
699
|
-
}
|
|
700
|
-
function renderKey(key) {
|
|
701
|
-
if (key === "__proto__") return JSON.stringify(key);
|
|
702
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
703
|
-
}
|
|
704
890
|
function indent(text, spaces) {
|
|
705
891
|
const pad = " ".repeat(spaces);
|
|
706
892
|
return text.split("\n").map((line) => line.trim() ? `${pad}${line}` : line).join("\n");
|
|
@@ -871,12 +1057,11 @@ var MongoMigrationPlanner = class {
|
|
|
871
1057
|
/**
|
|
872
1058
|
* Produce an empty `migration.ts` authoring surface for `migration new`.
|
|
873
1059
|
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
* contract `.d.ts`.
|
|
1060
|
+
* The "empty migration" is a `PlannerProducedMongoMigration` with no
|
|
1061
|
+
* operations; `renderTypeScript()` emits a stub class with the correct
|
|
1062
|
+
* `from`/`to` metadata that the user then fills in with operations. The
|
|
1063
|
+
* contract path on the context is unused — Mongo's emitted source does
|
|
1064
|
+
* not import from the generated contract `.d.ts`.
|
|
880
1065
|
*/
|
|
881
1066
|
emptyMigration(context) {
|
|
882
1067
|
return new PlannerProducedMongoMigration([], {
|
|
@@ -923,6 +1108,10 @@ function planMutableOptionsDiffCall(collName, origin, dest) {
|
|
|
923
1108
|
|
|
924
1109
|
//#endregion
|
|
925
1110
|
//#region src/core/mongo-runner.ts
|
|
1111
|
+
const READ_ONLY_CHECK_COMMAND_KINDS = new Set(["aggregate", "rawAggregate"]);
|
|
1112
|
+
function hasProfileHash(value) {
|
|
1113
|
+
return typeof value === "object" && value !== null && Object.hasOwn(value, "profileHash") && typeof value.profileHash === "string";
|
|
1114
|
+
}
|
|
926
1115
|
function runnerFailure(code, summary, opts) {
|
|
927
1116
|
return notOk({
|
|
928
1117
|
code,
|
|
@@ -935,7 +1124,7 @@ var MongoMigrationRunner = class {
|
|
|
935
1124
|
this.deps = deps;
|
|
936
1125
|
}
|
|
937
1126
|
async execute(options) {
|
|
938
|
-
const { commandExecutor, inspectionExecutor, markerOps } = this.deps;
|
|
1127
|
+
const { commandExecutor, inspectionExecutor, adapter, driver, markerOps } = this.deps;
|
|
939
1128
|
const operations = deserializeMongoOps(options.plan.operations);
|
|
940
1129
|
const policyCheck = this.enforcePolicyCompatibility(options.policy, operations);
|
|
941
1130
|
if (policyCheck) return policyCheck;
|
|
@@ -951,15 +1140,22 @@ var MongoMigrationRunner = class {
|
|
|
951
1140
|
for (const operation of operations) {
|
|
952
1141
|
options.callbacks?.onOperationStart?.(operation);
|
|
953
1142
|
try {
|
|
1143
|
+
if (operation.operationClass === "data") {
|
|
1144
|
+
const result = await this.executeDataTransform(operation, adapter, driver, filterEvaluator, runIdempotency, runPrechecks, runPostchecks);
|
|
1145
|
+
if (result.failure) return result.failure;
|
|
1146
|
+
if (result.executed) operationsExecuted += 1;
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
const ddlOp = operation;
|
|
954
1150
|
if (runPostchecks && runIdempotency) {
|
|
955
|
-
if (await this.allChecksSatisfied(
|
|
1151
|
+
if (await this.allChecksSatisfied(ddlOp.postcheck, inspectionExecutor, filterEvaluator)) continue;
|
|
956
1152
|
}
|
|
957
1153
|
if (runPrechecks) {
|
|
958
|
-
if (!await this.evaluateChecks(
|
|
1154
|
+
if (!await this.evaluateChecks(ddlOp.precheck, inspectionExecutor, filterEvaluator)) return runnerFailure("PRECHECK_FAILED", `Operation ${operation.id} failed during precheck`, { meta: { operationId: operation.id } });
|
|
959
1155
|
}
|
|
960
|
-
for (const step of
|
|
1156
|
+
for (const step of ddlOp.execute) await step.command.accept(commandExecutor);
|
|
961
1157
|
if (runPostchecks) {
|
|
962
|
-
if (!await this.evaluateChecks(
|
|
1158
|
+
if (!await this.evaluateChecks(ddlOp.postcheck, inspectionExecutor, filterEvaluator)) return runnerFailure("POSTCHECK_FAILED", `Operation ${operation.id} failed during postcheck`, { meta: { operationId: operation.id } });
|
|
963
1159
|
}
|
|
964
1160
|
operationsExecuted += 1;
|
|
965
1161
|
} finally {
|
|
@@ -967,7 +1163,7 @@ var MongoMigrationRunner = class {
|
|
|
967
1163
|
}
|
|
968
1164
|
}
|
|
969
1165
|
const destination = options.plan.destination;
|
|
970
|
-
const profileHash = options.destinationContract.profileHash
|
|
1166
|
+
const profileHash = hasProfileHash(options.destinationContract) ? options.destinationContract.profileHash : destination.storageHash;
|
|
971
1167
|
if (operationsExecuted === 0 && existingMarker?.storageHash === destination.storageHash && existingMarker.profileHash === profileHash) return ok({
|
|
972
1168
|
operationsPlanned: operations.length,
|
|
973
1169
|
operationsExecuted
|
|
@@ -995,6 +1191,56 @@ var MongoMigrationRunner = class {
|
|
|
995
1191
|
operationsExecuted
|
|
996
1192
|
});
|
|
997
1193
|
}
|
|
1194
|
+
async executeDataTransform(op, adapter, driver, filterEvaluator, runIdempotency, runPrechecks, runPostchecks) {
|
|
1195
|
+
if (runPostchecks && runIdempotency && op.postcheck.length > 0) {
|
|
1196
|
+
if (await this.evaluateDataTransformChecks(op.postcheck, adapter, driver, filterEvaluator)) return { executed: false };
|
|
1197
|
+
}
|
|
1198
|
+
if (runPrechecks && op.precheck.length > 0) {
|
|
1199
|
+
if (!await this.evaluateDataTransformChecks(op.precheck, adapter, driver, filterEvaluator)) return {
|
|
1200
|
+
executed: false,
|
|
1201
|
+
failure: runnerFailure("PRECHECK_FAILED", `Operation ${op.id} failed during precheck`, { meta: {
|
|
1202
|
+
operationId: op.id,
|
|
1203
|
+
name: op.name
|
|
1204
|
+
} })
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
for (const plan of op.run) {
|
|
1208
|
+
const wireCommand = adapter.lower(plan);
|
|
1209
|
+
for await (const _ of driver.execute(wireCommand));
|
|
1210
|
+
}
|
|
1211
|
+
if (runPostchecks && op.postcheck.length > 0) {
|
|
1212
|
+
if (!await this.evaluateDataTransformChecks(op.postcheck, adapter, driver, filterEvaluator)) return {
|
|
1213
|
+
executed: false,
|
|
1214
|
+
failure: runnerFailure("POSTCHECK_FAILED", `Operation ${op.id} failed during postcheck`, { meta: {
|
|
1215
|
+
operationId: op.id,
|
|
1216
|
+
name: op.name
|
|
1217
|
+
} })
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
return { executed: true };
|
|
1221
|
+
}
|
|
1222
|
+
async evaluateDataTransformChecks(checks, adapter, driver, filterEvaluator) {
|
|
1223
|
+
for (const check of checks) {
|
|
1224
|
+
const commandKind = check.source.command.kind;
|
|
1225
|
+
if (!READ_ONLY_CHECK_COMMAND_KINDS.has(commandKind)) throw errorRunnerFailed(`Data-transform check rejected: command kind "${commandKind}" is not read-only`, {
|
|
1226
|
+
why: "Data-transform checks must use aggregate or rawAggregate commands so the pre/postcheck path cannot mutate the database.",
|
|
1227
|
+
fix: "Author the check.source as an aggregate pipeline (or rawAggregate) rather than a DML write command.",
|
|
1228
|
+
meta: {
|
|
1229
|
+
checkDescription: check.description,
|
|
1230
|
+
commandKind,
|
|
1231
|
+
collection: check.source.collection
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
const wireCommand = adapter.lower(check.source);
|
|
1235
|
+
let matchFound = false;
|
|
1236
|
+
for await (const row of driver.execute(wireCommand)) if (filterEvaluator.evaluate(check.filter, row)) {
|
|
1237
|
+
matchFound = true;
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
if (!(check.expect === "exists" ? matchFound : !matchFound)) return false;
|
|
1241
|
+
}
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
998
1244
|
async evaluateChecks(checks, inspectionExecutor, filterEvaluator) {
|
|
999
1245
|
for (const check of checks) {
|
|
1000
1246
|
const matchFound = (await check.source.accept(inspectionExecutor)).some((doc) => filterEvaluator.evaluate(check.filter, doc));
|