@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.
@@ -1,4 +1,5 @@
1
1
  import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import { errorRunnerFailed } from '@prisma-next/errors/execution';
2
3
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
4
  import type {
4
5
  MigrationOperationPolicy,
@@ -8,13 +9,29 @@ import type {
8
9
  MigrationRunnerFailure,
9
10
  MigrationRunnerResult,
10
11
  } from '@prisma-next/framework-components/control';
12
+ import type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';
11
13
  import type {
14
+ AnyMongoMigrationOperation,
15
+ MongoDataTransformCheck,
16
+ MongoDataTransformOperation,
12
17
  MongoDdlCommandVisitor,
13
18
  MongoInspectionCommandVisitor,
14
19
  MongoMigrationCheck,
15
20
  MongoMigrationPlanOperation,
16
21
  } from '@prisma-next/mongo-query-ast/control';
17
22
  import { notOk, ok } from '@prisma-next/utils/result';
23
+
24
+ const READ_ONLY_CHECK_COMMAND_KINDS: ReadonlySet<string> = new Set(['aggregate', 'rawAggregate']);
25
+
26
+ function hasProfileHash(value: unknown): value is { readonly profileHash: string } {
27
+ return (
28
+ typeof value === 'object' &&
29
+ value !== null &&
30
+ Object.hasOwn(value, 'profileHash') &&
31
+ typeof (value as { profileHash: unknown }).profileHash === 'string'
32
+ );
33
+ }
34
+
18
35
  import { FilterEvaluator } from './filter-evaluator';
19
36
  import { deserializeMongoOps } from './mongo-ops-serializer';
20
37
 
@@ -38,6 +55,8 @@ export interface MarkerOperations {
38
55
  export interface MongoRunnerDependencies {
39
56
  readonly commandExecutor: MongoDdlCommandVisitor<Promise<void>>;
40
57
  readonly inspectionExecutor: MongoInspectionCommandVisitor<Promise<Record<string, unknown>[]>>;
58
+ readonly adapter: MongoAdapter;
59
+ readonly driver: MongoDriver;
41
60
  readonly markerOps: MarkerOperations;
42
61
  }
43
62
 
@@ -67,7 +86,7 @@ export class MongoMigrationRunner {
67
86
  readonly executionChecks?: MigrationRunnerExecutionChecks;
68
87
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', 'mongo'>>;
69
88
  }): Promise<MigrationRunnerResult> {
70
- const { commandExecutor, inspectionExecutor, markerOps } = this.deps;
89
+ const { commandExecutor, inspectionExecutor, adapter, driver, markerOps } = this.deps;
71
90
  const operations = deserializeMongoOps(options.plan.operations as readonly unknown[]);
72
91
 
73
92
  const policyCheck = this.enforcePolicyCompatibility(options.policy, operations);
@@ -90,9 +109,26 @@ export class MongoMigrationRunner {
90
109
  for (const operation of operations) {
91
110
  options.callbacks?.onOperationStart?.(operation);
92
111
  try {
112
+ if (operation.operationClass === 'data') {
113
+ const result = await this.executeDataTransform(
114
+ operation as MongoDataTransformOperation,
115
+ adapter,
116
+ driver,
117
+ filterEvaluator,
118
+ runIdempotency,
119
+ runPrechecks,
120
+ runPostchecks,
121
+ );
122
+ if (result.failure) return result.failure;
123
+ if (result.executed) operationsExecuted += 1;
124
+ continue;
125
+ }
126
+
127
+ const ddlOp = operation as MongoMigrationPlanOperation;
128
+
93
129
  if (runPostchecks && runIdempotency) {
94
130
  const allSatisfied = await this.allChecksSatisfied(
95
- operation.postcheck,
131
+ ddlOp.postcheck,
96
132
  inspectionExecutor,
97
133
  filterEvaluator,
98
134
  );
@@ -101,7 +137,7 @@ export class MongoMigrationRunner {
101
137
 
102
138
  if (runPrechecks) {
103
139
  const precheckResult = await this.evaluateChecks(
104
- operation.precheck,
140
+ ddlOp.precheck,
105
141
  inspectionExecutor,
106
142
  filterEvaluator,
107
143
  );
@@ -114,13 +150,13 @@ export class MongoMigrationRunner {
114
150
  }
115
151
  }
116
152
 
117
- for (const step of operation.execute) {
153
+ for (const step of ddlOp.execute) {
118
154
  await step.command.accept(commandExecutor);
119
155
  }
120
156
 
121
157
  if (runPostchecks) {
122
158
  const postcheckResult = await this.evaluateChecks(
123
- operation.postcheck,
159
+ ddlOp.postcheck,
124
160
  inspectionExecutor,
125
161
  filterEvaluator,
126
162
  );
@@ -140,8 +176,9 @@ export class MongoMigrationRunner {
140
176
  }
141
177
 
142
178
  const destination = options.plan.destination;
143
- const contract = options.destinationContract as { profileHash?: string };
144
- const profileHash = contract.profileHash ?? destination.storageHash;
179
+ const profileHash = hasProfileHash(options.destinationContract)
180
+ ? options.destinationContract.profileHash
181
+ : destination.storageHash;
145
182
 
146
183
  if (
147
184
  operationsExecuted === 0 &&
@@ -185,6 +222,105 @@ export class MongoMigrationRunner {
185
222
  return ok({ operationsPlanned: operations.length, operationsExecuted });
186
223
  }
187
224
 
225
+ private async executeDataTransform(
226
+ op: MongoDataTransformOperation,
227
+ adapter: MongoAdapter,
228
+ driver: MongoDriver,
229
+ filterEvaluator: FilterEvaluator,
230
+ runIdempotency: boolean,
231
+ runPrechecks: boolean,
232
+ runPostchecks: boolean,
233
+ ): Promise<{ executed: boolean; failure?: MigrationRunnerResult }> {
234
+ if (runPostchecks && runIdempotency && op.postcheck.length > 0) {
235
+ const allSatisfied = await this.evaluateDataTransformChecks(
236
+ op.postcheck,
237
+ adapter,
238
+ driver,
239
+ filterEvaluator,
240
+ );
241
+ if (allSatisfied) return { executed: false };
242
+ }
243
+
244
+ if (runPrechecks && op.precheck.length > 0) {
245
+ const passed = await this.evaluateDataTransformChecks(
246
+ op.precheck,
247
+ adapter,
248
+ driver,
249
+ filterEvaluator,
250
+ );
251
+ if (!passed) {
252
+ return {
253
+ executed: false,
254
+ failure: runnerFailure('PRECHECK_FAILED', `Operation ${op.id} failed during precheck`, {
255
+ meta: { operationId: op.id, name: op.name },
256
+ }),
257
+ };
258
+ }
259
+ }
260
+
261
+ for (const plan of op.run) {
262
+ const wireCommand = adapter.lower(plan);
263
+ for await (const _ of driver.execute(wireCommand)) {
264
+ /* consume */
265
+ }
266
+ }
267
+
268
+ if (runPostchecks && op.postcheck.length > 0) {
269
+ const passed = await this.evaluateDataTransformChecks(
270
+ op.postcheck,
271
+ adapter,
272
+ driver,
273
+ filterEvaluator,
274
+ );
275
+ if (!passed) {
276
+ return {
277
+ executed: false,
278
+ failure: runnerFailure('POSTCHECK_FAILED', `Operation ${op.id} failed during postcheck`, {
279
+ meta: { operationId: op.id, name: op.name },
280
+ }),
281
+ };
282
+ }
283
+ }
284
+
285
+ return { executed: true };
286
+ }
287
+
288
+ private async evaluateDataTransformChecks(
289
+ checks: readonly MongoDataTransformCheck[],
290
+ adapter: MongoAdapter,
291
+ driver: MongoDriver,
292
+ filterEvaluator: FilterEvaluator,
293
+ ): Promise<boolean> {
294
+ for (const check of checks) {
295
+ const commandKind = check.source.command.kind;
296
+ if (!READ_ONLY_CHECK_COMMAND_KINDS.has(commandKind)) {
297
+ throw errorRunnerFailed(
298
+ `Data-transform check rejected: command kind "${commandKind}" is not read-only`,
299
+ {
300
+ why: 'Data-transform checks must use aggregate or rawAggregate commands so the pre/postcheck path cannot mutate the database.',
301
+ fix: 'Author the check.source as an aggregate pipeline (or rawAggregate) rather than a DML write command.',
302
+ meta: {
303
+ checkDescription: check.description,
304
+ commandKind,
305
+ collection: check.source.collection,
306
+ },
307
+ },
308
+ );
309
+ }
310
+ const wireCommand = adapter.lower(check.source);
311
+ let matchFound = false;
312
+ for await (const row of driver.execute<Record<string, unknown>>(wireCommand)) {
313
+ if (filterEvaluator.evaluate(check.filter, row)) {
314
+ matchFound = true;
315
+ break;
316
+ }
317
+ }
318
+ const passed = check.expect === 'exists' ? matchFound : !matchFound;
319
+ if (!passed) return false;
320
+ }
321
+ return true;
322
+ }
323
+
188
324
  private async evaluateChecks(
189
325
  checks: readonly MongoMigrationCheck[],
190
326
  inspectionExecutor: MongoInspectionCommandVisitor<Promise<Record<string, unknown>[]>>,
@@ -212,7 +348,7 @@ export class MongoMigrationRunner {
212
348
 
213
349
  private enforcePolicyCompatibility(
214
350
  policy: MigrationOperationPolicy,
215
- operations: readonly MongoMigrationPlanOperation[],
351
+ operations: readonly AnyMongoMigrationOperation[],
216
352
  ): MigrationRunnerResult | undefined {
217
353
  const allowedClasses = new Set(policy.allowedOperationClasses);
218
354
  for (const operation of operations) {
@@ -1,9 +1,32 @@
1
- import type { MigrationOperationClass } from '@prisma-next/framework-components/control';
1
+ /**
2
+ * Mongo migration IR: one concrete `*Call` class per pure factory under
3
+ * `migration-factories.ts`, plus a shared `OpFactoryCallNode` abstract
4
+ * base. Every call class carries the literal arguments its backing
5
+ * factory would receive, computes a human-readable `label` in its
6
+ * constructor, and implements two polymorphic hooks:
7
+ *
8
+ * - `toOp()` — converts the IR node to a runtime
9
+ * `MongoMigrationPlanOperation` by delegating to the matching pure
10
+ * factory in `migration-factories.ts`.
11
+ * - `renderTypeScript()` / `importRequirements()` — inherited from
12
+ * `TsExpression`. Used by `renderCallsToTypeScript` to emit the call
13
+ * as a TypeScript expression inside the scaffolded `migration.ts`.
14
+ *
15
+ * The abstract base and all concrete classes are package-private.
16
+ * External consumers see only the framework-level `OpFactoryCall`
17
+ * interface and the `OpFactoryCall` union.
18
+ */
19
+
20
+ import type {
21
+ OpFactoryCall as FrameworkOpFactoryCall,
22
+ MigrationOperationClass,
23
+ } from '@prisma-next/framework-components/control';
2
24
  import type {
3
25
  CollModOptions,
4
26
  CreateCollectionOptions,
5
27
  CreateIndexOptions,
6
28
  MongoIndexKey,
29
+ MongoMigrationPlanOperation,
7
30
  } from '@prisma-next/mongo-query-ast/control';
8
31
  import type {
9
32
  MongoSchemaCollection,
@@ -11,6 +34,14 @@ import type {
11
34
  MongoSchemaIndex,
12
35
  MongoSchemaValidator,
13
36
  } from '@prisma-next/mongo-schema-ir';
37
+ import { type ImportRequirement, jsonToTsSource, TsExpression } from '@prisma-next/ts-render';
38
+ import {
39
+ collMod,
40
+ createCollection,
41
+ createIndex,
42
+ dropCollection,
43
+ dropIndex,
44
+ } from './migration-factories';
14
45
 
15
46
  export interface CollModMeta {
16
47
  readonly id?: string;
@@ -18,19 +49,17 @@ export interface CollModMeta {
18
49
  readonly operationClass?: MigrationOperationClass;
19
50
  }
20
51
 
21
- export interface OpFactoryCallVisitor<R> {
22
- createIndex(call: CreateIndexCall): R;
23
- dropIndex(call: DropIndexCall): R;
24
- createCollection(call: CreateCollectionCall): R;
25
- dropCollection(call: DropCollectionCall): R;
26
- collMod(call: CollModCall): R;
27
- }
52
+ const TARGET_MIGRATION_MODULE = '@prisma-next/target-mongo/migration';
28
53
 
29
- abstract class OpFactoryCallNode {
30
- abstract readonly factory: string;
54
+ abstract class OpFactoryCallNode extends TsExpression implements FrameworkOpFactoryCall {
55
+ abstract readonly factoryName: string;
31
56
  abstract readonly operationClass: MigrationOperationClass;
32
57
  abstract readonly label: string;
33
- abstract accept<R>(visitor: OpFactoryCallVisitor<R>): R;
58
+ abstract toOp(): MongoMigrationPlanOperation;
59
+
60
+ importRequirements(): readonly ImportRequirement[] {
61
+ return [{ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: this.factoryName }];
62
+ }
34
63
 
35
64
  protected freeze(): void {
36
65
  Object.freeze(this);
@@ -42,7 +71,7 @@ function formatKeys(keys: ReadonlyArray<MongoIndexKey>): string {
42
71
  }
43
72
 
44
73
  export class CreateIndexCall extends OpFactoryCallNode {
45
- readonly factory = 'createIndex' as const;
74
+ readonly factoryName = 'createIndex' as const;
46
75
  readonly operationClass = 'additive' as const;
47
76
  readonly collection: string;
48
77
  readonly keys: ReadonlyArray<MongoIndexKey>;
@@ -62,13 +91,19 @@ export class CreateIndexCall extends OpFactoryCallNode {
62
91
  this.freeze();
63
92
  }
64
93
 
65
- accept<R>(visitor: OpFactoryCallVisitor<R>): R {
66
- return visitor.createIndex(this);
94
+ toOp(): MongoMigrationPlanOperation {
95
+ return createIndex(this.collection, this.keys, this.options);
96
+ }
97
+
98
+ renderTypeScript(): string {
99
+ return this.options
100
+ ? `createIndex(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.keys)}, ${jsonToTsSource(this.options)})`
101
+ : `createIndex(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.keys)})`;
67
102
  }
68
103
  }
69
104
 
70
105
  export class DropIndexCall extends OpFactoryCallNode {
71
- readonly factory = 'dropIndex' as const;
106
+ readonly factoryName = 'dropIndex' as const;
72
107
  readonly operationClass = 'destructive' as const;
73
108
  readonly collection: string;
74
109
  readonly keys: ReadonlyArray<MongoIndexKey>;
@@ -82,13 +117,17 @@ export class DropIndexCall extends OpFactoryCallNode {
82
117
  this.freeze();
83
118
  }
84
119
 
85
- accept<R>(visitor: OpFactoryCallVisitor<R>): R {
86
- return visitor.dropIndex(this);
120
+ toOp(): MongoMigrationPlanOperation {
121
+ return dropIndex(this.collection, this.keys);
122
+ }
123
+
124
+ renderTypeScript(): string {
125
+ return `dropIndex(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.keys)})`;
87
126
  }
88
127
  }
89
128
 
90
129
  export class CreateCollectionCall extends OpFactoryCallNode {
91
- readonly factory = 'createCollection' as const;
130
+ readonly factoryName = 'createCollection' as const;
92
131
  readonly operationClass = 'additive' as const;
93
132
  readonly collection: string;
94
133
  readonly options: CreateCollectionOptions | undefined;
@@ -102,13 +141,19 @@ export class CreateCollectionCall extends OpFactoryCallNode {
102
141
  this.freeze();
103
142
  }
104
143
 
105
- accept<R>(visitor: OpFactoryCallVisitor<R>): R {
106
- return visitor.createCollection(this);
144
+ toOp(): MongoMigrationPlanOperation {
145
+ return createCollection(this.collection, this.options);
146
+ }
147
+
148
+ renderTypeScript(): string {
149
+ return this.options
150
+ ? `createCollection(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.options)})`
151
+ : `createCollection(${jsonToTsSource(this.collection)})`;
107
152
  }
108
153
  }
109
154
 
110
155
  export class DropCollectionCall extends OpFactoryCallNode {
111
- readonly factory = 'dropCollection' as const;
156
+ readonly factoryName = 'dropCollection' as const;
112
157
  readonly operationClass = 'destructive' as const;
113
158
  readonly collection: string;
114
159
  readonly label: string;
@@ -120,13 +165,17 @@ export class DropCollectionCall extends OpFactoryCallNode {
120
165
  this.freeze();
121
166
  }
122
167
 
123
- accept<R>(visitor: OpFactoryCallVisitor<R>): R {
124
- return visitor.dropCollection(this);
168
+ toOp(): MongoMigrationPlanOperation {
169
+ return dropCollection(this.collection);
170
+ }
171
+
172
+ renderTypeScript(): string {
173
+ return `dropCollection(${jsonToTsSource(this.collection)})`;
125
174
  }
126
175
  }
127
176
 
128
177
  export class CollModCall extends OpFactoryCallNode {
129
- readonly factory = 'collMod' as const;
178
+ readonly factoryName = 'collMod' as const;
130
179
  readonly collection: string;
131
180
  readonly options: CollModOptions;
132
181
  readonly meta: CollModMeta | undefined;
@@ -143,8 +192,14 @@ export class CollModCall extends OpFactoryCallNode {
143
192
  this.freeze();
144
193
  }
145
194
 
146
- accept<R>(visitor: OpFactoryCallVisitor<R>): R {
147
- return visitor.collMod(this);
195
+ toOp(): MongoMigrationPlanOperation {
196
+ return collMod(this.collection, this.options, this.meta);
197
+ }
198
+
199
+ renderTypeScript(): string {
200
+ return this.meta
201
+ ? `collMod(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.options)}, ${jsonToTsSource(this.meta)})`
202
+ : `collMod(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.options)})`;
148
203
  }
149
204
  }
150
205
 
@@ -1,39 +1,6 @@
1
1
  import type { MongoMigrationPlanOperation } from '@prisma-next/mongo-query-ast/control';
2
- import {
3
- collMod,
4
- createCollection,
5
- createIndex,
6
- dropCollection,
7
- dropIndex,
8
- } from './migration-factories';
9
- import type {
10
- CollModCall,
11
- CreateCollectionCall,
12
- CreateIndexCall,
13
- DropCollectionCall,
14
- DropIndexCall,
15
- OpFactoryCall,
16
- OpFactoryCallVisitor,
17
- } from './op-factory-call';
18
-
19
- const renderVisitor: OpFactoryCallVisitor<MongoMigrationPlanOperation> = {
20
- createIndex(call: CreateIndexCall) {
21
- return createIndex(call.collection, call.keys, call.options);
22
- },
23
- dropIndex(call: DropIndexCall) {
24
- return dropIndex(call.collection, call.keys);
25
- },
26
- createCollection(call: CreateCollectionCall) {
27
- return createCollection(call.collection, call.options);
28
- },
29
- dropCollection(call: DropCollectionCall) {
30
- return dropCollection(call.collection);
31
- },
32
- collMod(call: CollModCall) {
33
- return collMod(call.collection, call.options, call.meta);
34
- },
35
- };
2
+ import type { OpFactoryCall } from './op-factory-call';
36
3
 
37
4
  export function renderOps(calls: ReadonlyArray<OpFactoryCall>): MongoMigrationPlanOperation[] {
38
- return calls.map((call) => call.accept(renderVisitor));
5
+ return calls.map((call) => call.toOp());
39
6
  }
@@ -1,13 +1,6 @@
1
1
  import { detectScaffoldRuntime, shebangLineFor } from '@prisma-next/migration-tools/migration-ts';
2
- import type {
3
- CollModCall,
4
- CreateCollectionCall,
5
- CreateIndexCall,
6
- DropCollectionCall,
7
- DropIndexCall,
8
- OpFactoryCall,
9
- OpFactoryCallVisitor,
10
- } from './op-factory-call';
2
+ import { type ImportRequirement, jsonToTsSource, renderImports } from '@prisma-next/ts-render';
3
+ import type { OpFactoryCall } from './op-factory-call';
11
4
 
12
5
  export interface RenderMigrationMeta {
13
6
  readonly from: string;
@@ -16,22 +9,34 @@ export interface RenderMigrationMeta {
16
9
  readonly labels?: readonly string[];
17
10
  }
18
11
 
12
+ const BASE_IMPORT: ImportRequirement = {
13
+ moduleSpecifier: '@prisma-next/family-mongo/migration',
14
+ symbol: 'Migration',
15
+ };
16
+
19
17
  /**
20
- * Render a list of Mongo `OpFactoryCall`s as a class-flow `migration.ts`
18
+ * Render a list of Mongo `OpFactoryCall`s as a `migration.ts`
21
19
  * source string. The result is shebanged, extends the user-facing
22
20
  * `Migration` (i.e. `MongoMigration`) from `@prisma-next/family-mongo`, and
23
21
  * implements the abstract `operations` and `describe` members. `meta` is
24
22
  * always rendered — `describe()` is part of the `Migration` contract, so
25
23
  * even an empty stub must satisfy it; callers pass empty strings for a
26
24
  * migration-new scaffold.
25
+ *
26
+ * The walk is polymorphic: each call node contributes its own
27
+ * `renderTypeScript()` expression and declares its own
28
+ * `importRequirements()`. The top-level renderer aggregates imports
29
+ * across all nodes and emits one `import { … } from "…"` line per module.
30
+ * The `Migration` import from `@prisma-next/family-mongo/migration` is
31
+ * always emitted — it's driven by `meta` (the rendered scaffold always
32
+ * extends `Migration`), not by any node.
27
33
  */
28
34
  export function renderCallsToTypeScript(
29
35
  calls: ReadonlyArray<OpFactoryCall>,
30
36
  meta: RenderMigrationMeta,
31
37
  ): string {
32
- const factoryNames = collectFactoryNames(calls);
33
- const imports = buildImports(factoryNames);
34
- const operationsBody = calls.map((c) => c.accept(renderCallVisitor)).join(',\n');
38
+ const imports = buildImports(calls);
39
+ const operationsBody = calls.map((c) => c.renderTypeScript()).join(',\n');
35
40
 
36
41
  return [
37
42
  shebangLineFor(detectScaffoldRuntime()),
@@ -52,20 +57,14 @@ export function renderCallsToTypeScript(
52
57
  ].join('\n');
53
58
  }
54
59
 
55
- function collectFactoryNames(calls: ReadonlyArray<OpFactoryCall>): string[] {
56
- const names = new Set<string>();
60
+ function buildImports(calls: ReadonlyArray<OpFactoryCall>): string {
61
+ const requirements: ImportRequirement[] = [BASE_IMPORT];
57
62
  for (const call of calls) {
58
- names.add(call.factory);
63
+ for (const req of call.importRequirements()) {
64
+ requirements.push(req);
65
+ }
59
66
  }
60
- return [...names].sort();
61
- }
62
-
63
- function buildImports(factoryNames: string[]): string {
64
- const lines = ["import { Migration } from '@prisma-next/family-mongo/migration';"];
65
- if (factoryNames.length > 0) {
66
- lines.push(`import { ${factoryNames.join(', ')} } from '@prisma-next/target-mongo/migration';`);
67
- }
68
- return lines.join('\n');
67
+ return renderImports(requirements);
69
68
  }
70
69
 
71
70
  function buildDescribeMethod(meta: RenderMigrationMeta): string {
@@ -78,7 +77,7 @@ function buildDescribeMethod(meta: RenderMigrationMeta): string {
78
77
  lines.push(` kind: ${JSON.stringify(meta.kind)},`);
79
78
  }
80
79
  if (meta.labels && meta.labels.length > 0) {
81
- lines.push(` labels: ${renderLiteral(meta.labels)},`);
80
+ lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
82
81
  }
83
82
  lines.push(' };');
84
83
  lines.push(' }');
@@ -86,58 +85,6 @@ function buildDescribeMethod(meta: RenderMigrationMeta): string {
86
85
  return lines.join('\n');
87
86
  }
88
87
 
89
- const renderCallVisitor: OpFactoryCallVisitor<string> = {
90
- createIndex(call: CreateIndexCall) {
91
- return call.options
92
- ? `createIndex(${renderLiteral(call.collection)}, ${renderLiteral(call.keys)}, ${renderLiteral(call.options)})`
93
- : `createIndex(${renderLiteral(call.collection)}, ${renderLiteral(call.keys)})`;
94
- },
95
- dropIndex(call: DropIndexCall) {
96
- return `dropIndex(${renderLiteral(call.collection)}, ${renderLiteral(call.keys)})`;
97
- },
98
- createCollection(call: CreateCollectionCall) {
99
- return call.options
100
- ? `createCollection(${renderLiteral(call.collection)}, ${renderLiteral(call.options)})`
101
- : `createCollection(${renderLiteral(call.collection)})`;
102
- },
103
- dropCollection(call: DropCollectionCall) {
104
- return `dropCollection(${renderLiteral(call.collection)})`;
105
- },
106
- collMod(call: CollModCall) {
107
- return call.meta
108
- ? `collMod(${renderLiteral(call.collection)}, ${renderLiteral(call.options)}, ${renderLiteral(call.meta)})`
109
- : `collMod(${renderLiteral(call.collection)}, ${renderLiteral(call.options)})`;
110
- },
111
- };
112
-
113
- function renderLiteral(value: unknown): string {
114
- if (value === undefined) return 'undefined';
115
- if (value === null) return 'null';
116
- if (typeof value === 'string') return JSON.stringify(value);
117
- if (typeof value === 'number' || typeof value === 'boolean') return String(value);
118
- if (Array.isArray(value)) {
119
- if (value.length === 0) return '[]';
120
- const items = value.map((v) => renderLiteral(v));
121
- const singleLine = `[${items.join(', ')}]`;
122
- if (singleLine.length <= 80) return singleLine;
123
- return `[\n${items.map((i) => ` ${i}`).join(',\n')},\n]`;
124
- }
125
- if (typeof value === 'object') {
126
- const entries = Object.entries(value).filter(([, v]) => v !== undefined);
127
- if (entries.length === 0) return '{}';
128
- const items = entries.map(([k, v]) => `${renderKey(k)}: ${renderLiteral(v)}`);
129
- const singleLine = `{ ${items.join(', ')} }`;
130
- if (singleLine.length <= 80) return singleLine;
131
- return `{\n${items.map((i) => ` ${i}`).join(',\n')},\n}`;
132
- }
133
- return String(value);
134
- }
135
-
136
- function renderKey(key: string): string {
137
- if (key === '__proto__') return JSON.stringify(key);
138
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
139
- }
140
-
141
88
  function indent(text: string, spaces: number): string {
142
89
  const pad = ' '.repeat(spaces);
143
90
  return text
@@ -11,7 +11,7 @@ export type { PlanCallsResult } from '../core/mongo-planner';
11
11
  export { MongoMigrationPlanner } from '../core/mongo-planner';
12
12
  export type { MarkerOperations, MongoRunnerDependencies } from '../core/mongo-runner';
13
13
  export { MongoMigrationRunner } from '../core/mongo-runner';
14
- export type { CollModMeta, OpFactoryCall, OpFactoryCallVisitor } from '../core/op-factory-call';
14
+ export type { CollModMeta, OpFactoryCall } from '../core/op-factory-call';
15
15
  export {
16
16
  CollModCall,
17
17
  CreateCollectionCall,
@@ -3,6 +3,7 @@ export {
3
3
  collMod,
4
4
  createCollection,
5
5
  createIndex,
6
+ dataTransform,
6
7
  dropCollection,
7
8
  dropIndex,
8
9
  setValidation,
@@ -1 +0,0 @@
1
- {"version":3,"file":"migration-factories-Brzz-QGG.mjs","names":[],"sources":["../src/core/migration-factories.ts"],"sourcesContent":["import type { MongoIndexKey } from '@prisma-next/mongo-query-ast/control';\nimport {\n buildIndexOpId,\n CollModCommand,\n type CollModOptions,\n CreateCollectionCommand,\n type CreateCollectionOptions,\n CreateIndexCommand,\n type CreateIndexOptions,\n DropCollectionCommand,\n DropIndexCommand,\n defaultMongoIndexName,\n keysToKeySpec,\n ListCollectionsCommand,\n ListIndexesCommand,\n MongoAndExpr,\n MongoFieldFilter,\n type MongoMigrationPlanOperation,\n} from '@prisma-next/mongo-query-ast/control';\nimport type { CollModMeta } from './op-factory-call';\n\nfunction formatKeys(keys: ReadonlyArray<MongoIndexKey>): string {\n return keys.map((k) => `${k.field}:${k.direction}`).join(', ');\n}\n\nfunction isTextIndex(keys: ReadonlyArray<MongoIndexKey>): boolean {\n return keys.some((k) => k.direction === 'text');\n}\n\nfunction keyFilter(keys: ReadonlyArray<MongoIndexKey>) {\n return isTextIndex(keys)\n ? MongoFieldFilter.eq('key._fts', 'text')\n : MongoFieldFilter.eq('key', keysToKeySpec(keys));\n}\n\nexport function createIndex(\n collection: string,\n keys: ReadonlyArray<MongoIndexKey>,\n options?: CreateIndexOptions,\n): MongoMigrationPlanOperation {\n const name = defaultMongoIndexName(keys);\n const filter = keyFilter(keys);\n const fullFilter = options?.unique\n ? MongoAndExpr.of([filter, MongoFieldFilter.eq('unique', true)])\n : filter;\n\n return {\n id: buildIndexOpId('create', collection, keys),\n label: `Create index on ${collection} (${formatKeys(keys)})`,\n operationClass: 'additive',\n precheck: [\n {\n description: `index does not already exist on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter,\n expect: 'notExists',\n },\n ],\n execute: [\n {\n description: `create index on ${collection}`,\n command: new CreateIndexCommand(collection, keys, {\n ...options,\n unique: options?.unique ?? undefined,\n name,\n }),\n },\n ],\n postcheck: [\n {\n description: `index exists on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter: fullFilter,\n expect: 'exists',\n },\n ],\n };\n}\n\nexport function dropIndex(\n collection: string,\n keys: ReadonlyArray<MongoIndexKey>,\n): MongoMigrationPlanOperation {\n const indexName = defaultMongoIndexName(keys);\n const filter = keyFilter(keys);\n\n return {\n id: buildIndexOpId('drop', collection, keys),\n label: `Drop index on ${collection} (${formatKeys(keys)})`,\n operationClass: 'destructive',\n precheck: [\n {\n description: `index exists on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter,\n expect: 'exists',\n },\n ],\n execute: [\n {\n description: `drop index on ${collection}`,\n command: new DropIndexCommand(collection, indexName),\n },\n ],\n postcheck: [\n {\n description: `index no longer exists on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter,\n expect: 'notExists',\n },\n ],\n };\n}\n\nexport function createCollection(\n collection: string,\n options?: CreateCollectionOptions,\n): MongoMigrationPlanOperation {\n return {\n id: `collection.${collection}.create`,\n label: `Create collection ${collection}`,\n operationClass: 'additive',\n precheck: [\n {\n description: `collection ${collection} does not exist`,\n source: new ListCollectionsCommand(),\n filter: MongoFieldFilter.eq('name', collection),\n expect: 'notExists',\n },\n ],\n execute: [\n {\n description: `create collection ${collection}`,\n command: new CreateCollectionCommand(collection, options),\n },\n ],\n postcheck: [],\n };\n}\n\nexport function dropCollection(collection: string): MongoMigrationPlanOperation {\n return {\n id: `collection.${collection}.drop`,\n label: `Drop collection ${collection}`,\n operationClass: 'destructive',\n precheck: [],\n execute: [\n {\n description: `drop collection ${collection}`,\n command: new DropCollectionCommand(collection),\n },\n ],\n postcheck: [],\n };\n}\n\nexport function setValidation(\n collection: string,\n schema: Record<string, unknown>,\n options?: { validationLevel?: 'strict' | 'moderate'; validationAction?: 'error' | 'warn' },\n): MongoMigrationPlanOperation {\n return {\n id: `collection.${collection}.setValidation`,\n label: `Set validation on ${collection}`,\n operationClass: 'destructive',\n precheck: [],\n execute: [\n {\n description: `set validation on ${collection}`,\n command: new CollModCommand(collection, {\n validator: { $jsonSchema: schema },\n validationLevel: options?.validationLevel,\n validationAction: options?.validationAction,\n }),\n },\n ],\n postcheck: [],\n };\n}\n\nexport function collMod(\n collection: string,\n options: CollModOptions,\n meta?: CollModMeta,\n): MongoMigrationPlanOperation {\n const hasValidator = options.validator != null && Object.keys(options.validator).length > 0;\n\n return {\n id: meta?.id ?? `collection.${collection}.collMod`,\n label: meta?.label ?? `Modify collection ${collection}`,\n operationClass: meta?.operationClass ?? 'destructive',\n precheck:\n options.validator != null\n ? [\n {\n description: `collection ${collection} exists`,\n source: new ListCollectionsCommand(),\n filter: MongoFieldFilter.eq('name', collection),\n expect: 'exists' as const,\n },\n ]\n : [],\n execute: [\n {\n description: `modify ${collection}`,\n command: new CollModCommand(collection, options),\n },\n ],\n postcheck: hasValidator\n ? [\n {\n description: `validator applied on ${collection}`,\n source: new ListCollectionsCommand(),\n filter: MongoAndExpr.of([\n MongoFieldFilter.eq('name', collection),\n ...(options.validationLevel\n ? [MongoFieldFilter.eq('options.validationLevel', options.validationLevel)]\n : []),\n ...(options.validationAction\n ? [MongoFieldFilter.eq('options.validationAction', options.validationAction)]\n : []),\n ]),\n expect: 'exists' as const,\n },\n ]\n : [],\n };\n}\n\nexport function validatedCollection(\n name: string,\n schema: Record<string, unknown>,\n indexes: ReadonlyArray<{ keys: MongoIndexKey[]; unique?: boolean }>,\n): MongoMigrationPlanOperation[] {\n return [\n createCollection(name, {\n validator: { $jsonSchema: schema },\n validationLevel: 'strict',\n validationAction: 'error',\n }),\n ...indexes.map((idx) => createIndex(name, idx.keys, { unique: idx.unique })),\n ];\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"op-factory-call-CfPGebEH.d.mts","names":[],"sources":["../src/core/op-factory-call.ts"],"sourcesContent":[],"mappings":";;;;;UAciB,WAAA;;EAAA,SAAA,KAAW,CAAA,EAAA,MAAA;EAMX,SAAA,cAAoB,CAAA,EAHT,uBAGS;;AACC,UADrB,oBACqB,CAAA,CAAA,CAAA,CAAA;EACpB,WAAA,CAAA,IAAA,EADE,eACF,CAAA,EADoB,CACpB;EAAgB,SAAA,CAAA,IAAA,EAAhB,aAAgB,CAAA,EAAA,CAAA;EACT,gBAAA,CAAA,IAAA,EAAA,oBAAA,CAAA,EAAuB,CAAvB;EAAuB,cAAA,CAAA,IAAA,EACzB,kBADyB,CAAA,EACJ,CADI;EACzB,OAAA,CAAA,IAAA,EACP,WADO,CAAA,EACO,CADP;;uBAIR,iBAAA,CAHC;EAAc,kBAAA,OAAA,EAAA,MAAA;EAAC,kBAAA,cAAA,EAKK,uBALL;EAGhB,kBAAA,KAAiB,EAAA,MAAA;EAEI,SAAA,MAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAEN,oBAFM,CAEe,CAFf,CAAA,CAAA,EAEoB,CAFpB;EAEe,UAAA,MAAA,CAAA,CAAA,EAAA,IAAA;;AAAK,cAW3C,eAAA,SAAwB,iBAAA,CAXmB;EAAC,SAAA,OAAA,EAAA,aAAA;EAW5C,SAAA,cAAgB,EAAA,UAAA;EAIE,SAAA,UAAA,EAAA,MAAA;EAAd,SAAA,IAAA,EAAA,aAAA,CAAc,aAAd,CAAA;EACG,SAAA,OAAA,EAAA,kBAAA,GAAA,SAAA;EAKI,SAAA,KAAA,EAAA,MAAA;EAAd,WAAA,CAAA,UAAA,EAAA,MAAA,EAAA,IAAA,EAAA,aAAA,CAAc,aAAd,CAAA,EAAA,OAAA,CAAA,EACI,kBADJ;EACI,MAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAUO,oBAVP,CAU4B,CAV5B,CAAA,CAAA,EAUiC,CAVjC;;AAUO,cAKR,aAAA,SAAsB,iBAAA,CALd;EAA0B,SAAA,OAAA,EAAA,WAAA;EArBV,SAAA,cAAA,EAAA,aAAA;EAAiB,SAAA,UAAA,EAAA,MAAA;EA0BzC,SAAA,IAAA,EAII,aAJU,CAII,aAJJ,CAAA;EAII,SAAA,KAAA,EAAA,MAAA;EAAd,WAAA,CAAA,UAAA,EAAA,MAAA,EAAA,IAAA,EAGuB,aAHvB,CAGqC,aAHrC,CAAA;EAGqC,MAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAQjC,oBARiC,CAQZ,CARY,CAAA,CAAA,EAQP,CARO;;AAQZ,cAK7B,oBAAA,SAA6B,iBAAA,CALA;EAArB,SAAA,OAAA,EAAA,kBAAA;EAA0B,SAAA,cAAA,EAAA,UAAA;EAfZ,SAAA,UAAA,EAAA,MAAA;EAAiB,SAAA,OAAA,EAwBhC,uBAxBgC,GAAA,SAAA;EAoBvC,SAAA,KAAA,EAAA,MAAA;EAIO,WAAA,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAGwB,uBAHxB;EAGwB,MAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAQvB,oBARuB,CAQF,CARE,CAAA,CAAA,EAQG,CARH;;AAQvB,cAKR,kBAAA,SAA2B,iBAAA,CALnB;EAA0B,SAAA,OAAA,EAAA,gBAAA;EAfL,SAAA,cAAA,EAAA,aAAA;EAAiB,SAAA,UAAA,EAAA,MAAA;EAoB9C,SAAA,KAAA,EAAA,MAAmB;EAaU,WAAA,CAAA,UAAA,EAAA,MAAA;EAArB,MAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAAA,oBAAA,CAAqB,CAArB,CAAA,CAAA,EAA0B,CAA1B;;AAbmB,cAkB3B,WAAA,SAAoB,iBAAA,CAlBO;EAAiB,SAAA,OAAA,EAAA,SAAA;EAkB5C,SAAA,UAAY,EAAA,MAAA;EAGL,SAAA,OAAA,EAAA,cAAA;EACH,SAAA,IAAA,EAAA,WAAA,GAAA,SAAA;EACU,SAAA,cAAA,EAAA,uBAAA;EAGgB,SAAA,KAAA,EAAA,MAAA;EAAuB,WAAA,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAAvB,cAAuB,EAAA,IAAA,CAAA,EAAA,WAAA;EAUxB,MAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAArB,oBAAqB,CAAA,CAAA,CAAA,CAAA,EAAK,CAAL;;AAAK,KAKnC,aAAA,GACR,eAN2C,GAO3C,aAP2C,GAQ3C,oBAR2C,GAS3C,kBAT2C,GAU3C,WAV2C;AAlBd,iBA8BjB,+BAAA,CA9BiB,KAAA,EA8BsB,gBA9BtB,CAAA,EA8ByC,kBA9BzC;AAAiB,iBA4ClC,yCAAA,CA5CkC,IAAA,EA6C1C,qBA7C0C,CAAA,EA8C/C,uBA9C+C,GAAA,SAAA"}