@prisma-next/family-mongo 0.4.0-dev.9 → 0.4.2
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 +6 -3
- package/dist/control.d.mts +4 -10
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +22 -448
- package/dist/control.mjs.map +1 -1
- package/dist/migration.d.mts +9 -3
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -1
- package/dist/migration.mjs.map +1 -1
- package/dist/schema-verify.d.mts +2 -0
- package/dist/schema-verify.mjs +3 -0
- package/package.json +17 -16
- package/src/core/control-instance.ts +12 -30
- package/src/core/mongo-migration.ts +9 -3
- package/src/core/mongo-target-descriptor.ts +23 -16
- package/src/exports/schema-verify.ts +2 -0
- package/src/core/mongo-emit.ts +0 -135
- package/src/core/schema-diff.ts +0 -402
package/src/core/mongo-emit.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mongo's in-process implementation of the `emit` capability on
|
|
3
|
-
* `TargetMigrationsCapability`. Invoked by the framework's class-flow emit
|
|
4
|
-
* dispatcher in `@prisma-next/cli/lib/migration-emit` — see that module's
|
|
5
|
-
* preamble for the cross-cutting story (when the CLI dispatches here, who
|
|
6
|
-
* attests `migration.json`, why both flows produce byte-identical artifacts,
|
|
7
|
-
* and the relationship to the self-emitting `Migration.run` shebang path).
|
|
8
|
-
*
|
|
9
|
-
* Mongo-specific responsibilities of this helper:
|
|
10
|
-
*
|
|
11
|
-
* - Accept two authoring shapes for `migration.ts`'s default export, both
|
|
12
|
-
* adhering to the `MigrationPlan` interface:
|
|
13
|
-
*
|
|
14
|
-
* 1. Class subclass (canonical, scaffolded form):
|
|
15
|
-
* class M extends Migration {
|
|
16
|
-
* override get operations() { return [...]; }
|
|
17
|
-
* override describe() { return { from, to }; }
|
|
18
|
-
* }
|
|
19
|
-
* export default M;
|
|
20
|
-
* Migration.run(import.meta.url, M);
|
|
21
|
-
*
|
|
22
|
-
* 2. Factory function returning a MigrationPlan-shaped object:
|
|
23
|
-
* export default () => ({
|
|
24
|
-
* targetId: 'mongo',
|
|
25
|
-
* destination: { storageHash: '...' },
|
|
26
|
-
* operations: [createCollection("users")],
|
|
27
|
-
* });
|
|
28
|
-
*
|
|
29
|
-
* Only the class form is scaffolded; the factory form is supported for
|
|
30
|
-
* authors who prefer it.
|
|
31
|
-
* - Dynamic-import the file so structured errors thrown during evaluation
|
|
32
|
-
* (notably `placeholder(...)`) surface to the CLI as real exceptions.
|
|
33
|
-
* - Dispatch on the default export's shape and validate the factory return
|
|
34
|
-
* is `MigrationPlan`-shaped.
|
|
35
|
-
* - Persist `ops.json` via the framework I/O helper and return the
|
|
36
|
-
* operations to the caller (which performs attestation).
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import { stat } from 'node:fs/promises';
|
|
40
|
-
import { pathToFileURL } from 'node:url';
|
|
41
|
-
import {
|
|
42
|
-
errorMigrationFileMissing,
|
|
43
|
-
errorMigrationInvalidDefaultExport,
|
|
44
|
-
errorMigrationPlanNotArray,
|
|
45
|
-
} from '@prisma-next/errors/migration';
|
|
46
|
-
import type {
|
|
47
|
-
MigrationPlan,
|
|
48
|
-
MigrationPlanOperation,
|
|
49
|
-
TargetMigrationsCapability,
|
|
50
|
-
} from '@prisma-next/framework-components/control';
|
|
51
|
-
import { writeMigrationOps } from '@prisma-next/migration-tools/io';
|
|
52
|
-
import { Migration } from '@prisma-next/migration-tools/migration';
|
|
53
|
-
import { join } from 'pathe';
|
|
54
|
-
|
|
55
|
-
const MIGRATION_TS_FILE = 'migration.ts';
|
|
56
|
-
|
|
57
|
-
type EmitOptions = Parameters<NonNullable<TargetMigrationsCapability['emit']>>[0];
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Implementation of `TargetMigrationsCapability.emit` for Mongo.
|
|
61
|
-
*
|
|
62
|
-
* Loads `<dir>/migration.ts` and dispatches on the default export's shape:
|
|
63
|
-
* if it is a `Migration` subclass, instantiates it; otherwise invokes it as a
|
|
64
|
-
* factory function (sync or async) and validates the returned value is
|
|
65
|
-
* `MigrationPlan`-shaped. In both cases reads `.operations` to produce the
|
|
66
|
-
* operations list, writes `ops.json`, and returns the operations for the
|
|
67
|
-
* framework helper to render. Attestation of `migration.json` is the
|
|
68
|
-
* caller's responsibility: the framework's `emitMigration` helper calls
|
|
69
|
-
* `attestMigration` after this function returns. This capability MUST NOT
|
|
70
|
-
* call `attestMigration` itself, to avoid double-attestation when the helper
|
|
71
|
-
* drives emit.
|
|
72
|
-
*/
|
|
73
|
-
export async function mongoEmit(options: EmitOptions): Promise<readonly MigrationPlanOperation[]> {
|
|
74
|
-
const filePath = join(options.dir, MIGRATION_TS_FILE);
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
await stat(filePath);
|
|
78
|
-
} catch {
|
|
79
|
-
throw errorMigrationFileMissing(options.dir);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const fileUrl = pathToFileURL(filePath).href;
|
|
83
|
-
const mod = (await import(fileUrl)) as { default?: unknown };
|
|
84
|
-
|
|
85
|
-
const MigrationExport = mod.default;
|
|
86
|
-
if (typeof MigrationExport !== 'function') {
|
|
87
|
-
throw errorMigrationInvalidDefaultExport(
|
|
88
|
-
options.dir,
|
|
89
|
-
`default export of type ${typeof MigrationExport}`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
let plan: MigrationPlan;
|
|
94
|
-
if (MigrationExport.prototype instanceof Migration) {
|
|
95
|
-
plan = new (MigrationExport as new () => Migration)();
|
|
96
|
-
} else {
|
|
97
|
-
let factoryResult: unknown;
|
|
98
|
-
try {
|
|
99
|
-
factoryResult = await (MigrationExport as () => unknown | Promise<unknown>)();
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (error instanceof TypeError && /cannot be invoked without 'new'/i.test(error.message)) {
|
|
102
|
-
throw errorMigrationInvalidDefaultExport(
|
|
103
|
-
options.dir,
|
|
104
|
-
'a default export that does not extend Migration (from @prisma-next/migration-tools/migration)',
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
if (
|
|
110
|
-
typeof factoryResult !== 'object' ||
|
|
111
|
-
factoryResult === null ||
|
|
112
|
-
!('operations' in factoryResult)
|
|
113
|
-
) {
|
|
114
|
-
throw errorMigrationInvalidDefaultExport(
|
|
115
|
-
options.dir,
|
|
116
|
-
`factory must return a MigrationPlan-shaped object; got ${describeValue(factoryResult)}`,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
plan = factoryResult as MigrationPlan;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const operations: unknown = plan.operations;
|
|
123
|
-
if (!Array.isArray(operations)) {
|
|
124
|
-
throw errorMigrationPlanNotArray(options.dir, describeValue(operations));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
await writeMigrationOps(options.dir, operations);
|
|
128
|
-
|
|
129
|
-
return operations;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function describeValue(value: unknown): string {
|
|
133
|
-
if (value === null) return 'null';
|
|
134
|
-
return `a value of type ${typeof value}`;
|
|
135
|
-
}
|
package/src/core/schema-diff.ts
DELETED
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SchemaIssue,
|
|
3
|
-
SchemaVerificationNode,
|
|
4
|
-
} from '@prisma-next/framework-components/control';
|
|
5
|
-
import type {
|
|
6
|
-
MongoSchemaCollection,
|
|
7
|
-
MongoSchemaIndex,
|
|
8
|
-
MongoSchemaIR,
|
|
9
|
-
} from '@prisma-next/mongo-schema-ir';
|
|
10
|
-
import { canonicalize, deepEqual } from '@prisma-next/mongo-schema-ir';
|
|
11
|
-
|
|
12
|
-
export function diffMongoSchemas(
|
|
13
|
-
live: MongoSchemaIR,
|
|
14
|
-
expected: MongoSchemaIR,
|
|
15
|
-
strict: boolean,
|
|
16
|
-
): {
|
|
17
|
-
root: SchemaVerificationNode;
|
|
18
|
-
issues: SchemaIssue[];
|
|
19
|
-
counts: { pass: number; warn: number; fail: number; totalNodes: number };
|
|
20
|
-
} {
|
|
21
|
-
const issues: SchemaIssue[] = [];
|
|
22
|
-
const collectionChildren: SchemaVerificationNode[] = [];
|
|
23
|
-
let pass = 0;
|
|
24
|
-
let warn = 0;
|
|
25
|
-
let fail = 0;
|
|
26
|
-
|
|
27
|
-
const allNames = new Set([...live.collectionNames, ...expected.collectionNames]);
|
|
28
|
-
|
|
29
|
-
for (const name of [...allNames].sort()) {
|
|
30
|
-
const liveColl = live.collection(name);
|
|
31
|
-
const expectedColl = expected.collection(name);
|
|
32
|
-
|
|
33
|
-
if (!liveColl && expectedColl) {
|
|
34
|
-
issues.push({
|
|
35
|
-
kind: 'missing_table',
|
|
36
|
-
table: name,
|
|
37
|
-
message: `Collection "${name}" is missing from the database`,
|
|
38
|
-
});
|
|
39
|
-
collectionChildren.push({
|
|
40
|
-
status: 'fail',
|
|
41
|
-
kind: 'collection',
|
|
42
|
-
name,
|
|
43
|
-
contractPath: `storage.collections.${name}`,
|
|
44
|
-
code: 'MISSING_COLLECTION',
|
|
45
|
-
message: `Collection "${name}" is missing`,
|
|
46
|
-
expected: name,
|
|
47
|
-
actual: null,
|
|
48
|
-
children: [],
|
|
49
|
-
});
|
|
50
|
-
fail++;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (liveColl && !expectedColl) {
|
|
55
|
-
const status = strict ? 'fail' : 'warn';
|
|
56
|
-
issues.push({
|
|
57
|
-
kind: 'extra_table',
|
|
58
|
-
table: name,
|
|
59
|
-
message: `Extra collection "${name}" exists in the database but not in the contract`,
|
|
60
|
-
});
|
|
61
|
-
collectionChildren.push({
|
|
62
|
-
status,
|
|
63
|
-
kind: 'collection',
|
|
64
|
-
name,
|
|
65
|
-
contractPath: `storage.collections.${name}`,
|
|
66
|
-
code: 'EXTRA_COLLECTION',
|
|
67
|
-
message: `Extra collection "${name}" found`,
|
|
68
|
-
expected: null,
|
|
69
|
-
actual: name,
|
|
70
|
-
children: [],
|
|
71
|
-
});
|
|
72
|
-
if (status === 'fail') fail++;
|
|
73
|
-
else warn++;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const lc = liveColl as MongoSchemaCollection;
|
|
78
|
-
const ec = expectedColl as MongoSchemaCollection;
|
|
79
|
-
const indexChildren = diffIndexes(name, lc, ec, strict, issues);
|
|
80
|
-
const validatorChildren = diffValidator(name, lc, ec, strict, issues);
|
|
81
|
-
const optionsChildren = diffOptions(name, lc, ec, strict, issues);
|
|
82
|
-
const children = [...indexChildren, ...validatorChildren, ...optionsChildren];
|
|
83
|
-
|
|
84
|
-
const worstStatus = children.reduce<'pass' | 'warn' | 'fail'>(
|
|
85
|
-
(s, c) => (c.status === 'fail' ? 'fail' : c.status === 'warn' && s !== 'fail' ? 'warn' : s),
|
|
86
|
-
'pass',
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
for (const c of children) {
|
|
90
|
-
if (c.status === 'pass') pass++;
|
|
91
|
-
else if (c.status === 'warn') warn++;
|
|
92
|
-
else fail++;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (children.length === 0) {
|
|
96
|
-
pass++;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
collectionChildren.push({
|
|
100
|
-
status: worstStatus,
|
|
101
|
-
kind: 'collection',
|
|
102
|
-
name,
|
|
103
|
-
contractPath: `storage.collections.${name}`,
|
|
104
|
-
code: worstStatus === 'pass' ? 'MATCH' : 'DRIFT',
|
|
105
|
-
message:
|
|
106
|
-
worstStatus === 'pass' ? `Collection "${name}" matches` : `Collection "${name}" has drift`,
|
|
107
|
-
expected: name,
|
|
108
|
-
actual: name,
|
|
109
|
-
children,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const rootStatus = fail > 0 ? 'fail' : warn > 0 ? 'warn' : 'pass';
|
|
114
|
-
const totalNodes = pass + warn + fail + collectionChildren.length;
|
|
115
|
-
|
|
116
|
-
const root: SchemaVerificationNode = {
|
|
117
|
-
status: rootStatus,
|
|
118
|
-
kind: 'root',
|
|
119
|
-
name: 'mongo-schema',
|
|
120
|
-
contractPath: 'storage',
|
|
121
|
-
code: rootStatus === 'pass' ? 'MATCH' : 'DRIFT',
|
|
122
|
-
message: rootStatus === 'pass' ? 'Schema matches' : 'Schema has drift',
|
|
123
|
-
expected: null,
|
|
124
|
-
actual: null,
|
|
125
|
-
children: collectionChildren,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
return { root, issues, counts: { pass, warn, fail, totalNodes } };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function buildIndexLookupKey(index: MongoSchemaIndex): string {
|
|
132
|
-
const keys = index.keys.map((k) => `${k.field}:${k.direction}`).join(',');
|
|
133
|
-
const opts = [
|
|
134
|
-
index.unique ? 'unique' : '',
|
|
135
|
-
index.sparse ? 'sparse' : '',
|
|
136
|
-
index.expireAfterSeconds != null ? `ttl:${index.expireAfterSeconds}` : '',
|
|
137
|
-
index.partialFilterExpression ? `pfe:${canonicalize(index.partialFilterExpression)}` : '',
|
|
138
|
-
index.wildcardProjection ? `wp:${canonicalize(index.wildcardProjection)}` : '',
|
|
139
|
-
index.collation ? `col:${canonicalize(index.collation)}` : '',
|
|
140
|
-
index.weights ? `wt:${canonicalize(index.weights)}` : '',
|
|
141
|
-
index.default_language ? `dl:${index.default_language}` : '',
|
|
142
|
-
index.language_override ? `lo:${index.language_override}` : '',
|
|
143
|
-
]
|
|
144
|
-
.filter(Boolean)
|
|
145
|
-
.join(';');
|
|
146
|
-
return opts ? `${keys}|${opts}` : keys;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function formatIndexName(index: MongoSchemaIndex): string {
|
|
150
|
-
return index.keys.map((k) => `${k.field}:${k.direction}`).join(', ');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function diffIndexes(
|
|
154
|
-
collName: string,
|
|
155
|
-
live: MongoSchemaCollection,
|
|
156
|
-
expected: MongoSchemaCollection,
|
|
157
|
-
strict: boolean,
|
|
158
|
-
issues: SchemaIssue[],
|
|
159
|
-
): SchemaVerificationNode[] {
|
|
160
|
-
const nodes: SchemaVerificationNode[] = [];
|
|
161
|
-
const liveLookup = new Map<string, MongoSchemaIndex>();
|
|
162
|
-
for (const idx of live.indexes) liveLookup.set(buildIndexLookupKey(idx), idx);
|
|
163
|
-
|
|
164
|
-
const expectedLookup = new Map<string, MongoSchemaIndex>();
|
|
165
|
-
for (const idx of expected.indexes) expectedLookup.set(buildIndexLookupKey(idx), idx);
|
|
166
|
-
|
|
167
|
-
for (const [key, idx] of expectedLookup) {
|
|
168
|
-
if (liveLookup.has(key)) {
|
|
169
|
-
nodes.push({
|
|
170
|
-
status: 'pass',
|
|
171
|
-
kind: 'index',
|
|
172
|
-
name: formatIndexName(idx),
|
|
173
|
-
contractPath: `storage.collections.${collName}.indexes`,
|
|
174
|
-
code: 'MATCH',
|
|
175
|
-
message: `Index ${formatIndexName(idx)} matches`,
|
|
176
|
-
expected: key,
|
|
177
|
-
actual: key,
|
|
178
|
-
children: [],
|
|
179
|
-
});
|
|
180
|
-
} else {
|
|
181
|
-
issues.push({
|
|
182
|
-
kind: 'index_mismatch',
|
|
183
|
-
table: collName,
|
|
184
|
-
indexOrConstraint: formatIndexName(idx),
|
|
185
|
-
message: `Index ${formatIndexName(idx)} missing on collection "${collName}"`,
|
|
186
|
-
});
|
|
187
|
-
nodes.push({
|
|
188
|
-
status: 'fail',
|
|
189
|
-
kind: 'index',
|
|
190
|
-
name: formatIndexName(idx),
|
|
191
|
-
contractPath: `storage.collections.${collName}.indexes`,
|
|
192
|
-
code: 'MISSING_INDEX',
|
|
193
|
-
message: `Index ${formatIndexName(idx)} missing`,
|
|
194
|
-
expected: key,
|
|
195
|
-
actual: null,
|
|
196
|
-
children: [],
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
for (const [key, idx] of liveLookup) {
|
|
202
|
-
if (!expectedLookup.has(key)) {
|
|
203
|
-
const status = strict ? 'fail' : 'warn';
|
|
204
|
-
issues.push({
|
|
205
|
-
kind: 'extra_index',
|
|
206
|
-
table: collName,
|
|
207
|
-
indexOrConstraint: formatIndexName(idx),
|
|
208
|
-
message: `Extra index ${formatIndexName(idx)} on collection "${collName}"`,
|
|
209
|
-
});
|
|
210
|
-
nodes.push({
|
|
211
|
-
status,
|
|
212
|
-
kind: 'index',
|
|
213
|
-
name: formatIndexName(idx),
|
|
214
|
-
contractPath: `storage.collections.${collName}.indexes`,
|
|
215
|
-
code: 'EXTRA_INDEX',
|
|
216
|
-
message: `Extra index ${formatIndexName(idx)}`,
|
|
217
|
-
expected: null,
|
|
218
|
-
actual: key,
|
|
219
|
-
children: [],
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return nodes;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function diffValidator(
|
|
228
|
-
collName: string,
|
|
229
|
-
live: MongoSchemaCollection,
|
|
230
|
-
expected: MongoSchemaCollection,
|
|
231
|
-
strict: boolean,
|
|
232
|
-
issues: SchemaIssue[],
|
|
233
|
-
): SchemaVerificationNode[] {
|
|
234
|
-
if (!live.validator && !expected.validator) return [];
|
|
235
|
-
|
|
236
|
-
if (expected.validator && !live.validator) {
|
|
237
|
-
issues.push({
|
|
238
|
-
kind: 'type_missing',
|
|
239
|
-
table: collName,
|
|
240
|
-
message: `Validator missing on collection "${collName}"`,
|
|
241
|
-
});
|
|
242
|
-
return [
|
|
243
|
-
{
|
|
244
|
-
status: 'fail',
|
|
245
|
-
kind: 'validator',
|
|
246
|
-
name: 'validator',
|
|
247
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
248
|
-
code: 'MISSING_VALIDATOR',
|
|
249
|
-
message: 'Validator missing',
|
|
250
|
-
expected: canonicalize(expected.validator.jsonSchema),
|
|
251
|
-
actual: null,
|
|
252
|
-
children: [],
|
|
253
|
-
},
|
|
254
|
-
];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (!expected.validator && live.validator) {
|
|
258
|
-
const status = strict ? 'fail' : 'warn';
|
|
259
|
-
issues.push({
|
|
260
|
-
kind: 'extra_validator',
|
|
261
|
-
table: collName,
|
|
262
|
-
message: `Extra validator on collection "${collName}"`,
|
|
263
|
-
});
|
|
264
|
-
return [
|
|
265
|
-
{
|
|
266
|
-
status,
|
|
267
|
-
kind: 'validator',
|
|
268
|
-
name: 'validator',
|
|
269
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
270
|
-
code: 'EXTRA_VALIDATOR',
|
|
271
|
-
message: 'Extra validator found',
|
|
272
|
-
expected: null,
|
|
273
|
-
actual: canonicalize(live.validator.jsonSchema),
|
|
274
|
-
children: [],
|
|
275
|
-
},
|
|
276
|
-
];
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const liveVal = live.validator as NonNullable<typeof live.validator>;
|
|
280
|
-
const expectedVal = expected.validator as NonNullable<typeof expected.validator>;
|
|
281
|
-
const liveSchema = canonicalize(liveVal.jsonSchema);
|
|
282
|
-
const expectedSchema = canonicalize(expectedVal.jsonSchema);
|
|
283
|
-
|
|
284
|
-
if (
|
|
285
|
-
liveSchema !== expectedSchema ||
|
|
286
|
-
liveVal.validationLevel !== expectedVal.validationLevel ||
|
|
287
|
-
liveVal.validationAction !== expectedVal.validationAction
|
|
288
|
-
) {
|
|
289
|
-
issues.push({
|
|
290
|
-
kind: 'type_mismatch',
|
|
291
|
-
table: collName,
|
|
292
|
-
expected: expectedSchema,
|
|
293
|
-
actual: liveSchema,
|
|
294
|
-
message: `Validator mismatch on collection "${collName}"`,
|
|
295
|
-
});
|
|
296
|
-
return [
|
|
297
|
-
{
|
|
298
|
-
status: 'fail',
|
|
299
|
-
kind: 'validator',
|
|
300
|
-
name: 'validator',
|
|
301
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
302
|
-
code: 'VALIDATOR_MISMATCH',
|
|
303
|
-
message: 'Validator mismatch',
|
|
304
|
-
expected: {
|
|
305
|
-
jsonSchema: expectedVal.jsonSchema,
|
|
306
|
-
validationLevel: expectedVal.validationLevel,
|
|
307
|
-
validationAction: expectedVal.validationAction,
|
|
308
|
-
},
|
|
309
|
-
actual: {
|
|
310
|
-
jsonSchema: liveVal.jsonSchema,
|
|
311
|
-
validationLevel: liveVal.validationLevel,
|
|
312
|
-
validationAction: liveVal.validationAction,
|
|
313
|
-
},
|
|
314
|
-
children: [],
|
|
315
|
-
},
|
|
316
|
-
];
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return [
|
|
320
|
-
{
|
|
321
|
-
status: 'pass',
|
|
322
|
-
kind: 'validator',
|
|
323
|
-
name: 'validator',
|
|
324
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
325
|
-
code: 'MATCH',
|
|
326
|
-
message: 'Validator matches',
|
|
327
|
-
expected: expectedSchema,
|
|
328
|
-
actual: liveSchema,
|
|
329
|
-
children: [],
|
|
330
|
-
},
|
|
331
|
-
];
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function diffOptions(
|
|
335
|
-
collName: string,
|
|
336
|
-
live: MongoSchemaCollection,
|
|
337
|
-
expected: MongoSchemaCollection,
|
|
338
|
-
strict: boolean,
|
|
339
|
-
issues: SchemaIssue[],
|
|
340
|
-
): SchemaVerificationNode[] {
|
|
341
|
-
if (!live.options && !expected.options) return [];
|
|
342
|
-
|
|
343
|
-
if (!expected.options && live.options) {
|
|
344
|
-
const status = strict ? 'fail' : 'warn';
|
|
345
|
-
issues.push({
|
|
346
|
-
kind: 'type_mismatch',
|
|
347
|
-
table: collName,
|
|
348
|
-
actual: canonicalize(live.options),
|
|
349
|
-
message: `Extra collection options on "${collName}"`,
|
|
350
|
-
});
|
|
351
|
-
return [
|
|
352
|
-
{
|
|
353
|
-
status,
|
|
354
|
-
kind: 'options',
|
|
355
|
-
name: 'options',
|
|
356
|
-
contractPath: `storage.collections.${collName}.options`,
|
|
357
|
-
code: 'EXTRA_OPTIONS',
|
|
358
|
-
message: 'Extra collection options found',
|
|
359
|
-
expected: null,
|
|
360
|
-
actual: live.options,
|
|
361
|
-
children: [],
|
|
362
|
-
},
|
|
363
|
-
];
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (deepEqual(live.options, expected.options)) {
|
|
367
|
-
return [
|
|
368
|
-
{
|
|
369
|
-
status: 'pass',
|
|
370
|
-
kind: 'options',
|
|
371
|
-
name: 'options',
|
|
372
|
-
contractPath: `storage.collections.${collName}.options`,
|
|
373
|
-
code: 'MATCH',
|
|
374
|
-
message: 'Collection options match',
|
|
375
|
-
expected: canonicalize(expected.options),
|
|
376
|
-
actual: canonicalize(live.options),
|
|
377
|
-
children: [],
|
|
378
|
-
},
|
|
379
|
-
];
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
issues.push({
|
|
383
|
-
kind: 'type_mismatch',
|
|
384
|
-
table: collName,
|
|
385
|
-
expected: canonicalize(expected.options),
|
|
386
|
-
actual: canonicalize(live.options),
|
|
387
|
-
message: `Collection options mismatch on "${collName}"`,
|
|
388
|
-
});
|
|
389
|
-
return [
|
|
390
|
-
{
|
|
391
|
-
status: 'fail',
|
|
392
|
-
kind: 'options',
|
|
393
|
-
name: 'options',
|
|
394
|
-
contractPath: `storage.collections.${collName}.options`,
|
|
395
|
-
code: 'OPTIONS_MISMATCH',
|
|
396
|
-
message: 'Collection options mismatch',
|
|
397
|
-
expected: expected.options,
|
|
398
|
-
actual: live.options,
|
|
399
|
-
children: [],
|
|
400
|
-
},
|
|
401
|
-
];
|
|
402
|
-
}
|