@ignex/ninox 0.1.5 → 0.1.6
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 +15 -8
- package/dist/index.d.ts +21 -4
- package/dist/index.js +114 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -787,10 +787,15 @@ you need the code → status mapping yourself.
|
|
|
787
787
|
> transient errors.
|
|
788
788
|
|
|
789
789
|
Schema validation is enforced **by MongoDB** — the DB `$jsonSchema` validator
|
|
790
|
-
derived from your schema is installed with `createSchema
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
790
|
+
derived from your schema is installed with `createSchema`, and the DB rejects
|
|
791
|
+
violating writes with `VALIDATION_FAILED`. `createSchema` is **idempotent and
|
|
792
|
+
self-reconciling**: it provisions a NEW collection, and when the collection
|
|
793
|
+
already exists it upgrades the DB schema to match the current ORM schema
|
|
794
|
+
(hot-swapping the validator via `collMod` only when it has drifted, and
|
|
795
|
+
creating any newly-declared indexes) — so calling it on every boot keeps
|
|
796
|
+
Mongo's schema in lockstep with your models as they change. `updateSchema`
|
|
797
|
+
hot-swaps a validator explicitly (via `collMod`). ninox deliberately does
|
|
798
|
+
**not** validate *incoming user input* at runtime (no client-side/zod layer).
|
|
794
799
|
|
|
795
800
|
- **Strict by default** — `additionalProperties: false` is emitted unless a
|
|
796
801
|
field opts out (`s.object({ ... }, { additionalProperties: true })`), so
|
|
@@ -814,7 +819,8 @@ client-side/zod layer).
|
|
|
814
819
|
`DomainError` carries the offending field paths (`extra.fields`), the failing
|
|
815
820
|
document id (`extra.documentId`), and the raw `extra.details`, so a rejected
|
|
816
821
|
write names exactly what (and which document) violated the schema.
|
|
817
|
-
- **Index lifecycle** — `createSchema` installs the declared indexes
|
|
822
|
+
- **Index lifecycle** — `createSchema` installs the declared indexes (and, on
|
|
823
|
+
an existing collection, adds any newly-declared ones without dropping);
|
|
818
824
|
`syncIndexes(collection)` reconciles drift (`await db.syncIndexes('products')`
|
|
819
825
|
creates missing declared keys and drops undeclared ones, `_id_` kept).
|
|
820
826
|
|
|
@@ -1015,9 +1021,10 @@ unknown keys in object literals AND non-literal patches. The only way an unknown
|
|
|
1015
1021
|
key passes is a variable explicitly typed with an index signature
|
|
1016
1022
|
(`Record<string, any>`) — prefer inline literals for full checking.
|
|
1017
1023
|
|
|
1018
|
-
**Index drift.** `createSchema` installs declared indexes
|
|
1019
|
-
|
|
1020
|
-
|
|
1024
|
+
**Index drift.** `createSchema` installs declared indexes on creation and adds
|
|
1025
|
+
newly-declared ones on re-provision; if indexes drift out of band (manual
|
|
1026
|
+
drops, other tooling), `syncIndexes('collection')` reconciles — creates
|
|
1027
|
+
missing declared indexes and drops undeclared ones (`_id_` kept).
|
|
1021
1028
|
|
|
1022
1029
|
**Observability.** `QueryCache.stats()` (hits/misses/evictions/…) and
|
|
1023
1030
|
`hot.stats()` (per-query hits/misses/refreshes/loadErrors/evictions/…) give you
|
package/dist/index.d.ts
CHANGED
|
@@ -1467,6 +1467,13 @@ interface DbOpMeta {
|
|
|
1467
1467
|
physicalCollection?: string;
|
|
1468
1468
|
db: string;
|
|
1469
1469
|
op: string;
|
|
1470
|
+
/**
|
|
1471
|
+
* What was sent to the database (filter / document / update / pipeline /
|
|
1472
|
+
* options). Surfaced in the ignex debugbar as the span's `params`, so the
|
|
1473
|
+
* dashboard shows exactly what the query carried — not just its name.
|
|
1474
|
+
* Optional: ops without a payload worth showing omit it.
|
|
1475
|
+
*/
|
|
1476
|
+
params?: unknown;
|
|
1470
1477
|
}
|
|
1471
1478
|
|
|
1472
1479
|
/**
|
|
@@ -1549,7 +1556,7 @@ interface TextSearchPagination {
|
|
|
1549
1556
|
|
|
1550
1557
|
interface CrudOpDeps<TCollection> {
|
|
1551
1558
|
trace: <T>(meta: DbOpMeta, fn: () => T | Promise<T>) => Promise<T>;
|
|
1552
|
-
meta: (collection: TCollection, op: string) => DbOpMeta;
|
|
1559
|
+
meta: (collection: TCollection, op: string, params?: unknown) => DbOpMeta;
|
|
1553
1560
|
}
|
|
1554
1561
|
|
|
1555
1562
|
/**
|
|
@@ -2420,9 +2427,19 @@ interface SchemaOpsOptions {
|
|
|
2420
2427
|
getDefinition: (logical: string) => CollectionRegistryEntry | undefined;
|
|
2421
2428
|
}
|
|
2422
2429
|
/**
|
|
2423
|
-
* Schema-first collection management.
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2430
|
+
* Schema-first collection management.
|
|
2431
|
+
*
|
|
2432
|
+
* `createSchema` is IDEMPOTENT and SELF-RECONCILING: on a NEW collection it
|
|
2433
|
+
* provisions the `$jsonSchema` validator derived from the ORM schema plus the
|
|
2434
|
+
* declared indexes; when the collection ALREADY exists it upgrades the DB
|
|
2435
|
+
* schema to match the current ORM schema — hot-swapping the validator via
|
|
2436
|
+
* `collMod` only when it has actually drifted, and creating any newly-declared
|
|
2437
|
+
* indexes. It never drops indexes or data (destructive reconciliation stays
|
|
2438
|
+
* explicit via `syncIndexes`), so it is safe to call on every boot: the Mongo
|
|
2439
|
+
* schema follows the app schema automatically as models change.
|
|
2440
|
+
*
|
|
2441
|
+
* `updateSchema` hot-swaps a validator explicitly (via `collMod`);
|
|
2442
|
+
* `syncIndexes` reconciles index drift (create missing, drop undeclared).
|
|
2426
2443
|
*/
|
|
2427
2444
|
declare const makeSchemaOps: (client: Db, _logger: LoggerLike, opts: SchemaOpsOptions) => {
|
|
2428
2445
|
createSchema: (collection: string, createCollectionOptions?: CreateCollectionOptions) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -3142,13 +3142,28 @@ var probeDebugQuery = async () => {
|
|
|
3142
3142
|
debugQueryCache = null;
|
|
3143
3143
|
}
|
|
3144
3144
|
};
|
|
3145
|
+
var sanitizeDebugParams = (value) => {
|
|
3146
|
+
try {
|
|
3147
|
+
return JSON.parse(JSON.stringify(value));
|
|
3148
|
+
} catch {
|
|
3149
|
+
try {
|
|
3150
|
+
return String(value);
|
|
3151
|
+
} catch {
|
|
3152
|
+
return "[unserializable]";
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3145
3156
|
var traceDbOp = async (logger, meta, fn, options = {}) => {
|
|
3146
3157
|
const start = performance.now();
|
|
3147
3158
|
logger.debug({ ...meta, operation: "start" }, "start");
|
|
3148
3159
|
try {
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3160
|
+
let debugQuery = options.debugQuery;
|
|
3161
|
+
if (debugQuery === void 0) {
|
|
3162
|
+
if (debugQueryCache === void 0) await probeDebugQuery();
|
|
3163
|
+
debugQuery = debugQueryCache;
|
|
3164
|
+
}
|
|
3165
|
+
const params = meta.params === void 0 ? void 0 : sanitizeDebugParams(meta.params);
|
|
3166
|
+
const result = debugQuery ? await debugQuery(`${meta.collection}.${meta.op}`, params, () => fn()) : await fn();
|
|
3152
3167
|
const durationMs = performance.now() - start;
|
|
3153
3168
|
logger.info({ ...meta, durationMs }, "ok");
|
|
3154
3169
|
return result;
|
|
@@ -3164,11 +3179,12 @@ var traceDbOp = async (logger, meta, fn, options = {}) => {
|
|
|
3164
3179
|
// src/service/op-deps.ts
|
|
3165
3180
|
var makeOpDeps = (logger, dbLabel, resolve, wrapMongoErrors) => {
|
|
3166
3181
|
const trace = (meta2, fn) => traceDbOp(logger, meta2, fn, { wrapMongoErrors });
|
|
3167
|
-
const meta = (collection, op) => ({
|
|
3182
|
+
const meta = (collection, op, params) => ({
|
|
3168
3183
|
collection: String(collection),
|
|
3169
3184
|
physicalCollection: resolve(String(collection)),
|
|
3170
3185
|
db: dbLabel,
|
|
3171
|
-
op
|
|
3186
|
+
op,
|
|
3187
|
+
...params !== void 0 ? { params } : {}
|
|
3172
3188
|
});
|
|
3173
3189
|
return { trace, meta };
|
|
3174
3190
|
};
|
|
@@ -3214,11 +3230,11 @@ var resolveDriftMode = (perOp, serviceDefault) => {
|
|
|
3214
3230
|
};
|
|
3215
3231
|
|
|
3216
3232
|
// src/service/crud-op.ts
|
|
3217
|
-
var defineCrudOp = async (deps2, collection, opName, execute, options, write = false) => {
|
|
3233
|
+
var defineCrudOp = async (deps2, collection, opName, execute, options, write = false, params) => {
|
|
3218
3234
|
const resolved = resolveQueryOptions(options);
|
|
3219
3235
|
const maxAttempts = write && resolved.retryWrites !== true ? 1 : resolved.maxAttempts;
|
|
3220
3236
|
return deps2.trace(
|
|
3221
|
-
deps2.meta(collection, opName),
|
|
3237
|
+
deps2.meta(collection, opName, params),
|
|
3222
3238
|
async () => withRetry(async () => execute(resolved), {
|
|
3223
3239
|
maxAttempts,
|
|
3224
3240
|
delayMs: resolved.retryDelayMs
|
|
@@ -3323,7 +3339,9 @@ var PipelineBuilder = class _PipelineBuilder {
|
|
|
3323
3339
|
this.deps.logical,
|
|
3324
3340
|
"mongo.pipeline",
|
|
3325
3341
|
run,
|
|
3326
|
-
this.deps.options
|
|
3342
|
+
this.deps.options,
|
|
3343
|
+
false,
|
|
3344
|
+
{ pipeline: this.stages }
|
|
3327
3345
|
);
|
|
3328
3346
|
}
|
|
3329
3347
|
/** Run the pipeline and return the first output document (or null). */
|
|
@@ -3350,7 +3368,9 @@ var PipelineBuilder = class _PipelineBuilder {
|
|
|
3350
3368
|
this.deps.logical,
|
|
3351
3369
|
"mongo.pipeline.first",
|
|
3352
3370
|
run,
|
|
3353
|
-
this.deps.options
|
|
3371
|
+
this.deps.options,
|
|
3372
|
+
false,
|
|
3373
|
+
{ pipeline: this.stages }
|
|
3354
3374
|
);
|
|
3355
3375
|
}
|
|
3356
3376
|
/** Streaming cursor (raw driver cursor; no retry / trace). */
|
|
@@ -3470,7 +3490,9 @@ var makeAggregateOps = (ctx) => {
|
|
|
3470
3490
|
collection,
|
|
3471
3491
|
"mongo.aggregate",
|
|
3472
3492
|
(r) => coll(collection).aggregate(pipeline2, mergeAggOptions2(r.driverOpts, r.sdk)),
|
|
3473
|
-
options
|
|
3493
|
+
options,
|
|
3494
|
+
false,
|
|
3495
|
+
{ pipeline: pipeline2 }
|
|
3474
3496
|
);
|
|
3475
3497
|
};
|
|
3476
3498
|
const pipeline = (collection, options) => new PipelineBuilder({
|
|
@@ -3556,7 +3578,7 @@ var createCachedAggregate = (ctx) => {
|
|
|
3556
3578
|
const noSession = !resolved.sdk.session;
|
|
3557
3579
|
const useCache = cache !== void 0 && noSession && resolved.cache !== false && isCacheablePipeline(pipeline);
|
|
3558
3580
|
const shouldDedupe = resolved.dedupe !== false && (resolved.dedupe === true || opts.dedupeReads === true && noSession) && opts.inFlight !== void 0;
|
|
3559
|
-
const runDriver = () => defineCrudOp(deps2, collection, opName, execute, options);
|
|
3581
|
+
const runDriver = () => defineCrudOp(deps2, collection, opName, execute, options, false, { pipeline });
|
|
3560
3582
|
const key = useCache ? cache.key(
|
|
3561
3583
|
cacheCollectionKey(client.databaseName, physical),
|
|
3562
3584
|
stableHash([opName, pipeline, options])
|
|
@@ -3905,8 +3927,8 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
3905
3927
|
);
|
|
3906
3928
|
const coll = (name) => client.collection(resolve(String(name)));
|
|
3907
3929
|
const toDriverFilter = (filter) => filter;
|
|
3908
|
-
const run = (collection, opName, execute, options) => defineCrudOp(deps2, collection, opName, execute, options);
|
|
3909
|
-
const writeRun = (collection, opName, execute, options) => defineCrudOp(deps2, collection, opName, execute, options, true);
|
|
3930
|
+
const run = (collection, opName, execute, options, params) => defineCrudOp(deps2, collection, opName, execute, options, false, params);
|
|
3931
|
+
const writeRun = (collection, opName, execute, options, params) => defineCrudOp(deps2, collection, opName, execute, options, true, params);
|
|
3910
3932
|
const invalidate = (collection) => {
|
|
3911
3933
|
opts.cache?.invalidateByCollection(
|
|
3912
3934
|
cacheCollectionKey(client.databaseName, resolve(String(collection)))
|
|
@@ -3942,6 +3964,9 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
3942
3964
|
const read = async (collection, opName, filter, options, execute, shape = "none", keyExtra) => {
|
|
3943
3965
|
const physical = resolve(String(collection));
|
|
3944
3966
|
const resolved = resolveQueryOptions(options);
|
|
3967
|
+
const sentParams = { filter };
|
|
3968
|
+
if (Object.keys(resolved.driverOpts).length > 0) sentParams.options = resolved.driverOpts;
|
|
3969
|
+
if (keyExtra !== void 0) sentParams.field = keyExtra;
|
|
3945
3970
|
const noSession = !resolved.sdk.session;
|
|
3946
3971
|
const useCache = opts.cache !== void 0 && noSession && resolved.cache !== false;
|
|
3947
3972
|
const shouldDedupe = resolved.dedupe !== false && (resolved.dedupe === true || opts.dedupeReads === true && noSession) && opts.inFlight !== void 0;
|
|
@@ -3963,7 +3988,7 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
3963
3988
|
);
|
|
3964
3989
|
};
|
|
3965
3990
|
const executeWrapped = () => deps2.trace(
|
|
3966
|
-
deps2.meta(collection, opName),
|
|
3991
|
+
deps2.meta(collection, opName, sentParams),
|
|
3967
3992
|
() => withRetry(() => execute(resolved), {
|
|
3968
3993
|
maxAttempts: resolved.maxAttempts,
|
|
3969
3994
|
delayMs: resolved.retryDelayMs
|
|
@@ -4292,7 +4317,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4292
4317
|
insertOpts
|
|
4293
4318
|
);
|
|
4294
4319
|
},
|
|
4295
|
-
options
|
|
4320
|
+
options,
|
|
4321
|
+
{ doc }
|
|
4296
4322
|
);
|
|
4297
4323
|
invalidate(collection);
|
|
4298
4324
|
await safeAfterHooks(String(collection), "afterCreate", {
|
|
@@ -4325,7 +4351,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4325
4351
|
bulkOpts
|
|
4326
4352
|
);
|
|
4327
4353
|
},
|
|
4328
|
-
options
|
|
4354
|
+
options,
|
|
4355
|
+
{ docs }
|
|
4329
4356
|
);
|
|
4330
4357
|
invalidate(collection);
|
|
4331
4358
|
await safeAfterHooks(String(collection), "afterCreate", {
|
|
@@ -4354,7 +4381,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4354
4381
|
updateOpts
|
|
4355
4382
|
);
|
|
4356
4383
|
},
|
|
4357
|
-
options
|
|
4384
|
+
options,
|
|
4385
|
+
{ filter, update: effective }
|
|
4358
4386
|
);
|
|
4359
4387
|
invalidate(collection);
|
|
4360
4388
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4383,7 +4411,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4383
4411
|
updateOpts
|
|
4384
4412
|
);
|
|
4385
4413
|
},
|
|
4386
|
-
options
|
|
4414
|
+
options,
|
|
4415
|
+
{ filter, update: effective }
|
|
4387
4416
|
);
|
|
4388
4417
|
invalidate(collection);
|
|
4389
4418
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4416,7 +4445,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4416
4445
|
fOpts
|
|
4417
4446
|
);
|
|
4418
4447
|
},
|
|
4419
|
-
options
|
|
4448
|
+
options,
|
|
4449
|
+
{ filter, update: effective }
|
|
4420
4450
|
);
|
|
4421
4451
|
invalidate(collection);
|
|
4422
4452
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4449,7 +4479,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4449
4479
|
fOpts
|
|
4450
4480
|
);
|
|
4451
4481
|
},
|
|
4452
|
-
options
|
|
4482
|
+
options,
|
|
4483
|
+
{ filter, replacement: effective }
|
|
4453
4484
|
);
|
|
4454
4485
|
invalidate(collection);
|
|
4455
4486
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4477,7 +4508,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4477
4508
|
repOpts
|
|
4478
4509
|
);
|
|
4479
4510
|
},
|
|
4480
|
-
options
|
|
4511
|
+
options,
|
|
4512
|
+
{ filter, replacement }
|
|
4481
4513
|
);
|
|
4482
4514
|
invalidate(collection);
|
|
4483
4515
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4501,7 +4533,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4501
4533
|
};
|
|
4502
4534
|
return await coll(collection).deleteOne(toDriverFilter(filter), delOpts);
|
|
4503
4535
|
},
|
|
4504
|
-
options
|
|
4536
|
+
options,
|
|
4537
|
+
{ filter }
|
|
4505
4538
|
);
|
|
4506
4539
|
invalidate(collection);
|
|
4507
4540
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4525,7 +4558,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4525
4558
|
};
|
|
4526
4559
|
return await coll(collection).deleteMany(toDriverFilter(filter), delOpts);
|
|
4527
4560
|
},
|
|
4528
|
-
options
|
|
4561
|
+
options,
|
|
4562
|
+
{ filter }
|
|
4529
4563
|
);
|
|
4530
4564
|
invalidate(collection);
|
|
4531
4565
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4554,7 +4588,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4554
4588
|
fOpts
|
|
4555
4589
|
);
|
|
4556
4590
|
},
|
|
4557
|
-
options
|
|
4591
|
+
options,
|
|
4592
|
+
{ filter }
|
|
4558
4593
|
);
|
|
4559
4594
|
invalidate(collection);
|
|
4560
4595
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4586,7 +4621,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4586
4621
|
updateOpts
|
|
4587
4622
|
);
|
|
4588
4623
|
},
|
|
4589
|
-
options
|
|
4624
|
+
options,
|
|
4625
|
+
{ filter, update: effective }
|
|
4590
4626
|
);
|
|
4591
4627
|
invalidate(collection);
|
|
4592
4628
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4612,7 +4648,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4612
4648
|
};
|
|
4613
4649
|
return await coll(collection).updateOne(toDriverFilter(filter), formatted, updateOpts);
|
|
4614
4650
|
},
|
|
4615
|
-
options
|
|
4651
|
+
options,
|
|
4652
|
+
{ filter, update: formatted }
|
|
4616
4653
|
);
|
|
4617
4654
|
invalidate(collection);
|
|
4618
4655
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4644,7 +4681,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4644
4681
|
};
|
|
4645
4682
|
return await coll(collection).bulkWrite(writes, bulkOpts);
|
|
4646
4683
|
},
|
|
4647
|
-
options
|
|
4684
|
+
options,
|
|
4685
|
+
{ operations }
|
|
4648
4686
|
);
|
|
4649
4687
|
invalidate(collection);
|
|
4650
4688
|
return result;
|
|
@@ -4665,7 +4703,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4665
4703
|
};
|
|
4666
4704
|
return await coll(collection).bulkWrite(operations, bulkOpts);
|
|
4667
4705
|
},
|
|
4668
|
-
options
|
|
4706
|
+
options,
|
|
4707
|
+
{ operations }
|
|
4669
4708
|
);
|
|
4670
4709
|
invalidate(collection);
|
|
4671
4710
|
return result;
|
|
@@ -4848,7 +4887,9 @@ var makePaginateCursor = (ctx) => {
|
|
|
4848
4887
|
};
|
|
4849
4888
|
return client.collection(resolve(String(collection))).find(filter, findOpts).sort(effectiveSort).limit(limit + 1).toArray();
|
|
4850
4889
|
},
|
|
4851
|
-
config.queryOptions
|
|
4890
|
+
config.queryOptions,
|
|
4891
|
+
false,
|
|
4892
|
+
{ filter, sort: effectiveSort, limit }
|
|
4852
4893
|
);
|
|
4853
4894
|
const hasMore = rows.length > limit;
|
|
4854
4895
|
const page = backwards ? rows.slice(0, limit).reverse() : rows.slice(0, limit);
|
|
@@ -4974,40 +5015,63 @@ var optionSignature = (o) => JSON.stringify([
|
|
|
4974
5015
|
]);
|
|
4975
5016
|
var indexKeyOf = (key, options) => `${JSON.stringify(Object.keys(key).map((k) => [k, key[k]]))}|${optionSignature(options)}`;
|
|
4976
5017
|
var makeSchemaOps = (client, _logger, opts) => {
|
|
5018
|
+
const readCollectionValidator = async (physical) => {
|
|
5019
|
+
const info = await client.listCollections({ name: physical }).next();
|
|
5020
|
+
return info?.options?.validator;
|
|
5021
|
+
};
|
|
5022
|
+
const ensureDeclaredIndexes = async (collection, physical, def) => {
|
|
5023
|
+
const declared = def?.indexes ?? [];
|
|
5024
|
+
if (declared.length === 0) return;
|
|
5025
|
+
const handle = client.collection(physical);
|
|
5026
|
+
const existing = new Set(
|
|
5027
|
+
(await handle.listIndexes().toArray()).filter((i) => i.name !== "_id_").map((i) => indexKeyOf(i.key, i))
|
|
5028
|
+
);
|
|
5029
|
+
for (const index of declared) {
|
|
5030
|
+
if (existing.has(indexKeyOf(index.key, index.options))) continue;
|
|
5031
|
+
try {
|
|
5032
|
+
await handle.createIndex(index.key, index.options);
|
|
5033
|
+
} catch (err) {
|
|
5034
|
+
if (err?.code === 85) {
|
|
5035
|
+
throw new Error(
|
|
5036
|
+
`Cannot apply declared index on "${collection}": an index with the same name/key already exists with different options. Run db.syncIndexes("${collection}") to reconcile index drift.`,
|
|
5037
|
+
{ cause: err }
|
|
5038
|
+
);
|
|
5039
|
+
}
|
|
5040
|
+
throw err;
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
};
|
|
4977
5044
|
const createSchema = async (collection, createCollectionOptions) => {
|
|
4978
5045
|
const physical = opts.resolveCollectionName(collection);
|
|
4979
5046
|
const def = opts.getDefinition(collection);
|
|
4980
|
-
const
|
|
5047
|
+
const createOptions = {
|
|
5048
|
+
...createCollectionOptions ?? {},
|
|
5049
|
+
...def?.schema ? { validator: toMongoValidator(def.schema) } : {}
|
|
5050
|
+
};
|
|
5051
|
+
let created = true;
|
|
4981
5052
|
try {
|
|
4982
|
-
await client.createCollection(physical,
|
|
4983
|
-
// Caller options FIRST, derived validator LAST — a stale `validator`
|
|
4984
|
-
// key in the options must not clobber the schema-derived one.
|
|
4985
|
-
...createCollectionOptions ?? {},
|
|
4986
|
-
...validator ? { validator } : {}
|
|
4987
|
-
});
|
|
5053
|
+
await client.createCollection(physical, createOptions);
|
|
4988
5054
|
} catch (err) {
|
|
4989
|
-
if (err?.code
|
|
4990
|
-
|
|
4991
|
-
physicalCollection: physical
|
|
4992
|
-
});
|
|
4993
|
-
}
|
|
4994
|
-
throw err;
|
|
5055
|
+
if (err?.code !== 48) throw err;
|
|
5056
|
+
created = false;
|
|
4995
5057
|
}
|
|
4996
|
-
|
|
4997
|
-
if (indexes.length > 0) {
|
|
5058
|
+
if (created) {
|
|
4998
5059
|
try {
|
|
4999
|
-
await
|
|
5000
|
-
indexes.map((index) => ({
|
|
5001
|
-
key: index.key,
|
|
5002
|
-
...index.options ?? {}
|
|
5003
|
-
}))
|
|
5004
|
-
);
|
|
5060
|
+
await ensureDeclaredIndexes(collection, physical, def);
|
|
5005
5061
|
} catch (err) {
|
|
5006
5062
|
await client.dropCollection(physical).catch(() => {
|
|
5007
5063
|
});
|
|
5008
5064
|
throw err;
|
|
5009
5065
|
}
|
|
5066
|
+
return;
|
|
5067
|
+
}
|
|
5068
|
+
if (createOptions.validator) {
|
|
5069
|
+
const current = await readCollectionValidator(physical);
|
|
5070
|
+
if (JSON.stringify(current) !== JSON.stringify(createOptions.validator)) {
|
|
5071
|
+
await client.command({ collMod: physical, validator: createOptions.validator });
|
|
5072
|
+
}
|
|
5010
5073
|
}
|
|
5074
|
+
await ensureDeclaredIndexes(collection, physical, def);
|
|
5011
5075
|
};
|
|
5012
5076
|
const updateSchema = async (collection, schema) => {
|
|
5013
5077
|
const physical = opts.resolveCollectionName(collection);
|