@signaldb/core 2.0.0-beta.13 → 2.0.0-beta.15

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 (83) hide show
  1. package/dist/.vite/manifest.json +72 -46
  2. package/dist/AsyncDataAdapter.d.ts +11 -2
  3. package/dist/Collection/Cursor.d.ts +11 -1
  4. package/dist/Collection/Observer.d.ts +31 -0
  5. package/dist/Collection/index.d.ts +40 -0
  6. package/dist/DataAdapter.d.ts +15 -2
  7. package/dist/WorkerDataAdapter.d.ts +25 -2
  8. package/dist/WorkerDataAdapterHost.d.ts +2 -0
  9. package/dist/index.cjs.js +15 -15
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.mjs +15 -15
  12. package/dist/index10.cjs.js +7 -19
  13. package/dist/index10.mjs +7 -19
  14. package/dist/index11.cjs.js +19 -41
  15. package/dist/index11.mjs +19 -41
  16. package/dist/index12.cjs.js +41 -18
  17. package/dist/index12.mjs +41 -18
  18. package/dist/index13.cjs.js +18 -41
  19. package/dist/index13.mjs +18 -41
  20. package/dist/index14.cjs.js +39 -80
  21. package/dist/index14.mjs +39 -80
  22. package/dist/index15.cjs.js +82 -11
  23. package/dist/index15.mjs +82 -11
  24. package/dist/index16.cjs.js +11 -132
  25. package/dist/index16.mjs +11 -132
  26. package/dist/index17.cjs.js +127 -38
  27. package/dist/index17.mjs +127 -38
  28. package/dist/index18.cjs.js +43 -11
  29. package/dist/index18.mjs +43 -11
  30. package/dist/index19.cjs.js +10 -18
  31. package/dist/index19.mjs +11 -19
  32. package/dist/index20.cjs.js +19 -33
  33. package/dist/index20.mjs +19 -33
  34. package/dist/index21.cjs.js +33 -31
  35. package/dist/index21.mjs +33 -31
  36. package/dist/index22.cjs.js +31 -21
  37. package/dist/index22.mjs +31 -21
  38. package/dist/index23.cjs.js +16 -14
  39. package/dist/index23.mjs +16 -14
  40. package/dist/index24.cjs.js +15 -338
  41. package/dist/index24.mjs +15 -338
  42. package/dist/index25.cjs.js +122 -554
  43. package/dist/index25.mjs +121 -554
  44. package/dist/index26.cjs.js +34 -7
  45. package/dist/index26.mjs +34 -7
  46. package/dist/index27.cjs.js +339 -7
  47. package/dist/index27.mjs +339 -7
  48. package/dist/index28.cjs.js +595 -83
  49. package/dist/index28.mjs +595 -82
  50. package/dist/index29.cjs.js +8 -422
  51. package/dist/index29.mjs +8 -422
  52. package/dist/index30.cjs.js +7 -68
  53. package/dist/index30.mjs +7 -68
  54. package/dist/index31.cjs.js +86 -28
  55. package/dist/index31.mjs +85 -28
  56. package/dist/index32.cjs.js +446 -278
  57. package/dist/index32.mjs +446 -278
  58. package/dist/index33.cjs.js +68 -17
  59. package/dist/index33.mjs +68 -17
  60. package/dist/index34.cjs.js +460 -304
  61. package/dist/index34.mjs +460 -304
  62. package/dist/index35.cjs.js +14 -532
  63. package/dist/index35.mjs +14 -532
  64. package/dist/index36.cjs.js +389 -0
  65. package/dist/index36.mjs +389 -0
  66. package/dist/index37.cjs.js +539 -0
  67. package/dist/index37.mjs +539 -0
  68. package/dist/index4.cjs.js +199 -140
  69. package/dist/index4.mjs +195 -140
  70. package/dist/index5.cjs.js +152 -253
  71. package/dist/index5.mjs +152 -253
  72. package/dist/index6.cjs.js +267 -89
  73. package/dist/index6.mjs +267 -89
  74. package/dist/index7.cjs.js +121 -29
  75. package/dist/index7.mjs +121 -29
  76. package/dist/index8.cjs.js +29 -8
  77. package/dist/index8.mjs +29 -8
  78. package/dist/index9.cjs.js +8 -7
  79. package/dist/index9.mjs +8 -7
  80. package/dist/utils/incrementalQueryUpdate.d.ts +60 -0
  81. package/dist/utils/projectItems.d.ts +12 -0
  82. package/dist/utils/queryDelta.d.ts +83 -0
  83. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "src/AsyncDataAdapter.ts": {
3
- "file": "index29.cjs.js",
3
+ "file": "index32.cjs.js",
4
4
  "name": "AsyncDataAdapter",
5
5
  "src": "src/AsyncDataAdapter.ts",
6
6
  "isDynamicEntry": true,
@@ -8,16 +8,18 @@
8
8
  "src/getIndexInfo.ts",
9
9
  "src/utils/deepClone.ts",
10
10
  "src/utils/getMatchingKeys.ts",
11
+ "src/utils/incrementalQueryUpdate.ts",
11
12
  "src/utils/isEqual.ts",
12
13
  "src/utils/match.ts",
13
14
  "src/utils/modify.ts",
14
- "src/utils/project.ts",
15
+ "src/utils/projectItems.ts",
16
+ "src/utils/queryDelta.ts",
15
17
  "src/utils/queryId.ts",
16
18
  "src/utils/sortItems.ts"
17
19
  ]
