cry-synced-db-client 0.1.178 → 0.1.180
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,114 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 0.1.180 (2026-05-14)
|
|
4
|
+
|
|
5
|
+
### `SyncedDb.callWorker` — invoke ebus2 worker services over the WS
|
|
6
|
+
|
|
7
|
+
New API for calling any registered cry-ebus2 worker directly from the
|
|
8
|
+
client, riding the same WebSocket connection the notifier already uses
|
|
9
|
+
for pub/sub. Built on ebus-proxy 2.0's WS `request` / `response`
|
|
10
|
+
correlation frames, so there's no extra TCP/TLS handshake and no HTTP
|
|
11
|
+
overhead — the worker reply arrives on the open socket.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// echo round-trip (any msgpack-serializable value)
|
|
15
|
+
const echoed = await syncedDb.callWorker<string>("echo", "hello");
|
|
16
|
+
|
|
17
|
+
// pass an object payload, type the reply
|
|
18
|
+
const result = await syncedDb.callWorker<{ ok: boolean; jobId: string }>(
|
|
19
|
+
"billing",
|
|
20
|
+
{ customerId, amount: 1990 },
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// per-call timeout (default 5000 ms) + caller AbortSignal
|
|
24
|
+
const ctrl = new AbortController();
|
|
25
|
+
setTimeout(() => ctrl.abort(), 200);
|
|
26
|
+
const fast = await syncedDb.callWorker("priority-queue", null, {
|
|
27
|
+
timeoutMs: 200,
|
|
28
|
+
signal: ctrl.signal,
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
API contract:
|
|
33
|
+
|
|
34
|
+
| Argument | Type | Notes |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `service` | `string` | Worker service name registered on the broker. |
|
|
37
|
+
| `payload` | `unknown?` | Anything `msgpack+structuredClone` can serialize. BSON `ObjectId` / `Timestamp` preprocessed automatically. Omit (or pass `undefined`) to call without a payload. |
|
|
38
|
+
| `options.timeoutMs` | `number?` | Per-call timeout; also forwarded to the proxy. Default `5000`. |
|
|
39
|
+
| `options.signal` | `AbortSignal?` | External cancellation. |
|
|
40
|
+
| Returns | `Promise<T>` | Worker's reply, typed as caller-asserted `T`. |
|
|
41
|
+
|
|
42
|
+
Error semantics:
|
|
43
|
+
|
|
44
|
+
- WebSocket not OPEN → synchronous reject (no implicit reconnect).
|
|
45
|
+
- Worker error / proxy error → rejects with the proxy's error message.
|
|
46
|
+
- Per-call `timeoutMs` elapses → rejects with timeout error and frees
|
|
47
|
+
the pending entry (the proxy may still complete the call server-side;
|
|
48
|
+
client-side state is freed regardless).
|
|
49
|
+
- WebSocket closes mid-flight → all in-flight calls reject with
|
|
50
|
+
"WebSocket closed before reply".
|
|
51
|
+
- Caller's `signal` aborts → rejects with "aborted"; pending entry is
|
|
52
|
+
freed and the abort listener is removed.
|
|
53
|
+
|
|
54
|
+
Multiple concurrent calls are safe — each request uses a unique
|
|
55
|
+
correlation id and the response/error dispatch keys off it. The
|
|
56
|
+
existing pub/sub channel and keepalive ping/pong are unaffected.
|
|
57
|
+
|
|
58
|
+
Implementation:
|
|
59
|
+
|
|
60
|
+
- `I_ServerUpdateNotifier.callWorker?` — optional interface method, so
|
|
61
|
+
REST-only / polling notifier alternatives can omit it.
|
|
62
|
+
- `Ebus2ProxyServerUpdateNotifier.callWorker` — sends `{type:"request",
|
|
63
|
+
service, payload, timeout, id}` and registers the resolver in a
|
|
64
|
+
`_pendingRequests` Map keyed by id (same pattern as `_pendingRttPings`
|
|
65
|
+
for `measureWsRtt`).
|
|
66
|
+
- `handleMessage` adds `case "response"` to dispatch replies and routes
|
|
67
|
+
correlated `case "error"` to the matching pending promise instead of
|
|
68
|
+
module-level `console.error`.
|
|
69
|
+
- `handleClose` rejects every in-flight call with a clear "WebSocket
|
|
70
|
+
closed before reply" message — the socket they were sent over is
|
|
71
|
+
gone; the client can't observe replies on a different connection.
|
|
72
|
+
- `dispose` is a defensive backstop (`handleClose` normally drains
|
|
73
|
+
first) and rejects any stragglers with "notifier disposed".
|
|
74
|
+
- AbortSignal listeners are cleaned up on every exit path (resolve,
|
|
75
|
+
reject, timeout, dispose) so callers don't leak listeners.
|
|
76
|
+
|
|
77
|
+
Peer-dep bump: `cry-ebus-proxy` now `^2.0.0` (needed for
|
|
78
|
+
`WsRequestMessage` / `WsResponseMessage` types and the proxy-side
|
|
79
|
+
request/response handling).
|
|
80
|
+
|
|
81
|
+
Tests: 8 new mock-based cases in `test/callWorker.test.ts` covering
|
|
82
|
+
delegation, generic return, payload+options forwarding, undefined
|
|
83
|
+
payload preservation, error propagation, missing-notifier guard,
|
|
84
|
+
concurrent independence. 736 bun + 18 vitest pass (was 728 + 18 — 8 new
|
|
85
|
+
bun cases).
|
|
86
|
+
|
|
87
|
+
## 0.1.179 (2026-05-13)
|
|
88
|
+
|
|
89
|
+
### Non-error `console.*` calls now pass numbers as separate arguments
|
|
90
|
+
|
|
91
|
+
The console-reporting skill was updated to distinguish two flavors of
|
|
92
|
+
Rule 3:
|
|
93
|
+
|
|
94
|
+
- For `console.error` (and error-reporting `console.warn`): interpolate
|
|
95
|
+
critical info into the first-arg string. Grep-friendly single line.
|
|
96
|
+
- For progress / count / duration style writes: separate strings and
|
|
97
|
+
numbers into separate arguments. Dev console renders numbers natively
|
|
98
|
+
and the runtime avoids eager string coercion.
|
|
99
|
+
|
|
100
|
+
Five sites updated:
|
|
101
|
+
|
|
102
|
+
- `RestProxy` timing logs (`operation: 12.34 ms (total: 567.89 ms, count: 42)`)
|
|
103
|
+
in both `restCall` and `findNewerManyStream`.
|
|
104
|
+
- `SyncEngine.uploadDirtyItems` "X dirty entries but 0 resolvable" warn.
|
|
105
|
+
- `SyncEngine.uploadDirtyItems` "X items sent but not acknowledged" warn.
|
|
106
|
+
- `SyncedDb.init` "X dirty record(s) had no matching Dexie main row" warn.
|
|
107
|
+
|
|
108
|
+
`console.error` sites (including the `typeof` debug log in
|
|
109
|
+
`PendingChangesManager`) keep their fully-interpolated form — they
|
|
110
|
+
report errors and grep-friendliness wins there.
|
|
111
|
+
|
|
3
112
|
## 0.1.178 (2026-05-13)
|
|
4
113
|
|
|
5
114
|
### Error reason interpolated into every `console.*` tag line
|
package/dist/index.js
CHANGED
|
@@ -3298,7 +3298,9 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3298
3298
|
}
|
|
3299
3299
|
if (updates.length === 0) {
|
|
3300
3300
|
console.warn(
|
|
3301
|
-
`[SyncEngine] uploadDirtyItems: ${collectionName} has
|
|
3301
|
+
`[SyncEngine] uploadDirtyItems: ${collectionName} has`,
|
|
3302
|
+
dirtyChanges.length,
|
|
3303
|
+
"dirty entries but 0 resolvable items",
|
|
3302
3304
|
skipped
|
|
3303
3305
|
);
|
|
3304
3306
|
if (this.callbacks.onUploadSkip) {
|
|
@@ -3571,7 +3573,9 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3571
3573
|
}
|
|
3572
3574
|
if (unacked.length > 0) {
|
|
3573
3575
|
console.warn(
|
|
3574
|
-
`[SyncEngine] uploadDirtyItems: ${collection}
|
|
3576
|
+
`[SyncEngine] uploadDirtyItems: ${collection}:`,
|
|
3577
|
+
unacked.length,
|
|
3578
|
+
"items sent but not acknowledged:",
|
|
3575
3579
|
unacked
|
|
3576
3580
|
);
|
|
3577
3581
|
}
|
|
@@ -4678,6 +4682,24 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4678
4682
|
}
|
|
4679
4683
|
return fn.call(this.serverUpdateNotifier, timeoutMs);
|
|
4680
4684
|
}
|
|
4685
|
+
/**
|
|
4686
|
+
* Call an arbitrary worker service via the configured server-update
|
|
4687
|
+
* notifier (typically over its WebSocket). Delegates to
|
|
4688
|
+
* `serverUpdateNotifier.callWorker`. Throws if no notifier is
|
|
4689
|
+
* configured or the notifier doesn't support worker invocation.
|
|
4690
|
+
*
|
|
4691
|
+
* @see I_ServerUpdateNotifier.callWorker
|
|
4692
|
+
*/
|
|
4693
|
+
async callWorker(service, payload, options) {
|
|
4694
|
+
var _a;
|
|
4695
|
+
const fn = (_a = this.serverUpdateNotifier) == null ? void 0 : _a.callWorker;
|
|
4696
|
+
if (!fn) {
|
|
4697
|
+
throw new Error(
|
|
4698
|
+
"[SyncedDb] callWorker: no serverUpdateNotifier or notifier does not support callWorker"
|
|
4699
|
+
);
|
|
4700
|
+
}
|
|
4701
|
+
return fn.call(this.serverUpdateNotifier, service, payload, options);
|
|
4702
|
+
}
|
|
4681
4703
|
/**
|
|
4682
4704
|
* Register a collection for sync at runtime. See `I_SyncedDb.addCollectionToSync`.
|
|
4683
4705
|
*/
|
|
@@ -6409,7 +6431,9 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6409
6431
|
}
|
|
6410
6432
|
if (orphanCount > 0) {
|
|
6411
6433
|
console.warn(
|
|
6412
|
-
`[SyncedDb] init(${name})
|
|
6434
|
+
`[SyncedDb] init(${name}):`,
|
|
6435
|
+
orphanCount,
|
|
6436
|
+
"dirty record(s) had no matching Dexie main row \u2014 included in in-mem pending next sync."
|
|
6413
6437
|
);
|
|
6414
6438
|
}
|
|
6415
6439
|
allItems.length = 0;
|
|
@@ -9385,7 +9409,13 @@ var RestProxy = class {
|
|
|
9385
9409
|
this._requestCount++;
|
|
9386
9410
|
if (this.timeRequestsPrint) {
|
|
9387
9411
|
console.log(
|
|
9388
|
-
`[RestProxy] ${operation}
|
|
9412
|
+
`[RestProxy] ${operation}:`,
|
|
9413
|
+
elapsed.toFixed(2),
|
|
9414
|
+
"ms (total:",
|
|
9415
|
+
this._totalRequestMs.toFixed(2),
|
|
9416
|
+
"ms, count:",
|
|
9417
|
+
this._requestCount,
|
|
9418
|
+
")"
|
|
9389
9419
|
);
|
|
9390
9420
|
}
|
|
9391
9421
|
}
|
|
@@ -9517,7 +9547,13 @@ var RestProxy = class {
|
|
|
9517
9547
|
this._requestCount++;
|
|
9518
9548
|
if (this.timeRequestsPrint) {
|
|
9519
9549
|
console.log(
|
|
9520
|
-
|
|
9550
|
+
"[RestProxy] findNewerManyStream:",
|
|
9551
|
+
elapsed.toFixed(2),
|
|
9552
|
+
"ms (total:",
|
|
9553
|
+
this._totalRequestMs.toFixed(2),
|
|
9554
|
+
"ms, count:",
|
|
9555
|
+
this._requestCount,
|
|
9556
|
+
")"
|
|
9521
9557
|
);
|
|
9522
9558
|
}
|
|
9523
9559
|
}
|
|
@@ -9695,6 +9731,13 @@ var Ebus2ProxyServerUpdateNotifier = class {
|
|
|
9695
9731
|
* after if any are still pending).
|
|
9696
9732
|
*/
|
|
9697
9733
|
this._pendingRttPings = /* @__PURE__ */ new Map();
|
|
9734
|
+
/**
|
|
9735
|
+
* Pending `callWorker` promises keyed by request id. The matching
|
|
9736
|
+
* `response` (or correlated `error`) frame resolves/rejects them.
|
|
9737
|
+
* Cleared on disconnect — pending callers are rejected with a
|
|
9738
|
+
* "WebSocket closed" error and the per-call timeout is freed.
|
|
9739
|
+
*/
|
|
9740
|
+
this._pendingRequests = /* @__PURE__ */ new Map();
|
|
9698
9741
|
var _a, _b, _c, _d, _e;
|
|
9699
9742
|
this.endpoint = config.wsUrl;
|
|
9700
9743
|
this.wsUrl = config.wsUrl;
|
|
@@ -9751,6 +9794,18 @@ var Ebus2ProxyServerUpdateNotifier = class {
|
|
|
9751
9794
|
this.onWsDisconnectCallbacks.length = 0;
|
|
9752
9795
|
this.onWsReconnectCallbacks.length = 0;
|
|
9753
9796
|
this._pendingRttPings.clear();
|
|
9797
|
+
for (const [, pending] of this._pendingRequests) {
|
|
9798
|
+
clearTimeout(pending.timer);
|
|
9799
|
+
if (pending.abortListener && pending.signal) {
|
|
9800
|
+
pending.signal.removeEventListener("abort", pending.abortListener);
|
|
9801
|
+
}
|
|
9802
|
+
pending.reject(
|
|
9803
|
+
new Error(
|
|
9804
|
+
`[Ebus2ProxyNotifier] callWorker(${pending.service}): notifier disposed`
|
|
9805
|
+
)
|
|
9806
|
+
);
|
|
9807
|
+
}
|
|
9808
|
+
this._pendingRequests.clear();
|
|
9754
9809
|
}
|
|
9755
9810
|
isConnected() {
|
|
9756
9811
|
return this.connected && !this.forcedOffline;
|
|
@@ -9832,6 +9887,111 @@ var Ebus2ProxyServerUpdateNotifier = class {
|
|
|
9832
9887
|
}
|
|
9833
9888
|
return performance.now() - t0;
|
|
9834
9889
|
}
|
|
9890
|
+
/**
|
|
9891
|
+
* Call an arbitrary ebus2 worker service over the existing
|
|
9892
|
+
* WebSocket and return its msgpack-decoded reply.
|
|
9893
|
+
*
|
|
9894
|
+
* Uses the WS `request` / `response` correlation introduced in
|
|
9895
|
+
* ebus-proxy 2.0 — the whole frame is msgpack, so the payload is
|
|
9896
|
+
* sent as a raw value (no base64, no JSON envelope). Lower latency
|
|
9897
|
+
* than HTTP (no TCP/TLS handshake, no per-call connection setup)
|
|
9898
|
+
* and reuses the same auth + connection that the pub/sub channel
|
|
9899
|
+
* is already using.
|
|
9900
|
+
*
|
|
9901
|
+
* Errors:
|
|
9902
|
+
* - WebSocket not OPEN → throws synchronously (rejects).
|
|
9903
|
+
* - Worker error / proxy error → rejects with the proxy's `error`
|
|
9904
|
+
* frame message.
|
|
9905
|
+
* - Per-call `timeoutMs` elapses → rejects with timeout error and
|
|
9906
|
+
* removes the pending entry. The proxy may still process the
|
|
9907
|
+
* call server-side; client-side state is freed regardless.
|
|
9908
|
+
* - WebSocket closes mid-flight → rejects with "WebSocket closed
|
|
9909
|
+
* before reply".
|
|
9910
|
+
* - Caller `options.signal` aborts → rejects with "aborted".
|
|
9911
|
+
*
|
|
9912
|
+
* Multiple concurrent calls are safe — each request uses a unique
|
|
9913
|
+
* correlation id and the `response`/`error` dispatch keys off it.
|
|
9914
|
+
*
|
|
9915
|
+
* @param service Worker service name registered on cry-ebus2.
|
|
9916
|
+
* @param payload Anything msgpack+structuredClone can serialize.
|
|
9917
|
+
* BSON ObjectId/Timestamp are preprocessed like all other WS
|
|
9918
|
+
* frames. Pass `undefined` to call without a payload.
|
|
9919
|
+
* @param options.timeoutMs Per-call timeout (default 5000). Also
|
|
9920
|
+
* forwarded as the proxy-side `timeout` field.
|
|
9921
|
+
* @param options.signal External AbortSignal for cancellation.
|
|
9922
|
+
* @returns Worker's reply, msgpack-decoded as T.
|
|
9923
|
+
*/
|
|
9924
|
+
async callWorker(service, payload, options) {
|
|
9925
|
+
var _a;
|
|
9926
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
9927
|
+
throw new Error(
|
|
9928
|
+
`[Ebus2ProxyNotifier] callWorker(${service}): WebSocket not OPEN`
|
|
9929
|
+
);
|
|
9930
|
+
}
|
|
9931
|
+
const timeoutMs = (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 5e3;
|
|
9932
|
+
const id = `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
9933
|
+
return new Promise((resolve, reject) => {
|
|
9934
|
+
var _a2;
|
|
9935
|
+
if ((_a2 = options == null ? void 0 : options.signal) == null ? void 0 : _a2.aborted) {
|
|
9936
|
+
reject(new Error(`[Ebus2ProxyNotifier] callWorker(${service}): aborted`));
|
|
9937
|
+
return;
|
|
9938
|
+
}
|
|
9939
|
+
const timer = setTimeout(() => {
|
|
9940
|
+
const pending = this._pendingRequests.get(id);
|
|
9941
|
+
if (!pending) return;
|
|
9942
|
+
this._pendingRequests.delete(id);
|
|
9943
|
+
if (pending.abortListener && pending.signal) {
|
|
9944
|
+
pending.signal.removeEventListener("abort", pending.abortListener);
|
|
9945
|
+
}
|
|
9946
|
+
reject(
|
|
9947
|
+
new Error(
|
|
9948
|
+
`[Ebus2ProxyNotifier] callWorker(${service}): timeout after ${timeoutMs}ms`
|
|
9949
|
+
)
|
|
9950
|
+
);
|
|
9951
|
+
}, timeoutMs);
|
|
9952
|
+
const abortListener = (options == null ? void 0 : options.signal) ? () => {
|
|
9953
|
+
const pending = this._pendingRequests.get(id);
|
|
9954
|
+
if (!pending) return;
|
|
9955
|
+
clearTimeout(pending.timer);
|
|
9956
|
+
this._pendingRequests.delete(id);
|
|
9957
|
+
reject(
|
|
9958
|
+
new Error(`[Ebus2ProxyNotifier] callWorker(${service}): aborted`)
|
|
9959
|
+
);
|
|
9960
|
+
} : void 0;
|
|
9961
|
+
if (abortListener && options.signal) {
|
|
9962
|
+
options.signal.addEventListener("abort", abortListener, { once: true });
|
|
9963
|
+
}
|
|
9964
|
+
this._pendingRequests.set(id, {
|
|
9965
|
+
resolve,
|
|
9966
|
+
reject,
|
|
9967
|
+
timer,
|
|
9968
|
+
service,
|
|
9969
|
+
abortListener,
|
|
9970
|
+
signal: options == null ? void 0 : options.signal
|
|
9971
|
+
});
|
|
9972
|
+
const req = {
|
|
9973
|
+
type: "request",
|
|
9974
|
+
service,
|
|
9975
|
+
payload: payload !== void 0 ? preprocessForPack2(payload) : void 0,
|
|
9976
|
+
timeout: timeoutMs,
|
|
9977
|
+
id
|
|
9978
|
+
};
|
|
9979
|
+
try {
|
|
9980
|
+
this.ws.send(packr2.pack(req));
|
|
9981
|
+
} catch (err) {
|
|
9982
|
+
clearTimeout(timer);
|
|
9983
|
+
this._pendingRequests.delete(id);
|
|
9984
|
+
if (abortListener && options.signal) {
|
|
9985
|
+
options.signal.removeEventListener("abort", abortListener);
|
|
9986
|
+
}
|
|
9987
|
+
reject(
|
|
9988
|
+
new Error(
|
|
9989
|
+
`[Ebus2ProxyNotifier] callWorker(${service}): send failed: ${err}`
|
|
9990
|
+
)
|
|
9991
|
+
);
|
|
9992
|
+
}
|
|
9993
|
+
});
|
|
9994
|
+
}
|
|
9835
9995
|
/**
|
|
9836
9996
|
* Set connection lifecycle callbacks.
|
|
9837
9997
|
* These are merged with any callbacks provided in the constructor config.
|
|
@@ -9943,6 +10103,20 @@ var Ebus2ProxyServerUpdateNotifier = class {
|
|
|
9943
10103
|
this.connected = false;
|
|
9944
10104
|
this.subscribedChannels.clear();
|
|
9945
10105
|
this.clearTimers();
|
|
10106
|
+
if (this._pendingRequests.size > 0) {
|
|
10107
|
+
for (const [, pending] of this._pendingRequests) {
|
|
10108
|
+
clearTimeout(pending.timer);
|
|
10109
|
+
if (pending.abortListener && pending.signal) {
|
|
10110
|
+
pending.signal.removeEventListener("abort", pending.abortListener);
|
|
10111
|
+
}
|
|
10112
|
+
pending.reject(
|
|
10113
|
+
new Error(
|
|
10114
|
+
`[Ebus2ProxyNotifier] callWorker(${pending.service}): WebSocket closed before reply`
|
|
10115
|
+
)
|
|
10116
|
+
);
|
|
10117
|
+
}
|
|
10118
|
+
this._pendingRequests.clear();
|
|
10119
|
+
}
|
|
9946
10120
|
const reason = event.reason || `Code: ${event.code}`;
|
|
9947
10121
|
if (wasConnected) {
|
|
9948
10122
|
for (const callback of this.onWsDisconnectCallbacks) {
|
|
@@ -9986,7 +10160,37 @@ var Ebus2ProxyServerUpdateNotifier = class {
|
|
|
9986
10160
|
}
|
|
9987
10161
|
}
|
|
9988
10162
|
break;
|
|
10163
|
+
case "response": {
|
|
10164
|
+
if (message.id !== void 0) {
|
|
10165
|
+
const pending = this._pendingRequests.get(message.id);
|
|
10166
|
+
if (pending) {
|
|
10167
|
+
clearTimeout(pending.timer);
|
|
10168
|
+
this._pendingRequests.delete(message.id);
|
|
10169
|
+
if (pending.abortListener && pending.signal) {
|
|
10170
|
+
pending.signal.removeEventListener("abort", pending.abortListener);
|
|
10171
|
+
}
|
|
10172
|
+
pending.resolve(message.data);
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
break;
|
|
10176
|
+
}
|
|
9989
10177
|
case "error":
|
|
10178
|
+
if (message.id !== void 0) {
|
|
10179
|
+
const pending = this._pendingRequests.get(message.id);
|
|
10180
|
+
if (pending) {
|
|
10181
|
+
clearTimeout(pending.timer);
|
|
10182
|
+
this._pendingRequests.delete(message.id);
|
|
10183
|
+
if (pending.abortListener && pending.signal) {
|
|
10184
|
+
pending.signal.removeEventListener("abort", pending.abortListener);
|
|
10185
|
+
}
|
|
10186
|
+
pending.reject(
|
|
10187
|
+
new Error(
|
|
10188
|
+
`[Ebus2ProxyNotifier] callWorker(${pending.service}): ${message.error}`
|
|
10189
|
+
)
|
|
10190
|
+
);
|
|
10191
|
+
break;
|
|
10192
|
+
}
|
|
10193
|
+
}
|
|
9990
10194
|
console.error(`[Ebus2ProxyNotifier] WebSocket server error: ${message.error}`, message.error);
|
|
9991
10195
|
break;
|
|
9992
10196
|
}
|
|
@@ -69,6 +69,13 @@ export declare class Ebus2ProxyServerUpdateNotifier implements I_ServerUpdateNot
|
|
|
69
69
|
* after if any are still pending).
|
|
70
70
|
*/
|
|
71
71
|
private _pendingRttPings;
|
|
72
|
+
/**
|
|
73
|
+
* Pending `callWorker` promises keyed by request id. The matching
|
|
74
|
+
* `response` (or correlated `error`) frame resolves/rejects them.
|
|
75
|
+
* Cleared on disconnect — pending callers are rejected with a
|
|
76
|
+
* "WebSocket closed" error and the per-call timeout is freed.
|
|
77
|
+
*/
|
|
78
|
+
private _pendingRequests;
|
|
72
79
|
constructor(config: Ebus2ProxyServerUpdateNotifierConfig);
|
|
73
80
|
subscribe(callback: ServerUpdateCallback): () => void;
|
|
74
81
|
connect(): Promise<void>;
|
|
@@ -110,6 +117,44 @@ export declare class Ebus2ProxyServerUpdateNotifier implements I_ServerUpdateNot
|
|
|
110
117
|
* @returns RTT in milliseconds (full round-trip)
|
|
111
118
|
*/
|
|
112
119
|
measureEndToEndRtt(timeoutMs?: number): Promise<number>;
|
|
120
|
+
/**
|
|
121
|
+
* Call an arbitrary ebus2 worker service over the existing
|
|
122
|
+
* WebSocket and return its msgpack-decoded reply.
|
|
123
|
+
*
|
|
124
|
+
* Uses the WS `request` / `response` correlation introduced in
|
|
125
|
+
* ebus-proxy 2.0 — the whole frame is msgpack, so the payload is
|
|
126
|
+
* sent as a raw value (no base64, no JSON envelope). Lower latency
|
|
127
|
+
* than HTTP (no TCP/TLS handshake, no per-call connection setup)
|
|
128
|
+
* and reuses the same auth + connection that the pub/sub channel
|
|
129
|
+
* is already using.
|
|
130
|
+
*
|
|
131
|
+
* Errors:
|
|
132
|
+
* - WebSocket not OPEN → throws synchronously (rejects).
|
|
133
|
+
* - Worker error / proxy error → rejects with the proxy's `error`
|
|
134
|
+
* frame message.
|
|
135
|
+
* - Per-call `timeoutMs` elapses → rejects with timeout error and
|
|
136
|
+
* removes the pending entry. The proxy may still process the
|
|
137
|
+
* call server-side; client-side state is freed regardless.
|
|
138
|
+
* - WebSocket closes mid-flight → rejects with "WebSocket closed
|
|
139
|
+
* before reply".
|
|
140
|
+
* - Caller `options.signal` aborts → rejects with "aborted".
|
|
141
|
+
*
|
|
142
|
+
* Multiple concurrent calls are safe — each request uses a unique
|
|
143
|
+
* correlation id and the `response`/`error` dispatch keys off it.
|
|
144
|
+
*
|
|
145
|
+
* @param service Worker service name registered on cry-ebus2.
|
|
146
|
+
* @param payload Anything msgpack+structuredClone can serialize.
|
|
147
|
+
* BSON ObjectId/Timestamp are preprocessed like all other WS
|
|
148
|
+
* frames. Pass `undefined` to call without a payload.
|
|
149
|
+
* @param options.timeoutMs Per-call timeout (default 5000). Also
|
|
150
|
+
* forwarded as the proxy-side `timeout` field.
|
|
151
|
+
* @param options.signal External AbortSignal for cancellation.
|
|
152
|
+
* @returns Worker's reply, msgpack-decoded as T.
|
|
153
|
+
*/
|
|
154
|
+
callWorker<T = unknown>(service: string, payload?: unknown, options?: {
|
|
155
|
+
timeoutMs?: number;
|
|
156
|
+
signal?: AbortSignal;
|
|
157
|
+
}): Promise<T>;
|
|
113
158
|
/**
|
|
114
159
|
* Set connection lifecycle callbacks.
|
|
115
160
|
* These are merged with any callbacks provided in the constructor config.
|
|
@@ -78,6 +78,18 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
78
78
|
* notifier is configured or the notifier doesn't support it.
|
|
79
79
|
*/
|
|
80
80
|
measureEndToEndRtt(timeoutMs?: number): Promise<number>;
|
|
81
|
+
/**
|
|
82
|
+
* Call an arbitrary worker service via the configured server-update
|
|
83
|
+
* notifier (typically over its WebSocket). Delegates to
|
|
84
|
+
* `serverUpdateNotifier.callWorker`. Throws if no notifier is
|
|
85
|
+
* configured or the notifier doesn't support worker invocation.
|
|
86
|
+
*
|
|
87
|
+
* @see I_ServerUpdateNotifier.callWorker
|
|
88
|
+
*/
|
|
89
|
+
callWorker<T = unknown>(service: string, payload?: unknown, options?: {
|
|
90
|
+
timeoutMs?: number;
|
|
91
|
+
signal?: AbortSignal;
|
|
92
|
+
}): Promise<T>;
|
|
81
93
|
/**
|
|
82
94
|
* Register a collection for sync at runtime. See `I_SyncedDb.addCollectionToSync`.
|
|
83
95
|
*/
|
|
@@ -83,4 +83,29 @@ export interface I_ServerUpdateNotifier {
|
|
|
83
83
|
* @param timeoutMs Max wait for response (default: 5000)
|
|
84
84
|
*/
|
|
85
85
|
measureEndToEndRtt?(timeoutMs?: number): Promise<number>;
|
|
86
|
+
/**
|
|
87
|
+
* Call an arbitrary worker service via the notifier's transport and
|
|
88
|
+
* return its decoded reply.
|
|
89
|
+
*
|
|
90
|
+
* For WS-capable notifiers, this typically rides the same WebSocket
|
|
91
|
+
* connection as pub/sub (lower latency than spinning up an HTTP
|
|
92
|
+
* fetch) and uses transport-native correlation (`request` /
|
|
93
|
+
* `response` frames). Implementations may fall back to HTTP if the
|
|
94
|
+
* transport doesn't support inline RPC.
|
|
95
|
+
*
|
|
96
|
+
* Optional. Throws if transport is not connected, the worker errors,
|
|
97
|
+
* or the per-call timeout elapses. Implementations that don't
|
|
98
|
+
* support worker invocation may omit this method.
|
|
99
|
+
*
|
|
100
|
+
* @param service Worker service name (e.g. `"echo"`, `"print"`).
|
|
101
|
+
* @param payload Service-specific payload — must be serializable by
|
|
102
|
+
* the transport's codec (msgpack with `structuredClone: true` for
|
|
103
|
+
* the WS implementation).
|
|
104
|
+
* @param options.timeoutMs Per-call timeout in milliseconds.
|
|
105
|
+
* @param options.signal External AbortSignal for cancellation.
|
|
106
|
+
*/
|
|
107
|
+
callWorker?<T = unknown>(service: string, payload?: unknown, options?: {
|
|
108
|
+
timeoutMs?: number;
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
}): Promise<T>;
|
|
86
111
|
}
|
|
@@ -1118,6 +1118,26 @@ export interface I_SyncedDb {
|
|
|
1118
1118
|
* @returns RTT in milliseconds (full round-trip)
|
|
1119
1119
|
*/
|
|
1120
1120
|
measureEndToEndRtt(timeoutMs?: number): Promise<number>;
|
|
1121
|
+
/**
|
|
1122
|
+
* Call an arbitrary ebus2 worker service via the configured server-
|
|
1123
|
+
* update notifier and return the worker's decoded reply. Routes
|
|
1124
|
+
* through the existing WebSocket on the
|
|
1125
|
+
* `Ebus2ProxyServerUpdateNotifier` implementation (ebus-proxy ≥ 2.0
|
|
1126
|
+
* `request` / `response` frames). Throws if no notifier is
|
|
1127
|
+
* configured, the transport is not connected, the worker errors, or
|
|
1128
|
+
* the per-call timeout elapses.
|
|
1129
|
+
*
|
|
1130
|
+
* @param service Worker service name (e.g. "echo", "print").
|
|
1131
|
+
* @param payload Anything msgpack+structuredClone can serialize.
|
|
1132
|
+
* BSON ObjectId/Timestamp preprocessed automatically.
|
|
1133
|
+
* @param options.timeoutMs Per-call timeout (default 5000).
|
|
1134
|
+
* @param options.signal External AbortSignal for cancellation.
|
|
1135
|
+
* @returns Worker's reply typed as T (caller-asserted).
|
|
1136
|
+
*/
|
|
1137
|
+
callWorker<T = unknown>(service: string, payload?: unknown, options?: {
|
|
1138
|
+
timeoutMs?: number;
|
|
1139
|
+
signal?: AbortSignal;
|
|
1140
|
+
}): Promise<T>;
|
|
1121
1141
|
/**
|
|
1122
1142
|
* Get metadata for a single object.
|
|
1123
1143
|
* @param collection Collection name
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cry-synced-db-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.180",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/bun": "latest",
|
|
31
31
|
"bson": "^7.2.0",
|
|
32
|
-
"cry-ebus-proxy": "^
|
|
32
|
+
"cry-ebus-proxy": "^2.0.0",
|
|
33
33
|
"dexie": "^4.4.2",
|
|
34
34
|
"esbuild": "^0.28.0",
|
|
35
35
|
"fake-indexeddb": "^6.2.5",
|
|
36
36
|
"typescript": "^6",
|
|
37
|
-
"vitest": "^4.1.
|
|
37
|
+
"vitest": "^4.1.6"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"cry-helpers": "^2.1.194",
|