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

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 (52) hide show
  1. package/dist/.vite/manifest.json +25 -12
  2. package/dist/AutoFetchDataAdapter.d.ts +0 -1
  3. package/dist/Collection/index.d.ts +2 -0
  4. package/dist/DataAdapter.d.ts +7 -6
  5. package/dist/DefaultDataAdapter.d.ts +1 -0
  6. package/dist/WorkerDataAdapter.d.ts +4 -1
  7. package/dist/WorkerDataAdapterHost.d.ts +9 -6
  8. package/dist/index.cjs12.js +32 -21
  9. package/dist/index.cjs13.js +25 -13
  10. package/dist/index.cjs14.js +31 -37
  11. package/dist/index.cjs15.js +120 -75
  12. package/dist/index.cjs16.js +45 -36
  13. package/dist/index.cjs17.js +1 -1
  14. package/dist/index.cjs2.js +5 -2
  15. package/dist/index.cjs20.js +5 -102
  16. package/dist/index.cjs21.js +93 -118
  17. package/dist/index.cjs22.js +127 -5
  18. package/dist/index.cjs23.js +5 -25
  19. package/dist/index.cjs24.js +25 -5
  20. package/dist/index.cjs26.js +1 -1
  21. package/dist/index.cjs27.js +44 -3
  22. package/dist/index.cjs28.js +6 -5
  23. package/dist/index.cjs29.js +7 -27
  24. package/dist/index.cjs3.js +25 -21
  25. package/dist/index.cjs30.js +3 -40
  26. package/dist/index.cjs31.js +5 -7
  27. package/dist/index.cjs32.js +29 -0
  28. package/dist/index.cjs33.js +42 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index12.mjs +32 -21
  31. package/dist/index13.mjs +25 -13
  32. package/dist/index14.mjs +31 -37
  33. package/dist/index15.mjs +120 -75
  34. package/dist/index16.mjs +45 -36
  35. package/dist/index17.mjs +1 -1
  36. package/dist/index2.mjs +5 -2
  37. package/dist/index20.mjs +5 -102
  38. package/dist/index21.mjs +93 -117
  39. package/dist/index22.mjs +126 -5
  40. package/dist/index23.mjs +5 -25
  41. package/dist/index24.mjs +25 -5
  42. package/dist/index26.mjs +1 -1
  43. package/dist/index27.mjs +44 -3
  44. package/dist/index28.mjs +6 -5
  45. package/dist/index29.mjs +7 -27
  46. package/dist/index3.mjs +25 -21
  47. package/dist/index30.mjs +3 -40
  48. package/dist/index31.mjs +5 -7
  49. package/dist/index32.mjs +30 -0
  50. package/dist/index33.mjs +43 -0
  51. package/dist/utils/batchOnNextTick.d.ts +16 -0
  52. package/package.json +1 -1
@@ -1,9 +1,7 @@
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 intersection(...arrays) {
3
+ if (arrays.length === 0)
4
+ return [];
5
+ return [...new Set(arrays.reduce((a, b) => a.filter((c) => b.includes(c))))];
8
6
  }
9
- module.exports = uniqueBy;
7
+ module.exports = intersection;
@@ -0,0 +1,29 @@
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;
28
+ }
29
+ module.exports = set;
@@ -0,0 +1,42 @@
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;
41
+ }
42
+ module.exports = isFieldExpression;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export type { default as StorageAdapter, Changeset, } from './types/StorageAdapt
3
3
  export type { default as Selector } from './types/Selector';
4
4
  export type { default as Modifier } from './types/Modifier';
5
5
  export type { BaseItem, ObserveCallbacks, CursorOptions, Transform, 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,13 @@ 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
+ return this.cachedQueryResults.get(collection.name)?.get(queryId(selector, options)) ?? [];
317
327
  }
318
- return result;
328
+ return this.executeQuery(collection, selector, options);
319
329
  },
330
+ executeQuery: (selector, options) => Promise.resolve(this.executeQuery(collection, selector, options)),
320
331
  // lifecycle methods
