@classytic/mongokit 3.29.0 → 3.31.0
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 +43 -0
- package/dist/{PaginationEngine-CczbvCAq.mjs → PaginationEngine-CuONvwCM.mjs} +21 -1
- package/dist/{create-repository-D4ZU94b3.d.mts → Repository-BAzymuOz.d.mts} +31 -58
- package/dist/{Repository-DThvFCaV.mjs → Repository-BQ3FEnOa.mjs} +352 -56
- package/dist/actions/index.d.mts +1 -1
- package/dist/actions/index.mjs +1 -1
- package/dist/adapter/index.mjs +1 -1
- package/dist/{adapter-CLvUHiTW.mjs → adapter-BitHg-CN.mjs} +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.d.mts +13 -0
- package/dist/better-auth/index.mjs +76 -6
- package/dist/{create-repository-uhKt_oUT.mjs → create-repository-BOTVMsIg.mjs} +3 -2
- package/dist/create-repository-mEdfH9sz.d.mts +54 -0
- package/dist/{index-DceInCTX.d.mts → index-Beyr1r4d.d.mts} +1 -1
- package/dist/index.d.mts +305 -43
- package/dist/index.mjs +118 -11
- package/dist/kernel-conformance/index.d.mts +217 -0
- package/dist/kernel-conformance/index.mjs +603 -0
- package/dist/method-registry.plugin-DblM7V6j.mjs +112 -0
- package/dist/model-registry-ClKtWiHa.mjs +67 -0
- package/dist/{mongooseToJsonSchema-CByt1VhQ.mjs → mongooseToJsonSchema-DKbnKduN.mjs} +14 -3
- package/dist/{multi-tenant.plugin-DVyVGREb.d.mts → multi-tenant.plugin-2VtSSm1Y.d.mts} +1 -1
- package/dist/{operations-C8IcCHF9.d.mts → operations-CuzX9m2Z.d.mts} +42 -3
- package/dist/outbox/index.d.mts +63 -0
- package/dist/outbox/index.mjs +239 -0
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/{plugin-options-vFufQZMZ.d.mts → plugin-options-BsFH0sTi.d.mts} +1 -1
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +3 -2
- package/dist/{repository-DCHRWRXw.d.mts → repository-Q16ErTu8.d.mts} +19 -1
- package/dist/testkit/index.d.mts +8 -1
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-CFL6LlYZ.mjs → timestamp.plugin-i9Qxm6kn.mjs} +32 -143
- package/dist/{to-plain-DkF5px0R.d.mts → to-plain-BuX1RUZV.d.mts} +12 -4
- package/dist/{update-CI2NdAW6.mjs → update-DAO5STDv.mjs} +62 -7
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +1 -1
- package/dist/{validation-chain.plugin-BLecaKa0.d.mts → validation-chain.plugin-BSshLJTA.d.mts} +3 -3
- package/dist/{validation-chain.plugin-CwOcUJC1.mjs → validation-chain.plugin-CpcB6dOd.mjs} +4 -3
- package/package.json +193 -187
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as PopulateSpec, i as ObjectId, l as SortSpec, o as ReadPreferenceType, r as FilterQuery, s as SelectSpec, t as AnyDocument } from "./core-62oo3N1C.mjs";
|
|
2
2
|
import { r as DocField } from "./type-utils-CLzRFtIA.mjs";
|
|
3
|
-
import { RepositoryBase } from "@classytic/repo-core/repository";
|
|
3
|
+
import { RepoCapabilities, RepositoryBase } from "@classytic/repo-core/repository";
|
|
4
4
|
import { Model, PipelineStage } from "mongoose";
|
|
5
5
|
import { HttpError } from "@classytic/repo-core/errors";
|
|
6
6
|
import { OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
@@ -191,6 +191,24 @@ interface RepositoryOptions {
|
|
|
191
191
|
* ```
|
|
192
192
|
*/
|
|
193
193
|
requirePlugins?: readonly RepositoryPluginName[];
|
|
194
|
+
/**
|
|
195
|
+
* Override the whole runtime capability descriptor.
|
|
196
|
+
*
|
|
197
|
+
* By DEFAULT `repo.capabilities` is OBSERVED from the model's live
|
|
198
|
+
* connection topology (see `capabilities.ts`) — a standalone `mongod`
|
|
199
|
+
* reports `transactions: false`, and an unreadable topology reports
|
|
200
|
+
* `false` with `transactionsResolution: 'unknown'` so money kernels fail
|
|
201
|
+
* closed. Supply this only when you genuinely know better than the driver:
|
|
202
|
+
* a proxied/wrapped connection, a non-mongoose seam, or a test that needs
|
|
203
|
+
* to pin a descriptor.
|
|
204
|
+
*
|
|
205
|
+
* **Never pass `{ ...MONGOKIT_CAPABILITIES }` to quiet a boot gate.** That
|
|
206
|
+
* re-creates the exact defect the observation replaced: a declared
|
|
207
|
+
* capability nothing can falsify. If a deployment truly cannot do
|
|
208
|
+
* transactions and you accept the risk, say so at the KERNEL
|
|
209
|
+
* (`allowNonTransactional: true`), where the degradation is logged.
|
|
210
|
+
*/
|
|
211
|
+
capabilities?: RepoCapabilities;
|
|
194
212
|
/**
|
|
195
213
|
* Standard Schema validator (Zod 3.24+, Valibot 1+, ArkType 2+, ...)
|
|
196
214
|
* for write payloads. Forwarded to `RepositoryBase`, which validates
|
package/dist/testkit/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as Repository } from "../Repository-BAzymuOz.mjs";
|
|
2
|
+
import { n as CreateRepositoryConfig } from "../create-repository-mEdfH9sz.mjs";
|
|
2
3
|
import { Connection, Model, Schema } from "mongoose";
|
|
3
4
|
import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server";
|
|
4
5
|
//#region src/testkit/index.d.ts
|
|
@@ -7,6 +8,12 @@ interface MongoMemoryOptions {
|
|
|
7
8
|
* Boot a single-node replica set instead of a standalone mongod. Required for
|
|
8
9
|
* any code that opens a `session.startTransaction()` — a standalone server
|
|
9
10
|
* rejects it with error 263. Slightly slower to start (~2-3s). Default false.
|
|
11
|
+
*
|
|
12
|
+
* Since 2026-08-05 this also changes what `repo.capabilities` REPORTS:
|
|
13
|
+
* capabilities are observed from the live topology, so a standalone harness
|
|
14
|
+
* yields `transactions: false` and any kernel boot gate built on it will
|
|
15
|
+
* (correctly) refuse to bind. Set `replset: true` when the code under test
|
|
16
|
+
* constructs a money kernel — or pass that kernel's `allowNonTransactional`.
|
|
10
17
|
*/
|
|
11
18
|
replset?: boolean;
|
|
12
19
|
/** Database name baked into the generated URI. Default `"mongokit-test"`. */
|
package/dist/testkit/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { c as ALL_OPERATIONS, t as HOOK_PRIORITY, u as OP_REGISTRY } from "./Repository-
|
|
1
|
+
import { c as ALL_OPERATIONS, t as HOOK_PRIORITY, u as OP_REGISTRY } from "./Repository-BQ3FEnOa.mjs";
|
|
2
2
|
import { n as warn } from "./logger-K7dPmu2U.mjs";
|
|
3
|
-
import { t as createError } from "./error-BuByZwKU.mjs";
|
|
4
3
|
import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
|
|
5
4
|
import mongoose from "mongoose";
|
|
6
5
|
import { cachePlugin } from "@classytic/repo-core/cache";
|
|
@@ -632,81 +631,6 @@ var AuditTrailQuery = class {
|
|
|
632
631
|
}
|
|
633
632
|
};
|
|
634
633
|
//#endregion
|
|
635
|
-
//#region src/plugins/batch-operations.plugin.ts
|
|
636
|
-
/**
|
|
637
|
-
* Batch operations plugin.
|
|
638
|
-
*
|
|
639
|
-
* @example
|
|
640
|
-
* const repo = new Repository(Model, [
|
|
641
|
-
* methodRegistryPlugin(),
|
|
642
|
-
* batchOperationsPlugin(),
|
|
643
|
-
* ]);
|
|
644
|
-
*
|
|
645
|
-
* // updateMany + deleteMany are primitives on Repository — no plugin needed.
|
|
646
|
-
* await repo.updateMany({ status: 'pending' }, { status: 'active' });
|
|
647
|
-
* await repo.deleteMany({ status: 'deleted' });
|
|
648
|
-
*
|
|
649
|
-
* // bulkWrite is what this plugin contributes.
|
|
650
|
-
* await repo.bulkWrite([...ops]);
|
|
651
|
-
*/
|
|
652
|
-
function batchOperationsPlugin() {
|
|
653
|
-
return {
|
|
654
|
-
name: "batch-operations",
|
|
655
|
-
apply(repo) {
|
|
656
|
-
if (!repo.registerMethod) throw new Error("batchOperationsPlugin requires methodRegistryPlugin");
|
|
657
|
-
/**
|
|
658
|
-
* Execute heterogeneous bulk write operations in a single database call.
|
|
659
|
-
*
|
|
660
|
-
* Supports insertOne, updateOne, updateMany, deleteOne, deleteMany, and replaceOne
|
|
661
|
-
* operations mixed together for maximum efficiency.
|
|
662
|
-
*
|
|
663
|
-
* @example
|
|
664
|
-
* await repo.bulkWrite([
|
|
665
|
-
* { insertOne: { document: { name: 'New Item', price: 10 } } },
|
|
666
|
-
* { updateOne: { filter: { _id: id1 }, update: { $inc: { views: 1 } } } },
|
|
667
|
-
* { updateMany: { filter: { status: 'draft' }, update: { $set: { status: 'published' } } } },
|
|
668
|
-
* { deleteOne: { filter: { _id: id2 } } },
|
|
669
|
-
* ]);
|
|
670
|
-
*/
|
|
671
|
-
repo.registerMethod("bulkWrite", async function(operations, options = {}) {
|
|
672
|
-
const context = await this._buildContext("bulkWrite", {
|
|
673
|
-
operations,
|
|
674
|
-
...options
|
|
675
|
-
});
|
|
676
|
-
try {
|
|
677
|
-
const finalOps = context.operations || operations;
|
|
678
|
-
if (!finalOps || finalOps.length === 0) throw createError(400, "bulkWrite requires at least one operation");
|
|
679
|
-
const result = await this.Model.bulkWrite(finalOps, {
|
|
680
|
-
ordered: options.ordered ?? true,
|
|
681
|
-
session: options.session
|
|
682
|
-
});
|
|
683
|
-
const bulkResult = {
|
|
684
|
-
ok: result.ok,
|
|
685
|
-
insertedCount: result.insertedCount,
|
|
686
|
-
upsertedCount: result.upsertedCount,
|
|
687
|
-
matchedCount: result.matchedCount,
|
|
688
|
-
modifiedCount: result.modifiedCount,
|
|
689
|
-
deletedCount: result.deletedCount,
|
|
690
|
-
insertedIds: result.insertedIds,
|
|
691
|
-
upsertedIds: result.upsertedIds
|
|
692
|
-
};
|
|
693
|
-
await this.emitAsync("after:bulkWrite", {
|
|
694
|
-
context,
|
|
695
|
-
result: bulkResult
|
|
696
|
-
});
|
|
697
|
-
return bulkResult;
|
|
698
|
-
} catch (error) {
|
|
699
|
-
this.emit("error:bulkWrite", {
|
|
700
|
-
context,
|
|
701
|
-
error
|
|
702
|
-
});
|
|
703
|
-
throw this._handleError(error);
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
//#endregion
|
|
710
634
|
//#region src/plugins/custom-id.plugin.ts
|
|
711
635
|
/**
|
|
712
636
|
* Custom ID Plugin
|
|
@@ -893,7 +817,6 @@ function dateSequentialId(options) {
|
|
|
893
817
|
default:
|
|
894
818
|
datePart = `${year}${separator}${month}`;
|
|
895
819
|
counterKey = `${model.modelName}:${year}-${month}`;
|
|
896
|
-
break;
|
|
897
820
|
}
|
|
898
821
|
const seq = await getNextSequence(counterKey, 1, context._counterConnection, context.session);
|
|
899
822
|
return `${prefix}${separator}${datePart}${separator}${String(seq).padStart(padding, "0")}`;
|
|
@@ -972,50 +895,18 @@ function customIdPlugin(options) {
|
|
|
972
895
|
docsNeedingIds.push(doc);
|
|
973
896
|
}
|
|
974
897
|
if (docsNeedingIds.length === 0) return;
|
|
975
|
-
for (const doc of docsNeedingIds)
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
898
|
+
for (const doc of docsNeedingIds) {
|
|
899
|
+
const id = await options.generator({
|
|
900
|
+
...context,
|
|
901
|
+
data: doc
|
|
902
|
+
});
|
|
903
|
+
doc[fieldName] = id;
|
|
904
|
+
}
|
|
979
905
|
});
|
|
980
906
|
}
|
|
981
907
|
};
|
|
982
908
|
}
|
|
983
909
|
//#endregion
|
|
984
|
-
//#region src/plugins/method-registry.plugin.ts
|
|
985
|
-
/**
|
|
986
|
-
* Method registry plugin that enables dynamic method registration
|
|
987
|
-
*/
|
|
988
|
-
function methodRegistryPlugin() {
|
|
989
|
-
return {
|
|
990
|
-
name: "method-registry",
|
|
991
|
-
apply(repo) {
|
|
992
|
-
const registeredMethods = [];
|
|
993
|
-
/**
|
|
994
|
-
* Register a new method on the repository instance
|
|
995
|
-
*/
|
|
996
|
-
repo.registerMethod = (name, fn) => {
|
|
997
|
-
if (repo[name]) throw new Error(`Cannot register method '${name}': Method already exists on repository. Choose a different name or use a plugin that doesn't conflict.`);
|
|
998
|
-
if (!name || typeof name !== "string") throw new Error("Method name must be a non-empty string");
|
|
999
|
-
if (typeof fn !== "function") throw new Error(`Method '${name}' must be a function`);
|
|
1000
|
-
repo[name] = fn.bind(repo);
|
|
1001
|
-
registeredMethods.push(name);
|
|
1002
|
-
repo.emit("method:registered", {
|
|
1003
|
-
name,
|
|
1004
|
-
fn
|
|
1005
|
-
});
|
|
1006
|
-
};
|
|
1007
|
-
/**
|
|
1008
|
-
* Check if a method is registered
|
|
1009
|
-
*/
|
|
1010
|
-
repo.hasMethod = (name) => typeof repo[name] === "function";
|
|
1011
|
-
/**
|
|
1012
|
-
* Get list of all dynamically registered methods
|
|
1013
|
-
*/
|
|
1014
|
-
repo.getRegisteredMethods = () => [...registeredMethods];
|
|
1015
|
-
}
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
//#endregion
|
|
1019
910
|
//#region src/plugins/multi-tenant.plugin.ts
|
|
1020
911
|
/**
|
|
1021
912
|
* Multi-Tenant Plugin
|
|
@@ -1147,34 +1038,32 @@ function multiTenantPlugin(options = {}) {
|
|
|
1147
1038
|
}
|
|
1148
1039
|
}
|
|
1149
1040
|
break;
|
|
1150
|
-
case "operations":
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
for (const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
]
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
opBody.filter
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
const insertBody = subOp.insertOne;
|
|
1171
|
-
if (insertBody?.document) {
|
|
1172
|
-
assertTenantValue(insertBody.document[tenantField], "bulkWrite insertOne.document");
|
|
1173
|
-
insertBody.document[tenantField] = castId;
|
|
1041
|
+
case "operations": if (context.operations) {
|
|
1042
|
+
const ops = context.operations;
|
|
1043
|
+
for (const subOp of ops) {
|
|
1044
|
+
for (const key of [
|
|
1045
|
+
"updateOne",
|
|
1046
|
+
"updateMany",
|
|
1047
|
+
"deleteOne",
|
|
1048
|
+
"deleteMany",
|
|
1049
|
+
"replaceOne"
|
|
1050
|
+
]) {
|
|
1051
|
+
const opBody = subOp[key];
|
|
1052
|
+
if (opBody?.filter) {
|
|
1053
|
+
assertTenantValue(opBody.filter[tenantField], `bulkWrite ${key}.filter`);
|
|
1054
|
+
opBody.filter = {
|
|
1055
|
+
...opBody.filter,
|
|
1056
|
+
[tenantField]: castId
|
|
1057
|
+
};
|
|
1174
1058
|
}
|
|
1175
1059
|
}
|
|
1060
|
+
const insertBody = subOp.insertOne;
|
|
1061
|
+
if (insertBody?.document) {
|
|
1062
|
+
assertTenantValue(insertBody.document[tenantField], "bulkWrite insertOne.document");
|
|
1063
|
+
insertBody.document[tenantField] = castId;
|
|
1064
|
+
}
|
|
1176
1065
|
}
|
|
1177
|
-
|
|
1066
|
+
}
|
|
1178
1067
|
}
|
|
1179
1068
|
}, { priority: HOOK_PRIORITY.POLICY });
|
|
1180
1069
|
}
|
|
@@ -1542,4 +1431,4 @@ function timestampPlugin() {
|
|
|
1542
1431
|
};
|
|
1543
1432
|
}
|
|
1544
1433
|
//#endregion
|
|
1545
|
-
export {
|
|
1434
|
+
export { customIdPlugin as a, prefixedId as c, AuditTrailQuery as d, auditTrailPlugin as f, multiTenantPlugin as i, sequentialId as l, auditLogPlugin as m, softDeletePlugin as n, dateSequentialId as o, ensureAuditTrailReady as p, adminBypass$1 as r, getNextSequence as s, timestampPlugin as t, cachePlugin as u };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { v as UserContext } from "./repository-
|
|
2
|
-
import { r as FieldPreset } from "./plugin-options-
|
|
1
|
+
import { v as UserContext } from "./repository-Q16ErTu8.mjs";
|
|
2
|
+
import { r as FieldPreset } from "./plugin-options-BsFH0sTi.mjs";
|
|
3
3
|
import mongoose, { Schema } from "mongoose";
|
|
4
4
|
import { CacheAdapter } from "@classytic/repo-core/cache";
|
|
5
5
|
import { CrudSchemas, SchemaBuilderOptions } from "@classytic/repo-core/schema";
|
|
@@ -288,8 +288,16 @@ declare function buildCrudSchemasFromMongooseSchema(mongooseSchema: Schema, opti
|
|
|
288
288
|
* field accepts this function directly with no glue. Future Mongo-shaped
|
|
289
289
|
* kits implementing the same contract are interchangeable at the adapter
|
|
290
290
|
* boundary.
|
|
291
|
+
*
|
|
292
|
+
* GENERIC over the document type, not fixed to `Model<unknown>`. Only
|
|
293
|
+
* `.schema` is read, so the document type is irrelevant here — but Mongoose's
|
|
294
|
+
* `Model<T>` is INVARIANT through `schema.static`, so a hard `Model<unknown>`
|
|
295
|
+
* parameter rejects every concretely-typed model a host actually has. That is
|
|
296
|
+
* not theoretical: it made `arc init`'s generated `example.schemas.ts` fail
|
|
297
|
+
* `tsc` on a brand-new project. Instantiating `TDoc = unknown` still satisfies
|
|
298
|
+
* `SchemaGenerator<Model<unknown>>`, so the contract claim above is unchanged.
|
|
291
299
|
*/
|
|
292
|
-
declare function buildCrudSchemasFromModel(mongooseModel: mongoose.Model<
|
|
300
|
+
declare function buildCrudSchemasFromModel<TDoc = unknown>(mongooseModel: mongoose.Model<TDoc>, options?: SchemaBuilderOptions): CrudSchemasFramework;
|
|
293
301
|
//#endregion
|
|
294
302
|
//#region src/utils/to-plain.d.ts
|
|
295
303
|
/**
|
|
@@ -320,4 +328,4 @@ declare function buildCrudSchemasFromModel(mongooseModel: mongoose.Model<unknown
|
|
|
320
328
|
*/
|
|
321
329
|
declare function toPlain<T>(value: T): T;
|
|
322
330
|
//#endregion
|
|
323
|
-
export {
|
|
331
|
+
export { parseDuplicateKeyError as S, getMongooseProjection as _, createMemoryCache as a, createError as b, IdType as c, isObjectIdInstance as d, isValidIdForType as f, getFieldsForUser as g, filterResponseData as h, buildCrudSchemasFromMongooseSchema as i, getSchemaIdType as l, createFieldPreset as m, CrudSchemasFramework as n, LoggerConfig as o, toObjectId as p, buildCrudSchemasFromModel as r, configureLogger as s, toPlain as t, idVariants as u, CreateErrorOptions as v, isDuplicateKeyError as x, ParseDuplicateKeyOptions as y };
|
|
@@ -77,9 +77,28 @@ function expandShorthands(obj) {
|
|
|
77
77
|
}
|
|
78
78
|
return out;
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Build the schema oracle `coerceFilterDates` needs from a mongoose schema.
|
|
82
|
+
*
|
|
83
|
+
* A path is date-coercible only when the schema says it is a Date. Everything else — most
|
|
84
|
+
* importantly a `String` path whose values are ISO-shaped, like a `civilDate` of
|
|
85
|
+
* `'2026-08-02'` — keeps its operand verbatim.
|
|
86
|
+
*
|
|
87
|
+
* Unknown paths default to TRUE (coerce). A filter on a path the schema does not declare
|
|
88
|
+
* is usually a `strict: false` extension or a nested/dynamic key, and the old
|
|
89
|
+
* value-shape-only behaviour is the safer default there: coercing a genuine date string is
|
|
90
|
+
* right far more often than not for an undeclared path.
|
|
91
|
+
*/
|
|
92
|
+
function schemaDateOracle(schema) {
|
|
93
|
+
return (field) => {
|
|
94
|
+
const path = schema.path(field);
|
|
95
|
+
if (!path) return true;
|
|
96
|
+
return path.instance === "Date";
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function compileFilterToMongo(input, schema) {
|
|
81
100
|
if (!input) return {};
|
|
82
|
-
if (!isFilter(input)) return coerceFilterDates(expandShorthands(input));
|
|
101
|
+
if (!isFilter(input)) return coerceFilterDates(expandShorthands(input), schema ? { isDateField: schemaDateOracle(schema) } : void 0);
|
|
83
102
|
return compile(input);
|
|
84
103
|
}
|
|
85
104
|
function compile(filter) {
|
|
@@ -182,6 +201,25 @@ const DANGEROUS_OPERATORS = [
|
|
|
182
201
|
"$accumulator"
|
|
183
202
|
];
|
|
184
203
|
/**
|
|
204
|
+
* The local side of a join correlation, coerced when the two sides are stored
|
|
205
|
+
* as different BSON types.
|
|
206
|
+
*
|
|
207
|
+
* `$convert` with `onError`/`onNull` rather than `$toObjectId`: the latter
|
|
208
|
+
* throws on the first unconvertible value, which would fail the entire
|
|
209
|
+
* aggregation because ONE row held a legacy non-ObjectId reference. Returning
|
|
210
|
+
* null makes that row miss the join, exactly as a genuine non-match would.
|
|
211
|
+
*/
|
|
212
|
+
function localCorrelation(localField, coerce) {
|
|
213
|
+
const path = `$${localField}`;
|
|
214
|
+
if (coerce !== "objectId") return path;
|
|
215
|
+
return { $convert: {
|
|
216
|
+
input: path,
|
|
217
|
+
to: "objectId",
|
|
218
|
+
onError: null,
|
|
219
|
+
onNull: null
|
|
220
|
+
} };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
185
223
|
* Fluent builder for MongoDB $lookup aggregation stage
|
|
186
224
|
* Optimized for custom field joins at scale
|
|
187
225
|
*/
|
|
@@ -222,6 +260,17 @@ var LookupBuilder = class LookupBuilder {
|
|
|
222
260
|
return this;
|
|
223
261
|
}
|
|
224
262
|
/**
|
|
263
|
+
* Coerce the local field before comparing it to the foreign field.
|
|
264
|
+
*
|
|
265
|
+
* Use when the two sides are stored as different BSON types — most commonly a
|
|
266
|
+
* `string` reference against an `ObjectId` `_id`. Without it the join matches
|
|
267
|
+
* nothing and reports no error. See {@link LookupOptions.coerce}.
|
|
268
|
+
*/
|
|
269
|
+
coerce(kind) {
|
|
270
|
+
this.options.coerce = kind;
|
|
271
|
+
return this;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
225
274
|
* Mark this lookup as returning a single document
|
|
226
275
|
* Automatically unwraps the array result to a single object or null
|
|
227
276
|
*/
|
|
@@ -274,11 +323,12 @@ var LookupBuilder = class LookupBuilder {
|
|
|
274
323
|
* Otherwise, we use the simpler localField/foreignField form.
|
|
275
324
|
*/
|
|
276
325
|
build() {
|
|
277
|
-
const { from, localField, foreignField, as, single, pipeline, let: letVars } = this.options;
|
|
326
|
+
const { from, localField, foreignField, as, single, pipeline, let: letVars, coerce } = this.options;
|
|
278
327
|
if (!from) throw new Error("LookupBuilder: \"from\" collection is required");
|
|
328
|
+
if (coerce && (!localField || !foreignField)) throw new Error("LookupBuilder: \"coerce\" needs both localField and foreignField — it describes how to compare them. Without both there is no comparison to coerce.");
|
|
279
329
|
const outputField = as || from;
|
|
280
330
|
const stages = [];
|
|
281
|
-
const usePipelineForm = pipeline || letVars;
|
|
331
|
+
const usePipelineForm = pipeline || letVars || coerce;
|
|
282
332
|
let lookupStage;
|
|
283
333
|
if (usePipelineForm) if (!pipeline || pipeline.length === 0) {
|
|
284
334
|
if (!localField || !foreignField) throw new Error("LookupBuilder: When using pipeline form without a custom pipeline, both localField and foreignField are required to auto-generate the pipeline");
|
|
@@ -286,7 +336,7 @@ var LookupBuilder = class LookupBuilder {
|
|
|
286
336
|
lookupStage = { $lookup: {
|
|
287
337
|
from,
|
|
288
338
|
let: {
|
|
289
|
-
[localField]:
|
|
339
|
+
[localField]: localCorrelation(localField, coerce),
|
|
290
340
|
...letVars || {}
|
|
291
341
|
},
|
|
292
342
|
pipeline: autoPipeline,
|
|
@@ -297,7 +347,7 @@ var LookupBuilder = class LookupBuilder {
|
|
|
297
347
|
let effectiveLet = letVars;
|
|
298
348
|
let effectivePipeline = safePipeline;
|
|
299
349
|
if (localField && foreignField && !letVars) {
|
|
300
|
-
effectiveLet = { lookupJoinVal:
|
|
350
|
+
effectiveLet = { lookupJoinVal: localCorrelation(localField, coerce) };
|
|
301
351
|
effectivePipeline = [{ $match: { $expr: { $eq: [`$${foreignField}`, "$$lookupJoinVal"] } } }, ...safePipeline];
|
|
302
352
|
}
|
|
303
353
|
lookupStage = { $lookup: {
|
|
@@ -370,7 +420,11 @@ var LookupBuilder = class LookupBuilder {
|
|
|
370
420
|
...lookup.let || {}
|
|
371
421
|
});
|
|
372
422
|
builder.sanitize(false);
|
|
373
|
-
} else
|
|
423
|
+
} else {
|
|
424
|
+
if (lookup.let) builder.let(lookup.let);
|
|
425
|
+
if (lookup.coerce) builder.coerce(lookup.coerce);
|
|
426
|
+
if (lookup.coerce) builder.coerce(lookup.coerce);
|
|
427
|
+
}
|
|
374
428
|
return builder.build();
|
|
375
429
|
});
|
|
376
430
|
}
|
|
@@ -394,6 +448,7 @@ var LookupBuilder = class LookupBuilder {
|
|
|
394
448
|
if (lookup.single !== void 0) builder.single(lookup.single);
|
|
395
449
|
if (lookup.pipeline) builder.pipeline(lookup.pipeline);
|
|
396
450
|
if (lookup.let) builder.let(lookup.let);
|
|
451
|
+
if (lookup.coerce) builder.coerce(lookup.coerce);
|
|
397
452
|
return builder.build();
|
|
398
453
|
});
|
|
399
454
|
}
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, toObjectId, toPlain };
|
|
1
|
+
import { _ as getMongooseProjection, a as createMemoryCache, b as createError, c as IdType, d as isObjectIdInstance, f as isValidIdForType, g as getFieldsForUser, h as filterResponseData, i as buildCrudSchemasFromMongooseSchema, l as getSchemaIdType, m as createFieldPreset, n as CrudSchemasFramework, o as LoggerConfig, p as toObjectId, r as buildCrudSchemasFromModel, s as configureLogger, t as toPlain, v as CreateErrorOptions, y as ParseDuplicateKeyOptions } from "../to-plain-BuX1RUZV.mjs";
|
|
2
|
+
export { type CreateErrorOptions, type CrudSchemasFramework, type IdType, type LoggerConfig, type ParseDuplicateKeyOptions, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, toObjectId, toPlain };
|
package/dist/utils/index.mjs
CHANGED
|
@@ -3,5 +3,5 @@ import { t as configureLogger } from "../logger-K7dPmu2U.mjs";
|
|
|
3
3
|
import { t as createError } from "../error-BuByZwKU.mjs";
|
|
4
4
|
import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "../field-selection-FSQFLpsU.mjs";
|
|
5
5
|
import { n as createMemoryCache, t as toPlain } from "../to-plain-vksRrLtc.mjs";
|
|
6
|
-
import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-
|
|
6
|
+
import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-DKbnKduN.mjs";
|
|
7
7
|
export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, toObjectId, toPlain };
|
package/dist/{validation-chain.plugin-BLecaKa0.d.mts → validation-chain.plugin-BSshLJTA.d.mts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as PopulateSpec, i as ObjectId, l as SortSpec, s as SelectSpec } from "./core-62oo3N1C.mjs";
|
|
2
|
-
import { h as RepositoryInstance, l as Plugin, p as RepositoryContext } from "./repository-
|
|
3
|
-
import { c as ValidationChainOptions, i as Logger, l as ValidatorDefinition, o as SoftDeleteOptions, r as FieldPreset, t as CascadeOptions } from "./plugin-options-
|
|
2
|
+
import { h as RepositoryInstance, l as Plugin, p as RepositoryContext } from "./repository-Q16ErTu8.mjs";
|
|
3
|
+
import { c as ValidationChainOptions, i as Logger, l as ValidatorDefinition, o as SoftDeleteOptions, r as FieldPreset, t as CascadeOptions } from "./plugin-options-BsFH0sTi.mjs";
|
|
4
4
|
import { BulkWriteResult } from "@classytic/repo-core/repository";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
@@ -958,4 +958,4 @@ declare function immutableField(field: string): ValidatorDefinition;
|
|
|
958
958
|
*/
|
|
959
959
|
declare function uniqueField(field: string, errorMessage?: string): ValidatorDefinition;
|
|
960
960
|
//#endregion
|
|
961
|
-
export {
|
|
961
|
+
export { IdempotencyStoreMethods as A, auditLogPlugin as B, LeaseMethods as C, ImmutableStatesPluginOptions as D, ImmutableClaimView as E, elasticSearchPlugin as F, appendOnlyPlugin as H, cascadePlugin as I, BatchOperationsMethods as L, idempotencyStorePlugin as M, fieldFilterPlugin as N, immutableStatesPlugin as O, ElasticSearchOptions as P, BulkWriteResult as R, methodRegistryPlugin as S, leasePlugin as T, AggregateHelpersMethods as U, AppendOnlyPluginOptions as V, aggregateHelpersPlugin as W, OperationMetric as _, requireField as a, mongoOperationsPlugin as b, timestampPlugin as c, createTenantContext as d, SubdocumentMethods as f, ObservabilityOptions as g, softDeletePlugin as h, immutableField as i, IdempotencyStorePluginOptions as j, IdempotencyClaim as k, TenantContext as l, SoftDeleteMethods as m, autoInject as n, uniqueField as o, subdocumentPlugin as p, blockIf as r, validationChainPlugin as s, OperationType as t, TenantStore as u, observabilityPlugin as v, LeasePluginOptions as w, MethodRegistryRepository as x, MongoOperationsMethods as y, batchOperationsPlugin as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as ALL_OPERATIONS, u as OP_REGISTRY } from "./Repository-
|
|
2
|
-
import { _ as upsert } from "./update-
|
|
1
|
+
import { c as ALL_OPERATIONS, u as OP_REGISTRY } from "./Repository-BQ3FEnOa.mjs";
|
|
2
|
+
import { _ as upsert } from "./update-DAO5STDv.mjs";
|
|
3
3
|
import { n as warn } from "./logger-K7dPmu2U.mjs";
|
|
4
4
|
import { t as createError } from "./error-BuByZwKU.mjs";
|
|
5
5
|
import { r as getFieldsForUser } from "./field-selection-FSQFLpsU.mjs";
|
|
@@ -463,7 +463,8 @@ function fieldFilterPlugin(fieldPreset) {
|
|
|
463
463
|
apply(repo) {
|
|
464
464
|
const applyFieldFiltering = (context) => {
|
|
465
465
|
if (!fieldPreset) return;
|
|
466
|
-
const
|
|
466
|
+
const user = context.context?.user || context.user;
|
|
467
|
+
const presetSelect = getFieldsForUser(user, fieldPreset).join(" ");
|
|
467
468
|
if (context.select) context.select = `${presetSelect} ${context.select}`;
|
|
468
469
|
else context.select = presetSelect;
|
|
469
470
|
};
|