@signaldb/core 2.0.0-beta.16 → 2.0.0-beta.18

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.
@@ -42,10 +42,15 @@ export default class WorkerDataAdapter implements DataAdapter {
42
42
  private execInBackground;
43
43
  private queryItemsById;
44
44
  private flattenPendingWrites;
45
+ private static pushPendingEntry;
46
+ private static dropPendingEntry;
47
+ private static writeFlatEntry;
45
48
  private static providesFullItems;
46
49
  private observableItems;
47
50
  private observableItemsByIds;
48
51
  private servedResult;
52
+ private advanceServedResult;
53
+ private changesetForIds;
49
54
  private computeServedResult;
50
55
  /**
51
56
  * Registers a write's effect locally and notifies every active query it
@@ -185,24 +185,44 @@ var WorkerDataAdapter = class WorkerDataAdapter {
185
185
  return query.itemsById;
186
186
  }
187
187
  flattenPendingWrites(collectionName) {
188
- const pending = this.pendingWrites.get(collectionName);
189
- if (!pending || pending.size === 0) return null;
190
- const upserts = /* @__PURE__ */ new Map();
191
- const deletes = /* @__PURE__ */ new Set();
192
- [...pending.entries()].sort(([a], [b]) => a - b).forEach(([, write]) => {
193
- write.upserts.forEach((item, id) => {
194
- upserts.set(id, item);
195
- deletes.delete(id);
196
- });
197
- write.deletes.forEach((id) => {
198
- deletes.add(id);
199
- upserts.delete(id);
200
- });
188
+ const state = this.pendingWrites.get(collectionName);
189
+ if (!state || state.writes.size === 0) return null;
190
+ return state.flat;
191
+ }
192
+ static pushPendingEntry(state, id, item, seq) {
193
+ const stack = state.byId.get(id);
194
+ if (stack) stack.push({
195
+ seq,
196
+ item
201
197
  });
202
- return {
203
- upserts,
204
- deletes
205
- };
198
+ else state.byId.set(id, [{
199
+ seq,
200
+ item
201
+ }]);
202
+ WorkerDataAdapter.writeFlatEntry(state, id, item);
203
+ }
204
+ static dropPendingEntry(state, id, seq) {
205
+ const stack = state.byId.get(id);
206
+ if (!stack) return;
207
+ const index = stack.findIndex((entry) => entry.seq === seq);
208
+ if (index !== -1) stack.splice(index, 1);
209
+ const top = stack.at(-1);
210
+ if (!top) {
211
+ state.byId.delete(id);
212
+ state.flat.upserts.delete(id);
213
+ state.flat.deletes.delete(id);
214
+ return;
215
+ }
216
+ WorkerDataAdapter.writeFlatEntry(state, id, top.item);
217
+ }
218
+ static writeFlatEntry(state, id, item) {
219
+ if (item === null) {
220
+ state.flat.deletes.add(id);
221
+ state.flat.upserts.delete(id);
222
+ return;
223
+ }
224
+ state.flat.upserts.set(id, item);
225
+ state.flat.deletes.delete(id);
206
226
  }
207
227
  static providesFullItems(query) {
208
228
  return query.options?.fields == null;
@@ -246,7 +266,7 @@ var WorkerDataAdapter = class WorkerDataAdapter {
246
266
  servedResult(collectionName, query) {
247
267
  const pendingVersion = this.pendingWriteVersions.get(collectionName) ?? 0;
248
268
  if (query.served && query.served.fromItems === query.items && query.served.pendingVersion === pendingVersion) return query.served.items;
249
- const items = this.computeServedResult(collectionName, query);
269
+ const items = this.advanceServedResult(collectionName, query, pendingVersion) ?? this.computeServedResult(collectionName, query);
250
270
  query.served = {
251
271
  items,
252
272
  fromItems: query.items,
@@ -254,6 +274,39 @@ var WorkerDataAdapter = class WorkerDataAdapter {
254
274
  };
255
275
  return items;
256
276
  }
277
+ advanceServedResult(collectionName, query, pendingVersion) {
278
+ const served = query.served;
279
+ if (!served || served.fromItems !== query.items) return null;
280
+ if (served.pendingVersion !== pendingVersion - 1) return null;
281
+ if (!WorkerDataAdapter.providesFullItems(query) || query.options?.limit != null) return null;
282
+ const state = this.pendingWrites.get(collectionName);
283
+ if (state?.lastChange == null || state.lastChange.version !== pendingVersion) return null;
284
+ return require_incrementalQueryUpdate.mergeChangesetIntoResult(served.items, query.selector, query.options, this.changesetForIds(collectionName, query, state.lastChange.ids));
285
+ }
286
+ changesetForIds(collectionName, query, ids) {
287
+ const pending = this.flattenPendingWrites(collectionName);
288
+ const stored = this.queryItemsById(query);
289
+ const upserts = [];
290
+ const deletes = [];
291
+ ids.forEach((id) => {
292
+ const pendingItem = pending?.upserts.get(id);
293
+ if (pendingItem) {
294
+ upserts.push(pendingItem);
295
+ return;
296
+ }
297
+ if (pending?.deletes.has(id)) {
298
+ deletes.push(id);
299
+ return;
300
+ }
301
+ const storedItem = stored.get(id);
302
+ if (storedItem) upserts.push(storedItem);
303
+ else deletes.push(id);
304
+ });
305
+ return {
306
+ upserts,
307
+ deletes
308
+ };
309
+ }
257
310
  computeServedResult(collectionName, query) {
258
311
  const pending = this.flattenPendingWrites(collectionName);
259
312
  if (!pending) return query.items;
@@ -288,22 +341,45 @@ var WorkerDataAdapter = class WorkerDataAdapter {
288
341
  const affected = this.affectedQueries(collectionName, upserts, affectedIds);
289
342
  const servedBefore = this.servedResults(collectionName, affected);
290
343
  const seq = this.pendingWriteSeq += 1;
291
- const pending = this.pendingWrites.get(collectionName) ?? /* @__PURE__ */ new Map();
292
- pending.set(seq, {
344
+ const state = this.pendingWrites.get(collectionName) ?? {
345
+ writes: /* @__PURE__ */ new Map(),
346
+ byId: /* @__PURE__ */ new Map(),
347
+ flat: {
348
+ upserts: /* @__PURE__ */ new Map(),
349
+ deletes: /* @__PURE__ */ new Set()
350
+ },
351
+ lastChange: null
352
+ };
353
+ const write = {
293
354
  upserts: new Map(upserts.map((item) => [item.id, item])),
294
355
  deletes: new Set(deletes)
295
- });
296
- this.pendingWrites.set(collectionName, pending);
356
+ };
357
+ state.writes.set(seq, write);
358
+ write.upserts.forEach((item, id) => WorkerDataAdapter.pushPendingEntry(state, id, item, seq));
359
+ write.deletes.forEach((id) => WorkerDataAdapter.pushPendingEntry(state, id, null, seq));
360
+ this.pendingWrites.set(collectionName, state);
297
361
  this.bumpPendingWriteVersion(collectionName);
362
+ state.lastChange = {
363
+ version: this.pendingWriteVersions.get(collectionName) ?? 0,
364
+ ids: [...affectedIds]
365
+ };
298
366
  this.notifyWithDeltas(collectionName, affected, servedBefore);
299
367
  return () => {
300
368
  const current = this.pendingWrites.get(collectionName);
301
369
  if (!current) return;
370
+ const settled = current.writes.get(seq);
371
+ if (!settled) return;
302
372
  const affectedOnDrop = this.affectedQueries(collectionName, upserts, affectedIds);
303
373
  const beforeDrop = this.servedResults(collectionName, affectedOnDrop);
304
- current.delete(seq);
305
- if (current.size === 0) this.pendingWrites.delete(collectionName);
374
+ current.writes.delete(seq);
375
+ settled.upserts.forEach((item, id) => WorkerDataAdapter.dropPendingEntry(current, id, seq));
376
+ settled.deletes.forEach((id) => WorkerDataAdapter.dropPendingEntry(current, id, seq));
377
+ if (current.writes.size === 0) this.pendingWrites.delete(collectionName);
306
378
  this.bumpPendingWriteVersion(collectionName);
379
+ if (current.writes.size > 0) current.lastChange = {
380
+ version: this.pendingWriteVersions.get(collectionName) ?? 0,
381
+ ids: [...affectedIds]
382
+ };
307
383
  this.notifyWithDeltas(collectionName, affectedOnDrop, beforeDrop);
308
384
  };
309
385
  }
package/dist/index34.mjs CHANGED
@@ -185,24 +185,44 @@ var WorkerDataAdapter = class WorkerDataAdapter {
185
185
  return query.itemsById;
186
186
  }
187
187
  flattenPendingWrites(collectionName) {
188
- const pending = this.pendingWrites.get(collectionName);
189
- if (!pending || pending.size === 0) return null;
190
- const upserts = /* @__PURE__ */ new Map();
191
- const deletes = /* @__PURE__ */ new Set();
192
- [...pending.entries()].sort(([a], [b]) => a - b).forEach(([, write]) => {
193
- write.upserts.forEach((item, id) => {
194
- upserts.set(id, item);
195
- deletes.delete(id);
196
- });
197
- write.deletes.forEach((id) => {
198
- deletes.add(id);
199
- upserts.delete(id);
200
- });
188
+ const state = this.pendingWrites.get(collectionName);
189
+ if (!state || state.writes.size === 0) return null;
190
+ return state.flat;
191
+ }
192
+ static pushPendingEntry(state, id, item, seq) {
193
+ const stack = state.byId.get(id);
194
+ if (stack) stack.push({
195
+ seq,
196
+ item
201
197
  });
202
- return {
203
- upserts,
204
- deletes
205
- };
198
+ else state.byId.set(id, [{
199
+ seq,
200
+ item
201
+ }]);
202
+ WorkerDataAdapter.writeFlatEntry(state, id, item);
203
+ }
204
+ static dropPendingEntry(state, id, seq) {
205
+ const stack = state.byId.get(id);
206
+ if (!stack) return;
207
+ const index = stack.findIndex((entry) => entry.seq === seq);
208
+ if (index !== -1) stack.splice(index, 1);
209
+ const top = stack.at(-1);
210
+ if (!top) {
211
+ state.byId.delete(id);
212
+ state.flat.upserts.delete(id);
213
+ state.flat.deletes.delete(id);
214
+ return;
215
+ }
216
+ WorkerDataAdapter.writeFlatEntry(state, id, top.item);
217
+ }
218
+ static writeFlatEntry(state, id, item) {
219
+ if (item === null) {
220
+ state.flat.deletes.add(id);
221
+ state.flat.upserts.delete(id);
222
+ return;
223
+ }
224
+ state.flat.upserts.set(id, item);
225
+ state.flat.deletes.delete(id);
206
226
  }
207
227
  static providesFullItems(query) {
208
228
  return query.options?.fields == null;
@@ -246,7 +266,7 @@ var WorkerDataAdapter = class WorkerDataAdapter {
246
266
  servedResult(collectionName, query) {
247
267
  const pendingVersion = this.pendingWriteVersions.get(collectionName) ?? 0;
248
268
  if (query.served && query.served.fromItems === query.items && query.served.pendingVersion === pendingVersion) return query.served.items;
249
- const items = this.computeServedResult(collectionName, query);
269
+ const items = this.advanceServedResult(collectionName, query, pendingVersion) ?? this.computeServedResult(collectionName, query);
250
270
  query.served = {
251
271
  items,
252
272
  fromItems: query.items,
@@ -254,6 +274,39 @@ var WorkerDataAdapter = class WorkerDataAdapter {
254
274
  };
255
275
  return items;
256
276
  }
277
+ advanceServedResult(collectionName, query, pendingVersion) {
278
+ const served = query.served;
279
+ if (!served || served.fromItems !== query.items) return null;
280
+ if (served.pendingVersion !== pendingVersion - 1) return null;
281
+ if (!WorkerDataAdapter.providesFullItems(query) || query.options?.limit != null) return null;
282
+ const state = this.pendingWrites.get(collectionName);
283
+ if (state?.lastChange == null || state.lastChange.version !== pendingVersion) return null;
284
+ return mergeChangesetIntoResult(served.items, query.selector, query.options, this.changesetForIds(collectionName, query, state.lastChange.ids));
285
+ }
286
+ changesetForIds(collectionName, query, ids) {
287
+ const pending = this.flattenPendingWrites(collectionName);
288
+ const stored = this.queryItemsById(query);
289
+ const upserts = [];
290
+ const deletes = [];
291
+ ids.forEach((id) => {
292
+ const pendingItem = pending?.upserts.get(id);
293
+ if (pendingItem) {
294
+ upserts.push(pendingItem);
295
+ return;
296
+ }
297
+ if (pending?.deletes.has(id)) {
298
+ deletes.push(id);
299
+ return;
300
+ }
301
+ const storedItem = stored.get(id);
302
+ if (storedItem) upserts.push(storedItem);
303
+ else deletes.push(id);
304
+ });
305
+ return {
306
+ upserts,
307
+ deletes
308
+ };
309
+ }
257
310
  computeServedResult(collectionName, query) {
258
311
  const pending = this.flattenPendingWrites(collectionName);
259
312
  if (!pending) return query.items;
@@ -288,22 +341,45 @@ var WorkerDataAdapter = class WorkerDataAdapter {
288
341
  const affected = this.affectedQueries(collectionName, upserts, affectedIds);
289
342
  const servedBefore = this.servedResults(collectionName, affected);
290
343
  const seq = this.pendingWriteSeq += 1;
291
- const pending = this.pendingWrites.get(collectionName) ?? /* @__PURE__ */ new Map();
292
- pending.set(seq, {
344
+ const state = this.pendingWrites.get(collectionName) ?? {
345
+ writes: /* @__PURE__ */ new Map(),
346
+ byId: /* @__PURE__ */ new Map(),
347
+ flat: {
348
+ upserts: /* @__PURE__ */ new Map(),
349
+ deletes: /* @__PURE__ */ new Set()
350
+ },
351
+ lastChange: null
352
+ };
353
+ const write = {
293
354
  upserts: new Map(upserts.map((item) => [item.id, item])),
294
355
  deletes: new Set(deletes)
295
- });
296
- this.pendingWrites.set(collectionName, pending);
356
+ };
357
+ state.writes.set(seq, write);
358
+ write.upserts.forEach((item, id) => WorkerDataAdapter.pushPendingEntry(state, id, item, seq));
359
+ write.deletes.forEach((id) => WorkerDataAdapter.pushPendingEntry(state, id, null, seq));
360
+ this.pendingWrites.set(collectionName, state);
297
361
  this.bumpPendingWriteVersion(collectionName);
362
+ state.lastChange = {
363
+ version: this.pendingWriteVersions.get(collectionName) ?? 0,
364
+ ids: [...affectedIds]
365
+ };
298
366
  this.notifyWithDeltas(collectionName, affected, servedBefore);
299
367
  return () => {
300
368
  const current = this.pendingWrites.get(collectionName);
301
369
  if (!current) return;
370
+ const settled = current.writes.get(seq);
371
+ if (!settled) return;
302
372
  const affectedOnDrop = this.affectedQueries(collectionName, upserts, affectedIds);
303
373
  const beforeDrop = this.servedResults(collectionName, affectedOnDrop);
304
- current.delete(seq);
305
- if (current.size === 0) this.pendingWrites.delete(collectionName);
374
+ current.writes.delete(seq);
375
+ settled.upserts.forEach((item, id) => WorkerDataAdapter.dropPendingEntry(current, id, seq));
376
+ settled.deletes.forEach((id) => WorkerDataAdapter.dropPendingEntry(current, id, seq));
377
+ if (current.writes.size === 0) this.pendingWrites.delete(collectionName);
306
378
  this.bumpPendingWriteVersion(collectionName);
379
+ if (current.writes.size > 0) current.lastChange = {
380
+ version: this.pendingWriteVersions.get(collectionName) ?? 0,
381
+ ids: [...affectedIds]
382
+ };
307
383
  this.notifyWithDeltas(collectionName, affectedOnDrop, beforeDrop);
308
384
  };
309
385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaldb/core",
3
- "version": "2.0.0-beta.16",
3
+ "version": "2.0.0-beta.18",
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",