@atscript/mongo 0.1.32 → 0.1.33

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 CHANGED
@@ -44,22 +44,6 @@ else obj[key] = value;
44
44
  return obj;
45
45
  }
46
46
  var CollectionPatcher = class CollectionPatcher {
47
- /**
48
- * Extract a set of *key properties* (annotated with `@expect.array.key`) from an
49
- * array‐of‐objects type definition. These keys uniquely identify an element
50
- * inside the array and are later used for `$update`, `$remove` and `$upsert`.
51
- *
52
- * @param def Atscript array type
53
- * @returns Set of property names marked as keys; empty set if none
54
- */ static getKeyProps(def) {
55
- if (def.type.of.type.kind === "object") {
56
- const objType = def.type.of.type;
57
- const keyProps = new Set();
58
- for (const [key, val] of objType.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
59
- return keyProps;
60
- }
61
- return new Set();
62
- }
63
47
  /**
64
48
  * Build a runtime *Validator* that understands the extended patch payload.
65
49
  *
@@ -130,14 +114,12 @@ else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(va
130
114
  * @param val Value to be written
131
115
  * @private
132
116
  */ _set(key, val) {
133
- for (const pipe of this.updatePipeline) {
134
- if (!pipe.$set) pipe.$set = {};
135
- if (!pipe.$set[key]) {
136
- pipe.$set[key] = val;
137
- return;
138
- }
117
+ if (this.currentSetStage && !(key in this.currentSetStage.$set)) {
118
+ this.currentSetStage.$set[key] = val;
119
+ return;
139
120
  }
140
- this.updatePipeline.push({ $set: { [key]: val } });
121
+ this.currentSetStage = { $set: { [key]: val } };
122
+ this.updatePipeline.push(this.currentSetStage);
141
123
  }
142
124
  /**
143
125
  * Recursively walk through the patch *payload* and convert it into `$set`/…
@@ -152,8 +134,8 @@ else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(va
152
134
  const key = evalKey(_key);
153
135
  const flatType = this.collection.flatMap.get(key);
154
136
  const topLevelArray = flatType?.metadata?.get("db.mongo.__topLevelArray");
155
- if (typeof value === "object" && topLevelArray) this.parseArrayPatch(key, value);
156
- else if (typeof value === "object" && this.collection.flatMap.get(key)?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
137
+ if (typeof value === "object" && topLevelArray) this.parseArrayPatch(key, value, flatType);
138
+ else if (typeof value === "object" && flatType?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
157
139
  else if (key !== "_id") this._set(key, value);
158
140
  }
159
141
  return this.updatePipeline;
@@ -165,19 +147,19 @@ else if (key !== "_id") this._set(key, value);
165
147
  * @param key Dotted path to the array field
166
148
  * @param value Payload slice for that field
167
149
  * @private
168
- */ parseArrayPatch(key, value) {
169
- const flatType = this.collection.flatMap.get(key);
150
+ */ parseArrayPatch(key, value, flatType) {
170
151
  const toRemove = value.$remove;
171
152
  const toReplace = value.$replace;
172
153
  const toInsert = value.$insert;
173
154
  const toUpsert = value.$upsert;
174
155
  const toUpdate = value.$update;
175
- const keyProps = flatType?.type.kind === "array" ? CollectionPatcher.getKeyProps(flatType) : new Set();
176
- this._remove(key, toRemove, keyProps);
156
+ const keyProps = flatType.type.kind === "array" ? (0, __atscript_utils_db.getKeyProps)(flatType) : new Set();
157
+ const keys = keyProps.size > 0 ? [...keyProps] : [];
158
+ this._remove(key, toRemove, keys, flatType);
177
159
  this._replace(key, toReplace);
178
- this._insert(key, toInsert, keyProps);
179
- this._upsert(key, toUpsert, keyProps);
180
- this._update(key, toUpdate, keyProps);
160
+ this._insert(key, toInsert, keys, flatType);
161
+ this._upsert(key, toUpsert, keys, flatType);
162
+ this._update(key, toUpdate, keys, flatType);
181
163
  }
182
164
  /**
183
165
  * Build an *aggregation‐expression* that checks equality by **all** keys in
@@ -190,7 +172,8 @@ else if (key !== "_id") this._set(key, value);
190
172
  * @param left Base token for *left* expression (e.g. `"$$el"`)
191
173
  * @param right Base token for *right* expression (e.g. `"$$this"`)
192
174
  */ _keysEqual(keys, left, right) {
193
- return keys.map((k) => ({ $eq: [`${left}.${k}`, `${right}.${k}`] })).reduce((acc, cur) => acc ? { $and: [acc, cur] } : cur);
175
+ const eqs = keys.map((k) => ({ $eq: [`${left}.${k}`, `${right}.${k}`] }));
176
+ return eqs.length === 1 ? eqs[0] : { $and: eqs };
194
177
  }
195
178
  /**
196
179
  * `$replace` – overwrite the entire array with `input`.
@@ -205,20 +188,19 @@ else if (key !== "_id") this._set(key, value);
205
188
  * `$insert`
206
189
  * - plain append → $concatArrays
207
190
  * - unique / keyed → delegate to _upsert (insert-or-update)
208
- */ _insert(key, input, keyProps) {
191
+ */ _insert(key, input, keys, flatType) {
209
192
  if (!input?.length) return;
210
- const uniqueItems = this.collection.flatMap.get(key)?.metadata?.has("expect.array.uniqueItems");
211
- if (uniqueItems || keyProps.size > 0) this._upsert(key, input, keyProps);
193
+ const uniqueItems = flatType.metadata?.has("expect.array.uniqueItems");
194
+ if (uniqueItems || keys.length > 0) this._upsert(key, input, keys, flatType);
212
195
  else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
213
196
  }
214
197
  /**
215
198
  * `$upsert`
216
199
  * - keyed → remove existing matching by key(s) then append candidate
217
200
  * - unique → $setUnion (deep equality)
218
- */ _upsert(key, input, keyProps) {
201
+ */ _upsert(key, input, keys, _flatType) {
219
202
  if (!input?.length) return;
220
- if (keyProps.size > 0) {
221
- const keys = [...keyProps];
203
+ if (keys.length > 0) {
222
204
  this._set(key, { $reduce: {
223
205
  input,
224
206
  initialValue: { $ifNull: [`$${key}`, []] },
@@ -242,11 +224,10 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
242
224
  * `$update`
243
225
  * - keyed → map array and merge / replace matching element(s)
244
226
  * - non-keyed → behave like `$addToSet` (insert only when not present)
245
- */ _update(key, input, keyProps) {
227
+ */ _update(key, input, keys, flatType) {
246
228
  if (!input?.length) return;
247
- if (keyProps.size > 0) {
248
- const mergeStrategy = this.collection.flatMap.get(key)?.metadata?.get("db.patch.strategy") === "merge";
249
- const keys = [...keyProps];
229
+ if (keys.length > 0) {
230
+ const mergeStrategy = flatType.metadata?.get("db.patch.strategy") === "merge";
250
231
  this._set(key, { $reduce: {
251
232
  input,
252
233
  initialValue: { $ifNull: [`$${key}`, []] },
@@ -266,23 +247,21 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
266
247
  * `$remove`
267
248
  * - keyed → filter out any element whose key set matches a payload item
268
249
  * - non-keyed → deep equality remove (`$setDifference`)
269
- */ _remove(key, input, keyProps) {
250
+ */ _remove(key, input, keys, _flatType) {
270
251
  if (!input?.length) return;
271
- if (keyProps.size > 0) {
272
- const keys = [...keyProps];
273
- this._set(key, { $let: {
274
- vars: { rem: input },
275
- in: { $filter: {
276
- input: { $ifNull: [`$${key}`, []] },
277
- as: "el",
278
- cond: { $not: { $anyElementTrue: { $map: {
279
- input: "$$rem",
280
- as: "r",
281
- in: this._keysEqual(keys, "$$el", "$$r")
282
- } } } }
283
- } }
284
- } });
285
- } else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
252
+ if (keys.length > 0) this._set(key, { $let: {
253
+ vars: { rem: input },
254
+ in: { $filter: {
255
+ input: { $ifNull: [`$${key}`, []] },
256
+ as: "el",
257
+ cond: { $not: { $anyElementTrue: { $map: {
258
+ input: "$$rem",
259
+ as: "r",
260
+ in: this._keysEqual(keys, "$$el", "$$r")
261
+ } } } }
262
+ } }
263
+ } });
264
+ else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
286
265
  }
287
266
  constructor(collection, payload) {
288
267
  _define_property$2(this, "collection", void 0);
@@ -292,14 +271,17 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
292
271
  * Filled only with the `_id` field right now.
293
272
  */ _define_property$2(this, "filterObj", void 0);
294
273
  /** MongoDB *update* document being built. */ _define_property$2(this, "updatePipeline", void 0);
274
+ /** Current `$set` stage being populated. */ _define_property$2(this, "currentSetStage", void 0);
295
275
  /** Additional *options* (mainly `arrayFilters`). */ _define_property$2(this, "optionsObj", void 0);
296
276
  this.collection = collection;
297
277
  this.payload = payload;
298
278
  this.filterObj = {};
299
279
  this.updatePipeline = [];
280
+ this.currentSetStage = null;
300
281
  this.optionsObj = {};
301
282
  }
302
283
  };
284
+ _define_property$2(CollectionPatcher, "getKeyProps", __atscript_utils_db.getKeyProps);
303
285
 
304
286
  //#endregion
305
287
  //#region packages/mongo/src/lib/mongo-filter.ts
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { TAtscriptAnnotatedType, TValidatorOptions, Validator, TAtscriptTypeArray, TValidatorPlugin, TMetadataMap } from '@atscript/typescript/utils';
2
- import { AtscriptDbTable, BaseDbAdapter, FilterExpr, TDbUpdateResult, TSearchIndexInfo, DbQuery, TDbInsertResult, TDbInsertManyResult, TDbDeleteResult } from '@atscript/utils-db';
1
+ import { TAtscriptAnnotatedType, TValidatorOptions, Validator, TValidatorPlugin, TMetadataMap } from '@atscript/typescript/utils';
2
+ import { AtscriptDbTable, getKeyProps, BaseDbAdapter, FilterExpr, TDbUpdateResult, TSearchIndexInfo, DbQuery, TDbInsertResult, TDbInsertManyResult, TDbDeleteResult } from '@atscript/utils-db';
3
3
  import * as mongodb from 'mongodb';
4
4
  import { MongoClient, Filter, UpdateFilter, Document, UpdateOptions, Db, Collection, AggregationCursor, ObjectId } from 'mongodb';
5
5
 
@@ -60,15 +60,7 @@ declare class CollectionPatcher {
60
60
  private collection;
61
61
  private payload;
62
62
  constructor(collection: TCollectionPatcherContext, payload: any);
63
- /**
64
- * Extract a set of *key properties* (annotated with `@expect.array.key`) from an
65
- * array‐of‐objects type definition. These keys uniquely identify an element
66
- * inside the array and are later used for `$update`, `$remove` and `$upsert`.
67
- *
68
- * @param def Atscript array type
69
- * @returns Set of property names marked as keys; empty set if none
70
- */
71
- static getKeyProps(def: TAtscriptAnnotatedType<TAtscriptTypeArray>): Set<string>;
63
+ static getKeyProps: typeof getKeyProps;
72
64
  /**
73
65
  * Build a runtime *Validator* that understands the extended patch payload.
74
66
  *
@@ -86,6 +78,8 @@ declare class CollectionPatcher {
86
78
  private filterObj;
87
79
  /** MongoDB *update* document being built. */
88
80
  private updatePipeline;
81
+ /** Current `$set` stage being populated. */
82
+ private currentSetStage;
89
83
  /** Additional *options* (mainly `arrayFilters`). */
90
84
  private optionsObj;
91
85
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AtscriptDbTable, BaseDbAdapter, walkFilter } from "@atscript/utils-db";
1
+ import { AtscriptDbTable, BaseDbAdapter, getKeyProps, walkFilter } from "@atscript/utils-db";
2
2
  import { MongoClient, ObjectId } from "mongodb";
3
3
  import { defineAnnotatedType, isAnnotatedTypeOfPrimitive } from "@atscript/typescript/utils";
4
4
 
@@ -20,22 +20,6 @@ else obj[key] = value;
20
20
  return obj;
21
21
  }
22
22
  var CollectionPatcher = class CollectionPatcher {
23
- /**
24
- * Extract a set of *key properties* (annotated with `@expect.array.key`) from an
25
- * array‐of‐objects type definition. These keys uniquely identify an element
26
- * inside the array and are later used for `$update`, `$remove` and `$upsert`.
27
- *
28
- * @param def Atscript array type
29
- * @returns Set of property names marked as keys; empty set if none
30
- */ static getKeyProps(def) {
31
- if (def.type.of.type.kind === "object") {
32
- const objType = def.type.of.type;
33
- const keyProps = new Set();
34
- for (const [key, val] of objType.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
35
- return keyProps;
36
- }
37
- return new Set();
38
- }
39
23
  /**
40
24
  * Build a runtime *Validator* that understands the extended patch payload.
41
25
  *
@@ -106,14 +90,12 @@ else t.prop(key, defineAnnotatedType().refTo(val).copyMetadata(def.metadata).opt
106
90
  * @param val Value to be written
107
91
  * @private
108
92
  */ _set(key, val) {
109
- for (const pipe of this.updatePipeline) {
110
- if (!pipe.$set) pipe.$set = {};
111
- if (!pipe.$set[key]) {
112
- pipe.$set[key] = val;
113
- return;
114
- }
93
+ if (this.currentSetStage && !(key in this.currentSetStage.$set)) {
94
+ this.currentSetStage.$set[key] = val;
95
+ return;
115
96
  }
116
- this.updatePipeline.push({ $set: { [key]: val } });
97
+ this.currentSetStage = { $set: { [key]: val } };
98
+ this.updatePipeline.push(this.currentSetStage);
117
99
  }
118
100
  /**
119
101
  * Recursively walk through the patch *payload* and convert it into `$set`/…
@@ -128,8 +110,8 @@ else t.prop(key, defineAnnotatedType().refTo(val).copyMetadata(def.metadata).opt
128
110
  const key = evalKey(_key);
129
111
  const flatType = this.collection.flatMap.get(key);
130
112
  const topLevelArray = flatType?.metadata?.get("db.mongo.__topLevelArray");
131
- if (typeof value === "object" && topLevelArray) this.parseArrayPatch(key, value);
132
- else if (typeof value === "object" && this.collection.flatMap.get(key)?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
113
+ if (typeof value === "object" && topLevelArray) this.parseArrayPatch(key, value, flatType);
114
+ else if (typeof value === "object" && flatType?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
133
115
  else if (key !== "_id") this._set(key, value);
134
116
  }
135
117
  return this.updatePipeline;
@@ -141,19 +123,19 @@ else if (key !== "_id") this._set(key, value);
141
123
  * @param key Dotted path to the array field
142
124
  * @param value Payload slice for that field
143
125
  * @private
144
- */ parseArrayPatch(key, value) {
145
- const flatType = this.collection.flatMap.get(key);
126
+ */ parseArrayPatch(key, value, flatType) {
146
127
  const toRemove = value.$remove;
147
128
  const toReplace = value.$replace;
148
129
  const toInsert = value.$insert;
149
130
  const toUpsert = value.$upsert;
150
131
  const toUpdate = value.$update;
151
- const keyProps = flatType?.type.kind === "array" ? CollectionPatcher.getKeyProps(flatType) : new Set();
152
- this._remove(key, toRemove, keyProps);
132
+ const keyProps = flatType.type.kind === "array" ? getKeyProps(flatType) : new Set();
133
+ const keys = keyProps.size > 0 ? [...keyProps] : [];
134
+ this._remove(key, toRemove, keys, flatType);
153
135
  this._replace(key, toReplace);
154
- this._insert(key, toInsert, keyProps);
155
- this._upsert(key, toUpsert, keyProps);
156
- this._update(key, toUpdate, keyProps);
136
+ this._insert(key, toInsert, keys, flatType);
137
+ this._upsert(key, toUpsert, keys, flatType);
138
+ this._update(key, toUpdate, keys, flatType);
157
139
  }
158
140
  /**
159
141
  * Build an *aggregation‐expression* that checks equality by **all** keys in
@@ -166,7 +148,8 @@ else if (key !== "_id") this._set(key, value);
166
148
  * @param left Base token for *left* expression (e.g. `"$$el"`)
167
149
  * @param right Base token for *right* expression (e.g. `"$$this"`)
168
150
  */ _keysEqual(keys, left, right) {
169
- return keys.map((k) => ({ $eq: [`${left}.${k}`, `${right}.${k}`] })).reduce((acc, cur) => acc ? { $and: [acc, cur] } : cur);
151
+ const eqs = keys.map((k) => ({ $eq: [`${left}.${k}`, `${right}.${k}`] }));
152
+ return eqs.length === 1 ? eqs[0] : { $and: eqs };
170
153
  }
171
154
  /**
172
155
  * `$replace` – overwrite the entire array with `input`.
@@ -181,20 +164,19 @@ else if (key !== "_id") this._set(key, value);
181
164
  * `$insert`
182
165
  * - plain append → $concatArrays
183
166
  * - unique / keyed → delegate to _upsert (insert-or-update)
184
- */ _insert(key, input, keyProps) {
167
+ */ _insert(key, input, keys, flatType) {
185
168
  if (!input?.length) return;
186
- const uniqueItems = this.collection.flatMap.get(key)?.metadata?.has("expect.array.uniqueItems");
187
- if (uniqueItems || keyProps.size > 0) this._upsert(key, input, keyProps);
169
+ const uniqueItems = flatType.metadata?.has("expect.array.uniqueItems");
170
+ if (uniqueItems || keys.length > 0) this._upsert(key, input, keys, flatType);
188
171
  else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
189
172
  }
190
173
  /**
191
174
  * `$upsert`
192
175
  * - keyed → remove existing matching by key(s) then append candidate
193
176
  * - unique → $setUnion (deep equality)
194
- */ _upsert(key, input, keyProps) {
177
+ */ _upsert(key, input, keys, _flatType) {
195
178
  if (!input?.length) return;
196
- if (keyProps.size > 0) {
197
- const keys = [...keyProps];
179
+ if (keys.length > 0) {
198
180
  this._set(key, { $reduce: {
199
181
  input,
200
182
  initialValue: { $ifNull: [`$${key}`, []] },
@@ -218,11 +200,10 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
218
200
  * `$update`
219
201
  * - keyed → map array and merge / replace matching element(s)
220
202
  * - non-keyed → behave like `$addToSet` (insert only when not present)
221
- */ _update(key, input, keyProps) {
203
+ */ _update(key, input, keys, flatType) {
222
204
  if (!input?.length) return;
223
- if (keyProps.size > 0) {
224
- const mergeStrategy = this.collection.flatMap.get(key)?.metadata?.get("db.patch.strategy") === "merge";
225
- const keys = [...keyProps];
205
+ if (keys.length > 0) {
206
+ const mergeStrategy = flatType.metadata?.get("db.patch.strategy") === "merge";
226
207
  this._set(key, { $reduce: {
227
208
  input,
228
209
  initialValue: { $ifNull: [`$${key}`, []] },
@@ -242,23 +223,21 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
242
223
  * `$remove`
243
224
  * - keyed → filter out any element whose key set matches a payload item
244
225
  * - non-keyed → deep equality remove (`$setDifference`)
245
- */ _remove(key, input, keyProps) {
226
+ */ _remove(key, input, keys, _flatType) {
246
227
  if (!input?.length) return;
247
- if (keyProps.size > 0) {
248
- const keys = [...keyProps];
249
- this._set(key, { $let: {
250
- vars: { rem: input },
251
- in: { $filter: {
252
- input: { $ifNull: [`$${key}`, []] },
253
- as: "el",
254
- cond: { $not: { $anyElementTrue: { $map: {
255
- input: "$$rem",
256
- as: "r",
257
- in: this._keysEqual(keys, "$$el", "$$r")
258
- } } } }
259
- } }
260
- } });
261
- } else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
228
+ if (keys.length > 0) this._set(key, { $let: {
229
+ vars: { rem: input },
230
+ in: { $filter: {
231
+ input: { $ifNull: [`$${key}`, []] },
232
+ as: "el",
233
+ cond: { $not: { $anyElementTrue: { $map: {
234
+ input: "$$rem",
235
+ as: "r",
236
+ in: this._keysEqual(keys, "$$el", "$$r")
237
+ } } } }
238
+ } }
239
+ } });
240
+ else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
262
241
  }
263
242
  constructor(collection, payload) {
264
243
  _define_property$2(this, "collection", void 0);
@@ -268,14 +247,17 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
268
247
  * Filled only with the `_id` field right now.
269
248
  */ _define_property$2(this, "filterObj", void 0);
270
249
  /** MongoDB *update* document being built. */ _define_property$2(this, "updatePipeline", void 0);
250
+ /** Current `$set` stage being populated. */ _define_property$2(this, "currentSetStage", void 0);
271
251
  /** Additional *options* (mainly `arrayFilters`). */ _define_property$2(this, "optionsObj", void 0);
272
252
  this.collection = collection;
273
253
  this.payload = payload;
274
254
  this.filterObj = {};
275
255
  this.updatePipeline = [];
256
+ this.currentSetStage = null;
276
257
  this.optionsObj = {};
277
258
  }
278
259
  };
260
+ _define_property$2(CollectionPatcher, "getKeyProps", getKeyProps);
279
261
 
280
262
  //#endregion
281
263
  //#region packages/mongo/src/lib/mongo-filter.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/mongo",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "Mongodb plugin for atscript.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -57,9 +57,9 @@
57
57
  },
58
58
  "peerDependencies": {
59
59
  "mongodb": "^6.17.0",
60
- "@atscript/core": "^0.1.32",
61
- "@atscript/utils-db": "^0.1.32",
62
- "@atscript/typescript": "^0.1.32"
60
+ "@atscript/core": "^0.1.33",
61
+ "@atscript/utils-db": "^0.1.33",
62
+ "@atscript/typescript": "^0.1.33"
63
63
  },
64
64
  "build": [
65
65
  {},