@prisma-next/target-mongo 0.4.0-dev.8 → 0.4.0-dev.9
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 +9 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +289 -12
- 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 +15 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +2 -2
- package/package.json +13 -12
- package/src/core/migration-factories.ts +69 -1
- package/src/core/mongo-ops-serializer.ts +324 -5
- package/src/core/mongo-runner.ts +144 -8
- package/src/exports/migration.ts +1 -0
- package/dist/migration-factories-Brzz-QGG.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/target-mongo",
|
|
3
|
-
"version": "0.4.0-dev.
|
|
3
|
+
"version": "0.4.0-dev.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "MongoDB target pack for Prisma Next",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"arktype": "^2.1.29",
|
|
9
9
|
"mongodb": "^6.16.0",
|
|
10
|
-
"@prisma-next/contract": "0.4.0-dev.
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/mongo-
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/
|
|
16
|
-
"@prisma-next/
|
|
17
|
-
"@prisma-next/mongo-
|
|
18
|
-
"@prisma-next/utils": "0.4.0-dev.
|
|
10
|
+
"@prisma-next/contract": "0.4.0-dev.9",
|
|
11
|
+
"@prisma-next/migration-tools": "0.4.0-dev.9",
|
|
12
|
+
"@prisma-next/errors": "0.4.0-dev.9",
|
|
13
|
+
"@prisma-next/mongo-contract": "0.4.0-dev.9",
|
|
14
|
+
"@prisma-next/mongo-lowering": "0.4.0-dev.9",
|
|
15
|
+
"@prisma-next/mongo-schema-ir": "0.4.0-dev.9",
|
|
16
|
+
"@prisma-next/framework-components": "0.4.0-dev.9",
|
|
17
|
+
"@prisma-next/mongo-query-ast": "0.4.0-dev.9",
|
|
18
|
+
"@prisma-next/utils": "0.4.0-dev.9",
|
|
19
|
+
"@prisma-next/mongo-value": "0.4.0-dev.9"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"mongodb-memory-server": "10.4.3",
|
|
@@ -24,8 +25,8 @@
|
|
|
24
25
|
"typescript": "5.9.3",
|
|
25
26
|
"vitest": "4.0.17",
|
|
26
27
|
"@prisma-next/test-utils": "0.0.1",
|
|
27
|
-
"@prisma-next/
|
|
28
|
-
"@prisma-next/
|
|
28
|
+
"@prisma-next/tsdown": "0.0.0",
|
|
29
|
+
"@prisma-next/tsconfig": "0.0.0"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|
|
31
32
|
"dist",
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
MongoDataTransformCheck,
|
|
3
|
+
MongoDataTransformOperation,
|
|
4
|
+
MongoFilterExpr,
|
|
5
|
+
MongoIndexKey,
|
|
6
|
+
} from '@prisma-next/mongo-query-ast/control';
|
|
2
7
|
import {
|
|
3
8
|
buildIndexOpId,
|
|
4
9
|
CollModCommand,
|
|
@@ -14,11 +19,74 @@ import {
|
|
|
14
19
|
ListCollectionsCommand,
|
|
15
20
|
ListIndexesCommand,
|
|
16
21
|
MongoAndExpr,
|
|
22
|
+
MongoExistsExpr,
|
|
17
23
|
MongoFieldFilter,
|
|
18
24
|
type MongoMigrationPlanOperation,
|
|
19
25
|
} from '@prisma-next/mongo-query-ast/control';
|
|
26
|
+
import type { MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';
|
|
20
27
|
import type { CollModMeta } from './op-factory-call';
|
|
21
28
|
|
|
29
|
+
interface Buildable {
|
|
30
|
+
build(): MongoQueryPlan;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isBuildable(value: unknown): value is Buildable {
|
|
34
|
+
return (
|
|
35
|
+
typeof value === 'object' &&
|
|
36
|
+
value !== null &&
|
|
37
|
+
'build' in value &&
|
|
38
|
+
typeof (value as { build: unknown }).build === 'function'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveQuery(value: MongoQueryPlan | Buildable): MongoQueryPlan {
|
|
43
|
+
return isBuildable(value) ? value.build() : value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Every MongoDB document carries `_id`, so `exists('_id')` is equivalent to
|
|
47
|
+
// "match all". The filter AST has no identity/always-true expression.
|
|
48
|
+
const MATCH_ALL_FILTER: MongoFilterExpr = MongoExistsExpr.exists('_id');
|
|
49
|
+
|
|
50
|
+
export function dataTransform(
|
|
51
|
+
name: string,
|
|
52
|
+
options: {
|
|
53
|
+
check?: {
|
|
54
|
+
source: () => MongoQueryPlan | Buildable;
|
|
55
|
+
filter?: MongoFilterExpr;
|
|
56
|
+
expect?: 'exists' | 'notExists';
|
|
57
|
+
description?: string;
|
|
58
|
+
};
|
|
59
|
+
run: () => MongoQueryPlan | Buildable;
|
|
60
|
+
},
|
|
61
|
+
): MongoDataTransformOperation {
|
|
62
|
+
let precheck: readonly MongoDataTransformCheck[] = [];
|
|
63
|
+
let postcheck: readonly MongoDataTransformCheck[] = [];
|
|
64
|
+
|
|
65
|
+
if (options.check) {
|
|
66
|
+
const source = resolveQuery(options.check.source());
|
|
67
|
+
const filter = options.check.filter ?? MATCH_ALL_FILTER;
|
|
68
|
+
const description = options.check.description ?? `Check for data transform: ${name}`;
|
|
69
|
+
const precheckExpect = options.check.expect ?? 'exists';
|
|
70
|
+
const postcheckExpect: 'exists' | 'notExists' =
|
|
71
|
+
precheckExpect === 'exists' ? 'notExists' : 'exists';
|
|
72
|
+
|
|
73
|
+
precheck = [{ description, source, filter, expect: precheckExpect }];
|
|
74
|
+
postcheck = [{ description, source, filter, expect: postcheckExpect }];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const run: MongoQueryPlan[] = [resolveQuery(options.run())];
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
id: `data_transform.${name}`,
|
|
81
|
+
label: `Data transform: ${name}`,
|
|
82
|
+
operationClass: 'data',
|
|
83
|
+
name,
|
|
84
|
+
precheck,
|
|
85
|
+
run,
|
|
86
|
+
postcheck,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
22
90
|
function formatKeys(keys: ReadonlyArray<MongoIndexKey>): string {
|
|
23
91
|
return keys.map((k) => `${k.field}:${k.direction}`).join(', ');
|
|
24
92
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { PlanMeta } from '@prisma-next/contract/types';
|
|
1
2
|
import type { MigrationOperationClass } from '@prisma-next/framework-components/control';
|
|
2
3
|
import {
|
|
3
4
|
type AnyMongoDdlCommand,
|
|
4
5
|
type AnyMongoInspectionCommand,
|
|
6
|
+
type AnyMongoMigrationOperation,
|
|
5
7
|
CollModCommand,
|
|
6
8
|
CreateCollectionCommand,
|
|
7
9
|
CreateIndexCommand,
|
|
@@ -10,6 +12,8 @@ import {
|
|
|
10
12
|
ListCollectionsCommand,
|
|
11
13
|
ListIndexesCommand,
|
|
12
14
|
MongoAndExpr,
|
|
15
|
+
type MongoDataTransformCheck,
|
|
16
|
+
type MongoDataTransformOperation,
|
|
13
17
|
MongoExistsExpr,
|
|
14
18
|
MongoFieldFilter,
|
|
15
19
|
type MongoFilterExpr,
|
|
@@ -19,6 +23,30 @@ import {
|
|
|
19
23
|
MongoNotExpr,
|
|
20
24
|
MongoOrExpr,
|
|
21
25
|
} from '@prisma-next/mongo-query-ast/control';
|
|
26
|
+
import {
|
|
27
|
+
AggregateCommand,
|
|
28
|
+
type AnyMongoCommand,
|
|
29
|
+
MongoAddFieldsStage,
|
|
30
|
+
MongoLimitStage,
|
|
31
|
+
MongoLookupStage,
|
|
32
|
+
MongoMatchStage,
|
|
33
|
+
MongoMergeStage,
|
|
34
|
+
type MongoPipelineStage,
|
|
35
|
+
MongoProjectStage,
|
|
36
|
+
type MongoQueryPlan,
|
|
37
|
+
MongoSortStage,
|
|
38
|
+
type MongoUpdatePipelineStage,
|
|
39
|
+
RawAggregateCommand,
|
|
40
|
+
RawDeleteManyCommand,
|
|
41
|
+
RawDeleteOneCommand,
|
|
42
|
+
RawFindOneAndDeleteCommand,
|
|
43
|
+
RawFindOneAndUpdateCommand,
|
|
44
|
+
RawInsertManyCommand,
|
|
45
|
+
RawInsertOneCommand,
|
|
46
|
+
RawUpdateManyCommand,
|
|
47
|
+
RawUpdateOneCommand,
|
|
48
|
+
} from '@prisma-next/mongo-query-ast/execution';
|
|
49
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
22
50
|
import { type } from 'arktype';
|
|
23
51
|
|
|
24
52
|
const IndexKeyDirection = type('1 | -1 | "text" | "2dsphere" | "2d" | "hashed"');
|
|
@@ -97,6 +125,97 @@ const ExistsFilterJson = type({
|
|
|
97
125
|
exists: 'boolean',
|
|
98
126
|
});
|
|
99
127
|
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// DML command schemas
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
const RawInsertOneJson = type({
|
|
133
|
+
kind: '"rawInsertOne"',
|
|
134
|
+
collection: 'string',
|
|
135
|
+
document: 'Record<string, unknown>',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const RawInsertManyJson = type({
|
|
139
|
+
kind: '"rawInsertMany"',
|
|
140
|
+
collection: 'string',
|
|
141
|
+
documents: 'Record<string, unknown>[]',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const RawUpdateOneJson = type({
|
|
145
|
+
kind: '"rawUpdateOne"',
|
|
146
|
+
collection: 'string',
|
|
147
|
+
filter: 'Record<string, unknown>',
|
|
148
|
+
update: 'Record<string, unknown> | Record<string, unknown>[]',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const RawUpdateManyJson = type({
|
|
152
|
+
kind: '"rawUpdateMany"',
|
|
153
|
+
collection: 'string',
|
|
154
|
+
filter: 'Record<string, unknown>',
|
|
155
|
+
update: 'Record<string, unknown> | Record<string, unknown>[]',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const RawDeleteOneJson = type({
|
|
159
|
+
kind: '"rawDeleteOne"',
|
|
160
|
+
collection: 'string',
|
|
161
|
+
filter: 'Record<string, unknown>',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const RawDeleteManyJson = type({
|
|
165
|
+
kind: '"rawDeleteMany"',
|
|
166
|
+
collection: 'string',
|
|
167
|
+
filter: 'Record<string, unknown>',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const RawAggregateJson = type({
|
|
171
|
+
kind: '"rawAggregate"',
|
|
172
|
+
collection: 'string',
|
|
173
|
+
pipeline: 'Record<string, unknown>[]',
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const RawFindOneAndUpdateJson = type({
|
|
177
|
+
kind: '"rawFindOneAndUpdate"',
|
|
178
|
+
collection: 'string',
|
|
179
|
+
filter: 'Record<string, unknown>',
|
|
180
|
+
update: 'Record<string, unknown> | Record<string, unknown>[]',
|
|
181
|
+
upsert: 'boolean',
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const RawFindOneAndDeleteJson = type({
|
|
185
|
+
kind: '"rawFindOneAndDelete"',
|
|
186
|
+
collection: 'string',
|
|
187
|
+
filter: 'Record<string, unknown>',
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const TypedAggregateJson = type({
|
|
191
|
+
kind: '"aggregate"',
|
|
192
|
+
collection: 'string',
|
|
193
|
+
pipeline: 'Record<string, unknown>[]',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const PlanMetaJson = type({
|
|
197
|
+
target: 'string',
|
|
198
|
+
storageHash: 'string',
|
|
199
|
+
lane: 'string',
|
|
200
|
+
paramDescriptors: 'unknown[]',
|
|
201
|
+
'targetFamily?': 'string',
|
|
202
|
+
'profileHash?': 'string',
|
|
203
|
+
'annotations?': 'Record<string, unknown>',
|
|
204
|
+
'refs?': 'Record<string, unknown>',
|
|
205
|
+
'projection?': 'Record<string, string> | string[]',
|
|
206
|
+
'projectionTypes?': 'Record<string, string>',
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const QueryPlanJson = type({
|
|
210
|
+
collection: 'string',
|
|
211
|
+
command: 'Record<string, unknown>',
|
|
212
|
+
meta: PlanMetaJson,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// DDL check/step schemas
|
|
217
|
+
// ============================================================================
|
|
218
|
+
|
|
100
219
|
const CheckJson = type({
|
|
101
220
|
description: 'string',
|
|
102
221
|
source: 'Record<string, unknown>',
|
|
@@ -109,7 +228,7 @@ const StepJson = type({
|
|
|
109
228
|
command: 'Record<string, unknown>',
|
|
110
229
|
});
|
|
111
230
|
|
|
112
|
-
const
|
|
231
|
+
const DdlOperationJson = type({
|
|
113
232
|
id: 'string',
|
|
114
233
|
label: 'string',
|
|
115
234
|
operationClass: '"additive" | "widening" | "destructive"',
|
|
@@ -118,6 +237,23 @@ const OperationJson = type({
|
|
|
118
237
|
postcheck: 'Record<string, unknown>[]',
|
|
119
238
|
});
|
|
120
239
|
|
|
240
|
+
const DataTransformCheckJson = type({
|
|
241
|
+
description: 'string',
|
|
242
|
+
source: 'Record<string, unknown>',
|
|
243
|
+
filter: 'Record<string, unknown>',
|
|
244
|
+
expect: '"exists" | "notExists"',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const DataTransformOperationJson = type({
|
|
248
|
+
id: 'string',
|
|
249
|
+
label: 'string',
|
|
250
|
+
operationClass: '"data"',
|
|
251
|
+
name: 'string',
|
|
252
|
+
precheck: 'Record<string, unknown>[]',
|
|
253
|
+
run: 'Record<string, unknown>[]',
|
|
254
|
+
postcheck: 'Record<string, unknown>[]',
|
|
255
|
+
});
|
|
256
|
+
|
|
121
257
|
function validate<T>(schema: { assert: (data: unknown) => T }, data: unknown, context: string): T {
|
|
122
258
|
try {
|
|
123
259
|
return schema.assert(data);
|
|
@@ -161,6 +297,151 @@ function deserializeFilterExpr(json: unknown): MongoFilterExpr {
|
|
|
161
297
|
}
|
|
162
298
|
}
|
|
163
299
|
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// Pipeline stage deserialization
|
|
302
|
+
// ============================================================================
|
|
303
|
+
|
|
304
|
+
export function deserializePipelineStage(json: unknown): MongoPipelineStage {
|
|
305
|
+
const record = json as Record<string, unknown>;
|
|
306
|
+
const kind = record['kind'] as string;
|
|
307
|
+
switch (kind) {
|
|
308
|
+
case 'match':
|
|
309
|
+
return new MongoMatchStage(deserializeFilterExpr(record['filter']));
|
|
310
|
+
case 'limit':
|
|
311
|
+
return new MongoLimitStage(record['limit'] as number);
|
|
312
|
+
case 'sort':
|
|
313
|
+
return new MongoSortStage(record['sort'] as Record<string, 1 | -1>);
|
|
314
|
+
case 'project':
|
|
315
|
+
return new MongoProjectStage(record['projection'] as Record<string, 0 | 1>);
|
|
316
|
+
case 'addFields':
|
|
317
|
+
return new MongoAddFieldsStage(record['fields'] as Record<string, never>);
|
|
318
|
+
case 'lookup': {
|
|
319
|
+
const opts: {
|
|
320
|
+
from: string;
|
|
321
|
+
as: string;
|
|
322
|
+
localField?: string;
|
|
323
|
+
foreignField?: string;
|
|
324
|
+
pipeline?: ReadonlyArray<MongoPipelineStage>;
|
|
325
|
+
let_?: Record<string, never>;
|
|
326
|
+
} = {
|
|
327
|
+
from: record['from'] as string,
|
|
328
|
+
as: record['as'] as string,
|
|
329
|
+
};
|
|
330
|
+
if (record['localField'] !== undefined) opts.localField = record['localField'] as string;
|
|
331
|
+
if (record['foreignField'] !== undefined)
|
|
332
|
+
opts.foreignField = record['foreignField'] as string;
|
|
333
|
+
if (record['pipeline'] !== undefined)
|
|
334
|
+
opts.pipeline = (record['pipeline'] as unknown[]).map(deserializePipelineStage);
|
|
335
|
+
if (record['let_'] !== undefined) opts.let_ = record['let_'] as Record<string, never>;
|
|
336
|
+
return new MongoLookupStage(opts);
|
|
337
|
+
}
|
|
338
|
+
case 'merge': {
|
|
339
|
+
const opts: {
|
|
340
|
+
into: string | { db: string; coll: string };
|
|
341
|
+
on?: string | ReadonlyArray<string>;
|
|
342
|
+
whenMatched?: string | ReadonlyArray<MongoUpdatePipelineStage>;
|
|
343
|
+
whenNotMatched?: string;
|
|
344
|
+
} = {
|
|
345
|
+
into: record['into'] as string | { db: string; coll: string },
|
|
346
|
+
};
|
|
347
|
+
if (record['on'] !== undefined) opts.on = record['on'] as string | string[];
|
|
348
|
+
if (record['whenMatched'] !== undefined) {
|
|
349
|
+
const wm = record['whenMatched'];
|
|
350
|
+
opts.whenMatched =
|
|
351
|
+
typeof wm === 'string'
|
|
352
|
+
? wm
|
|
353
|
+
: ((wm as unknown[]).map(deserializePipelineStage) as MongoUpdatePipelineStage[]);
|
|
354
|
+
}
|
|
355
|
+
if (record['whenNotMatched'] !== undefined)
|
|
356
|
+
opts.whenNotMatched = record['whenNotMatched'] as string;
|
|
357
|
+
return new MongoMergeStage(opts);
|
|
358
|
+
}
|
|
359
|
+
default:
|
|
360
|
+
throw new Error(`Unknown pipeline stage kind: ${kind}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// DML command deserialization
|
|
366
|
+
// ============================================================================
|
|
367
|
+
|
|
368
|
+
export function deserializeDmlCommand(json: unknown): AnyMongoCommand {
|
|
369
|
+
const record = json as Record<string, unknown>;
|
|
370
|
+
const kind = record['kind'] as string;
|
|
371
|
+
switch (kind) {
|
|
372
|
+
case 'rawInsertOne': {
|
|
373
|
+
const data = validate(RawInsertOneJson, json, 'rawInsertOne command');
|
|
374
|
+
return new RawInsertOneCommand(data.collection, data.document);
|
|
375
|
+
}
|
|
376
|
+
case 'rawInsertMany': {
|
|
377
|
+
const data = validate(RawInsertManyJson, json, 'rawInsertMany command');
|
|
378
|
+
return new RawInsertManyCommand(data.collection, data.documents);
|
|
379
|
+
}
|
|
380
|
+
case 'rawUpdateOne': {
|
|
381
|
+
const data = validate(RawUpdateOneJson, json, 'rawUpdateOne command');
|
|
382
|
+
return new RawUpdateOneCommand(data.collection, data.filter, data.update);
|
|
383
|
+
}
|
|
384
|
+
case 'rawUpdateMany': {
|
|
385
|
+
const data = validate(RawUpdateManyJson, json, 'rawUpdateMany command');
|
|
386
|
+
return new RawUpdateManyCommand(data.collection, data.filter, data.update);
|
|
387
|
+
}
|
|
388
|
+
case 'rawDeleteOne': {
|
|
389
|
+
const data = validate(RawDeleteOneJson, json, 'rawDeleteOne command');
|
|
390
|
+
return new RawDeleteOneCommand(data.collection, data.filter);
|
|
391
|
+
}
|
|
392
|
+
case 'rawDeleteMany': {
|
|
393
|
+
const data = validate(RawDeleteManyJson, json, 'rawDeleteMany command');
|
|
394
|
+
return new RawDeleteManyCommand(data.collection, data.filter);
|
|
395
|
+
}
|
|
396
|
+
case 'rawAggregate': {
|
|
397
|
+
const data = validate(RawAggregateJson, json, 'rawAggregate command');
|
|
398
|
+
return new RawAggregateCommand(data.collection, data.pipeline);
|
|
399
|
+
}
|
|
400
|
+
case 'rawFindOneAndUpdate': {
|
|
401
|
+
const data = validate(RawFindOneAndUpdateJson, json, 'rawFindOneAndUpdate command');
|
|
402
|
+
return new RawFindOneAndUpdateCommand(data.collection, data.filter, data.update, data.upsert);
|
|
403
|
+
}
|
|
404
|
+
case 'rawFindOneAndDelete': {
|
|
405
|
+
const data = validate(RawFindOneAndDeleteJson, json, 'rawFindOneAndDelete command');
|
|
406
|
+
return new RawFindOneAndDeleteCommand(data.collection, data.filter);
|
|
407
|
+
}
|
|
408
|
+
case 'aggregate': {
|
|
409
|
+
const data = validate(TypedAggregateJson, json, 'aggregate command');
|
|
410
|
+
const pipeline = data.pipeline.map(deserializePipelineStage);
|
|
411
|
+
return new AggregateCommand(data.collection, pipeline);
|
|
412
|
+
}
|
|
413
|
+
default:
|
|
414
|
+
throw new Error(`Unknown DML command kind: ${kind}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// MongoQueryPlan deserialization
|
|
420
|
+
// ============================================================================
|
|
421
|
+
|
|
422
|
+
export function deserializeMongoQueryPlan(json: unknown): MongoQueryPlan {
|
|
423
|
+
const data = validate(QueryPlanJson, json, 'Mongo query plan');
|
|
424
|
+
const command = deserializeDmlCommand(data.command);
|
|
425
|
+
const m = data.meta;
|
|
426
|
+
const meta: PlanMeta = {
|
|
427
|
+
target: m.target,
|
|
428
|
+
storageHash: m.storageHash,
|
|
429
|
+
lane: m.lane,
|
|
430
|
+
paramDescriptors: m.paramDescriptors as PlanMeta['paramDescriptors'],
|
|
431
|
+
...ifDefined('targetFamily', m.targetFamily),
|
|
432
|
+
...ifDefined('profileHash', m.profileHash),
|
|
433
|
+
...ifDefined('annotations', m.annotations),
|
|
434
|
+
...ifDefined('refs', m.refs),
|
|
435
|
+
...ifDefined('projection', m.projection),
|
|
436
|
+
...ifDefined('projectionTypes', m.projectionTypes),
|
|
437
|
+
};
|
|
438
|
+
return { collection: data.collection, command, meta };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ============================================================================
|
|
442
|
+
// DDL command deserialization
|
|
443
|
+
// ============================================================================
|
|
444
|
+
|
|
164
445
|
function deserializeDdlCommand(json: unknown): AnyMongoDdlCommand {
|
|
165
446
|
const record = json as Record<string, unknown>;
|
|
166
447
|
const kind = record['kind'] as string;
|
|
@@ -256,8 +537,16 @@ function deserializeStep(json: unknown): MongoMigrationStep {
|
|
|
256
537
|
};
|
|
257
538
|
}
|
|
258
539
|
|
|
259
|
-
|
|
260
|
-
|
|
540
|
+
function isDataTransformJson(json: unknown): boolean {
|
|
541
|
+
return (
|
|
542
|
+
typeof json === 'object' &&
|
|
543
|
+
json !== null &&
|
|
544
|
+
(json as Record<string, unknown>)['operationClass'] === 'data'
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function deserializeDdlOp(json: unknown): MongoMigrationPlanOperation {
|
|
549
|
+
const data = validate(DdlOperationJson, json, 'migration operation');
|
|
261
550
|
return {
|
|
262
551
|
id: data.id,
|
|
263
552
|
label: data.label,
|
|
@@ -268,10 +557,40 @@ export function deserializeMongoOp(json: unknown): MongoMigrationPlanOperation {
|
|
|
268
557
|
};
|
|
269
558
|
}
|
|
270
559
|
|
|
271
|
-
|
|
560
|
+
function deserializeDataTransformCheck(json: unknown): MongoDataTransformCheck {
|
|
561
|
+
const data = validate(DataTransformCheckJson, json, 'data transform check');
|
|
562
|
+
return {
|
|
563
|
+
description: data.description,
|
|
564
|
+
source: deserializeMongoQueryPlan(data.source),
|
|
565
|
+
filter: deserializeFilterExpr(data.filter),
|
|
566
|
+
expect: data.expect,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function deserializeDataTransformOp(json: unknown): MongoDataTransformOperation {
|
|
571
|
+
const data = validate(DataTransformOperationJson, json, 'data transform operation');
|
|
572
|
+
return {
|
|
573
|
+
id: data.id,
|
|
574
|
+
label: data.label,
|
|
575
|
+
operationClass: 'data',
|
|
576
|
+
name: data.name,
|
|
577
|
+
precheck: data.precheck.map(deserializeDataTransformCheck),
|
|
578
|
+
run: data.run.map(deserializeMongoQueryPlan),
|
|
579
|
+
postcheck: data.postcheck.map(deserializeDataTransformCheck),
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function deserializeMongoOp(json: unknown): AnyMongoMigrationOperation {
|
|
584
|
+
if (isDataTransformJson(json)) {
|
|
585
|
+
return deserializeDataTransformOp(json);
|
|
586
|
+
}
|
|
587
|
+
return deserializeDdlOp(json);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function deserializeMongoOps(json: readonly unknown[]): AnyMongoMigrationOperation[] {
|
|
272
591
|
return json.map(deserializeMongoOp);
|
|
273
592
|
}
|
|
274
593
|
|
|
275
|
-
export function serializeMongoOps(ops: readonly
|
|
594
|
+
export function serializeMongoOps(ops: readonly AnyMongoMigrationOperation[]): string {
|
|
276
595
|
return JSON.stringify(ops, null, 2);
|
|
277
596
|
}
|