18
20
  },
19
21
  "src/AutoFetchDataAdapter.ts": {
20
- "file": "index35.cjs.js",
22
+ "file": "index37.cjs.js",
21
23
  "name": "AutoFetchDataAdapter",
22
24
  "src": "src/AutoFetchDataAdapter.ts",
23
25
  "isDynamicEntry": true,
@@ -34,7 +36,7 @@
34
36
  ]
35
37
  },
36
38
  "src/Collection/Cursor.ts": {
37
- "file": "index5.cjs.js",
39
+ "file": "index6.cjs.js",
38
40
  "name": "Collection/Cursor",
39
41
  "src": "src/Collection/Cursor.ts",
40
42
  "isDynamicEntry": true,
@@ -43,17 +45,18 @@
43
45
  ]
44
46
  },
45
47
  "src/Collection/Observer.ts": {
46
- "file": "index4.cjs.js",
48
+ "file": "index5.cjs.js",
47
49
  "name": "Collection/Observer",
48
50
  "src": "src/Collection/Observer.ts",
49
51
  "isDynamicEntry": true,
50
52
  "imports": [
51
53
  "src/utils/isEqual.ts",
54
+ "src/utils/queryDelta.ts",
52
55
  "src/utils/uniqueBy.ts"
53
56
  ]
54
57
  },
55
58
  "src/Collection/index.ts": {
56
- "file": "index25.cjs.js",
59
+ "file": "index28.cjs.js",
57
60
  "name": "Collection/index",
58
61
  "src": "src/Collection/index.ts",
59
62
  "isDynamicEntry": true,
@@ -70,7 +73,7 @@
70
73
  ]
71
74
  },
72
75
  "src/DefaultDataAdapter.ts": {
73
- "file": "index24.cjs.js",
76
+ "file": "index27.cjs.js",
74
77
  "name": "DefaultDataAdapter",
75
78
  "src": "src/DefaultDataAdapter.ts",
76
79
  "isDynamicEntry": true,
@@ -79,32 +82,35 @@
79
82
  "src/getIndexInfo.ts",
80
83
  "src/utils/EventEmitter.ts",
81
84
  "src/utils/deepClone.ts",
85
+ "src/utils/incrementalQueryUpdate.ts",
82
86
  "src/utils/isEqual.ts",
83
87
  "src/utils/match.ts",
84
88
  "src/utils/modify.ts",
85
- "src/utils/project.ts",
89
+ "src/utils/projectItems.ts",
90
+ "src/utils/queryDelta.ts",
86
91
  "src/utils/queryId.ts",
87
92
  "src/utils/serializeValue.ts",
88
93
  "src/utils/sortItems.ts"
89
94
  ]
90
95
  },
91
96
  "src/WorkerDataAdapter.ts": {
92
- "file": "index32.cjs.js",
97
+ "file": "index34.cjs.js",
93
98
  "name": "WorkerDataAdapter",
94
99
  "src": "src/WorkerDataAdapter.ts",
95
100
  "isDynamicEntry": true,
96
101
  "imports": [
97
- "src/utils/applyQueryOptions.ts",
98
102
  "src/utils/batchOnNextTick.ts",
99
103
  "src/utils/deepClone.ts",
104
+ "src/utils/incrementalQueryUpdate.ts",
100
105
  "src/utils/match.ts",
101
106
  "src/utils/modify.ts",
107
+ "src/utils/queryDelta.ts",
102
108
  "src/utils/queryId.ts",
103
109
  "src/utils/randomId.ts"
104
110
  ]
105
111
  },