321
332
  dispose: async () => {
322
333
  const adapter = this.storageAdapters.get(collection.name);
package/dist/index13.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import deepClone from "./index19.mjs";
2
- import match from "./index22.mjs";
2
+ import match from "./index23.mjs";
3
3
  import modify from "./index7.mjs";
4
- import queryId from "./index24.mjs";
4
+ import queryId from "./index20.mjs";
5
5
  import isEqual from "./index6.mjs";
6
- import getIndexInfo from "./index21.mjs";
6
+ import getIndexInfo from "./index22.mjs";
7
7
  import getMatchingKeys from "./index26.mjs";
8
8
  import sortItems from "./index25.mjs";
9
- import project from "./index23.mjs";
9
+ import project from "./index24.mjs";
10
10
  class AsyncDataAdapter {
11
11
  options;
12
12
  id;
@@ -119,6 +119,13 @@ class AsyncDataAdapter {
119
119
  getQueryError,
120
120
  getQueryResult,
121
121
  onQueryStateChange,
122
+ executeQuery: async (selector, options) => {
123
+ await ready;
124
+ registerQuery(selector, options);
125
+ const result = getQueryResult(selector, options);
126
+ unregisterQuery(selector, options);
127
+ return result;
128
+ },
122
129
  dispose: async () => {
123
130
  this.storageAdapters.delete(collection.name);
124
131
  this.queries.delete(collection.name);
@@ -134,10 +141,7 @@ class AsyncDataAdapter {
134
141
  const storage = this.storageAdapters.get(collectionName);
135
142
  if (!storage)
136
143
  throw new Error(`No persistence adapter for collection ${collectionName}`);
137
- await Promise.all([
138
- storage.createIndex("id"),
139
- ...indices.map((index) => storage.createIndex(index))
140
- ]);
144
+ await Promise.all(indices.map((index) => storage.createIndex(index)));
141
145
  await storage.setup();
142
146
  }
143
147
  ensureStorageAdapter(name) {
@@ -199,20 +203,28 @@ class AsyncDataAdapter {
199
203
  rec.items = items;
200
204
  }
201
205
  async getIndexInfo(collectionName, selector) {
202
- const storage = this.storageAdapters.get(collectionName);
203
- if (!storage)
206
+ const storageAdapter = this.storageAdapters.get(collectionName);
207
+ if (!storageAdapter)
204
208
  throw new Error(`No persistence adapter for collection ${collectionName}`);
205
209
  if (selector != null && Object.keys(selector).length === 1 && "id" in selector && typeof selector.id !== "object") {
206
- return { matched: true, ids: [selector.id], optimizedSelector: {} };
210
+ return {
211
+ matched: true,
212
+ ids: [selector.id].filter(Boolean),
213
+ optimizedSelector: {}
214
+ };
207
215
  }
208
216
  if (selector == null) {
209
- return { matched: false, ids: [], optimizedSelector: {} };
217
+ return {
218
+ matched: false,
219
+ ids: [],
220
+ optimizedSelector: {}
221
+ };
210
222
  }
211
223
  const indices = this.collectionIndices.get(collectionName) ?? [];
212
224
  return getIndexInfo(indices.map((field) => async (flatSelector) => {
213
225
  if (!Object.hasOwnProperty.call(flatSelector, field))
214
226
  return { matched: false };
215
- const index = await storage.readIndex(field);
227
+ const index = await storageAdapter.readIndex(field);
216
228
  const fieldSelector = flatSelector[field];
217
229
  const filtersForNull = fieldSelector == null || fieldSelector.$exists === false;
218
230
  const keys = filtersForNull ? { include: null, exclude: [...index.keys()].filter((key) => key != null) } : getMatchingKeys(field, flatSelector);
package/dist/index14.mjs CHANGED
@@ -1,17 +1,23 @@
1
- import queryId from "./index24.mjs";
1
+ import queryId from "./index20.mjs";
2
2
  import randomId from "./index8.mjs";
3
+ import batchOnNextTick from "./index27.mjs";
3
4
  class WorkerDataAdapter {
4
5
  worker;
5
6
  options;
6
7
  id;
7
8
  isDisposed = false;
8
9
  workerReady;
10
+ log = () => {
11
+ };
9
12
  collectionReady = /* @__PURE__ */ new Map();
13
+ batchExecutionHelpers = /* @__PURE__ */ new Map();
10
14
  queries = {};
11
15
  constructor(worker, options) {
12
16
  this.worker = worker;
13
17
  this.options = options;
14
18
  this.id = this.options.id || "default-worker-data-adapter";
19
+ if (this.options.log)
20
+ this.log = this.options.log;
15
21
  this.workerReady = new Promise((resolve, reject) => {
16
22
  const timeoutId = setTimeout(() => {
17
23
  reject(new Error("WorkerDataAdapter initialization timed out"));
@@ -50,6 +56,7 @@ class WorkerDataAdapter {
50
56
  return;
51
57
  if (id !== messageId)
52
58
  return;
59
+ this.log(method, "result", data ?? error);
53
60
  if (error) {
54
61
  reject(error);
55
62
  } else {
@@ -66,74 +73,61 @@ class WorkerDataAdapter {
66
73
  });
67
74
  });
68
75
  }
69
- queryListeners(collectionName, query, listeners) {
70
- if (listeners != null) {
71
- return this.updateQuery(collectionName, query, { listeners });
72
- }
73
- const id = queryId(query.selector, query.options);
74
- const collectionQueries = this.queries[collectionName];
75
- if (!collectionQueries)
76
- return 0;
77
- const existing = collectionQueries.get(id);
78
- return existing?.listeners || 0;
76
+ enqueueBatched(collectionName, method, args) {
77
+ const helper = this.batchExecutionHelpers.get(collectionName);
78
+ if (!helper)
79
+ throw new Error(`Collection "${collectionName}" is not registered in WorkerDataAdapter`);
80
+ return helper.enqueue(method, args);
79
81
  }
82
+ // ---------- end batching integration ----------
80
83
  updateQuery(collectionName, query, update) {
81
84
  const id = queryId(query.selector, query.options);
82
85
  const collectionQueries = this.queries[collectionName];
83
86
  if (!collectionQueries)
84
87
  return;
85
88
  const existing = collectionQueries.get(id);
86
- collectionQueries.set(id, {
87
- listeners: 0,
89
+ const newState = {
88
90
  state: "active",
89
91
  error: null,
90
92
  items: [],
91
93
  ...existing,
92
94
  ...update
93
- });
95
+ };
96
+ collectionQueries.set(id, newState);
94
97
  this.queries[collectionName] = collectionQueries;
95
98
  }
96
99
  createCollectionBackend(collection, indices = []) {
97
100
  this.queries[collection.name] = /* @__PURE__ */ new Map();
98
101
  void this.exec("registerCollection", collection.name, indices);
99
102
  this.collectionReady.set(collection.name, this.exec("isReady", collection.name));
103
+ this.batchExecutionHelpers.set(collection.name, batchOnNextTick(async (method, args) => this.exec(method, collection.name, args)));
100
104
  return {
101
105
  insert: async (item) => {
102
- return this.exec("insert", collection.name, item);
106
+ return this.enqueueBatched(collection.name, "insert", [item]);
103
107
  },
104
108
  updateOne: async (selector, modifier) => {
105
- return this.exec("updateOne", collection.name, selector, modifier);
109
+ return this.enqueueBatched(collection.name, "updateOne", [selector, modifier]);
106
110
  },
107
111
  updateMany: async (selector, modifier) => {
108
- return this.exec("updateMany", collection.name, selector, modifier);
112
+ return this.enqueueBatched(collection.name, "updateMany", [selector, modifier]);
109
113
  },
110
114
  replaceOne: async (selector, replacement) => {
111
- return this.exec("replaceOne", collection.name, selector, replacement);
115
+ return this.enqueueBatched(collection.name, "replaceOne", [selector, replacement]);
112
116
  },
113
117
  removeOne: async (selector) => {
114
- return this.exec("removeOne", collection.name, selector);
118
+ return this.enqueueBatched(collection.name, "removeOne", [selector]);
115
119
  },
116
120
  removeMany: async (selector) => {
117
- return this.exec("removeMany", collection.name, selector);
121
+ return this.enqueueBatched(collection.name, "removeMany", [selector]);
118
122
  },
119
123
  // methods for registering and unregistering queries that will be called from the collection during find/findOne
120
124
  registerQuery: (selector, options) => {
121
- const listeners = this.queryListeners(collection.name, { selector, options });
122
- if (listeners === 0) {
123
- this.updateQuery(collection.name, { selector, options }, { state: "active", error: null, items: [] });
124
- void this.exec("registerQuery", collection.name, selector, options);
125
- }
126
- this.queryListeners(collection.name, { selector, options }, listeners + 1);
125
+ this.updateQuery(collection.name, { selector, options }, { state: "active", error: null, items: [] });
126
+ void this.exec("registerQuery", collection.name, selector, options);
127
127
  },
128
128
  unregisterQuery: (selector, options) => {
129
- setTimeout(() => {
130
- const listeners = this.queryListeners(collection.name, { selector, options });
131
- const newListeners = Math.max(0, listeners - 1);
132
- if (newListeners === 0) {
133
- this.queries[collection.name]?.delete(queryId(selector, options));
134
- void this.exec("unregisterQuery", collection.name, selector, options);
135
- }
136
- }, 0);
129
+ this.queries[collection.name]?.delete(queryId(selector, options));
130
+ void this.exec("unregisterQuery", collection.name, selector, options);
137
131
  },
138
132
  getQueryState: (selector, options) => {
139
133
  const query = this.queries[collection.name]?.get(queryId(selector, options));
@@ -159,10 +153,9 @@ class WorkerDataAdapter {
159
153
  return;
160
154
  if (collectionName !== collection.name)
161
155
  return;
162
- if (JSON.stringify(responseSelector) !== JSON.stringify(selector))
163
- return;
164
- if (JSON.stringify(responseOptions) !== JSON.stringify(options))
156
+ if (queryId(responseSelector, responseOptions) !== queryId(selector, options))
165
157
  return;
158
+ this.log("queryUpdate", responseSelector, responseOptions, state, data ?? error);
166
159
  this.updateQuery(collection.name, {
167
160
  selector: responseSelector,
168
161
  options: responseOptions
@@ -174,6 +167,7 @@ class WorkerDataAdapter {
174
167
  this.worker.removeEventListener("message", handler);
175
168
  };
176
169
  },
170
+ executeQuery: (selector, options) => this.exec("executeQuery", collection.name, selector, options),
177
171
  // lifecycle methods
178
172
  dispose: async () => {
179
173
  await this.exec("unregisterCollection", collection.name);