@prisma-next/family-mongo 0.7.0 → 0.8.0-dev.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/README.md +22 -9
- package/dist/control-adapter.d.mts +75 -0
- package/dist/control-adapter.d.mts.map +1 -0
- package/dist/control-adapter.mjs +1 -0
- package/dist/control.d.mts +60 -16
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +257 -276
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +131 -0
- package/dist/ir.d.mts.map +1 -0
- package/dist/ir.mjs +54 -0
- package/dist/ir.mjs.map +1 -0
- package/dist/mongo-contract-serializer-Co3EaTVj.mjs +98 -0
- package/dist/mongo-contract-serializer-Co3EaTVj.mjs.map +1 -0
- package/dist/schema-verify.d.mts +22 -2
- package/dist/schema-verify.d.mts.map +1 -0
- package/dist/schema-verify.mjs +1 -1
- package/dist/verify-mongo-schema-BL7t9YTB.mjs +592 -0
- package/dist/verify-mongo-schema-BL7t9YTB.mjs.map +1 -0
- package/package.json +20 -22
- package/src/core/contract-to-schema.ts +84 -0
- package/src/core/control-adapter.ts +97 -0
- package/src/core/control-instance.ts +275 -272
- package/src/core/control-target-descriptor.ts +26 -0
- package/src/core/control-types.ts +6 -4
- package/src/core/ir/mongo-contract-serializer-base.ts +124 -0
- package/src/core/ir/mongo-contract-serializer.ts +18 -0
- package/src/core/ir/mongo-schema-verifier-base.ts +87 -0
- package/src/core/operation-preview.ts +131 -0
- package/src/core/schema-diff.ts +402 -0
- package/src/core/schema-verify/canonicalize-introspection.ts +389 -0
- package/src/core/schema-verify/verify-mongo-schema.ts +60 -0
- package/src/exports/control-adapter.ts +1 -0
- package/src/exports/control.ts +8 -1
- package/src/exports/ir.ts +3 -0
- package/src/exports/schema-verify.ts +4 -2
- package/src/core/mongo-target-descriptor.ts +0 -180
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { validateContractDomain } from '@prisma-next/contract/validate-domain';
|
|
2
|
+
import type { ContractSerializer } from '@prisma-next/framework-components/control';
|
|
3
|
+
import {
|
|
4
|
+
MongoCollection,
|
|
5
|
+
type MongoCollectionInput,
|
|
6
|
+
type MongoContract,
|
|
7
|
+
MongoContractSchema,
|
|
8
|
+
validateMongoStorage,
|
|
9
|
+
} from '@prisma-next/mongo-contract';
|
|
10
|
+
import type { JsonObject } from '@prisma-next/utils/json';
|
|
11
|
+
import { type as arktypeType } from 'arktype';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Mongo family `ContractSerializer` abstract base. Owns the family-shared
|
|
15
|
+
* deserialization pipeline:
|
|
16
|
+
*
|
|
17
|
+
* 1. Structural validation against the Mongo contract arktype schema
|
|
18
|
+
* (`MongoContractSchema`).
|
|
19
|
+
* 2. Framework-shared domain validation (`validateContractDomain`).
|
|
20
|
+
* 3. Family-shared storage validation (`validateMongoStorage`).
|
|
21
|
+
*
|
|
22
|
+
* The validated value is handed to the target via the
|
|
23
|
+
* `constructTargetContract` hook, which wraps the plain-JSON shape in
|
|
24
|
+
* the family-layer `MongoStorage` class instance (carrying the
|
|
25
|
+
* target-supplied `namespaces` map). Targets that need to add
|
|
26
|
+
* structural checks beyond the family default can override
|
|
27
|
+
* `parseMongoContractStructure`.
|
|
28
|
+
*
|
|
29
|
+
* Default `serializeContract` is identity over the contract — Mongo
|
|
30
|
+
* target classes carry JSON-clean fields by construction, so the value
|
|
31
|
+
* can be `JSON.stringify`'d directly. Targets that need on-the-way-out
|
|
32
|
+
* canonicalization override `serializeContract`.
|
|
33
|
+
*/
|
|
34
|
+
export abstract class MongoContractSerializerBase<TContract>
|
|
35
|
+
implements ContractSerializer<TContract>
|
|
36
|
+
{
|
|
37
|
+
deserializeContract(json: unknown): TContract {
|
|
38
|
+
const validated = this.parseMongoContractStructure(json);
|
|
39
|
+
return this.constructTargetContract(validated);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
serializeContract(contract: TContract): JsonObject {
|
|
43
|
+
// Mongo contract class fields are JSON-clean by construction; the
|
|
44
|
+
// cast asserts that. Targets that need to canonicalize on the way
|
|
45
|
+
// out override this method.
|
|
46
|
+
return contract as unknown as JsonObject;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Family-shared structural validation: parse against the Mongo
|
|
51
|
+
* contract arktype schema, then run framework-shared domain + Mongo
|
|
52
|
+
* family storage checks, then hydrate the validated tree into Mongo
|
|
53
|
+
* Contract IR class instances. Targets can override to add
|
|
54
|
+
* target-specific structural checks; most targets accept the family
|
|
55
|
+
* default.
|
|
56
|
+
*
|
|
57
|
+
* The returned `MongoContract` carries class instances under
|
|
58
|
+
* `storage.collections` (each value is a `MongoCollection`, with
|
|
59
|
+
* nested `MongoIndex` / `MongoValidator` / `MongoCollectionOptions`
|
|
60
|
+
* constructed by the `MongoCollection` constructor). The rest of the
|
|
61
|
+
* contract envelope (models, valueObjects, capabilities, …) remains
|
|
62
|
+
* in plain-JSON form; those IR layers are handled by sibling
|
|
63
|
+
* subsystems and don't sit behind this SPI.
|
|
64
|
+
*/
|
|
65
|
+
protected parseMongoContractStructure(json: unknown): MongoContract {
|
|
66
|
+
const parsed = MongoContractSchema(json);
|
|
67
|
+
if (parsed instanceof arktypeType.errors) {
|
|
68
|
+
throw new Error(`Contract structural validation failed: ${parsed.summary}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// arktype's `infer`d type for `MongoContractSchema` is structurally
|
|
72
|
+
// equivalent to `MongoContract` (both describe the same on-disk JSON
|
|
73
|
+
// envelope) but not nominally so: the arktype DSL produces a type whose
|
|
74
|
+
// optional/readonly profile, narrowed string-literal positions, and
|
|
75
|
+
// utility-type wrappings (`Type.infer`, `Out`, …) differ from the
|
|
76
|
+
// hand-authored `MongoContract<S, M>` generic surface. The schema and
|
|
77
|
+
// the type are kept in lockstep by the round-trip fixtures under
|
|
78
|
+
// `test/validate.test.ts`. The hydration walk below additionally
|
|
79
|
+
// re-shapes `storage.collections` from plain data into IR-class
|
|
80
|
+
// instances, so the `MongoContract` returned here carries class
|
|
81
|
+
// identity under `storage.collections.*` (and transitively under
|
|
82
|
+
// `indexes` / `validator` / `options`).
|
|
83
|
+
const validatedShape = parsed as unknown as MongoContract;
|
|
84
|
+
|
|
85
|
+
const hydratedContract = this.hydrateMongoContract(validatedShape);
|
|
86
|
+
|
|
87
|
+
validateContractDomain(hydratedContract);
|
|
88
|
+
validateMongoStorage(hydratedContract);
|
|
89
|
+
|
|
90
|
+
return hydratedContract;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Walk a structurally-validated Mongo contract and convert
|
|
95
|
+
* `storage.collections` entries from plain data into
|
|
96
|
+
* `MongoCollection` IR-class instances. Idempotent: already-class
|
|
97
|
+
* instances pass through unchanged.
|
|
98
|
+
*/
|
|
99
|
+
protected hydrateMongoContract(contract: MongoContract): MongoContract {
|
|
100
|
+
const rawCollections = contract.storage.collections;
|
|
101
|
+
const hydrated: Record<string, MongoCollection> = {};
|
|
102
|
+
for (const [name, raw] of Object.entries(rawCollections)) {
|
|
103
|
+
hydrated[name] =
|
|
104
|
+
raw instanceof MongoCollection ? raw : new MongoCollection(raw as MongoCollectionInput);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
...contract,
|
|
108
|
+
storage: {
|
|
109
|
+
...contract.storage,
|
|
110
|
+
collections: hydrated,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Target-specific class construction from the validated structural
|
|
117
|
+
* data. The target wraps the contract envelope in the family-layer
|
|
118
|
+
* `MongoStorage` class instance, supplying the `namespaces` map
|
|
119
|
+
* (target concretions like `MongoTargetUnspecifiedDatabase`). The
|
|
120
|
+
* leaf collection / index shapes are already family-layer IR-class
|
|
121
|
+
* instances after the hydration walk above.
|
|
122
|
+
*/
|
|
123
|
+
protected abstract constructTargetContract(validated: MongoContract): TContract;
|
|
124
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MongoContract } from '@prisma-next/mongo-contract';
|
|
2
|
+
import { MongoContractSerializerBase } from './mongo-contract-serializer-base';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Default Mongo family `ContractSerializer` concretion. Inherits the
|
|
6
|
+
* Mongo-shared deserialization pipeline (structural validation +
|
|
7
|
+
* collection-level hydration) and falls through `constructTargetContract`
|
|
8
|
+
* with the validated `MongoContract` shape. Family-level call sites
|
|
9
|
+
* (family-instance methods, family-layer tests that don't reach into
|
|
10
|
+
* a target descriptor) instantiate this directly; targets with their
|
|
11
|
+
* own storage concretion (`target-mongo`'s `MongoTargetContractSerializer`)
|
|
12
|
+
* override `constructTargetContract` to wrap the storage shape.
|
|
13
|
+
*/
|
|
14
|
+
export class MongoContractSerializer extends MongoContractSerializerBase<MongoContract> {
|
|
15
|
+
protected constructTargetContract(validated: MongoContract): MongoContract {
|
|
16
|
+
return validated;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SchemaIssue,
|
|
3
|
+
SchemaVerifier,
|
|
4
|
+
SchemaVerifyOptions,
|
|
5
|
+
SchemaVerifyResult,
|
|
6
|
+
} from '@prisma-next/framework-components/control';
|
|
7
|
+
import type { Namespace } from '@prisma-next/framework-components/ir';
|
|
8
|
+
import type { MongoStorage } from '@prisma-next/mongo-contract';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mongo family `SchemaVerifier` abstract base. Commits the Mongo family
|
|
12
|
+
* to namespace-keyed verification: the family-shared walk iterates
|
|
13
|
+
* `storage.namespaces` in sorted order and dispatches per-namespace
|
|
14
|
+
* through the protected `verifyNamespace` hook, then aggregates
|
|
15
|
+
* target-extension issues from `verifyTargetExtensions`.
|
|
16
|
+
*
|
|
17
|
+
* Per-element diff work (collection / index / validator comparisons)
|
|
18
|
+
* lives on the target inside `verifyNamespace`. The family's structural
|
|
19
|
+
* commitment is "verification is namespaced"; the target's commitment is
|
|
20
|
+
* "verification of a given namespace's collections is the existing
|
|
21
|
+
* diff/canonicalize pipeline". The split keeps target-mongo's
|
|
22
|
+
* introspection-side helpers (`contractToMongoSchemaIR`,
|
|
23
|
+
* `canonicalizeSchemasForVerification`, `diffMongoSchemas`) in the target
|
|
24
|
+
* layer where they belong, while the family base owns the iteration
|
|
25
|
+
* scaffolding that makes namespaces a first-class verifier concept.
|
|
26
|
+
*
|
|
27
|
+
* Target-specific issue kinds (Atlas-only, future RLS-equivalents)
|
|
28
|
+
* surface through `verifyTargetExtensions`; that hook returns the empty
|
|
29
|
+
* list when no extensions exist over the Mongo family alphabet.
|
|
30
|
+
*/
|
|
31
|
+
export abstract class MongoSchemaVerifierBase<
|
|
32
|
+
TContract extends { readonly storage: MongoStorage },
|
|
33
|
+
TSchema,
|
|
34
|
+
> implements SchemaVerifier<TContract, TSchema>
|
|
35
|
+
{
|
|
36
|
+
verifySchema(options: SchemaVerifyOptions<TContract, TSchema>): SchemaVerifyResult {
|
|
37
|
+
const issues: SchemaIssue[] = [];
|
|
38
|
+
issues.push(...this.verifyCommonMongoSchema(options));
|
|
39
|
+
issues.push(...this.verifyTargetExtensions(options));
|
|
40
|
+
return { ok: issues.length === 0, issues };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected verifyCommonMongoSchema(
|
|
44
|
+
options: SchemaVerifyOptions<TContract, TSchema>,
|
|
45
|
+
): readonly SchemaIssue[] {
|
|
46
|
+
const issues: SchemaIssue[] = [];
|
|
47
|
+
const { namespaces } = options.contract.storage;
|
|
48
|
+
const namespaceIds = Object.keys(namespaces).sort();
|
|
49
|
+
for (const namespaceId of namespaceIds) {
|
|
50
|
+
const namespace = namespaces[namespaceId];
|
|
51
|
+
if (!namespace) continue;
|
|
52
|
+
issues.push(
|
|
53
|
+
...this.verifyNamespace({
|
|
54
|
+
contract: options.contract,
|
|
55
|
+
schema: options.schema,
|
|
56
|
+
namespaceId,
|
|
57
|
+
namespace,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return issues;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Per-namespace verification hook. Receives the namespace metadata plus
|
|
66
|
+
* the full contract + schema pair; the target's implementation owns the
|
|
67
|
+
* per-collection diff using its existing introspection-side helpers.
|
|
68
|
+
* Slice the schema by namespace at the call site (or compute the full
|
|
69
|
+
* diff once and dispatch per namespace) — the family base does not
|
|
70
|
+
* prescribe the per-namespace shape.
|
|
71
|
+
*/
|
|
72
|
+
protected abstract verifyNamespace(options: {
|
|
73
|
+
readonly contract: TContract;
|
|
74
|
+
readonly schema: TSchema;
|
|
75
|
+
readonly namespaceId: string;
|
|
76
|
+
readonly namespace: Namespace;
|
|
77
|
+
}): readonly SchemaIssue[];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Target-specific extensions — Atlas-only kinds, target-only
|
|
81
|
+
* namespace-mismatch issues that don't fit the family-shared walk.
|
|
82
|
+
* Returns the empty list when the target ships no extensions.
|
|
83
|
+
*/
|
|
84
|
+
protected abstract verifyTargetExtensions(
|
|
85
|
+
options: SchemaVerifyOptions<TContract, TSchema>,
|
|
86
|
+
): readonly SchemaIssue[];
|
|
87
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MigrationPlanOperation,
|
|
3
|
+
OperationPreview,
|
|
4
|
+
} from '@prisma-next/framework-components/control';
|
|
5
|
+
import type {
|
|
6
|
+
CollModCommand,
|
|
7
|
+
CreateCollectionCommand,
|
|
8
|
+
CreateIndexCommand,
|
|
9
|
+
DropCollectionCommand,
|
|
10
|
+
DropIndexCommand,
|
|
11
|
+
MongoDdlCommandVisitor,
|
|
12
|
+
MongoIndexKey,
|
|
13
|
+
} from '@prisma-next/mongo-query-ast/control';
|
|
14
|
+
|
|
15
|
+
function formatKeySpec(keys: ReadonlyArray<MongoIndexKey>): string {
|
|
16
|
+
const entries = keys.map((k) => `${JSON.stringify(k.field)}: ${JSON.stringify(k.direction)}`);
|
|
17
|
+
return `{ ${entries.join(', ')} }`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatOptions(cmd: CreateIndexCommand): string | undefined {
|
|
21
|
+
const parts: string[] = [];
|
|
22
|
+
if (cmd.unique) parts.push('unique: true');
|
|
23
|
+
if (cmd.sparse) parts.push('sparse: true');
|
|
24
|
+
if (cmd.expireAfterSeconds !== undefined)
|
|
25
|
+
parts.push(`expireAfterSeconds: ${cmd.expireAfterSeconds}`);
|
|
26
|
+
if (cmd.name) parts.push(`name: ${JSON.stringify(cmd.name)}`);
|
|
27
|
+
if (cmd.collation) parts.push(`collation: ${JSON.stringify(cmd.collation)}`);
|
|
28
|
+
if (cmd.weights) parts.push(`weights: ${JSON.stringify(cmd.weights)}`);
|
|
29
|
+
if (cmd.default_language) parts.push(`default_language: ${JSON.stringify(cmd.default_language)}`);
|
|
30
|
+
if (cmd.language_override)
|
|
31
|
+
parts.push(`language_override: ${JSON.stringify(cmd.language_override)}`);
|
|
32
|
+
if (cmd.wildcardProjection)
|
|
33
|
+
parts.push(`wildcardProjection: ${JSON.stringify(cmd.wildcardProjection)}`);
|
|
34
|
+
if (cmd.partialFilterExpression)
|
|
35
|
+
parts.push(`partialFilterExpression: ${JSON.stringify(cmd.partialFilterExpression)}`);
|
|
36
|
+
if (parts.length === 0) return undefined;
|
|
37
|
+
return `{ ${parts.join(', ')} }`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatCreateCollectionOptions(cmd: CreateCollectionCommand): string | undefined {
|
|
41
|
+
const parts: string[] = [];
|
|
42
|
+
if (cmd.capped) parts.push('capped: true');
|
|
43
|
+
if (cmd.size !== undefined) parts.push(`size: ${cmd.size}`);
|
|
44
|
+
if (cmd.max !== undefined) parts.push(`max: ${cmd.max}`);
|
|
45
|
+
if (cmd.timeseries) parts.push(`timeseries: ${JSON.stringify(cmd.timeseries)}`);
|
|
46
|
+
if (cmd.collation) parts.push(`collation: ${JSON.stringify(cmd.collation)}`);
|
|
47
|
+
if (cmd.clusteredIndex) parts.push(`clusteredIndex: ${JSON.stringify(cmd.clusteredIndex)}`);
|
|
48
|
+
if (cmd.validator) parts.push(`validator: ${JSON.stringify(cmd.validator)}`);
|
|
49
|
+
if (cmd.validationLevel) parts.push(`validationLevel: ${JSON.stringify(cmd.validationLevel)}`);
|
|
50
|
+
if (cmd.validationAction) parts.push(`validationAction: ${JSON.stringify(cmd.validationAction)}`);
|
|
51
|
+
if (cmd.changeStreamPreAndPostImages)
|
|
52
|
+
parts.push(`changeStreamPreAndPostImages: ${JSON.stringify(cmd.changeStreamPreAndPostImages)}`);
|
|
53
|
+
if (parts.length === 0) return undefined;
|
|
54
|
+
return `{ ${parts.join(', ')} }`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class MongoDdlCommandFormatter implements MongoDdlCommandVisitor<string> {
|
|
58
|
+
createIndex(cmd: CreateIndexCommand): string {
|
|
59
|
+
const keySpec = formatKeySpec(cmd.keys);
|
|
60
|
+
const opts = formatOptions(cmd);
|
|
61
|
+
return opts
|
|
62
|
+
? `db.${cmd.collection}.createIndex(${keySpec}, ${opts})`
|
|
63
|
+
: `db.${cmd.collection}.createIndex(${keySpec})`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
dropIndex(cmd: DropIndexCommand): string {
|
|
67
|
+
return `db.${cmd.collection}.dropIndex(${JSON.stringify(cmd.name)})`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
createCollection(cmd: CreateCollectionCommand): string {
|
|
71
|
+
const opts = formatCreateCollectionOptions(cmd);
|
|
72
|
+
return opts
|
|
73
|
+
? `db.createCollection(${JSON.stringify(cmd.collection)}, ${opts})`
|
|
74
|
+
: `db.createCollection(${JSON.stringify(cmd.collection)})`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
dropCollection(cmd: DropCollectionCommand): string {
|
|
78
|
+
return `db.${cmd.collection}.drop()`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
collMod(cmd: CollModCommand): string {
|
|
82
|
+
const parts: string[] = [`collMod: ${JSON.stringify(cmd.collection)}`];
|
|
83
|
+
if (cmd.validator) parts.push(`validator: ${JSON.stringify(cmd.validator)}`);
|
|
84
|
+
if (cmd.validationLevel) parts.push(`validationLevel: ${JSON.stringify(cmd.validationLevel)}`);
|
|
85
|
+
if (cmd.validationAction)
|
|
86
|
+
parts.push(`validationAction: ${JSON.stringify(cmd.validationAction)}`);
|
|
87
|
+
if (cmd.changeStreamPreAndPostImages)
|
|
88
|
+
parts.push(
|
|
89
|
+
`changeStreamPreAndPostImages: ${JSON.stringify(cmd.changeStreamPreAndPostImages)}`,
|
|
90
|
+
);
|
|
91
|
+
return `db.runCommand({ ${parts.join(', ')} })`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const formatter = new MongoDdlCommandFormatter();
|
|
96
|
+
|
|
97
|
+
interface MongoExecuteStep {
|
|
98
|
+
readonly command: { readonly accept: <R>(visitor: MongoDdlCommandVisitor<R>) => R };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function formatMongoOperations(operations: readonly MigrationPlanOperation[]): string[] {
|
|
102
|
+
const statements: string[] = [];
|
|
103
|
+
for (const operation of operations) {
|
|
104
|
+
const candidate = operation as unknown as Record<string, unknown>;
|
|
105
|
+
if (!('execute' in candidate) || !Array.isArray(candidate['execute'])) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
for (const step of candidate['execute'] as MongoExecuteStep[]) {
|
|
109
|
+
if (step.command && typeof step.command.accept === 'function') {
|
|
110
|
+
statements.push(step.command.accept(formatter));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return statements;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Wraps `formatMongoOperations` into the family-agnostic
|
|
119
|
+
* `OperationPreview` shape. Each statement carries
|
|
120
|
+
* `language: 'mongodb-shell'`. Mirrors `sqlOperationsToPreview`.
|
|
121
|
+
*/
|
|
122
|
+
export function mongoOperationsToPreview(
|
|
123
|
+
operations: readonly MigrationPlanOperation[],
|
|
124
|
+
): OperationPreview {
|
|
125
|
+
return {
|
|
126
|
+
statements: formatMongoOperations(operations).map((text) => ({
|
|
127
|
+
text,
|
|
128
|
+
language: 'mongodb-shell',
|
|
129
|
+
})),
|
|
130
|
+
};
|
|
131
|
+
}
|