@nxgt/mongo 0.3.0 → 0.3.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/dist/collection/context.d.ts +64 -0
- package/dist/collection/context.d.ts.map +1 -0
- package/dist/collection/documents.d.ts +21 -0
- package/dist/collection/documents.d.ts.map +1 -0
- package/dist/collection/filters.d.ts +15 -0
- package/dist/collection/filters.d.ts.map +1 -0
- package/dist/collection/get-collection.d.ts +1 -1
- package/dist/collection/get-collection.d.ts.map +1 -1
- package/dist/collection/paginate.d.ts +6 -0
- package/dist/collection/paginate.d.ts.map +1 -0
- package/dist/collection/reads.d.ts +19 -0
- package/dist/collection/reads.d.ts.map +1 -0
- package/dist/collection/writes.d.ts +12 -0
- package/dist/collection/writes.d.ts.map +1 -0
- package/dist/index.js +694 -643
- package/dist/index.js.map +14 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,29 +1,117 @@
|
|
|
1
|
-
// src/definition/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/definition/json-schema.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var MONGO_JSON_SCHEMA_KEYWORDS = new Set([
|
|
4
|
+
"additionalItems",
|
|
5
|
+
"additionalProperties",
|
|
6
|
+
"allOf",
|
|
7
|
+
"anyOf",
|
|
8
|
+
"bsonType",
|
|
9
|
+
"dependencies",
|
|
10
|
+
"description",
|
|
11
|
+
"enum",
|
|
12
|
+
"exclusiveMaximum",
|
|
13
|
+
"exclusiveMinimum",
|
|
14
|
+
"items",
|
|
15
|
+
"maxItems",
|
|
16
|
+
"maxLength",
|
|
17
|
+
"maxProperties",
|
|
18
|
+
"maximum",
|
|
19
|
+
"minItems",
|
|
20
|
+
"minLength",
|
|
21
|
+
"minProperties",
|
|
22
|
+
"minimum",
|
|
23
|
+
"multipleOf",
|
|
24
|
+
"not",
|
|
25
|
+
"oneOf",
|
|
26
|
+
"pattern",
|
|
27
|
+
"patternProperties",
|
|
28
|
+
"properties",
|
|
29
|
+
"required",
|
|
30
|
+
"title",
|
|
31
|
+
"type",
|
|
32
|
+
"uniqueItems"
|
|
33
|
+
]);
|
|
34
|
+
var SCHEMA_MAPS = new Set([
|
|
35
|
+
"properties",
|
|
36
|
+
"patternProperties",
|
|
37
|
+
"dependencies"
|
|
38
|
+
]);
|
|
39
|
+
function isRecord(value) {
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
var INTEGER_BSON_TYPES = ["int", "long", "double"];
|
|
43
|
+
function convertIntegerType(node) {
|
|
44
|
+
const type = node.type;
|
|
45
|
+
if (type === "integer") {
|
|
46
|
+
delete node.type;
|
|
47
|
+
node.bsonType = [...INTEGER_BSON_TYPES];
|
|
48
|
+
node.multipleOf ??= 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(type) && type.includes("integer")) {
|
|
52
|
+
delete node.type;
|
|
53
|
+
node.bsonType = [
|
|
54
|
+
...type.filter((one) => one !== "integer"),
|
|
55
|
+
...INTEGER_BSON_TYPES
|
|
56
|
+
];
|
|
57
|
+
node.multipleOf ??= 1;
|
|
5
58
|
}
|
|
6
|
-
return Object.freeze({
|
|
7
|
-
...config,
|
|
8
|
-
indexes: Object.freeze([...config.indexes ?? []]),
|
|
9
|
-
validation: Object.freeze({
|
|
10
|
-
level: config.validation?.level ?? "strict",
|
|
11
|
-
action: config.validation?.action ?? "error"
|
|
12
|
-
})
|
|
13
|
-
});
|
|
14
59
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
60
|
+
function refName(ref) {
|
|
61
|
+
return ref.replace(/^#\/(definitions|\$defs)\//, "");
|
|
62
|
+
}
|
|
63
|
+
function inline(value, defs, stack) {
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return value.map((one) => inline(one, defs, stack));
|
|
66
|
+
}
|
|
67
|
+
if (!isRecord(value))
|
|
68
|
+
return value;
|
|
69
|
+
if (typeof value.$ref === "string") {
|
|
70
|
+
const name = refName(value.$ref);
|
|
71
|
+
if (stack.includes(name)) {
|
|
72
|
+
throw new TypeError(`toMongoJsonSchema: "${name}" refers to itself. MongoDB's $jsonSchema ` + "has no $ref, so a recursive schema cannot be a validator. Give the " + "collection no validator, or model the field as an object with no " + "schema of its own.");
|
|
73
|
+
}
|
|
74
|
+
const target = defs[name];
|
|
75
|
+
if (!isRecord(target)) {
|
|
76
|
+
throw new TypeError(`toMongoJsonSchema: cannot resolve ${value.$ref}, which zod emitted`);
|
|
77
|
+
}
|
|
78
|
+
const { $ref: _ref, ...siblings } = value;
|
|
79
|
+
return {
|
|
80
|
+
...inline(target, defs, [...stack, name]),
|
|
81
|
+
...inline(siblings, defs, stack)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
86
|
+
if (!MONGO_JSON_SCHEMA_KEYWORDS.has(key))
|
|
87
|
+
continue;
|
|
88
|
+
if (SCHEMA_MAPS.has(key) && isRecord(inner)) {
|
|
89
|
+
const mapped = {};
|
|
90
|
+
for (const [name, schema] of Object.entries(inner)) {
|
|
91
|
+
mapped[name] = inline(schema, defs, stack);
|
|
92
|
+
}
|
|
93
|
+
out[key] = mapped;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
out[key] = inline(inner, defs, stack);
|
|
97
|
+
}
|
|
98
|
+
convertIntegerType(out);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function toMongoJsonSchema(schema) {
|
|
102
|
+
const json = z.toJSONSchema(schema, {
|
|
103
|
+
target: "draft-4",
|
|
104
|
+
io: "output",
|
|
105
|
+
unrepresentable: "any",
|
|
106
|
+
override: (ctx) => {
|
|
107
|
+
const type = ctx.zodSchema._zod.def.type;
|
|
108
|
+
if (type === "date" && ctx.jsonSchema.bsonType === undefined) {
|
|
109
|
+
ctx.jsonSchema.bsonType = "date";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const definitions = isRecord(json.definitions) ? json.definitions : isRecord(json.$defs) ? json.$defs : {};
|
|
114
|
+
return inline(json, definitions, []);
|
|
27
115
|
}
|
|
28
116
|
|
|
29
117
|
// src/errors/data-error.ts
|
|
@@ -94,7 +182,7 @@ class InvalidCursorError extends DataError {
|
|
|
94
182
|
}
|
|
95
183
|
|
|
96
184
|
// src/errors/to-data-error.ts
|
|
97
|
-
function
|
|
185
|
+
function isRecord2(value) {
|
|
98
186
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
99
187
|
}
|
|
100
188
|
function asArray(value) {
|
|
@@ -110,8 +198,8 @@ function indexFromMessage(message) {
|
|
|
110
198
|
}
|
|
111
199
|
function keysOfDuplicate(error) {
|
|
112
200
|
const pattern = error.keyPattern;
|
|
113
|
-
if (
|
|
114
|
-
const values =
|
|
201
|
+
if (isRecord2(pattern)) {
|
|
202
|
+
const values = isRecord2(error.keyValue) ? error.keyValue : undefined;
|
|
115
203
|
return { keys: Object.keys(pattern), values };
|
|
116
204
|
}
|
|
117
205
|
const inMessage = text(error.errmsg)?.match(/dup key:\s*\{([^}]*)\}/)?.[1];
|
|
@@ -122,8 +210,8 @@ function keysOfDuplicate(error) {
|
|
|
122
210
|
}
|
|
123
211
|
function firstWriteError(error) {
|
|
124
212
|
for (const write of asArray(error.writeErrors)) {
|
|
125
|
-
const inner =
|
|
126
|
-
if (
|
|
213
|
+
const inner = isRecord2(write) && isRecord2(write.err) ? write.err : write;
|
|
214
|
+
if (isRecord2(inner))
|
|
127
215
|
return inner;
|
|
128
216
|
}
|
|
129
217
|
return;
|
|
@@ -131,11 +219,11 @@ function firstWriteError(error) {
|
|
|
131
219
|
function issuesOf(details, path = []) {
|
|
132
220
|
const issues = [];
|
|
133
221
|
for (const rule of asArray(details)) {
|
|
134
|
-
if (!
|
|
222
|
+
if (!isRecord2(rule))
|
|
135
223
|
continue;
|
|
136
224
|
if (rule.propertiesNotSatisfied !== undefined) {
|
|
137
225
|
for (const property of asArray(rule.propertiesNotSatisfied)) {
|
|
138
|
-
if (!
|
|
226
|
+
if (!isRecord2(property))
|
|
139
227
|
continue;
|
|
140
228
|
const name = text(property.propertyName) ?? "";
|
|
141
229
|
const nested = issuesOf(property.details, [...path, name]);
|
|
@@ -171,7 +259,7 @@ function issuesOf(details, path = []) {
|
|
|
171
259
|
function toDataError(error, context = {}) {
|
|
172
260
|
if (error instanceof DataError)
|
|
173
261
|
return error;
|
|
174
|
-
if (!
|
|
262
|
+
if (!isRecord2(error))
|
|
175
263
|
return error;
|
|
176
264
|
const source = firstWriteError(error) ?? error;
|
|
177
265
|
const code = typeof source.code === "number" ? source.code : typeof error.code === "number" ? error.code : undefined;
|
|
@@ -191,303 +279,96 @@ function toDataError(error, context = {}) {
|
|
|
191
279
|
return new ConflictError(`Duplicate key on ${named}${context.collection ? ` in "${context.collection}"` : ""}`, { ...common, index, keys, values });
|
|
192
280
|
}
|
|
193
281
|
if (code === 121) {
|
|
194
|
-
const errInfo =
|
|
282
|
+
const errInfo = isRecord2(source.errInfo) ? source.errInfo : undefined;
|
|
195
283
|
const issues = issuesOf(errInfo?.details);
|
|
196
284
|
return new ValidationError(`Document failed validation${context.collection ? ` in "${context.collection}"` : ""}${issues.length > 0 ? `: ${issues.map((i) => `${i.path} ${i.reason}`).join(", ")}` : ""}`, { ...common, issues, keys: issues.map((issue) => issue.path) });
|
|
197
285
|
}
|
|
198
286
|
return new DataError(message || `MongoDB error ${code}`, common);
|
|
199
287
|
}
|
|
200
288
|
|
|
201
|
-
// src/
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
289
|
+
// src/sync/index-diff.ts
|
|
290
|
+
var COLLATION_DEFAULTS = {
|
|
291
|
+
caseLevel: false,
|
|
292
|
+
caseFirst: "off",
|
|
293
|
+
strength: 3,
|
|
294
|
+
numericOrdering: false,
|
|
295
|
+
alternate: "non-ignorable",
|
|
296
|
+
maxVariable: "punct",
|
|
297
|
+
normalization: false,
|
|
298
|
+
backwards: false
|
|
299
|
+
};
|
|
300
|
+
var OPTION_DEFAULTS = {
|
|
301
|
+
unique: false,
|
|
302
|
+
sparse: false,
|
|
303
|
+
hidden: false,
|
|
304
|
+
background: false
|
|
305
|
+
};
|
|
306
|
+
var IGNORED = new Set(["v", "ns", "key", "name"]);
|
|
307
|
+
function keyOf(index) {
|
|
308
|
+
const key = index.key;
|
|
309
|
+
return key instanceof Map ? Object.fromEntries(key) : { ...key };
|
|
205
310
|
}
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
if (raw instanceof Date)
|
|
209
|
-
return { $date: raw.toISOString() };
|
|
210
|
-
if (typeof raw === "bigint")
|
|
211
|
-
return { $bigint: raw.toString() };
|
|
212
|
-
if (isObjectId(raw))
|
|
213
|
-
return { $oid: raw.toHexString() };
|
|
214
|
-
return value;
|
|
311
|
+
function indexNameOf(key) {
|
|
312
|
+
return Object.entries(key).map(([field, direction]) => `${field}_${String(direction)}`).join("_");
|
|
215
313
|
}
|
|
216
|
-
function
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (typeof tagged.$bigint === "string")
|
|
224
|
-
return BigInt(tagged.$bigint);
|
|
225
|
-
if (typeof tagged.$oid === "string")
|
|
226
|
-
return new ObjectId(tagged.$oid);
|
|
227
|
-
}
|
|
314
|
+
function canonicalCollation(value) {
|
|
315
|
+
if (typeof value !== "object" || value === null)
|
|
316
|
+
return value;
|
|
317
|
+
const collation = value;
|
|
318
|
+
const out = {};
|
|
319
|
+
for (const [field, fallback] of Object.entries(COLLATION_DEFAULTS)) {
|
|
320
|
+
out[field] = collation[field] ?? fallback;
|
|
228
321
|
}
|
|
229
|
-
|
|
322
|
+
out.locale = collation.locale;
|
|
323
|
+
return out;
|
|
230
324
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
325
|
+
function normalizeIndex(index) {
|
|
326
|
+
const key = keyOf(index);
|
|
327
|
+
const options = {};
|
|
328
|
+
for (const [name, value] of Object.entries(index)) {
|
|
329
|
+
if (IGNORED.has(name) || value === undefined)
|
|
330
|
+
continue;
|
|
331
|
+
if (name === "collation") {
|
|
332
|
+
options.collation = canonicalCollation(value);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (OPTION_DEFAULTS[name] === value)
|
|
336
|
+
continue;
|
|
337
|
+
options[name] = value;
|
|
235
338
|
}
|
|
236
|
-
return
|
|
339
|
+
return { name: index.name ?? indexNameOf(key), key, options };
|
|
237
340
|
}
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
const binary = atob(base64 + "=".repeat((4 - base64.length % 4) % 4));
|
|
241
|
-
return new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
|
|
341
|
+
function canonical(value) {
|
|
342
|
+
return JSON.stringify(value, (_name, inner) => inner && typeof inner === "object" && !Array.isArray(inner) ? Object.fromEntries(Object.entries(inner).sort(([a], [b]) => a < b ? -1 : 1)) : inner);
|
|
242
343
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
344
|
+
function indexMatches(wanted, live) {
|
|
345
|
+
const a = normalizeIndex(wanted);
|
|
346
|
+
const b = normalizeIndex(live);
|
|
347
|
+
return JSON.stringify(Object.entries(a.key)) === JSON.stringify(Object.entries(b.key)) && canonical(a.options) === canonical(b.options);
|
|
245
348
|
}
|
|
246
|
-
function
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
349
|
+
function diffIndexes(wanted, live) {
|
|
350
|
+
const byName = new Map(live.map((index) => [normalizeIndex(index).name, index]));
|
|
351
|
+
const diff = {
|
|
352
|
+
create: [],
|
|
353
|
+
recreate: [],
|
|
354
|
+
unchanged: [],
|
|
355
|
+
extra: []
|
|
356
|
+
};
|
|
357
|
+
const named = new Set;
|
|
358
|
+
for (const index of wanted) {
|
|
359
|
+
const name = normalizeIndex(index).name;
|
|
360
|
+
named.add(name);
|
|
361
|
+
const existing = byName.get(name);
|
|
362
|
+
if (!existing)
|
|
363
|
+
diff.create.push({ ...index, name });
|
|
364
|
+
else if (indexMatches(index, existing))
|
|
365
|
+
diff.unchanged.push(name);
|
|
366
|
+
else
|
|
367
|
+
diff.recreate.push({ ...index, name });
|
|
254
368
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const [key, values] = parsed;
|
|
259
|
-
if (expectedKey !== undefined && key !== expectedKey) {
|
|
260
|
-
throw new InvalidCursorError(`Invalid cursor: it was written for the ordering ${key}, not ${expectedKey}`);
|
|
261
|
-
}
|
|
262
|
-
return { key, values };
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// src/pagination/page.ts
|
|
266
|
-
var DEFAULT_PAGE_SIZE = 20;
|
|
267
|
-
var DEFAULT_MAX_PAGE_SIZE = 100;
|
|
268
|
-
function positiveInteger(name, value) {
|
|
269
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
270
|
-
throw new RangeError(`${name} must be an integer of at least 1, not ${value}`);
|
|
271
|
-
}
|
|
272
|
-
return value;
|
|
273
|
-
}
|
|
274
|
-
function pageWindow(options = {}, maxPageSize = DEFAULT_MAX_PAGE_SIZE) {
|
|
275
|
-
const page = positiveInteger("page", options.page ?? 1);
|
|
276
|
-
const pageSize = Math.min(positiveInteger("pageSize", options.pageSize ?? DEFAULT_PAGE_SIZE), maxPageSize);
|
|
277
|
-
return { page, pageSize, limit: pageSize, skip: (page - 1) * pageSize };
|
|
278
|
-
}
|
|
279
|
-
function toPage(items, total, window) {
|
|
280
|
-
return {
|
|
281
|
-
items,
|
|
282
|
-
total,
|
|
283
|
-
page: window.page,
|
|
284
|
-
pageSize: window.pageSize,
|
|
285
|
-
pageCount: Math.ceil(total / window.pageSize)
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
function cursorLimit(limit, maxPageSize = DEFAULT_MAX_PAGE_SIZE) {
|
|
289
|
-
return Math.min(positiveInteger("limit", limit ?? DEFAULT_PAGE_SIZE), maxPageSize);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// src/definition/json-schema.ts
|
|
293
|
-
import { z } from "zod";
|
|
294
|
-
var MONGO_JSON_SCHEMA_KEYWORDS = new Set([
|
|
295
|
-
"additionalItems",
|
|
296
|
-
"additionalProperties",
|
|
297
|
-
"allOf",
|
|
298
|
-
"anyOf",
|
|
299
|
-
"bsonType",
|
|
300
|
-
"dependencies",
|
|
301
|
-
"description",
|
|
302
|
-
"enum",
|
|
303
|
-
"exclusiveMaximum",
|
|
304
|
-
"exclusiveMinimum",
|
|
305
|
-
"items",
|
|
306
|
-
"maxItems",
|
|
307
|
-
"maxLength",
|
|
308
|
-
"maxProperties",
|
|
309
|
-
"maximum",
|
|
310
|
-
"minItems",
|
|
311
|
-
"minLength",
|
|
312
|
-
"minProperties",
|
|
313
|
-
"minimum",
|
|
314
|
-
"multipleOf",
|
|
315
|
-
"not",
|
|
316
|
-
"oneOf",
|
|
317
|
-
"pattern",
|
|
318
|
-
"patternProperties",
|
|
319
|
-
"properties",
|
|
320
|
-
"required",
|
|
321
|
-
"title",
|
|
322
|
-
"type",
|
|
323
|
-
"uniqueItems"
|
|
324
|
-
]);
|
|
325
|
-
var SCHEMA_MAPS = new Set([
|
|
326
|
-
"properties",
|
|
327
|
-
"patternProperties",
|
|
328
|
-
"dependencies"
|
|
329
|
-
]);
|
|
330
|
-
function isRecord2(value) {
|
|
331
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
332
|
-
}
|
|
333
|
-
var INTEGER_BSON_TYPES = ["int", "long", "double"];
|
|
334
|
-
function convertIntegerType(node) {
|
|
335
|
-
const type = node.type;
|
|
336
|
-
if (type === "integer") {
|
|
337
|
-
delete node.type;
|
|
338
|
-
node.bsonType = [...INTEGER_BSON_TYPES];
|
|
339
|
-
node.multipleOf ??= 1;
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (Array.isArray(type) && type.includes("integer")) {
|
|
343
|
-
delete node.type;
|
|
344
|
-
node.bsonType = [
|
|
345
|
-
...type.filter((one) => one !== "integer"),
|
|
346
|
-
...INTEGER_BSON_TYPES
|
|
347
|
-
];
|
|
348
|
-
node.multipleOf ??= 1;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
function refName(ref) {
|
|
352
|
-
return ref.replace(/^#\/(definitions|\$defs)\//, "");
|
|
353
|
-
}
|
|
354
|
-
function inline(value, defs, stack) {
|
|
355
|
-
if (Array.isArray(value)) {
|
|
356
|
-
return value.map((one) => inline(one, defs, stack));
|
|
357
|
-
}
|
|
358
|
-
if (!isRecord2(value))
|
|
359
|
-
return value;
|
|
360
|
-
if (typeof value.$ref === "string") {
|
|
361
|
-
const name = refName(value.$ref);
|
|
362
|
-
if (stack.includes(name)) {
|
|
363
|
-
throw new TypeError(`toMongoJsonSchema: "${name}" refers to itself. MongoDB's $jsonSchema ` + "has no $ref, so a recursive schema cannot be a validator. Give the " + "collection no validator, or model the field as an object with no " + "schema of its own.");
|
|
364
|
-
}
|
|
365
|
-
const target = defs[name];
|
|
366
|
-
if (!isRecord2(target)) {
|
|
367
|
-
throw new TypeError(`toMongoJsonSchema: cannot resolve ${value.$ref}, which zod emitted`);
|
|
368
|
-
}
|
|
369
|
-
const { $ref: _ref, ...siblings } = value;
|
|
370
|
-
return {
|
|
371
|
-
...inline(target, defs, [...stack, name]),
|
|
372
|
-
...inline(siblings, defs, stack)
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
const out = {};
|
|
376
|
-
for (const [key, inner] of Object.entries(value)) {
|
|
377
|
-
if (!MONGO_JSON_SCHEMA_KEYWORDS.has(key))
|
|
378
|
-
continue;
|
|
379
|
-
if (SCHEMA_MAPS.has(key) && isRecord2(inner)) {
|
|
380
|
-
const mapped = {};
|
|
381
|
-
for (const [name, schema] of Object.entries(inner)) {
|
|
382
|
-
mapped[name] = inline(schema, defs, stack);
|
|
383
|
-
}
|
|
384
|
-
out[key] = mapped;
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
out[key] = inline(inner, defs, stack);
|
|
388
|
-
}
|
|
389
|
-
convertIntegerType(out);
|
|
390
|
-
return out;
|
|
391
|
-
}
|
|
392
|
-
function toMongoJsonSchema(schema) {
|
|
393
|
-
const json = z.toJSONSchema(schema, {
|
|
394
|
-
target: "draft-4",
|
|
395
|
-
io: "output",
|
|
396
|
-
unrepresentable: "any",
|
|
397
|
-
override: (ctx) => {
|
|
398
|
-
const type = ctx.zodSchema._zod.def.type;
|
|
399
|
-
if (type === "date" && ctx.jsonSchema.bsonType === undefined) {
|
|
400
|
-
ctx.jsonSchema.bsonType = "date";
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
const definitions = isRecord2(json.definitions) ? json.definitions : isRecord2(json.$defs) ? json.$defs : {};
|
|
405
|
-
return inline(json, definitions, []);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// src/sync/index-diff.ts
|
|
409
|
-
var COLLATION_DEFAULTS = {
|
|
410
|
-
caseLevel: false,
|
|
411
|
-
caseFirst: "off",
|
|
412
|
-
strength: 3,
|
|
413
|
-
numericOrdering: false,
|
|
414
|
-
alternate: "non-ignorable",
|
|
415
|
-
maxVariable: "punct",
|
|
416
|
-
normalization: false,
|
|
417
|
-
backwards: false
|
|
418
|
-
};
|
|
419
|
-
var OPTION_DEFAULTS = {
|
|
420
|
-
unique: false,
|
|
421
|
-
sparse: false,
|
|
422
|
-
hidden: false,
|
|
423
|
-
background: false
|
|
424
|
-
};
|
|
425
|
-
var IGNORED = new Set(["v", "ns", "key", "name"]);
|
|
426
|
-
function keyOf(index) {
|
|
427
|
-
const key = index.key;
|
|
428
|
-
return key instanceof Map ? Object.fromEntries(key) : { ...key };
|
|
429
|
-
}
|
|
430
|
-
function indexNameOf(key) {
|
|
431
|
-
return Object.entries(key).map(([field, direction]) => `${field}_${String(direction)}`).join("_");
|
|
432
|
-
}
|
|
433
|
-
function canonicalCollation(value) {
|
|
434
|
-
if (typeof value !== "object" || value === null)
|
|
435
|
-
return value;
|
|
436
|
-
const collation = value;
|
|
437
|
-
const out = {};
|
|
438
|
-
for (const [field, fallback] of Object.entries(COLLATION_DEFAULTS)) {
|
|
439
|
-
out[field] = collation[field] ?? fallback;
|
|
440
|
-
}
|
|
441
|
-
out.locale = collation.locale;
|
|
442
|
-
return out;
|
|
443
|
-
}
|
|
444
|
-
function normalizeIndex(index) {
|
|
445
|
-
const key = keyOf(index);
|
|
446
|
-
const options = {};
|
|
447
|
-
for (const [name, value] of Object.entries(index)) {
|
|
448
|
-
if (IGNORED.has(name) || value === undefined)
|
|
449
|
-
continue;
|
|
450
|
-
if (name === "collation") {
|
|
451
|
-
options.collation = canonicalCollation(value);
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
if (OPTION_DEFAULTS[name] === value)
|
|
455
|
-
continue;
|
|
456
|
-
options[name] = value;
|
|
457
|
-
}
|
|
458
|
-
return { name: index.name ?? indexNameOf(key), key, options };
|
|
459
|
-
}
|
|
460
|
-
function canonical(value) {
|
|
461
|
-
return JSON.stringify(value, (_name, inner) => inner && typeof inner === "object" && !Array.isArray(inner) ? Object.fromEntries(Object.entries(inner).sort(([a], [b]) => a < b ? -1 : 1)) : inner);
|
|
462
|
-
}
|
|
463
|
-
function indexMatches(wanted, live) {
|
|
464
|
-
const a = normalizeIndex(wanted);
|
|
465
|
-
const b = normalizeIndex(live);
|
|
466
|
-
return JSON.stringify(Object.entries(a.key)) === JSON.stringify(Object.entries(b.key)) && canonical(a.options) === canonical(b.options);
|
|
467
|
-
}
|
|
468
|
-
function diffIndexes(wanted, live) {
|
|
469
|
-
const byName = new Map(live.map((index) => [normalizeIndex(index).name, index]));
|
|
470
|
-
const diff = {
|
|
471
|
-
create: [],
|
|
472
|
-
recreate: [],
|
|
473
|
-
unchanged: [],
|
|
474
|
-
extra: []
|
|
475
|
-
};
|
|
476
|
-
const named = new Set;
|
|
477
|
-
for (const index of wanted) {
|
|
478
|
-
const name = normalizeIndex(index).name;
|
|
479
|
-
named.add(name);
|
|
480
|
-
const existing = byName.get(name);
|
|
481
|
-
if (!existing)
|
|
482
|
-
diff.create.push({ ...index, name });
|
|
483
|
-
else if (indexMatches(index, existing))
|
|
484
|
-
diff.unchanged.push(name);
|
|
485
|
-
else
|
|
486
|
-
diff.recreate.push({ ...index, name });
|
|
487
|
-
}
|
|
488
|
-
for (const name of byName.keys()) {
|
|
489
|
-
if (name !== "_id_" && !named.has(name))
|
|
490
|
-
diff.extra.push(name);
|
|
369
|
+
for (const name of byName.keys()) {
|
|
370
|
+
if (name !== "_id_" && !named.has(name))
|
|
371
|
+
diff.extra.push(name);
|
|
491
372
|
}
|
|
492
373
|
return diff;
|
|
493
374
|
}
|
|
@@ -628,368 +509,538 @@ async function syncCollections(db, definitions, options = {}) {
|
|
|
628
509
|
return reports;
|
|
629
510
|
}
|
|
630
511
|
|
|
631
|
-
// src/
|
|
632
|
-
function
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
function isUpdateFilter(patch) {
|
|
636
|
-
return Object.keys(patch).some((key) => key.startsWith("$"));
|
|
637
|
-
}
|
|
638
|
-
function mergeFilters(a, b) {
|
|
639
|
-
const left = a && Object.keys(a).length > 0 ? a : undefined;
|
|
640
|
-
const right = b && Object.keys(b).length > 0 ? b : undefined;
|
|
641
|
-
if (!left)
|
|
642
|
-
return right ?? {};
|
|
643
|
-
if (!right)
|
|
644
|
-
return left;
|
|
645
|
-
return { $and: [left, right] };
|
|
646
|
-
}
|
|
647
|
-
function databaseOf(source, name) {
|
|
648
|
-
const client = source;
|
|
649
|
-
if (typeof client.db === "function")
|
|
650
|
-
return client.db(name);
|
|
651
|
-
const db = source;
|
|
652
|
-
if (name !== undefined && db.databaseName !== name) {
|
|
653
|
-
throw new TypeError(`getCollection: given a Db for "${db.databaseName}", and a db option of ` + `"${name}". Pass the client, or the database you mean.`);
|
|
512
|
+
// src/definition/define-collection.ts
|
|
513
|
+
function defineCollection(config) {
|
|
514
|
+
if (!("_id" in config.schema.shape)) {
|
|
515
|
+
throw new TypeError(`defineCollection: "${config.name}"'s schema has no _id. Add ` + "`_id: id()`, which fills a new ObjectId on create, or declare the " + "key your documents use.");
|
|
654
516
|
}
|
|
655
|
-
return
|
|
517
|
+
return Object.freeze({
|
|
518
|
+
...config,
|
|
519
|
+
indexes: Object.freeze([...config.indexes ?? []]),
|
|
520
|
+
validation: Object.freeze({
|
|
521
|
+
level: config.validation?.level ?? "strict",
|
|
522
|
+
action: config.validation?.action ?? "error"
|
|
523
|
+
})
|
|
524
|
+
});
|
|
656
525
|
}
|
|
657
|
-
function
|
|
658
|
-
const
|
|
659
|
-
|
|
526
|
+
function stampsOf(definition) {
|
|
527
|
+
const shape = definition.schema.shape;
|
|
528
|
+
const has = (name) => (name in shape);
|
|
529
|
+
return {
|
|
530
|
+
createdAt: has("createdAt"),
|
|
531
|
+
updatedAt: has("updatedAt"),
|
|
532
|
+
deletedAt: has("deletedAt"),
|
|
533
|
+
version: has("version"),
|
|
534
|
+
createdBy: has("createdBy"),
|
|
535
|
+
updatedBy: has("updatedBy"),
|
|
536
|
+
deletedBy: has("deletedBy")
|
|
537
|
+
};
|
|
660
538
|
}
|
|
661
|
-
|
|
539
|
+
|
|
540
|
+
// src/pagination/page.ts
|
|
541
|
+
var DEFAULT_PAGE_SIZE = 20;
|
|
542
|
+
var DEFAULT_MAX_PAGE_SIZE = 100;
|
|
543
|
+
function positiveInteger(name, value) {
|
|
544
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
545
|
+
throw new RangeError(`${name} must be an integer of at least 1, not ${value}`);
|
|
546
|
+
}
|
|
547
|
+
return value;
|
|
548
|
+
}
|
|
549
|
+
function pageWindow(options = {}, maxPageSize = DEFAULT_MAX_PAGE_SIZE) {
|
|
550
|
+
const page = positiveInteger("page", options.page ?? 1);
|
|
551
|
+
const pageSize = Math.min(positiveInteger("pageSize", options.pageSize ?? DEFAULT_PAGE_SIZE), maxPageSize);
|
|
552
|
+
return { page, pageSize, limit: pageSize, skip: (page - 1) * pageSize };
|
|
553
|
+
}
|
|
554
|
+
function toPage(items, total, window) {
|
|
555
|
+
return {
|
|
556
|
+
items,
|
|
557
|
+
total,
|
|
558
|
+
page: window.page,
|
|
559
|
+
pageSize: window.pageSize,
|
|
560
|
+
pageCount: Math.ceil(total / window.pageSize)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function cursorLimit(limit, maxPageSize = DEFAULT_MAX_PAGE_SIZE) {
|
|
564
|
+
return Math.min(positiveInteger("limit", limit ?? DEFAULT_PAGE_SIZE), maxPageSize);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/collection/context.ts
|
|
568
|
+
function createContext(db, definition, options) {
|
|
662
569
|
const name = definition.name;
|
|
663
|
-
const collection = db.collection(name);
|
|
664
570
|
const shape = definition.schema.shape;
|
|
665
|
-
const hasOwnId = "id" in shape;
|
|
666
571
|
const stamps = stampsOf(definition);
|
|
667
572
|
const session = options.session;
|
|
668
|
-
const actor = options.actor;
|
|
669
|
-
const maxPageSize = options.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE;
|
|
670
|
-
const parses = (options.validate ?? "parse") === "parse";
|
|
671
|
-
const softDeletes = options.softDelete ?? stamps.deletedAt;
|
|
672
|
-
const touches = options.touchUpdatedAt ?? stamps.updatedAt;
|
|
673
|
-
const locks = options.optimisticLock ?? stamps.version;
|
|
674
573
|
if (options.softDelete === true && !stamps.deletedAt) {
|
|
675
574
|
throw new TypeError(`getCollection: softDelete needs a "deletedAt" field, and "${name}" has none`);
|
|
676
575
|
}
|
|
677
576
|
if (options.optimisticLock === true && !stamps.version) {
|
|
678
577
|
throw new TypeError(`getCollection: optimisticLock needs a "version" field, and "${name}" has none`);
|
|
679
578
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
579
|
+
return {
|
|
580
|
+
definition,
|
|
581
|
+
db,
|
|
582
|
+
collection: db.collection(name),
|
|
583
|
+
name,
|
|
584
|
+
shape,
|
|
585
|
+
stamps,
|
|
586
|
+
actor: options.actor,
|
|
587
|
+
session,
|
|
588
|
+
sessionOption: session ? { session } : {},
|
|
589
|
+
maxPageSize: options.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE,
|
|
590
|
+
hasOwnId: "id" in shape,
|
|
591
|
+
parses: (options.validate ?? "parse") === "parse",
|
|
592
|
+
softDeletes: options.softDelete ?? stamps.deletedAt,
|
|
593
|
+
touches: options.touchUpdatedAt ?? stamps.updatedAt,
|
|
594
|
+
locks: options.optimisticLock ?? stamps.version
|
|
686
595
|
};
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
596
|
+
}
|
|
597
|
+
async function run(ctx, fn) {
|
|
598
|
+
try {
|
|
599
|
+
return await fn();
|
|
600
|
+
} catch (error) {
|
|
601
|
+
throw toDataError(error, { collection: ctx.name });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function notFound(ctx, id) {
|
|
605
|
+
return new NotFoundError(`No document in "${ctx.name}" with _id ${String(id)}`, { collection: ctx.name, id });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/pagination/cursor.ts
|
|
609
|
+
import { ObjectId } from "mongodb";
|
|
610
|
+
function isObjectId(value) {
|
|
611
|
+
return typeof value === "object" && value !== null && value._bsontype === "ObjectId";
|
|
612
|
+
}
|
|
613
|
+
function replacer(key, value) {
|
|
614
|
+
const raw = this[key];
|
|
615
|
+
if (raw instanceof Date)
|
|
616
|
+
return { $date: raw.toISOString() };
|
|
617
|
+
if (typeof raw === "bigint")
|
|
618
|
+
return { $bigint: raw.toString() };
|
|
619
|
+
if (isObjectId(raw))
|
|
620
|
+
return { $oid: raw.toHexString() };
|
|
621
|
+
return value;
|
|
622
|
+
}
|
|
623
|
+
function reviver(_key, value) {
|
|
624
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
625
|
+
const keys = Object.keys(value);
|
|
626
|
+
if (keys.length === 1) {
|
|
627
|
+
const tagged = value;
|
|
628
|
+
if (typeof tagged.$date === "string")
|
|
629
|
+
return new Date(tagged.$date);
|
|
630
|
+
if (typeof tagged.$bigint === "string")
|
|
631
|
+
return BigInt(tagged.$bigint);
|
|
632
|
+
if (typeof tagged.$oid === "string")
|
|
633
|
+
return new ObjectId(tagged.$oid);
|
|
693
634
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
635
|
+
}
|
|
636
|
+
return value;
|
|
637
|
+
}
|
|
638
|
+
function toBase64Url(text) {
|
|
639
|
+
let binary = "";
|
|
640
|
+
for (const byte of new TextEncoder().encode(text)) {
|
|
641
|
+
binary += String.fromCharCode(byte);
|
|
642
|
+
}
|
|
643
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
644
|
+
}
|
|
645
|
+
function fromBase64Url(text) {
|
|
646
|
+
const base64 = text.replace(/-/g, "+").replace(/_/g, "/");
|
|
647
|
+
const binary = atob(base64 + "=".repeat((4 - base64.length % 4) % 4));
|
|
648
|
+
return new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
|
|
649
|
+
}
|
|
650
|
+
function encodeCursor(payload) {
|
|
651
|
+
return toBase64Url(JSON.stringify([payload.key, payload.values], replacer));
|
|
652
|
+
}
|
|
653
|
+
function decodeCursor(cursor, expectedKey) {
|
|
654
|
+
let parsed;
|
|
655
|
+
try {
|
|
656
|
+
parsed = JSON.parse(fromBase64Url(cursor), reviver);
|
|
657
|
+
} catch (cause) {
|
|
658
|
+
throw new InvalidCursorError("Invalid cursor: it cannot be decoded", {
|
|
659
|
+
cause
|
|
698
660
|
});
|
|
661
|
+
}
|
|
662
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 || typeof parsed[0] !== "string" || !Array.isArray(parsed[1])) {
|
|
663
|
+
throw new InvalidCursorError("Invalid cursor: unexpected shape");
|
|
664
|
+
}
|
|
665
|
+
const [key, values] = parsed;
|
|
666
|
+
if (expectedKey !== undefined && key !== expectedKey) {
|
|
667
|
+
throw new InvalidCursorError(`Invalid cursor: it was written for the ordering ${key}, not ${expectedKey}`);
|
|
668
|
+
}
|
|
669
|
+
return { key, values };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/collection/filters.ts
|
|
673
|
+
function isRecord3(value) {
|
|
674
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
675
|
+
}
|
|
676
|
+
function isUpdateFilter(patch) {
|
|
677
|
+
return Object.keys(patch).some((key) => key.startsWith("$"));
|
|
678
|
+
}
|
|
679
|
+
function mergeFilters(a, b) {
|
|
680
|
+
const left = a && Object.keys(a).length > 0 ? a : undefined;
|
|
681
|
+
const right = b && Object.keys(b).length > 0 ? b : undefined;
|
|
682
|
+
if (!left)
|
|
683
|
+
return right ?? {};
|
|
684
|
+
if (!right)
|
|
685
|
+
return left;
|
|
686
|
+
return { $and: [left, right] };
|
|
687
|
+
}
|
|
688
|
+
function live(ctx, withDeleted) {
|
|
689
|
+
return ctx.softDeletes && !withDeleted ? { deletedAt: null } : undefined;
|
|
690
|
+
}
|
|
691
|
+
function scoped(ctx, filter, withDeleted) {
|
|
692
|
+
return mergeFilters(isRecord3(filter) ? filter : undefined, live(ctx, withDeleted));
|
|
693
|
+
}
|
|
694
|
+
function requireFilter(ctx, method, filter) {
|
|
695
|
+
if (!isRecord3(filter) || Object.keys(filter).length === 0) {
|
|
696
|
+
throw new TypeError(`${method} needs a filter. Pass \`{ _id: { $exists: true } }\` to target every document of "${ctx.name}".`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/collection/documents.ts
|
|
701
|
+
function withId(ctx, document) {
|
|
702
|
+
if (ctx.hasOwnId || !isRecord3(document) || document._id === undefined || Object.hasOwn(document, "id")) {
|
|
699
703
|
return document;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
+
}
|
|
705
|
+
Object.defineProperty(document, "id", {
|
|
706
|
+
get: () => String(document._id),
|
|
707
|
+
enumerable: true,
|
|
708
|
+
configurable: true
|
|
704
709
|
});
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (
|
|
713
|
-
|
|
714
|
-
if (actor !== undefined) {
|
|
715
|
-
if (stamps.createdBy && stamped.createdBy === undefined) {
|
|
716
|
-
stamped.createdBy = actor;
|
|
717
|
-
}
|
|
718
|
-
if (stamps.updatedBy && stamped.updatedBy === undefined) {
|
|
719
|
-
stamped.updatedBy = actor;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
return parses ? definition.schema.parse(stamped) : stamped;
|
|
723
|
-
};
|
|
724
|
-
const toUpdate = (patch) => {
|
|
725
|
-
if (!isRecord3(patch)) {
|
|
726
|
-
throw new TypeError(`update: expected the document's fields or MongoDB's operators, not ${String(patch)}`);
|
|
727
|
-
}
|
|
728
|
-
const update = isUpdateFilter(patch) ? { ...patch } : {};
|
|
729
|
-
const set = isRecord3(update.$set) ? { ...update.$set } : {};
|
|
730
|
-
if (!isUpdateFilter(patch)) {
|
|
731
|
-
for (const [field, value] of Object.entries(patch)) {
|
|
732
|
-
if (value === undefined)
|
|
733
|
-
continue;
|
|
734
|
-
const schema = shape[field];
|
|
735
|
-
if (!schema) {
|
|
736
|
-
throw new TypeError(`update: "${name}" has no field "${field}" in its schema`);
|
|
737
|
-
}
|
|
738
|
-
set[field] = parses ? schema.parse(value) : value;
|
|
739
|
-
}
|
|
710
|
+
return document;
|
|
711
|
+
}
|
|
712
|
+
function toDocument(ctx, values) {
|
|
713
|
+
const stamped = { ...values };
|
|
714
|
+
if (!ctx.hasOwnId)
|
|
715
|
+
delete stamped.id;
|
|
716
|
+
if (ctx.actor !== undefined) {
|
|
717
|
+
if (ctx.stamps.createdBy && stamped.createdBy === undefined) {
|
|
718
|
+
stamped.createdBy = ctx.actor;
|
|
740
719
|
}
|
|
741
|
-
if (
|
|
742
|
-
|
|
743
|
-
if (actor !== undefined && stamps.updatedBy && set.updatedBy === undefined) {
|
|
744
|
-
set.updatedBy = actor;
|
|
720
|
+
if (ctx.stamps.updatedBy && stamped.updatedBy === undefined) {
|
|
721
|
+
stamped.updatedBy = ctx.actor;
|
|
745
722
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
723
|
+
}
|
|
724
|
+
return ctx.parses ? ctx.definition.schema.parse(stamped) : stamped;
|
|
725
|
+
}
|
|
726
|
+
function setFromFields(ctx, patch) {
|
|
727
|
+
const set = {};
|
|
728
|
+
for (const [field, value] of Object.entries(patch)) {
|
|
729
|
+
if (value === undefined)
|
|
730
|
+
continue;
|
|
731
|
+
const schema = ctx.shape[field];
|
|
732
|
+
if (!schema) {
|
|
733
|
+
throw new TypeError(`update: "${ctx.name}" has no field "${field}" in its schema`);
|
|
752
734
|
}
|
|
753
|
-
|
|
735
|
+
set[field] = ctx.parses ? schema.parse(value) : value;
|
|
736
|
+
}
|
|
737
|
+
return set;
|
|
738
|
+
}
|
|
739
|
+
function toUpdate(ctx, patch) {
|
|
740
|
+
if (!isRecord3(patch)) {
|
|
741
|
+
throw new TypeError(`update: expected the document's fields or MongoDB's operators, not ${String(patch)}`);
|
|
742
|
+
}
|
|
743
|
+
const operators = isUpdateFilter(patch);
|
|
744
|
+
const update = operators ? { ...patch } : {};
|
|
745
|
+
const set = {
|
|
746
|
+
...isRecord3(update.$set) ? update.$set : {},
|
|
747
|
+
...operators ? {} : setFromFields(ctx, patch)
|
|
754
748
|
};
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
749
|
+
if (ctx.touches && set.updatedAt === undefined)
|
|
750
|
+
set.updatedAt = new Date;
|
|
751
|
+
if (ctx.actor !== undefined && ctx.stamps.updatedBy && set.updatedBy === undefined) {
|
|
752
|
+
set.updatedBy = ctx.actor;
|
|
753
|
+
}
|
|
754
|
+
if (Object.keys(set).length > 0)
|
|
755
|
+
update.$set = set;
|
|
756
|
+
if (ctx.locks) {
|
|
757
|
+
const inc = isRecord3(update.$inc) ? { ...update.$inc } : {};
|
|
758
|
+
inc.version = inc.version ?? 1;
|
|
759
|
+
update.$inc = inc;
|
|
760
|
+
}
|
|
761
|
+
return update;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/collection/reads.ts
|
|
765
|
+
function findOne(ctx, filter, projection) {
|
|
766
|
+
return run(ctx, async () => {
|
|
767
|
+
const found = await ctx.collection.findOne(filter, {
|
|
768
|
+
...ctx.sessionOption,
|
|
758
769
|
...projection ? { projection } : {}
|
|
759
770
|
});
|
|
760
|
-
return found === null ? null : withId(found);
|
|
771
|
+
return found === null ? null : withId(ctx, found);
|
|
761
772
|
});
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
if (opts.sort !== undefined)
|
|
779
|
-
cursor = cursor.sort(opts.sort);
|
|
780
|
-
if (opts.skip !== undefined)
|
|
781
|
-
cursor = cursor.skip(opts.skip);
|
|
782
|
-
if (opts.limit !== undefined)
|
|
783
|
-
cursor = cursor.limit(opts.limit);
|
|
784
|
-
const found = await cursor.toArray();
|
|
785
|
-
return found.map((document) => withId(document));
|
|
773
|
+
}
|
|
774
|
+
async function findById(ctx, id, opts = {}) {
|
|
775
|
+
const found = await findOne(ctx, scoped(ctx, { _id: id }, opts.withDeleted));
|
|
776
|
+
return found ?? undefined;
|
|
777
|
+
}
|
|
778
|
+
async function getById(ctx, id, opts = {}) {
|
|
779
|
+
const found = await findById(ctx, id, opts);
|
|
780
|
+
if (!found)
|
|
781
|
+
throw notFound(ctx, id);
|
|
782
|
+
return found;
|
|
783
|
+
}
|
|
784
|
+
async function findMany(ctx, opts = {}) {
|
|
785
|
+
return run(ctx, async () => {
|
|
786
|
+
let cursor = ctx.collection.find(scoped(ctx, opts.filter, opts.withDeleted), {
|
|
787
|
+
...ctx.sessionOption,
|
|
788
|
+
...opts.projection ? { projection: opts.projection } : {}
|
|
786
789
|
});
|
|
790
|
+
if (opts.sort !== undefined)
|
|
791
|
+
cursor = cursor.sort(opts.sort);
|
|
792
|
+
if (opts.skip !== undefined)
|
|
793
|
+
cursor = cursor.skip(opts.skip);
|
|
794
|
+
if (opts.limit !== undefined)
|
|
795
|
+
cursor = cursor.limit(opts.limit);
|
|
796
|
+
const found = await cursor.toArray();
|
|
797
|
+
return found.map((document) => withId(ctx, document));
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
async function findFirst(ctx, filter, opts = {}) {
|
|
801
|
+
const [first] = await findMany(ctx, { ...opts, filter, limit: 1 });
|
|
802
|
+
return first;
|
|
803
|
+
}
|
|
804
|
+
async function countDocuments(ctx, filter, opts = {}) {
|
|
805
|
+
return run(ctx, async () => ctx.collection.countDocuments(scoped(ctx, filter, opts.withDeleted), {
|
|
806
|
+
...ctx.sessionOption
|
|
807
|
+
}));
|
|
808
|
+
}
|
|
809
|
+
async function exists(ctx, filter, opts = {}) {
|
|
810
|
+
const found = await findOne(ctx, scoped(ctx, filter, opts.withDeleted), {
|
|
811
|
+
_id: 1
|
|
812
|
+
});
|
|
813
|
+
return found !== null && found !== undefined;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/collection/paginate.ts
|
|
817
|
+
async function paginate(ctx, opts = {}) {
|
|
818
|
+
const window = pageWindow(opts, ctx.maxPageSize);
|
|
819
|
+
const [items, total] = await Promise.all([
|
|
820
|
+
findMany(ctx, {
|
|
821
|
+
filter: opts.filter,
|
|
822
|
+
sort: opts.sort ?? { _id: 1 },
|
|
823
|
+
limit: window.limit,
|
|
824
|
+
skip: window.skip,
|
|
825
|
+
withDeleted: opts.withDeleted
|
|
826
|
+
}),
|
|
827
|
+
countDocuments(ctx, opts.filter, {
|
|
828
|
+
withDeleted: opts.withDeleted
|
|
829
|
+
})
|
|
830
|
+
]);
|
|
831
|
+
return toPage(items, total, window);
|
|
832
|
+
}
|
|
833
|
+
async function paginateByCursor(ctx, opts = {}) {
|
|
834
|
+
const sortField = opts.orderBy ?? "_id";
|
|
835
|
+
if (!ctx.shape[sortField] && sortField !== "_id") {
|
|
836
|
+
throw new TypeError(`paginateByCursor: "${ctx.name}" has no field "${sortField}" in its schema`);
|
|
787
837
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
if (updated)
|
|
799
|
-
return withId(updated);
|
|
800
|
-
if (expectedVersion !== undefined) {
|
|
801
|
-
const current = await findOne({ _id: id });
|
|
802
|
-
if (current) {
|
|
803
|
-
throw new OptimisticLockError(`Document ${String(id)} of "${name}" is at version ${String(current.version)}, not ${expectedVersion}: it changed since it was read`, {
|
|
804
|
-
collection: name,
|
|
805
|
-
id,
|
|
806
|
-
expectedVersion,
|
|
807
|
-
actualVersion: typeof current.version === "number" ? current.version : undefined
|
|
808
|
-
});
|
|
809
|
-
}
|
|
838
|
+
const direction = opts.direction ?? "asc";
|
|
839
|
+
const fields = sortField === "_id" ? ["_id"] : [sortField, "_id"];
|
|
840
|
+
const cursorKey = `${sortField}:${direction}`;
|
|
841
|
+
const limit = cursorLimit(opts.limit, ctx.maxPageSize);
|
|
842
|
+
const past = direction === "asc" ? "$gt" : "$lt";
|
|
843
|
+
let after;
|
|
844
|
+
if (opts.after) {
|
|
845
|
+
const { values } = decodeCursor(opts.after, cursorKey);
|
|
846
|
+
if (values.length !== fields.length) {
|
|
847
|
+
throw new DataError(`Invalid cursor: expected ${fields.length} value(s), got ${values.length}`, { collection: ctx.name });
|
|
810
848
|
}
|
|
811
|
-
|
|
849
|
+
after = {
|
|
850
|
+
$or: fields.map((field, index) => ({
|
|
851
|
+
...Object.fromEntries(fields.slice(0, index).map((previous, i) => [previous, values[i]])),
|
|
852
|
+
[field]: { [past]: values[index] }
|
|
853
|
+
}))
|
|
854
|
+
};
|
|
812
855
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
856
|
+
const sort = Object.fromEntries(fields.map((field) => [field, direction === "asc" ? 1 : -1]));
|
|
857
|
+
const documents = await findMany(ctx, {
|
|
858
|
+
filter: mergeFilters(isRecord3(opts.filter) ? opts.filter : undefined, after),
|
|
859
|
+
sort,
|
|
860
|
+
limit: limit + 1,
|
|
861
|
+
withDeleted: opts.withDeleted
|
|
862
|
+
});
|
|
863
|
+
const items = documents.slice(0, limit);
|
|
864
|
+
const last = items.at(-1);
|
|
865
|
+
if (documents.length <= limit || !last) {
|
|
866
|
+
return { items, nextCursor: null };
|
|
818
867
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
868
|
+
const values = fields.map((field) => {
|
|
869
|
+
const value = last[field];
|
|
870
|
+
if (value === null || value === undefined) {
|
|
871
|
+
throw new TypeError(`paginateByCursor: "${field}" is null in a document of "${ctx.name}". ` + "Page along a field every document has.");
|
|
872
|
+
}
|
|
873
|
+
return value;
|
|
874
|
+
});
|
|
875
|
+
return { items, nextCursor: encodeCursor({ key: cursorKey, values }) };
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/collection/writes.ts
|
|
879
|
+
async function updatedOrThrow(ctx, id, filter, update, expectedVersion) {
|
|
880
|
+
const updated = await run(ctx, async () => ctx.collection.findOneAndUpdate(filter, update, {
|
|
881
|
+
...ctx.sessionOption,
|
|
882
|
+
returnDocument: "after"
|
|
883
|
+
}));
|
|
884
|
+
if (updated)
|
|
885
|
+
return withId(ctx, updated);
|
|
886
|
+
if (expectedVersion !== undefined) {
|
|
887
|
+
const current = await findOne(ctx, { _id: id });
|
|
888
|
+
if (current) {
|
|
889
|
+
throw new OptimisticLockError(`Document ${String(id)} of "${ctx.name}" is at version ${String(current.version)}, not ${expectedVersion}: it changed since it was read`, {
|
|
890
|
+
collection: ctx.name,
|
|
891
|
+
id,
|
|
892
|
+
expectedVersion,
|
|
893
|
+
actualVersion: typeof current.version === "number" ? current.version : undefined
|
|
824
894
|
});
|
|
825
|
-
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
throw notFound(ctx, id);
|
|
898
|
+
}
|
|
899
|
+
function softDeleteUpdate(ctx) {
|
|
900
|
+
const set = { deletedAt: new Date };
|
|
901
|
+
if (ctx.actor !== undefined && ctx.stamps.deletedBy) {
|
|
902
|
+
set.deletedBy = ctx.actor;
|
|
903
|
+
}
|
|
904
|
+
const update = { $set: set };
|
|
905
|
+
if (ctx.locks)
|
|
906
|
+
update.$inc = { version: 1 };
|
|
907
|
+
return update;
|
|
908
|
+
}
|
|
909
|
+
async function create(ctx, values) {
|
|
910
|
+
const document = toDocument(ctx, values);
|
|
911
|
+
return run(ctx, async () => {
|
|
912
|
+
await ctx.collection.insertOne(document, {
|
|
913
|
+
...ctx.sessionOption
|
|
914
|
+
});
|
|
915
|
+
return withId(ctx, document);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
async function createMany(ctx, values) {
|
|
919
|
+
if (values.length === 0)
|
|
920
|
+
return [];
|
|
921
|
+
const documents = values.map((value) => toDocument(ctx, value));
|
|
922
|
+
return run(ctx, async () => {
|
|
923
|
+
await ctx.collection.insertMany(documents, {
|
|
924
|
+
...ctx.sessionOption
|
|
826
925
|
});
|
|
926
|
+
return documents.map((document) => withId(ctx, document));
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
async function update(ctx, id, patch, opts = {}) {
|
|
930
|
+
const expectedVersion = opts.expectedVersion;
|
|
931
|
+
if (expectedVersion !== undefined && !ctx.locks) {
|
|
932
|
+
throw new TypeError(`update: expectedVersion needs a "version" field, and "${ctx.name}" has none`);
|
|
827
933
|
}
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
},
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
count: countDocuments,
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
},
|
|
932
|
-
async paginate(opts = {}) {
|
|
933
|
-
const window = pageWindow(opts, maxPageSize);
|
|
934
|
-
const [items, total] = await Promise.all([
|
|
935
|
-
findMany({
|
|
936
|
-
filter: opts.filter,
|
|
937
|
-
sort: opts.sort ?? { _id: 1 },
|
|
938
|
-
limit: window.limit,
|
|
939
|
-
skip: window.skip,
|
|
940
|
-
withDeleted: opts.withDeleted
|
|
941
|
-
}),
|
|
942
|
-
countDocuments(opts.filter, {
|
|
943
|
-
withDeleted: opts.withDeleted
|
|
944
|
-
})
|
|
945
|
-
]);
|
|
946
|
-
return toPage(items, total, window);
|
|
947
|
-
},
|
|
948
|
-
async paginateByCursor(opts = {}) {
|
|
949
|
-
const sortField = opts.orderBy ?? "_id";
|
|
950
|
-
if (!shape[sortField] && sortField !== "_id") {
|
|
951
|
-
throw new TypeError(`paginateByCursor: "${name}" has no field "${sortField}" in its schema`);
|
|
952
|
-
}
|
|
953
|
-
const direction = opts.direction ?? "asc";
|
|
954
|
-
const fields = sortField === "_id" ? ["_id"] : [sortField, "_id"];
|
|
955
|
-
const cursorKey = `${sortField}:${direction}`;
|
|
956
|
-
const limit = cursorLimit(opts.limit, maxPageSize);
|
|
957
|
-
const past = direction === "asc" ? "$gt" : "$lt";
|
|
958
|
-
let after;
|
|
959
|
-
if (opts.after) {
|
|
960
|
-
const { values } = decodeCursor(opts.after, cursorKey);
|
|
961
|
-
if (values.length !== fields.length) {
|
|
962
|
-
throw new DataError(`Invalid cursor: expected ${fields.length} value(s), got ${values.length}`, { collection: name });
|
|
963
|
-
}
|
|
964
|
-
after = {
|
|
965
|
-
$or: fields.map((field, index) => ({
|
|
966
|
-
...Object.fromEntries(fields.slice(0, index).map((previous, i) => [previous, values[i]])),
|
|
967
|
-
[field]: { [past]: values[index] }
|
|
968
|
-
}))
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
const sort = Object.fromEntries(fields.map((field) => [field, direction === "asc" ? 1 : -1]));
|
|
972
|
-
const documents = await findMany({
|
|
973
|
-
filter: mergeFilters(isRecord3(opts.filter) ? opts.filter : undefined, after),
|
|
974
|
-
sort,
|
|
975
|
-
limit: limit + 1,
|
|
976
|
-
withDeleted: opts.withDeleted
|
|
977
|
-
});
|
|
978
|
-
const items = documents.slice(0, limit);
|
|
979
|
-
const last = items.at(-1);
|
|
980
|
-
if (documents.length <= limit || !last) {
|
|
981
|
-
return { items, nextCursor: null };
|
|
982
|
-
}
|
|
983
|
-
const values = fields.map((field) => {
|
|
984
|
-
const value = last[field];
|
|
985
|
-
if (value === null || value === undefined) {
|
|
986
|
-
throw new TypeError(`paginateByCursor: "${field}" is null in a document of "${name}". ` + "Page along a field every document has.");
|
|
987
|
-
}
|
|
988
|
-
return value;
|
|
989
|
-
});
|
|
990
|
-
return { items, nextCursor: encodeCursor({ key: cursorKey, values }) };
|
|
991
|
-
}
|
|
934
|
+
const patched = toUpdate(ctx, patch);
|
|
935
|
+
const filter = mergeFilters({
|
|
936
|
+
_id: id,
|
|
937
|
+
...expectedVersion === undefined ? {} : { version: expectedVersion }
|
|
938
|
+
}, live(ctx));
|
|
939
|
+
return updatedOrThrow(ctx, id, filter, patched, expectedVersion);
|
|
940
|
+
}
|
|
941
|
+
async function updateMany(ctx, filter, patch) {
|
|
942
|
+
requireFilter(ctx, "updateMany", filter);
|
|
943
|
+
const patched = toUpdate(ctx, patch);
|
|
944
|
+
return run(ctx, async () => {
|
|
945
|
+
const result = await ctx.collection.updateMany(scoped(ctx, filter), patched, { ...ctx.sessionOption });
|
|
946
|
+
return result.modifiedCount;
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
async function hardDelete(ctx, id) {
|
|
950
|
+
const deleted = await run(ctx, async () => ctx.collection.findOneAndDelete({ _id: id }, { ...ctx.sessionOption }));
|
|
951
|
+
if (!deleted)
|
|
952
|
+
throw notFound(ctx, id);
|
|
953
|
+
return withId(ctx, deleted);
|
|
954
|
+
}
|
|
955
|
+
async function hardDeleteMany(ctx, filter) {
|
|
956
|
+
requireFilter(ctx, "hardDeleteMany", filter);
|
|
957
|
+
return run(ctx, async () => {
|
|
958
|
+
const result = await ctx.collection.deleteMany(filter, {
|
|
959
|
+
...ctx.sessionOption
|
|
960
|
+
});
|
|
961
|
+
return result.deletedCount;
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
async function deleteOne(ctx, id) {
|
|
965
|
+
if (!ctx.softDeletes)
|
|
966
|
+
return hardDelete(ctx, id);
|
|
967
|
+
return updatedOrThrow(ctx, id, mergeFilters({ _id: id }, live(ctx)), softDeleteUpdate(ctx), undefined);
|
|
968
|
+
}
|
|
969
|
+
async function deleteMany(ctx, filter) {
|
|
970
|
+
requireFilter(ctx, "deleteMany", filter);
|
|
971
|
+
if (!ctx.softDeletes)
|
|
972
|
+
return hardDeleteMany(ctx, filter);
|
|
973
|
+
return run(ctx, async () => {
|
|
974
|
+
const result = await ctx.collection.updateMany(scoped(ctx, filter), softDeleteUpdate(ctx), { ...ctx.sessionOption });
|
|
975
|
+
return result.modifiedCount;
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
async function restore(ctx, id) {
|
|
979
|
+
if (!ctx.stamps.deletedAt) {
|
|
980
|
+
throw new TypeError(`restore: "${ctx.name}" has no soft delete`);
|
|
981
|
+
}
|
|
982
|
+
const set = { deletedAt: null };
|
|
983
|
+
if (ctx.stamps.deletedBy)
|
|
984
|
+
set.deletedBy = null;
|
|
985
|
+
if (ctx.touches)
|
|
986
|
+
set.updatedAt = new Date;
|
|
987
|
+
const update = { $set: set };
|
|
988
|
+
if (ctx.locks)
|
|
989
|
+
update.$inc = { version: 1 };
|
|
990
|
+
return updatedOrThrow(ctx, id, { _id: id }, update, undefined);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/collection/get-collection.ts
|
|
994
|
+
function databaseOf(source, name) {
|
|
995
|
+
const client = source;
|
|
996
|
+
if (typeof client.db === "function")
|
|
997
|
+
return client.db(name);
|
|
998
|
+
const db = source;
|
|
999
|
+
if (name !== undefined && db.databaseName !== name) {
|
|
1000
|
+
throw new TypeError(`getCollection: given a Db for "${db.databaseName}", and a db option of ` + `"${name}". Pass the client, or the database you mean.`);
|
|
1001
|
+
}
|
|
1002
|
+
return db;
|
|
1003
|
+
}
|
|
1004
|
+
function getCollection(source, definition, options = {}) {
|
|
1005
|
+
const db = databaseOf(source, options.db);
|
|
1006
|
+
return build(db, definition, options);
|
|
1007
|
+
}
|
|
1008
|
+
function apiOf(ctx, rebuild) {
|
|
1009
|
+
return {
|
|
1010
|
+
definition: ctx.definition,
|
|
1011
|
+
db: ctx.db,
|
|
1012
|
+
raw: ctx.collection,
|
|
1013
|
+
session: ctx.session,
|
|
1014
|
+
withSession: (other) => rebuild({ session: other }),
|
|
1015
|
+
as: (who) => rebuild({ actor: who }),
|
|
1016
|
+
sync: (syncOptions = {}) => syncCollection(ctx.db, ctx.definition, {
|
|
1017
|
+
...ctx.sessionOption,
|
|
1018
|
+
...syncOptions
|
|
1019
|
+
}),
|
|
1020
|
+
findById: (id, opts) => findById(ctx, id, opts),
|
|
1021
|
+
getById: (id, opts) => getById(ctx, id, opts),
|
|
1022
|
+
findFirst: (filter, opts) => findFirst(ctx, filter, opts),
|
|
1023
|
+
findMany: (opts) => findMany(ctx, opts),
|
|
1024
|
+
create: (values) => create(ctx, values),
|
|
1025
|
+
createMany: (values) => createMany(ctx, values),
|
|
1026
|
+
update: (id, patch, opts) => update(ctx, id, patch, opts),
|
|
1027
|
+
updateMany: (filter, patch) => updateMany(ctx, filter, patch),
|
|
1028
|
+
delete: (id) => deleteOne(ctx, id),
|
|
1029
|
+
deleteMany: (filter) => deleteMany(ctx, filter),
|
|
1030
|
+
hardDelete: (id) => hardDelete(ctx, id),
|
|
1031
|
+
hardDeleteMany: (filter) => hardDeleteMany(ctx, filter),
|
|
1032
|
+
restore: (id) => restore(ctx, id),
|
|
1033
|
+
count: (filter, opts) => countDocuments(ctx, filter, opts),
|
|
1034
|
+
exists: (filter, opts) => exists(ctx, filter, opts),
|
|
1035
|
+
paginate: (opts) => paginate(ctx, opts),
|
|
1036
|
+
paginateByCursor: (opts) => paginateByCursor(ctx, opts)
|
|
992
1037
|
};
|
|
1038
|
+
}
|
|
1039
|
+
function build(db, definition, options) {
|
|
1040
|
+
const ctx = createContext(db, definition, options);
|
|
1041
|
+
const rebuild = (changed) => build(db, definition, { ...options, ...changed });
|
|
1042
|
+
const api = apiOf(ctx, rebuild);
|
|
1043
|
+
const collection = ctx.collection;
|
|
993
1044
|
return new Proxy(api, {
|
|
994
1045
|
get(target, key, receiver) {
|
|
995
1046
|
if (Reflect.has(target, key))
|
|
@@ -1157,5 +1208,5 @@ export {
|
|
|
1157
1208
|
withTransaction
|
|
1158
1209
|
};
|
|
1159
1210
|
|
|
1160
|
-
//# debugId=
|
|
1211
|
+
//# debugId=AC287DF6F57068F864756E2164756E21
|
|
1161
1212
|
//# sourceMappingURL=index.js.map
|