106
112
  "src/WorkerDataAdapterHost.ts": {
107
- "file": "index34.cjs.js",
113
+ "file": "index36.cjs.js",
108
114
  "name": "WorkerDataAdapterHost",
109
115
  "src": "src/WorkerDataAdapterHost.ts",
110
116
  "isDynamicEntry": true,
@@ -113,16 +119,18 @@
113
119
  "src/utils/compact.ts",
114
120
  "src/utils/deepClone.ts",
115
121
  "src/utils/getMatchingKeys.ts",
122
+ "src/utils/incrementalQueryUpdate.ts",
116
123
  "src/utils/isEqual.ts",
117
124
  "src/utils/match.ts",
118
125
  "src/utils/modify.ts",
119
- "src/utils/project.ts",
126
+ "src/utils/projectItems.ts",
127
+ "src/utils/queryDelta.ts",
120
128
  "src/utils/queryId.ts",
121
129
  "src/utils/sortItems.ts"
122
130
  ]
123
131
  },
124
132
  "src/createIndex.ts": {
125
- "file": "index14.cjs.js",
133
+ "file": "index15.cjs.js",
126
134
  "name": "createIndex",
127
135
  "src": "src/createIndex.ts",
128
136
  "isDynamicEntry": true,
@@ -134,25 +142,25 @@
134
142
  ]
135
143
  },
136
144
  "src/createIndexProvider.ts": {
137
- "file": "index9.cjs.js",
145
+ "file": "index10.cjs.js",
138
146
  "name": "createIndexProvider",
139
147
  "src": "src/createIndexProvider.ts",
140
148
  "isDynamicEntry": true
141
149
  },
142
150
  "src/createReactivityAdapter.ts": {
143
- "file": "index27.cjs.js",
151
+ "file": "index30.cjs.js",
144
152
  "name": "createReactivityAdapter",
145
153
  "src": "src/createReactivityAdapter.ts",
146
154
  "isDynamicEntry": true
147
155
  },
148
156
  "src/createStorageAdapter.ts": {
149
- "file": "index26.cjs.js",
157
+ "file": "index29.cjs.js",
150
158
  "name": "createStorageAdapter",
151
159
  "src": "src/createStorageAdapter.ts",
152
160
  "isDynamicEntry": true
153
161
  },
154
162
  "src/getIndexInfo.ts": {
155
- "file": "index16.cjs.js",
163
+ "file": "index17.cjs.js",
156
164
  "name": "getIndexInfo",
157
165
  "src": "src/getIndexInfo.ts",
158
166
  "isDynamicEntry": true,
@@ -185,54 +193,43 @@
185
193
  ]
186
194
  },
187
195
  "src/utils/EventEmitter.ts": {
188
- "file": "index6.cjs.js",
196
+ "file": "index7.cjs.js",
189
197
  "name": "utils/EventEmitter",
190
198
  "src": "src/utils/EventEmitter.ts",
191
199
  "isDynamicEntry": true
192
200
  },
193
- "src/utils/applyQueryOptions.ts": {
194
- "file": "index31.cjs.js",
195
- "name": "utils/applyQueryOptions",
196
- "src": "src/utils/applyQueryOptions.ts",
197
- "isDynamicEntry": true,
198
- "imports": [
199
- "src/utils/match.ts",
200
- "src/utils/project.ts",
201
- "src/utils/sortItems.ts"
202
- ]
203
- },
204
201
  "src/utils/batchOnNextTick.ts": {
205
- "file": "index30.cjs.js",
202
+ "file": "index33.cjs.js",
206
203
  "name": "utils/batchOnNextTick",
207
204
  "src": "src/utils/batchOnNextTick.ts",
208
205
  "isDynamicEntry": true
209
206
  },
210
207
  "src/utils/compact.ts": {
211
- "file": "index33.cjs.js",
208
+ "file": "index35.cjs.js",
212
209
  "name": "utils/compact",
213
210
  "src": "src/utils/compact.ts",
214
211
  "isDynamicEntry": true
215
212
  },
216
213
  "src/utils/createSignal.ts": {
217
- "file": "index7.cjs.js",
214
+ "file": "index8.cjs.js",
218
215
  "name": "utils/createSignal",
219
216
  "src": "src/utils/createSignal.ts",
220
217
  "isDynamicEntry": true
221
218
  },
222
219
  "src/utils/deepClone.ts": {
223
- "file": "index17.cjs.js",
220
+ "file": "index18.cjs.js",
224
221
  "name": "utils/deepClone",
225
222
  "src": "src/utils/deepClone.ts",
226
223
  "isDynamicEntry": true
227
224
  },
