@classytic/mongokit 3.39.0 → 3.40.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/dist/{PaginationEngine-DQ7beTCR.mjs → PaginationEngine-Dvl8jcRY.mjs} +33 -17
- package/dist/{Repository-D7fDpyFx.mjs → Repository-BMgPSkqZ.mjs} +279 -625
- package/dist/{Repository-IxI983_m.d.mts → Repository-CR8Y9g2E.d.mts} +57 -32
- package/dist/actions/index.mjs +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.mjs +1 -1
- package/dist/{create-repository-DOe9E_5F.mjs → create-repository-BzBUMvd1.mjs} +2 -2
- package/dist/{create-repository-CRXjDa3N.d.mts → create-repository-CAle1MSU.d.mts} +4 -4
- package/dist/idempotency/index.d.mts +23 -0
- package/dist/idempotency/index.mjs +134 -0
- package/dist/index.d.mts +58 -32
- package/dist/index.mjs +285 -675
- package/dist/indexes/verify-indexes.d.mts +14 -1
- package/dist/indexes/verify-indexes.mjs +54 -7
- package/dist/{multi-tenant.plugin-B-tzNeYL.d.mts → multi-tenant.plugin-CBgCo9im.d.mts} +18 -2
- package/dist/outbox/index.d.mts +1 -1
- package/dist/outbox/index.mjs +6 -4
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/{plugin-options-BuvtOSZf.d.mts → plugin-options-Zk46lD6F.d.mts} +15 -1
- package/dist/plugins/index.d.mts +8 -3
- package/dist/plugins/index.mjs +98 -25
- package/dist/{repository-ZbLtL7Nk.d.mts → repository-odl03582.d.mts} +20 -2
- package/dist/sync/index.d.mts +40 -0
- package/dist/sync/index.mjs +334 -0
- package/dist/tenant/index.mjs +2 -0
- package/dist/testkit/index.d.mts +2 -2
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-B5eiaItQ.mjs → timestamp.plugin-DF0gJB6p.mjs} +86 -2
- package/dist/{to-plain-B8ufk3Qz.d.mts → to-plain-k7SjNlSS.d.mts} +2 -2
- package/dist/transaction-B5L9glO0.mjs +533 -0
- package/dist/{update-c_01A0if.mjs → update-DG0fCwRJ.mjs} +168 -11
- package/dist/utils/index.d.mts +1 -1
- package/dist/{validation-chain.plugin-Bju1D1Sn.mjs → validation-chain.plugin-BLY2gLEk.mjs} +192 -279
- package/dist/{validation-chain.plugin-DvrLJIbJ.d.mts → validation-chain.plugin-DYp9E7K2.d.mts} +25 -3
- package/package.json +13 -4
|
@@ -95,6 +95,122 @@ async function upsert(Model, query, data, options = {}) {
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
//#endregion
|
|
98
|
+
//#region src/filter/cast-schema.ts
|
|
99
|
+
/** Operators whose operand is a single scalar to cast. */
|
|
100
|
+
const SCALAR_OPS = /* @__PURE__ */ new Set([
|
|
101
|
+
"$eq",
|
|
102
|
+
"$ne",
|
|
103
|
+
"$gt",
|
|
104
|
+
"$gte",
|
|
105
|
+
"$lt",
|
|
106
|
+
"$lte"
|
|
107
|
+
]);
|
|
108
|
+
/** Operators whose operand is an array of scalars to cast element-wise. */
|
|
109
|
+
const ARRAY_OPS = /* @__PURE__ */ new Set(["$in", "$nin"]);
|
|
110
|
+
/** Logical operators whose operand is an ARRAY of sub-filters. */
|
|
111
|
+
const LOGICAL_ARRAY_OPS$1 = /* @__PURE__ */ new Set([
|
|
112
|
+
"$and",
|
|
113
|
+
"$or",
|
|
114
|
+
"$nor"
|
|
115
|
+
]);
|
|
116
|
+
/** Logical operators whose operand is a SINGLE nested sub-filter. */
|
|
117
|
+
const LOGICAL_OBJECT_OPS = /* @__PURE__ */ new Set(["$not"]);
|
|
118
|
+
function isPlainObject(value) {
|
|
119
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Cast one value through a schema path. Returns the original value when the
|
|
123
|
+
* path is unknown, the value is nullish, or the cast throws — never raises.
|
|
124
|
+
*/
|
|
125
|
+
function castValue(schema, field, value) {
|
|
126
|
+
if (value === null || value === void 0) return value;
|
|
127
|
+
if (typeof value === "object" && !(value instanceof Date)) return value;
|
|
128
|
+
let schemaType;
|
|
129
|
+
try {
|
|
130
|
+
schemaType = schema.path(field);
|
|
131
|
+
} catch {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
if (!schemaType || typeof schemaType.cast !== "function") return value;
|
|
135
|
+
try {
|
|
136
|
+
return schemaType.cast(value);
|
|
137
|
+
} catch {
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Cast the operand(s) of a field-level operator object, leaving operators we
|
|
143
|
+
* don't understand (`$regex`, `$exists`, `$size`, `$elemMatch`, …) alone.
|
|
144
|
+
*/
|
|
145
|
+
function castOperatorObject(schema, field, ops) {
|
|
146
|
+
let changed = false;
|
|
147
|
+
const out = {};
|
|
148
|
+
for (const [op, operand] of Object.entries(ops)) if (SCALAR_OPS.has(op)) {
|
|
149
|
+
const next = castValue(schema, field, operand);
|
|
150
|
+
out[op] = next;
|
|
151
|
+
if (next !== operand) changed = true;
|
|
152
|
+
} else if (ARRAY_OPS.has(op) && Array.isArray(operand)) {
|
|
153
|
+
const arr = operand.map((entry) => castValue(schema, field, entry));
|
|
154
|
+
out[op] = arr;
|
|
155
|
+
if (arr.some((v, i) => v !== operand[i])) changed = true;
|
|
156
|
+
} else out[op] = operand;
|
|
157
|
+
return changed ? out : ops;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cast a record-shape Mongo filter against a Mongoose schema so it is safe to
|
|
161
|
+
* hand to a `$match` stage. Recurses `$and` / `$or` / `$nor` / `$not`, since a
|
|
162
|
+
* policy/tenant scope conjoined with a caller filter nests one level down.
|
|
163
|
+
*
|
|
164
|
+
* Filter IR nodes and non-objects pass through untouched — IR values are built
|
|
165
|
+
* in code (already correctly typed) and carry no schema paths to resolve.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* // organizationId is an ObjectId column; arc supplies the scope id as a string
|
|
170
|
+
* castFilterToSchema({ organizationId: '6a6a…' }, Order.schema)
|
|
171
|
+
* // → { organizationId: ObjectId('6a6a…') }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
function castFilterToSchema(filter, schema) {
|
|
175
|
+
if (!isPlainObject(filter)) return filter;
|
|
176
|
+
if (typeof filter.op === "string") return filter;
|
|
177
|
+
let changed = false;
|
|
178
|
+
const out = {};
|
|
179
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
180
|
+
if (LOGICAL_ARRAY_OPS$1.has(key) && Array.isArray(value)) {
|
|
181
|
+
const arr = value.map((entry) => castFilterToSchema(entry, schema));
|
|
182
|
+
out[key] = arr;
|
|
183
|
+
if (arr.some((v, i) => v !== value[i])) changed = true;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (LOGICAL_OBJECT_OPS.has(key) && isPlainObject(value)) {
|
|
187
|
+
const nested = castFilterToSchema(value, schema);
|
|
188
|
+
out[key] = nested;
|
|
189
|
+
if (nested !== value) changed = true;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (key.startsWith("$")) {
|
|
193
|
+
out[key] = value;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (isPlainObject(value)) {
|
|
197
|
+
const keys = Object.keys(value);
|
|
198
|
+
if (keys.length > 0 && keys.every((k) => k.startsWith("$"))) {
|
|
199
|
+
const nextOps = castOperatorObject(schema, key, value);
|
|
200
|
+
out[key] = nextOps;
|
|
201
|
+
if (nextOps !== value) changed = true;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
out[key] = value;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const next = castValue(schema, key, value);
|
|
208
|
+
out[key] = next;
|
|
209
|
+
if (next !== value) changed = true;
|
|
210
|
+
}
|
|
211
|
+
return changed ? out : filter;
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
98
214
|
//#region src/filter/compile.ts
|
|
99
215
|
/**
|
|
100
216
|
* Compile a Filter IR node to a MongoDB query object. Returns `{}`
|
|
@@ -114,6 +230,12 @@ async function upsert(Model, query, data, options = {}) {
|
|
|
114
230
|
* handles them"). This set covers every operator the arc aggregation
|
|
115
231
|
* guard accepts in `parseDateRange` and `hasFilterOnField`.
|
|
116
232
|
*/
|
|
233
|
+
/**
|
|
234
|
+
* The query-shorthand vocabulary, shared with `compileFilterToMongoExpr` so
|
|
235
|
+
* both filter surfaces recognise the same operator names. Exported because a
|
|
236
|
+
* second private copy is how the two compilers drifted apart in the first
|
|
237
|
+
* place: one accepted `{ f: { eq: 'x' } }` and the other read it as a literal.
|
|
238
|
+
*/
|
|
117
239
|
const SHORTHAND_OPS = /* @__PURE__ */ new Set([
|
|
118
240
|
"eq",
|
|
119
241
|
"ne",
|
|
@@ -191,7 +313,10 @@ function schemaDateOracle(schema) {
|
|
|
191
313
|
}
|
|
192
314
|
function compileFilterToMongo(input, schema) {
|
|
193
315
|
if (!input) return {};
|
|
194
|
-
if (!isFilter(input))
|
|
316
|
+
if (!isFilter(input)) {
|
|
317
|
+
const expanded = expandShorthands(input);
|
|
318
|
+
return coerceFilterDates(schema ? castFilterToSchema(expanded, schema) : expanded, schema ? { isDateField: schemaDateOracle(schema) } : void 0);
|
|
319
|
+
}
|
|
195
320
|
return compile(input);
|
|
196
321
|
}
|
|
197
322
|
function compile(filter) {
|
|
@@ -199,7 +324,7 @@ function compile(filter) {
|
|
|
199
324
|
case "true": return {};
|
|
200
325
|
case "false": return { $expr: false };
|
|
201
326
|
case "eq": return { [filter.field]: filter.value };
|
|
202
|
-
case "ne": return filter.value === null ? { [filter.field]: { $ne: null } } : {
|
|
327
|
+
case "ne": return filter.value === null ? { [filter.field]: { $ne: null } } : { [filter.field]: { $nin: [filter.value, null] } };
|
|
203
328
|
case "gt": return { [filter.field]: { $gt: filter.value } };
|
|
204
329
|
case "gte": return { [filter.field]: { $gte: filter.value } };
|
|
205
330
|
case "lt": return { [filter.field]: { $lt: filter.value } };
|
|
@@ -209,7 +334,7 @@ function compile(filter) {
|
|
|
209
334
|
return { [filter.field]: { $in: [...filter.values] } };
|
|
210
335
|
case "nin":
|
|
211
336
|
if (filter.values.length === 0) return {};
|
|
212
|
-
return { [filter.field]: { $nin: [...filter.values] } };
|
|
337
|
+
return { [filter.field]: { $nin: filter.values.includes(null) ? [...filter.values] : [...filter.values, null] } };
|
|
213
338
|
case "exists": return filter.exists ? { [filter.field]: { $ne: null } } : { [filter.field]: null };
|
|
214
339
|
case "like": return { [filter.field]: likeToRegex(filter.pattern, filter.caseSensitivity) };
|
|
215
340
|
/**
|
|
@@ -233,10 +358,10 @@ function compile(filter) {
|
|
|
233
358
|
$options: filter.flags
|
|
234
359
|
} : { $regex: filter.pattern } };
|
|
235
360
|
case "and": {
|
|
236
|
-
|
|
237
|
-
|
|
361
|
+
const parts = filter.children.map(compile).filter((part) => Object.keys(part).length > 0);
|
|
362
|
+
if (parts.length === 0) return {};
|
|
238
363
|
if (parts.length === 1) return parts[0];
|
|
239
|
-
return { $and: parts };
|
|
364
|
+
return mergeConjunction(parts) ?? { $and: parts };
|
|
240
365
|
}
|
|
241
366
|
case "or": {
|
|
242
367
|
if (filter.children.length === 0) return { $expr: false };
|
|
@@ -268,7 +393,7 @@ function compile(filter) {
|
|
|
268
393
|
* same way sqlitekit's `LIKE` semantically does.
|
|
269
394
|
*/
|
|
270
395
|
function likeToRegex(pattern, caseSensitivity) {
|
|
271
|
-
let out = "
|
|
396
|
+
let out = "";
|
|
272
397
|
for (let i = 0; i < pattern.length; i++) {
|
|
273
398
|
const ch = pattern[i];
|
|
274
399
|
if (ch === "\\" && i + 1 < pattern.length) {
|
|
@@ -283,12 +408,44 @@ function likeToRegex(pattern, caseSensitivity) {
|
|
|
283
408
|
else if (ch === "_") out += ".";
|
|
284
409
|
else out += escapeRegexChar(ch);
|
|
285
410
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
411
|
+
const source = `${out.startsWith(".*") ? out.slice(2) : `^${out}`}`;
|
|
412
|
+
const anchored = source.endsWith(".*") && !source.endsWith("\\.*") ? source.slice(0, -2) : `${source}$`;
|
|
413
|
+
return caseSensitivity === "sensitive" ? { $regex: anchored } : {
|
|
414
|
+
$regex: anchored,
|
|
289
415
|
$options: "i"
|
|
290
416
|
};
|
|
291
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Merge an AND of field conditions into one object (`{ price: { $gte, $lte } }`) when no field
|
|
420
|
+
* repeats an operator; `undefined` when a merge would change meaning, so the caller keeps `$and`.
|
|
421
|
+
*/
|
|
422
|
+
function mergeConjunction(parts) {
|
|
423
|
+
const merged = {};
|
|
424
|
+
for (const part of parts) for (const [field, condition] of Object.entries(part)) {
|
|
425
|
+
if (field.startsWith("$")) return void 0;
|
|
426
|
+
if (!(field in merged)) {
|
|
427
|
+
merged[field] = condition;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const left = asOperators(merged[field]);
|
|
431
|
+
const right = asOperators(condition);
|
|
432
|
+
if (!left || !right || Object.keys(right).some((op) => op in left)) return void 0;
|
|
433
|
+
merged[field] = {
|
|
434
|
+
...left,
|
|
435
|
+
...right
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return merged;
|
|
439
|
+
}
|
|
440
|
+
/** A field condition as an operator object — an equality value becomes `{ $eq }`. */
|
|
441
|
+
function asOperators(condition) {
|
|
442
|
+
if (condition instanceof RegExp || Array.isArray(condition)) return void 0;
|
|
443
|
+
if (condition !== null && typeof condition === "object" && !(condition instanceof Date)) {
|
|
444
|
+
const keys = Object.keys(condition);
|
|
445
|
+
return keys.length > 0 && keys.every((k) => k.startsWith("$")) ? condition : void 0;
|
|
446
|
+
}
|
|
447
|
+
return condition === null ? void 0 : { $eq: condition };
|
|
448
|
+
}
|
|
292
449
|
function escapeRegexChar(ch) {
|
|
293
450
|
return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
294
451
|
}
|
|
@@ -1257,4 +1414,4 @@ async function pullFromArray(Model, id, field, value, options = {}) {
|
|
|
1257
1414
|
return update(Model, id, { $pull: { [field]: value } }, options);
|
|
1258
1415
|
}
|
|
1259
1416
|
//#endregion
|
|
1260
|
-
export {
|
|
1417
|
+
export { isCreateManyPartialError as C, create_exports as S, SHORTHAND_OPS as _, count as a, create as b, getByQuery as c, deleteById as d, deleteByQuery as f, LookupBuilder as g, distinct as h, update_exports as i, getOrCreate as l, aggregate_exports as m, update as n, exists as o, delete_exports as p, updateByQuery as r, getById as s, findOneAndUpdate as t, read_exports as u, compileFilterToMongo as v, upsert as w, createMany as x, castFilterToSchema as y };
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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-
|
|
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-k7SjNlSS.mjs";
|
|
2
2
|
import { ClientSession, Model } from "mongoose";
|
|
3
3
|
//#region src/utils/id-chunks.d.ts
|
|
4
4
|
/**
|