@powerhousedao/reactor-browser 6.2.0-dev.5 → 6.2.0-dev.50
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/{client-CYhc2W1V.d.ts → client-09xv0Dq6.d.ts} +1 -1
- package/dist/{client-CYhc2W1V.d.ts.map → client-09xv0Dq6.d.ts.map} +1 -1
- package/dist/{client-CvgFU1Dv.js → client-_Gh2Uf0E.js} +1 -1
- package/dist/{client-CvgFU1Dv.js.map → client-_Gh2Uf0E.js.map} +1 -1
- package/dist/client-u13cr-Vg.js +51 -0
- package/dist/client-u13cr-Vg.js.map +1 -0
- package/dist/{document-by-id-BrIy0iHX.js → document-by-id-dLYFX-xz.js} +3 -3
- package/dist/{document-by-id-BrIy0iHX.js.map → document-by-id-dLYFX-xz.js.map} +1 -1
- package/dist/{index-Bz4MqX_j.d.ts → index-D3bV5rcl.d.ts} +1 -2
- package/dist/index-D3bV5rcl.d.ts.map +1 -0
- package/dist/{index-ZltD7u5Z.d.ts → index-DS4W07Y8.d.ts} +2 -45
- package/dist/index-DS4W07Y8.d.ts.map +1 -0
- package/dist/index.d.ts +43 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +137 -131
- package/dist/index.js.map +1 -1
- package/dist/inspector-proxy-CvQDDxmT.d.ts +255 -0
- package/dist/inspector-proxy-CvQDDxmT.d.ts.map +1 -0
- package/dist/{make-ph-event-functions-DwiD1zH9.js → make-ph-event-functions-BHoLPif5.js} +2 -2
- package/dist/{make-ph-event-functions-DwiD1zH9.js.map → make-ph-event-functions-BHoLPif5.js.map} +1 -1
- package/dist/{relational-4_LVwp8p.js → relational-Dj06-YkI.js} +14 -2
- package/dist/relational-Dj06-YkI.js.map +1 -0
- package/dist/{renown-Dzmo1gJD.js → renown-D-1c_Bvx.js} +35 -48
- package/dist/renown-D-1c_Bvx.js.map +1 -0
- package/dist/src/analytics/index.d.ts +2 -2
- package/dist/src/analytics/index.js +2 -2
- package/dist/src/document-model.d.ts +8 -0
- package/dist/src/document-model.d.ts.map +1 -0
- package/dist/src/document-model.js +14 -0
- package/dist/src/document-model.js.map +1 -0
- package/dist/src/graphql/client.d.ts +1 -1
- package/dist/src/graphql/client.js +1 -1
- package/dist/src/relational/index.d.ts +46 -2
- package/dist/src/relational/index.d.ts.map +1 -0
- package/dist/src/relational/index.js +1 -1
- package/dist/src/renown/index.d.ts +1 -1
- package/dist/src/renown/index.js +1 -1
- package/dist/src/rpc/index.d.ts +240 -0
- package/dist/src/rpc/index.d.ts.map +1 -0
- package/dist/src/rpc/index.js +1250 -0
- package/dist/src/rpc/index.js.map +1 -0
- package/dist/{types-BmR60pPN.d.ts → types-CPV0Cnsy.d.ts} +1 -1
- package/dist/{types-BmR60pPN.d.ts.map → types-CPV0Cnsy.d.ts.map} +1 -1
- package/package.json +37 -11
- package/dist/index-Bz4MqX_j.d.ts.map +0 -1
- package/dist/index-ZltD7u5Z.d.ts.map +0 -1
- package/dist/relational-4_LVwp8p.js.map +0 -1
- package/dist/renown-Dzmo1gJD.js.map +0 -1
|
@@ -0,0 +1,1250 @@
|
|
|
1
|
+
import { t as RegistryClient } from "../../client-u13cr-Vg.js";
|
|
2
|
+
import { DriveCollectionId, ReactorEventTypes, SyncEventTypes } from "@powerhousedao/reactor";
|
|
3
|
+
//#region src/rpc/listeners.ts
|
|
4
|
+
var KeyedListeners = class {
|
|
5
|
+
map = /* @__PURE__ */ new Map();
|
|
6
|
+
add(key, fn) {
|
|
7
|
+
let set = this.map.get(key);
|
|
8
|
+
if (!set) {
|
|
9
|
+
set = /* @__PURE__ */ new Set();
|
|
10
|
+
this.map.set(key, set);
|
|
11
|
+
}
|
|
12
|
+
set.add(fn);
|
|
13
|
+
return () => {
|
|
14
|
+
const current = this.map.get(key);
|
|
15
|
+
if (!current) return;
|
|
16
|
+
current.delete(fn);
|
|
17
|
+
if (current.size === 0) this.map.delete(key);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
emit(key, ...a) {
|
|
21
|
+
const set = this.map.get(key);
|
|
22
|
+
if (!set) return;
|
|
23
|
+
for (const fn of [...set]) fn(...a);
|
|
24
|
+
}
|
|
25
|
+
emitAll(...a) {
|
|
26
|
+
for (const set of [...this.map.values()]) for (const fn of [...set]) fn(...a);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var Listeners = class {
|
|
30
|
+
keyed = new KeyedListeners();
|
|
31
|
+
add(fn) {
|
|
32
|
+
return this.keyed.add(null, fn);
|
|
33
|
+
}
|
|
34
|
+
emit(...a) {
|
|
35
|
+
this.keyed.emit(null, ...a);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/rpc/op-channel.ts
|
|
40
|
+
const RPC_DEFAULT_TIMEOUT_MS = 3e4;
|
|
41
|
+
function toVoid(promise) {
|
|
42
|
+
return promise.then(() => void 0);
|
|
43
|
+
}
|
|
44
|
+
function opChannel(router, kind, timeoutMs = RPC_DEFAULT_TIMEOUT_MS) {
|
|
45
|
+
const call = (method, args = []) => router.request((id) => ({
|
|
46
|
+
k: kind,
|
|
47
|
+
id,
|
|
48
|
+
method,
|
|
49
|
+
args
|
|
50
|
+
}), { timeoutMs });
|
|
51
|
+
return {
|
|
52
|
+
call,
|
|
53
|
+
callVoid: (method, args) => toVoid(call(method, args))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/rpc/admin-client.ts
|
|
58
|
+
/** Worker lifecycle channel (info/restart/clearStorage/migrate) over the shared router. */
|
|
59
|
+
function createWorkerAdminClient(router) {
|
|
60
|
+
let migrationState = { status: "idle" };
|
|
61
|
+
const migrationListeners = new Listeners();
|
|
62
|
+
router.on("migration", (msg) => {
|
|
63
|
+
migrationState = msg.state;
|
|
64
|
+
migrationListeners.emit();
|
|
65
|
+
});
|
|
66
|
+
const send = (method) => router.request((id) => ({
|
|
67
|
+
k: "admin",
|
|
68
|
+
id,
|
|
69
|
+
method
|
|
70
|
+
}), { timeoutMs: RPC_DEFAULT_TIMEOUT_MS });
|
|
71
|
+
return {
|
|
72
|
+
info: () => send("info"),
|
|
73
|
+
restart: () => toVoid(send("restart")),
|
|
74
|
+
clearStorage: () => toVoid(send("clearStorage")),
|
|
75
|
+
migrate: () => toVoid(send("migrate")),
|
|
76
|
+
getMigrationState: () => migrationState,
|
|
77
|
+
subscribeMigration: (callback) => migrationListeners.add(callback)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/rpc/error-info.ts
|
|
82
|
+
const MAX_STACK_LENGTH = 8 * 1024;
|
|
83
|
+
const MAX_CAUSE_DEPTH = 8;
|
|
84
|
+
function toErrorInfo(err, depth = 0) {
|
|
85
|
+
if (depth > MAX_CAUSE_DEPTH) return {
|
|
86
|
+
name: "Error",
|
|
87
|
+
message: "cause chain truncated"
|
|
88
|
+
};
|
|
89
|
+
if (err instanceof Error) {
|
|
90
|
+
const info = {
|
|
91
|
+
name: err.name || "Error",
|
|
92
|
+
message: err.message
|
|
93
|
+
};
|
|
94
|
+
if (typeof err.stack === "string") info.stack = err.stack.length > MAX_STACK_LENGTH ? err.stack.slice(0, MAX_STACK_LENGTH) : err.stack;
|
|
95
|
+
if (err.cause !== void 0) info.cause = toErrorInfo(err.cause, depth + 1);
|
|
96
|
+
return info;
|
|
97
|
+
}
|
|
98
|
+
if (typeof err === "string") return {
|
|
99
|
+
name: "Error",
|
|
100
|
+
message: err
|
|
101
|
+
};
|
|
102
|
+
if (err && typeof err === "object") {
|
|
103
|
+
const record = err;
|
|
104
|
+
return {
|
|
105
|
+
name: typeof record.name === "string" ? record.name : "Error",
|
|
106
|
+
message: typeof record.message === "string" ? record.message : safeStringify(err)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
name: "Error",
|
|
111
|
+
message: safeStringify(err)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function safeStringify(value) {
|
|
115
|
+
if (value === null || value === void 0) return String(value);
|
|
116
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
|
|
117
|
+
try {
|
|
118
|
+
return JSON.stringify(value);
|
|
119
|
+
} catch {
|
|
120
|
+
return "[unserializable]";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function fromErrorInfo(info) {
|
|
124
|
+
const err = new Error(info.message);
|
|
125
|
+
Object.defineProperty(err, "name", {
|
|
126
|
+
value: info.name,
|
|
127
|
+
configurable: true,
|
|
128
|
+
writable: true
|
|
129
|
+
});
|
|
130
|
+
if (info.stack !== void 0) err.stack = info.stack;
|
|
131
|
+
if (info.cause !== void 0) Object.defineProperty(err, "cause", {
|
|
132
|
+
value: fromErrorInfo(info.cause),
|
|
133
|
+
configurable: true,
|
|
134
|
+
writable: true
|
|
135
|
+
});
|
|
136
|
+
return err;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/rpc/paging.ts
|
|
140
|
+
function isPageableResult(value) {
|
|
141
|
+
return value !== null && typeof value === "object" && typeof value.next === "function";
|
|
142
|
+
}
|
|
143
|
+
function hasNextToken(value) {
|
|
144
|
+
return value !== null && typeof value === "object" && typeof value.nextToken === "string";
|
|
145
|
+
}
|
|
146
|
+
function dehydratePage(value, registerCursor) {
|
|
147
|
+
if (!isPageableResult(value)) return value;
|
|
148
|
+
const { next, ...rest } = value;
|
|
149
|
+
return {
|
|
150
|
+
...rest,
|
|
151
|
+
nextToken: registerCursor(next)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function rehydratePage(value, fetchPage) {
|
|
155
|
+
if (!hasNextToken(value)) return value;
|
|
156
|
+
const { nextToken, ...rest } = value;
|
|
157
|
+
return {
|
|
158
|
+
...rest,
|
|
159
|
+
next: () => fetchPage(nextToken)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/rpc/subscription.ts
|
|
164
|
+
var SubscriptionStore = class {
|
|
165
|
+
map = /* @__PURE__ */ new Map();
|
|
166
|
+
set(id, unsubscribe) {
|
|
167
|
+
this.map.set(id, unsubscribe);
|
|
168
|
+
}
|
|
169
|
+
get(id) {
|
|
170
|
+
return this.map.get(id);
|
|
171
|
+
}
|
|
172
|
+
delete(id) {
|
|
173
|
+
this.map.delete(id);
|
|
174
|
+
}
|
|
175
|
+
end(id) {
|
|
176
|
+
const unsubscribe = this.map.get(id);
|
|
177
|
+
if (unsubscribe) {
|
|
178
|
+
this.map.delete(id);
|
|
179
|
+
unsubscribe();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
drain() {
|
|
183
|
+
for (const unsubscribe of this.map.values()) unsubscribe();
|
|
184
|
+
this.map.clear();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
function createCorrelatedSubscriptions(router, config) {
|
|
188
|
+
let counter = 0;
|
|
189
|
+
const subs = /* @__PURE__ */ new Map();
|
|
190
|
+
router.on(config.eventKind, (message) => {
|
|
191
|
+
const sub = subs.get(message.id);
|
|
192
|
+
if (sub) config.onEvent(sub, message);
|
|
193
|
+
});
|
|
194
|
+
router.on(config.errKind, (message) => {
|
|
195
|
+
const id = message.id;
|
|
196
|
+
const sub = subs.get(id);
|
|
197
|
+
if (sub) {
|
|
198
|
+
subs.delete(id);
|
|
199
|
+
router.post(config.close(id));
|
|
200
|
+
config.onError(sub, message);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return { subscribe(sub, open) {
|
|
204
|
+
const id = `${config.idPrefix}${++counter}`;
|
|
205
|
+
subs.set(id, sub);
|
|
206
|
+
router.post(open(id));
|
|
207
|
+
return () => {
|
|
208
|
+
subs.delete(id);
|
|
209
|
+
router.post(config.close(id));
|
|
210
|
+
};
|
|
211
|
+
} };
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/rpc/client-proxy.ts
|
|
215
|
+
function isAbortSignal(value) {
|
|
216
|
+
return typeof value === "object" && value !== null && typeof value.aborted === "boolean" && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function";
|
|
217
|
+
}
|
|
218
|
+
function createReactorClientProxy(router, options = {}) {
|
|
219
|
+
const registry = options.registry;
|
|
220
|
+
function fetchPage(token) {
|
|
221
|
+
return router.request((id) => ({
|
|
222
|
+
k: "page",
|
|
223
|
+
id,
|
|
224
|
+
token
|
|
225
|
+
}), { transform: rehydrate });
|
|
226
|
+
}
|
|
227
|
+
function rehydrate(value) {
|
|
228
|
+
return rehydratePage(value, fetchPage);
|
|
229
|
+
}
|
|
230
|
+
const changeSubs = createCorrelatedSubscriptions(router, {
|
|
231
|
+
idPrefix: "sub",
|
|
232
|
+
eventKind: "event",
|
|
233
|
+
errKind: "sub-err",
|
|
234
|
+
onEvent: (callback, msg) => callback(msg.change),
|
|
235
|
+
onError: (_callback, msg) => console.error("Reactor subscription failed:", fromErrorInfo(msg.error)),
|
|
236
|
+
close: (id) => ({
|
|
237
|
+
k: "unsub",
|
|
238
|
+
id
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
router.on("reload", (msg) => {
|
|
242
|
+
options.onReload?.(msg.reason, msg.workerGen);
|
|
243
|
+
});
|
|
244
|
+
const call = (method, args) => {
|
|
245
|
+
let abortAt;
|
|
246
|
+
for (let i = 0; i < args.length; i++) if (isAbortSignal(args[i])) abortAt = i;
|
|
247
|
+
let wire = args;
|
|
248
|
+
if (abortAt !== void 0) {
|
|
249
|
+
wire = args.slice();
|
|
250
|
+
wire[abortAt] = void 0;
|
|
251
|
+
}
|
|
252
|
+
return router.request((id) => ({
|
|
253
|
+
k: "req",
|
|
254
|
+
id,
|
|
255
|
+
method,
|
|
256
|
+
args: wire,
|
|
257
|
+
abortAt
|
|
258
|
+
}), {
|
|
259
|
+
transform: rehydrate,
|
|
260
|
+
setup: (id) => {
|
|
261
|
+
if (abortAt === void 0) return;
|
|
262
|
+
const signal = args[abortAt];
|
|
263
|
+
const sendAbort = () => router.post({
|
|
264
|
+
k: "abort",
|
|
265
|
+
targetId: id
|
|
266
|
+
});
|
|
267
|
+
if (signal.aborted) {
|
|
268
|
+
sendAbort();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
signal.addEventListener("abort", sendAbort, { once: true });
|
|
272
|
+
return () => signal.removeEventListener("abort", sendAbort);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
const subscribe = (search, callback, view) => changeSubs.subscribe(callback, (id) => ({
|
|
277
|
+
k: "sub",
|
|
278
|
+
id,
|
|
279
|
+
search,
|
|
280
|
+
view
|
|
281
|
+
}));
|
|
282
|
+
const forwarders = /* @__PURE__ */ new Map();
|
|
283
|
+
const forwarder = (method) => {
|
|
284
|
+
let fn = forwarders.get(method);
|
|
285
|
+
if (!fn) {
|
|
286
|
+
fn = (...args) => call(method, args);
|
|
287
|
+
forwarders.set(method, fn);
|
|
288
|
+
}
|
|
289
|
+
return fn;
|
|
290
|
+
};
|
|
291
|
+
const drives = new Proxy({}, { get: (_target, prop) => typeof prop === "string" ? forwarder(`drives.${prop}`) : void 0 });
|
|
292
|
+
return new Proxy({}, { get(_target, prop) {
|
|
293
|
+
if (typeof prop !== "string" || prop === "then") return;
|
|
294
|
+
if (prop === "drives") return drives;
|
|
295
|
+
if (prop === "subscribe") return subscribe;
|
|
296
|
+
if (registry) {
|
|
297
|
+
if (prop === "getDocumentModelModule") return (documentType) => Promise.resolve().then(() => registry.getModule(documentType));
|
|
298
|
+
if (prop === "getDocumentModelModules") return (_namespace, paging) => Promise.resolve({
|
|
299
|
+
results: registry.getAllModules(),
|
|
300
|
+
options: paging ?? {
|
|
301
|
+
cursor: "",
|
|
302
|
+
limit: Number.MAX_SAFE_INTEGER
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return forwarder(prop);
|
|
307
|
+
} });
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/rpc/inspector-proxy.ts
|
|
311
|
+
function createInspectorProxy(router) {
|
|
312
|
+
const ops = opChannel(router, "inspector-op");
|
|
313
|
+
return {
|
|
314
|
+
getQueueState: () => ops.call("queue.getState"),
|
|
315
|
+
pauseQueue: () => ops.callVoid("queue.pause"),
|
|
316
|
+
resumeQueue: () => ops.callVoid("queue.resume"),
|
|
317
|
+
getProcessors: () => ops.call("processors.getAll"),
|
|
318
|
+
retryProcessor: (processorId) => ops.callVoid("processors.retry", [processorId]),
|
|
319
|
+
validateDocument: (documentId, branch) => ops.call("integrity.validate", [documentId, branch]),
|
|
320
|
+
rebuildKeyframes: (documentId, branch) => ops.call("integrity.rebuildKeyframes", [documentId, branch]),
|
|
321
|
+
rebuildSnapshots: (documentId, branch) => ops.call("integrity.rebuildSnapshots", [documentId, branch]),
|
|
322
|
+
queryReactorDb: (sql, params) => ops.call("db.query", [sql, params ?? []])
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/rpc/protocol.ts
|
|
327
|
+
const RPC_PROTOCOL_VERSION = 2;
|
|
328
|
+
/** Error reply kind for a client message kind, so it reaches the right registry. */
|
|
329
|
+
function responseErrorKind(k) {
|
|
330
|
+
if (k === "sub") return "sub-err";
|
|
331
|
+
if (k === "sub-live") return "live-err";
|
|
332
|
+
return "err";
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/rpc/host-reply.ts
|
|
336
|
+
const defaultOk = () => ({ ok: true });
|
|
337
|
+
function hostResponder(transport) {
|
|
338
|
+
const ok = (id, value = { ok: true }) => {
|
|
339
|
+
transport.post({
|
|
340
|
+
k: "res",
|
|
341
|
+
id,
|
|
342
|
+
value
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
const err = (id, error) => {
|
|
346
|
+
transport.post({
|
|
347
|
+
k: "err",
|
|
348
|
+
id,
|
|
349
|
+
error: toErrorInfo(error)
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
return {
|
|
353
|
+
ok,
|
|
354
|
+
err,
|
|
355
|
+
errForKind(message, error) {
|
|
356
|
+
if (!("id" in message)) return;
|
|
357
|
+
transport.post({
|
|
358
|
+
k: responseErrorKind(message.k),
|
|
359
|
+
id: message.id,
|
|
360
|
+
error: toErrorInfo(error)
|
|
361
|
+
});
|
|
362
|
+
},
|
|
363
|
+
async run(id, body, mapOk = defaultOk) {
|
|
364
|
+
try {
|
|
365
|
+
ok(id, mapOk(await body()));
|
|
366
|
+
} catch (error) {
|
|
367
|
+
err(id, error);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/rpc/live-query-proxy.ts
|
|
374
|
+
function createLiveQueryProxy(router) {
|
|
375
|
+
const subs = createCorrelatedSubscriptions(router, {
|
|
376
|
+
idPrefix: "live",
|
|
377
|
+
eventKind: "event-live",
|
|
378
|
+
errKind: "live-err",
|
|
379
|
+
onEvent: (sub, msg) => sub.onResults(msg.results),
|
|
380
|
+
onError: (sub, msg) => sub.onError?.(fromErrorInfo(msg.error)),
|
|
381
|
+
close: (id) => ({
|
|
382
|
+
k: "unsub-live",
|
|
383
|
+
id
|
|
384
|
+
})
|
|
385
|
+
});
|
|
386
|
+
return { query(sql, params, onResults, onError) {
|
|
387
|
+
return subs.subscribe({
|
|
388
|
+
onResults,
|
|
389
|
+
onError
|
|
390
|
+
}, (id) => ({
|
|
391
|
+
k: "sub-live",
|
|
392
|
+
id,
|
|
393
|
+
sql,
|
|
394
|
+
params
|
|
395
|
+
}));
|
|
396
|
+
} };
|
|
397
|
+
}
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/rpc/relational-db-proxy.ts
|
|
400
|
+
function createRelationalPgliteProxy(router) {
|
|
401
|
+
const ops = opChannel(router, "db-op");
|
|
402
|
+
const liveProxy = createLiveQueryProxy(router);
|
|
403
|
+
return {
|
|
404
|
+
query: async (sql, params) => {
|
|
405
|
+
return { rows: await ops.call("query", [sql, params ?? []]) };
|
|
406
|
+
},
|
|
407
|
+
live: { query: (sql, params, callback) => new Promise((resolve, reject) => {
|
|
408
|
+
let settled = false;
|
|
409
|
+
const unsubscribe = liveProxy.query(sql, params ?? [], (results) => {
|
|
410
|
+
if (!settled) {
|
|
411
|
+
settled = true;
|
|
412
|
+
resolve({ unsubscribe });
|
|
413
|
+
}
|
|
414
|
+
callback?.(results);
|
|
415
|
+
}, (error) => {
|
|
416
|
+
if (!settled) {
|
|
417
|
+
settled = true;
|
|
418
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}) }
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/rpc/sync-manager-proxy.ts
|
|
426
|
+
const SYNC_STATUS_CHANGED_EVENT = 90001;
|
|
427
|
+
const SEED_MAX_ATTEMPTS = 3;
|
|
428
|
+
const SEED_RETRY_DELAY_MS = 500;
|
|
429
|
+
const DEFAULT_SNAPSHOT = {
|
|
430
|
+
state: "connecting",
|
|
431
|
+
failureCount: 0,
|
|
432
|
+
lastSuccessUtcMs: 0,
|
|
433
|
+
lastFailureUtcMs: 0,
|
|
434
|
+
pushBlocked: false,
|
|
435
|
+
pushFailureCount: 0,
|
|
436
|
+
receivingPages: false,
|
|
437
|
+
requiresAuth: false
|
|
438
|
+
};
|
|
439
|
+
var NoopMailbox = class {
|
|
440
|
+
get items() {
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
get ackOrdinal() {
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
get latestOrdinal() {
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
init() {}
|
|
450
|
+
advanceOrdinal() {}
|
|
451
|
+
get() {}
|
|
452
|
+
add() {}
|
|
453
|
+
remove() {}
|
|
454
|
+
onAdded() {}
|
|
455
|
+
onRemoved() {}
|
|
456
|
+
pause() {}
|
|
457
|
+
resume() {}
|
|
458
|
+
flush() {}
|
|
459
|
+
isPaused() {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const NOOP_MAILBOX = new NoopMailbox();
|
|
464
|
+
function rehydrateMeta(wire) {
|
|
465
|
+
return {
|
|
466
|
+
id: wire.id,
|
|
467
|
+
name: wire.name,
|
|
468
|
+
collectionId: DriveCollectionId.forDrive(wire.collectionId.driveId, wire.collectionId.branch),
|
|
469
|
+
channelConfig: wire.channelConfig,
|
|
470
|
+
filter: wire.filter,
|
|
471
|
+
options: wire.options
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function channelUrl(meta) {
|
|
475
|
+
const url = meta.channelConfig.parameters.url;
|
|
476
|
+
return typeof url === "string" ? url : void 0;
|
|
477
|
+
}
|
|
478
|
+
var SyncManagerProxy = class {
|
|
479
|
+
ops;
|
|
480
|
+
connectionStates = /* @__PURE__ */ new Map();
|
|
481
|
+
connectionListeners = new KeyedListeners();
|
|
482
|
+
syncStatuses = /* @__PURE__ */ new Map();
|
|
483
|
+
syncStatusListeners = new Listeners();
|
|
484
|
+
remotes = [];
|
|
485
|
+
seedPromise = null;
|
|
486
|
+
constructor(router, busProxy) {
|
|
487
|
+
this.ops = opChannel(router, "sync-op");
|
|
488
|
+
busProxy.subscribe(SyncEventTypes.CONNECTION_STATE_CHANGED, (_type, event) => {
|
|
489
|
+
const e = event;
|
|
490
|
+
this.connectionStates.set(e.remoteName, e.snapshot);
|
|
491
|
+
this.notifyConnection(e.remoteName);
|
|
492
|
+
});
|
|
493
|
+
busProxy.subscribe(SYNC_STATUS_CHANGED_EVENT, (_type, event) => {
|
|
494
|
+
const e = event;
|
|
495
|
+
this.syncStatuses.set(e.documentId, e.status);
|
|
496
|
+
this.syncStatusListeners.emit(e.documentId, e.status);
|
|
497
|
+
});
|
|
498
|
+
this.ensureSeeded();
|
|
499
|
+
}
|
|
500
|
+
async startup() {
|
|
501
|
+
let lastError;
|
|
502
|
+
for (let attempt = 0; attempt < SEED_MAX_ATTEMPTS; attempt++) try {
|
|
503
|
+
await this.ensureSeeded();
|
|
504
|
+
return;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
lastError = error;
|
|
507
|
+
if (attempt < SEED_MAX_ATTEMPTS - 1) await new Promise((resolve) => setTimeout(resolve, SEED_RETRY_DELAY_MS));
|
|
508
|
+
}
|
|
509
|
+
throw lastError;
|
|
510
|
+
}
|
|
511
|
+
shutdown() {
|
|
512
|
+
return {
|
|
513
|
+
isShutdown: true,
|
|
514
|
+
completed: Promise.resolve()
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
getByName(name) {
|
|
518
|
+
const remote = this.remotes.find((r) => r.meta.name === name);
|
|
519
|
+
if (!remote) throw new Error(`Unknown remote: ${name}`);
|
|
520
|
+
return remote;
|
|
521
|
+
}
|
|
522
|
+
getById(id) {
|
|
523
|
+
const remote = this.remotes.find((r) => r.meta.id === id);
|
|
524
|
+
if (!remote) throw new Error(`Unknown remote id: ${id}`);
|
|
525
|
+
return remote;
|
|
526
|
+
}
|
|
527
|
+
async add(name, collectionId, channelConfig, filter, options) {
|
|
528
|
+
await this.callSyncOp("add", [
|
|
529
|
+
name,
|
|
530
|
+
collectionId.key,
|
|
531
|
+
channelConfig,
|
|
532
|
+
filter,
|
|
533
|
+
options
|
|
534
|
+
]);
|
|
535
|
+
await this.refreshRemotes();
|
|
536
|
+
return this.getByName(name);
|
|
537
|
+
}
|
|
538
|
+
triggerPull(name) {
|
|
539
|
+
this.callSyncOp("triggerPull", [name]).catch((error) => {
|
|
540
|
+
console.error(`triggerPull failed for remote "${name}":`, error);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async remove(name) {
|
|
544
|
+
await this.callSyncOp("remove", [name]);
|
|
545
|
+
await this.refreshRemotes();
|
|
546
|
+
}
|
|
547
|
+
list() {
|
|
548
|
+
return [...this.remotes];
|
|
549
|
+
}
|
|
550
|
+
waitForSync() {
|
|
551
|
+
return Promise.reject(/* @__PURE__ */ new Error("waitForSync is not supported over the worker RPC boundary"));
|
|
552
|
+
}
|
|
553
|
+
getSyncStatus(documentId) {
|
|
554
|
+
return this.syncStatuses.get(documentId);
|
|
555
|
+
}
|
|
556
|
+
onSyncStatusChange(callback) {
|
|
557
|
+
return this.syncStatusListeners.add(callback);
|
|
558
|
+
}
|
|
559
|
+
callSyncOp(method, args) {
|
|
560
|
+
return this.ops.call(method, args);
|
|
561
|
+
}
|
|
562
|
+
notifyConnection(remoteName) {
|
|
563
|
+
if (remoteName !== void 0) {
|
|
564
|
+
this.connectionListeners.emit(remoteName);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
this.connectionListeners.emitAll();
|
|
568
|
+
}
|
|
569
|
+
makeChannel(remoteName, url) {
|
|
570
|
+
return {
|
|
571
|
+
inbox: NOOP_MAILBOX,
|
|
572
|
+
outbox: NOOP_MAILBOX,
|
|
573
|
+
deadLetter: NOOP_MAILBOX,
|
|
574
|
+
init: () => Promise.resolve(),
|
|
575
|
+
shutdown: () => Promise.resolve(),
|
|
576
|
+
getConnectionState: () => this.connectionStates.get(remoteName) ?? DEFAULT_SNAPSHOT,
|
|
577
|
+
onConnectionStateChange: (callback) => {
|
|
578
|
+
const listener = () => callback(this.connectionStates.get(remoteName) ?? DEFAULT_SNAPSHOT);
|
|
579
|
+
return this.connectionListeners.add(remoteName, listener);
|
|
580
|
+
},
|
|
581
|
+
triggerPull: () => {
|
|
582
|
+
this.callSyncOp("triggerPull", [remoteName]).catch((error) => {
|
|
583
|
+
console.error(`triggerPull failed for remote "${remoteName}":`, error);
|
|
584
|
+
});
|
|
585
|
+
},
|
|
586
|
+
config: { url }
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async refreshRemotes() {
|
|
590
|
+
const wire = await this.callSyncOp("list", []);
|
|
591
|
+
const names = /* @__PURE__ */ new Set();
|
|
592
|
+
this.remotes = wire.map((w) => {
|
|
593
|
+
const meta = rehydrateMeta(w.meta);
|
|
594
|
+
names.add(meta.name);
|
|
595
|
+
if (!this.connectionStates.has(meta.name)) this.connectionStates.set(meta.name, w.connectionState);
|
|
596
|
+
return {
|
|
597
|
+
meta,
|
|
598
|
+
channel: this.makeChannel(meta.name, channelUrl(meta))
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
for (const name of [...this.connectionStates.keys()]) if (!names.has(name)) this.connectionStates.delete(name);
|
|
602
|
+
this.notifyConnection();
|
|
603
|
+
}
|
|
604
|
+
ensureSeeded() {
|
|
605
|
+
if (!this.seedPromise) {
|
|
606
|
+
const pending = this.refreshRemotes();
|
|
607
|
+
this.seedPromise = pending;
|
|
608
|
+
pending.catch(() => {
|
|
609
|
+
if (this.seedPromise === pending) this.seedPromise = null;
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
return this.seedPromise;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
function createSyncManagerProxy(router, busProxy) {
|
|
616
|
+
return new SyncManagerProxy(router, busProxy);
|
|
617
|
+
}
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/rpc/forwarded-events.ts
|
|
620
|
+
const FORWARDED_EVENT_TYPES = [
|
|
621
|
+
SyncEventTypes.SYNC_PENDING,
|
|
622
|
+
SyncEventTypes.SYNC_SUCCEEDED,
|
|
623
|
+
SyncEventTypes.SYNC_FAILED,
|
|
624
|
+
SyncEventTypes.DEAD_LETTER_ADDED,
|
|
625
|
+
SyncEventTypes.CONNECTION_STATE_CHANGED,
|
|
626
|
+
ReactorEventTypes.MODEL_LOADED
|
|
627
|
+
];
|
|
628
|
+
const FORWARDED_BUS_EVENT_TYPES = [...FORWARDED_EVENT_TYPES, SYNC_STATUS_CHANGED_EVENT];
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/rpc/event-bus-proxy.ts
|
|
631
|
+
/** Tab-side IEventBus over bus-event; emit() is unsupported (the worker emits). */
|
|
632
|
+
var ReactorEventBusProxy = class {
|
|
633
|
+
forwardedTypes = new Set(FORWARDED_BUS_EVENT_TYPES);
|
|
634
|
+
subscribers = new KeyedListeners();
|
|
635
|
+
constructor(router) {
|
|
636
|
+
router.on("bus-event", (msg) => {
|
|
637
|
+
this.subscribers.emit(msg.eventType, msg.eventType, msg.event);
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
subscribe(type, subscriber) {
|
|
641
|
+
if (!this.forwardedTypes.has(type)) throw new Error(`ReactorEventBusProxy cannot subscribe to event type ${type}: the worker forwards only [${[...this.forwardedTypes].join(", ")}]`);
|
|
642
|
+
const entry = subscriber;
|
|
643
|
+
return this.subscribers.add(type, entry);
|
|
644
|
+
}
|
|
645
|
+
emit() {
|
|
646
|
+
return Promise.reject(/* @__PURE__ */ new Error("EventBus.emit is not supported tab-side; the reactor graph emits in the worker"));
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
function createReactorEventBusProxy(router) {
|
|
650
|
+
return new ReactorEventBusProxy(router);
|
|
651
|
+
}
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/rpc/connect-reactor.ts
|
|
654
|
+
function connectReactorClient(router, hello, onReload, registry) {
|
|
655
|
+
const client = createReactorClientProxy(router, {
|
|
656
|
+
onReload,
|
|
657
|
+
registry
|
|
658
|
+
});
|
|
659
|
+
router.post({
|
|
660
|
+
k: "hello",
|
|
661
|
+
id: "hello",
|
|
662
|
+
version: hello.version,
|
|
663
|
+
construct: hello.construct,
|
|
664
|
+
packages: hello.packages
|
|
665
|
+
});
|
|
666
|
+
return client;
|
|
667
|
+
}
|
|
668
|
+
function postReactorIdentity(router, user) {
|
|
669
|
+
router.post({
|
|
670
|
+
k: "identity",
|
|
671
|
+
user
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/rpc/host-server.ts
|
|
676
|
+
const MAX_PENDING_PAGES = 1e3;
|
|
677
|
+
var ReactorHostServer = class {
|
|
678
|
+
client;
|
|
679
|
+
transport;
|
|
680
|
+
reply;
|
|
681
|
+
aborters = /* @__PURE__ */ new Map();
|
|
682
|
+
subscriptions = new SubscriptionStore();
|
|
683
|
+
pages = /* @__PURE__ */ new Map();
|
|
684
|
+
pageCounter = 0;
|
|
685
|
+
detach = () => {};
|
|
686
|
+
constructor(client, transport) {
|
|
687
|
+
this.client = client;
|
|
688
|
+
this.transport = transport;
|
|
689
|
+
this.reply = hostResponder(transport);
|
|
690
|
+
}
|
|
691
|
+
start() {
|
|
692
|
+
this.detach = this.transport.onMessage((message) => {
|
|
693
|
+
this.handleMessage(message);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
stop() {
|
|
697
|
+
this.detach();
|
|
698
|
+
this.subscriptions.drain();
|
|
699
|
+
this.aborters.clear();
|
|
700
|
+
this.pages.clear();
|
|
701
|
+
}
|
|
702
|
+
async handleMessage(message) {
|
|
703
|
+
try {
|
|
704
|
+
switch (message.k) {
|
|
705
|
+
case "req":
|
|
706
|
+
await this.handleRequest(message);
|
|
707
|
+
return;
|
|
708
|
+
case "page":
|
|
709
|
+
await this.handlePage(message);
|
|
710
|
+
return;
|
|
711
|
+
case "abort":
|
|
712
|
+
this.aborters.get(message.targetId)?.abort();
|
|
713
|
+
return;
|
|
714
|
+
case "sub":
|
|
715
|
+
this.handleSubscribe(message);
|
|
716
|
+
return;
|
|
717
|
+
case "unsub":
|
|
718
|
+
this.subscriptions.end(message.id);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
} catch (error) {
|
|
722
|
+
this.reply.errForKind(message, error);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async handleRequest(message) {
|
|
726
|
+
let args = message.args;
|
|
727
|
+
let controller;
|
|
728
|
+
if (message.abortAt !== void 0) {
|
|
729
|
+
controller = new AbortController();
|
|
730
|
+
this.aborters.set(message.id, controller);
|
|
731
|
+
args = message.args.slice();
|
|
732
|
+
args[message.abortAt] = controller.signal;
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
const value = await this.resolveMethod(message.method)(...args);
|
|
736
|
+
this.reply.ok(message.id, this.prepareResult(value));
|
|
737
|
+
} catch (error) {
|
|
738
|
+
this.reply.err(message.id, error);
|
|
739
|
+
} finally {
|
|
740
|
+
if (controller) this.aborters.delete(message.id);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async handlePage(message) {
|
|
744
|
+
const next = this.pages.get(message.token);
|
|
745
|
+
this.pages.delete(message.token);
|
|
746
|
+
if (!next) {
|
|
747
|
+
this.reply.err(message.id, /* @__PURE__ */ new Error("Paged cursor already consumed or expired"));
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
const value = await next();
|
|
752
|
+
this.reply.ok(message.id, this.prepareResult(value));
|
|
753
|
+
} catch (error) {
|
|
754
|
+
this.reply.err(message.id, error);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
handleSubscribe(message) {
|
|
758
|
+
const unsubscribe = this.client.subscribe(message.search, (change) => this.transport.post({
|
|
759
|
+
k: "event",
|
|
760
|
+
id: message.id,
|
|
761
|
+
change
|
|
762
|
+
}), message.view);
|
|
763
|
+
this.subscriptions.set(message.id, unsubscribe);
|
|
764
|
+
}
|
|
765
|
+
prepareResult(value) {
|
|
766
|
+
return dehydratePage(value, (next) => {
|
|
767
|
+
const token = `p${++this.pageCounter}`;
|
|
768
|
+
this.pages.set(token, next);
|
|
769
|
+
if (this.pages.size > MAX_PENDING_PAGES) {
|
|
770
|
+
const oldest = this.pages.keys().next().value;
|
|
771
|
+
if (oldest !== void 0) this.pages.delete(oldest);
|
|
772
|
+
}
|
|
773
|
+
return token;
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
resolveMethod(path) {
|
|
777
|
+
if (path.startsWith("drives.")) {
|
|
778
|
+
const name = path.slice(7);
|
|
779
|
+
const fn = this.client.drives[name];
|
|
780
|
+
if (typeof fn !== "function") throw new Error(`Unknown drive method: ${name}`);
|
|
781
|
+
return fn.bind(this.client.drives);
|
|
782
|
+
}
|
|
783
|
+
const fn = this.client[path];
|
|
784
|
+
if (typeof fn !== "function") throw new Error(`Unknown reactor method: ${path}`);
|
|
785
|
+
return fn.bind(this.client);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
//#endregion
|
|
789
|
+
//#region src/rpc/transport.ts
|
|
790
|
+
function createPortTransport(port) {
|
|
791
|
+
port.start();
|
|
792
|
+
port.addEventListener("messageerror", (event) => {
|
|
793
|
+
console.error("[rpc transport] failed to deserialize message", event);
|
|
794
|
+
});
|
|
795
|
+
return {
|
|
796
|
+
post(message) {
|
|
797
|
+
port.postMessage(message);
|
|
798
|
+
},
|
|
799
|
+
onMessage(listener) {
|
|
800
|
+
const handler = (event) => {
|
|
801
|
+
listener(event.data);
|
|
802
|
+
};
|
|
803
|
+
port.addEventListener("message", handler);
|
|
804
|
+
return () => port.removeEventListener("message", handler);
|
|
805
|
+
},
|
|
806
|
+
close() {
|
|
807
|
+
port.close();
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
//#endregion
|
|
812
|
+
//#region src/rpc/reactor-host.ts
|
|
813
|
+
function isDataMessage(msg) {
|
|
814
|
+
return msg.k === "req" || msg.k === "sub" || msg.k === "page" || msg.k === "sync-op" || msg.k === "db-op" || msg.k === "inspector-op" || msg.k === "sub-live";
|
|
815
|
+
}
|
|
816
|
+
function versionsCompatible(a, b) {
|
|
817
|
+
return a.appBuildId === b.appBuildId && a.rpcProtocolVersion === b.rpcProtocolVersion;
|
|
818
|
+
}
|
|
819
|
+
function workerGenForVersion(version) {
|
|
820
|
+
return `v${version.rpcProtocolVersion}-${version.appBuildId}`;
|
|
821
|
+
}
|
|
822
|
+
var ReactorHost = class {
|
|
823
|
+
options;
|
|
824
|
+
disposers = /* @__PURE__ */ new Set();
|
|
825
|
+
clients = /* @__PURE__ */ new Set();
|
|
826
|
+
clientPromise = null;
|
|
827
|
+
baseline = null;
|
|
828
|
+
ownerId;
|
|
829
|
+
bootedAtMs;
|
|
830
|
+
migrationState = null;
|
|
831
|
+
constructor(options) {
|
|
832
|
+
this.options = options;
|
|
833
|
+
this.ownerId = options.ownerId ?? crypto.randomUUID();
|
|
834
|
+
this.bootedAtMs = options.bootedAtMs ?? Date.now();
|
|
835
|
+
if (options.client) this.clientPromise = Promise.resolve(options.client);
|
|
836
|
+
}
|
|
837
|
+
connect(transport) {
|
|
838
|
+
let server = null;
|
|
839
|
+
let ready = false;
|
|
840
|
+
const buffer = [];
|
|
841
|
+
const liveSubs = new SubscriptionStore();
|
|
842
|
+
const reply = hostResponder(transport);
|
|
843
|
+
const ensureServer = async (construct) => {
|
|
844
|
+
if (server) return;
|
|
845
|
+
server = new ReactorHostServer(await this.resolveClient(construct), transport);
|
|
846
|
+
};
|
|
847
|
+
const drainBuffer = async () => {
|
|
848
|
+
while (buffer.length > 0) {
|
|
849
|
+
const message = buffer.shift();
|
|
850
|
+
await server.handleMessage(message);
|
|
851
|
+
}
|
|
852
|
+
ready = true;
|
|
853
|
+
};
|
|
854
|
+
const detach = transport.onMessage((message) => {
|
|
855
|
+
const msg = message;
|
|
856
|
+
if (msg.k === "ping") {
|
|
857
|
+
transport.post({
|
|
858
|
+
k: "pong",
|
|
859
|
+
id: msg.id,
|
|
860
|
+
ownerId: this.ownerId,
|
|
861
|
+
bootedAtMs: this.bootedAtMs
|
|
862
|
+
});
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (this.migrationState?.status === "migrating" && isDataMessage(msg)) {
|
|
866
|
+
reply.errForKind(msg, /* @__PURE__ */ new Error("migration in progress"));
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
if (msg.k === "hello") {
|
|
870
|
+
this.handleHello(msg, transport, reply, ensureServer, drainBuffer);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (msg.k === "register-packages") {
|
|
874
|
+
this.handleRegister(msg, reply);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (msg.k === "unregister-packages") {
|
|
878
|
+
this.handleUnregister(msg, reply);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (msg.k === "identity") {
|
|
882
|
+
this.options.onIdentity?.(msg.user);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (msg.k === "sync-op") {
|
|
886
|
+
this.handleOp(msg, this.options.onSyncOp, "sync", reply);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (msg.k === "db-op") {
|
|
890
|
+
this.handleOp(msg, this.options.onDbOp, "db", reply);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if (msg.k === "inspector-op") {
|
|
894
|
+
this.handleOp(msg, this.options.onInspectorOp, "inspector", reply);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (msg.k === "sub-live") {
|
|
898
|
+
this.handleLiveSubscribe(msg, transport, reply, liveSubs);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (msg.k === "unsub-live") {
|
|
902
|
+
liveSubs.end(msg.id);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (msg.k === "admin") {
|
|
906
|
+
this.handleAdmin(msg, reply);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (ready && server) server.handleMessage(msg);
|
|
910
|
+
else buffer.push(msg);
|
|
911
|
+
});
|
|
912
|
+
this.clients.add(transport);
|
|
913
|
+
if (this.migrationState) transport.post({
|
|
914
|
+
k: "migration",
|
|
915
|
+
state: this.migrationState
|
|
916
|
+
});
|
|
917
|
+
if (this.options.client) ensureServer().then(drainBuffer).catch((error) => {
|
|
918
|
+
console.error("ReactorHost buffer drain failed", error);
|
|
919
|
+
});
|
|
920
|
+
const dispose = () => {
|
|
921
|
+
server?.stop();
|
|
922
|
+
detach();
|
|
923
|
+
liveSubs.drain();
|
|
924
|
+
this.clients.delete(transport);
|
|
925
|
+
this.disposers.delete(dispose);
|
|
926
|
+
};
|
|
927
|
+
this.disposers.add(dispose);
|
|
928
|
+
return dispose;
|
|
929
|
+
}
|
|
930
|
+
connectPort(port) {
|
|
931
|
+
return this.connect(createPortTransport(port));
|
|
932
|
+
}
|
|
933
|
+
broadcastBusEvent(eventType, event) {
|
|
934
|
+
for (const transport of this.clients) transport.post({
|
|
935
|
+
k: "bus-event",
|
|
936
|
+
eventType,
|
|
937
|
+
event
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
broadcastReload(reason, workerGen) {
|
|
941
|
+
for (const transport of this.clients) transport.post({
|
|
942
|
+
k: "reload",
|
|
943
|
+
reason,
|
|
944
|
+
workerGen
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
setMigrationState(state) {
|
|
948
|
+
this.migrationState = state;
|
|
949
|
+
for (const transport of this.clients) transport.post({
|
|
950
|
+
k: "migration",
|
|
951
|
+
state
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
get connectionCount() {
|
|
955
|
+
return this.disposers.size;
|
|
956
|
+
}
|
|
957
|
+
handleAdmin(message, reply) {
|
|
958
|
+
if (message.method === "restart") {
|
|
959
|
+
this.options.onAdminRestart?.();
|
|
960
|
+
reply.ok(message.id);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (message.method === "clearStorage") {
|
|
964
|
+
this.handleAdminAsync(this.options.onAdminClearStorage, message, reply);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (message.method === "migrate") {
|
|
968
|
+
this.handleAdminAsync(this.options.onAdminMigrate, message, reply);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const info = {
|
|
972
|
+
namespace: this.options.namespace ?? "",
|
|
973
|
+
ownerId: this.ownerId,
|
|
974
|
+
bootedAtMs: this.bootedAtMs,
|
|
975
|
+
connectedClients: this.connectionCount,
|
|
976
|
+
appBuildId: this.baseline?.appBuildId ?? this.options.appBuildId ?? "unknown",
|
|
977
|
+
rpcProtocolVersion: this.baseline?.rpcProtocolVersion ?? 2
|
|
978
|
+
};
|
|
979
|
+
reply.ok(message.id, info);
|
|
980
|
+
}
|
|
981
|
+
async handleAdminAsync(handler, message, reply) {
|
|
982
|
+
await reply.run(message.id, async () => {
|
|
983
|
+
await handler?.();
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
resolveClient(construct) {
|
|
987
|
+
if (!this.clientPromise) {
|
|
988
|
+
const build = this.options.build;
|
|
989
|
+
if (!build) return Promise.reject(/* @__PURE__ */ new Error("ReactorHost has no client or builder"));
|
|
990
|
+
const pending = build(construct);
|
|
991
|
+
this.clientPromise = pending;
|
|
992
|
+
pending.catch(() => {
|
|
993
|
+
if (this.clientPromise === pending) this.clientPromise = null;
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
return this.clientPromise;
|
|
997
|
+
}
|
|
998
|
+
async awaitClientReady() {
|
|
999
|
+
if (this.clientPromise) await this.clientPromise;
|
|
1000
|
+
}
|
|
1001
|
+
requireHandler(handler, message, reply, label) {
|
|
1002
|
+
if (handler) return true;
|
|
1003
|
+
reply.errForKind(message, /* @__PURE__ */ new Error(`ReactorHost has no ${label} handler`));
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
async handleHello(message, transport, reply, ensureServer, drainBuffer) {
|
|
1007
|
+
if (this.baseline) {
|
|
1008
|
+
if (!versionsCompatible(this.baseline, message.version)) {
|
|
1009
|
+
transport.post({
|
|
1010
|
+
k: "reload",
|
|
1011
|
+
reason: "reactor version mismatch",
|
|
1012
|
+
workerGen: workerGenForVersion(message.version)
|
|
1013
|
+
});
|
|
1014
|
+
reply.ok(message.id, { ok: false });
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
} else this.baseline = message.version;
|
|
1018
|
+
await reply.run(message.id, async () => {
|
|
1019
|
+
await ensureServer(message.construct);
|
|
1020
|
+
if (message.packages && message.packages.length > 0) await this.options.registerPackages?.(message.packages);
|
|
1021
|
+
await drainBuffer();
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
async handleRegister(message, reply) {
|
|
1025
|
+
await reply.run(message.id, async () => {
|
|
1026
|
+
await this.options.registerPackages?.(message.specs);
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
async handleUnregister(message, reply) {
|
|
1030
|
+
await reply.run(message.id, async () => {
|
|
1031
|
+
await this.options.unregisterPackages?.(message.names);
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
async handleOp(message, handler, label, reply) {
|
|
1035
|
+
if (!this.requireHandler(handler, message, reply, label)) return;
|
|
1036
|
+
await reply.run(message.id, async () => {
|
|
1037
|
+
await this.awaitClientReady();
|
|
1038
|
+
return handler(message.method, message.args);
|
|
1039
|
+
}, (value) => value);
|
|
1040
|
+
}
|
|
1041
|
+
async handleLiveSubscribe(message, transport, reply, liveSubs) {
|
|
1042
|
+
const handler = this.options.onLiveQuery;
|
|
1043
|
+
if (!this.requireHandler(handler, message, reply, "live-query")) return;
|
|
1044
|
+
const placeholder = () => void 0;
|
|
1045
|
+
liveSubs.set(message.id, placeholder);
|
|
1046
|
+
try {
|
|
1047
|
+
await this.awaitClientReady();
|
|
1048
|
+
const unsubscribe = await handler(message.sql, message.params, (results) => {
|
|
1049
|
+
transport.post({
|
|
1050
|
+
k: "event-live",
|
|
1051
|
+
id: message.id,
|
|
1052
|
+
results
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
1055
|
+
if (liveSubs.get(message.id) !== placeholder) {
|
|
1056
|
+
unsubscribe();
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
liveSubs.set(message.id, unsubscribe);
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
if (liveSubs.get(message.id) === placeholder) liveSubs.delete(message.id);
|
|
1062
|
+
reply.errForKind(message, error);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region src/rpc/worker-package-loader.ts
|
|
1068
|
+
function packageName(spec) {
|
|
1069
|
+
const at = spec.lastIndexOf("@");
|
|
1070
|
+
return at > 0 ? spec.slice(0, at) : spec;
|
|
1071
|
+
}
|
|
1072
|
+
function isDocumentModelModule(value) {
|
|
1073
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1074
|
+
const candidate = value;
|
|
1075
|
+
return typeof candidate.reducer === "function" && typeof candidate.documentModel?.global?.id === "string";
|
|
1076
|
+
}
|
|
1077
|
+
var WorkerPackageLoader = class {
|
|
1078
|
+
cdnUrl;
|
|
1079
|
+
importPackage;
|
|
1080
|
+
resolvePackages;
|
|
1081
|
+
modulesByType = /* @__PURE__ */ new Map();
|
|
1082
|
+
loadedSpecs = /* @__PURE__ */ new Set();
|
|
1083
|
+
failures = [];
|
|
1084
|
+
constructor(options) {
|
|
1085
|
+
this.cdnUrl = options.cdnUrl.replace(/\/$/, "");
|
|
1086
|
+
this.importPackage = options.importPackage;
|
|
1087
|
+
const registryClient = new RegistryClient(options.cdnUrl);
|
|
1088
|
+
this.resolvePackages = options.resolvePackages ?? ((documentType) => registryClient.getPackagesByDocumentType(documentType));
|
|
1089
|
+
}
|
|
1090
|
+
async loadPackages(specs) {
|
|
1091
|
+
await Promise.all([...new Set(specs)].map((spec) => this.loadPackage(spec)));
|
|
1092
|
+
return this.models;
|
|
1093
|
+
}
|
|
1094
|
+
async load(documentType) {
|
|
1095
|
+
const existing = this.modulesByType.get(documentType);
|
|
1096
|
+
if (existing) return existing;
|
|
1097
|
+
const packageNames = await this.resolvePackages(documentType);
|
|
1098
|
+
const failuresBefore = this.failures.length;
|
|
1099
|
+
await Promise.all([...new Set(packageNames)].map((name) => this.loadPackage(name)));
|
|
1100
|
+
const loaded = this.modulesByType.get(documentType);
|
|
1101
|
+
if (loaded) return loaded;
|
|
1102
|
+
throw this.notLoadedError(documentType, packageNames, this.failures.slice(failuresBefore));
|
|
1103
|
+
}
|
|
1104
|
+
get models() {
|
|
1105
|
+
return [...new Set(this.modulesByType.values())];
|
|
1106
|
+
}
|
|
1107
|
+
get loadFailures() {
|
|
1108
|
+
return [...this.failures];
|
|
1109
|
+
}
|
|
1110
|
+
notLoadedError(documentType, packageNames, failures) {
|
|
1111
|
+
if (packageNames.length === 0) return /* @__PURE__ */ new Error(`No package found for document model: ${documentType}`);
|
|
1112
|
+
if (failures.length === 0) return /* @__PURE__ */ new Error(`Imported [${packageNames.join(", ")}] but document model not found: ${documentType}`);
|
|
1113
|
+
const cause = failures.length === 1 ? failures[0].error : new AggregateError(failures.map((failure) => failure.error));
|
|
1114
|
+
return new Error(`Failed to import package(s) [${packageNames.join(", ")}] for document model: ${documentType}`, { cause });
|
|
1115
|
+
}
|
|
1116
|
+
async loadPackage(spec) {
|
|
1117
|
+
if (this.loadedSpecs.has(spec)) return;
|
|
1118
|
+
const name = packageName(spec);
|
|
1119
|
+
const url = `${this.cdnUrl}/${name}/browser/document-models/index.js`;
|
|
1120
|
+
try {
|
|
1121
|
+
const namespace = await this.importPackage(url);
|
|
1122
|
+
for (const value of Object.values(namespace)) if (isDocumentModelModule(value)) this.modulesByType.set(value.documentModel.global.id, value);
|
|
1123
|
+
this.loadedSpecs.add(spec);
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
this.failures.push({
|
|
1126
|
+
name,
|
|
1127
|
+
url,
|
|
1128
|
+
error
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
//#endregion
|
|
1134
|
+
//#region src/rpc/rpc-correlator.ts
|
|
1135
|
+
/**
|
|
1136
|
+
* Request/response correlation shared by every RPC proxy: one pending map,
|
|
1137
|
+
* res/err settlement, and per-request timeout/transform/cleanup.
|
|
1138
|
+
*/
|
|
1139
|
+
var RpcCorrelator = class {
|
|
1140
|
+
counter = 0;
|
|
1141
|
+
pending = /* @__PURE__ */ new Map();
|
|
1142
|
+
constructor(poster, prefix = "r") {
|
|
1143
|
+
this.poster = poster;
|
|
1144
|
+
this.prefix = prefix;
|
|
1145
|
+
}
|
|
1146
|
+
nextId() {
|
|
1147
|
+
return `${this.prefix}${++this.counter}`;
|
|
1148
|
+
}
|
|
1149
|
+
/** Settle the pending request a res/err refers to; a missing id is a no-op. */
|
|
1150
|
+
handleMessage(message) {
|
|
1151
|
+
if (message.k !== "res" && message.k !== "err") return;
|
|
1152
|
+
const entry = this.pending.get(message.id);
|
|
1153
|
+
if (!entry) return;
|
|
1154
|
+
this.pending.delete(message.id);
|
|
1155
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
1156
|
+
entry.cleanup?.();
|
|
1157
|
+
if (message.k === "res") entry.resolve(entry.transform ? entry.transform(message.value) : message.value);
|
|
1158
|
+
else entry.reject(fromErrorInfo(message.error));
|
|
1159
|
+
}
|
|
1160
|
+
/** Send a request, resolving on its res/err. `build` stamps the generated id. */
|
|
1161
|
+
request(build, options = {}) {
|
|
1162
|
+
const id = this.nextId();
|
|
1163
|
+
const message = build(id);
|
|
1164
|
+
const method = message.method;
|
|
1165
|
+
const label = typeof method === "string" ? `${message.k} "${method}"` : message.k;
|
|
1166
|
+
const promise = new Promise((resolve, reject) => {
|
|
1167
|
+
let timer;
|
|
1168
|
+
if (options.timeoutMs !== void 0) {
|
|
1169
|
+
const timeoutMs = options.timeoutMs;
|
|
1170
|
+
timer = setTimeout(() => {
|
|
1171
|
+
const entry = this.pending.get(id);
|
|
1172
|
+
if (entry) {
|
|
1173
|
+
this.pending.delete(id);
|
|
1174
|
+
entry.cleanup?.();
|
|
1175
|
+
reject(/* @__PURE__ */ new Error(`Reactor worker did not respond to ${label} within ${timeoutMs}ms; the worker may have failed to load`));
|
|
1176
|
+
}
|
|
1177
|
+
}, timeoutMs);
|
|
1178
|
+
}
|
|
1179
|
+
this.pending.set(id, {
|
|
1180
|
+
resolve,
|
|
1181
|
+
reject,
|
|
1182
|
+
timer,
|
|
1183
|
+
transform: options.transform
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
this.poster.post(message);
|
|
1187
|
+
const cleanup = options.setup?.(id);
|
|
1188
|
+
if (cleanup) {
|
|
1189
|
+
const entry = this.pending.get(id);
|
|
1190
|
+
if (entry) entry.cleanup = cleanup;
|
|
1191
|
+
else cleanup();
|
|
1192
|
+
}
|
|
1193
|
+
return promise;
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region src/rpc/message-router.ts
|
|
1198
|
+
/**
|
|
1199
|
+
* Owns the single transport.onMessage and dispatches each message to the one
|
|
1200
|
+
* consumer that owns its kind. Composes the shared RpcCorrelator (res/err) and
|
|
1201
|
+
* exposes request/nextId; attach/detach swap the transport for reconnect.
|
|
1202
|
+
*/
|
|
1203
|
+
var MessageRouter = class {
|
|
1204
|
+
handlers = /* @__PURE__ */ new Map();
|
|
1205
|
+
correlator;
|
|
1206
|
+
transport = null;
|
|
1207
|
+
detachTransport = () => {};
|
|
1208
|
+
constructor(prefix = "r") {
|
|
1209
|
+
this.correlator = new RpcCorrelator(this, prefix);
|
|
1210
|
+
const settle = (message) => this.correlator.handleMessage(message);
|
|
1211
|
+
this.handlers.set("res", settle);
|
|
1212
|
+
this.handlers.set("err", settle);
|
|
1213
|
+
}
|
|
1214
|
+
attach(transport) {
|
|
1215
|
+
this.detachTransport();
|
|
1216
|
+
this.transport = transport;
|
|
1217
|
+
this.detachTransport = transport.onMessage((message) => {
|
|
1218
|
+
const msg = message;
|
|
1219
|
+
this.handlers.get(msg.k)?.(msg);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
detach() {
|
|
1223
|
+
this.detachTransport();
|
|
1224
|
+
this.detachTransport = () => {};
|
|
1225
|
+
this.transport = null;
|
|
1226
|
+
}
|
|
1227
|
+
post(message) {
|
|
1228
|
+
if (!this.transport) throw new Error("MessageRouter.post called before attach");
|
|
1229
|
+
this.transport.post(message);
|
|
1230
|
+
}
|
|
1231
|
+
/** Register the sole owner of a message kind; throws on a duplicate. */
|
|
1232
|
+
on(kind, handler) {
|
|
1233
|
+
if (this.handlers.has(kind)) throw new Error(`MessageRouter already has a handler for "${kind}"`);
|
|
1234
|
+
const route = handler;
|
|
1235
|
+
this.handlers.set(kind, route);
|
|
1236
|
+
return () => {
|
|
1237
|
+
if (this.handlers.get(kind) === route) this.handlers.delete(kind);
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
request(build, options) {
|
|
1241
|
+
return this.correlator.request(build, options);
|
|
1242
|
+
}
|
|
1243
|
+
nextId() {
|
|
1244
|
+
return this.correlator.nextId();
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
//#endregion
|
|
1248
|
+
export { FORWARDED_BUS_EVENT_TYPES, FORWARDED_EVENT_TYPES, KeyedListeners, Listeners, MessageRouter, RPC_DEFAULT_TIMEOUT_MS, RPC_PROTOCOL_VERSION, ReactorEventBusProxy, ReactorHost, ReactorHostServer, RpcCorrelator, SYNC_STATUS_CHANGED_EVENT, SubscriptionStore, SyncManagerProxy, WorkerPackageLoader, connectReactorClient, createCorrelatedSubscriptions, createInspectorProxy, createLiveQueryProxy, createPortTransport, createReactorClientProxy, createReactorEventBusProxy, createRelationalPgliteProxy, createSyncManagerProxy, createWorkerAdminClient, fromErrorInfo, hostResponder, opChannel, postReactorIdentity, toErrorInfo, toVoid };
|
|
1249
|
+
|
|
1250
|
+
//# sourceMappingURL=index.js.map
|