@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.mjs CHANGED
@@ -1,11 +1,13 @@
1
- import { a as dropIndex, i as dropCollection, n as createCollection, r as createIndex, t as collMod } from "./migration-factories-Brzz-QGG.mjs";
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 OperationJson = type({
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 deserializeMongoOp(json) {
433
- const data = validate(OperationJson, json, "migration operation");
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
- var OpFactoryCallNode = class {
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
- factory = "createIndex";
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
- accept(visitor) {
476
- return visitor.createIndex(this);
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
- factory = "dropIndex";
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
- accept(visitor) {
493
- return visitor.dropIndex(this);
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
- factory = "createCollection";
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
- accept(visitor) {
510
- return visitor.createCollection(this);
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
- factory = "dropCollection";
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
- accept(visitor) {
525
- return visitor.dropCollection(this);
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
- factory = "collMod";
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
- accept(visitor) {
545
- return visitor.collMod(this);
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.accept(renderVisitor));
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 class-flow `migration.ts`
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(collectFactoryNames(calls));
619
- const operationsBody = calls.map((c) => c.accept(renderCallVisitor)).join(",\n");
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 collectFactoryNames(calls) {
639
- const names = /* @__PURE__ */ new Set();
640
- for (const call of calls) names.add(call.factory);
641
- return [...names].sort();
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: ${renderLiteral(meta.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
- * Mongo is a class-flow target, so the "empty migration" is a
875
- * `PlannerProducedMongoMigration` with no operations; `renderTypeScript()`
876
- * emits a stub class with the correct `from`/`to` metadata that the user
877
- * then fills in with operations. The contract path on the context is
878
- * unused — Mongo's emitted source does not import from the generated
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(operation.postcheck, inspectionExecutor, filterEvaluator)) continue;
1151
+ if (await this.allChecksSatisfied(ddlOp.postcheck, inspectionExecutor, filterEvaluator)) continue;
956
1152
  }
957
1153
  if (runPrechecks) {
958
- if (!await this.evaluateChecks(operation.precheck, inspectionExecutor, filterEvaluator)) return runnerFailure("PRECHECK_FAILED", `Operation ${operation.id} failed during precheck`, { meta: { operationId: operation.id } });
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 operation.execute) await step.command.accept(commandExecutor);
1156
+ for (const step of ddlOp.execute) await step.command.accept(commandExecutor);
961
1157
  if (runPostchecks) {
962
- if (!await this.evaluateChecks(operation.postcheck, inspectionExecutor, filterEvaluator)) return runnerFailure("POSTCHECK_FAILED", `Operation ${operation.id} failed during postcheck`, { meta: { operationId: operation.id } });
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 ?? destination.storageHash;
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));