@nxgt/mongo 0.1.0 → 0.3.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 +149 -29
- package/dist/collection/get-collection.d.ts +26 -0
- package/dist/collection/get-collection.d.ts.map +1 -0
- package/dist/collection/types.d.ts +270 -0
- package/dist/collection/types.d.ts.map +1 -0
- package/dist/definition/define-collection.d.ts +37 -5
- package/dist/definition/define-collection.d.ts.map +1 -1
- package/dist/definition/fields.d.ts +5 -5
- package/dist/definition/fields.d.ts.map +1 -1
- package/dist/definition/object-id.d.ts +51 -0
- package/dist/definition/object-id.d.ts.map +1 -0
- package/dist/errors/data-error.d.ts +13 -1
- package/dist/errors/data-error.d.ts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +292 -188
- package/dist/index.js.map +10 -9
- package/dist/transaction/with-transaction.d.ts +4 -4
- package/package.json +1 -1
- package/dist/repository/create-repository.d.ts +0 -21
- package/dist/repository/create-repository.d.ts.map +0 -1
- package/dist/repository/types.d.ts +0 -145
- package/dist/repository/types.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -25,162 +25,7 @@ function stampsOf(definition) {
|
|
|
25
25
|
deletedBy: has("deletedBy")
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
import { ObjectId } from "mongodb";
|
|
30
|
-
import { z } from "zod";
|
|
31
|
-
function isObjectId(value) {
|
|
32
|
-
return typeof value === "object" && value !== null && value._bsontype === "ObjectId";
|
|
33
|
-
}
|
|
34
|
-
function objectId() {
|
|
35
|
-
return z.custom(isObjectId, { error: "must be an ObjectId" }).meta({ bsonType: "objectId" });
|
|
36
|
-
}
|
|
37
|
-
function id() {
|
|
38
|
-
return objectId().default(() => new ObjectId);
|
|
39
|
-
}
|
|
40
|
-
function timestamps() {
|
|
41
|
-
return {
|
|
42
|
-
createdAt: z.date().default(() => new Date),
|
|
43
|
-
updatedAt: z.date().default(() => new Date)
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function softDelete() {
|
|
47
|
-
return { deletedAt: z.date().nullable().default(null) };
|
|
48
|
-
}
|
|
49
|
-
function optimisticLock() {
|
|
50
|
-
return { version: z.int().nonnegative().default(0) };
|
|
51
|
-
}
|
|
52
|
-
function actors(actor = objectId()) {
|
|
53
|
-
return {
|
|
54
|
-
createdBy: actor.nullable().default(null),
|
|
55
|
-
updatedBy: actor.nullable().default(null),
|
|
56
|
-
deletedBy: actor.nullable().default(null)
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
var STAMP_FIELDS = {
|
|
60
|
-
id: "_id",
|
|
61
|
-
createdAt: "createdAt",
|
|
62
|
-
updatedAt: "updatedAt",
|
|
63
|
-
deletedAt: "deletedAt",
|
|
64
|
-
version: "version",
|
|
65
|
-
createdBy: "createdBy",
|
|
66
|
-
updatedBy: "updatedBy",
|
|
67
|
-
deletedBy: "deletedBy"
|
|
68
|
-
};
|
|
69
|
-
// src/definition/json-schema.ts
|
|
70
|
-
import { z as z2 } from "zod";
|
|
71
|
-
var MONGO_JSON_SCHEMA_KEYWORDS = new Set([
|
|
72
|
-
"additionalItems",
|
|
73
|
-
"additionalProperties",
|
|
74
|
-
"allOf",
|
|
75
|
-
"anyOf",
|
|
76
|
-
"bsonType",
|
|
77
|
-
"dependencies",
|
|
78
|
-
"description",
|
|
79
|
-
"enum",
|
|
80
|
-
"exclusiveMaximum",
|
|
81
|
-
"exclusiveMinimum",
|
|
82
|
-
"items",
|
|
83
|
-
"maxItems",
|
|
84
|
-
"maxLength",
|
|
85
|
-
"maxProperties",
|
|
86
|
-
"maximum",
|
|
87
|
-
"minItems",
|
|
88
|
-
"minLength",
|
|
89
|
-
"minProperties",
|
|
90
|
-
"minimum",
|
|
91
|
-
"multipleOf",
|
|
92
|
-
"not",
|
|
93
|
-
"oneOf",
|
|
94
|
-
"pattern",
|
|
95
|
-
"patternProperties",
|
|
96
|
-
"properties",
|
|
97
|
-
"required",
|
|
98
|
-
"title",
|
|
99
|
-
"type",
|
|
100
|
-
"uniqueItems"
|
|
101
|
-
]);
|
|
102
|
-
var SCHEMA_MAPS = new Set([
|
|
103
|
-
"properties",
|
|
104
|
-
"patternProperties",
|
|
105
|
-
"dependencies"
|
|
106
|
-
]);
|
|
107
|
-
function isRecord(value) {
|
|
108
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
109
|
-
}
|
|
110
|
-
var INTEGER_BSON_TYPES = ["int", "long", "double"];
|
|
111
|
-
function convertIntegerType(node) {
|
|
112
|
-
const type = node.type;
|
|
113
|
-
if (type === "integer") {
|
|
114
|
-
delete node.type;
|
|
115
|
-
node.bsonType = [...INTEGER_BSON_TYPES];
|
|
116
|
-
node.multipleOf ??= 1;
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (Array.isArray(type) && type.includes("integer")) {
|
|
120
|
-
delete node.type;
|
|
121
|
-
node.bsonType = [
|
|
122
|
-
...type.filter((one) => one !== "integer"),
|
|
123
|
-
...INTEGER_BSON_TYPES
|
|
124
|
-
];
|
|
125
|
-
node.multipleOf ??= 1;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
function refName(ref) {
|
|
129
|
-
return ref.replace(/^#\/(definitions|\$defs)\//, "");
|
|
130
|
-
}
|
|
131
|
-
function inline(value, defs, stack) {
|
|
132
|
-
if (Array.isArray(value)) {
|
|
133
|
-
return value.map((one) => inline(one, defs, stack));
|
|
134
|
-
}
|
|
135
|
-
if (!isRecord(value))
|
|
136
|
-
return value;
|
|
137
|
-
if (typeof value.$ref === "string") {
|
|
138
|
-
const name = refName(value.$ref);
|
|
139
|
-
if (stack.includes(name)) {
|
|
140
|
-
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.");
|
|
141
|
-
}
|
|
142
|
-
const target = defs[name];
|
|
143
|
-
if (!isRecord(target)) {
|
|
144
|
-
throw new TypeError(`toMongoJsonSchema: cannot resolve ${value.$ref}, which zod emitted`);
|
|
145
|
-
}
|
|
146
|
-
const { $ref: _ref, ...siblings } = value;
|
|
147
|
-
return {
|
|
148
|
-
...inline(target, defs, [...stack, name]),
|
|
149
|
-
...inline(siblings, defs, stack)
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
const out = {};
|
|
153
|
-
for (const [key, inner] of Object.entries(value)) {
|
|
154
|
-
if (!MONGO_JSON_SCHEMA_KEYWORDS.has(key))
|
|
155
|
-
continue;
|
|
156
|
-
if (SCHEMA_MAPS.has(key) && isRecord(inner)) {
|
|
157
|
-
const mapped = {};
|
|
158
|
-
for (const [name, schema] of Object.entries(inner)) {
|
|
159
|
-
mapped[name] = inline(schema, defs, stack);
|
|
160
|
-
}
|
|
161
|
-
out[key] = mapped;
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
out[key] = inline(inner, defs, stack);
|
|
165
|
-
}
|
|
166
|
-
convertIntegerType(out);
|
|
167
|
-
return out;
|
|
168
|
-
}
|
|
169
|
-
function toMongoJsonSchema(schema) {
|
|
170
|
-
const json = z2.toJSONSchema(schema, {
|
|
171
|
-
target: "draft-4",
|
|
172
|
-
io: "output",
|
|
173
|
-
unrepresentable: "any",
|
|
174
|
-
override: (ctx) => {
|
|
175
|
-
const type = ctx.zodSchema._zod.def.type;
|
|
176
|
-
if (type === "date" && ctx.jsonSchema.bsonType === undefined) {
|
|
177
|
-
ctx.jsonSchema.bsonType = "date";
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
const definitions = isRecord(json.definitions) ? json.definitions : isRecord(json.$defs) ? json.$defs : {};
|
|
182
|
-
return inline(json, definitions, []);
|
|
183
|
-
}
|
|
28
|
+
|
|
184
29
|
// src/errors/data-error.ts
|
|
185
30
|
class DataError extends Error {
|
|
186
31
|
constructor(message = "Database error", options = {}) {
|
|
@@ -232,6 +77,14 @@ class OptimisticLockError extends DataError {
|
|
|
232
77
|
}
|
|
233
78
|
}
|
|
234
79
|
|
|
80
|
+
class InvalidIdError extends DataError {
|
|
81
|
+
constructor(message = "Invalid id", options = {}) {
|
|
82
|
+
super(message, options);
|
|
83
|
+
this.name = "InvalidIdError";
|
|
84
|
+
this.code = "INVALID_ID";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
235
88
|
class InvalidCursorError extends DataError {
|
|
236
89
|
constructor(message = "Invalid cursor", options = {}) {
|
|
237
90
|
super(message, options);
|
|
@@ -239,8 +92,9 @@ class InvalidCursorError extends DataError {
|
|
|
239
92
|
this.code = "INVALID_CURSOR";
|
|
240
93
|
}
|
|
241
94
|
}
|
|
95
|
+
|
|
242
96
|
// src/errors/to-data-error.ts
|
|
243
|
-
function
|
|
97
|
+
function isRecord(value) {
|
|
244
98
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
245
99
|
}
|
|
246
100
|
function asArray(value) {
|
|
@@ -256,8 +110,8 @@ function indexFromMessage(message) {
|
|
|
256
110
|
}
|
|
257
111
|
function keysOfDuplicate(error) {
|
|
258
112
|
const pattern = error.keyPattern;
|
|
259
|
-
if (
|
|
260
|
-
const values =
|
|
113
|
+
if (isRecord(pattern)) {
|
|
114
|
+
const values = isRecord(error.keyValue) ? error.keyValue : undefined;
|
|
261
115
|
return { keys: Object.keys(pattern), values };
|
|
262
116
|
}
|
|
263
117
|
const inMessage = text(error.errmsg)?.match(/dup key:\s*\{([^}]*)\}/)?.[1];
|
|
@@ -268,8 +122,8 @@ function keysOfDuplicate(error) {
|
|
|
268
122
|
}
|
|
269
123
|
function firstWriteError(error) {
|
|
270
124
|
for (const write of asArray(error.writeErrors)) {
|
|
271
|
-
const inner =
|
|
272
|
-
if (
|
|
125
|
+
const inner = isRecord(write) && isRecord(write.err) ? write.err : write;
|
|
126
|
+
if (isRecord(inner))
|
|
273
127
|
return inner;
|
|
274
128
|
}
|
|
275
129
|
return;
|
|
@@ -277,11 +131,11 @@ function firstWriteError(error) {
|
|
|
277
131
|
function issuesOf(details, path = []) {
|
|
278
132
|
const issues = [];
|
|
279
133
|
for (const rule of asArray(details)) {
|
|
280
|
-
if (!
|
|
134
|
+
if (!isRecord(rule))
|
|
281
135
|
continue;
|
|
282
136
|
if (rule.propertiesNotSatisfied !== undefined) {
|
|
283
137
|
for (const property of asArray(rule.propertiesNotSatisfied)) {
|
|
284
|
-
if (!
|
|
138
|
+
if (!isRecord(property))
|
|
285
139
|
continue;
|
|
286
140
|
const name = text(property.propertyName) ?? "";
|
|
287
141
|
const nested = issuesOf(property.details, [...path, name]);
|
|
@@ -317,7 +171,7 @@ function issuesOf(details, path = []) {
|
|
|
317
171
|
function toDataError(error, context = {}) {
|
|
318
172
|
if (error instanceof DataError)
|
|
319
173
|
return error;
|
|
320
|
-
if (!
|
|
174
|
+
if (!isRecord(error))
|
|
321
175
|
return error;
|
|
322
176
|
const source = firstWriteError(error) ?? error;
|
|
323
177
|
const code = typeof source.code === "number" ? source.code : typeof error.code === "number" ? error.code : undefined;
|
|
@@ -337,15 +191,16 @@ function toDataError(error, context = {}) {
|
|
|
337
191
|
return new ConflictError(`Duplicate key on ${named}${context.collection ? ` in "${context.collection}"` : ""}`, { ...common, index, keys, values });
|
|
338
192
|
}
|
|
339
193
|
if (code === 121) {
|
|
340
|
-
const errInfo =
|
|
194
|
+
const errInfo = isRecord(source.errInfo) ? source.errInfo : undefined;
|
|
341
195
|
const issues = issuesOf(errInfo?.details);
|
|
342
196
|
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) });
|
|
343
197
|
}
|
|
344
198
|
return new DataError(message || `MongoDB error ${code}`, common);
|
|
345
199
|
}
|
|
200
|
+
|
|
346
201
|
// src/pagination/cursor.ts
|
|
347
|
-
import { ObjectId
|
|
348
|
-
function
|
|
202
|
+
import { ObjectId } from "mongodb";
|
|
203
|
+
function isObjectId(value) {
|
|
349
204
|
return typeof value === "object" && value !== null && value._bsontype === "ObjectId";
|
|
350
205
|
}
|
|
351
206
|
function replacer(key, value) {
|
|
@@ -354,7 +209,7 @@ function replacer(key, value) {
|
|
|
354
209
|
return { $date: raw.toISOString() };
|
|
355
210
|
if (typeof raw === "bigint")
|
|
356
211
|
return { $bigint: raw.toString() };
|
|
357
|
-
if (
|
|
212
|
+
if (isObjectId(raw))
|
|
358
213
|
return { $oid: raw.toHexString() };
|
|
359
214
|
return value;
|
|
360
215
|
}
|
|
@@ -368,7 +223,7 @@ function reviver(_key, value) {
|
|
|
368
223
|
if (typeof tagged.$bigint === "string")
|
|
369
224
|
return BigInt(tagged.$bigint);
|
|
370
225
|
if (typeof tagged.$oid === "string")
|
|
371
|
-
return new
|
|
226
|
+
return new ObjectId(tagged.$oid);
|
|
372
227
|
}
|
|
373
228
|
}
|
|
374
229
|
return value;
|
|
@@ -406,6 +261,7 @@ function decodeCursor(cursor, expectedKey) {
|
|
|
406
261
|
}
|
|
407
262
|
return { key, values };
|
|
408
263
|
}
|
|
264
|
+
|
|
409
265
|
// src/pagination/page.ts
|
|
410
266
|
var DEFAULT_PAGE_SIZE = 20;
|
|
411
267
|
var DEFAULT_MAX_PAGE_SIZE = 100;
|
|
@@ -432,6 +288,123 @@ function toPage(items, total, window) {
|
|
|
432
288
|
function cursorLimit(limit, maxPageSize = DEFAULT_MAX_PAGE_SIZE) {
|
|
433
289
|
return Math.min(positiveInteger("limit", limit ?? DEFAULT_PAGE_SIZE), maxPageSize);
|
|
434
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
|
+
|
|
435
408
|
// src/sync/index-diff.ts
|
|
436
409
|
var COLLATION_DEFAULTS = {
|
|
437
410
|
caseLevel: false,
|
|
@@ -655,7 +628,7 @@ async function syncCollections(db, definitions, options = {}) {
|
|
|
655
628
|
return reports;
|
|
656
629
|
}
|
|
657
630
|
|
|
658
|
-
// src/
|
|
631
|
+
// src/collection/get-collection.ts
|
|
659
632
|
function isRecord3(value) {
|
|
660
633
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
661
634
|
}
|
|
@@ -671,13 +644,25 @@ function mergeFilters(a, b) {
|
|
|
671
644
|
return left;
|
|
672
645
|
return { $and: [left, right] };
|
|
673
646
|
}
|
|
674
|
-
function
|
|
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.`);
|
|
654
|
+
}
|
|
655
|
+
return db;
|
|
656
|
+
}
|
|
657
|
+
function getCollection(source, definition, options = {}) {
|
|
658
|
+
const db = databaseOf(source, options.db);
|
|
675
659
|
return build(db, definition, options);
|
|
676
660
|
}
|
|
677
661
|
function build(db, definition, options) {
|
|
678
662
|
const name = definition.name;
|
|
679
663
|
const collection = db.collection(name);
|
|
680
664
|
const shape = definition.schema.shape;
|
|
665
|
+
const hasOwnId = "id" in shape;
|
|
681
666
|
const stamps = stampsOf(definition);
|
|
682
667
|
const session = options.session;
|
|
683
668
|
const actor = options.actor;
|
|
@@ -687,10 +672,10 @@ function build(db, definition, options) {
|
|
|
687
672
|
const touches = options.touchUpdatedAt ?? stamps.updatedAt;
|
|
688
673
|
const locks = options.optimisticLock ?? stamps.version;
|
|
689
674
|
if (options.softDelete === true && !stamps.deletedAt) {
|
|
690
|
-
throw new TypeError(`
|
|
675
|
+
throw new TypeError(`getCollection: softDelete needs a "deletedAt" field, and "${name}" has none`);
|
|
691
676
|
}
|
|
692
677
|
if (options.optimisticLock === true && !stamps.version) {
|
|
693
|
-
throw new TypeError(`
|
|
678
|
+
throw new TypeError(`getCollection: optimisticLock needs a "version" field, and "${name}" has none`);
|
|
694
679
|
}
|
|
695
680
|
const run = async (fn) => {
|
|
696
681
|
try {
|
|
@@ -702,6 +687,17 @@ function build(db, definition, options) {
|
|
|
702
687
|
const sessionOption = session ? { session } : {};
|
|
703
688
|
const live = (withDeleted) => softDeletes && !withDeleted ? { deletedAt: null } : undefined;
|
|
704
689
|
const scoped = (filter, withDeleted) => mergeFilters(isRecord3(filter) ? filter : undefined, live(withDeleted));
|
|
690
|
+
const withId = (document) => {
|
|
691
|
+
if (hasOwnId || !isRecord3(document) || document._id === undefined || Object.hasOwn(document, "id")) {
|
|
692
|
+
return document;
|
|
693
|
+
}
|
|
694
|
+
Object.defineProperty(document, "id", {
|
|
695
|
+
get: () => String(document._id),
|
|
696
|
+
enumerable: true,
|
|
697
|
+
configurable: true
|
|
698
|
+
});
|
|
699
|
+
return document;
|
|
700
|
+
};
|
|
705
701
|
const notFound = (id) => new NotFoundError(`No document in "${name}" with _id ${String(id)}`, {
|
|
706
702
|
collection: name,
|
|
707
703
|
id
|
|
@@ -713,6 +709,8 @@ function build(db, definition, options) {
|
|
|
713
709
|
};
|
|
714
710
|
const toDocument = (values) => {
|
|
715
711
|
const stamped = { ...values };
|
|
712
|
+
if (!hasOwnId)
|
|
713
|
+
delete stamped.id;
|
|
716
714
|
if (actor !== undefined) {
|
|
717
715
|
if (stamps.createdBy && stamped.createdBy === undefined) {
|
|
718
716
|
stamped.createdBy = actor;
|
|
@@ -754,10 +752,13 @@ function build(db, definition, options) {
|
|
|
754
752
|
}
|
|
755
753
|
return update;
|
|
756
754
|
};
|
|
757
|
-
const findOne = async (filter, projection) => run(async () =>
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
755
|
+
const findOne = async (filter, projection) => run(async () => {
|
|
756
|
+
const found = await collection.findOne(filter, {
|
|
757
|
+
...sessionOption,
|
|
758
|
+
...projection ? { projection } : {}
|
|
759
|
+
});
|
|
760
|
+
return found === null ? null : withId(found);
|
|
761
|
+
});
|
|
761
762
|
async function findById(id, opts = {}) {
|
|
762
763
|
const found = await findOne(scoped({ _id: id }, opts.withDeleted));
|
|
763
764
|
return found ?? undefined;
|
|
@@ -780,7 +781,8 @@ function build(db, definition, options) {
|
|
|
780
781
|
cursor = cursor.skip(opts.skip);
|
|
781
782
|
if (opts.limit !== undefined)
|
|
782
783
|
cursor = cursor.limit(opts.limit);
|
|
783
|
-
|
|
784
|
+
const found = await cursor.toArray();
|
|
785
|
+
return found.map((document) => withId(document));
|
|
784
786
|
});
|
|
785
787
|
}
|
|
786
788
|
async function countDocuments(filter, opts = {}) {
|
|
@@ -794,7 +796,7 @@ function build(db, definition, options) {
|
|
|
794
796
|
returnDocument: "after"
|
|
795
797
|
}));
|
|
796
798
|
if (updated)
|
|
797
|
-
return updated;
|
|
799
|
+
return withId(updated);
|
|
798
800
|
if (expectedVersion !== undefined) {
|
|
799
801
|
const current = await findOne({ _id: id });
|
|
800
802
|
if (current) {
|
|
@@ -812,7 +814,7 @@ function build(db, definition, options) {
|
|
|
812
814
|
const deleted = await run(async () => collection.findOneAndDelete({ _id: id }, { ...sessionOption }));
|
|
813
815
|
if (!deleted)
|
|
814
816
|
throw notFound(id);
|
|
815
|
-
return deleted;
|
|
817
|
+
return withId(deleted);
|
|
816
818
|
}
|
|
817
819
|
async function hardDeleteMany(filter) {
|
|
818
820
|
requireFilter("hardDeleteMany", filter);
|
|
@@ -823,12 +825,12 @@ function build(db, definition, options) {
|
|
|
823
825
|
return result.deletedCount;
|
|
824
826
|
});
|
|
825
827
|
}
|
|
826
|
-
const
|
|
828
|
+
const api = {
|
|
827
829
|
definition,
|
|
828
830
|
db,
|
|
829
|
-
collection,
|
|
831
|
+
raw: collection,
|
|
830
832
|
session,
|
|
831
|
-
|
|
833
|
+
withSession: (other) => build(db, definition, { ...options, session: other }),
|
|
832
834
|
as: (who) => build(db, definition, { ...options, actor: who }),
|
|
833
835
|
sync: (syncOptions = {}) => syncCollection(db, definition, { ...sessionOption, ...syncOptions }),
|
|
834
836
|
findById,
|
|
@@ -842,7 +844,7 @@ function build(db, definition, options) {
|
|
|
842
844
|
const document = toDocument(values);
|
|
843
845
|
return run(async () => {
|
|
844
846
|
await collection.insertOne(document, { ...sessionOption });
|
|
845
|
-
return document;
|
|
847
|
+
return withId(document);
|
|
846
848
|
});
|
|
847
849
|
},
|
|
848
850
|
async createMany(values) {
|
|
@@ -853,7 +855,7 @@ function build(db, definition, options) {
|
|
|
853
855
|
await collection.insertMany(documents, {
|
|
854
856
|
...sessionOption
|
|
855
857
|
});
|
|
856
|
-
return documents;
|
|
858
|
+
return documents.map((document) => withId(document));
|
|
857
859
|
});
|
|
858
860
|
},
|
|
859
861
|
async update(id, patch, opts = {}) {
|
|
@@ -988,8 +990,102 @@ function build(db, definition, options) {
|
|
|
988
990
|
return { items, nextCursor: encodeCursor({ key: cursorKey, values }) };
|
|
989
991
|
}
|
|
990
992
|
};
|
|
991
|
-
return
|
|
993
|
+
return new Proxy(api, {
|
|
994
|
+
get(target, key, receiver) {
|
|
995
|
+
if (Reflect.has(target, key))
|
|
996
|
+
return Reflect.get(target, key, receiver);
|
|
997
|
+
const value = collection[key];
|
|
998
|
+
return typeof value === "function" ? value.bind(collection) : value;
|
|
999
|
+
},
|
|
1000
|
+
has(target, key) {
|
|
1001
|
+
return Reflect.has(target, key) || key in collection;
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
992
1004
|
}
|
|
1005
|
+
// src/definition/fields.ts
|
|
1006
|
+
import { ObjectId as ObjectId3 } from "mongodb";
|
|
1007
|
+
import { z as z3 } from "zod";
|
|
1008
|
+
|
|
1009
|
+
// src/definition/object-id.ts
|
|
1010
|
+
import { ObjectId as ObjectId2 } from "mongodb";
|
|
1011
|
+
import { z as z2 } from "zod";
|
|
1012
|
+
var HEX_24 = /^[0-9a-fA-F]{24}$/;
|
|
1013
|
+
function isObjectId2(value) {
|
|
1014
|
+
return typeof value === "object" && value !== null && value._bsontype === "ObjectId";
|
|
1015
|
+
}
|
|
1016
|
+
function isObjectIdString(value) {
|
|
1017
|
+
return typeof value === "string" && HEX_24.test(value);
|
|
1018
|
+
}
|
|
1019
|
+
function isValidObjectId(value) {
|
|
1020
|
+
return isObjectId2(value) || isObjectIdString(value);
|
|
1021
|
+
}
|
|
1022
|
+
function tryObjectId(value) {
|
|
1023
|
+
if (isObjectId2(value))
|
|
1024
|
+
return value;
|
|
1025
|
+
if (isObjectIdString(value))
|
|
1026
|
+
return ObjectId2.createFromHexString(value);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
function describe(value) {
|
|
1030
|
+
if (value === null)
|
|
1031
|
+
return "null";
|
|
1032
|
+
if (value === undefined)
|
|
1033
|
+
return "undefined";
|
|
1034
|
+
if (typeof value === "string")
|
|
1035
|
+
return `the string ${JSON.stringify(value)}`;
|
|
1036
|
+
return `a ${typeof value}`;
|
|
1037
|
+
}
|
|
1038
|
+
function toObjectId(value, field = "_id") {
|
|
1039
|
+
const made = tryObjectId(value);
|
|
1040
|
+
if (made)
|
|
1041
|
+
return made;
|
|
1042
|
+
throw new InvalidIdError(`${field}: expected an ObjectId or its 24-character hex string, got ${describe(value)}`, { id: value, keys: [field] });
|
|
1043
|
+
}
|
|
1044
|
+
function toObjectIds(values, field = "_id") {
|
|
1045
|
+
return [...values].map((value) => toObjectId(value, field));
|
|
1046
|
+
}
|
|
1047
|
+
function objectIdParam() {
|
|
1048
|
+
return z2.custom(isValidObjectId, {
|
|
1049
|
+
error: "must be an ObjectId or its 24-character hex string"
|
|
1050
|
+
}).transform((value) => toObjectId(value));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/definition/fields.ts
|
|
1054
|
+
function objectId() {
|
|
1055
|
+
return z3.custom(isObjectId2, { error: "must be an ObjectId" }).meta({ bsonType: "objectId" });
|
|
1056
|
+
}
|
|
1057
|
+
function id() {
|
|
1058
|
+
return objectId().default(() => new ObjectId3);
|
|
1059
|
+
}
|
|
1060
|
+
function timestamps() {
|
|
1061
|
+
return {
|
|
1062
|
+
createdAt: z3.date().default(() => new Date),
|
|
1063
|
+
updatedAt: z3.date().default(() => new Date)
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
function softDelete() {
|
|
1067
|
+
return { deletedAt: z3.date().nullable().default(null) };
|
|
1068
|
+
}
|
|
1069
|
+
function optimisticLock() {
|
|
1070
|
+
return { version: z3.int().nonnegative().default(0) };
|
|
1071
|
+
}
|
|
1072
|
+
function actors(actor = objectId()) {
|
|
1073
|
+
return {
|
|
1074
|
+
createdBy: actor.nullable().default(null),
|
|
1075
|
+
updatedBy: actor.nullable().default(null),
|
|
1076
|
+
deletedBy: actor.nullable().default(null)
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
var STAMP_FIELDS = {
|
|
1080
|
+
id: "_id",
|
|
1081
|
+
createdAt: "createdAt",
|
|
1082
|
+
updatedAt: "updatedAt",
|
|
1083
|
+
deletedAt: "deletedAt",
|
|
1084
|
+
version: "version",
|
|
1085
|
+
createdBy: "createdBy",
|
|
1086
|
+
updatedBy: "updatedBy",
|
|
1087
|
+
deletedBy: "deletedBy"
|
|
1088
|
+
};
|
|
993
1089
|
// src/transaction/with-transaction.ts
|
|
994
1090
|
function isSession(host) {
|
|
995
1091
|
return typeof host.inTransaction === "function";
|
|
@@ -1021,24 +1117,29 @@ export {
|
|
|
1021
1117
|
DEFAULT_PAGE_SIZE,
|
|
1022
1118
|
DataError,
|
|
1023
1119
|
InvalidCursorError,
|
|
1120
|
+
InvalidIdError,
|
|
1024
1121
|
MONGO_JSON_SCHEMA_KEYWORDS,
|
|
1025
1122
|
NotFoundError,
|
|
1026
1123
|
OptimisticLockError,
|
|
1027
1124
|
STAMP_FIELDS,
|
|
1028
1125
|
ValidationError,
|
|
1029
1126
|
actors,
|
|
1030
|
-
createRepository,
|
|
1031
1127
|
cursorLimit,
|
|
1032
1128
|
decodeCursor,
|
|
1033
1129
|
defineCollection,
|
|
1034
1130
|
diffIndexes,
|
|
1035
1131
|
encodeCursor,
|
|
1132
|
+
getCollection,
|
|
1036
1133
|
hasValidator,
|
|
1037
1134
|
id,
|
|
1038
1135
|
indexMatches,
|
|
1039
1136
|
indexNameOf,
|
|
1137
|
+
isObjectId2 as isObjectId,
|
|
1138
|
+
isObjectIdString,
|
|
1139
|
+
isValidObjectId,
|
|
1040
1140
|
normalizeIndex,
|
|
1041
1141
|
objectId,
|
|
1142
|
+
objectIdParam,
|
|
1042
1143
|
optimisticLock,
|
|
1043
1144
|
pageWindow,
|
|
1044
1145
|
softDelete,
|
|
@@ -1048,10 +1149,13 @@ export {
|
|
|
1048
1149
|
timestamps,
|
|
1049
1150
|
toDataError,
|
|
1050
1151
|
toMongoJsonSchema,
|
|
1152
|
+
toObjectId,
|
|
1153
|
+
toObjectIds,
|
|
1051
1154
|
toPage,
|
|
1155
|
+
tryObjectId,
|
|
1052
1156
|
validationMatches,
|
|
1053
1157
|
withTransaction
|
|
1054
1158
|
};
|
|
1055
1159
|
|
|
1056
|
-
//# debugId=
|
|
1160
|
+
//# debugId=3B847E76D4AAF66F64756E2164756E21
|
|
1057
1161
|
//# sourceMappingURL=index.js.map
|