@signaldb/core 2.0.0-beta.0 → 2.0.0-beta.2

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.
Files changed (54) hide show
  1. package/dist/.vite/manifest.json +24 -11
  2. package/dist/AsyncDataAdapter.d.ts +1 -1
  3. package/dist/AutoFetchDataAdapter.d.ts +1 -2
  4. package/dist/Collection/Cursor.d.ts +3 -2
  5. package/dist/Collection/index.d.ts +17 -12
  6. package/dist/Collection/types.d.ts +1 -0
  7. package/dist/DataAdapter.d.ts +9 -7
  8. package/dist/DefaultDataAdapter.d.ts +2 -1
  9. package/dist/WorkerDataAdapter.d.ts +5 -2
  10. package/dist/WorkerDataAdapterHost.d.ts +9 -6
  11. package/dist/index.cjs12.js +35 -21
  12. package/dist/index.cjs13.js +27 -15
  13. package/dist/index.cjs14.js +72 -56
  14. package/dist/index.cjs15.js +122 -75
  15. package/dist/index.cjs16.js +45 -36
  16. package/dist/index.cjs17.js +4 -2
  17. package/dist/index.cjs19.js +0 -1
  18. package/dist/index.cjs2.js +6 -2
  19. package/dist/index.cjs20.js +14 -102
  20. package/dist/index.cjs21.js +93 -118
  21. package/dist/index.cjs22.js +127 -5
  22. package/dist/index.cjs23.js +5 -25
  23. package/dist/index.cjs24.js +25 -5
  24. package/dist/index.cjs27.js +44 -3
  25. package/dist/index.cjs28.js +6 -5
  26. package/dist/index.cjs29.js +5 -27
  27. package/dist/index.cjs3.js +41 -22
  28. package/dist/index.cjs31.js +27 -7
  29. package/dist/index.cjs32.js +9 -0
  30. package/dist/index.cjs33.js +5 -0
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index12.mjs +35 -21
  33. package/dist/index13.mjs +27 -15
  34. package/dist/index14.mjs +72 -56
  35. package/dist/index15.mjs +122 -75
  36. package/dist/index16.mjs +45 -36
  37. package/dist/index17.mjs +4 -2
  38. package/dist/index19.mjs +0 -1
  39. package/dist/index2.mjs +6 -2
  40. package/dist/index20.mjs +14 -102
  41. package/dist/index21.mjs +93 -117
  42. package/dist/index22.mjs +126 -5
  43. package/dist/index23.mjs +5 -25
  44. package/dist/index24.mjs +25 -5
  45. package/dist/index27.mjs +44 -3
  46. package/dist/index28.mjs +6 -5
  47. package/dist/index29.mjs +5 -27
  48. package/dist/index3.mjs +41 -22
  49. package/dist/index31.mjs +27 -7
  50. package/dist/index32.mjs +10 -0
  51. package/dist/index33.mjs +6 -0
  52. package/dist/utils/batchOnNextTick.d.ts +16 -0
  53. package/dist/utils/deepClone.d.ts +1 -1
  54. package/package.json +3 -3
