@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
@@ -1,7 +1,129 @@
1
1
  "use strict";
2
- const mingo = require("mingo");
3
- function match(item, selector) {
4
- const query = new mingo.Query(selector);
5
- return query.test(item);
2
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
+ const intersection = require("./index.cjs29.js");
4
+ function getMergedIndexInfo(queryFunctions, selector) {
5
+ return queryFunctions.reduce((memoOrPromise, queryFunction) => {
6
+ const resultOrPromise = queryFunction(selector);
7
+ const processResult = (memo2, result) => {
8
+ if (!result.matched)
9
+ return memo2;
10
+ const optimizedSelector = result.keepSelector ? memo2.optimizedSelector : Object.fromEntries(Object.entries(memo2.optimizedSelector).filter(([key]) => !result.fields.includes(key)));
11
+ return {
12
+ matched: true,
13
+ ids: [...new Set(memo2.matched ? intersection(memo2.ids, result.ids) : result.ids)],
14
+ optimizedSelector
15
+ };
16
+ };
17
+ if (resultOrPromise instanceof Promise) {
18
+ return resultOrPromise.then(async (result) => {
19
+ const memo2 = memoOrPromise instanceof Promise ? await memoOrPromise : memoOrPromise;
20
+ return processResult(memo2, result);
21
+ });
22
+ }
23
+ const memo = memoOrPromise;
24
+ if (memo instanceof Promise)
25
+ throw new Error("Mixing async and sync index providers is not supported");
26
+ return processResult(memo, resultOrPromise);
27
+ }, {
28
+ matched: false,
29
+ ids: [],
30
+ optimizedSelector: { ...selector }
31
+ });
6
32
  }
7
- module.exports = match;
33
+ function optimizeLogicGate(queryFunctions, logicGate, idsCallback) {
34
+ return logicGate.reduce((memoOrPromise, sel) => {
35
+ const getSelector = (indexInfo) => {
36
+ const { matched: selMatched, ids: selIds, optimizedSelector: optimizedSelector2 } = indexInfo;
37
+ if (selMatched) {
38
+ idsCallback(true, selIds);
39
+ if (Object.keys(optimizedSelector2).length > 0) {
40
+ return optimizedSelector2;
41
+ }
42
+ } else {
43
+ idsCallback(false, []);
44
+ return sel;
45
+ }
46
+ };
47
+ const indexInfoOrPromise = getIndexInfo(queryFunctions, sel);
48
+ if (indexInfoOrPromise instanceof Promise) {
49
+ return indexInfoOrPromise.then(async (indexInfo) => {
50
+ const memo2 = memoOrPromise instanceof Promise ? await memoOrPromise : memoOrPromise;
51
+ const optimizedSelector2 = getSelector(indexInfo);
52
+ if (optimizedSelector2)
53
+ memo2.push(optimizedSelector2);
54
+ return memo2;
55
+ });
56
+ }
57
+ const memo = memoOrPromise;
58
+ if (memo instanceof Promise)
59
+ throw new Error("Mixing async and sync index providers is not supported");
60
+ const optimizedSelector = getSelector(indexInfoOrPromise);
61
+ if (optimizedSelector)
62
+ memo.push(optimizedSelector);
63
+ return memo;
64
+ }, []);
65
+ }
66
+ function getIndexInfo(queryFunctions, selector) {
67
+ if (selector == null || Object.keys(selector).length <= 0) {
68
+ return {
69
+ matched: false,
70
+ ids: [],
71
+ optimizedSelector: selector
72
+ };
73
+ }
74
+ const { $and, $or, ...rest } = selector;
75
+ const flatInfoOrPromise = getMergedIndexInfo(queryFunctions, rest);
76
+ const processFlatInfo = (flatInfo) => {
77
+ let { matched, ids } = flatInfo;
78
+ const newSelector = flatInfo.optimizedSelector;
79
+ const $andNewOrPromise = Array.isArray($and) ? optimizeLogicGate(queryFunctions, $and, (match, selIds) => {
80
+ if (!match)
81
+ return;
82
+ ids = matched ? intersection(ids, selIds) : selIds;
83
+ matched = true;
84
+ }) : void 0;
85
+ const process$and = ($andNew) => {
86
+ if ($andNew && $andNew.length > 0)
87
+ newSelector.$and = $andNew;
88
+ let hasNonIndexField = false;
89
+ const matchedBefore = matched;
90
+ const idsBefore = ids;
91
+ const process$or = ($orNew) => {
92
+ if ($orNew && $orNew.length > 0)
93
+ newSelector.$or = $orNew;
94
+ if (hasNonIndexField) {
95
+ newSelector.$or = $or;
96
+ matched = matchedBefore;
97
+ ids = idsBefore;
98
+ }
99
+ return {
100
+ matched,
101
+ ids: ids || [],
102
+ optimizedSelector: newSelector
103
+ };
104
+ };
105
+ const $orNewOrPromise = Array.isArray($or) ? optimizeLogicGate(queryFunctions, $or, (match, selIds) => {
106
+ if (match) {
107
+ ids = [.../* @__PURE__ */ new Set([...ids, ...selIds])];
108
+ matched = true;
109
+ } else {
110
+ hasNonIndexField = true;
111
+ }
112
+ }) : void 0;
113
+ if ($orNewOrPromise instanceof Promise) {
114
+ return $orNewOrPromise.then(($orNew) => process$or($orNew));
115
+ }
116
+ return process$or($orNewOrPromise);
117
+ };
118
+ if ($andNewOrPromise instanceof Promise) {
119
+ return $andNewOrPromise.then(($andNew) => process$and($andNew));
120
+ }
121
+ return process$and($andNewOrPromise);
122
+ };
123
+ if (flatInfoOrPromise instanceof Promise) {
124
+ return flatInfoOrPromise.then(processFlatInfo);
125
+ }
126
+ return processFlatInfo(flatInfoOrPromise);
127
+ }
128
+ exports.default = getIndexInfo;
129
+ exports.getMergedIndexInfo = getMergedIndexInfo;
@@ -1,27 +1,7 @@
1
1
  "use strict";
2
- const get = require("./index.cjs10.js");
3
- const set = require("./index.cjs29.js");
4
- function project(item, fields) {
5
- const allFieldsDeactivated = Object.values(fields).every((value) => value === 0);
6
- if (allFieldsDeactivated) {
7
- const result2 = { ...item };
8
- Object.keys(fields).forEach((key) => {
9
- const fieldValue = get(item, key);
10
- if (fieldValue === void 0)
11
- return;
12
- set(result2, key, void 0, true);
13
- });
14
- return result2;
15
- }
16
- const result = {};
17
- Object.entries(fields).forEach(([key, value]) => {
18
- const fieldValue = get(item, key);
19
- if (fieldValue === void 0)
20
- return;
21
- if (fieldValue == null && value !== 1)
22
- return;
23
- set(result, key, value === 1 ? fieldValue : void 0);
24
- });
25
- return result;
2
+ const mingo = require("mingo");
3
+ function match(item, selector) {
4
+ const query = new mingo.Query(selector);
5
+ return query.test(item);
26
6
  }
27
- module.exports = project;
7
+ module.exports = match;
@@ -1,7 +1,27 @@
1
1
  "use strict";
2
- function queryId(selector, options) {
3
- const selectorId = JSON.stringify(selector);
4
- const optionsId = options == null ? -1 : JSON.stringify(options);
5
- return `${selectorId}:${optionsId}`;
2
+ const get = require("./index.cjs10.js");
3
+ const set = require("./index.cjs31.js");
4
+ function project(item, fields) {
5
+ const allFieldsDeactivated = Object.values(fields).every((value) => value === 0);
6
+ if (allFieldsDeactivated) {
7
+ const result2 = { ...item };
8
+ Object.keys(fields).forEach((key) => {
9
+ const fieldValue = get(item, key);
10
+ if (fieldValue === void 0)
11
+ return;
12
+ set(result2, key, void 0, true);
13
+ });
14
+ return result2;
15
+ }
16
+ const result = {};
17
+ Object.entries(fields).forEach(([key, value]) => {
18
+ const fieldValue = get(item, key);
19
+ if (fieldValue === void 0)
20
+ return;
21
+ if (fieldValue == null && value !== 1)
22
+ return;
23
+ set(result, key, value === 1 ? fieldValue : void 0);
24
+ });
25
+ return result;
6
26
  }
7
- module.exports = queryId;
27
+ module.exports = project;
@@ -1,5 +1,46 @@
1
1
  "use strict";
2
- function createIndexProvider(definition) {
3
- return definition;
2
+ function batchOnNextTick(onFlush) {
3
+ const queues = /* @__PURE__ */ new Map();
4
+ function enqueue(key, args) {
5
+ return new Promise((resolve, reject) => {
6
+ let q = queues.get(key);
7
+ if (!q) {
8
+ q = {
9
+ timer: null,
10
+ items: [],
11
+ flush: () => flush(key)
12
+ };
13
+ queues.set(key, q);
14
+ }
15
+ q.items.push({ args, resolve, reject });
16
+ if (q.timer == null) {
17
+ q.timer = setTimeout(() => {
18
+ q.timer = null;
19
+ void q.flush();
20
+ }, 0);
21
+ }
22
+ });
23
+ }
24
+ async function flush(key) {
25
+ const q = queues.get(key);
26
+ if (!q || q.items.length === 0)
27
+ return;
28
+ if (q.timer != null) {
29
+ clearTimeout(q.timer);
30
+ q.timer = null;
31
+ }
32
+ const items = q.items.splice(0);
33
+ onFlush(key, items.map((i) => i.args)).then((results) => {
34
+ for (const [index, result] of results.entries()) {
35
+ const { resolve } = items[index];
36
+ resolve(result);
37
+ }
38
+ }).catch((error) => {
39
+ for (const { reject } of items) {
40
+ reject(error);
41
+ }
42
+ });
43
+ }
44
+ return { enqueue, flush };
4
45
  }
5
- module.exports = createIndexProvider;
46
+ module.exports = batchOnNextTick;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
- function intersection(...arrays) {
3
- if (arrays.length === 0)
4
- return [];
5
- return [...new Set(arrays.reduce((a, b) => a.filter((c) => b.includes(c))))];
2
+ function truthy(value) {
3
+ return !!value;
6
4
  }
7
- module.exports = intersection;
5
+ function compact(array) {
6
+ return array.filter(truthy);
7
+ }
8
+ module.exports = compact;
@@ -1,29 +1,7 @@
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 intersection(...arrays) {
3
+ if (arrays.length === 0)
4
+ return [];
5
+ return [...new Set(arrays.reduce((a, b) => a.filter((c) => b.includes(c))))];
28
6
  }
29
- module.exports = set;
7
+ module.exports = intersection;
@@ -6,6 +6,7 @@ const randomId = require("./index.cjs8.js");
6
6
  const DefaultDataAdapter = require("./index.cjs12.js");
7
7
  const modify = require("./index.cjs7.js");
8
8
  const deepClone = require("./index.cjs19.js");
9
+ const queryId = require("./index.cjs20.js");
9
10
  const Cursor = require("./index.cjs2.js");
10
11
  class Collection extends EventEmitter {
11
12
  static collections = [];
@@ -68,6 +69,7 @@ class Collection extends EventEmitter {
68
69
  isDisposed = false;
69
70
  postBatchCallbacks = /* @__PURE__ */ new Set();
70
71
  fieldTracking = false;
72
+ queryListenersMap = /* @__PURE__ */ new Map();
71
73
  constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
72
74
  super();
73
75
  const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name || `${this.constructor.name}-${randomId()}`;
@@ -192,6 +194,11 @@ class Collection extends EventEmitter {
192
194
  return item;
193
195
  return this.options.transform(item);
194
196
  }
197
+ transformAll(items, fields) {
198
+ if (!this.options.transformAll)
199
+ return items;
200
+ return this.options.transformAll(deepClone.default(items), fields);
201
+ }
195
202
  getItem(selector, options) {
196
203
  const itemsOrPromise = this.getItems(selector, { ...options, limit: 1 });
197
204
  if (itemsOrPromise instanceof Promise) {
@@ -206,21 +213,9 @@ class Collection extends EventEmitter {
206
213
  return this.profile(() => {
207
214
  if (!options?.async)
208
215
  return this.backend.getQueryResult(selector, options);
209
- return new Promise((resolve, reject) => {
210
- this.isPullingSignal.set(true);
211
- const cleanup = this.backend.onQueryStateChange(selector, options, (state) => {
212
- if (state === "error") {
213
- cleanup();
214
- reject(this.backend.getQueryError(selector, options) || new Error("Unknown error"));
215
- } else if (state === "complete") {
216
- cleanup();
217
- resolve(this.backend.getQueryResult(selector, options) || []);
218
- }
219
- });
220
- this.backend.registerQuery(selector, options);
221
- }).finally(() => {
216
+ this.isPullingSignal.set(true);
217
+ return this.backend.executeQuery(selector, options).finally(() => {
222
218
  this.isPullingSignal.set(false);
223
- this.backend.unregisterQuery(selector, options);
224
219
  });
225
220
  }, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
226
221
  }
@@ -232,6 +227,13 @@ class Collection extends EventEmitter {
232
227
  this.isPushingSignal.set(false);
233
228
  }
234
229
  }
230
+ queryListeners(query, listeners) {
231
+ const id = queryId(query.selector, query.options);
232
+ if (listeners != null) {
233
+ return this.queryListenersMap.set(id, listeners);
234
+ }
235
+ return this.queryListenersMap.get(id) ?? 0;
236
+ }
235
237
  /**
236
238
  * Disposes the collection, unregisters persistence adapters, clears memory, and
237
239
  * cleans up all resources used by the collection.
@@ -257,7 +259,17 @@ class Collection extends EventEmitter {
257
259
  throw new Error("Collection is disposed");
258
260
  if (selector !== void 0 && (!selector || typeof selector !== "object"))
259
261
  throw new Error("Invalid selector");
260
- const cursor = new Cursor.default((() => this.getItems(selector, options || {})), {
262
+ const getTransformedItems = () => {
263
+ const itemsOrPromise = this.getItems(selector, options || {});
264
+ if (itemsOrPromise instanceof Promise) {
265
+ return itemsOrPromise.then((items2) => {
266
+ return this.transformAll(items2, options?.fields);
267
+ });
268
+ }
269
+ const items = itemsOrPromise;
270
+ return this.transformAll(items, options?.fields);
271
+ };
272
+ const cursor = new Cursor.default(getTransformedItems, {
261
273
  reactive: this.options.reactivity,
262
274
  fieldTracking: this.fieldTracking,
263
275
  ...options,
@@ -270,17 +282,25 @@ class Collection extends EventEmitter {
270
282
  }
271
283
  requery();
272
284
  };
273
- this.backend.registerQuery(selector, options);
274
- const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options, (state) => {
285
+ const listeners = this.queryListeners({ selector, options });
286
+ if (listeners === 0)
287
+ this.backend.registerQuery(selector, options || {});
288
+ this.queryListeners({ selector, options }, listeners + 1);
289
+ const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options || {}, (state) => {
275
290
  if (state !== "complete")
276
291
  return;
277
292
  handleRequery();
278
293
  });
279
294
  this.emit("observer.created", selector, options);
280
295
  return () => {
281
- this.backend.unregisterQuery(selector, options);
282
- queryStateChangeCleanup();
283
- this.emit("observer.disposed", selector, options);
296
+ setTimeout(() => {
297
+ const newListeners = Math.max(0, this.queryListeners({ selector, options }) - 1);
298
+ if (newListeners === 0)
299
+ this.backend.unregisterQuery(selector, options || {});
300
+ this.queryListeners({ selector, options }, newListeners);
301
+ queryStateChangeCleanup();
302
+ this.emit("observer.disposed", selector, options);
303
+ }, 0);
284
304
  };
285
305
  }
286
306
  });
@@ -373,9 +393,8 @@ class Collection extends EventEmitter {
373
393
  throw new Error("Collection is disposed");
374
394
  if (!items)
375
395
  throw new Error("Invalid items");
376
- if (items.length === 0) {
396
+ if (items.length === 0)
377
397
  return [];
378
- }
379
398
  const ids = [];
380
399
  await this.batch(async () => {
381
400
  await Promise.all(items.map(async (item) => {
@@ -1,9 +1,29 @@
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
+ 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;
8
28
  }
9
- module.exports = uniqueBy;
29
+ module.exports = set;
@@ -0,0 +1,9 @@
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
+ });
8
+ }
9
+ module.exports = uniqueBy;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ function createIndexProvider(definition) {
3
+ return definition;
4
+ }
5
+ module.exports = createIndexProvider;
package/dist/index.d.ts CHANGED
@@ -2,7 +2,8 @@ export type { default as ReactivityAdapter } from './types/ReactivityAdapter';
2
2
  export type { default as StorageAdapter, Changeset, } from './types/StorageAdapter';
3
3
  export type { default as Selector } from './types/Selector';
4
4
  export type { default as Modifier } from './types/Modifier';
5
- export type { BaseItem, ObserveCallbacks, CursorOptions, Transform, SortSpecifier, FieldSpecifier, FindOptions, CollectionOptions, } from './Collection';
5
+ export type { BaseItem, ObserveCallbacks, CursorOptions, Transform, TransformAll, SortSpecifier, FieldSpecifier, FindOptions, CollectionOptions, } from './Collection';
6
+ export type { default as DataAdapter } from './DataAdapter';
6
7
  export { default as Cursor } from './Collection/Cursor';
7
8
  export { default as Collection } from './Collection';
8
9
  export { default as createStorageAdapter } from './createStorageAdapter';
package/dist/index12.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import createIndex from "./index20.mjs";
2
- import getIndexInfo from "./index21.mjs";
1
+ import createIndex from "./index21.mjs";
2
+ import getIndexInfo from "./index22.mjs";
3
3
  import deepClone from "./index19.mjs";
4
4
  import EventEmitter from "./index9.mjs";
5
5
  import isEqual from "./index6.mjs";
6
- import match from "./index22.mjs";
6
+ import match from "./index23.mjs";
7
7
  import modify from "./index7.mjs";
8
- import project from "./index23.mjs";
9
- import queryId from "./index24.mjs";
8
+ import project from "./index24.mjs";
9
+ import queryId from "./index20.mjs";
10
10
  import serializeValue from "./index11.mjs";
11
11
  import sortItems from "./index25.mjs";
12
12
  function hasPendingUpdates(pendingUpdates) {
@@ -142,14 +142,28 @@ class DefaultDataAdapter {
142
142
  return;
143
143
  this.queuedQueryUpdates.set(collection.name, { added: [], modified: [], removed: [] });
144
144
  const flatItems = [...changes.added, ...changes.modified, ...changes.removed];
145
- const queries = [...this.activeQueries.get(collection.name)?.values() ?? []].filter(({ selector }) => flatItems.some((item) => match(item, selector)));
145
+ const itemIds = new Set(flatItems.map((i) => i.id));
146
+ const queries = [
147
+ ...this.activeQueries.get(collection.name)?.values() ?? []
148
+ ].filter(({ selector, options }) => {
149
+ const idsInQuery = this.cachedQueryResults.get(collection.name)?.get(queryId(selector, options))?.map((i) => i.id) ?? [];
150
+ if (idsInQuery.some((id) => itemIds.has(id)))
151
+ return true;
152
+ return flatItems.some((item) => match(item, selector));
153
+ });
146
154
  queries.forEach(({ selector, options }) => {
147
- const emitter = this.queryEmitters.get(collection.name);
148
- if (!emitter)
149
- return;
150
- emitter.emit("change", selector, options, "complete");
155
+ this.executeAndCacheQuery(collection, selector, options);
151
156
  });
152
157
  }
158
+ executeAndCacheQuery(collection, selector, options) {
159
+ const result = this.executeQuery(collection, selector, options);
160
+ this.cachedQueryResults.set(collection.name, this.cachedQueryResults.get(collection.name) || /* @__PURE__ */ new Map());
161
+ this.cachedQueryResults.get(collection.name)?.set(queryId(selector, options), result);
162
+ const emitter = this.queryEmitters.get(collection.name);
163
+ if (!emitter)
164
+ return;
165
+ emitter.emit("change", selector, options, "complete");
166
+ }
153
167
  updateQueries(collection, changes) {
154
168
  this.queuedQueryUpdates.set(collection.name, this.queuedQueryUpdates.get(collection.name) || { added: [], modified: [], removed: [] });
155
169
  this.queuedQueryUpdates.get(collection.name)?.added.push(...changes.added);
@@ -161,6 +175,7 @@ class DefaultDataAdapter {
161
175
  this.ensureStorageAdapter(collection.name);
162
176
  this.items.set(collection.name, this.items.get(collection.name) ?? /* @__PURE__ */ new Map());
163
177
  this.queryEmitters.set(collection.name, this.queryEmitters.get(collection.name) ?? new EventEmitter());
178
+ this.queryEmitters.get(collection.name)?.setMaxListeners(Infinity);
164
179
  this.activeQueries.set(collection.name, this.activeQueries.get(collection.name) || /* @__PURE__ */ new Map());
165
180
  this.indices.set(collection.name, indices.map((field) => createIndex(field)));
166
181
  this.rebuildIndices(collection);
@@ -206,7 +221,7 @@ class DefaultDataAdapter {
206
221
  },
207
222
  updateMany: async (selector, modifier) => {
208
223
  const { $setOnInsert, ...restModifier } = modifier;
209
- const items = backend.getQueryResult(selector);
224
+ const items = this.executeQuery(collection, selector, {});
210
225
  const changedItems = items.map((item) => {
211
226
  const modifiedItem = modify(deepClone(item), restModifier);
212
227
  const hasItemWithSameId = item.id !== modifiedItem.id && this.getItem(collection, { id: modifiedItem.id }) != null;
@@ -262,7 +277,7 @@ class DefaultDataAdapter {
262
277
  return [item];
263
278
  },
264
279
  removeMany: async (selector) => {
265
- const items = backend.getQueryResult(selector);
280
+ const items = backend.getQueryResult(selector, {});
266
281
  items.forEach((item) => {
267
282
  this.items.get(collection.name)?.delete(serializeValue(item.id));
268
283
  });
@@ -282,10 +297,7 @@ class DefaultDataAdapter {
282
297
  selector,
283
298
  options
284
299
  });
285
- const emitter = this.queryEmitters.get(collection.name);
286
- if (!emitter)
287
- throw new Error(`Query emitter not found for collection ${collection.name}`);
288
- emitter.emit("change", selector, options, "complete");
300
+ this.executeAndCacheQuery(collection, selector, options);
289
301
  },
290
302
  unregisterQuery: (selector, options) => {
291
303
  if (!this.activeQueries.get(collection.name))
@@ -298,7 +310,7 @@ class DefaultDataAdapter {
298
310
  if (!emitter)
299
311
  throw new Error(`Query emitter not found for collection ${collection.name}`);
300
312
  const handler = (querySelector, queryOptions, state) => {
301
- if (querySelector !== selector || queryOptions !== options)
313
+ if (queryId(querySelector, queryOptions) !== queryId(selector, options))
302
314
  return;
303
315
  callback(state);
304
316
  };
@@ -309,14 +321,16 @@ class DefaultDataAdapter {
309
321
  },
310
322
  getQueryError: () => null,
311
323
  getQueryResult: (selector, options) => {
312
- const result = this.executeQuery(collection, selector, options);
313
324
  const isQueryActive = this.activeQueries.get(collection.name)?.has(queryId(selector, options));
314
325
  if (isQueryActive) {
315
- this.cachedQueryResults.set(collection.name, this.cachedQueryResults.get(collection.name) || /* @__PURE__ */ new Map());
316
- this.cachedQueryResults.get(collection.name)?.set(queryId(selector, options), result);
326
+ const results = this.cachedQueryResults.get(collection.name)?.get(queryId(selector, options));
327
+ if (!results)
328
+ throw new Error("Cached query results are not defined!");
329
+ return results;
317
330
  }
318
- return result;
331
+ return this.executeQuery(collection, selector, options);
319
332
  },
333
+ executeQuery: (selector, options) => Promise.resolve(this.executeQuery(collection, selector, options)),
320
334
  // lifecycle methods
321
335
  dispose: async () => {
322
336
  const adapter = this.storageAdapters.get(collection.name);