@atscript/mongo 0.1.31 → 0.1.32
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 +522 -643
- package/dist/index.d.ts +241 -106
- package/dist/index.mjs +519 -642
- package/dist/plugin.cjs +228 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.mjs +204 -0
- package/package.json +28 -3
- package/skills/atscript-mongo/core.md +1 -1
package/dist/index.cjs
CHANGED
|
@@ -22,304 +22,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
|
|
24
24
|
//#endregion
|
|
25
|
-
const
|
|
26
|
-
const __atscript_typescript_utils = __toESM(require("@atscript/typescript/utils"));
|
|
25
|
+
const __atscript_utils_db = __toESM(require("@atscript/utils-db"));
|
|
27
26
|
const mongodb = __toESM(require("mongodb"));
|
|
27
|
+
const __atscript_typescript_utils = __toESM(require("@atscript/typescript/utils"));
|
|
28
28
|
|
|
29
|
-
//#region packages/mongo/src/plugin/annotations.ts
|
|
30
|
-
const analyzers = [
|
|
31
|
-
"lucene.standard",
|
|
32
|
-
"lucene.simple",
|
|
33
|
-
"lucene.whitespace",
|
|
34
|
-
"lucene.english",
|
|
35
|
-
"lucene.french",
|
|
36
|
-
"lucene.german",
|
|
37
|
-
"lucene.italian",
|
|
38
|
-
"lucene.portuguese",
|
|
39
|
-
"lucene.spanish",
|
|
40
|
-
"lucene.chinese",
|
|
41
|
-
"lucene.hindi",
|
|
42
|
-
"lucene.bengali",
|
|
43
|
-
"lucene.russian",
|
|
44
|
-
"lucene.arabic"
|
|
45
|
-
];
|
|
46
|
-
const annotations = {
|
|
47
|
-
collection: new __atscript_core.AnnotationSpec({
|
|
48
|
-
description: "Marks an interface as a **MongoDB collection**.\n\n- Use together with `@db.table \"name\"` which provides the collection name.\n- Automatically injects a **non-optional** `_id` field if not explicitly defined.\n- `_id` must be of type **`string`**, **`number`**, or **`mongo.objectId`**.\n\n**Example:**\n```atscript\n@db.table \"users\"\n@db.mongo.collection\nexport interface User {\n _id: mongo.objectId\n email: string.email\n}\n```\n",
|
|
49
|
-
nodeType: ["interface"],
|
|
50
|
-
validate(token, args, doc) {
|
|
51
|
-
const parent = token.parentNode;
|
|
52
|
-
const struc = parent?.getDefinition();
|
|
53
|
-
const errors = [];
|
|
54
|
-
if ((0, __atscript_core.isInterface)(parent) && parent.props.has("_id") && (0, __atscript_core.isStructure)(struc)) {
|
|
55
|
-
const _id = parent.props.get("_id");
|
|
56
|
-
const isOptional = !!_id.token("optional");
|
|
57
|
-
if (isOptional) errors.push({
|
|
58
|
-
message: `[db.mongo] _id can't be optional in Mongo Collection`,
|
|
59
|
-
severity: 1,
|
|
60
|
-
range: _id.token("identifier").range
|
|
61
|
-
});
|
|
62
|
-
const definition = _id.getDefinition();
|
|
63
|
-
if (!definition) return errors;
|
|
64
|
-
let wrongType = false;
|
|
65
|
-
if ((0, __atscript_core.isRef)(definition)) {
|
|
66
|
-
const def = doc.unwindType(definition.id, definition.chain)?.def;
|
|
67
|
-
if ((0, __atscript_core.isPrimitive)(def) && !["string", "number"].includes(def.config.type)) wrongType = true;
|
|
68
|
-
} else wrongType = true;
|
|
69
|
-
if (wrongType) errors.push({
|
|
70
|
-
message: `[db.mongo] _id must be of type string, number or mongo.objectId`,
|
|
71
|
-
severity: 1,
|
|
72
|
-
range: _id.token("identifier").range
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return errors;
|
|
76
|
-
},
|
|
77
|
-
modify(token, args, doc) {
|
|
78
|
-
const parent = token.parentNode;
|
|
79
|
-
const struc = parent?.getDefinition();
|
|
80
|
-
if ((0, __atscript_core.isInterface)(parent) && !parent.props.has("_id") && (0, __atscript_core.isStructure)(struc)) struc.addVirtualProp({
|
|
81
|
-
name: "_id",
|
|
82
|
-
type: "mongo.objectId",
|
|
83
|
-
documentation: "Mongodb Primary Key ObjectId"
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}),
|
|
87
|
-
autoIndexes: new __atscript_core.AnnotationSpec({
|
|
88
|
-
description: "Switch on/off the automatic index creation. Works with as-mongo moost controller.\n\nDefault: true",
|
|
89
|
-
nodeType: ["interface"],
|
|
90
|
-
argument: {
|
|
91
|
-
name: "type",
|
|
92
|
-
type: "boolean",
|
|
93
|
-
description: "On/Off the automatic index creation"
|
|
94
|
-
}
|
|
95
|
-
}),
|
|
96
|
-
index: { text: new __atscript_core.AnnotationSpec({
|
|
97
|
-
description: "Creates a **legacy MongoDB text index** for full-text search with optional **weight** specification.\n\nUse this when you need per-field weight control. For simple full-text indexing without weights, use the generic `@db.index.fulltext` instead.\n\n**Example:**\n```atscript\n@db.mongo.index.text 5\nbio: string\n```\n",
|
|
98
|
-
nodeType: ["prop"],
|
|
99
|
-
argument: {
|
|
100
|
-
optional: true,
|
|
101
|
-
name: "weight",
|
|
102
|
-
type: "number",
|
|
103
|
-
description: "Field importance in search results (higher = more relevant). Defaults to `1`."
|
|
104
|
-
}
|
|
105
|
-
}) },
|
|
106
|
-
search: {
|
|
107
|
-
dynamic: new __atscript_core.AnnotationSpec({
|
|
108
|
-
description: "Creates a **dynamic MongoDB Search Index** that applies to the entire collection.\n\n- **Indexes all text fields automatically** (no need to specify fields).\n- Supports **language analyzers** for text tokenization.\n- Enables **fuzzy search** (typo tolerance) if needed.\n\n**Example:**\n```atscript\n@db.mongo.search.dynamic \"lucene.english\", 1\nexport interface MongoCollection {}\n```\n",
|
|
109
|
-
nodeType: ["interface"],
|
|
110
|
-
multiple: false,
|
|
111
|
-
argument: [{
|
|
112
|
-
optional: true,
|
|
113
|
-
name: "analyzer",
|
|
114
|
-
type: "string",
|
|
115
|
-
description: "The **text analyzer** for tokenization. Defaults to `\"lucene.standard\"`.\n\n**Available options:** `\"lucene.standard\"`, `\"lucene.english\"`, `\"lucene.spanish\"`, etc.",
|
|
116
|
-
values: analyzers
|
|
117
|
-
}, {
|
|
118
|
-
optional: true,
|
|
119
|
-
name: "fuzzy",
|
|
120
|
-
type: "number",
|
|
121
|
-
description: "Maximum typo tolerance (`0-2`). Defaults to `0` (no fuzzy search).\n\n- `0` → Exact match required.\n- `1` → Allows small typos (e.g., `\"mongo\"` ≈ `\"mango\"`).\n- `2` → More typo tolerance (e.g., `\"mongodb\"` ≈ `\"mangodb\"`)."
|
|
122
|
-
}]
|
|
123
|
-
}),
|
|
124
|
-
static: new __atscript_core.AnnotationSpec({
|
|
125
|
-
description: "Defines a **MongoDB Atlas Search Index** for the collection. The props can refer to this index using `@db.mongo.search.text` annotation.\n\n- **Creates a named search index** for full-text search.\n- **Specify analyzers and fuzzy search** behavior at the index level.\n- **Fields must explicitly use `@db.mongo.search.text`** to be included in this search index.\n\n**Example:**\n```atscript\n@db.mongo.search.static \"lucene.english\", 1, \"mySearchIndex\"\nexport interface MongoCollection {}\n```\n",
|
|
126
|
-
nodeType: ["interface"],
|
|
127
|
-
multiple: true,
|
|
128
|
-
argument: [
|
|
129
|
-
{
|
|
130
|
-
optional: true,
|
|
131
|
-
name: "analyzer",
|
|
132
|
-
type: "string",
|
|
133
|
-
description: "The text analyzer for tokenization. Defaults to `\"lucene.standard\"`.\n\n**Available options:** `\"lucene.standard\"`, `\"lucene.english\"`, `\"lucene.spanish\"`, `\"lucene.german\"`, etc.",
|
|
134
|
-
values: analyzers
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
optional: true,
|
|
138
|
-
name: "fuzzy",
|
|
139
|
-
type: "number",
|
|
140
|
-
description: "Maximum typo tolerance (`0-2`). **Defaults to `0` (no fuzzy matching).**\n\n- `0` → No typos allowed (exact match required).\n- `1` → Allows small typos (e.g., \"mongo\" ≈ \"mango\").\n- `2` → More typo tolerance (e.g., \"mongodb\" ≈ \"mangodb\")."
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
optional: true,
|
|
144
|
-
name: "indexName",
|
|
145
|
-
type: "string",
|
|
146
|
-
description: "The name of the search index. Fields must reference this name using `@db.mongo.search.text`. If not set, defaults to `\"DEFAULT\"`."
|
|
147
|
-
}
|
|
148
|
-
]
|
|
149
|
-
}),
|
|
150
|
-
text: new __atscript_core.AnnotationSpec({
|
|
151
|
-
description: "Marks a field to be **included in a MongoDB Atlas Search Index** defined by `@db.mongo.search.static`.\n\n- **The field has to reference an existing search index name**.\n- If index name is not defined, a new search index with default attributes will be created.\n\n**Example:**\n```atscript\n@db.mongo.search.text \"lucene.english\", \"mySearchIndex\"\nfirstName: string\n```\n",
|
|
152
|
-
nodeType: ["prop"],
|
|
153
|
-
multiple: true,
|
|
154
|
-
argument: [{
|
|
155
|
-
optional: true,
|
|
156
|
-
name: "analyzer",
|
|
157
|
-
type: "string",
|
|
158
|
-
description: "The text analyzer for tokenization. Defaults to `\"lucene.standard\"`.\n\n**Available options:** `\"lucene.standard\"`, `\"lucene.english\"`, `\"lucene.spanish\"`, `\"lucene.german\"`, etc.",
|
|
159
|
-
values: analyzers
|
|
160
|
-
}, {
|
|
161
|
-
optional: true,
|
|
162
|
-
name: "indexName",
|
|
163
|
-
type: "string",
|
|
164
|
-
description: "The **name of the search index** defined in `@db.mongo.search.static`. This links the field to the correct index. If not set, defaults to `\"DEFAULT\"`."
|
|
165
|
-
}]
|
|
166
|
-
}),
|
|
167
|
-
vector: new __atscript_core.AnnotationSpec({
|
|
168
|
-
description: "Creates a **MongoDB Vector Search Index** for **semantic search, embeddings, and AI-powered search**.\n\n- Each field that stores vector embeddings **must define its own vector index**.\n- Supports **cosine similarity, Euclidean distance, and dot product similarity**.\n- Vector fields must be an **array of numbers**.\n\n**Example:**\n```atscript\n@db.mongo.search.vector 512, \"cosine\"\nembedding: mongo.vector\n```\n",
|
|
169
|
-
nodeType: ["prop"],
|
|
170
|
-
multiple: false,
|
|
171
|
-
argument: [
|
|
172
|
-
{
|
|
173
|
-
optional: false,
|
|
174
|
-
name: "dimensions",
|
|
175
|
-
type: "number",
|
|
176
|
-
description: "The **number of dimensions in the vector** (e.g., 512 for OpenAI embeddings).",
|
|
177
|
-
values: [
|
|
178
|
-
"512",
|
|
179
|
-
"768",
|
|
180
|
-
"1024",
|
|
181
|
-
"1536",
|
|
182
|
-
"3072",
|
|
183
|
-
"4096"
|
|
184
|
-
]
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
optional: true,
|
|
188
|
-
name: "similarity",
|
|
189
|
-
type: "string",
|
|
190
|
-
description: "The **similarity metric** used for vector search. Defaults to `\"cosine\"`.\n\n**Available options:** `\"cosine\"`, `\"euclidean\"`, `\"dotProduct\"`.",
|
|
191
|
-
values: [
|
|
192
|
-
"cosine",
|
|
193
|
-
"euclidean",
|
|
194
|
-
"dotProduct"
|
|
195
|
-
]
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
optional: true,
|
|
199
|
-
name: "indexName",
|
|
200
|
-
type: "string",
|
|
201
|
-
description: "The **name of the vector search index** (optional, defaults to property name)."
|
|
202
|
-
}
|
|
203
|
-
]
|
|
204
|
-
}),
|
|
205
|
-
filter: new __atscript_core.AnnotationSpec({
|
|
206
|
-
description: "Assigns a field as a **filter field** for a **MongoDB Vector Search Index**.\n\n- The assigned field **must be indexed** for efficient filtering.\n- Filters allow vector search queries to return results **only within a specific category, user group, or tag**.\n- The vector index must be defined using `@db.mongo.search.vector`.\n\n**Example:**\n```atscript\n@db.mongo.search.vector 512, \"cosine\"\nembedding: number[]\n\n@db.mongo.search.filter \"embedding\"\ncategory: string\n```\n",
|
|
207
|
-
nodeType: ["prop"],
|
|
208
|
-
multiple: true,
|
|
209
|
-
argument: [{
|
|
210
|
-
optional: false,
|
|
211
|
-
name: "indexName",
|
|
212
|
-
type: "string",
|
|
213
|
-
description: "The **name of the vector search index** this field should be used as a filter for."
|
|
214
|
-
}]
|
|
215
|
-
})
|
|
216
|
-
},
|
|
217
|
-
patch: { strategy: new __atscript_core.AnnotationSpec({
|
|
218
|
-
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@db.mongo.patch.strategy \"merge\"\nsettings: {\n notifications: boolean\n preferences: {\n theme: string\n }\n}\n```\n",
|
|
219
|
-
nodeType: ["prop"],
|
|
220
|
-
multiple: false,
|
|
221
|
-
argument: {
|
|
222
|
-
name: "strategy",
|
|
223
|
-
type: "string",
|
|
224
|
-
description: "The **patch strategy** for this field: `\"replace\"` (default) or `\"merge\"`.",
|
|
225
|
-
values: ["replace", "merge"]
|
|
226
|
-
},
|
|
227
|
-
validate(token, args, doc) {
|
|
228
|
-
const field = token.parentNode;
|
|
229
|
-
const errors = [];
|
|
230
|
-
const definition = field.getDefinition();
|
|
231
|
-
if (!definition) return errors;
|
|
232
|
-
let wrongType = false;
|
|
233
|
-
if ((0, __atscript_core.isRef)(definition)) {
|
|
234
|
-
const def = doc.unwindType(definition.id, definition.chain)?.def;
|
|
235
|
-
if (!(0, __atscript_core.isStructure)(def) && !(0, __atscript_core.isInterface)(def) && !(0, __atscript_core.isArray)(def)) wrongType = true;
|
|
236
|
-
} else if (!(0, __atscript_core.isStructure)(definition) && !(0, __atscript_core.isInterface)(definition) && !(0, __atscript_core.isArray)(definition)) wrongType = true;
|
|
237
|
-
if (wrongType) errors.push({
|
|
238
|
-
message: `[db.mongo] type of object or array expected when using @db.mongo.patch.strategy`,
|
|
239
|
-
severity: 1,
|
|
240
|
-
range: token.range
|
|
241
|
-
});
|
|
242
|
-
return errors;
|
|
243
|
-
}
|
|
244
|
-
}) },
|
|
245
|
-
array: { uniqueItems: new __atscript_core.AnnotationSpec({
|
|
246
|
-
description: "Marks an **array field** as containing *globally unique items* when handling **patch `$insert` operations**.\n\n- Forces the patcher to use **set-semantics** (`$setUnion`) instead of a plain append, so duplicates are silently skipped.\n- Has **no effect** on `$replace`, `$update`, or `$remove`.\n- If the array's element type already defines one or more `@expect.array.key` properties, *uniqueness is implied* and this annotation is unnecessary (but harmless).\n\n**Example:**\n```atscript\n@db.mongo.array.uniqueItems\ntags: string[]\n\n// Later in a patch payload …\n{\n $insert: {\n tags: [\"mongo\", \"mongo\"] // second \"mongo\" is ignored\n }\n}\n```\n",
|
|
247
|
-
nodeType: ["prop"],
|
|
248
|
-
multiple: false,
|
|
249
|
-
validate(token, args, doc) {
|
|
250
|
-
const field = token.parentNode;
|
|
251
|
-
const errors = [];
|
|
252
|
-
const definition = field.getDefinition();
|
|
253
|
-
if (!definition) return errors;
|
|
254
|
-
let wrongType = false;
|
|
255
|
-
if ((0, __atscript_core.isRef)(definition)) {
|
|
256
|
-
const def = doc.unwindType(definition.id, definition.chain)?.def;
|
|
257
|
-
if (!(0, __atscript_core.isArray)(def)) wrongType = true;
|
|
258
|
-
} else if (!(0, __atscript_core.isArray)(definition)) wrongType = true;
|
|
259
|
-
if (wrongType) errors.push({
|
|
260
|
-
message: `[db.mongo] type of array expected when using @db.mongo.array.uniqueItems`,
|
|
261
|
-
severity: 1,
|
|
262
|
-
range: token.range
|
|
263
|
-
});
|
|
264
|
-
return errors;
|
|
265
|
-
}
|
|
266
|
-
}) }
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
//#endregion
|
|
270
|
-
//#region packages/mongo/src/plugin/primitives.ts
|
|
271
|
-
const primitives = { mongo: { extensions: {
|
|
272
|
-
objectId: {
|
|
273
|
-
type: "string",
|
|
274
|
-
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",
|
|
275
|
-
annotations: { "expect.pattern": { pattern: "^[a-fA-F0-9]{24}$" } }
|
|
276
|
-
},
|
|
277
|
-
vector: {
|
|
278
|
-
type: {
|
|
279
|
-
kind: "array",
|
|
280
|
-
of: "number"
|
|
281
|
-
},
|
|
282
|
-
documentation: "Represents a **MongoDB Vector (Array of Numbers)** for **Vector Search**.\n\n- Equivalent to `number[]` but explicitly used for **vector embeddings**.\n\n**Example:**\n```atscript\nembedding: mongo.vector\n```\n"
|
|
283
|
-
}
|
|
284
|
-
} } };
|
|
285
|
-
|
|
286
|
-
//#endregion
|
|
287
|
-
//#region packages/mongo/src/plugin/index.ts
|
|
288
|
-
const MongoPlugin = () => ({
|
|
289
|
-
name: "mongo",
|
|
290
|
-
config() {
|
|
291
|
-
return {
|
|
292
|
-
primitives,
|
|
293
|
-
annotations: { db: { mongo: annotations } }
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
//#endregion
|
|
299
29
|
//#region packages/mongo/src/lib/validate-plugins.ts
|
|
300
30
|
const validateMongoIdPlugin = (ctx, def, value) => {
|
|
301
31
|
if (ctx.path === "_id" && def.type.tags.has("objectId")) return ctx.validateAnnotatedType(def, value instanceof mongodb.ObjectId ? value.toString() : value);
|
|
302
32
|
};
|
|
303
|
-
const validateMongoUniqueArrayItemsPlugin = (ctx, def, value) => {
|
|
304
|
-
if (def.metadata.has("db.mongo.array.uniqueItems") && def.type.kind === "array") {
|
|
305
|
-
if (Array.isArray(value)) {
|
|
306
|
-
const separator = "▼↩";
|
|
307
|
-
const seen = new Set();
|
|
308
|
-
const keyProps = CollectionPatcher.getKeyProps(def);
|
|
309
|
-
for (const item of value) {
|
|
310
|
-
let key = "";
|
|
311
|
-
if (keyProps.size > 0) for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
312
|
-
else key = JSON.stringify(item);
|
|
313
|
-
if (seen.has(key)) {
|
|
314
|
-
ctx.error(`Duplicate items are not allowed`);
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
seen.add(key);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return undefined;
|
|
322
|
-
};
|
|
323
33
|
|
|
324
34
|
//#endregion
|
|
325
35
|
//#region packages/mongo/src/lib/collection-patcher.ts
|
|
@@ -354,25 +64,25 @@ var CollectionPatcher = class CollectionPatcher {
|
|
|
354
64
|
* Build a runtime *Validator* that understands the extended patch payload.
|
|
355
65
|
*
|
|
356
66
|
* * Adds per‑array *patch* wrappers (the `$replace`, `$insert`, … fields).
|
|
357
|
-
* * Honors `db.
|
|
67
|
+
* * Honors `db.patch.strategy === "merge"` metadata.
|
|
358
68
|
*
|
|
359
69
|
* @param collection Target collection wrapper
|
|
360
70
|
* @returns Atscript Validator
|
|
361
|
-
*/ static prepareValidator(
|
|
362
|
-
return
|
|
363
|
-
plugins: [validateMongoIdPlugin
|
|
71
|
+
*/ static prepareValidator(context) {
|
|
72
|
+
return context.createValidator({
|
|
73
|
+
plugins: [validateMongoIdPlugin],
|
|
364
74
|
replace: (def, path) => {
|
|
365
75
|
if (path === "" && def.type.kind === "object") {
|
|
366
76
|
const obj = (0, __atscript_typescript_utils.defineAnnotatedType)("object").copyMetadata(def.metadata);
|
|
367
77
|
for (const [prop, type] of def.type.props.entries()) obj.prop(prop, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(type).copyMetadata(type.metadata).optional(prop !== "_id").$type);
|
|
368
78
|
return obj.$type;
|
|
369
79
|
}
|
|
370
|
-
if (def.type.kind === "array" &&
|
|
80
|
+
if (def.type.kind === "array" && context.flatMap.get(path)?.metadata.get("db.mongo.__topLevelArray") && !def.metadata.has("db.mongo.__patchArrayValue")) {
|
|
371
81
|
const defArray = def;
|
|
372
|
-
const mergeStrategy = defArray.metadata.get("db.
|
|
82
|
+
const mergeStrategy = defArray.metadata.get("db.patch.strategy") === "merge";
|
|
373
83
|
function getPatchType() {
|
|
374
|
-
const isPrimitive
|
|
375
|
-
if (isPrimitive
|
|
84
|
+
const isPrimitive = (0, __atscript_typescript_utils.isAnnotatedTypeOfPrimitive)(defArray.type.of);
|
|
85
|
+
if (isPrimitive) return (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(def).copyMetadata(def.metadata).annotate("db.mongo.__patchArrayValue").optional().$type;
|
|
376
86
|
if (defArray.type.of.type.kind === "object") {
|
|
377
87
|
const objType = defArray.type.of.type;
|
|
378
88
|
const t = (0, __atscript_typescript_utils.defineAnnotatedType)("object").copyMetadata(defArray.type.of.metadata);
|
|
@@ -390,7 +100,7 @@ else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(va
|
|
|
390
100
|
}
|
|
391
101
|
return def;
|
|
392
102
|
},
|
|
393
|
-
partial: (def, path) => path !== "" && def.metadata.get("db.
|
|
103
|
+
partial: (def, path) => path !== "" && def.metadata.get("db.patch.strategy") === "merge"
|
|
394
104
|
});
|
|
395
105
|
}
|
|
396
106
|
/**
|
|
@@ -443,7 +153,7 @@ else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(va
|
|
|
443
153
|
const flatType = this.collection.flatMap.get(key);
|
|
444
154
|
const topLevelArray = flatType?.metadata?.get("db.mongo.__topLevelArray");
|
|
445
155
|
if (typeof value === "object" && topLevelArray) this.parseArrayPatch(key, value);
|
|
446
|
-
else if (typeof value === "object" && this.collection.flatMap.get(key)?.metadata?.get("db.
|
|
156
|
+
else if (typeof value === "object" && this.collection.flatMap.get(key)?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
|
|
447
157
|
else if (key !== "_id") this._set(key, value);
|
|
448
158
|
}
|
|
449
159
|
return this.updatePipeline;
|
|
@@ -497,7 +207,7 @@ else if (key !== "_id") this._set(key, value);
|
|
|
497
207
|
* - unique / keyed → delegate to _upsert (insert-or-update)
|
|
498
208
|
*/ _insert(key, input, keyProps) {
|
|
499
209
|
if (!input?.length) return;
|
|
500
|
-
const uniqueItems = this.collection.flatMap.get(key)?.metadata?.has("
|
|
210
|
+
const uniqueItems = this.collection.flatMap.get(key)?.metadata?.has("expect.array.uniqueItems");
|
|
501
211
|
if (uniqueItems || keyProps.size > 0) this._upsert(key, input, keyProps);
|
|
502
212
|
else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
|
|
503
213
|
}
|
|
@@ -535,7 +245,7 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
|
|
|
535
245
|
*/ _update(key, input, keyProps) {
|
|
536
246
|
if (!input?.length) return;
|
|
537
247
|
if (keyProps.size > 0) {
|
|
538
|
-
const mergeStrategy = this.collection.flatMap.get(key)?.metadata?.get("db.
|
|
248
|
+
const mergeStrategy = this.collection.flatMap.get(key)?.metadata?.get("db.patch.strategy") === "merge";
|
|
539
249
|
const keys = [...keyProps];
|
|
540
250
|
this._set(key, { $reduce: {
|
|
541
251
|
input,
|
|
@@ -592,17 +302,34 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
|
|
|
592
302
|
};
|
|
593
303
|
|
|
594
304
|
//#endregion
|
|
595
|
-
//#region packages/mongo/src/lib/
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
305
|
+
//#region packages/mongo/src/lib/mongo-filter.ts
|
|
306
|
+
const EMPTY = {};
|
|
307
|
+
const mongoVisitor = {
|
|
308
|
+
comparison(field, op, value) {
|
|
309
|
+
if (op === "$eq") return { [field]: value };
|
|
310
|
+
return { [field]: { [op]: value } };
|
|
311
|
+
},
|
|
312
|
+
and(children) {
|
|
313
|
+
if (children.length === 0) return EMPTY;
|
|
314
|
+
if (children.length === 1) return children[0];
|
|
315
|
+
return { $and: children };
|
|
316
|
+
},
|
|
317
|
+
or(children) {
|
|
318
|
+
if (children.length === 0) return { _impossible: true };
|
|
319
|
+
if (children.length === 1) return children[0];
|
|
320
|
+
return { $or: children };
|
|
321
|
+
},
|
|
322
|
+
not(child) {
|
|
323
|
+
return { $nor: [child] };
|
|
324
|
+
}
|
|
602
325
|
};
|
|
326
|
+
function buildMongoFilter(filter) {
|
|
327
|
+
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
328
|
+
return (0, __atscript_utils_db.walkFilter)(filter, mongoVisitor);
|
|
329
|
+
}
|
|
603
330
|
|
|
604
331
|
//#endregion
|
|
605
|
-
//#region packages/mongo/src/lib/
|
|
332
|
+
//#region packages/mongo/src/lib/mongo-adapter.ts
|
|
606
333
|
function _define_property$1(obj, key, value) {
|
|
607
334
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
608
335
|
value,
|
|
@@ -615,43 +342,38 @@ else obj[key] = value;
|
|
|
615
342
|
}
|
|
616
343
|
const INDEX_PREFIX = "atscript__";
|
|
617
344
|
const DEFAULT_INDEX_NAME = "DEFAULT";
|
|
618
|
-
|
|
619
|
-
* Generates a key for mongo index
|
|
620
|
-
* @param type index type
|
|
621
|
-
* @param name index name
|
|
622
|
-
* @returns index key
|
|
623
|
-
*/ function indexKey(type, name) {
|
|
345
|
+
function mongoIndexKey(type, name) {
|
|
624
346
|
const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
|
|
625
347
|
return `${INDEX_PREFIX}${type}__${cleanName}`;
|
|
626
348
|
}
|
|
627
|
-
var
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
async exists() {
|
|
632
|
-
return this.asMongo.collectionExists(this.name);
|
|
349
|
+
var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
350
|
+
get collection() {
|
|
351
|
+
if (!this._collection) this._collection = this.db.collection(this.resolveTableName(false));
|
|
352
|
+
return this._collection;
|
|
633
353
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if (!exists) await this.asMongo.db.createCollection(this.name, { comment: "Created by Atscript Mongo Collection" });
|
|
354
|
+
aggregate(pipeline) {
|
|
355
|
+
return this.collection.aggregate(pipeline);
|
|
637
356
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
*/ get idType() {
|
|
641
|
-
const idProp = this.type.type.props.get("_id");
|
|
357
|
+
get idType() {
|
|
358
|
+
const idProp = this._table.type.type.props.get("_id");
|
|
642
359
|
const idTags = idProp?.type.tags;
|
|
643
360
|
if (idTags?.has("objectId") && idTags?.has("mongo")) return "objectId";
|
|
644
361
|
if (idProp?.type.kind === "") return idProp.type.designType;
|
|
645
362
|
return "objectId";
|
|
646
363
|
}
|
|
364
|
+
prepareId(id, fieldType) {
|
|
365
|
+
const tags = fieldType.type.tags;
|
|
366
|
+
if (tags?.has("objectId") && tags?.has("mongo")) return id instanceof mongodb.ObjectId ? id : new mongodb.ObjectId(id);
|
|
367
|
+
if (fieldType.type.kind === "") {
|
|
368
|
+
const dt = fieldType.type.designType;
|
|
369
|
+
if (dt === "number") return Number(id);
|
|
370
|
+
}
|
|
371
|
+
return String(id);
|
|
372
|
+
}
|
|
647
373
|
/**
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
|
|
651
|
-
* @param {string | number | ObjectId} id - The validated ID.
|
|
652
|
-
* @returns {string | number | ObjectId} - The transformed ID.
|
|
653
|
-
* @throws {Error} If the `_id` type is unknown.
|
|
654
|
-
*/ prepareId(id) {
|
|
374
|
+
* Convenience method that uses `idType` to transform an ID value.
|
|
375
|
+
* For use in controllers that don't have access to the field type.
|
|
376
|
+
*/ prepareIdFromIdType(id) {
|
|
655
377
|
switch (this.idType) {
|
|
656
378
|
case "objectId": return id instanceof mongodb.ObjectId ? id : new mongodb.ObjectId(id);
|
|
657
379
|
case "number": return Number(id);
|
|
@@ -659,91 +381,59 @@ var AsCollection = class {
|
|
|
659
381
|
default: throw new Error("Unknown \"_id\" type");
|
|
660
382
|
}
|
|
661
383
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
384
|
+
supportsNestedObjects() {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
supportsNativePatch() {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
getValidatorPlugins() {
|
|
391
|
+
return [validateMongoIdPlugin];
|
|
392
|
+
}
|
|
393
|
+
getTopLevelArrayTag() {
|
|
394
|
+
return "db.mongo.__topLevelArray";
|
|
395
|
+
}
|
|
396
|
+
getAdapterTableName(type) {
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
buildInsertValidator(table) {
|
|
400
|
+
return table.createValidator({
|
|
401
|
+
plugins: this.getValidatorPlugins(),
|
|
402
|
+
replace: (type, path) => {
|
|
403
|
+
if (path === "_id" && type.type.tags?.has("objectId")) return {
|
|
404
|
+
...type,
|
|
405
|
+
optional: true
|
|
406
|
+
};
|
|
407
|
+
if (table.defaults.has(path)) return {
|
|
408
|
+
...type,
|
|
409
|
+
optional: true
|
|
410
|
+
};
|
|
411
|
+
return type;
|
|
683
412
|
}
|
|
684
|
-
|
|
685
|
-
this.validators.set(purpose, this.createValidator({ plugins: [validateMongoIdPlugin] }));
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
case "patch": {
|
|
689
|
-
this.validators.set(purpose, CollectionPatcher.prepareValidator(this));
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
default: throw new Error(`Unknown validator purpose: ${purpose}`);
|
|
693
|
-
}
|
|
694
|
-
return this.validators.get(purpose);
|
|
695
|
-
}
|
|
696
|
-
get type() {
|
|
697
|
-
return this._type;
|
|
413
|
+
});
|
|
698
414
|
}
|
|
699
|
-
|
|
700
|
-
this.
|
|
701
|
-
return this._indexes;
|
|
415
|
+
buildPatchValidator(table) {
|
|
416
|
+
return CollectionPatcher.prepareValidator(this.getPatcherContext());
|
|
702
417
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const weights = {};
|
|
710
|
-
index = {
|
|
711
|
-
key,
|
|
712
|
-
name,
|
|
713
|
-
type,
|
|
714
|
-
fields: { [field]: value },
|
|
715
|
-
weights
|
|
716
|
-
};
|
|
717
|
-
this._indexes.set(key, index);
|
|
718
|
-
}
|
|
719
|
-
if (weight) index.weights[field] = weight;
|
|
720
|
-
}
|
|
721
|
-
_setSearchIndex(type, name, definition) {
|
|
722
|
-
const key = indexKey(type, name || DEFAULT_INDEX_NAME);
|
|
723
|
-
this._indexes.set(key, {
|
|
724
|
-
key,
|
|
725
|
-
name: name || DEFAULT_INDEX_NAME,
|
|
726
|
-
type,
|
|
727
|
-
definition
|
|
728
|
-
});
|
|
418
|
+
/** Returns the context object used by CollectionPatcher. */ getPatcherContext() {
|
|
419
|
+
return {
|
|
420
|
+
flatMap: this._table.flatMap,
|
|
421
|
+
prepareId: (id) => this.prepareIdFromIdType(id),
|
|
422
|
+
createValidator: (opts) => this._table.createValidator(opts)
|
|
423
|
+
};
|
|
729
424
|
}
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
}
|
|
740
|
-
if (index) {
|
|
741
|
-
index.definition.mappings.fields[fieldName] = { type: "string" };
|
|
742
|
-
if (analyzer) index.definition.mappings.fields[fieldName].analyzer = analyzer;
|
|
743
|
-
}
|
|
425
|
+
async nativePatch(filter, patch) {
|
|
426
|
+
const mongoFilter = buildMongoFilter(filter);
|
|
427
|
+
const patcher = new CollectionPatcher(this.getPatcherContext(), patch);
|
|
428
|
+
const { updateFilter, updateOptions } = patcher.preparePatch();
|
|
429
|
+
const result = await this.collection.updateOne(mongoFilter, updateFilter, updateOptions);
|
|
430
|
+
return {
|
|
431
|
+
matchedCount: result.matchedCount,
|
|
432
|
+
modifiedCount: result.modifiedCount
|
|
433
|
+
};
|
|
744
434
|
}
|
|
745
|
-
|
|
746
|
-
const typeMeta =
|
|
435
|
+
onBeforeFlatten(type) {
|
|
436
|
+
const typeMeta = type.metadata;
|
|
747
437
|
const dynamicText = typeMeta.get("db.mongo.search.dynamic");
|
|
748
438
|
if (dynamicText) this._setSearchIndex("dynamic_text", "_", {
|
|
749
439
|
mappings: { dynamic: true },
|
|
@@ -756,73 +446,65 @@ else {
|
|
|
756
446
|
text: { fuzzy: { maxEdits: textSearch.fuzzy || 0 } }
|
|
757
447
|
});
|
|
758
448
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const index = this._indexes.get(key);
|
|
765
|
-
if (index && index.type === "vector") index.definition.fields?.push({
|
|
766
|
-
type: "filter",
|
|
767
|
-
path: value
|
|
768
|
-
});
|
|
449
|
+
onFieldScanned(field, type, metadata) {
|
|
450
|
+
if (field !== "_id" && metadata.has("meta.id")) {
|
|
451
|
+
this._table.removePrimaryKey(field);
|
|
452
|
+
this._addMongoIndexField("unique", "__pk", field);
|
|
453
|
+
this._table.addUniqueField(field);
|
|
769
454
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
_prepareIndexesForField(fieldName, metadata) {
|
|
776
|
-
for (const index of metadata.get("db.index.plain") || []) {
|
|
777
|
-
const name = index === true ? fieldName : index.name || fieldName;
|
|
778
|
-
this._addIndexField("plain", name, fieldName);
|
|
779
|
-
}
|
|
780
|
-
for (const index of metadata.get("db.index.unique") || []) {
|
|
781
|
-
const name = index === true ? fieldName : index.name || fieldName;
|
|
782
|
-
this._addIndexField("unique", name, fieldName);
|
|
455
|
+
const defaultFn = metadata.get("db.default.fn");
|
|
456
|
+
if (defaultFn === "increment") {
|
|
457
|
+
const physicalName = metadata.get("db.column") ?? field;
|
|
458
|
+
this._incrementFields.add(physicalName);
|
|
783
459
|
}
|
|
784
460
|
for (const index of metadata.get("db.index.fulltext") || []) {
|
|
785
461
|
const name = index === true ? "" : index.name || "";
|
|
786
|
-
|
|
462
|
+
const weight = index !== true && typeof index === "object" ? index.weight || 1 : 1;
|
|
463
|
+
this._addMongoIndexField("text", name, field, weight);
|
|
787
464
|
}
|
|
788
|
-
const
|
|
789
|
-
if (textWeight) this._addIndexField("text", "", fieldName, textWeight === true ? 1 : textWeight);
|
|
790
|
-
for (const index of metadata.get("db.mongo.search.text") || []) this._addFieldToSearchIndex("search_text", index.indexName, fieldName, index.analyzer);
|
|
465
|
+
for (const index of metadata.get("db.mongo.search.text") || []) this._addFieldToSearchIndex("search_text", index.indexName, field, index.analyzer);
|
|
791
466
|
const vectorIndex = metadata.get("db.mongo.search.vector");
|
|
792
|
-
if (vectorIndex) this._setSearchIndex("vector", vectorIndex.indexName ||
|
|
467
|
+
if (vectorIndex) this._setSearchIndex("vector", vectorIndex.indexName || field, { fields: [{
|
|
793
468
|
type: "vector",
|
|
794
|
-
path:
|
|
469
|
+
path: field,
|
|
795
470
|
similarity: vectorIndex.similarity || "dotProduct",
|
|
796
471
|
numDimensions: vectorIndex.dimensions
|
|
797
472
|
}] });
|
|
798
|
-
for (const index of metadata.get("db.mongo.search.filter") || []) this._vectorFilters.set(
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
473
|
+
for (const index of metadata.get("db.mongo.search.filter") || []) this._vectorFilters.set(mongoIndexKey("vector", index.indexName), field);
|
|
474
|
+
}
|
|
475
|
+
onAfterFlatten() {
|
|
476
|
+
this._table.addPrimaryKey("_id");
|
|
477
|
+
for (const [key, value] of this._vectorFilters.entries()) {
|
|
478
|
+
const index = this._mongoIndexes.get(key);
|
|
479
|
+
if (index && index.type === "vector") index.definition.fields?.push({
|
|
480
|
+
type: "filter",
|
|
481
|
+
path: value
|
|
807
482
|
});
|
|
808
|
-
this._finalizeIndexesForCollection();
|
|
809
483
|
}
|
|
810
484
|
}
|
|
811
|
-
|
|
485
|
+
/** Returns MongoDB-specific search index map (internal). */ getMongoSearchIndexes() {
|
|
812
486
|
if (!this._searchIndexesMap) {
|
|
487
|
+
this._table.flatMap;
|
|
813
488
|
this._searchIndexesMap = new Map();
|
|
814
|
-
let
|
|
815
|
-
for (const index of this.indexes.values())
|
|
489
|
+
let defaultIndex;
|
|
490
|
+
for (const index of this._table.indexes.values()) if (index.type === "fulltext" && !defaultIndex) defaultIndex = {
|
|
491
|
+
key: index.key,
|
|
492
|
+
name: index.name,
|
|
493
|
+
type: "text",
|
|
494
|
+
fields: Object.fromEntries(index.fields.map((f) => [f.name, "text"])),
|
|
495
|
+
weights: Object.fromEntries(index.fields.filter((f) => f.weight).map((f) => [f.name, f.weight]))
|
|
496
|
+
};
|
|
497
|
+
for (const index of this._mongoIndexes.values()) switch (index.type) {
|
|
816
498
|
case "text": {
|
|
817
|
-
if (!
|
|
499
|
+
if (!defaultIndex) defaultIndex = index;
|
|
818
500
|
break;
|
|
819
501
|
}
|
|
820
502
|
case "dynamic_text": {
|
|
821
|
-
|
|
503
|
+
defaultIndex = index;
|
|
822
504
|
break;
|
|
823
505
|
}
|
|
824
506
|
case "search_text": {
|
|
825
|
-
if (!
|
|
507
|
+
if (!defaultIndex || defaultIndex.type === "text") defaultIndex = index;
|
|
826
508
|
this._searchIndexesMap.set(index.name, index);
|
|
827
509
|
break;
|
|
828
510
|
}
|
|
@@ -832,21 +514,231 @@ else {
|
|
|
832
514
|
}
|
|
833
515
|
default:
|
|
834
516
|
}
|
|
835
|
-
if (
|
|
517
|
+
if (defaultIndex && !this._searchIndexesMap.has(DEFAULT_INDEX_NAME)) this._searchIndexesMap.set(DEFAULT_INDEX_NAME, defaultIndex);
|
|
836
518
|
}
|
|
837
519
|
return this._searchIndexesMap;
|
|
838
520
|
}
|
|
839
|
-
|
|
840
|
-
return this.
|
|
521
|
+
/** Returns a specific MongoDB search index by name. */ getMongoSearchIndex(name = DEFAULT_INDEX_NAME) {
|
|
522
|
+
return this.getMongoSearchIndexes().get(name);
|
|
523
|
+
}
|
|
524
|
+
/** Returns available search indexes as generic metadata for UI. */ getSearchIndexes() {
|
|
525
|
+
const mongoIndexes = this.getMongoSearchIndexes();
|
|
526
|
+
return [...mongoIndexes.entries()].map(([name, index]) => ({
|
|
527
|
+
name,
|
|
528
|
+
description: `${index.type} index`
|
|
529
|
+
}));
|
|
841
530
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
531
|
+
/**
|
|
532
|
+
* Builds a MongoDB `$search` pipeline stage.
|
|
533
|
+
* Override `buildVectorSearchStage` in subclasses to provide embeddings.
|
|
534
|
+
*/ buildSearchStage(text, indexName) {
|
|
535
|
+
const index = this.getMongoSearchIndex(indexName);
|
|
536
|
+
if (!index) return undefined;
|
|
537
|
+
if (index.type === "vector") return this.buildVectorSearchStage(text, index);
|
|
538
|
+
return { $search: {
|
|
539
|
+
index: index.key,
|
|
540
|
+
text: {
|
|
541
|
+
query: text,
|
|
542
|
+
path: { wildcard: "*" }
|
|
543
|
+
}
|
|
544
|
+
} };
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Builds a vector search stage. Override in subclasses to generate embeddings.
|
|
548
|
+
* Returns `undefined` by default (vector search requires custom implementation).
|
|
549
|
+
*/ buildVectorSearchStage(text, index) {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
async search(text, query, indexName) {
|
|
553
|
+
const searchStage = this.buildSearchStage(text, indexName);
|
|
554
|
+
if (!searchStage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
|
|
555
|
+
const filter = buildMongoFilter(query.filter);
|
|
556
|
+
const controls = query.controls || {};
|
|
557
|
+
const pipeline = [searchStage, { $match: filter }];
|
|
558
|
+
if (controls.$sort) pipeline.push({ $sort: controls.$sort });
|
|
559
|
+
if (controls.$skip) pipeline.push({ $skip: controls.$skip });
|
|
560
|
+
if (controls.$limit) pipeline.push({ $limit: controls.$limit });
|
|
561
|
+
else pipeline.push({ $limit: 1e3 });
|
|
562
|
+
if (controls.$select) pipeline.push({ $project: controls.$select.asProjection });
|
|
563
|
+
return this.collection.aggregate(pipeline).toArray();
|
|
564
|
+
}
|
|
565
|
+
async searchWithCount(text, query, indexName) {
|
|
566
|
+
const searchStage = this.buildSearchStage(text, indexName);
|
|
567
|
+
if (!searchStage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
|
|
568
|
+
const filter = buildMongoFilter(query.filter);
|
|
569
|
+
const controls = query.controls || {};
|
|
570
|
+
const pipeline = [
|
|
571
|
+
searchStage,
|
|
572
|
+
{ $match: filter },
|
|
573
|
+
{ $facet: {
|
|
574
|
+
data: [
|
|
575
|
+
controls.$sort ? { $sort: controls.$sort } : undefined,
|
|
576
|
+
controls.$skip ? { $skip: controls.$skip } : undefined,
|
|
577
|
+
controls.$limit ? { $limit: controls.$limit } : undefined,
|
|
578
|
+
controls.$select ? { $project: controls.$select.asProjection } : undefined
|
|
579
|
+
].filter(Boolean),
|
|
580
|
+
meta: [{ $count: "count" }]
|
|
581
|
+
} }
|
|
582
|
+
];
|
|
583
|
+
const result = await this.collection.aggregate(pipeline).toArray();
|
|
584
|
+
return {
|
|
585
|
+
data: result[0]?.data || [],
|
|
586
|
+
count: result[0]?.meta[0]?.count || 0
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async findManyWithCount(query) {
|
|
590
|
+
const filter = buildMongoFilter(query.filter);
|
|
591
|
+
const controls = query.controls || {};
|
|
592
|
+
const pipeline = [{ $match: filter }, { $facet: {
|
|
593
|
+
data: [
|
|
594
|
+
controls.$sort ? { $sort: controls.$sort } : undefined,
|
|
595
|
+
controls.$skip ? { $skip: controls.$skip } : undefined,
|
|
596
|
+
controls.$limit ? { $limit: controls.$limit } : undefined,
|
|
597
|
+
controls.$select ? { $project: controls.$select.asProjection } : undefined
|
|
598
|
+
].filter(Boolean),
|
|
599
|
+
meta: [{ $count: "count" }]
|
|
600
|
+
} }];
|
|
601
|
+
const result = await this.collection.aggregate(pipeline).toArray();
|
|
602
|
+
return {
|
|
603
|
+
data: result[0]?.data || [],
|
|
604
|
+
count: result[0]?.meta[0]?.count || 0
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
async collectionExists() {
|
|
608
|
+
if (this.asMongo) return this.asMongo.collectionExists(this._table.tableName);
|
|
609
|
+
const cols = await this.db.listCollections({ name: this._table.tableName }).toArray();
|
|
610
|
+
return cols.length > 0;
|
|
611
|
+
}
|
|
612
|
+
async ensureCollectionExists() {
|
|
613
|
+
const exists = await this.collectionExists();
|
|
614
|
+
if (!exists) await this.db.createCollection(this._table.tableName, { comment: "Created by Atscript Mongo Adapter" });
|
|
615
|
+
}
|
|
616
|
+
async insertOne(data) {
|
|
617
|
+
if (this._incrementFields.size > 0) {
|
|
618
|
+
const fields = this._fieldsNeedingIncrement(data);
|
|
619
|
+
if (fields.length > 0) {
|
|
620
|
+
const maxValues = await this._getMaxValues(fields);
|
|
621
|
+
for (const physical of fields) data[physical] = (maxValues.get(physical) ?? 0) + 1;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const result = await this.collection.insertOne(data);
|
|
625
|
+
return { insertedId: result.insertedId };
|
|
626
|
+
}
|
|
627
|
+
async insertMany(data) {
|
|
628
|
+
if (this._incrementFields.size > 0) {
|
|
629
|
+
const allFields = new Set();
|
|
630
|
+
for (const item of data) for (const f of this._fieldsNeedingIncrement(item)) allFields.add(f);
|
|
631
|
+
if (allFields.size > 0) {
|
|
632
|
+
const maxValues = await this._getMaxValues([...allFields]);
|
|
633
|
+
for (const item of data) for (const physical of allFields) if (item[physical] === undefined || item[physical] === null) {
|
|
634
|
+
const next = (maxValues.get(physical) ?? 0) + 1;
|
|
635
|
+
item[physical] = next;
|
|
636
|
+
maxValues.set(physical, next);
|
|
637
|
+
} else if (typeof item[physical] === "number") {
|
|
638
|
+
const current = maxValues.get(physical) ?? 0;
|
|
639
|
+
if (item[physical] > current) maxValues.set(physical, item[physical]);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const result = await this.collection.insertMany(data);
|
|
644
|
+
return {
|
|
645
|
+
insertedCount: result.insertedCount,
|
|
646
|
+
insertedIds: Object.values(result.insertedIds)
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
async findOne(query) {
|
|
650
|
+
const filter = buildMongoFilter(query.filter);
|
|
651
|
+
const opts = this._buildFindOptions(query.controls);
|
|
652
|
+
return this.collection.findOne(filter, opts);
|
|
653
|
+
}
|
|
654
|
+
async findMany(query) {
|
|
655
|
+
const filter = buildMongoFilter(query.filter);
|
|
656
|
+
const opts = this._buildFindOptions(query.controls);
|
|
657
|
+
return this.collection.find(filter, opts).toArray();
|
|
658
|
+
}
|
|
659
|
+
async count(query) {
|
|
660
|
+
const filter = buildMongoFilter(query.filter);
|
|
661
|
+
return this.collection.countDocuments(filter);
|
|
662
|
+
}
|
|
663
|
+
async updateOne(filter, data) {
|
|
664
|
+
const mongoFilter = buildMongoFilter(filter);
|
|
665
|
+
const result = await this.collection.updateOne(mongoFilter, { $set: data });
|
|
666
|
+
return {
|
|
667
|
+
matchedCount: result.matchedCount,
|
|
668
|
+
modifiedCount: result.modifiedCount
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async replaceOne(filter, data) {
|
|
672
|
+
const mongoFilter = buildMongoFilter(filter);
|
|
673
|
+
const result = await this.collection.replaceOne(mongoFilter, data);
|
|
674
|
+
return {
|
|
675
|
+
matchedCount: result.matchedCount,
|
|
676
|
+
modifiedCount: result.modifiedCount
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
async deleteOne(filter) {
|
|
680
|
+
const mongoFilter = buildMongoFilter(filter);
|
|
681
|
+
const result = await this.collection.deleteOne(mongoFilter);
|
|
682
|
+
return { deletedCount: result.deletedCount };
|
|
683
|
+
}
|
|
684
|
+
async updateMany(filter, data) {
|
|
685
|
+
const mongoFilter = buildMongoFilter(filter);
|
|
686
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data });
|
|
687
|
+
return {
|
|
688
|
+
matchedCount: result.matchedCount,
|
|
689
|
+
modifiedCount: result.modifiedCount
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
async replaceMany(filter, data) {
|
|
693
|
+
const mongoFilter = buildMongoFilter(filter);
|
|
694
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data });
|
|
695
|
+
return {
|
|
696
|
+
matchedCount: result.matchedCount,
|
|
697
|
+
modifiedCount: result.modifiedCount
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
async deleteMany(filter) {
|
|
701
|
+
const mongoFilter = buildMongoFilter(filter);
|
|
702
|
+
const result = await this.collection.deleteMany(mongoFilter);
|
|
703
|
+
return { deletedCount: result.deletedCount };
|
|
704
|
+
}
|
|
705
|
+
async ensureTable() {
|
|
706
|
+
return this.ensureCollectionExists();
|
|
845
707
|
}
|
|
846
708
|
async syncIndexes() {
|
|
847
|
-
await this.
|
|
709
|
+
await this.ensureCollectionExists();
|
|
710
|
+
const allIndexes = new Map();
|
|
711
|
+
for (const [key, index] of this._table.indexes.entries()) {
|
|
712
|
+
const fields = {};
|
|
713
|
+
const weights = {};
|
|
714
|
+
let mongoType;
|
|
715
|
+
if (index.type === "fulltext") {
|
|
716
|
+
mongoType = "text";
|
|
717
|
+
for (const f of index.fields) {
|
|
718
|
+
fields[f.name] = "text";
|
|
719
|
+
if (f.weight) weights[f.name] = f.weight;
|
|
720
|
+
}
|
|
721
|
+
} else {
|
|
722
|
+
mongoType = index.type;
|
|
723
|
+
for (const f of index.fields) fields[f.name] = 1;
|
|
724
|
+
}
|
|
725
|
+
allIndexes.set(key, {
|
|
726
|
+
key,
|
|
727
|
+
name: index.name,
|
|
728
|
+
type: mongoType,
|
|
729
|
+
fields,
|
|
730
|
+
weights
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
for (const [key, index] of this._mongoIndexes.entries()) if (index.type === "text") {
|
|
734
|
+
const existing = allIndexes.get(key);
|
|
735
|
+
if (existing && existing.type === "text") {
|
|
736
|
+
Object.assign(existing.fields, index.fields);
|
|
737
|
+
Object.assign(existing.weights, index.weights);
|
|
738
|
+
} else allIndexes.set(key, index);
|
|
739
|
+
} else allIndexes.set(key, index);
|
|
848
740
|
const existingIndexes = await this.collection.listIndexes().toArray();
|
|
849
|
-
const indexesToCreate = new Map(
|
|
741
|
+
const indexesToCreate = new Map(allIndexes);
|
|
850
742
|
for (const remote of existingIndexes) {
|
|
851
743
|
if (!remote.name.startsWith(INDEX_PREFIX)) continue;
|
|
852
744
|
if (indexesToCreate.has(remote.name)) {
|
|
@@ -856,54 +748,21 @@ else {
|
|
|
856
748
|
case "unique":
|
|
857
749
|
case "text": {
|
|
858
750
|
if ((local.type === "text" || objMatch(local.fields, remote.key)) && objMatch(local.weights || {}, remote.weights || {})) indexesToCreate.delete(remote.name);
|
|
859
|
-
else
|
|
860
|
-
this.logger.debug(`dropping index "${remote.name}"`);
|
|
861
|
-
await this.collection.dropIndex(remote.name);
|
|
862
|
-
}
|
|
863
|
-
break;
|
|
864
|
-
}
|
|
865
|
-
default:
|
|
866
|
-
}
|
|
867
|
-
} else {
|
|
868
|
-
this.logger.debug(`dropping index "${remote.name}"`);
|
|
869
|
-
await this.collection.dropIndex(remote.name);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
const toUpdate = new Set();
|
|
873
|
-
const existingSearchIndexes = await this.collection.listSearchIndexes().toArray();
|
|
874
|
-
for (const remote of existingSearchIndexes) {
|
|
875
|
-
if (!remote.name.startsWith(INDEX_PREFIX)) continue;
|
|
876
|
-
if (indexesToCreate.has(remote.name)) {
|
|
877
|
-
const local = indexesToCreate.get(remote.name);
|
|
878
|
-
const right = remote.latestDefinition;
|
|
879
|
-
switch (local.type) {
|
|
880
|
-
case "dynamic_text":
|
|
881
|
-
case "search_text": {
|
|
882
|
-
const left = local.definition;
|
|
883
|
-
if (left.analyzer === right.analyzer && fieldsMatch(left.mappings.fields || {}, right.mappings.fields || {})) indexesToCreate.delete(remote.name);
|
|
884
|
-
else toUpdate.add(remote.name);
|
|
885
|
-
break;
|
|
886
|
-
}
|
|
887
|
-
case "vector": {
|
|
888
|
-
if (vectorFieldsMatch(local.definition.fields || [], right.fields || [])) indexesToCreate.delete(remote.name);
|
|
889
|
-
else toUpdate.add(remote.name);
|
|
751
|
+
else await this.collection.dropIndex(remote.name);
|
|
890
752
|
break;
|
|
891
753
|
}
|
|
892
754
|
default:
|
|
893
755
|
}
|
|
894
|
-
} else
|
|
895
|
-
this.logger.debug(`dropping search index "${remote.name}"`);
|
|
896
|
-
await this.collection.dropSearchIndex(remote.name);
|
|
897
|
-
} else this.logger.debug(`search index "${remote.name}" is in deleting status`);
|
|
756
|
+
} else await this.collection.dropIndex(remote.name);
|
|
898
757
|
}
|
|
899
|
-
for (const [key, value] of
|
|
758
|
+
for (const [key, value] of allIndexes.entries()) switch (value.type) {
|
|
900
759
|
case "plain": {
|
|
901
|
-
|
|
760
|
+
if (!indexesToCreate.has(key)) continue;
|
|
902
761
|
await this.collection.createIndex(value.fields, { name: key });
|
|
903
762
|
break;
|
|
904
763
|
}
|
|
905
764
|
case "unique": {
|
|
906
|
-
|
|
765
|
+
if (!indexesToCreate.has(key)) continue;
|
|
907
766
|
await this.collection.createIndex(value.fields, {
|
|
908
767
|
name: key,
|
|
909
768
|
unique: true
|
|
@@ -911,160 +770,168 @@ else toUpdate.add(remote.name);
|
|
|
911
770
|
break;
|
|
912
771
|
}
|
|
913
772
|
case "text": {
|
|
914
|
-
|
|
773
|
+
if (!indexesToCreate.has(key)) continue;
|
|
915
774
|
await this.collection.createIndex(value.fields, {
|
|
916
775
|
weights: value.weights,
|
|
917
776
|
name: key
|
|
918
777
|
});
|
|
919
778
|
break;
|
|
920
779
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
780
|
+
default:
|
|
781
|
+
}
|
|
782
|
+
try {
|
|
783
|
+
const toUpdate = new Set();
|
|
784
|
+
const existingSearchIndexes = await this.collection.listSearchIndexes().toArray();
|
|
785
|
+
for (const remote of existingSearchIndexes) {
|
|
786
|
+
if (!remote.name.startsWith(INDEX_PREFIX)) continue;
|
|
787
|
+
if (indexesToCreate.has(remote.name)) {
|
|
788
|
+
const local = indexesToCreate.get(remote.name);
|
|
789
|
+
const right = remote.latestDefinition;
|
|
790
|
+
switch (local.type) {
|
|
791
|
+
case "dynamic_text":
|
|
792
|
+
case "search_text": {
|
|
793
|
+
const left = local.definition;
|
|
794
|
+
if (left.analyzer === right.analyzer && fieldsMatch(left.mappings.fields || {}, right.mappings.fields || {})) indexesToCreate.delete(remote.name);
|
|
795
|
+
else toUpdate.add(remote.name);
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
798
|
+
case "vector": {
|
|
799
|
+
if (vectorFieldsMatch(local.definition.fields || [], right.fields || [])) indexesToCreate.delete(remote.name);
|
|
800
|
+
else toUpdate.add(remote.name);
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
default:
|
|
804
|
+
}
|
|
805
|
+
} else if (remote.status !== "DELETING") await this.collection.dropSearchIndex(remote.name);
|
|
806
|
+
}
|
|
807
|
+
for (const [key, value] of indexesToCreate.entries()) switch (value.type) {
|
|
808
|
+
case "dynamic_text":
|
|
809
|
+
case "search_text":
|
|
810
|
+
case "vector": {
|
|
811
|
+
if (toUpdate.has(key)) await this.collection.updateSearchIndex(key, value.definition);
|
|
812
|
+
else await this.collection.createSearchIndex({
|
|
930
813
|
name: key,
|
|
931
814
|
type: value.type === "vector" ? "vectorSearch" : "search",
|
|
932
815
|
definition: value.definition
|
|
933
816
|
});
|
|
817
|
+
break;
|
|
934
818
|
}
|
|
935
|
-
|
|
819
|
+
default:
|
|
820
|
+
}
|
|
821
|
+
} catch {}
|
|
822
|
+
}
|
|
823
|
+
/** Returns physical field names of increment fields that are undefined in the data. */ _fieldsNeedingIncrement(data) {
|
|
824
|
+
const result = [];
|
|
825
|
+
for (const physical of this._incrementFields) if (data[physical] === undefined || data[physical] === null) result.push(physical);
|
|
826
|
+
return result;
|
|
827
|
+
}
|
|
828
|
+
/** Reads current max value for each field via $group aggregation. */ async _getMaxValues(physicalFields) {
|
|
829
|
+
const aliases = physicalFields.map((f) => [`max__${f.replace(/\./g, "__")}`, f]);
|
|
830
|
+
const group = { _id: null };
|
|
831
|
+
for (const [alias, field] of aliases) group[alias] = { $max: `$${field}` };
|
|
832
|
+
const result = await this.collection.aggregate([{ $group: group }]).toArray();
|
|
833
|
+
const maxMap = new Map();
|
|
834
|
+
if (result.length > 0) {
|
|
835
|
+
const row = result[0];
|
|
836
|
+
for (const [alias, field] of aliases) {
|
|
837
|
+
const val = row[alias];
|
|
838
|
+
maxMap.set(field, typeof val === "number" ? val : 0);
|
|
936
839
|
}
|
|
937
|
-
default:
|
|
938
840
|
}
|
|
841
|
+
return maxMap;
|
|
842
|
+
}
|
|
843
|
+
_buildFindOptions(controls) {
|
|
844
|
+
const opts = {};
|
|
845
|
+
if (!controls) return opts;
|
|
846
|
+
if (controls.$sort) opts.sort = controls.$sort;
|
|
847
|
+
if (controls.$limit) opts.limit = controls.$limit;
|
|
848
|
+
if (controls.$skip) opts.skip = controls.$skip;
|
|
849
|
+
if (controls.$select) opts.projection = controls.$select.asProjection;
|
|
850
|
+
return opts;
|
|
851
|
+
}
|
|
852
|
+
_addMongoIndexField(type, name, field, weight) {
|
|
853
|
+
const key = mongoIndexKey(type, name);
|
|
854
|
+
let index = this._mongoIndexes.get(key);
|
|
855
|
+
const value = type === "text" ? "text" : 1;
|
|
856
|
+
if (index) index.fields[field] = value;
|
|
857
|
+
else {
|
|
858
|
+
index = {
|
|
859
|
+
key,
|
|
860
|
+
name,
|
|
861
|
+
type,
|
|
862
|
+
fields: { [field]: value },
|
|
863
|
+
weights: {}
|
|
864
|
+
};
|
|
865
|
+
this._mongoIndexes.set(key, index);
|
|
866
|
+
}
|
|
867
|
+
if (weight) index.weights[field] = weight;
|
|
939
868
|
}
|
|
940
|
-
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
...opts,
|
|
948
|
-
...options
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
update(payload, options) {
|
|
952
|
-
const [filter, update, opts] = this.prepareUpdate(payload).toArgs();
|
|
953
|
-
return this.collection.updateOne(filter, update, {
|
|
954
|
-
...opts,
|
|
955
|
-
...options
|
|
869
|
+
_setSearchIndex(type, name, definition) {
|
|
870
|
+
const key = mongoIndexKey(type, name || DEFAULT_INDEX_NAME);
|
|
871
|
+
this._mongoIndexes.set(key, {
|
|
872
|
+
key,
|
|
873
|
+
name: name || DEFAULT_INDEX_NAME,
|
|
874
|
+
type,
|
|
875
|
+
definition
|
|
956
876
|
});
|
|
957
877
|
}
|
|
958
|
-
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const v = this.getValidator("update");
|
|
972
|
-
if (v.validate(payload)) {
|
|
973
|
-
const _id = this.prepareId(payload._id);
|
|
974
|
-
const data = {
|
|
975
|
-
...payload,
|
|
976
|
-
_id
|
|
977
|
-
};
|
|
978
|
-
return {
|
|
979
|
-
toArgs: () => [
|
|
980
|
-
{ _id },
|
|
981
|
-
data,
|
|
982
|
-
{}
|
|
983
|
-
],
|
|
984
|
-
filter: { _id },
|
|
985
|
-
updateFilter: data,
|
|
986
|
-
updateOptions: {}
|
|
987
|
-
};
|
|
878
|
+
_addFieldToSearchIndex(type, _name, fieldName, analyzer) {
|
|
879
|
+
const name = _name || DEFAULT_INDEX_NAME;
|
|
880
|
+
let index = this._mongoIndexes.get(mongoIndexKey(type, name));
|
|
881
|
+
if (!index && type === "search_text") {
|
|
882
|
+
this._setSearchIndex(type, name, {
|
|
883
|
+
mappings: { fields: {} },
|
|
884
|
+
text: { fuzzy: { maxEdits: 0 } }
|
|
885
|
+
});
|
|
886
|
+
index = this._mongoIndexes.get(mongoIndexKey(type, name));
|
|
887
|
+
}
|
|
888
|
+
if (index) {
|
|
889
|
+
index.definition.mappings.fields[fieldName] = { type: "string" };
|
|
890
|
+
if (analyzer) index.definition.mappings.fields[fieldName].analyzer = analyzer;
|
|
988
891
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
const v = this.getValidator("patch");
|
|
993
|
-
if (v.validate(payload)) return new CollectionPatcher(this, payload).preparePatch();
|
|
994
|
-
throw new Error("Invalid payload");
|
|
995
|
-
}
|
|
996
|
-
constructor(asMongo, _type, logger = NoopLogger) {
|
|
997
|
-
_define_property$1(this, "asMongo", void 0);
|
|
998
|
-
_define_property$1(this, "_type", void 0);
|
|
999
|
-
_define_property$1(this, "logger", void 0);
|
|
1000
|
-
_define_property$1(this, "name", void 0);
|
|
1001
|
-
_define_property$1(this, "collection", void 0);
|
|
1002
|
-
_define_property$1(this, "validators", void 0);
|
|
1003
|
-
_define_property$1(this, "_indexes", void 0);
|
|
1004
|
-
_define_property$1(this, "_vectorFilters", void 0);
|
|
1005
|
-
_define_property$1(this, "_flatMap", void 0);
|
|
1006
|
-
_define_property$1(this, "_uniqueProps", void 0);
|
|
1007
|
-
_define_property$1(this, "_searchIndexesMap", void 0);
|
|
1008
|
-
this.asMongo = asMongo;
|
|
1009
|
-
this._type = _type;
|
|
1010
|
-
this.logger = logger;
|
|
1011
|
-
this.validators = new Map();
|
|
1012
|
-
this._indexes = new Map();
|
|
1013
|
-
this._vectorFilters = new Map();
|
|
1014
|
-
this._uniqueProps = new Set();
|
|
1015
|
-
if (!(0, __atscript_typescript_utils.isAnnotatedType)(_type)) throw new Error("Atscript Annotated Type expected");
|
|
1016
|
-
const name = _type.metadata.get("db.table");
|
|
1017
|
-
if (!name) throw new Error("@db.table annotation expected with collection name");
|
|
1018
|
-
if (_type.type.kind !== "object") throw new Error("Mongo collection must be an object type");
|
|
1019
|
-
this.name = name;
|
|
1020
|
-
this.collection = asMongo.db.collection(name);
|
|
892
|
+
}
|
|
893
|
+
constructor(db, asMongo) {
|
|
894
|
+
super(), _define_property$1(this, "db", void 0), _define_property$1(this, "asMongo", void 0), _define_property$1(this, "_collection", void 0), _define_property$1(this, "_mongoIndexes", void 0), _define_property$1(this, "_vectorFilters", void 0), _define_property$1(this, "_searchIndexesMap", void 0), _define_property$1(this, "_incrementFields", void 0), this.db = db, this.asMongo = asMongo, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._incrementFields = new Set();
|
|
1021
895
|
}
|
|
1022
896
|
};
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
const rightMap = new Map();
|
|
1029
|
-
(right || []).forEach((f) => rightMap.set(f.path, f));
|
|
1030
|
-
if (leftMap.size === rightMap.size) {
|
|
1031
|
-
let match = true;
|
|
1032
|
-
for (const [key, left$1] of leftMap.entries()) {
|
|
1033
|
-
const right$1 = rightMap.get(key);
|
|
1034
|
-
if (!right$1) {
|
|
1035
|
-
match = false;
|
|
1036
|
-
break;
|
|
1037
|
-
}
|
|
1038
|
-
if (left$1.type === right$1.type && left$1.path === right$1.path && left$1.similarity === right$1.similarity && left$1.numDimensions === right$1.numDimensions) continue;
|
|
1039
|
-
match = false;
|
|
1040
|
-
break;
|
|
1041
|
-
}
|
|
1042
|
-
return match;
|
|
1043
|
-
} else return false;
|
|
897
|
+
function objMatch(o1, o2) {
|
|
898
|
+
const keys1 = Object.keys(o1);
|
|
899
|
+
const keys2 = Object.keys(o2);
|
|
900
|
+
if (keys1.length !== keys2.length) return false;
|
|
901
|
+
return keys1.every((key) => o1[key] === o2[key]);
|
|
1044
902
|
}
|
|
1045
|
-
|
|
1046
|
-
* Search Index fields matching
|
|
1047
|
-
*/ function fieldsMatch(left, right) {
|
|
903
|
+
function fieldsMatch(left, right) {
|
|
1048
904
|
if (!left || !right) return left === right;
|
|
1049
905
|
const leftKeys = Object.keys(left);
|
|
1050
906
|
const rightKeys = Object.keys(right);
|
|
1051
907
|
if (leftKeys.length !== rightKeys.length) return false;
|
|
1052
908
|
return leftKeys.every((key) => {
|
|
1053
909
|
if (!(key in right)) return false;
|
|
1054
|
-
|
|
1055
|
-
const rightField = right[key];
|
|
1056
|
-
return leftField.type === rightField.type && leftField.analyzer === rightField.analyzer;
|
|
910
|
+
return left[key].type === right[key].type && left[key].analyzer === right[key].analyzer;
|
|
1057
911
|
});
|
|
1058
912
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
913
|
+
function vectorFieldsMatch(left, right) {
|
|
914
|
+
const leftMap = new Map(left.map((f) => [f.path, f]));
|
|
915
|
+
const rightMap = new Map((right || []).map((f) => [f.path, f]));
|
|
916
|
+
if (leftMap.size !== rightMap.size) return false;
|
|
917
|
+
for (const [key, l] of leftMap.entries()) {
|
|
918
|
+
const r = rightMap.get(key);
|
|
919
|
+
if (!r) return false;
|
|
920
|
+
if (l.type !== r.type || l.path !== r.path || l.similarity !== r.similarity || l.numDimensions !== r.numDimensions) return false;
|
|
921
|
+
}
|
|
922
|
+
return true;
|
|
1066
923
|
}
|
|
1067
924
|
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region packages/mongo/src/lib/logger.ts
|
|
927
|
+
const NoopLogger = {
|
|
928
|
+
error: () => {},
|
|
929
|
+
warn: () => {},
|
|
930
|
+
log: () => {},
|
|
931
|
+
info: () => {},
|
|
932
|
+
debug: () => {}
|
|
933
|
+
};
|
|
934
|
+
|
|
1068
935
|
//#endregion
|
|
1069
936
|
//#region packages/mongo/src/lib/as-mongo.ts
|
|
1070
937
|
function _define_property(obj, key, value) {
|
|
@@ -1089,27 +956,39 @@ var AsMongo = class {
|
|
|
1089
956
|
const list = await this.getCollectionsList();
|
|
1090
957
|
return list.has(name);
|
|
1091
958
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
959
|
+
getAdapter(type) {
|
|
960
|
+
this._ensureCreated(type);
|
|
961
|
+
return this._adapters.get(type);
|
|
962
|
+
}
|
|
963
|
+
getTable(type, logger) {
|
|
964
|
+
this._ensureCreated(type, logger);
|
|
965
|
+
return this._tables.get(type);
|
|
966
|
+
}
|
|
967
|
+
_ensureCreated(type, logger) {
|
|
968
|
+
if (!this._adapters.has(type)) {
|
|
969
|
+
const adapter = new MongoAdapter(this.db, this);
|
|
970
|
+
const table = new __atscript_utils_db.AtscriptDbTable(type, adapter, logger || this.logger);
|
|
971
|
+
this._adapters.set(type, adapter);
|
|
972
|
+
this._tables.set(type, table);
|
|
1097
973
|
}
|
|
1098
|
-
return collection;
|
|
1099
974
|
}
|
|
1100
975
|
constructor(client, logger = NoopLogger) {
|
|
1101
976
|
_define_property(this, "logger", void 0);
|
|
1102
977
|
_define_property(this, "client", void 0);
|
|
1103
978
|
_define_property(this, "collectionsList", void 0);
|
|
1104
|
-
_define_property(this, "
|
|
979
|
+
_define_property(this, "_adapters", void 0);
|
|
980
|
+
_define_property(this, "_tables", void 0);
|
|
1105
981
|
this.logger = logger;
|
|
1106
|
-
this.
|
|
982
|
+
this._adapters = new WeakMap();
|
|
983
|
+
this._tables = new WeakMap();
|
|
1107
984
|
if (typeof client === "string") this.client = new mongodb.MongoClient(client);
|
|
1108
985
|
else this.client = client;
|
|
1109
986
|
}
|
|
1110
987
|
};
|
|
1111
988
|
|
|
1112
989
|
//#endregion
|
|
1113
|
-
exports.AsCollection = AsCollection
|
|
1114
990
|
exports.AsMongo = AsMongo
|
|
1115
|
-
exports.
|
|
991
|
+
exports.CollectionPatcher = CollectionPatcher
|
|
992
|
+
exports.MongoAdapter = MongoAdapter
|
|
993
|
+
exports.buildMongoFilter = buildMongoFilter
|
|
994
|
+
exports.validateMongoIdPlugin = validateMongoIdPlugin
|