package/dist/index22.mjs CHANGED
@@ -1,8 +1,129 @@
1
- import { Query } from "mingo";
2
- function match(item, selector) {
3
- const query = new Query(selector);
4
- return query.test(item);
1
+ import intersection from "./index29.mjs";
2
+ function getMergedIndexInfo(queryFunctions, selector) {
3
+ return queryFunctions.reduce((memoOrPromise, queryFunction) => {
4
+ const resultOrPromise = queryFunction(selector);
5
+ const processResult = (memo2, result) => {
6
+ if (!result.matched)
7
+ return memo2;
8
+ const optimizedSelector = result.keepSelector ? memo2.optimizedSelector : Object.fromEntries(Object.entries(memo2.optimizedSelector).filter(([key]) => !result.fields.includes(key)));
9
+ return {
10
+ matched: true,
11
+ ids: [...new Set(memo2.matched ? intersection(memo2.ids, result.ids) : result.ids)],
12
+ optimizedSelector
13
+ };
14
+ };
15
+ if (resultOrPromise instanceof Promise) {
16
+ return resultOrPromise.then(async (result) => {
17
+ const memo2 = memoOrPromise instanceof Promise ? await memoOrPromise : memoOrPromise;
18
+ return processResult(memo2, result);
19
+ });
20
+ }
21
+ const memo = memoOrPromise;
22
+ if (memo instanceof Promise)
23
+ throw new Error("Mixing async and sync index providers is not supported");
24
+ return processResult(memo, resultOrPromise);
25
+ }, {
26
+ matched: false,
27
+ ids: [],
28
+ optimizedSelector: { ...selector }
29
+ });
30
+ }
31
+ function optimizeLogicGate(queryFunctions, logicGate, idsCallback) {
32
+ return logicGate.reduce((memoOrPromise, sel) => {
33
+ const getSelector = (indexInfo) => {
34
+ const { matched: selMatched, ids: selIds, optimizedSelector: optimizedSelector2 } = indexInfo;
35
+ if (selMatched) {
36
+ idsCallback(true, selIds);
37
+ if (Object.keys(optimizedSelector2).length > 0) {
38
+ return optimizedSelector2;
39
+ }
40
+ } else {
41
+ idsCallback(false, []);
42
+ return sel;
43
+ }
44
+ };
45
+ const indexInfoOrPromise = getIndexInfo(queryFunctions, sel);
46
+ if (indexInfoOrPromise instanceof Promise) {
47
+ return indexInfoOrPromise.then(async (indexInfo) => {
48
+ const memo2 = memoOrPromise instanceof Promise ? await memoOrPromise : memoOrPromise;
49
+ const optimizedSelector2 = getSelector(indexInfo);
50
+ if (optimizedSelector2)
51
+ memo2.push(optimizedSelector2);
52
+ return memo2;
53
+ });
54
+ }
55
+ const memo = memoOrPromise;
56
+ if (memo instanceof Promise)
57
+ throw new Error("Mixing async and sync index providers is not supported");
58
+ const optimizedSelector = getSelector(indexInfoOrPromise);
59
+ if (optimizedSelector)
60
+ memo.push(optimizedSelector);
61
+ return memo;
62
+ }, []);
63
+ }
64
+ function getIndexInfo(queryFunctions, selector) {
65
+ if (selector == null || Object.keys(selector).length <= 0) {
66
+ return {
67
+ matched: false,
68
+ ids: [],
69
+ optimizedSelector: selector
70
+ };
71
+ }
72
+ const { $and, $or, ...rest } = selector;
73
+ const flatInfoOrPromise = getMergedIndexInfo(queryFunctions, rest);
74
+ const processFlatInfo = (flatInfo) => {
75
+ let { matched, ids } = flatInfo;
76
+ const newSelector = flatInfo.optimizedSelector;
77
+ const $andNewOrPromise = Array.isArray($and) ? optimizeLogicGate(queryFunctions, $and, (match, selIds) => {
78
+ if (!match)
79
+ return;
80
+ ids = matched ? intersection(ids, selIds) : selIds;
81
+ matched = true;
82
+ }) : void 0;
83
+ const process$and = ($andNew) => {
84
+ if ($andNew && $andNew.length > 0)
85
+ newSelector.$and = $andNew;
86
+ let hasNonIndexField = false;
87
+ const matchedBefore = matched;
88
+ const idsBefore = ids;
89
+ const process$or = ($orNew) => {
90
+ if ($orNew && $orNew.length > 0)
91
+ newSelector.$or = $orNew;
92
+ if (hasNonIndexField) {
93
+ newSelector.$or = $or;
94
+ matched = matchedBefore;
95
+ ids = idsBefore;
96
+ }
97
+ return {
98
+ matched,
99
+ ids: ids || [],
100
+ optimizedSelector: newSelector
101
+ };
102
+ };
103
+ const $orNewOrPromise = Array.isArray($or) ? optimizeLogicGate(queryFunctions, $or, (match, selIds) => {
104
+ if (match) {
105
+ ids = [.../* @__PURE__ */ new Set([...ids, ...selIds])];
106
+ matched = true;
107
+ } else {
108
+ hasNonIndexField = true;
109
+ }
110
+ }) : void 0;
111
+ if ($orNewOrPromise instanceof Promise) {
112
+ return $orNewOrPromise.then(($orNew) => process$or($orNew));
113
+ }
114
+ return process$or($orNewOrPromise);
115
+ };
116
+ if ($andNewOrPromise instanceof Promise) {
117
+ return $andNewOrPromise.then(($andNew) => process$and($andNew));
118
+ }
119
+ return process$and($andNewOrPromise);
120
+ };
121
+ if (flatInfoOrPromise instanceof Promise) {
122
+ return flatInfoOrPromise.then(processFlatInfo);
123
+ }
124
+ return processFlatInfo(flatInfoOrPromise);
5
125
  }
6
126
  export {
7
- match as default
127
+ getIndexInfo as default,
128
+ getMergedIndexInfo
8
129
  };
package/dist/index23.mjs CHANGED
@@ -1,28 +1,8 @@
1
- import get from "./index10.mjs";
2
- import set from "./index29.mjs";
3
- function project(item, fields) {
4
- const allFieldsDeactivated = Object.values(fields).every((value) => value === 0);
5
- if (allFieldsDeactivated) {
6
- const result2 = { ...item };
7
- Object.keys(fields).forEach((key) => {
8
- const fieldValue = get(item, key);
9
- if (fieldValue === void 0)
10
- return;
11
- set(result2, key, void 0, true);
12
- });
13
- return result2;
14
- }
15
- const result = {};
16
- Object.entries(fields).forEach(([key, value]) => {
17
- const fieldValue = get(item, key);
18
- if (fieldValue === void 0)
19
- return;
20
- if (fieldValue == null && value !== 1)
21
- return;
22
- set(result, key, value === 1 ? fieldValue : void 0);
23
- });
24
- return result;
1
+ import { Query } from "mingo";
2
+ function match(item, selector) {
3
+ const query = new Query(selector);
4
+ return query.test(item);
25
5
  }
26
6
  export {
27
- project as default
7
+ match as default
28
8
  };
package/dist/index24.mjs CHANGED
@@ -1,8 +1,28 @@
1
- function queryId(selector, options) {
2
- const selectorId = JSON.stringify(selector);
3
- const optionsId = options == null ? -1 : JSON.stringify(options);
4
- return `${selectorId}:${optionsId}`;
1
+ import get from "./index10.mjs";
2
+ import set from "./index31.mjs";
3
+ function project(item, fields) {
4
+ const allFieldsDeactivated = Object.values(fields).every((value) => value === 0);
5
+ if (allFieldsDeactivated) {
6
+ const result2 = { ...item };
7
+ Object.keys(fields).forEach((key) => {
8
+ const fieldValue = get(item, key);
9
+ if (fieldValue === void 0)
10
+ return;
11
+ set(result2, key, void 0, true);
12
+ });
13
+ return result2;
14
+ }
15
+ const result = {};
16
+ Object.entries(fields).forEach(([key, value]) => {
17
+ const fieldValue = get(item, key);
18
+ if (fieldValue === void 0)
19
+ return;
20
+ if (fieldValue == null && value !== 1)
21
+ return;
22
+ set(result, key, value === 1 ? fieldValue : void 0);
23
+ });
24
+ return result;
5
25
  }
6
26
  export {
7
- queryId as default
27
+ project as default
8
28
  };
package/dist/index27.mjs CHANGED
@@ -1,6 +1,47 @@
1
- function createIndexProvider(definition) {
2
- return definition;
1
+ function batchOnNextTick(onFlush) {
2
+ const queues = /* @__PURE__ */ new Map();
3
+ function enqueue(key, args) {
4
+ return new Promise((resolve, reject) => {
5
+ let q = queues.get(key);
6
+ if (!q) {
7
+ q = {
8
+ timer: null,
9
+ items: [],
10
+ flush: () => flush(key)
11
+ };
12
+ queues.set(key, q);
13
+ }
14
+ q.items.push({ args, resolve, reject });
15
+ if (q.timer == null) {
16
+ q.timer = setTimeout(() => {
17
+ q.timer = null;
18
+ void q.flush();
19
+ }, 0);
20
+ }
21
+ });
22
+ }
23
+ async function flush(key) {
24
+ const q = queues.get(key);
25
+ if (!q || q.items.length === 0)
26
+ return;
27
+ if (q.timer != null) {
28
+ clearTimeout(q.timer);
29
+ q.timer = null;
30
+ }
31
+ const items = q.items.splice(0);
32
+ onFlush(key, items.map((i) => i.args)).then((results) => {
33
+ for (const [index, result] of results.entries()) {
34
+ const { resolve } = items[index];
35
+ resolve(result);
36
+ }
37
+ }).catch((error) => {
38
+ for (const { reject } of items) {
39
+ reject(error);
40
+ }
41
+ });
42
+ }
43
+ return { enqueue, flush };
3
44
  }
4
45
  export {
5
- createIndexProvider as default
46
+ batchOnNextTick as default
6
47
  };
package/dist/index28.mjs CHANGED
@@ -1,8 +1,9 @@
1
- function intersection(...arrays) {
2
- if (arrays.length === 0)
3
- return [];
4
- return [...new Set(arrays.reduce((a, b) => a.filter((c) => b.includes(c))))];
1
+ function truthy(value) {
2
+ return !!value;
3
+ }
4
+ function compact(array) {
5
+ return array.filter(truthy);
5
6
  }
6
7
  export {
7
- intersection as default
8
+ compact as default
8
9
  };
package/dist/index29.mjs CHANGED
@@ -1,30 +1,8 @@
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 intersection(...arrays) {
2
+ if (arrays.length === 0)
3
+ return [];
4
+ return [...new Set(arrays.reduce((a, b) => a.filter((c) => b.includes(c))))];
27
5
  }
28
6
  export {
29
- set as default
7
+ intersection as default
30
8
  };
package/dist/index3.mjs CHANGED
@@ -4,6 +4,7 @@ import randomId from "./index8.mjs";
4
4
  import DefaultDataAdapter from "./index12.mjs";
5
5
  import modify from "./index7.mjs";
6
6
  import deepClone from "./index19.mjs";
7
+ import queryId from "./index20.mjs";
7
8
  import Cursor from "./index2.mjs";
8
9
  class Collection extends EventEmitter {
9
10
  static collections = [];
@@ -66,6 +67,7 @@ class Collection extends EventEmitter {
66
67
  isDisposed = false;
67
68
  postBatchCallbacks = /* @__PURE__ */ new Set();
68
69
  fieldTracking = false;
70
+ queryListenersMap = /* @__PURE__ */ new Map();
69
71
  constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
70
72
  super();
71
73
  const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name || `${this.constructor.name}-${randomId()}`;
@@ -190,6 +192,11 @@ class Collection extends EventEmitter {
190
192
  return item;
191
193
  return this.options.transform(item);
192
194
  }
195
+ transformAll(items, fields) {
196
+ if (!this.options.transformAll)
197
+ return items;
198
+ return this.options.transformAll(deepClone(items), fields);
199
+ }
193
200
  getItem(selector, options) {
194
201
  const itemsOrPromise = this.getItems(selector, { ...options, limit: 1 });
195
202
  if (itemsOrPromise instanceof Promise) {
@@ -204,21 +211,9 @@ class Collection extends EventEmitter {
204
211
  return this.profile(() => {
205
212
  if (!options?.async)
206
213
  return this.backend.getQueryResult(selector, options);
207
- return new Promise((resolve, reject) => {
208
- this.isPullingSignal.set(true);
209
- const cleanup = this.backend.onQueryStateChange(selector, options, (state) => {
210
- if (state === "error") {
211
- cleanup();
212
- reject(this.backend.getQueryError(selector, options) || new Error("Unknown error"));
213
- } else if (state === "complete") {
214
- cleanup();
215
- resolve(this.backend.getQueryResult(selector, options) || []);
216
- }
217
- });
218
- this.backend.registerQuery(selector, options);
219
- }).finally(() => {
214
+ this.isPullingSignal.set(true);
215
+ return this.backend.executeQuery(selector, options).finally(() => {
220
216
  this.isPullingSignal.set(false);
221
- this.backend.unregisterQuery(selector, options);
222
217
  });
223
218
  }, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
224
219
  }
@@ -230,6 +225,13 @@ class Collection extends EventEmitter {
230
225
  this.isPushingSignal.set(false);
231
226
  }
232
227
  }
228
+ queryListeners(query, listeners) {
229
+ const id = queryId(query.selector, query.options);
230
+ if (listeners != null) {
231
+ return this.queryListenersMap.set(id, listeners);
232
+ }
233
+ return this.queryListenersMap.get(id) ?? 0;
234
+ }
233
235
  /**
234
236
  * Disposes the collection, unregisters persistence adapters, clears memory, and
235
237
  * cleans up all resources used by the collection.
@@ -255,7 +257,17 @@ class Collection extends EventEmitter {
255
257
  throw new Error("Collection is disposed");
256
258
  if (selector !== void 0 && (!selector || typeof selector !== "object"))
257
259
  throw new Error("Invalid selector");
258
- const cursor = new Cursor((() => this.getItems(selector, options || {})), {
260
+ const getTransformedItems = () => {
261
+ const itemsOrPromise = this.getItems(selector, options || {});
262
+ if (itemsOrPromise instanceof Promise) {
263
+ return itemsOrPromise.then((items2) => {
264
+ return this.transformAll(items2, options?.fields);
265
+ });
266
+ }
267
+ const items = itemsOrPromise;
268
+ return this.transformAll(items, options?.fields);
269
+ };
270
+ const cursor = new Cursor(getTransformedItems, {
259
271
  reactive: this.options.reactivity,
260
272
  fieldTracking: this.fieldTracking,
261
273
  ...options,
@@ -268,17 +280,25 @@ class Collection extends EventEmitter {
268
280
  }
269
281
  requery();
270
282
  };
271
- this.backend.registerQuery(selector, options);
272
- const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options, (state) => {
283
+ const listeners = this.queryListeners({ selector, options });
284
+ if (listeners === 0)
285
+ this.backend.registerQuery(selector, options || {});
286
+ this.queryListeners({ selector, options }, listeners + 1);
287
+ const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options || {}, (state) => {
273
288
  if (state !== "complete")
274
289
  return;
275
290
  handleRequery();
276
291
  });
277
292
  this.emit("observer.created", selector, options);
278
293
  return () => {
279
- this.backend.unregisterQuery(selector, options);
280
- queryStateChangeCleanup();
281
- this.emit("observer.disposed", selector, options);
294
+ setTimeout(() => {
295
+ const newListeners = Math.max(0, this.queryListeners({ selector, options }) - 1);
296
+ if (newListeners === 0)
297
+ this.backend.unregisterQuery(selector, options || {});
298
+ this.queryListeners({ selector, options }, newListeners);
299
+ queryStateChangeCleanup();
300
+ this.emit("observer.disposed", selector, options);
301
+ }, 0);
282
302
  };
283
303
  }
284
304
  });
@@ -371,9 +391,8 @@ class Collection extends EventEmitter {
371
391
  throw new Error("Collection is disposed");
372
392
  if (!items)
373
393
  throw new Error("Invalid items");
374
- if (items.length === 0) {
394
+ if (items.length === 0)
375
395
  return [];
376
- }
377
396
  const ids = [];
378
397
  await this.batch(async () => {
379
398
  await Promise.all(items.map(async (item) => {
package/dist/index31.mjs CHANGED
@@ -1,10 +1,30 @@
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
+ 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;
7
27
  }
8
28
  export {
9
- uniqueBy as default
29
+ set as default
10
30
  };
@@ -0,0 +1,10 @@
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
+ });
7
+ }
8
+ export {
9
+ uniqueBy as default
10
+ };
@@ -0,0 +1,6 @@
1
+ function createIndexProvider(definition) {
2
+ return definition;
3
+ }
4
+ export {
5
+ createIndexProvider as default
6
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Groups multiple calls by key and flushes them on the next tick (macrotask).
3
+ * @param onFlush - Function that will be called with the key and all queued items when flushing.
4
+ * @returns An object with `enqueue` and `flush` methods.
5
+ * @example
6
+ * const batcher = batchOnNextTick<string>(async (key, items) => {
7
+ * // items is an array of { args, resolve, reject }
8
+ * // do something once with all args...
9
+ * })
10
+ *
11
+ * batcher.enqueue("my-key", [arg1, arg2])
12
+ */
13
+ export default function batchOnNextTick<TKey>(onFlush: (key: TKey, items: any[][]) => Promise<any[]>): {
14
+ enqueue: (key: TKey, args: any[]) => Promise<any>;
15
+ flush: (key: TKey) => Promise<void>;
16
+ };
@@ -4,7 +4,7 @@
4
4
  * @template T - The type of the value to clone.
5
5
  * @param value - The value to deep clone.
6
6
  * @returns A deep copy of the provided value.
7
- * @throws An error if the value is a function, as cloning functions is not supported.
7
+ * @throws {Error} An error if the value is a function, as cloning functions is not supported.
8
8
  */
9
9
  export declare function clone<T>(value: T): T;
10
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaldb/core",
3
- "version": "2.0.0-beta.0",
3
+ "version": "2.0.0-beta.2",
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",
@@ -55,9 +55,9 @@
55
55
  ],
56
56
  "dependencies": {
57
57
  "fast-sort": "^3.4.1",
58
- "mingo": "^6.5.1"
58
+ "mingo": "^7.1.1"
59
59
  },
60
60
  "devDependencies": {
61
- "zod": "^4.0.14"
61
+ "zod": "^4.3.5"
62
62
  }
63
63
  }