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