@atscript/mongo 0.0.15 → 0.0.17
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/index.cjs +127 -5
- package/dist/index.d.ts +17 -8
- package/dist/index.mjs +129 -7
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,8 @@ const mongodb = __toESM(require("mongodb"));
|
|
|
30
30
|
const primitives = { mongo: { extensions: {
|
|
31
31
|
objectId: {
|
|
32
32
|
type: "string",
|
|
33
|
-
documentation: "Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed.\n\n**Example:**\n```atscript\nuserId: mongo.objectId\n```\n"
|
|
33
|
+
documentation: "Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed.\n\n**Example:**\n```atscript\nuserId: mongo.objectId\n```\n",
|
|
34
|
+
expect: { pattern: /^[a-fA-F0-9]{24}$/ }
|
|
34
35
|
},
|
|
35
36
|
vector: {
|
|
36
37
|
type: {
|
|
@@ -249,7 +250,35 @@ const annotations = { mongo: {
|
|
|
249
250
|
description: "The **name of the vector search index** this field should be used as a filter for."
|
|
250
251
|
}]
|
|
251
252
|
})
|
|
252
|
-
}
|
|
253
|
+
},
|
|
254
|
+
patch: { strategy: new __atscript_core.AnnotationSpec({
|
|
255
|
+
description: "Defines the **patching strategy** for updating MongoDB documents.\n\n- **\"replace\"** → The field or object will be **fully replaced**.\n- **\"merge\"** → The field or object will be **merged recursively** (applies only to objects, not arrays).\n\n**Example:**\n```atscript\n@mongo.patch.strategy \"merge\"\nsettings: {\n notifications: boolean\n preferences: {\n theme: string\n }\n}\n```\n",
|
|
256
|
+
nodeType: ["prop"],
|
|
257
|
+
multiple: false,
|
|
258
|
+
argument: {
|
|
259
|
+
name: "strategy",
|
|
260
|
+
type: "string",
|
|
261
|
+
description: "The **patch strategy** for this field: `\"replace\"` (default) or `\"merge\"`.",
|
|
262
|
+
values: ["replace", "merge"]
|
|
263
|
+
},
|
|
264
|
+
validate(token, args, doc) {
|
|
265
|
+
const field = token.parentNode;
|
|
266
|
+
const errors = [];
|
|
267
|
+
const definition = field.getDefinition();
|
|
268
|
+
if (!definition) return errors;
|
|
269
|
+
let wrongType = false;
|
|
270
|
+
if ((0, __atscript_core.isRef)(definition)) {
|
|
271
|
+
const def = doc.unwindType(definition.id, definition.chain)?.def;
|
|
272
|
+
if (!(0, __atscript_core.isStructure)(def) && !(0, __atscript_core.isInterface)(def) && !(0, __atscript_core.isArray)(def)) wrongType = true;
|
|
273
|
+
} else if (!(0, __atscript_core.isStructure)(definition) && !(0, __atscript_core.isInterface)(definition) && !(0, __atscript_core.isArray)(definition)) wrongType = true;
|
|
274
|
+
if (wrongType) errors.push({
|
|
275
|
+
message: `[mongo] type of object or array expected when using @mongo.patch.strategy`,
|
|
276
|
+
severity: 1,
|
|
277
|
+
range: token.range
|
|
278
|
+
});
|
|
279
|
+
return errors;
|
|
280
|
+
}
|
|
281
|
+
}) }
|
|
253
282
|
} };
|
|
254
283
|
|
|
255
284
|
//#endregion
|
|
@@ -288,9 +317,14 @@ function _define_property$1(obj, key, value) {
|
|
|
288
317
|
else obj[key] = value;
|
|
289
318
|
return obj;
|
|
290
319
|
}
|
|
291
|
-
const INDEX_PREFIX = "
|
|
320
|
+
const INDEX_PREFIX = "atscript__";
|
|
292
321
|
const DEFAULT_INDEX_NAME = "DEFAULT";
|
|
293
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Generates a key for mongo index
|
|
324
|
+
* @param type index type
|
|
325
|
+
* @param name index name
|
|
326
|
+
* @returns index key
|
|
327
|
+
*/ function indexKey(type, name) {
|
|
294
328
|
const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
|
|
295
329
|
return `${INDEX_PREFIX}${type}__${cleanName}`;
|
|
296
330
|
}
|
|
@@ -302,6 +336,57 @@ var AsCollection = class {
|
|
|
302
336
|
const exists = await this.exists();
|
|
303
337
|
if (!exists) await this.asMongo.db.createCollection(this.name, { comment: "Created by Atscript Mongo Collection" });
|
|
304
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Returns the a type definition of the "_id" prop.
|
|
341
|
+
*/ get idType() {
|
|
342
|
+
const idProp = this.type.type.props.get("_id");
|
|
343
|
+
const idTags = idProp?.type.tags;
|
|
344
|
+
if (idTags?.has("objectId") && idTags?.has("mongo")) return "objectId";
|
|
345
|
+
if (idProp?.type.kind === "") return idProp.type.designType;
|
|
346
|
+
return "objectId";
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Transforms an "_id" value to the expected type (`ObjectId`, `number`, or `string`).
|
|
350
|
+
* Assumes input has already been validated.
|
|
351
|
+
*
|
|
352
|
+
* @param {string | number | ObjectId} id - The validated ID.
|
|
353
|
+
* @returns {string | number | ObjectId} - The transformed ID.
|
|
354
|
+
* @throws {Error} If the `_id` type is unknown.
|
|
355
|
+
*/ prepareId(id) {
|
|
356
|
+
switch (this.idType) {
|
|
357
|
+
case "objectId": return id instanceof mongodb.ObjectId ? id : new mongodb.ObjectId(id);
|
|
358
|
+
case "number": return Number(id);
|
|
359
|
+
case "string": return String(id);
|
|
360
|
+
default: throw new Error("Unknown \"_id\" type");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Retrieves a validator for a given purpose. If the validator is not already cached,
|
|
365
|
+
* it creates and stores a new one based on the purpose.
|
|
366
|
+
*
|
|
367
|
+
* @param {TValidatorPurpose} purpose - The validation purpose (`input`, `update`, `patch`).
|
|
368
|
+
* @returns {Validator} The corresponding validator instance.
|
|
369
|
+
* @throws {Error} If an unknown purpose is provided.
|
|
370
|
+
*/ getValidator(purpose) {
|
|
371
|
+
if (!this.validators.has(purpose)) switch (purpose) {
|
|
372
|
+
case "insert": {
|
|
373
|
+
this.validators.set(purpose, this.type.validator(this.idType === "objectId" ? { skipList: new Set(["_id"]) } : {}));
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
case "update": {
|
|
377
|
+
this.validators.set(purpose, this.type.validator());
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "patch": {
|
|
381
|
+
this.validators.set(purpose, this.type.validator({ partial: (def, path) => {
|
|
382
|
+
return path === "" || def.metadata.get("mongo.patch.strategy") === "merge";
|
|
383
|
+
} }));
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
default: throw new Error(`Unknown validator purpose: ${purpose}`);
|
|
387
|
+
}
|
|
388
|
+
return this.validators.get(purpose);
|
|
389
|
+
}
|
|
305
390
|
get type() {
|
|
306
391
|
return this._type;
|
|
307
392
|
}
|
|
@@ -349,11 +434,13 @@ else {
|
|
|
349
434
|
_flattenType(type, prefix) {
|
|
350
435
|
switch (type.type.kind) {
|
|
351
436
|
case "object":
|
|
437
|
+
this._flatMap?.set(prefix || "", type);
|
|
352
438
|
const items = Array.from(type.type.props.entries());
|
|
353
439
|
for (const [key, value] of items) this._flattenType(value, prefix ? `${prefix}.${key}` : key);
|
|
354
440
|
break;
|
|
355
441
|
case "array":
|
|
356
|
-
this.
|
|
442
|
+
this._flatMap?.set(prefix || "", type);
|
|
443
|
+
if (type.type.of.type.kind) this._flattenType(type.type.of, prefix);
|
|
357
444
|
break;
|
|
358
445
|
case "intersection":
|
|
359
446
|
case "tuple":
|
|
@@ -501,18 +588,53 @@ else toUpdate.add(remote.name);
|
|
|
501
588
|
default:
|
|
502
589
|
}
|
|
503
590
|
}
|
|
591
|
+
prepareInsert(payload) {
|
|
592
|
+
const v = this.getValidator("insert");
|
|
593
|
+
if (v.validate(payload)) {
|
|
594
|
+
const data = { ...payload };
|
|
595
|
+
if (data._id) data._id = this.prepareId(data._id);
|
|
596
|
+
else if (this.idType !== "objectId") throw new Error("Missing \"_id\" field");
|
|
597
|
+
return data;
|
|
598
|
+
}
|
|
599
|
+
throw new Error("Invalid payload");
|
|
600
|
+
}
|
|
601
|
+
prepareUpdate(payload) {
|
|
602
|
+
const v = this.getValidator("insert");
|
|
603
|
+
if (v.validate(payload)) {
|
|
604
|
+
const data = { ...payload };
|
|
605
|
+
data._id = this.prepareId(data._id);
|
|
606
|
+
return data;
|
|
607
|
+
}
|
|
608
|
+
throw new Error("Invalid payload");
|
|
609
|
+
}
|
|
610
|
+
preparePatch(payload) {
|
|
611
|
+
const v = this.getValidator("patch");
|
|
612
|
+
if (v.validate(payload)) return { $set: this._flattenPayload(payload) };
|
|
613
|
+
throw new Error("Invalid payload");
|
|
614
|
+
}
|
|
615
|
+
_flattenPayload(payload, prefix = "", obj = {}) {
|
|
616
|
+
const evalKey = (k) => prefix ? `${prefix}.${k}` : k;
|
|
617
|
+
for (const [_key, value] of Object.entries(payload)) {
|
|
618
|
+
const key = evalKey(_key);
|
|
619
|
+
if (typeof value === "object" && this.flatMap.get(key)?.metadata?.get("mongo.patch.strategy") === "merge") this._flattenPayload(value, key, obj);
|
|
620
|
+
else obj[key] = value;
|
|
621
|
+
}
|
|
622
|
+
return obj;
|
|
623
|
+
}
|
|
504
624
|
constructor(asMongo, _type, logger = NoopLogger) {
|
|
505
625
|
_define_property$1(this, "asMongo", void 0);
|
|
506
626
|
_define_property$1(this, "_type", void 0);
|
|
507
627
|
_define_property$1(this, "logger", void 0);
|
|
508
628
|
_define_property$1(this, "name", void 0);
|
|
509
629
|
_define_property$1(this, "collection", void 0);
|
|
630
|
+
_define_property$1(this, "validators", void 0);
|
|
510
631
|
_define_property$1(this, "_indexes", void 0);
|
|
511
632
|
_define_property$1(this, "_vectorFilters", void 0);
|
|
512
633
|
_define_property$1(this, "_flatMap", void 0);
|
|
513
634
|
this.asMongo = asMongo;
|
|
514
635
|
this._type = _type;
|
|
515
636
|
this.logger = logger;
|
|
637
|
+
this.validators = new Map();
|
|
516
638
|
this._indexes = new Map();
|
|
517
639
|
this._vectorFilters = new Map();
|
|
518
640
|
if (!(0, __atscript_typescript.isAnnotatedType)(_type)) throw new Error("Atscript Annotated Type expected");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TAtscriptPlugin } from '@atscript/core';
|
|
2
|
-
import { TAtscriptAnnotatedType, TAtscriptTypeObject, TMetadataMap } from '@atscript/typescript';
|
|
2
|
+
import { TAtscriptAnnotatedTypeConstructor, TAtscriptAnnotatedType, TAtscriptTypeObject, TMetadataMap } from '@atscript/typescript';
|
|
3
3
|
import * as mongodb from 'mongodb';
|
|
4
|
-
import { MongoClient, Collection } from 'mongodb';
|
|
4
|
+
import { MongoClient, Collection, ObjectId, WithId, UpdateFilter, MatchKeysAndValues } from 'mongodb';
|
|
5
5
|
|
|
6
6
|
declare const MongoPlugin: () => TAtscriptPlugin;
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ declare class AsMongo {
|
|
|
21
21
|
protected collectionsList?: Promise<Set<string>>;
|
|
22
22
|
protected getCollectionsList(): Promise<Set<string>>;
|
|
23
23
|
collectionExists(name: string): Promise<boolean>;
|
|
24
|
-
getCollection<T extends
|
|
24
|
+
getCollection<T extends TAtscriptAnnotatedTypeConstructor>(type: T, logger?: TGenericLogger): AsCollection<T>;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
type TPlainIndex = {
|
|
@@ -34,30 +34,39 @@ type TSearchIndex = {
|
|
|
34
34
|
definition: TMongoSearchIndexDefinition;
|
|
35
35
|
};
|
|
36
36
|
type TIndex = TPlainIndex | TSearchIndex;
|
|
37
|
-
|
|
37
|
+
type TValidatorPurpose = 'insert' | 'update' | 'patch';
|
|
38
|
+
declare class AsCollection<T extends TAtscriptAnnotatedTypeConstructor> {
|
|
38
39
|
protected readonly asMongo: AsMongo;
|
|
39
40
|
protected readonly _type: T;
|
|
40
41
|
protected readonly logger: TGenericLogger;
|
|
41
42
|
readonly name: string;
|
|
42
43
|
readonly collection: Collection<InstanceType<T>>;
|
|
44
|
+
protected readonly validators: Map<TValidatorPurpose, Validator<T>>;
|
|
45
|
+
protected _indexes: Map<string, TIndex>;
|
|
46
|
+
protected _vectorFilters: Map<string, string>;
|
|
47
|
+
protected _flatMap?: Map<string, TAtscriptAnnotatedType>;
|
|
43
48
|
constructor(asMongo: AsMongo, _type: T, logger?: TGenericLogger);
|
|
44
49
|
exists(): Promise<boolean>;
|
|
45
50
|
ensureExists(): Promise<void>;
|
|
51
|
+
get idType(): 'string' | 'number' | 'objectId';
|
|
52
|
+
prepareId<D = string | number | ObjectId>(id: string | number | ObjectId): D;
|
|
53
|
+
getValidator(purpose: TValidatorPurpose): any;
|
|
46
54
|
get type(): TAtscriptAnnotatedType<TAtscriptTypeObject>;
|
|
47
|
-
protected _indexes: Map<string, TIndex>;
|
|
48
|
-
protected _vectorFilters: Map<string, string>;
|
|
49
55
|
get indexes(): Map<string, TIndex>;
|
|
50
56
|
protected _addIndexField(type: TPlainIndex['type'], name: string, field: string, weight?: number): void;
|
|
51
57
|
protected _setSearchIndex(type: TSearchIndex['type'], name: string | undefined, definition: TMongoSearchIndexDefinition): void;
|
|
52
58
|
protected _addFieldToSearchIndex(type: TSearchIndex['type'], _name: string | undefined, fieldName: string, analyzer?: string): void;
|
|
53
|
-
protected _flatMap?: Map<string, TAtscriptAnnotatedType>;
|
|
54
59
|
protected _flattenType(type: TAtscriptAnnotatedType, prefix?: string): void;
|
|
55
60
|
protected _prepareIndexesForCollection(): void;
|
|
56
61
|
protected _finalizeIndexesForCollection(): void;
|
|
57
62
|
protected _prepareIndexesForField(fieldName: string, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
58
63
|
protected _flatten(): void;
|
|
59
|
-
get flatMap(): Map<string, TAtscriptAnnotatedType
|
|
64
|
+
get flatMap(): Map<string, TAtscriptAnnotatedType>;
|
|
60
65
|
syncIndexes(): Promise<void>;
|
|
66
|
+
prepareInsert(payload: any): InstanceType<T>;
|
|
67
|
+
prepareUpdate(payload: any): WithId<InstanceType<T>>;
|
|
68
|
+
preparePatch(payload: any): UpdateFilter<InstanceType<T>>;
|
|
69
|
+
protected _flattenPayload(payload: T, prefix?: string, obj?: MatchKeysAndValues<InstanceType<T>>): MatchKeysAndValues<InstanceType<T>>;
|
|
61
70
|
}
|
|
62
71
|
type TVectorSimilarity = 'cosine' | 'euclidean' | 'dotProduct';
|
|
63
72
|
type TMongoSearchIndexDefinition = {
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { AnnotationSpec, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
|
|
1
|
+
import { AnnotationSpec, isArray, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
|
|
2
2
|
import { isAnnotatedType } from "@atscript/typescript";
|
|
3
|
-
import { MongoClient } from "mongodb";
|
|
3
|
+
import { MongoClient, ObjectId } from "mongodb";
|
|
4
4
|
|
|
5
5
|
//#region packages/mongo/src/plugin/primitives.ts
|
|
6
6
|
const primitives = { mongo: { extensions: {
|
|
7
7
|
objectId: {
|
|
8
8
|
type: "string",
|
|
9
|
-
documentation: "Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed.\n\n**Example:**\n```atscript\nuserId: mongo.objectId\n```\n"
|
|
9
|
+
documentation: "Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed.\n\n**Example:**\n```atscript\nuserId: mongo.objectId\n```\n",
|
|
10
|
+
expect: { pattern: /^[a-fA-F0-9]{24}$/ }
|
|
10
11
|
},
|
|
11
12
|
vector: {
|
|
12
13
|
type: {
|
|
@@ -225,7 +226,35 @@ const annotations = { mongo: {
|
|
|
225
226
|
description: "The **name of the vector search index** this field should be used as a filter for."
|
|
226
227
|
}]
|
|
227
228
|
})
|
|
228
|
-
}
|
|
229
|
+
},
|
|
230
|
+
patch: { strategy: new AnnotationSpec({
|
|
231
|
+
description: "Defines the **patching strategy** for updating MongoDB documents.\n\n- **\"replace\"** → The field or object will be **fully replaced**.\n- **\"merge\"** → The field or object will be **merged recursively** (applies only to objects, not arrays).\n\n**Example:**\n```atscript\n@mongo.patch.strategy \"merge\"\nsettings: {\n notifications: boolean\n preferences: {\n theme: string\n }\n}\n```\n",
|
|
232
|
+
nodeType: ["prop"],
|
|
233
|
+
multiple: false,
|
|
234
|
+
argument: {
|
|
235
|
+
name: "strategy",
|
|
236
|
+
type: "string",
|
|
237
|
+
description: "The **patch strategy** for this field: `\"replace\"` (default) or `\"merge\"`.",
|
|
238
|
+
values: ["replace", "merge"]
|
|
239
|
+
},
|
|
240
|
+
validate(token, args, doc) {
|
|
241
|
+
const field = token.parentNode;
|
|
242
|
+
const errors = [];
|
|
243
|
+
const definition = field.getDefinition();
|
|
244
|
+
if (!definition) return errors;
|
|
245
|
+
let wrongType = false;
|
|
246
|
+
if (isRef(definition)) {
|
|
247
|
+
const def = doc.unwindType(definition.id, definition.chain)?.def;
|
|
248
|
+
if (!isStructure(def) && !isInterface(def) && !isArray(def)) wrongType = true;
|
|
249
|
+
} else if (!isStructure(definition) && !isInterface(definition) && !isArray(definition)) wrongType = true;
|
|
250
|
+
if (wrongType) errors.push({
|
|
251
|
+
message: `[mongo] type of object or array expected when using @mongo.patch.strategy`,
|
|
252
|
+
severity: 1,
|
|
253
|
+
range: token.range
|
|
254
|
+
});
|
|
255
|
+
return errors;
|
|
256
|
+
}
|
|
257
|
+
}) }
|
|
229
258
|
} };
|
|
230
259
|
|
|
231
260
|
//#endregion
|
|
@@ -264,9 +293,14 @@ function _define_property$1(obj, key, value) {
|
|
|
264
293
|
else obj[key] = value;
|
|
265
294
|
return obj;
|
|
266
295
|
}
|
|
267
|
-
const INDEX_PREFIX = "
|
|
296
|
+
const INDEX_PREFIX = "atscript__";
|
|
268
297
|
const DEFAULT_INDEX_NAME = "DEFAULT";
|
|
269
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Generates a key for mongo index
|
|
300
|
+
* @param type index type
|
|
301
|
+
* @param name index name
|
|
302
|
+
* @returns index key
|
|
303
|
+
*/ function indexKey(type, name) {
|
|
270
304
|
const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
|
|
271
305
|
return `${INDEX_PREFIX}${type}__${cleanName}`;
|
|
272
306
|
}
|
|
@@ -278,6 +312,57 @@ var AsCollection = class {
|
|
|
278
312
|
const exists = await this.exists();
|
|
279
313
|
if (!exists) await this.asMongo.db.createCollection(this.name, { comment: "Created by Atscript Mongo Collection" });
|
|
280
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Returns the a type definition of the "_id" prop.
|
|
317
|
+
*/ get idType() {
|
|
318
|
+
const idProp = this.type.type.props.get("_id");
|
|
319
|
+
const idTags = idProp?.type.tags;
|
|
320
|
+
if (idTags?.has("objectId") && idTags?.has("mongo")) return "objectId";
|
|
321
|
+
if (idProp?.type.kind === "") return idProp.type.designType;
|
|
322
|
+
return "objectId";
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Transforms an "_id" value to the expected type (`ObjectId`, `number`, or `string`).
|
|
326
|
+
* Assumes input has already been validated.
|
|
327
|
+
*
|
|
328
|
+
* @param {string | number | ObjectId} id - The validated ID.
|
|
329
|
+
* @returns {string | number | ObjectId} - The transformed ID.
|
|
330
|
+
* @throws {Error} If the `_id` type is unknown.
|
|
331
|
+
*/ prepareId(id) {
|
|
332
|
+
switch (this.idType) {
|
|
333
|
+
case "objectId": return id instanceof ObjectId ? id : new ObjectId(id);
|
|
334
|
+
case "number": return Number(id);
|
|
335
|
+
case "string": return String(id);
|
|
336
|
+
default: throw new Error("Unknown \"_id\" type");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Retrieves a validator for a given purpose. If the validator is not already cached,
|
|
341
|
+
* it creates and stores a new one based on the purpose.
|
|
342
|
+
*
|
|
343
|
+
* @param {TValidatorPurpose} purpose - The validation purpose (`input`, `update`, `patch`).
|
|
344
|
+
* @returns {Validator} The corresponding validator instance.
|
|
345
|
+
* @throws {Error} If an unknown purpose is provided.
|
|
346
|
+
*/ getValidator(purpose) {
|
|
347
|
+
if (!this.validators.has(purpose)) switch (purpose) {
|
|
348
|
+
case "insert": {
|
|
349
|
+
this.validators.set(purpose, this.type.validator(this.idType === "objectId" ? { skipList: new Set(["_id"]) } : {}));
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
case "update": {
|
|
353
|
+
this.validators.set(purpose, this.type.validator());
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
case "patch": {
|
|
357
|
+
this.validators.set(purpose, this.type.validator({ partial: (def, path) => {
|
|
358
|
+
return path === "" || def.metadata.get("mongo.patch.strategy") === "merge";
|
|
359
|
+
} }));
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
default: throw new Error(`Unknown validator purpose: ${purpose}`);
|
|
363
|
+
}
|
|
364
|
+
return this.validators.get(purpose);
|
|
365
|
+
}
|
|
281
366
|
get type() {
|
|
282
367
|
return this._type;
|
|
283
368
|
}
|
|
@@ -325,11 +410,13 @@ else {
|
|
|
325
410
|
_flattenType(type, prefix) {
|
|
326
411
|
switch (type.type.kind) {
|
|
327
412
|
case "object":
|
|
413
|
+
this._flatMap?.set(prefix || "", type);
|
|
328
414
|
const items = Array.from(type.type.props.entries());
|
|
329
415
|
for (const [key, value] of items) this._flattenType(value, prefix ? `${prefix}.${key}` : key);
|
|
330
416
|
break;
|
|
331
417
|
case "array":
|
|
332
|
-
this.
|
|
418
|
+
this._flatMap?.set(prefix || "", type);
|
|
419
|
+
if (type.type.of.type.kind) this._flattenType(type.type.of, prefix);
|
|
333
420
|
break;
|
|
334
421
|
case "intersection":
|
|
335
422
|
case "tuple":
|
|
@@ -477,18 +564,53 @@ else toUpdate.add(remote.name);
|
|
|
477
564
|
default:
|
|
478
565
|
}
|
|
479
566
|
}
|
|
567
|
+
prepareInsert(payload) {
|
|
568
|
+
const v = this.getValidator("insert");
|
|
569
|
+
if (v.validate(payload)) {
|
|
570
|
+
const data = { ...payload };
|
|
571
|
+
if (data._id) data._id = this.prepareId(data._id);
|
|
572
|
+
else if (this.idType !== "objectId") throw new Error("Missing \"_id\" field");
|
|
573
|
+
return data;
|
|
574
|
+
}
|
|
575
|
+
throw new Error("Invalid payload");
|
|
576
|
+
}
|
|
577
|
+
prepareUpdate(payload) {
|
|
578
|
+
const v = this.getValidator("insert");
|
|
579
|
+
if (v.validate(payload)) {
|
|
580
|
+
const data = { ...payload };
|
|
581
|
+
data._id = this.prepareId(data._id);
|
|
582
|
+
return data;
|
|
583
|
+
}
|
|
584
|
+
throw new Error("Invalid payload");
|
|
585
|
+
}
|
|
586
|
+
preparePatch(payload) {
|
|
587
|
+
const v = this.getValidator("patch");
|
|
588
|
+
if (v.validate(payload)) return { $set: this._flattenPayload(payload) };
|
|
589
|
+
throw new Error("Invalid payload");
|
|
590
|
+
}
|
|
591
|
+
_flattenPayload(payload, prefix = "", obj = {}) {
|
|
592
|
+
const evalKey = (k) => prefix ? `${prefix}.${k}` : k;
|
|
593
|
+
for (const [_key, value] of Object.entries(payload)) {
|
|
594
|
+
const key = evalKey(_key);
|
|
595
|
+
if (typeof value === "object" && this.flatMap.get(key)?.metadata?.get("mongo.patch.strategy") === "merge") this._flattenPayload(value, key, obj);
|
|
596
|
+
else obj[key] = value;
|
|
597
|
+
}
|
|
598
|
+
return obj;
|
|
599
|
+
}
|
|
480
600
|
constructor(asMongo, _type, logger = NoopLogger) {
|
|
481
601
|
_define_property$1(this, "asMongo", void 0);
|
|
482
602
|
_define_property$1(this, "_type", void 0);
|
|
483
603
|
_define_property$1(this, "logger", void 0);
|
|
484
604
|
_define_property$1(this, "name", void 0);
|
|
485
605
|
_define_property$1(this, "collection", void 0);
|
|
606
|
+
_define_property$1(this, "validators", void 0);
|
|
486
607
|
_define_property$1(this, "_indexes", void 0);
|
|
487
608
|
_define_property$1(this, "_vectorFilters", void 0);
|
|
488
609
|
_define_property$1(this, "_flatMap", void 0);
|
|
489
610
|
this.asMongo = asMongo;
|
|
490
611
|
this._type = _type;
|
|
491
612
|
this.logger = logger;
|
|
613
|
+
this.validators = new Map();
|
|
492
614
|
this._indexes = new Map();
|
|
493
615
|
this._vectorFilters = new Map();
|
|
494
616
|
if (!isAnnotatedType(_type)) throw new Error("Atscript Annotated Type expected");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/mongo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Mongodb plugin for atscript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"license": "ISC",
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"mongodb": "^6.13.0",
|
|
38
|
-
"@atscript/core": "^0.0.
|
|
39
|
-
"@atscript/typescript": "^0.0.
|
|
38
|
+
"@atscript/core": "^0.0.17",
|
|
39
|
+
"@atscript/typescript": "^0.0.17"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"vitest": "^3.0.0"
|