228
225
  "src/utils/get.ts": {
229
- "file": "index10.cjs.js",
226
+ "file": "index11.cjs.js",
230
227
  "name": "utils/get",
231
228
  "src": "src/utils/get.ts",
232
229
  "isDynamicEntry": true
233
230
  },
234
231
  "src/utils/getMatchingKeys.ts": {
235
- "file": "index13.cjs.js",
232
+ "file": "index14.cjs.js",
236
233
  "name": "utils/getMatchingKeys",
237
234
  "src": "src/utils/getMatchingKeys.ts",
238
235
  "isDynamicEntry": true,
@@ -241,8 +238,19 @@
241
238
  "src/utils/serializeValue.ts"
242
239
  ]
243
240
  },
241
+ "src/utils/incrementalQueryUpdate.ts": {
242
+ "file": "index25.cjs.js",
243
+ "name": "utils/incrementalQueryUpdate",
244
+ "src": "src/utils/incrementalQueryUpdate.ts",
245
+ "isDynamicEntry": true,
246
+ "imports": [
247
+ "src/utils/match.ts",
248
+ "src/utils/projectItems.ts",
249
+ "src/utils/sortItems.ts"
250
+ ]
251
+ },
244
252
  "src/utils/intersection.ts": {
245
- "file": "index15.cjs.js",
253
+ "file": "index16.cjs.js",
246
254
  "name": "utils/intersection",
247
255
  "src": "src/utils/intersection.ts",
248
256
  "isDynamicEntry": true
@@ -254,19 +262,19 @@
254
262
  "isDynamicEntry": true
255
263
  },
256
264
  "src/utils/isFieldExpression.ts": {
257
- "file": "index11.cjs.js",
265
+ "file": "index12.cjs.js",
258
266
  "name": "utils/isFieldExpression",
259
267
  "src": "src/utils/isFieldExpression.ts",
260
268
  "isDynamicEntry": true
261
269
  },
262
270
  "src/utils/match.ts": {
263
- "file": "index18.cjs.js",
271
+ "file": "index19.cjs.js",
264
272
  "name": "utils/match",
265
273
  "src": "src/utils/match.ts",
266
274
  "isDynamicEntry": true
267
275
  },
268
276
  "src/utils/modify.ts": {
269
- "file": "index19.cjs.js",
277
+ "file": "index20.cjs.js",
270
278
  "name": "utils/modify",
271
279
  "src": "src/utils/modify.ts",
272
280
  "isDynamicEntry": true,
@@ -275,7 +283,7 @@
275
283
  ]
276
284
  },
277
285
  "src/utils/project.ts": {
278
- "file": "index21.cjs.js",
286
+ "file": "index22.cjs.js",
279
287
  "name": "utils/project",
280
288
  "src": "src/utils/project.ts",
281
289
  "isDynamicEntry": true,
@@ -284,38 +292,56 @@
284
292
  "src/utils/set.ts"
285
293
  ]
286
294
  },
295
+ "src/utils/projectItems.ts": {
296
+ "file": "index23.cjs.js",
297
+ "name": "utils/projectItems",
298
+ "src": "src/utils/projectItems.ts",
299
+ "isDynamicEntry": true,
300
+ "imports": [
301
+ "src/utils/project.ts"
302
+ ]
303
+ },
304
+ "src/utils/queryDelta.ts": {
305
+ "file": "index4.cjs.js",
306
+ "name": "utils/queryDelta",
307
+ "src": "src/utils/queryDelta.ts",
308
+ "isDynamicEntry": true,
309
+ "imports": [
310
+ "src/utils/isEqual.ts"
311
+ ]
312
+ },
287
313
  "src/utils/queryId.ts": {
288
- "file": "index22.cjs.js",
314
+ "file": "index26.cjs.js",
289
315
  "name": "utils/queryId",
290
316
  "src": "src/utils/queryId.ts",
291
317
  "isDynamicEntry": true
292
318
  },
293
319
  "src/utils/randomId.ts": {
294
- "file": "index8.cjs.js",
320
+ "file": "index9.cjs.js",
295
321
  "name": "utils/randomId",
296
322
  "src": "src/utils/randomId.ts",
297
323
  "isDynamicEntry": true
298
324
  },
299
325
  "src/utils/reactiveOrAsync.ts": {
300
- "file": "index28.cjs.js",
326
+ "file": "index31.cjs.js",
301
327
  "name": "utils/reactiveOrAsync",
302
328
  "src": "src/utils/reactiveOrAsync.ts",
303
329
  "isDynamicEntry": true
304
330
  },
