@signaldb/core 2.0.0-beta.13 → 2.0.0-beta.14
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.
- package/dist/.vite/manifest.json +72 -46
- package/dist/AsyncDataAdapter.d.ts +11 -2
- package/dist/Collection/Cursor.d.ts +11 -1
- package/dist/Collection/Observer.d.ts +31 -0
- package/dist/DataAdapter.d.ts +15 -2
- package/dist/WorkerDataAdapter.d.ts +25 -2
- package/dist/WorkerDataAdapterHost.d.ts +2 -0
- package/dist/index.cjs.js +15 -15
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +15 -15
- package/dist/index10.cjs.js +7 -19
- package/dist/index10.mjs +7 -19
- package/dist/index11.cjs.js +19 -41
- package/dist/index11.mjs +19 -41
- package/dist/index12.cjs.js +41 -18
- package/dist/index12.mjs +41 -18
- package/dist/index13.cjs.js +18 -41
- package/dist/index13.mjs +18 -41
- package/dist/index14.cjs.js +39 -80
- package/dist/index14.mjs +39 -80
- package/dist/index15.cjs.js +82 -11
- package/dist/index15.mjs +82 -11
- package/dist/index16.cjs.js +11 -132
- package/dist/index16.mjs +11 -132
- package/dist/index17.cjs.js +127 -38
- package/dist/index17.mjs +127 -38
- package/dist/index18.cjs.js +43 -11
- package/dist/index18.mjs +43 -11
- package/dist/index19.cjs.js +10 -18
- package/dist/index19.mjs +11 -19
- package/dist/index20.cjs.js +19 -33
- package/dist/index20.mjs +19 -33
- package/dist/index21.cjs.js +33 -31
- package/dist/index21.mjs +33 -31
- package/dist/index22.cjs.js +31 -21
- package/dist/index22.mjs +31 -21
- package/dist/index23.cjs.js +16 -14
- package/dist/index23.mjs +16 -14
- package/dist/index24.cjs.js +15 -338
- package/dist/index24.mjs +15 -338
- package/dist/index25.cjs.js +122 -554
- package/dist/index25.mjs +121 -554
- package/dist/index26.cjs.js +34 -7
- package/dist/index26.mjs +34 -7
- package/dist/index27.cjs.js +339 -7
- package/dist/index27.mjs +339 -7
- package/dist/index28.cjs.js +550 -83
- package/dist/index28.mjs +550 -82
- package/dist/index29.cjs.js +8 -422
- package/dist/index29.mjs +8 -422
- package/dist/index30.cjs.js +7 -68
- package/dist/index30.mjs +7 -68
- package/dist/index31.cjs.js +86 -28
- package/dist/index31.mjs +85 -28
- package/dist/index32.cjs.js +446 -278
- package/dist/index32.mjs +446 -278
- package/dist/index33.cjs.js +68 -17
- package/dist/index33.mjs +68 -17
- package/dist/index34.cjs.js +460 -304
- package/dist/index34.mjs +460 -304
- package/dist/index35.cjs.js +14 -532
- package/dist/index35.mjs +14 -532
- package/dist/index36.cjs.js +389 -0
- package/dist/index36.mjs +389 -0
- package/dist/index37.cjs.js +539 -0
- package/dist/index37.mjs +539 -0
- package/dist/index4.cjs.js +199 -140
- package/dist/index4.mjs +195 -140
- package/dist/index5.cjs.js +152 -253
- package/dist/index5.mjs +152 -253
- package/dist/index6.cjs.js +267 -89
- package/dist/index6.mjs +267 -89
- package/dist/index7.cjs.js +121 -29
- package/dist/index7.mjs +121 -29
- package/dist/index8.cjs.js +29 -8
- package/dist/index8.mjs +29 -8
- package/dist/index9.cjs.js +8 -7
- package/dist/index9.mjs +8 -7
- package/dist/utils/incrementalQueryUpdate.d.ts +60 -0
- package/dist/utils/projectItems.d.ts +12 -0
- package/dist/utils/queryDelta.d.ts +83 -0
- package/package.json +1 -1
package/dist/index25.cjs.js
CHANGED
|
@@ -1,558 +1,126 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
require("./index14.cjs.js");
|
|
6
|
-
const require_deepClone = require("./index17.cjs.js");
|
|
7
|
-
const require_modify = require("./index19.cjs.js");
|
|
8
|
-
const require_queryId = require("./index22.cjs.js");
|
|
9
|
-
const require_DefaultDataAdapter = require("./index24.cjs.js");
|
|
10
|
-
//#region src/Collection/index.ts
|
|
1
|
+
const require_match = require("./index19.cjs.js");
|
|
2
|
+
const require_projectItems = require("./index23.cjs.js");
|
|
3
|
+
const require_sortItems = require("./index24.cjs.js");
|
|
4
|
+
//#region src/utils/incrementalQueryUpdate.ts
|
|
11
5
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
6
|
+
* Recomputes a query's result from its previous result and the change that was just written,
|
|
7
|
+
* without going back to the store.
|
|
8
|
+
*
|
|
9
|
+
* A store re-executing the query instead reads every item it holds (or every item an index points
|
|
10
|
+
* at) and filters, sorts and projects the lot — for a write that touched one row. This does the
|
|
11
|
+
* same job in the size of the write, which is what a query's result costs to keep up to date when
|
|
12
|
+
* the change that affects it is already in hand.
|
|
13
|
+
*
|
|
14
|
+
* Returns `null` when the previous result is not enough to answer, and the caller has to re-execute
|
|
15
|
+
* the query after all:
|
|
16
|
+
* - `limit` or `skip`: the result is a window onto a larger set, and an item leaving the window has
|
|
17
|
+
* to be replaced by one the previous result never contained.
|
|
18
|
+
* - `fields` together with `sort`: the previous items are projected, so the field the sort is keyed
|
|
19
|
+
* on may no longer be there to sort by.
|
|
20
|
+
* - a `null` selector, which matches nothing and is not worth a special case.
|
|
21
|
+
* @template T - The type of the items.
|
|
22
|
+
* @param previous - The query's previous result.
|
|
23
|
+
* @param selector - The query's selector.
|
|
24
|
+
* @param options - The query's options.
|
|
25
|
+
* @param changes - The items the write created, updated or removed.
|
|
26
|
+
* @returns The new result, or `null` when the query has to be re-executed.
|
|
18
27
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
name;
|
|
76
|
-
backend;
|
|
77
|
-
options;
|
|
78
|
-
isPullingSignal;
|
|
79
|
-
isPushingSignal;
|
|
80
|
-
readySignal;
|
|
81
|
-
debugMode;
|
|
82
|
-
batchOperationInProgress = false;
|
|
83
|
-
isDisposed = false;
|
|
84
|
-
postBatchCallbacks = /* @__PURE__ */ new Set();
|
|
85
|
-
fieldTracking = false;
|
|
86
|
-
queryListenersMap = /* @__PURE__ */ new Map();
|
|
87
|
-
settledQueriesSet = /* @__PURE__ */ new Set();
|
|
88
|
-
constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
|
|
89
|
-
super();
|
|
90
|
-
const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name || `${this.constructor.name}-${require_randomId.default()}`;
|
|
91
|
-
const options = typeof nameOrOptions === "string" ? maybeOptions || {} : nameOrOptions || {};
|
|
92
|
-
const persistence = options.persistence;
|
|
93
|
-
const dataAdapter = maybeDataAdapter || new require_DefaultDataAdapter.default({ ...persistence ? { storage: () => persistence } : {} });
|
|
94
|
-
Collection.collections.push(this);
|
|
95
|
-
this.name = name;
|
|
96
|
-
this.options = { ...options };
|
|
97
|
-
this.fieldTracking = this.options.fieldTracking ?? Collection.fieldTracking;
|
|
98
|
-
this.debugMode = this.options.enableDebugMode ?? Collection.debugMode;
|
|
99
|
-
this.isPullingSignal = require_createSignal.default(this.options.reactivity, false);
|
|
100
|
-
this.isPushingSignal = require_createSignal.default(this.options.reactivity, false);
|
|
101
|
-
this.readySignal = require_createSignal.default(this.options.reactivity, false);
|
|
102
|
-
this.backend = dataAdapter.createCollectionBackend(this, this.options.indices ?? []);
|
|
103
|
-
this.backend.isReady().then(() => {
|
|
104
|
-
this.readySignal.set(true);
|
|
105
|
-
}).catch(() => {});
|
|
106
|
-
Collection.onCreationCallbacks.forEach((callback) => callback(this));
|
|
107
|
-
}
|
|
108
|
-
isBatchOperationInProgress() {
|
|
109
|
-
return Collection.batchOperationInProgress || this.batchOperationInProgress;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Checks whether the collection is currently performing a pull operation
|
|
113
|
-
* ⚡️ this function is reactive!
|
|
114
|
-
* (loading data from the persistence adapter).
|
|
115
|
-
* @returns A boolean indicating if the collection is in the process of pulling data.
|
|
116
|
-
*/
|
|
117
|
-
isPulling() {
|
|
118
|
-
return this.isPullingSignal.get() ?? false;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Checks whether the collection is currently performing a push operation
|
|
122
|
-
* ⚡️ this function is reactive!
|
|
123
|
-
* (saving data to the persistence adapter).
|
|
124
|
-
* @returns A boolean indicating if the collection is in the process of pushing data.
|
|
125
|
-
*/
|
|
126
|
-
isPushing() {
|
|
127
|
-
return this.isPushingSignal.get() ?? false;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Checks whether the collection is currently performing either a pull or push operation,
|
|
131
|
-
* ⚡️ this function is reactive!
|
|
132
|
-
* indicating that it is loading or saving data.
|
|
133
|
-
* @returns A boolean indicating if the collection is in the process of loading or saving data.
|
|
134
|
-
*/
|
|
135
|
-
isLoading() {
|
|
136
|
-
const isPulling = this.isPulling();
|
|
137
|
-
const isPushing = this.isPushing();
|
|
138
|
-
return isPulling || isPushing;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Retrieves the current debug mode status of the collection.
|
|
142
|
-
* @returns A boolean indicating whether debug mode is enabled for the collection.
|
|
143
|
-
*/
|
|
144
|
-
getDebugMode() {
|
|
145
|
-
return this.debugMode;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Enables or disables debug mode for the collection.
|
|
149
|
-
* When debug mode is enabled, additional debugging information and events are emitted.
|
|
150
|
-
* @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) debug mode.
|
|
151
|
-
*/
|
|
152
|
-
setDebugMode(enable) {
|
|
153
|
-
this.debugMode = enable;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Enables or disables field tracking for the collection.
|
|
157
|
-
* @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) field tracking.
|
|
158
|
-
*/
|
|
159
|
-
setFieldTracking(enable) {
|
|
160
|
-
this.fieldTracking = enable;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Resolves when the persistence adapter finished initializing
|
|
164
|
-
* and the collection is ready to be used.
|
|
165
|
-
* @returns A promise that resolves when the collection is ready.
|
|
166
|
-
* @example
|
|
167
|
-
* ```ts
|
|
168
|
-
* const collection = new Collection({
|
|
169
|
-
* persistence: // ...
|
|
170
|
-
* })
|
|
171
|
-
* await collection.isReady()
|
|
172
|
-
*
|
|
173
|
-
* collection.insert({ name: 'Item 1' })
|
|
174
|
-
*/
|
|
175
|
-
async ready() {
|
|
176
|
-
return this.backend.isReady();
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Checks if the collection is ready.
|
|
180
|
-
* ⚡️ this function is reactive!
|
|
181
|
-
* @returns A boolean indicating whether the collection is ready.
|
|
182
|
-
*/
|
|
183
|
-
isReady() {
|
|
184
|
-
return this.readySignal.get() ?? false;
|
|
185
|
-
}
|
|
186
|
-
profile(fn, measureFunction) {
|
|
187
|
-
if (!this.debugMode) return fn();
|
|
188
|
-
const startTime = performance.now();
|
|
189
|
-
const handleProfileEnd = (result) => {
|
|
190
|
-
measureFunction(performance.now() - startTime);
|
|
191
|
-
return result;
|
|
192
|
-
};
|
|
193
|
-
const maybePromise = fn();
|
|
194
|
-
return maybePromise instanceof Promise ? maybePromise.then(handleProfileEnd) : handleProfileEnd(maybePromise);
|
|
195
|
-
}
|
|
196
|
-
executeInDebugMode(fn) {
|
|
197
|
-
if (!this.debugMode) return;
|
|
198
|
-
fn((/* @__PURE__ */ new Error()).stack || "");
|
|
199
|
-
}
|
|
200
|
-
transform(item) {
|
|
201
|
-
if (!this.options.transform) return item;
|
|
202
|
-
return this.options.transform(item);
|
|
203
|
-
}
|
|
204
|
-
transformAll(items, fields) {
|
|
205
|
-
if (!this.options.transformAll) return items;
|
|
206
|
-
return this.options.transformAll(require_deepClone.default(items), fields);
|
|
207
|
-
}
|
|
208
|
-
getItem(selector, options) {
|
|
209
|
-
const itemsOrPromise = this.getItems(selector, {
|
|
210
|
-
...options,
|
|
211
|
-
limit: 1
|
|
212
|
-
});
|
|
213
|
-
if (itemsOrPromise instanceof Promise) return itemsOrPromise.then((items) => {
|
|
214
|
-
return items[0] || void 0;
|
|
215
|
-
});
|
|
216
|
-
return itemsOrPromise[0];
|
|
217
|
-
}
|
|
218
|
-
getItems(selector, options) {
|
|
219
|
-
this.emit("getItems", selector);
|
|
220
|
-
return this.profile(() => {
|
|
221
|
-
if (!options?.async) return this.backend.getQueryResult(selector, options);
|
|
222
|
-
this.isPullingSignal.set(true);
|
|
223
|
-
return this.backend.executeQuery(selector, options).finally(() => {
|
|
224
|
-
this.isPullingSignal.set(false);
|
|
225
|
-
});
|
|
226
|
-
}, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
|
|
227
|
-
}
|
|
228
|
-
async withPushState(asyncFunction) {
|
|
229
|
-
this.isPushingSignal.set(true);
|
|
230
|
-
try {
|
|
231
|
-
return await asyncFunction();
|
|
232
|
-
} finally {
|
|
233
|
-
this.isPushingSignal.set(false);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
queryListeners(query, listeners) {
|
|
237
|
-
const id = require_queryId.default(query.selector, query.options);
|
|
238
|
-
if (listeners != null) return this.queryListenersMap.set(id, listeners);
|
|
239
|
-
return this.queryListenersMap.get(id) ?? 0;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Disposes the collection, unregisters persistence adapters, clears memory, and
|
|
243
|
-
* cleans up all resources used by the collection.
|
|
244
|
-
* @returns A promise that resolves when the collection is disposed.
|
|
245
|
-
*/
|
|
246
|
-
async dispose() {
|
|
247
|
-
await this.backend.dispose();
|
|
248
|
-
this.isDisposed = true;
|
|
249
|
-
this.removeAllListeners();
|
|
250
|
-
Collection.collections = Collection.collections.filter((collection) => collection !== this);
|
|
251
|
-
Collection.onDisposeCallbacks.forEach((callback) => callback(this));
|
|
252
|
-
}
|
|
253
|
-
find(selector = {}, options) {
|
|
254
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
255
|
-
if (selector !== void 0 && (!selector || typeof selector !== "object")) throw new Error("Invalid selector");
|
|
256
|
-
const getTransformedItems = () => {
|
|
257
|
-
const itemsOrPromise = this.getItems(selector, options || {});
|
|
258
|
-
if (itemsOrPromise instanceof Promise) return itemsOrPromise.then((items) => {
|
|
259
|
-
return this.transformAll(items, options?.fields);
|
|
260
|
-
});
|
|
261
|
-
const items = itemsOrPromise;
|
|
262
|
-
return this.transformAll(items, options?.fields);
|
|
263
|
-
};
|
|
264
|
-
const cursor = new require_Cursor.default(getTransformedItems, {
|
|
265
|
-
reactive: this.options.reactivity,
|
|
266
|
-
fieldTracking: this.fieldTracking,
|
|
267
|
-
...options,
|
|
268
|
-
transform: this.transform.bind(this),
|
|
269
|
-
queryState: {
|
|
270
|
-
hasSettled: () => {
|
|
271
|
-
if (this.settledQueriesSet.has(require_queryId.default(selector, options))) return true;
|
|
272
|
-
const state = this.backend.getQueryState(selector, options || {});
|
|
273
|
-
return state === "complete" || state === "error";
|
|
274
|
-
},
|
|
275
|
-
onSettled: (callback) => this.backend.onQueryStateChange(selector, options || {}, (state) => {
|
|
276
|
-
if (state !== "complete" && state !== "error") return;
|
|
277
|
-
this.settledQueriesSet.add(require_queryId.default(selector, options));
|
|
278
|
-
callback();
|
|
279
|
-
})
|
|
280
|
-
},
|
|
281
|
-
bindEvents: (requery) => {
|
|
282
|
-
const handleRequery = () => {
|
|
283
|
-
if (this.batchOperationInProgress) {
|
|
284
|
-
this.postBatchCallbacks.add(requery);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
requery();
|
|
288
|
-
};
|
|
289
|
-
const listeners = this.queryListeners({
|
|
290
|
-
selector,
|
|
291
|
-
options
|
|
292
|
-
});
|
|
293
|
-
const didRegister = listeners === 0;
|
|
294
|
-
if (didRegister) this.backend.registerQuery(selector, options || {});
|
|
295
|
-
this.queryListeners({
|
|
296
|
-
selector,
|
|
297
|
-
options
|
|
298
|
-
}, listeners + 1);
|
|
299
|
-
const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options || {}, (state) => {
|
|
300
|
-
if (state === "error") {
|
|
301
|
-
const queryError = this.backend.getQueryError(selector, options || {}) || /* @__PURE__ */ new Error(`Query on "${this.name}" failed`);
|
|
302
|
-
this.emit("query.error", queryError, selector, options);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (state !== "complete") return;
|
|
306
|
-
handleRequery();
|
|
307
|
-
});
|
|
308
|
-
this.emit("observer.created", selector, options);
|
|
309
|
-
return () => {
|
|
310
|
-
queueMicrotask(() => {
|
|
311
|
-
const newListeners = Math.max(0, this.queryListeners({
|
|
312
|
-
selector,
|
|
313
|
-
options
|
|
314
|
-
}) - 1);
|
|
315
|
-
if (newListeners === 0 && didRegister) {
|
|
316
|
-
this.backend.unregisterQuery(selector, options || {});
|
|
317
|
-
this.settledQueriesSet.delete(require_queryId.default(selector, options));
|
|
318
|
-
}
|
|
319
|
-
this.queryListeners({
|
|
320
|
-
selector,
|
|
321
|
-
options
|
|
322
|
-
}, newListeners);
|
|
323
|
-
queryStateChangeCleanup();
|
|
324
|
-
this.emit("observer.disposed", selector, options);
|
|
325
|
-
});
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
this.emit("find", selector, options, cursor);
|
|
330
|
-
this.executeInDebugMode((callstack) => this.emit("_debug.find", callstack, selector, options, cursor));
|
|
331
|
-
return cursor;
|
|
332
|
-
}
|
|
333
|
-
findOne(selector, options) {
|
|
334
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
335
|
-
const cursor = this.find(selector, {
|
|
336
|
-
limit: 1,
|
|
337
|
-
...options
|
|
338
|
-
});
|
|
339
|
-
const handleItems = (items) => {
|
|
340
|
-
const returnValue = items[0] || void 0;
|
|
341
|
-
this.emit("findOne", selector, options, returnValue);
|
|
342
|
-
this.executeInDebugMode((callstack) => this.emit("_debug.findOne", callstack, selector, options, returnValue));
|
|
343
|
-
return returnValue;
|
|
344
|
-
};
|
|
345
|
-
const maybePromise = cursor.fetch();
|
|
346
|
-
return maybePromise instanceof Promise ? maybePromise.then(handleItems) : handleItems(maybePromise);
|
|
347
|
-
}
|
|
348
|
-
batch(callback) {
|
|
349
|
-
if (this.batchOperationInProgress) return callback();
|
|
350
|
-
this.batchOperationInProgress = true;
|
|
351
|
-
const afterBatch = () => {
|
|
352
|
-
this.batchOperationInProgress = false;
|
|
353
|
-
this.postBatchCallbacks.forEach((callback_) => callback_());
|
|
354
|
-
this.postBatchCallbacks.clear();
|
|
355
|
-
};
|
|
356
|
-
let maybePromise;
|
|
357
|
-
try {
|
|
358
|
-
maybePromise = callback();
|
|
359
|
-
} catch (error) {
|
|
360
|
-
afterBatch();
|
|
361
|
-
throw error;
|
|
362
|
-
}
|
|
363
|
-
if (maybePromise && typeof maybePromise.then === "function") return maybePromise.then(() => afterBatch(), (error) => {
|
|
364
|
-
afterBatch();
|
|
365
|
-
throw error;
|
|
366
|
-
});
|
|
367
|
-
else afterBatch();
|
|
368
|
-
}
|
|
369
|
-
onPostBatch(callback) {
|
|
370
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
371
|
-
if (this.batchOperationInProgress) {
|
|
372
|
-
this.postBatchCallbacks.add(callback);
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
return callback();
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Inserts a single item into the collection. Generates a unique ID if not provided.
|
|
379
|
-
* @param item - The item to insert.
|
|
380
|
-
* @returns The ID of the inserted item.
|
|
381
|
-
* @throws {Error} If the collection is disposed or the item has an invalid ID.
|
|
382
|
-
*/
|
|
383
|
-
async insert(item) {
|
|
384
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
385
|
-
if (!item) throw new Error("Invalid item");
|
|
386
|
-
const itemWithId = {
|
|
387
|
-
id: (this.options.primaryKeyGenerator ?? require_randomId.default)(item),
|
|
388
|
-
...item
|
|
389
|
-
};
|
|
390
|
-
this.emit("validate", itemWithId);
|
|
391
|
-
const newItem = await this.withPushState(() => this.backend.insert(itemWithId));
|
|
392
|
-
this.emit("added", newItem);
|
|
393
|
-
this.emit("insert", newItem);
|
|
394
|
-
this.executeInDebugMode((callstack) => this.emit("_debug.insert", callstack, newItem));
|
|
395
|
-
return newItem.id;
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Inserts multiple items into the collection. Generates unique IDs for items if not provided.
|
|
399
|
-
* @param items - The items to insert.
|
|
400
|
-
* @returns An array of IDs of the inserted items.
|
|
401
|
-
* @throws {Error} If the collection is disposed or the items are invalid.
|
|
402
|
-
*/
|
|
403
|
-
async insertMany(items) {
|
|
404
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
405
|
-
if (!items) throw new Error("Invalid items");
|
|
406
|
-
if (items.length === 0) return [];
|
|
407
|
-
const ids = [];
|
|
408
|
-
await this.batch(async () => {
|
|
409
|
-
await Promise.all(items.map(async (item) => {
|
|
410
|
-
ids.push(await this.insert(item));
|
|
411
|
-
}));
|
|
412
|
-
});
|
|
413
|
-
return ids;
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Updates a single item in the collection that matches the given selector.
|
|
417
|
-
* @param selector - The criteria to select the item to update.
|
|
418
|
-
* @param modifier - The modifications to apply to the item.
|
|
419
|
-
* @param [options] - Optional settings for the update operation.
|
|
420
|
-
* @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
|
|
421
|
-
* @returns The number of items updated (0 or 1).
|
|
422
|
-
* @throws {Error} If the collection is disposed or invalid arguments are provided.
|
|
423
|
-
*/
|
|
424
|
-
async updateOne(selector, modifier, options) {
|
|
425
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
426
|
-
if (!selector) throw new Error("Invalid selector");
|
|
427
|
-
if (!modifier) throw new Error("Invalid modifier");
|
|
428
|
-
const { $setOnInsert, ...restModifier } = modifier;
|
|
429
|
-
const item = await this.getItem(selector, { async: true });
|
|
430
|
-
if (item == null) {
|
|
431
|
-
if (options?.upsert) {
|
|
432
|
-
const newItem = require_modify.default({}, {
|
|
433
|
-
...restModifier,
|
|
434
|
-
$set: {
|
|
435
|
-
...$setOnInsert,
|
|
436
|
-
...restModifier.$set
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
await this.insert(newItem);
|
|
440
|
-
return 1;
|
|
441
|
-
}
|
|
442
|
-
return 0;
|
|
443
|
-
}
|
|
444
|
-
const modifiedItem = require_modify.default(require_deepClone.default(item), restModifier);
|
|
445
|
-
this.emit("validate", modifiedItem);
|
|
446
|
-
const changes = await this.withPushState(() => this.backend.updateOne(selector, modifier));
|
|
447
|
-
this.emit("changed", modifiedItem, restModifier);
|
|
448
|
-
this.emit("updateOne", selector, modifier);
|
|
449
|
-
this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
|
|
450
|
-
return changes.length;
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Updates multiple items in the collection that match the given selector.
|
|
454
|
-
* @param selector - The criteria to select the items to update.
|
|
455
|
-
* @param modifier - The modifications to apply to the items.
|
|
456
|
-
* @param [options] - Optional settings for the update operation.
|
|
457
|
-
* @param [options.upsert] - If `true`, creates new items if no items match the selector.
|
|
458
|
-
* @returns The number of items updated.
|
|
459
|
-
* @throws {Error} If the collection is disposed or invalid arguments are provided.
|
|
460
|
-
*/
|
|
461
|
-
async updateMany(selector, modifier, options) {
|
|
462
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
463
|
-
if (!selector) throw new Error("Invalid selector");
|
|
464
|
-
if (!modifier) throw new Error("Invalid modifier");
|
|
465
|
-
const { $setOnInsert, ...restModifier } = modifier;
|
|
466
|
-
const items = await this.getItems(selector, { async: true });
|
|
467
|
-
if (items.length === 0) {
|
|
468
|
-
if (options?.upsert) {
|
|
469
|
-
const newItem = require_modify.default({}, {
|
|
470
|
-
...restModifier,
|
|
471
|
-
$set: {
|
|
472
|
-
...$setOnInsert,
|
|
473
|
-
...restModifier.$set
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
await this.insert(newItem);
|
|
477
|
-
return 1;
|
|
478
|
-
}
|
|
479
|
-
return 0;
|
|
480
|
-
}
|
|
481
|
-
items.forEach((item) => {
|
|
482
|
-
const modifiedItem = require_modify.default(require_deepClone.default(item), restModifier);
|
|
483
|
-
this.emit("validate", modifiedItem);
|
|
484
|
-
});
|
|
485
|
-
const changes = await this.withPushState(() => this.backend.updateMany(selector, modifier));
|
|
486
|
-
changes.forEach((item) => {
|
|
487
|
-
this.emit("changed", item, restModifier);
|
|
488
|
-
});
|
|
489
|
-
this.emit("updateMany", selector, modifier);
|
|
490
|
-
this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
|
|
491
|
-
return changes.length;
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Replaces a single item in the collection that matches the given selector.
|
|
495
|
-
* @param selector - The criteria to select the item to replace.
|
|
496
|
-
* @param replacement - The item to replace the selected item with.
|
|
497
|
-
* @param [options] - Optional settings for the replace operation.
|
|
498
|
-
* @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
|
|
499
|
-
* @returns The number of items replaced (0 or 1).
|
|
500
|
-
* @throws {Error} If the collection is disposed or invalid arguments are provided.
|
|
501
|
-
*/
|
|
502
|
-
async replaceOne(selector, replacement, options) {
|
|
503
|
-
if (this.isDisposed) throw new Error("Collection is disposed");
|
|
504
|
-
if (!selector) throw new Error("Invalid selector");
|
|
505
|
-
const item = await this.getItem(selector, { async: true });
|
|
506
|
-
if (item == null) {
|
|
507
|
-
if (options?.upsert) {
|
|
508
|
-
await this.insert(replacement);
|
|
509
|
-
return 1;
|
|
510
|
-
}
|
|
511
|
-
return 0;
|
|
28
|
+
function incrementalQueryUpdate(previous, selector, options, changes) {
|
|
29
|
+
if (selector == null) return null;
|
|
30
|
+
const { sort, skip, limit, fields } = options || {};
|
|
31
|
+
if (skip != null) return null;
|
|
32
|
+
if (fields != null && sort != null) return null;
|
|
33
|
+
if (limit != null && !windowStaysClosed(previous, selector, options, changes)) return null;
|
|
34
|
+
return mergeChangesetIntoResult(previous, selector, options, changes);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether two items are in the given order under the given sort, deciding ties against the caller.
|
|
38
|
+
*
|
|
39
|
+
* Uses the sort itself rather than a comparator of its own: a rule about which side of a window an
|
|
40
|
+
* item falls on is only as good as its agreement with the ordering that drew the window. A tie
|
|
41
|
+
* comes back as `false`, because a tie is exactly the case where an item could belong on either
|
|
42
|
+
* side and the answer has to be taken from the store.
|
|
43
|
+
* @template T - The type of the items.
|
|
44
|
+
* @param item - The item whose position is in question.
|
|
45
|
+
* @param edge - The item at the edge of the window.
|
|
46
|
+
* @param sort - The query's sort.
|
|
47
|
+
* @returns `true` when `item` sorts strictly before `edge`.
|
|
48
|
+
*/
|
|
49
|
+
function sortsBefore(item, edge, sort) {
|
|
50
|
+
return require_sortItems.default([edge, item], sort)[0] === item;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether a change to a windowed query can be answered from the window alone.
|
|
54
|
+
*
|
|
55
|
+
* A window holds the first `limit` items in sort order, and nothing about what lies beyond it. An
|
|
56
|
+
* item leaving the window therefore has to be replaced by one the window has never seen, and that
|
|
57
|
+
* answer can only come from the store. An item arriving is a different matter: it takes its place
|
|
58
|
+
* and pushes the last one out, and where that one goes is not the window's problem.
|
|
59
|
+
*
|
|
60
|
+
* The one case where none of this applies is a window that was never full, because then the query
|
|
61
|
+
* already returns everything it matches and there is no "beyond".
|
|
62
|
+
* @template T - The type of the items.
|
|
63
|
+
* @param previous - The query's previous result.
|
|
64
|
+
* @param selector - The query's selector.
|
|
65
|
+
* @param options - The query's options.
|
|
66
|
+
* @param changes - The items the write created, updated or removed.
|
|
67
|
+
* @returns `true` when the new window follows from the old one and the change.
|
|
68
|
+
*/
|
|
69
|
+
function windowStaysClosed(previous, selector, options, changes) {
|
|
70
|
+
const { sort, limit, fields } = options || {};
|
|
71
|
+
if (limit == null || previous.length < limit) return true;
|
|
72
|
+
if (sort == null || fields != null) return false;
|
|
73
|
+
const edge = previous.at(-1);
|
|
74
|
+
const runnerUp = previous.at(-2);
|
|
75
|
+
if (runnerUp != null && !sortsBefore(runnerUp, edge, sort)) return false;
|
|
76
|
+
const inWindow = new Set(previous.map((item) => item.id));
|
|
77
|
+
if (changes.deletes.some((id) => inWindow.has(id))) return false;
|
|
78
|
+
return changes.upserts.every((item) => {
|
|
79
|
+
if (item === edge) return true;
|
|
80
|
+
const before = sortsBefore(item, edge, sort);
|
|
81
|
+
if (!inWindow.has(item.id)) {
|
|
82
|
+
if (!require_match.default(item, selector)) return true;
|
|
83
|
+
return before || sortsBefore(edge, item, sort);
|
|
512
84
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
this.emit("removeMany", selector);
|
|
553
|
-
this.executeInDebugMode((callstack) => this.emit("_debug.removeMany", callstack, selector));
|
|
554
|
-
return removedItems.length;
|
|
555
|
-
}
|
|
556
|
-
};
|
|
85
|
+
if (!require_match.default(item, selector)) return false;
|
|
86
|
+
if (before) return true;
|
|
87
|
+
return item.id === edge.id && !sortsBefore(edge, item, sort);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Folds a change into a query's result, whatever the query's options.
|
|
92
|
+
*
|
|
93
|
+
* The unguarded version of `incrementalQueryUpdate`, for the places where the alternative is not a
|
|
94
|
+
* more accurate answer but a wrong one — layering a write that has not been confirmed yet on top of
|
|
95
|
+
* the last confirmed result, say. For a query returning everything it matches, this is exact. For a
|
|
96
|
+
* window onto a larger set it is the closest the window itself can get: an item that no longer
|
|
97
|
+
* belongs is dropped, one that does is placed, and the window is trimmed back to its length — but
|
|
98
|
+
* an item pulled in from beyond the window is not something the window knows about.
|
|
99
|
+
*
|
|
100
|
+
* What it never does is re-examine the items already in the result. They matched when the store
|
|
101
|
+
* produced them, they still match, and asking again is both wasteful and — for a projected result,
|
|
102
|
+
* whose items no longer carry the fields the selector names — wrong.
|
|
103
|
+
* @template T - The type of the items.
|
|
104
|
+
* @param previous - The query's previous result.
|
|
105
|
+
* @param selector - The query's selector.
|
|
106
|
+
* @param options - The query's options.
|
|
107
|
+
* @param changes - The items the write created, updated or removed.
|
|
108
|
+
* @returns The resulting items.
|
|
109
|
+
*/
|
|
110
|
+
function mergeChangesetIntoResult(previous, selector, options, changes) {
|
|
111
|
+
if (selector == null) return [];
|
|
112
|
+
const { sort, limit, fields } = options || {};
|
|
113
|
+
const byId = /* @__PURE__ */ new Map();
|
|
114
|
+
previous.forEach((item) => byId.set(item.id, item));
|
|
115
|
+
changes.deletes.forEach((id) => byId.delete(id));
|
|
116
|
+
changes.upserts.forEach((item) => {
|
|
117
|
+
if (require_match.default(item, selector)) byId.set(item.id, require_projectItems.default([item], fields)[0]);
|
|
118
|
+
else byId.delete(item.id);
|
|
119
|
+
});
|
|
120
|
+
const items = [...byId.values()];
|
|
121
|
+
const sorted = sort ? require_sortItems.default(items, sort) : items;
|
|
122
|
+
return limit == null ? sorted : sorted.slice(0, limit);
|
|
123
|
+
}
|
|
557
124
|
//#endregion
|
|
558
|
-
exports.default =
|
|
125
|
+
exports.default = incrementalQueryUpdate;
|
|
126
|
+
exports.mergeChangesetIntoResult = mergeChangesetIntoResult;
|