@signaldb/core 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -163,7 +163,7 @@
163
163
  "src": "src/utils/isEqual.ts"
164
164
  },
165
165
  "src/utils/isFieldExpression.ts": {
166
- "file": "index.cjs27.js",
166
+ "file": "index.cjs29.js",
167
167
  "name": "utils/isFieldExpression",
168
168
  "src": "src/utils/isFieldExpression.ts"
169
169
  },
@@ -197,7 +197,7 @@
197
197
  "src": "src/utils/serializeValue.ts"
198
198
  },
199
199
  "src/utils/set.ts": {
200
- "file": "index.cjs28.js",
200
+ "file": "index.cjs27.js",
201
201
  "name": "utils/set",
202
202
  "src": "src/utils/set.ts"
203
203
  },
@@ -210,7 +210,7 @@
210
210
  ]
211
211
  },
212
212
  "src/utils/uniqueBy.ts": {
213
- "file": "index.cjs29.js",
213
+ "file": "index.cjs28.js",
214
214
  "name": "utils/uniqueBy",
215
215
  "src": "src/utils/uniqueBy.ts"
216
216
  }
@@ -41,6 +41,7 @@ interface CollectionEvents<T extends BaseItem, U = T> {
41
41
  'insert': (item: Omit<T, 'id'> & Partial<Pick<T, 'id'>>) => void;
42
42
  'updateOne': (selector: Selector<T>, modifier: Modifier<T>) => void;
43
43
  'updateMany': (selector: Selector<T>, modifier: Modifier<T>) => void;
44
+ 'replaceOne': (selector: Selector<T>, item: Omit<T, 'id'> & Partial<Pick<T, 'id'>>) => void;
44
45
  'removeOne': (selector: Selector<T>) => void;
45
46
  'removeMany': (selector: Selector<T>) => void;
46
47
  '_debug.getItems': (callstack: string, selector: Selector<T> | undefined, measuredTime: number) => void;
@@ -49,6 +50,7 @@ interface CollectionEvents<T extends BaseItem, U = T> {
49
50
  '_debug.insert': (callstack: string, item: Omit<T, 'id'> & Partial<Pick<T, 'id'>>) => void;
50
51
  '_debug.updateOne': (callstack: string, selector: Selector<T>, modifier: Modifier<T>) => void;
51
52
  '_debug.updateMany': (callstack: string, selector: Selector<T>, modifier: Modifier<T>) => void;
53
+ '_debug.replaceOne': (callstack: string, selector: Selector<T>, item: Omit<T, 'id'> & Partial<Pick<T, 'id'>>) => void;
52
54
  '_debug.removeOne': (callstack: string, selector: Selector<T>) => void;
53
55
  '_debug.removeMany': (callstack: string, selector: Selector<T>) => void;
54
56
  }
@@ -229,18 +231,38 @@ export default class Collection<T extends BaseItem<I> = BaseItem, I = any, U = T
229
231
  * Updates a single item in the collection that matches the given selector.
230
232
  * @param selector - The criteria to select the item to update.
231
233
  * @param modifier - The modifications to apply to the item.
234
+ * @param [options] - Optional settings for the update operation.
235
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
232
236
  * @returns The number of items updated (0 or 1).
233
237
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
234
238
  */
235
- updateOne(selector: Selector<T>, modifier: Modifier<T>): 0 | 1;
239
+ updateOne(selector: Selector<T>, modifier: Modifier<T>, options?: {
240
+ upsert?: boolean;
241
+ }): 0 | 1;
236
242
  /**
237
243
  * Updates multiple items in the collection that match the given selector.
238
244
  * @param selector - The criteria to select the items to update.
239
245
  * @param modifier - The modifications to apply to the items.
246
+ * @param [options] - Optional settings for the update operation.
247
+ * @param [options.upsert] - If `true`, creates new items if no items match the selector.
240
248
  * @returns The number of items updated.
241
249
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
242
250
  */
243
- updateMany(selector: Selector<T>, modifier: Modifier<T>): number;
251
+ updateMany(selector: Selector<T>, modifier: Modifier<T>, options?: {
252
+ upsert?: boolean;
253
+ }): number;
254
+ /**
255
+ * Replaces a single item in the collection that matches the given selector.
256
+ * @param selector - The criteria to select the item to replace.
257
+ * @param replacement - The item to replace the selected item with.
258
+ * @param [options] - Optional settings for the replace operation.
259
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
260
+ * @returns The number of items replaced (0 or 1).
261
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
262
+ */
263
+ replaceOne(selector: Selector<T>, replacement: Omit<T, 'id'> & Partial<Pick<T, 'id'>>, options?: {
264
+ upsert?: boolean;
265
+ }): 0 | 1;
244
266
  /**
245
267
  * Removes a single item from the collection that matches the given selector.
246
268
  * @param selector - The criteria to select the item to remove.
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  const mingo = require("mingo");
3
3
  function modify(item, modifier) {
4
+ const hasOperators = Object.keys(modifier).some((key) => key.startsWith("$"));
5
+ if (!hasOperators)
6
+ return modifier;
4
7
  const clonedItem = { ...item };
5
8
  mingo.update(clonedItem, modifier);
6
9
  return clonedItem;
@@ -10,14 +10,38 @@ function createExternalIndex(field, index) {
10
10
  const keys = getMatchingKeys(field, selector);
11
11
  if (keys.include == null && keys.exclude == null)
12
12
  return { matched: false };
13
- const includedKeys = keys.include == null ? [...index.values()].reduce((memo, set) => [...memo, ...set], []) : keys.include.reduce((memo, key) => [...memo, ...index.get(key) || []], []);
14
- const itemPositions = keys.exclude == null ? includedKeys : keys.exclude.reduce((memo, key) => memo.filter((i) => {
15
- var _a;
16
- return !((_a = index.get(key)) == null ? void 0 : _a.has(i));
17
- }), includedKeys);
13
+ let includedPositions = [];
14
+ if (keys.include == null) {
15
+ for (const set of index.values()) {
16
+ for (const pos of set) {
17
+ includedPositions.push(pos);
18
+ }
19
+ }
20
+ } else {
21
+ for (const key of keys.include) {
22
+ const posSet = index.get(key);
23
+ if (posSet) {
24
+ for (const pos of posSet) {
25
+ includedPositions.push(pos);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ if (keys.exclude != null) {
31
+ const excludeSet = /* @__PURE__ */ new Set();
32
+ for (const key of keys.exclude) {
33
+ const posSet = index.get(key);
34
+ if (posSet) {
35
+ for (const pos of posSet) {
36
+ excludeSet.add(pos);
37
+ }
38
+ }
39
+ }
40
+ includedPositions = includedPositions.filter((pos) => !excludeSet.has(pos));
41
+ }
18
42
  return {
19
43
  matched: true,
20
- positions: itemPositions,
44
+ positions: includedPositions,
21
45
  fields: [field]
22
46
  };