305
331
  "src/utils/serializeValue.ts": {
306
- "file": "index12.cjs.js",
332
+ "file": "index13.cjs.js",
307
333
  "name": "utils/serializeValue",
308
334
  "src": "src/utils/serializeValue.ts",
309
335
  "isDynamicEntry": true
310
336
  },
311
337
  "src/utils/set.ts": {
312
- "file": "index20.cjs.js",
338
+ "file": "index21.cjs.js",
313
339
  "name": "utils/set",
314
340
  "src": "src/utils/set.ts",
315
341
  "isDynamicEntry": true
316
342
  },
317
343
  "src/utils/sortItems.ts": {
318
- "file": "index23.cjs.js",
344
+ "file": "index24.cjs.js",
319
345
  "name": "utils/sortItems",
320
346
  "src": "src/utils/sortItems.ts",
321
347
  "isDynamicEntry": true,
@@ -80,16 +80,25 @@ export default class AsyncDataAdapter implements DataAdapter {
80
80
  * @param qid - query id
81
81
  * @param state - new state
82
82
  * @param error - error if state is 'error', null otherwise
83
+ * @param delta - what changed about the result, when that is known
83
84
  */
84
85
  private publishState;
85
86
  private publishResult;
87
+ private queryItemIds;
86
88
  private getIndexInfo;
87
89
  private queryItems;
88
90
  private executeQuery;
89
91
  /**
90
- * After mutations, recompute and push updates for affected active queries
92
+ * After mutations, bring every affected active query up to date.
93
+ *
94
+ * A query whose previous result is enough to answer the change is brought up to date from that
95
+ * result alone — no round trip to the storage, and no detour through `'active'`, because there is
96
+ * no window in which the query is stale. Only a query the change cannot be reasoned about
97
+ * locally — a window onto a larger set, or one that has never been answered — goes back to the
98
+ * store, and that one gets the same retry and reporting behaviour a freshly registered query
99
+ * gets: a refresh that fails silently leaves exactly the same dead cursor.
91
100
  * @param collectionName - name of the collection
92
- * @param affectedItems - item states before and/or after the mutation
101
+ * @param changes - the items the write created, updated or removed
93
102
  */
94
103
  private checkQueryUpdates;
95
104
  private insert;
@@ -1,4 +1,5 @@
1
1
  import { default as ReactivityAdapter } from '../types/ReactivityAdapter';
2
+ import { QueryDelta } from '../utils/queryDelta';
2
3
  import { BaseItem, FindOptions, Transform } from './types';
3
4
  import { ObserveCallbacks } from './Observer';
4
5
  /**
@@ -25,7 +26,7 @@ export interface QueryStateAccessor {
25
26
  }
26
27
  export interface CursorOptions<T extends BaseItem, U = T, Async extends boolean = false> extends FindOptions<T, Async> {
27
28
  transform?: Transform<T, U>;
28
- bindEvents?: (requery: () => void) => () => void;
29
+ bindEvents?: (requery: () => void, applyDelta: (delta: QueryDelta<T>) => void) => () => void;
29
30
  queryState?: QueryStateAccessor;
30
31
  }
31
32
  /**
@@ -152,4 +153,13 @@ export default class Cursor<T extends BaseItem, U = T, Async extends boolean = f
152
153
  * criteria have changed, and you want to ensure the cursor reflects the latest state.
153
154
  */
154
155
  requery(): void;
156
+ /**
157
+ * Brings the cursor up to date from a description of what changed, rather than by re-running the
158
+ * query and comparing the result with the previous one.
159
+ *
160
+ * Falls back to `requery` when the delta does not fit the result the cursor currently holds, so
161
+ * a caller never has to decide which of the two is safe.
162
+ * @param delta - The change to apply.
163
+ */
164
+ applyDelta(delta: QueryDelta<T>): void;
155
165
  }
@@ -1,3 +1,4 @@
1
+ import { QueryDelta } from '../utils/queryDelta';
1
2
  type AddedCallback<T> = (item: T) => void;
2
3
  type AddedBeforeCallback<T> = (item: T, before: T) => void;
3
4
  type ChangedCallback<T> = (item: T) => void;
@@ -42,6 +43,36 @@ export default class Observer<T extends {
42
43
  * @param getItems - A function that returns a promise resolving to the new items or the items themselves.
43
44
  */
44
45
  runChecks(getItems: () => Promise<T[]> | T[]): void;
46
+ /**
47
+ * Brings the observer up to date from a description of what changed, instead of from the new
48
+ * result.
49
+ *
50
+ * `runChecks` has to rediscover the change by comparing the whole new result against the whole
51
+ * old one — a cost proportional to the result, paid on every write, to find out that one row
52
+ * moved. When the change is already known it can simply be reported, and the cost becomes
53
+ * proportional to the change.
54
+ *
55
+ * The delta must have been computed against exactly the result this observer holds. If it was
56
+ * not, this reports nothing, falls back to `runChecks`, and returns `false`.
57
+ *
58
+ * Note that the reported moves are minimal, where a comparison reports every item whose
59
+ * neighbour changed. Applying them yields the same order either way — there are simply fewer of
60
+ * them.
61
+ * @param delta - The change to report.
62
+ * @param getItems - Used to fall back to a comparison when the delta cannot be applied.
63
+ * @returns Whether the delta was applied.
64
+ */
65
+ applyDelta(delta: QueryDelta<T>, getItems: () => Promise<T[]> | T[]): boolean;
66
+ /**
67
+ * Reports a delta and adopts the result it produces.
68
+ *
69
+ * The single place the callbacks are fired from, whether the change arrived as a delta or was
70
+ * found by comparing two results — so the two can never disagree about what a consumer is told.
71
+ * @param delta - The change to report.
72
+ * @param nextItems - The result the delta produces.
73
+ */
74
+ private emitDelta;
75
+ private finishCheck;
45
76
  private checkItems;
46
77
  private stopped;
47
78
  /**
@@ -74,7 +74,22 @@ export default class Collection<T extends BaseItem<I> = BaseItem, I = any, E ext
74
74
  private static fieldTracking;
75
75
  private static onCreationCallbacks;
76
76
  private static onDisposeCallbacks;
77
+ private static largeQueryWarningThreshold;
78
+ private static reportedLargeQueries;
77
79
  static getCollections(): Collection<any, any, any, any>[];
80
+ /**
81
+ * Reports live queries whose result is larger than `rows`, once each, with
82
+ * the stack that registered them.
83
+ *
84
+ * A reactive query is re-evaluated whenever the data under it changes, and
85
+ * one registered from a long-lived place — a navigation bar, a provider
86
+ * near the root — keeps that cost for the lifetime of the application. There
87
+ * is otherwise nothing to see: the query works, and its price is only
88
+ * visible as an application that has grown slow. Finding one such query in a
89
+ * real app took a purpose-built profiler and the better part of a day.
90
+ * @param rows - Result size to report above, or `null` to switch the check off.
91
+ */
92
+ static reportLargeQueries(rows: number | null): void;
78
93
  static onCreation(callback: (collection: Collection<any>) => void): void;
79
94
  static onDispose(callback: (collection: Collection<any>) => void): void;
80
95
  /**
@@ -92,11 +107,27 @@ export default class Collection<T extends BaseItem<I> = BaseItem, I = any, E ext
92
107
  * This improves performance by avoiding repetitive index recalculations and
93
108
  * provides atomicity for the batch of operations.
94
109
  * Supports both synchronous and asynchronous callbacks.
110
+ *
111
+ * **Without a `collections` argument this affects every collection in the
112
+ * process, not only the ones being written to.** Each of them defers every
113
+ * live query's requery until the batch ends. That is what makes a batch
114
+ * cheap for a handful of writes belonging to one event, and what makes it
115
+ * dangerous around a loop whose length is data-dependent: while it is open
116
+ * nothing anywhere updates, and everything deferred is flushed at once when
117
+ * it closes. One application wrapped a sync of roughly 1,100 records this
118
+ * way and its screens stopped resolving their data for the whole drain.
119
+ *
120
+ * Pass the collections being written to whenever that scope is known — it is
121
+ * both cheaper and safer. `Collection.batch([logs, versions], () => …)`
122
+ * defers those two and leaves everything else live.
123
+ * @param collections - The collections to batch. Omit to batch all of them.
95
124
  * @param callback - The batch operation to execute.
96
125
  * @returns A promise if the callback returns a promise, otherwise `void`.
97
126
  */
98
127
  static batch<ReturnType>(callback: () => Promise<ReturnType>): Promise<void>;
99
128
  static batch<ReturnType>(callback: () => ReturnType): void;
129
+ static batch<ReturnType>(collections: Collection<any, any, any, any>[], callback: () => Promise<ReturnType>): Promise<void>;
130
+ static batch<ReturnType>(collections: Collection<any, any, any, any>[], callback: () => ReturnType): void;
100
131
  readonly name: string;
101
132
  private backend;
102
133
  private options;
@@ -131,6 +162,15 @@ export default class Collection<T extends BaseItem<I> = BaseItem, I = any, E ext
131
162
  */
132
163
  constructor(options?: CollectionOptions<T, I, E, U>);
133
164
  constructor(name: string, dataAdapter: DataAdapter, options?: CollectionOptions<T, I, E, U>);
165
+ /**
166
+ * Reports a live query the first time its result is found to be larger than
167
+ * the configured threshold. Once per query, because it re-runs on every
168
+ * write and a warning per write would be its own performance problem.
169
+ * @param selector - The query's selector.
170
+ * @param options - The query's options.
171
+ * @param registrationStack - Where the query was registered, if captured.
172
+ */
173
+ private reportIfLargeQuery;
134
174
  isBatchOperationInProgress(): boolean;
135
175
  /**
136
176
  * Checks whether the collection is currently performing a pull operation
@@ -1,6 +1,7 @@
1
1
  import { BaseItem, FieldSpecifier, SortSpecifier, default as Collection } from './Collection';
2
2
  import { default as Modifier } from './types/Modifier';
3
3
  import { default as Selector } from './types/Selector';
4
+ import { QueryDelta } from './utils/queryDelta';
4
5
  export interface QueryOptions<T extends BaseItem> {
5
6
  /** Sort order (default: natural order) */
6
7
  sort?: SortSpecifier<T> | undefined;
@@ -11,7 +12,19 @@ export interface QueryOptions<T extends BaseItem> {
11
12
  /** Dictionary of fields to return or exclude. */
12
13
  fields?: FieldSpecifier<T> | undefined;
13
14
  }
14
- export type StateChangeCallback = (state: 'active' | 'complete' | 'error') => void;
15
+ /**
16
+ * Notified when a query's state changes.
17
+ *
18
+ * A `'complete'` notification may carry a delta describing how the result changed since the last
19
+ * one. An adapter that can produce one saves its listeners from rediscovering the change by
20
+ * comparing the whole result against the whole previous result; one that cannot simply omits it,
21
+ * and its listeners fall back to exactly that comparison.
22
+ *
23
+ * A delta is only ever passed when it is relative to what `getQueryResult` returned the last time
24
+ * it was asked. An adapter that layers anything on top of its stored result — an optimistic write
25
+ * still in flight, for instance — must omit the delta for as long as it does.
26
+ */
27
+ export type StateChangeCallback<T extends BaseItem = BaseItem> = (state: 'active' | 'complete' | 'error', delta?: QueryDelta<T>) => void;
15
28
  export interface CollectionBackend<T extends BaseItem<I>, I> {
16
29
  insert(item: T): Promise<T>;
17
30
  updateOne(selector: Selector<T>, modifier: Modifier<T>): Promise<T[]>;
@@ -31,7 +44,7 @@ export interface CollectionBackend<T extends BaseItem<I>, I> {
31
44
  getQueryError<O extends QueryOptions<T>>(selector: Selector<T>, options: O): Error | null;
32
45
  getQueryResult<O extends QueryOptions<T>>(selector: Selector<T>, options: O): T[];
33
46
  executeQuery<O extends QueryOptions<T>>(selector: Selector<T>, options: O): Promise<T[]>;
34
- onQueryStateChange<O extends QueryOptions<T>>(selector: Selector<T>, options: O, callback: StateChangeCallback): () => void;
47
+ onQueryStateChange<O extends QueryOptions<T>>(selector: Selector<T>, options: O, callback: StateChangeCallback<T>): () => void;
35
48
  dispose(): Promise<void>;
36
49
  isReady(): Promise<void>;
37
50
  }
@@ -20,12 +20,33 @@ export default class WorkerDataAdapter implements DataAdapter {
20
20
  private collectionReady;
21
21
  private batchExecutionHelpers;
22
22
  private queries;
23
+ private pendingRequests;
23
24
  private pendingWrites;
24
25
  private pendingWriteSeq;
26
+ private pendingWriteVersions;
27
+ private bumpPendingWriteVersion;
25
28
  constructor(worker: WorkerDataAdapterEndpoint, options: WorkerDataAdapterOptions);
29
+ private resolveWorkerReady;
30
+ private handleWorkerMessage;
31
+ private handleQueryUpdate;
26
32
  private exec;
33
+ /**
34
+ * Issues a call whose result nobody is waiting for, and makes sure a failure has somewhere to
35
+ * go. A bare rejection here would surface as an uncaught error — which is what a disposed
36
+ * collection produced every time a cursor was cleaned up after it.
37
+ * @param method - The method to call on the worker.
38
+ * @param collectionName - The collection it applies to.
39
+ * @param args - The remaining arguments.
40
+ * @param onError - Called when the call fails, in place of merely logging it.
41
+ */
42
+ private execInBackground;
43
+ private queryItemsById;
44
+ private flattenPendingWrites;
45
+ private static providesFullItems;
27
46
  private observableItems;
28
- private mergePendingWrites;
47
+ private observableItemsByIds;
48
+ private servedResult;
49
+ private computeServedResult;
29
50
  /**
30
51
  * Registers a write's effect locally and notifies every active query it
31
52
  * touches, then returns a function that drops it again once the write
@@ -36,7 +57,9 @@ export default class WorkerDataAdapter implements DataAdapter {
36
57
  * @returns A function that drops the pending write and re-notifies.
37
58
  */
38
59
  private applyPendingWrite;
39
- private notifyAffectedQueries;
60
+ private affectedQueries;
61
+ private servedResults;
62
+ private notifyWithDeltas;
40
63
  private matchObservableItems;
41
64
  private resolveUpdate;
42
65
  private resolveRemoval;
@@ -44,6 +44,8 @@ export default class WorkerDataAdapterHost<T extends BaseItem<I>, I = any> {
44
44
  private queryItems;
45
45
  private executeQuery;
46
46
  private ensureQuery;
47
+ private setQueryItems;
48
+ private queryItemIds;
47
49
  private emitQueryUpdate;
48
50
  private ensureStorageAdapter;
49
51
  private checkQueryUpdates;
package/dist/index.cjs.js CHANGED
@@ -1,20 +1,20 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_isEqual = require("./index2.cjs.js");
3
- const require_Cursor = require("./index5.cjs.js");
4
- const require_EventEmitter = require("./index6.cjs.js");
5
- const require_randomId = require("./index8.cjs.js");
6
- const require_get = require("./index10.cjs.js");
7
- const require_serializeValue = require("./index12.cjs.js");
8
- const require_modify = require("./index19.cjs.js");
9
- const require_DefaultDataAdapter = require("./index24.cjs.js");
10
- const require_index = require("./index25.cjs.js");
11
- const require_createStorageAdapter = require("./index26.cjs.js");
12
- const require_createReactivityAdapter = require("./index27.cjs.js");
13
- const require_reactiveOrAsync = require("./index28.cjs.js");
14
- const require_AsyncDataAdapter = require("./index29.cjs.js");
15
- const require_WorkerDataAdapter = require("./index32.cjs.js");
16
- const require_WorkerDataAdapterHost = require("./index34.cjs.js");
17
- const require_AutoFetchDataAdapter = require("./index35.cjs.js");
3
+ const require_Cursor = require("./index6.cjs.js");
4
+ const require_EventEmitter = require("./index7.cjs.js");
5
+ const require_randomId = require("./index9.cjs.js");
6
+ const require_get = require("./index11.cjs.js");
7
+ const require_serializeValue = require("./index13.cjs.js");
8
+ const require_modify = require("./index20.cjs.js");
9
+ const require_DefaultDataAdapter = require("./index27.cjs.js");
10
+ const require_index = require("./index28.cjs.js");
11
+ const require_createStorageAdapter = require("./index29.cjs.js");
12
+ const require_createReactivityAdapter = require("./index30.cjs.js");
13
+ const require_reactiveOrAsync = require("./index31.cjs.js");
14
+ const require_AsyncDataAdapter = require("./index32.cjs.js");
15
+ const require_WorkerDataAdapter = require("./index34.cjs.js");
16
+ const require_WorkerDataAdapterHost = require("./index36.cjs.js");
17
+ const require_AutoFetchDataAdapter = require("./index37.cjs.js");
18
18
  exports.AsyncDataAdapter = require_AsyncDataAdapter.default;
19
19
  exports.AutoFetchDataAdapter = require_AutoFetchDataAdapter.default;
20
20
  exports.Collection = require_index.default;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ 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, TransformAll, SortSpecifier, FieldSpecifier, AnyFindOptions, AsyncFindOptions, FindOptions, SyncFindOptions, CollectionOptions, QueryStateAccessor, } from './Collection';
6
6
  export type { default as DataAdapter } from './DataAdapter';
7
+ export type { CollectionBackend, QueryOptions, StateChangeCallback, } from './DataAdapter';
8
+ export type { QueryDelta } from './utils/queryDelta';
7
9
  export { default as Cursor } from './Collection/Cursor';
8
10
  export { default as Collection } from './Collection';
9
11
  export { default as createStorageAdapter } from './createStorageAdapter';