@signaldb/core 2.0.0-beta.21 → 2.0.0-beta.22
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/README.md +3 -3
- package/dist/.vite/manifest.json +61 -59
- package/dist/AsyncDataAdapter.d.ts +15 -2
- package/dist/AutoFetchDataAdapter.d.ts +13 -2
- package/dist/Collection/Observer.d.ts +1 -1
- package/dist/Collection/index.d.ts +11 -12
- package/dist/DataAdapter.d.ts +19 -3
- package/dist/WorkerDataAdapterHost.d.ts +19 -6
- package/dist/index.cjs.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index12.cjs.js +1 -1
- package/dist/index12.mjs +1 -1
- package/dist/index15.cjs.js +6 -2
- package/dist/index15.mjs +6 -2
- package/dist/index17.cjs.js +4 -2
- package/dist/index17.mjs +4 -3
- package/dist/index18.cjs.js +1 -0
- package/dist/index18.mjs +1 -1
- package/dist/index28.cjs.js +15 -5
- package/dist/index28.mjs +15 -5
- package/dist/index29.cjs.js +29 -16
- package/dist/index29.mjs +29 -16
- package/dist/index33.cjs.js +17 -49
- package/dist/index33.mjs +17 -49
- package/dist/index34.cjs.js +45 -445
- package/dist/index34.mjs +45 -445
- package/dist/index35.cjs.js +132 -65
- package/dist/index35.mjs +132 -65
- package/dist/index36.cjs.js +385 -531
- package/dist/index36.mjs +386 -533
- package/dist/index37.cjs.js +68 -17
- package/dist/index37.mjs +68 -17
- package/dist/index38.cjs.js +536 -319
- package/dist/index38.mjs +538 -321
- package/dist/index39.cjs.js +301 -466
- package/dist/index39.mjs +301 -466
- package/dist/index40.cjs.js +484 -0
- package/dist/index40.mjs +484 -0
- package/dist/index5.cjs.js +3 -3
- package/dist/index5.mjs +3 -3
- package/dist/index6.cjs.js +1 -0
- package/dist/index6.mjs +1 -1
- package/dist/types/StorageAdapter.d.ts +80 -0
- package/dist/utils/executeStorageQuery.d.ts +28 -0
- package/package.json +1 -1
package/dist/index38.cjs.js
CHANGED
|
@@ -1,363 +1,580 @@
|
|
|
1
|
-
const require_isEqual = require("./index2.cjs.js");
|
|
2
1
|
const require_queryDelta = require("./index4.cjs.js");
|
|
3
|
-
const
|
|
2
|
+
const require_randomId = require("./index9.cjs.js");
|
|
4
3
|
const require_deepClone = require("./index18.cjs.js");
|
|
5
4
|
const require_match = require("./index19.cjs.js");
|
|
6
5
|
const require_modify = require("./index20.cjs.js");
|
|
7
|
-
const require_projectItems = require("./index23.cjs.js");
|
|
8
|
-
const require_sortItems = require("./index24.cjs.js");
|
|
9
6
|
const require_incrementalQueryUpdate = require("./index25.cjs.js");
|
|
10
7
|
const require_queryId = require("./index26.cjs.js");
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const require_compact = require("./index37.cjs.js");
|
|
14
|
-
//#region src/WorkerDataAdapterHost.ts
|
|
8
|
+
const require_batchOnNextTick = require("./index37.cjs.js");
|
|
9
|
+
//#region src/WorkerDataAdapter.ts
|
|
15
10
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* The
|
|
19
|
-
* its new state, while an item that is gone — removed, or given a new id — is described by the id
|
|
20
|
-
* it used to have and nothing else. Mixing the states from before and after a write into one list
|
|
21
|
-
* loses exactly that distinction.
|
|
22
|
-
* @template T - The type of the items.
|
|
23
|
-
* @param previousItems - The items as they were before the write.
|
|
24
|
-
* @param modifiedItems - The items as they are after it.
|
|
25
|
-
* @returns The changeset describing the write.
|
|
11
|
+
* Extracts the ids a selector names outright.
|
|
12
|
+
* @param selector - The selector to inspect.
|
|
13
|
+
* @returns The named ids, or `null` when the selector asks more than ids can answer.
|
|
26
14
|
*/
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
function selectorIds(selector) {
|
|
16
|
+
if (selector == null || typeof selector !== "object") return null;
|
|
17
|
+
const keys = Object.keys(selector);
|
|
18
|
+
if (keys.length !== 1 || keys[0] !== "id") return null;
|
|
19
|
+
const value = selector.id;
|
|
20
|
+
if (value == null) return null;
|
|
21
|
+
if (typeof value !== "object") return [value];
|
|
22
|
+
const valueKeys = Object.keys(value);
|
|
23
|
+
if (valueKeys.length !== 1 || valueKeys[0] !== "$in") return null;
|
|
24
|
+
const inValues = value.$in;
|
|
25
|
+
return Array.isArray(inValues) ? inValues : null;
|
|
33
26
|
}
|
|
34
|
-
var
|
|
35
|
-
|
|
27
|
+
var WorkerDataAdapter = class WorkerDataAdapter {
|
|
28
|
+
worker;
|
|
36
29
|
options;
|
|
37
30
|
id;
|
|
31
|
+
isDisposed = false;
|
|
32
|
+
workerReady;
|
|
38
33
|
log = () => {};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
34
|
+
collectionReady = /* @__PURE__ */ new Map();
|
|
35
|
+
batchExecutionHelpers = /* @__PURE__ */ new Map();
|
|
36
|
+
queries = {};
|
|
37
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
38
|
+
pendingWrites = /* @__PURE__ */ new Map();
|
|
39
|
+
pendingWriteSeq = 0;
|
|
40
|
+
pendingWriteVersions = /* @__PURE__ */ new Map();
|
|
41
|
+
bumpPendingWriteVersion(collectionName) {
|
|
42
|
+
const current = this.pendingWriteVersions.get(collectionName) ?? 0;
|
|
43
|
+
this.pendingWriteVersions.set(collectionName, current + 1);
|
|
44
|
+
}
|
|
45
|
+
constructor(worker, options) {
|
|
46
|
+
this.worker = worker;
|
|
48
47
|
this.options = options;
|
|
49
48
|
this.id = this.options.id || "default-worker-data-adapter";
|
|
50
|
-
if (this.options.onError) this.onError = this.options.onError;
|
|
51
49
|
if (this.options.log) this.log = this.options.log;
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.respond("ready", null, null, "ready");
|
|
61
|
-
}
|
|
62
|
-
respond(id, data, error = null, type = "response") {
|
|
63
|
-
this.workerContext.postMessage({
|
|
64
|
-
id,
|
|
65
|
-
workerId: this.id,
|
|
66
|
-
type,
|
|
67
|
-
data,
|
|
68
|
-
error
|
|
50
|
+
this.workerReady = new Promise((resolve, reject) => {
|
|
51
|
+
const timeoutId = setTimeout(() => {
|
|
52
|
+
reject(/* @__PURE__ */ new Error("WorkerDataAdapter initialization timed out"));
|
|
53
|
+
}, 5e3);
|
|
54
|
+
this.resolveWorkerReady = () => {
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
resolve();
|
|
57
|
+
};
|
|
69
58
|
});
|
|
59
|
+
this.worker.addEventListener("message", this.handleWorkerMessage);
|
|
70
60
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
75
|
-
|
|
61
|
+
resolveWorkerReady = () => {};
|
|
62
|
+
handleWorkerMessage = (event) => {
|
|
63
|
+
const message = event.data;
|
|
64
|
+
if (message == null) return;
|
|
65
|
+
if (message.workerId !== this.id) return;
|
|
66
|
+
if (message.type === "ready") {
|
|
67
|
+
this.resolveWorkerReady();
|
|
76
68
|
return;
|
|
77
69
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
if (message.type === "response") {
|
|
71
|
+
if (message.id == null) return;
|
|
72
|
+
const pending = this.pendingRequests.get(message.id);
|
|
73
|
+
if (!pending) return;
|
|
74
|
+
this.pendingRequests.delete(message.id);
|
|
75
|
+
this.log("response", message.data ?? message.error);
|
|
76
|
+
if (message.error) pending.reject(message.error);
|
|
77
|
+
else pending.resolve(message.data);
|
|
78
|
+
return;
|
|
85
79
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
if (message.type === "queryUpdate") this.handleQueryUpdate(message.data, message.error ?? null);
|
|
81
|
+
};
|
|
82
|
+
handleQueryUpdate(data, error) {
|
|
83
|
+
if (data == null) return;
|
|
84
|
+
const { collectionName, qid, selector, options, state, items, delta } = data;
|
|
85
|
+
if (collectionName == null) return;
|
|
86
|
+
const collectionQueries = this.queries[collectionName];
|
|
87
|
+
if (!collectionQueries) return;
|
|
88
|
+
const id = qid ?? (selector === void 0 ? void 0 : require_queryId.default(selector, options));
|
|
89
|
+
if (id == null) return;
|
|
90
|
+
const query = collectionQueries.get(id);
|
|
91
|
+
if (!query) return;
|
|
92
|
+
this.log("queryUpdate", query.selector, query.options, state, data ?? error);
|
|
93
|
+
let nextItems = items;
|
|
94
|
+
let deltaToPublish;
|
|
95
|
+
if (delta != null) {
|
|
96
|
+
const canApply = require_queryDelta.canApplyQueryDelta(query.items, delta);
|
|
97
|
+
if (!canApply || require_queryDelta.isEmptyQueryDelta(delta)) {
|
|
98
|
+
if (state === query.state) return;
|
|
99
|
+
this.updateQuery(collectionName, {
|
|
100
|
+
selector: query.selector,
|
|
101
|
+
options: query.options
|
|
102
|
+
}, {
|
|
103
|
+
state,
|
|
104
|
+
error
|
|
105
|
+
});
|
|
106
|
+
const settled = collectionQueries.get(id);
|
|
107
|
+
if (!settled) return;
|
|
108
|
+
settled.stateChangeCallbacks.forEach((callback) => require_queryDelta.callWithDelta(callback, state, canApply ? delta : void 0));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const servedBefore = this.flattenPendingWrites(collectionName) == null ? null : this.servedResult(collectionName, query);
|
|
112
|
+
nextItems = require_queryDelta.applyQueryDelta(query.items, delta);
|
|
113
|
+
if (servedBefore == null) deltaToPublish = delta;
|
|
114
|
+
else {
|
|
115
|
+
this.updateQuery(collectionName, {
|
|
116
|
+
selector: query.selector,
|
|
117
|
+
options: query.options
|
|
118
|
+
}, {
|
|
119
|
+
state,
|
|
120
|
+
error,
|
|
121
|
+
items: nextItems
|
|
122
|
+
});
|
|
123
|
+
const stored = collectionQueries.get(id);
|
|
124
|
+
if (!stored) return;
|
|
125
|
+
const servedDelta = require_queryDelta.diffQueryResults(servedBefore, this.servedResult(collectionName, stored));
|
|
126
|
+
if (require_queryDelta.isEmptyQueryDelta(servedDelta) && state === query.state) return;
|
|
127
|
+
stored.stateChangeCallbacks.forEach((callback) => require_queryDelta.callWithDelta(callback, state, servedDelta));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
97
130
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
131
|
+
this.updateQuery(collectionName, {
|
|
132
|
+
selector: query.selector,
|
|
133
|
+
options: query.options
|
|
134
|
+
}, {
|
|
135
|
+
state,
|
|
136
|
+
error,
|
|
137
|
+
items: nextItems
|
|
138
|
+
});
|
|
139
|
+
const updated = collectionQueries.get(id);
|
|
140
|
+
if (!updated) return;
|
|
141
|
+
updated.stateChangeCallbacks.forEach((callback) => require_queryDelta.callWithDelta(callback, state, deltaToPublish));
|
|
104
142
|
}
|
|
105
|
-
async
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (Object.keys(indexInfo.optimizedSelector).length <= 0) return true;
|
|
112
|
-
return require_match.default(item, indexInfo.optimizedSelector);
|
|
113
|
-
};
|
|
114
|
-
if (indexInfo.matched) {
|
|
115
|
-
const items = await storageAdapter.readIds(indexInfo.ids);
|
|
116
|
-
if (require_isEqual.default(indexInfo.optimizedSelector, {})) return items;
|
|
117
|
-
return items.filter(matchItems);
|
|
118
|
-
} else {
|
|
119
|
-
const allItems = await storageAdapter.readAll();
|
|
120
|
-
if (require_isEqual.default(selector, {})) return allItems;
|
|
121
|
-
return allItems.filter(matchItems);
|
|
143
|
+
async exec(method, collectionName, ...args) {
|
|
144
|
+
await this.workerReady;
|
|
145
|
+
if (method !== "isReady") {
|
|
146
|
+
const collectionReady = this.collectionReady.get(collectionName);
|
|
147
|
+
if (!collectionReady) throw new Error(`Collection "${collectionName}" is not registered in WorkerDataAdapter`);
|
|
148
|
+
await collectionReady;
|
|
122
149
|
}
|
|
150
|
+
if (this.isDisposed) throw new Error("WorkerDataAdapter is disposed");
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const messageId = require_randomId.default();
|
|
153
|
+
this.pendingRequests.set(messageId, {
|
|
154
|
+
resolve,
|
|
155
|
+
reject
|
|
156
|
+
});
|
|
157
|
+
this.worker.postMessage({
|
|
158
|
+
id: messageId,
|
|
159
|
+
workerId: this.id,
|
|
160
|
+
method,
|
|
161
|
+
args: [collectionName, ...args]
|
|
162
|
+
});
|
|
163
|
+
});
|
|
123
164
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Issues a call whose result nobody is waiting for, and makes sure a failure has somewhere to
|
|
167
|
+
* go. A bare rejection here would surface as an uncaught error — which is what a disposed
|
|
168
|
+
* collection produced every time a cursor was cleaned up after it.
|
|
169
|
+
* @param method - The method to call on the worker.
|
|
170
|
+
* @param collectionName - The collection it applies to.
|
|
171
|
+
* @param args - The remaining arguments.
|
|
172
|
+
* @param onError - Called when the call fails, in place of merely logging it.
|
|
173
|
+
*/
|
|
174
|
+
execInBackground(method, collectionName, args, onError) {
|
|
175
|
+
this.exec(method, collectionName, ...args).catch((error) => {
|
|
176
|
+
if (onError) {
|
|
177
|
+
onError(error);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.log(method, "failed", error);
|
|
181
|
+
});
|
|
131
182
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
183
|
+
queryItemsById(query) {
|
|
184
|
+
if (!query.itemsById) query.itemsById = new Map(query.items.map((item) => [item.id, item]));
|
|
185
|
+
return query.itemsById;
|
|
186
|
+
}
|
|
187
|
+
flattenPendingWrites(collectionName) {
|
|
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
|
|
197
|
+
});
|
|
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;
|
|
143
215
|
}
|
|
144
|
-
|
|
216
|
+
WorkerDataAdapter.writeFlatEntry(state, id, top.item);
|
|
145
217
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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);
|
|
149
226
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return query.itemIds;
|
|
227
|
+
static providesFullItems(query) {
|
|
228
|
+
return query.options?.fields == null;
|
|
153
229
|
}
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
230
|
+
observableItems(collectionName) {
|
|
231
|
+
const byId = /* @__PURE__ */ new Map();
|
|
232
|
+
this.queries[collectionName]?.forEach((query) => {
|
|
233
|
+
if (!WorkerDataAdapter.providesFullItems(query)) return;
|
|
234
|
+
query.items.forEach((item) => byId.set(item.id, item));
|
|
235
|
+
});
|
|
236
|
+
const pending = this.flattenPendingWrites(collectionName);
|
|
237
|
+
if (pending) {
|
|
238
|
+
pending.upserts.forEach((item, id) => byId.set(id, item));
|
|
239
|
+
pending.deletes.forEach((id) => byId.delete(id));
|
|
240
|
+
}
|
|
241
|
+
return [...byId.values()];
|
|
242
|
+
}
|
|
243
|
+
observableItemsByIds(collectionName, ids) {
|
|
244
|
+
const pending = this.flattenPendingWrites(collectionName);
|
|
245
|
+
const found = /* @__PURE__ */ new Map();
|
|
246
|
+
ids.forEach((id) => {
|
|
247
|
+
if (pending?.deletes.has(id)) return;
|
|
248
|
+
const pendingItem = pending?.upserts.get(id);
|
|
249
|
+
if (pendingItem) {
|
|
250
|
+
found.set(id, pendingItem);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const queries = this.queries[collectionName];
|
|
254
|
+
if (!queries) return;
|
|
255
|
+
for (const query of queries.values()) {
|
|
256
|
+
if (!WorkerDataAdapter.providesFullItems(query)) continue;
|
|
257
|
+
const item = this.queryItemsById(query).get(id);
|
|
258
|
+
if (item) {
|
|
259
|
+
found.set(id, item);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
return [...found.values()];
|
|
265
|
+
}
|
|
266
|
+
servedResult(collectionName, query) {
|
|
267
|
+
const pendingVersion = this.pendingWriteVersions.get(collectionName) ?? 0;
|
|
268
|
+
if (query.served && query.served.fromItems === query.items && query.served.pendingVersion === pendingVersion) return query.served.items;
|
|
269
|
+
const items = this.advanceServedResult(collectionName, query, pendingVersion) ?? this.computeServedResult(collectionName, query);
|
|
270
|
+
query.served = {
|
|
164
271
|
items,
|
|
165
|
-
|
|
166
|
-
|
|
272
|
+
fromItems: query.items,
|
|
273
|
+
pendingVersion
|
|
274
|
+
};
|
|
275
|
+
return items;
|
|
167
276
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
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));
|
|
173
285
|
}
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (affectedQueries.length === 0) return;
|
|
184
|
-
await Promise.all(affectedQueries.map(async (query) => {
|
|
185
|
-
const { selector, options } = query;
|
|
186
|
-
const previous = query.items;
|
|
187
|
-
const incremental = previous == null ? null : require_incrementalQueryUpdate.default(previous, selector, options, changes);
|
|
188
|
-
if (incremental != null) {
|
|
189
|
-
const delta = require_queryDelta.diffQueryResults(previous, incremental);
|
|
190
|
-
if (require_queryDelta.isEmptyQueryDelta(delta)) return;
|
|
191
|
-
this.setQueryItems(query, incremental);
|
|
192
|
-
this.emitQueryUpdate(collectionName, selector, options, "complete", null, void 0, delta);
|
|
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);
|
|
193
295
|
return;
|
|
194
296
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (previous == null) {
|
|
198
|
-
this.setQueryItems(query, queryItems);
|
|
199
|
-
this.emitQueryUpdate(collectionName, selector, options, "complete", null, queryItems);
|
|
297
|
+
if (pending?.deletes.has(id)) {
|
|
298
|
+
deletes.push(id);
|
|
200
299
|
return;
|
|
201
300
|
}
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
})
|
|
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
|
+
};
|
|
206
309
|
}
|
|
207
|
-
|
|
208
|
-
this.
|
|
209
|
-
|
|
210
|
-
this.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
await storageAdapter.setup();
|
|
215
|
-
await Promise.all(indices.map((index) => storageAdapter.createIndex(index)));
|
|
216
|
-
})();
|
|
217
|
-
this.storageAdapterReady.set(collectionName, setupPromise);
|
|
218
|
-
await setupPromise;
|
|
219
|
-
};
|
|
220
|
-
unregisterCollection = async (collectionName) => {
|
|
221
|
-
this.storageAdapters.delete(collectionName);
|
|
222
|
-
this.queries.delete(collectionName);
|
|
223
|
-
};
|
|
224
|
-
registerQuery = async (collectionName, selector, options) => {
|
|
225
|
-
const query = this.ensureQuery(collectionName, selector, options);
|
|
226
|
-
const queryItems = await this.executeQuery(collectionName, selector, options);
|
|
227
|
-
this.setQueryItems(query, queryItems);
|
|
228
|
-
this.emitQueryUpdate(collectionName, selector, options, "complete", null, queryItems);
|
|
229
|
-
};
|
|
230
|
-
unregisterQuery = async (collectionName, selector, options) => {
|
|
231
|
-
const id = require_queryId.default(selector, options);
|
|
232
|
-
if (!this.queries.get(collectionName)) throw new Error(`Collection ${collectionName} not initialized!`);
|
|
233
|
-
this.queries.get(collectionName)?.delete(id);
|
|
234
|
-
};
|
|
235
|
-
insert = async (collectionName, input) => {
|
|
236
|
-
const storageAdapter = this.storageAdapters.get(collectionName);
|
|
237
|
-
if (!storageAdapter) throw new Error(`No persistence adapter for collection ${collectionName}`);
|
|
238
|
-
const existingItems = await this.executeQuery(collectionName, { id: { $in: input.map((i) => i[0].id) } });
|
|
239
|
-
const result = input.map(([item]) => {
|
|
240
|
-
if (item.id == null) return /* @__PURE__ */ new Error("Item must have an id");
|
|
241
|
-
if (existingItems.some((existing) => existing.id === item.id)) return /* @__PURE__ */ new Error(`Item with id ${item.id} already exists`);
|
|
242
|
-
return item;
|
|
310
|
+
computeServedResult(collectionName, query) {
|
|
311
|
+
const pending = this.flattenPendingWrites(collectionName);
|
|
312
|
+
if (!pending) return query.items;
|
|
313
|
+
const byId = this.queryItemsById(query);
|
|
314
|
+
let affected = false;
|
|
315
|
+
pending.deletes.forEach((id) => {
|
|
316
|
+
if (byId.has(id)) affected = true;
|
|
243
317
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
deletes: []
|
|
318
|
+
if (!affected) pending.upserts.forEach((item, id) => {
|
|
319
|
+
if (affected) return;
|
|
320
|
+
if (byId.has(id)) affected = true;
|
|
321
|
+
else if (query.selector != null && require_match.default(item, query.selector)) affected = true;
|
|
249
322
|
});
|
|
250
|
-
return
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
previousItems.push(item);
|
|
310
|
-
const modifiedItem = {
|
|
311
|
-
...replacement,
|
|
312
|
-
id: replacement.id ?? item.id
|
|
323
|
+
if (!affected) return query.items;
|
|
324
|
+
return require_incrementalQueryUpdate.mergeChangesetIntoResult(query.items, query.selector, query.options, {
|
|
325
|
+
upserts: [...pending.upserts.values()],
|
|
326
|
+
deletes: [...pending.deletes]
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Registers a write's effect locally and notifies every active query it
|
|
331
|
+
* touches, then returns a function that drops it again once the write
|
|
332
|
+
* settles.
|
|
333
|
+
* @param collectionName - The collection the write targets.
|
|
334
|
+
* @param upserts - Items inserted or updated by the write.
|
|
335
|
+
* @param deletes - Ids removed by the write.
|
|
336
|
+
* @returns A function that drops the pending write and re-notifies.
|
|
337
|
+
*/
|
|
338
|
+
applyPendingWrite(collectionName, upserts, deletes) {
|
|
339
|
+
if (upserts.length === 0 && deletes.length === 0) return () => {};
|
|
340
|
+
const affectedIds = /* @__PURE__ */ new Set([...upserts.map((item) => item.id), ...deletes]);
|
|
341
|
+
const affected = this.affectedQueries(collectionName, upserts, affectedIds);
|
|
342
|
+
const servedBefore = this.servedResults(collectionName, affected);
|
|
343
|
+
const seq = this.pendingWriteSeq += 1;
|
|
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 = {
|
|
354
|
+
upserts: new Map(upserts.map((item) => [item.id, item])),
|
|
355
|
+
deletes: new Set(deletes)
|
|
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);
|
|
361
|
+
this.bumpPendingWriteVersion(collectionName);
|
|
362
|
+
state.lastChange = {
|
|
363
|
+
version: this.pendingWriteVersions.get(collectionName) ?? 0,
|
|
364
|
+
ids: [...affectedIds]
|
|
365
|
+
};
|
|
366
|
+
this.notifyWithDeltas(collectionName, affected, servedBefore);
|
|
367
|
+
return () => {
|
|
368
|
+
const current = this.pendingWrites.get(collectionName);
|
|
369
|
+
if (!current) return;
|
|
370
|
+
const settled = current.writes.get(seq);
|
|
371
|
+
if (!settled) return;
|
|
372
|
+
const affectedOnDrop = this.affectedQueries(collectionName, upserts, affectedIds);
|
|
373
|
+
const beforeDrop = this.servedResults(collectionName, affectedOnDrop);
|
|
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);
|
|
378
|
+
this.bumpPendingWriteVersion(collectionName);
|
|
379
|
+
if (current.writes.size > 0) current.lastChange = {
|
|
380
|
+
version: this.pendingWriteVersions.get(collectionName) ?? 0,
|
|
381
|
+
ids: [...affectedIds]
|
|
313
382
|
};
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return result;
|
|
325
|
-
};
|
|
326
|
-
removeOne = async (collectionName, selectors) => {
|
|
327
|
-
const storageAdapter = this.storageAdapters.get(collectionName);
|
|
328
|
-
if (!storageAdapter) throw new Error(`No persistence adapter for collection ${collectionName}`);
|
|
329
|
-
const result = await Promise.all(selectors.map(async ([selector]) => {
|
|
330
|
-
const item = await this.executeQuery(collectionName, selector, { limit: 1 }).then((items) => items[0] ?? null);
|
|
331
|
-
if (item == null) return [];
|
|
332
|
-
return [item];
|
|
333
|
-
}));
|
|
334
|
-
const items = result.flat();
|
|
335
|
-
if (items.length > 0) {
|
|
336
|
-
await storageAdapter.remove(items);
|
|
337
|
-
await this.checkQueryUpdates(collectionName, {
|
|
338
|
-
upserts: [],
|
|
339
|
-
deletes: items.map((item) => item.id)
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
return result;
|
|
343
|
-
};
|
|
344
|
-
removeMany = async (collectionName, selectors) => {
|
|
345
|
-
const storageAdapter = this.storageAdapters.get(collectionName);
|
|
346
|
-
if (!storageAdapter) throw new Error(`No persistence adapter for collection ${collectionName}`);
|
|
347
|
-
const result = await Promise.all(selectors.map(async ([selector]) => this.executeQuery(collectionName, selector)));
|
|
348
|
-
const items = result.flat();
|
|
349
|
-
if (items.length > 0) {
|
|
350
|
-
await storageAdapter.remove(items);
|
|
351
|
-
await this.checkQueryUpdates(collectionName, {
|
|
352
|
-
upserts: [],
|
|
353
|
-
deletes: items.map((item) => item.id)
|
|
383
|
+
this.notifyWithDeltas(collectionName, affectedOnDrop, beforeDrop);
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
affectedQueries(collectionName, upserts, affectedIds) {
|
|
387
|
+
const affected = [];
|
|
388
|
+
this.queries[collectionName]?.forEach((query) => {
|
|
389
|
+
const byId = this.queryItemsById(query);
|
|
390
|
+
let wasHolding = false;
|
|
391
|
+
affectedIds.forEach((id) => {
|
|
392
|
+
if (byId.has(id)) wasHolding = true;
|
|
354
393
|
});
|
|
394
|
+
const nowMatches = upserts.some((item) => query.selector != null && require_match.default(item, query.selector));
|
|
395
|
+
if (!wasHolding && !nowMatches) return;
|
|
396
|
+
affected.push(query);
|
|
397
|
+
});
|
|
398
|
+
return affected;
|
|
399
|
+
}
|
|
400
|
+
servedResults(collectionName, queries) {
|
|
401
|
+
return new Map(queries.map((query) => [query, this.servedResult(collectionName, query)]));
|
|
402
|
+
}
|
|
403
|
+
notifyWithDeltas(collectionName, queries, servedBefore) {
|
|
404
|
+
queries.forEach((query) => {
|
|
405
|
+
const before = servedBefore.get(query);
|
|
406
|
+
if (before == null) return;
|
|
407
|
+
const delta = require_queryDelta.diffQueryResults(before, this.servedResult(collectionName, query));
|
|
408
|
+
if (require_queryDelta.isEmptyQueryDelta(delta)) return;
|
|
409
|
+
query.stateChangeCallbacks.forEach((callback) => require_queryDelta.callWithDelta(callback, query.state, delta));
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
matchObservableItems(collectionName, selector, onlyFirst) {
|
|
413
|
+
if (selector == null) return [];
|
|
414
|
+
const ids = selectorIds(selector);
|
|
415
|
+
const matches = ids == null ? this.observableItems(collectionName).filter((item) => require_match.default(item, selector)) : this.observableItemsByIds(collectionName, ids);
|
|
416
|
+
return onlyFirst ? matches.slice(0, 1) : matches;
|
|
417
|
+
}
|
|
418
|
+
resolveUpdate(collectionName, selector, modifier, onlyFirst) {
|
|
419
|
+
const { $setOnInsert, ...restModifier } = modifier;
|
|
420
|
+
const upserts = [];
|
|
421
|
+
const deletes = [];
|
|
422
|
+
this.matchObservableItems(collectionName, selector, onlyFirst).forEach((item) => {
|
|
423
|
+
const modifiedItem = require_modify.default(require_deepClone.default(item), restModifier);
|
|
424
|
+
upserts.push(modifiedItem);
|
|
425
|
+
if (modifiedItem.id !== item.id) deletes.push(item.id);
|
|
426
|
+
});
|
|
427
|
+
return {
|
|
428
|
+
upserts,
|
|
429
|
+
deletes
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
resolveRemoval(collectionName, selector, onlyFirst) {
|
|
433
|
+
return this.matchObservableItems(collectionName, selector, onlyFirst).map((item) => item.id);
|
|
434
|
+
}
|
|
435
|
+
resolveReplacement(item, replacement) {
|
|
436
|
+
const modifiedItem = {
|
|
437
|
+
...replacement,
|
|
438
|
+
id: replacement.id ?? item.id
|
|
439
|
+
};
|
|
440
|
+
return {
|
|
441
|
+
upserts: [modifiedItem],
|
|
442
|
+
deletes: modifiedItem.id === item.id ? [] : [item.id]
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async withPendingWrite(collectionName, delta, run) {
|
|
446
|
+
const dropPendingWrite = this.applyPendingWrite(collectionName, delta.upserts, delta.deletes);
|
|
447
|
+
try {
|
|
448
|
+
return await run();
|
|
449
|
+
} finally {
|
|
450
|
+
dropPendingWrite();
|
|
355
451
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
452
|
+
}
|
|
453
|
+
enqueueBatched(collectionName, method, args) {
|
|
454
|
+
const helper = this.batchExecutionHelpers.get(collectionName);
|
|
455
|
+
if (!helper) throw new Error(`Collection "${collectionName}" is not registered in WorkerDataAdapter`);
|
|
456
|
+
return helper.enqueue(method, args);
|
|
457
|
+
}
|
|
458
|
+
updateQuery(collectionName, query, update) {
|
|
459
|
+
const id = require_queryId.default(query.selector, query.options);
|
|
460
|
+
const collectionQueries = this.queries[collectionName];
|
|
461
|
+
if (!collectionQueries) return;
|
|
462
|
+
const existing = collectionQueries.get(id);
|
|
463
|
+
const newState = {
|
|
464
|
+
selector: query.selector,
|
|
465
|
+
options: query.options,
|
|
466
|
+
state: "active",
|
|
467
|
+
error: null,
|
|
468
|
+
stateChangeCallbacks: [],
|
|
469
|
+
...existing,
|
|
470
|
+
...update,
|
|
471
|
+
...update.items ? {
|
|
472
|
+
items: update.items,
|
|
473
|
+
itemsById: void 0
|
|
474
|
+
} : { items: existing?.items ?? [] }
|
|
475
|
+
};
|
|
476
|
+
collectionQueries.set(id, newState);
|
|
477
|
+
this.queries[collectionName] = collectionQueries;
|
|
478
|
+
}
|
|
479
|
+
createCollectionBackend(collection, indices) {
|
|
480
|
+
this.queries[collection.name] = /* @__PURE__ */ new Map();
|
|
481
|
+
this.execInBackground("registerCollection", collection.name, [indices]);
|
|
482
|
+
this.collectionReady.set(collection.name, this.exec("isReady", collection.name));
|
|
483
|
+
this.batchExecutionHelpers.set(collection.name, require_batchOnNextTick.default(async (method, args) => this.exec(method, collection.name, args)));
|
|
484
|
+
return {
|
|
485
|
+
insert: async (item) => {
|
|
486
|
+
return this.withPendingWrite(collection.name, {
|
|
487
|
+
upserts: [item],
|
|
488
|
+
deletes: []
|
|
489
|
+
}, () => this.enqueueBatched(collection.name, "insert", [item]));
|
|
490
|
+
},
|
|
491
|
+
updateOne: async (selector, modifier) => {
|
|
492
|
+
return this.withPendingWrite(collection.name, this.resolveUpdate(collection.name, selector, modifier, true), () => this.enqueueBatched(collection.name, "updateOne", [selector, modifier]));
|
|
493
|
+
},
|
|
494
|
+
updateMany: async (selector, modifier) => {
|
|
495
|
+
return this.withPendingWrite(collection.name, this.resolveUpdate(collection.name, selector, modifier, false), () => this.enqueueBatched(collection.name, "updateMany", [selector, modifier]));
|
|
496
|
+
},
|
|
497
|
+
replaceOne: async (selector, replacement) => {
|
|
498
|
+
const [item] = this.matchObservableItems(collection.name, selector, true);
|
|
499
|
+
return this.withPendingWrite(collection.name, item == null ? {
|
|
500
|
+
upserts: [],
|
|
501
|
+
deletes: []
|
|
502
|
+
} : this.resolveReplacement(item, replacement), () => this.enqueueBatched(collection.name, "replaceOne", [selector, replacement]));
|
|
503
|
+
},
|
|
504
|
+
removeOne: async (selector) => {
|
|
505
|
+
return this.withPendingWrite(collection.name, {
|
|
506
|
+
upserts: [],
|
|
507
|
+
deletes: this.resolveRemoval(collection.name, selector, true)
|
|
508
|
+
}, () => this.enqueueBatched(collection.name, "removeOne", [selector]));
|
|
509
|
+
},
|
|
510
|
+
removeMany: async (selector) => {
|
|
511
|
+
return this.withPendingWrite(collection.name, {
|
|
512
|
+
upserts: [],
|
|
513
|
+
deletes: this.resolveRemoval(collection.name, selector, false)
|
|
514
|
+
}, () => this.enqueueBatched(collection.name, "removeMany", [selector]));
|
|
515
|
+
},
|
|
516
|
+
registerQuery: (selector, options) => {
|
|
517
|
+
this.updateQuery(collection.name, {
|
|
518
|
+
selector,
|
|
519
|
+
options
|
|
520
|
+
}, {
|
|
521
|
+
state: "active",
|
|
522
|
+
error: null,
|
|
523
|
+
items: []
|
|
524
|
+
});
|
|
525
|
+
this.execInBackground("registerQuery", collection.name, [selector, options], (error) => {
|
|
526
|
+
const query = this.queries[collection.name]?.get(require_queryId.default(selector, options));
|
|
527
|
+
if (!query) return;
|
|
528
|
+
this.updateQuery(collection.name, {
|
|
529
|
+
selector,
|
|
530
|
+
options
|
|
531
|
+
}, {
|
|
532
|
+
state: "error",
|
|
533
|
+
error
|
|
534
|
+
});
|
|
535
|
+
query.stateChangeCallbacks.forEach((callback) => callback("error"));
|
|
536
|
+
});
|
|
537
|
+
},
|
|
538
|
+
unregisterQuery: (selector, options) => {
|
|
539
|
+
this.queries[collection.name]?.delete(require_queryId.default(selector, options));
|
|
540
|
+
this.execInBackground("unregisterQuery", collection.name, [selector, options]);
|
|
541
|
+
},
|
|
542
|
+
getQueryState: (selector, options) => {
|
|
543
|
+
return (this.queries[collection.name]?.get(require_queryId.default(selector, options)))?.state || "active";
|
|
544
|
+
},
|
|
545
|
+
getQueryError: (selector, options) => {
|
|
546
|
+
return (this.queries[collection.name]?.get(require_queryId.default(selector, options)))?.error || null;
|
|
547
|
+
},
|
|
548
|
+
getQueryResult: (selector, options) => {
|
|
549
|
+
const query = this.queries[collection.name]?.get(require_queryId.default(selector, options));
|
|
550
|
+
if (!query) return [];
|
|
551
|
+
return this.servedResult(collection.name, query);
|
|
552
|
+
},
|
|
553
|
+
onQueryStateChange: (selector, options, callback) => {
|
|
554
|
+
this.updateQuery(collection.name, {
|
|
555
|
+
selector,
|
|
556
|
+
options
|
|
557
|
+
}, { stateChangeCallbacks: [...this.queries[collection.name]?.get(require_queryId.default(selector, options))?.stateChangeCallbacks || [], callback] });
|
|
558
|
+
return () => {
|
|
559
|
+
const currentCallbacks = this.queries[collection.name]?.get(require_queryId.default(selector, options))?.stateChangeCallbacks;
|
|
560
|
+
if (!currentCallbacks) return;
|
|
561
|
+
this.updateQuery(collection.name, {
|
|
562
|
+
selector,
|
|
563
|
+
options
|
|
564
|
+
}, { stateChangeCallbacks: currentCallbacks.filter((existingCallback) => existingCallback !== callback) });
|
|
565
|
+
};
|
|
566
|
+
},
|
|
567
|
+
executeQuery: (selector, options) => this.exec("executeQuery", collection.name, selector, options),
|
|
568
|
+
dispose: async () => {
|
|
569
|
+
await this.exec("unregisterCollection", collection.name);
|
|
570
|
+
this.isDisposed = true;
|
|
571
|
+
this.worker.terminate?.();
|
|
572
|
+
},
|
|
573
|
+
isReady: async () => {
|
|
574
|
+
await this.collectionReady.get(collection.name);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
361
578
|
};
|
|
362
579
|
//#endregion
|
|
363
|
-
exports.default =
|
|
580
|
+
exports.default = WorkerDataAdapter;
|