23
47
  },
@@ -567,61 +567,140 @@ const _Collection = class _Collection extends EventEmitter {
567
567
  * Updates a single item in the collection that matches the given selector.
568
568
  * @param selector - The criteria to select the item to update.
569
569
  * @param modifier - The modifications to apply to the item.
570
+ * @param [options] - Optional settings for the update operation.
571
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
570
572
  * @returns The number of items updated (0 or 1).
571
573
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
572
574
  */
573
- updateOne(selector, modifier) {
575
+ updateOne(selector, modifier, options) {
574
576
  if (this.isDisposed)
575
577
  throw new Error("Collection is disposed");
576
578
  if (!selector)
577
579
  throw new Error("Invalid selector");
578
580
  if (!modifier)
579
581
  throw new Error("Invalid modifier");
582
+ const { $setOnInsert, ...restModifier } = modifier;
580
583
  const { item, index } = this.getItemAndIndex(selector);
581
- if (item == null)
582
- return 0;
583
- const modifiedItem = modify(deepClone.default(item), modifier);
584
- const existingItem = this.findOne({ id: modifiedItem.id }, { reactive: false });
585
- if (!isEqual(existingItem, { ...existingItem, id: modifiedItem.id }))
586
- throw new Error("Item with same id already exists");
587
- this.memory().splice(index, 1, modifiedItem);
588
- this.rebuildIndices();
589
- this.emit("changed", modifiedItem, modifier);
584
+ if (item == null) {
585
+ if (options == null ? void 0 : options.upsert) {
586
+ const newItem = modify({}, {
587
+ ...restModifier,
588
+ $set: {
589
+ ...$setOnInsert,
590
+ ...restModifier.$set
591
+ }
592
+ });
593
+ if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
594
+ throw new Error("Item with same id already exists");
595
+ }
596
+ this.insert(newItem);
597
+ }
598
+ } else {
599
+ const modifiedItem = modify(deepClone.default(item), restModifier);
600
+ if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
601
+ throw new Error("Item with same id already exists");
602
+ }
603
+ this.memory().splice(index, 1, modifiedItem);
604
+ this.rebuildIndices();
605
+ this.emit("changed", modifiedItem, restModifier);
606
+ }
590
607
  this.emit("updateOne", selector, modifier);
591
608
  this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
609
+ if (item == null && !(options == null ? void 0 : options.upsert))
610
+ return 0;
592
611
  return 1;
593
612
  }
594
613
  /**
595
614
  * Updates multiple items in the collection that match the given selector.
596
615
  * @param selector - The criteria to select the items to update.
597
616
  * @param modifier - The modifications to apply to the items.
617
+ * @param [options] - Optional settings for the update operation.
618
+ * @param [options.upsert] - If `true`, creates new items if no items match the selector.
598
619
  * @returns The number of items updated.
599
620
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
600
621
  */
601
- updateMany(selector, modifier) {
622
+ updateMany(selector, modifier, options) {
602
623
  if (this.isDisposed)
603
624
  throw new Error("Collection is disposed");
604
625
  if (!selector)
605
626
  throw new Error("Invalid selector");
606
627
  if (!modifier)
607
628
  throw new Error("Invalid modifier");
629
+ const { $setOnInsert, ...restModifier } = modifier;
608
630
  const items = this.getItems(selector);
609
- const modifiedItems = [];
610
- items.forEach((item) => {
631
+ if (items.length === 0 && (options == null ? void 0 : options.upsert)) {
632
+ const newItem = modify({}, {
633
+ ...restModifier,
634
+ $set: {
635
+ ...$setOnInsert,
636
+ ...restModifier.$set
637
+ }
638
+ });
639
+ if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
640
+ throw new Error("Item with same id already exists");
641
+ }
642
+ this.insert(newItem);
643
+ }
644
+ const changes = items.map((item) => {
611
645
  const { index } = this.getItemAndIndex({ id: item.id });
612
646
  if (index === -1)
613
- throw new Error("Cannot resolve index for item");
614
- const modifiedItem = modify(deepClone.default(item), modifier);
615
- this.memory().splice(index, 1, modifiedItem);
616
- modifiedItems.push(modifiedItem);
647
+ throw new Error(`Cannot resolve index for item with id '${item.id}'`);
648
+ const modifiedItem = modify(deepClone.default(item), restModifier);
649
+ if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
650
+ throw new Error(`Item with same id '${modifiedItem.id}' already exists`);
651
+ }
652
+ return {
653
+ item: modifiedItem,
654
+ index
655
+ };
656
+ });
657
+ changes.forEach(({ item, index }) => {
658
+ this.memory().splice(index, 1, item);
617
659
  });
618
660
  this.rebuildIndices();
619
- modifiedItems.forEach((modifiedItem) => {
620
- this.emit("changed", modifiedItem, modifier);
661
+ changes.forEach(({ item }) => {
662
+ this.emit("changed", item, restModifier);
621
663
  });
622
664
  this.emit("updateMany", selector, modifier);
623
665
  this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
624
- return modifiedItems.length;
666
+ return changes.length === 0 && (options == null ? void 0 : options.upsert) ? 1 : changes.length;
667
+ }
668
+ /**
669
+ * Replaces a single item in the collection that matches the given selector.
670
+ * @param selector - The criteria to select the item to replace.
671
+ * @param replacement - The item to replace the selected item with.
672
+ * @param [options] - Optional settings for the replace operation.
673
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
674
+ * @returns The number of items replaced (0 or 1).
675
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
676
+ */
677
+ replaceOne(selector, replacement, options) {
678
+ if (this.isDisposed)
679
+ throw new Error("Collection is disposed");
680
+ if (!selector)
681
+ throw new Error("Invalid selector");
682
+ const { item, index } = this.getItemAndIndex(selector);
683
+ if (item == null) {
684
+ if (options == null ? void 0 : options.upsert) {
685
+ if (replacement.id != null && this.getItemAndIndex({ id: replacement.id }).item != null) {
686
+ throw new Error("Item with same id already exists");
687
+ }
688
+ this.insert(replacement);
689
+ }
690
+ } else {
691
+ if (item.id !== replacement.id && this.getItemAndIndex({ id: replacement.id }).item != null) {
692
+ throw new Error("Item with same id already exists");
693
+ }
694
+ const modifiedItem = { id: item.id, ...replacement };
695
+ this.memory().splice(index, 1, modifiedItem);
696
+ this.rebuildIndices();
697
+ this.emit("changed", modifiedItem, replacement);
698
+ }
699
+ this.emit("replaceOne", selector, replacement);
700
+ this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
701
+ if (item == null && !(options == null ? void 0 : options.upsert))
702
+ return 0;
703
+ return 1;
625
704
  }
626
705
  /**
627
706
  * Removes a single item from the collection that matches the given selector.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  const get = require("./index.cjs25.js");
3
- const set = require("./index.cjs28.js");
3
+ const set = require("./index.cjs27.js");
4
4
  function project(item, fields) {
5
5
  const allFieldsDeactivated = Object.values(fields).every((value) => value === 0);
6
6
  if (allFieldsDeactivated) {
@@ -3,7 +3,7 @@ var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  const isEqual = require("./index.cjs9.js");
6
- const uniqueBy = require("./index.cjs29.js");
6
+ const uniqueBy = require("./index.cjs28.js");
7
7
  class Observer {
8
8
  /**
9
9
  * Creates a new instance of the `Observer` class.
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const isFieldExpression = require("./index.cjs27.js");
2
+ const isFieldExpression = require("./index.cjs29.js");
3
3
  const serializeValue = require("./index.cjs16.js");
4
4
  function getMatchingKeys(field, selector) {
5
5
  const result = { include: null, exclude: null };
@@ -1,42 +1,29 @@
1
1
  "use strict";
2
- const expressionKeys = /* @__PURE__ */ new Set([
3
- "$eq",
4
- "$gt",
5
- "$gte",
6
- "$lt",
7
- "$lte",
8
- "$in",
9
- "$nin",
10
- "$ne",
11
- "$exists",
12
- "$not",
13
- "$expr",
14
- "$jsonSchema",
15
- "$mod",
16
- "$regex",
17
- "$options",
18
- "$text",
19
- "$where",
20
- "$all",
21
- "$elemMatch",
22
- "$size",
23
- "$bitsAllClear",
24
- "$bitsAllSet",
25
- "$bitsAnyClear",
26
- "$bitsAnySet"
27
- ]);
28
- function isFieldExpression(expression) {
29
- if (typeof expression !== "object" || expression == null) {
30
- return false;
31
- }
32
- const keys = Object.keys(expression);
33
- if (keys.length === 0) {
34
- return false;
35
- }
36
- const hasInvalidKeys = keys.some((key) => !expressionKeys.has(key));
37
- if (hasInvalidKeys)
38
- return false;
39
- const hasValidKeys = keys.every((key) => expressionKeys.has(key));
40
- return hasValidKeys;
2
+ function set(object, path, value, deleteIfUndefined = false) {
3
+ if (object == null)
4
+ return object;
5
+ const segments = path.split(/[.[\]]/g);
6
+ if (segments[0] === "")
7
+ segments.shift();
8
+ if (segments.at(-1) === "")
9
+ segments.pop();
10
+ const apply = (node) => {
11
+ if (segments.length > 1) {
12
+ const key = segments.shift();
13
+ const nextIsNumber = !Number.isNaN(Number.parseInt(segments[0], 10));
14
+ if (node[key] === void 0) {
15
+ node[key] = nextIsNumber ? [] : {};
16
+ }
17
+ apply(node[key]);
18
+ } else {
19
+ if (deleteIfUndefined && value === void 0) {
20
+ delete node[segments[0]];
21
+ return;
22
+ }
23
+ node[segments[0]] = value;
24
+ }
25
+ };
26
+ apply(object);
27
+ return object;
41
28
  }
42
- module.exports = isFieldExpression;
29
+ module.exports = set;
@@ -1,29 +1,9 @@
1
1
  "use strict";
2
- function set(object, path, value, deleteIfUndefined = false) {
3
- if (object == null)
4
- return object;
5
- const segments = path.split(/[.[\]]/g);
6
- if (segments[0] === "")
7
- segments.shift();
8
- if (segments.at(-1) === "")
9
- segments.pop();
10
- const apply = (node) => {
11
- if (segments.length > 1) {
12
- const key = segments.shift();
13
- const nextIsNumber = !Number.isNaN(Number.parseInt(segments[0], 10));
14
- if (node[key] === void 0) {
15
- node[key] = nextIsNumber ? [] : {};
16
- }
17
- apply(node[key]);
18
- } else {
19
- if (deleteIfUndefined && value === void 0) {
20
- delete node[segments[0]];
21
- return;
22
- }
23
- node[segments[0]] = value;
24
- }
25
- };
26
- apply(object);
27
- return object;
2
+ function uniqueBy(array, fn) {
3
+ const set = /* @__PURE__ */ new Set();
4
+ return array.filter((element) => {
5
+ const value = typeof fn === "function" ? fn(element) : element[fn];
6
+ return !set.has(value) && set.add(value);
7
+ });
28
8
  }
29
- module.exports = set;
9
+ module.exports = uniqueBy;
@@ -1,9 +1,42 @@
1
1
  "use strict";
2
- function uniqueBy(array, fn) {
3
- const set = /* @__PURE__ */ new Set();
4
- return array.filter((element) => {
5
- const value = typeof fn === "function" ? fn(element) : element[fn];
6
- return !set.has(value) && set.add(value);
7
- });
2
+ const expressionKeys = /* @__PURE__ */ new Set([
3
+ "$eq",
4
+ "$gt",
5
+ "$gte",
6
+ "$lt",
7
+ "$lte",
8
+ "$in",
9
+ "$nin",
10
+ "$ne",
11
+ "$exists",
12
+ "$not",
13
+ "$expr",
14
+ "$jsonSchema",
15
+ "$mod",
16
+ "$regex",
17
+ "$options",
18
+ "$text",
19
+ "$where",
20
+ "$all",
21
+ "$elemMatch",
22
+ "$size",
23
+ "$bitsAllClear",
24
+ "$bitsAllSet",
25
+ "$bitsAnyClear",
26
+ "$bitsAnySet"
27
+ ]);
28
+ function isFieldExpression(expression) {
29
+ if (typeof expression !== "object" || expression == null) {
30
+ return false;
31
+ }
32
+ const keys = Object.keys(expression);
33
+ if (keys.length === 0) {
34
+ return false;
35
+ }
36
+ const hasInvalidKeys = keys.some((key) => !expressionKeys.has(key));
37
+ if (hasInvalidKeys)
38
+ return false;
39
+ const hasValidKeys = keys.every((key) => expressionKeys.has(key));
40
+ return hasValidKeys;
8
41
  }
9
- module.exports = uniqueBy;
42
+ module.exports = isFieldExpression;
package/dist/index10.mjs CHANGED
@@ -1,5 +1,8 @@
1
1
  import { update } from "mingo";
2
2
  function modify(item, modifier) {
3
+ const hasOperators = Object.keys(modifier).some((key) => key.startsWith("$"));
4
+ if (!hasOperators)
5
+ return modifier;
3
6
  const clonedItem = { ...item };
4
7
  update(clonedItem, modifier);
5
8
  return clonedItem;
package/dist/index13.mjs CHANGED
@@ -8,14 +8,38 @@ function createExternalIndex(field, index) {
8
8
  const keys = getMatchingKeys(field, selector);
9
9
  if (keys.include == null && keys.exclude == null)
10
10
  return { matched: false };
11
- const includedKeys = keys.include == null ? [...index.values()].reduce((memo, set) => [...memo, ...set], []) : keys.include.reduce((memo, key) => [...memo, ...index.get(key) || []], []);
12
- const itemPositions = keys.exclude == null ? includedKeys : keys.exclude.reduce((memo, key) => memo.filter((i) => {
13
- var _a;
14
- return !((_a = index.get(key)) == null ? void 0 : _a.has(i));
15
- }), includedKeys);
11
+ let includedPositions = [];
12
+ if (keys.include == null) {
13
+ for (const set of index.values()) {
14
+ for (const pos of set) {
15
+ includedPositions.push(pos);
16
+ }
17
+ }
18
+ } else {
19
+ for (const key of keys.include) {
20
+ const posSet = index.get(key);
21
+ if (posSet) {
22
+ for (const pos of posSet) {
23
+ includedPositions.push(pos);
24
+ }
25
+ }
26
+ }
27
+ }
28
+ if (keys.exclude != null) {
29
+ const excludeSet = /* @__PURE__ */ new Set();
30
+ for (const key of keys.exclude) {
31
+ const posSet = index.get(key);
32
+ if (posSet) {
33
+ for (const pos of posSet) {
34
+ excludeSet.add(pos);
35
+ }
36
+ }
37
+ }
38
+ includedPositions = includedPositions.filter((pos) => !excludeSet.has(pos));
39
+ }
16
40
  return {
17
41
  matched: true,
18
- positions: itemPositions,
42
+ positions: includedPositions,
19
43
  fields: [field]
20
44
  };
21
45
  },
package/dist/index2.mjs CHANGED
@@ -566,61 +566,140 @@ const _Collection = class _Collection extends EventEmitter {
566
566
  * Updates a single item in the collection that matches the given selector.
567
567
  * @param selector - The criteria to select the item to update.
568
568
  * @param modifier - The modifications to apply to the item.
569
+ * @param [options] - Optional settings for the update operation.
570
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
569
571
  * @returns The number of items updated (0 or 1).
570
572
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
571
573
  */
572
- updateOne(selector, modifier) {
574
+ updateOne(selector, modifier, options) {
573
575
  if (this.isDisposed)
574
576
  throw new Error("Collection is disposed");
575
577
  if (!selector)
576
578
  throw new Error("Invalid selector");
577
579
  if (!modifier)
578
580
  throw new Error("Invalid modifier");
581
+ const { $setOnInsert, ...restModifier } = modifier;
579
582
  const { item, index } = this.getItemAndIndex(selector);
580
- if (item == null)
581
- return 0;
582
- const modifiedItem = modify(deepClone(item), modifier);
583
- const existingItem = this.findOne({ id: modifiedItem.id }, { reactive: false });
584
- if (!isEqual(existingItem, { ...existingItem, id: modifiedItem.id }))
585
- throw new Error("Item with same id already exists");
586
- this.memory().splice(index, 1, modifiedItem);
587
- this.rebuildIndices();
588
- this.emit("changed", modifiedItem, modifier);
583
+ if (item == null) {
584
+ if (options == null ? void 0 : options.upsert) {
585
+ const newItem = modify({}, {
586
+ ...restModifier,
587
+ $set: {
588
+ ...$setOnInsert,
589
+ ...restModifier.$set
590
+ }
591
+ });
592
+ if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
593
+ throw new Error("Item with same id already exists");
594
+ }
595
+ this.insert(newItem);
596
+ }
597
+ } else {
598
+ const modifiedItem = modify(deepClone(item), restModifier);
599
+ if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
600
+ throw new Error("Item with same id already exists");
601
+ }
602
+ this.memory().splice(index, 1, modifiedItem);
603
+ this.rebuildIndices();
604
+ this.emit("changed", modifiedItem, restModifier);
605
+ }
589
606
  this.emit("updateOne", selector, modifier);
590
607
  this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
608
+ if (item == null && !(options == null ? void 0 : options.upsert))
609
+ return 0;
591
610
  return 1;
592
611
  }
593
612
  /**
594
613
  * Updates multiple items in the collection that match the given selector.
595
614
  * @param selector - The criteria to select the items to update.
596
615
  * @param modifier - The modifications to apply to the items.
616
+ * @param [options] - Optional settings for the update operation.
617
+ * @param [options.upsert] - If `true`, creates new items if no items match the selector.
597
618
  * @returns The number of items updated.
598
619
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
599
620
  */
600
- updateMany(selector, modifier) {
621
+ updateMany(selector, modifier, options) {
601
622
  if (this.isDisposed)
602
623
  throw new Error("Collection is disposed");
603
624
  if (!selector)
604
625
  throw new Error("Invalid selector");
605
626
  if (!modifier)
606
627
  throw new Error("Invalid modifier");
628
+ const { $setOnInsert, ...restModifier } = modifier;
607
629
  const items = this.getItems(selector);
608
- const modifiedItems = [];
609
- items.forEach((item) => {
630
+ if (items.length === 0 && (options == null ? void 0 : options.upsert)) {
631
+ const newItem = modify({}, {
632
+ ...restModifier,
633
+ $set: {
634
+ ...$setOnInsert,
635
+ ...restModifier.$set
636
+ }
637
+ });
638
+ if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
639
+ throw new Error("Item with same id already exists");
640
+ }
641
+ this.insert(newItem);
642
+ }
643
+ const changes = items.map((item) => {
610
644
  const { index } = this.getItemAndIndex({ id: item.id });
611
645
  if (index === -1)
612
- throw new Error("Cannot resolve index for item");
613
- const modifiedItem = modify(deepClone(item), modifier);
614
- this.memory().splice(index, 1, modifiedItem);
615
- modifiedItems.push(modifiedItem);
646
+ throw new Error(`Cannot resolve index for item with id '${item.id}'`);
647
+ const modifiedItem = modify(deepClone(item), restModifier);
648
+ if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
649
+ throw new Error(`Item with same id '${modifiedItem.id}' already exists`);
650
+ }
651
+ return {
652
+ item: modifiedItem,
653
+ index
654
+ };
655
+ });
656
+ changes.forEach(({ item, index }) => {
657
+ this.memory().splice(index, 1, item);
616
658
  });
617
659
  this.rebuildIndices();
618
- modifiedItems.forEach((modifiedItem) => {
619
- this.emit("changed", modifiedItem, modifier);
660
+ changes.forEach(({ item }) => {
661
+ this.emit("changed", item, restModifier);
620
662
  });
621
663
  this.emit("updateMany", selector, modifier);
622
664
  this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
623
- return modifiedItems.length;
665
+ return changes.length === 0 && (options == null ? void 0 : options.upsert) ? 1 : changes.length;
666
+ }
667
+ /**
668
+ * Replaces a single item in the collection that matches the given selector.
669
+ * @param selector - The criteria to select the item to replace.
670
+ * @param replacement - The item to replace the selected item with.
671
+ * @param [options] - Optional settings for the replace operation.
672
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
673
+ * @returns The number of items replaced (0 or 1).
674
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
675
+ */
676
+ replaceOne(selector, replacement, options) {
677
+ if (this.isDisposed)
678
+ throw new Error("Collection is disposed");
679
+ if (!selector)
680
+ throw new Error("Invalid selector");
681
+ const { item, index } = this.getItemAndIndex(selector);
682
+ if (item == null) {
683
+ if (options == null ? void 0 : options.upsert) {
684
+ if (replacement.id != null && this.getItemAndIndex({ id: replacement.id }).item != null) {
685
+ throw new Error("Item with same id already exists");
686
+ }
687
+ this.insert(replacement);
688
+ }
689
+ } else {
690
+ if (item.id !== replacement.id && this.getItemAndIndex({ id: replacement.id }).item != null) {
691
+ throw new Error("Item with same id already exists");
692
+ }
693
+ const modifiedItem = { id: item.id, ...replacement };
694
+ this.memory().splice(index, 1, modifiedItem);
695
+ this.rebuildIndices();
696
+ this.emit("changed", modifiedItem, replacement);
697
+ }
698
+ this.emit("replaceOne", selector, replacement);
699
+ this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
700
+ if (item == null && !(options == null ? void 0 : options.upsert))
701
+ return 0;
702
+ return 1;
624
703
  }
625
704
  /**
626
705
  * Removes a single item from the collection that matches the given selector.
package/dist/index22.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import get from "./index25.mjs";
2
- import set from "./index28.mjs";
2
+ import set from "./index27.mjs";
3
3
  function project(item, fields) {
4
4
  const allFieldsDeactivated = Object.values(fields).every((value) => value === 0);
5
5
  if (allFieldsDeactivated) {
package/dist/index23.mjs CHANGED
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import isEqual from "./index9.mjs";
5
- import uniqueBy from "./index29.mjs";
5
+ import uniqueBy from "./index28.mjs";
6
6
  class Observer {
7
7
  /**
8
8
  * Creates a new instance of the `Observer` class.
package/dist/index26.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import isFieldExpression from "./index27.mjs";
1
+ import isFieldExpression from "./index29.mjs";
2
2
  import serializeValue from "./index16.mjs";
3
3
  function getMatchingKeys(field, selector) {
4
4
  const result = { include: null, exclude: null };
package/dist/index27.mjs CHANGED
@@ -1,43 +1,30 @@
1
- const expressionKeys = /* @__PURE__ */ new Set([
2
- "$eq",
3
- "$gt",
4
- "$gte",
5
- "$lt",
6
- "$lte",
7
- "$in",
8
- "$nin",
9
- "$ne",
10
- "$exists",
11
- "$not",
12
- "$expr",
13
- "$jsonSchema",
14
- "$mod",
15
- "$regex",
16
- "$options",
17
- "$text",
18
- "$where",
19
- "$all",
20
- "$elemMatch",
21
- "$size",
22
- "$bitsAllClear",
23
- "$bitsAllSet",
24
- "$bitsAnyClear",
25
- "$bitsAnySet"
26
- ]);
27
- function isFieldExpression(expression) {
28
- if (typeof expression !== "object" || expression == null) {
29
- return false;
30
- }
31
- const keys = Object.keys(expression);
32
- if (keys.length === 0) {
33
- return false;
34
- }
35
- const hasInvalidKeys = keys.some((key) => !expressionKeys.has(key));
36
- if (hasInvalidKeys)
37
- return false;
38
- const hasValidKeys = keys.every((key) => expressionKeys.has(key));
39
- return hasValidKeys;
1
+ function set(object, path, value, deleteIfUndefined = false) {
2
+ if (object == null)
3
+ return object;
4
+ const segments = path.split(/[.[\]]/g);
5
+ if (segments[0] === "")
6
+ segments.shift();
7
+ if (segments.at(-1) === "")
8
+ segments.pop();
9
+ const apply = (node) => {
10
+ if (segments.length > 1) {
11
+ const key = segments.shift();
12
+ const nextIsNumber = !Number.isNaN(Number.parseInt(segments[0], 10));
13
+ if (node[key] === void 0) {
14
+ node[key] = nextIsNumber ? [] : {};
15
+ }
16
+ apply(node[key]);
17
+ } else {
18
+ if (deleteIfUndefined && value === void 0) {
19
+ delete node[segments[0]];
20
+ return;
21
+ }
22
+ node[segments[0]] = value;
23
+ }
24
+ };
25
+ apply(object);
26
+ return object;
40
27
  }
41
28
  export {
42
- isFieldExpression as default
29
+ set as default
43
30
  };
package/dist/index28.mjs CHANGED
@@ -1,30 +1,10 @@
1
- function set(object, path, value, deleteIfUndefined = false) {
2
- if (object == null)
3
- return object;
4
- const segments = path.split(/[.[\]]/g);
5
- if (segments[0] === "")
6
- segments.shift();
7
- if (segments.at(-1) === "")
8
- segments.pop();
9
- const apply = (node) => {
10
- if (segments.length > 1) {
11
- const key = segments.shift();
12
- const nextIsNumber = !Number.isNaN(Number.parseInt(segments[0], 10));
13
- if (node[key] === void 0) {
14
- node[key] = nextIsNumber ? [] : {};
15
- }
16
- apply(node[key]);
17
- } else {
18
- if (deleteIfUndefined && value === void 0) {
19
- delete node[segments[0]];
20
- return;
21
- }
22
- node[segments[0]] = value;
23
- }
24
- };
25
- apply(object);
26
- return object;
1
+ function uniqueBy(array, fn) {
2
+ const set = /* @__PURE__ */ new Set();
3
+ return array.filter((element) => {
4
+ const value = typeof fn === "function" ? fn(element) : element[fn];
5
+ return !set.has(value) && set.add(value);
6
+ });
27
7
  }
28
8
  export {
29
- set as default
9
+ uniqueBy as default
30
10
  };
package/dist/index29.mjs CHANGED
@@ -1,10 +1,43 @@
1
- function uniqueBy(array, fn) {
2
- const set = /* @__PURE__ */ new Set();
3
- return array.filter((element) => {
4
- const value = typeof fn === "function" ? fn(element) : element[fn];
5
- return !set.has(value) && set.add(value);
6
- });
1
+ const expressionKeys = /* @__PURE__ */ new Set([
2
+ "$eq",
3
+ "$gt",
4
+ "$gte",
5
+ "$lt",
6
+ "$lte",
7
+ "$in",
8
+ "$nin",
9
+ "$ne",
10
+ "$exists",
11
+ "$not",
12
+ "$expr",
13
+ "$jsonSchema",
14
+ "$mod",
15
+ "$regex",
16
+ "$options",
17
+ "$text",
18
+ "$where",
19
+ "$all",
20
+ "$elemMatch",
21
+ "$size",
22
+ "$bitsAllClear",
23
+ "$bitsAllSet",
24
+ "$bitsAnyClear",
25
+ "$bitsAnySet"
26
+ ]);
27
+ function isFieldExpression(expression) {
28
+ if (typeof expression !== "object" || expression == null) {
29
+ return false;
30
+ }
31
+ const keys = Object.keys(expression);
32
+ if (keys.length === 0) {
33
+ return false;
34
+ }
35
+ const hasInvalidKeys = keys.some((key) => !expressionKeys.has(key));
36
+ if (hasInvalidKeys)
37
+ return false;
38
+ const hasValidKeys = keys.every((key) => expressionKeys.has(key));
39
+ return hasValidKeys;
7
40
  }
8
41
  export {
9
- uniqueBy as default
42
+ isFieldExpression as default
10
43
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaldb/core",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "SignalDB is a client-side database that provides a simple MongoDB-like interface to the data with first-class typescript support to achieve an optimistic UI. Data persistence can be achieved by using storage providers that store the data through a JSON interface to places such as localStorage.",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vite build"