@pollen-robotics/reachy-mini-sdk 1.10.0-dev.0.main.b50d97e → 1.10.0-dev.0.main.c8367cd
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/APP_CREATION_GUIDE.md +98 -0
- package/dist/lib/central-listener.d.ts +71 -0
- package/dist/lib/central-listener.d.ts.map +1 -0
- package/dist/lib/central-listener.js +277 -0
- package/dist/lib/central-listener.js.map +1 -0
- package/dist/lib/central-listener.test.d.ts +2 -0
- package/dist/lib/central-listener.test.d.ts.map +1 -0
- package/dist/lib/central-listener.test.js +378 -0
- package/dist/lib/central-listener.test.js.map +1 -0
- package/dist/lib/dc-watchdog.test.d.ts +2 -0
- package/dist/lib/dc-watchdog.test.d.ts.map +1 -0
- package/dist/lib/dc-watchdog.test.js +154 -0
- package/dist/lib/dc-watchdog.test.js.map +1 -0
- package/dist/lib/ensure-awake.test.d.ts +2 -0
- package/dist/lib/ensure-awake.test.d.ts.map +1 -0
- package/dist/lib/ensure-awake.test.js +117 -0
- package/dist/lib/ensure-awake.test.js.map +1 -0
- package/dist/lib/ice-grace.test.d.ts +2 -0
- package/dist/lib/ice-grace.test.d.ts.map +1 -0
- package/dist/lib/ice-grace.test.js +211 -0
- package/dist/lib/ice-grace.test.js.map +1 -0
- package/dist/lib/imu.test.d.ts +2 -0
- package/dist/lib/imu.test.d.ts.map +1 -0
- package/dist/lib/imu.test.js +78 -0
- package/dist/lib/imu.test.js.map +1 -0
- package/dist/lib/logger.d.ts +38 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +81 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/pending-replies.d.ts +127 -0
- package/dist/lib/pending-replies.d.ts.map +1 -0
- package/dist/lib/pending-replies.js +255 -0
- package/dist/lib/pending-replies.js.map +1 -0
- package/dist/lib/pending-replies.test.d.ts +2 -0
- package/dist/lib/pending-replies.test.d.ts.map +1 -0
- package/dist/lib/pending-replies.test.js +217 -0
- package/dist/lib/pending-replies.test.js.map +1 -0
- package/dist/lib/reachy-mini.d.ts +202 -88
- package/dist/lib/reachy-mini.d.ts.map +1 -1
- package/dist/lib/reachy-mini.js +651 -566
- package/dist/lib/reachy-mini.js.map +1 -1
- package/dist/lib/redial.test.d.ts +2 -0
- package/dist/lib/redial.test.d.ts.map +1 -0
- package/dist/lib/redial.test.js +206 -0
- package/dist/lib/redial.test.js.map +1 -0
- package/dist/lib/request.test.d.ts +2 -0
- package/dist/lib/request.test.d.ts.map +1 -0
- package/dist/lib/request.test.js +90 -0
- package/dist/lib/request.test.js.map +1 -0
- package/dist/lib/session-supervisor.d.ts +201 -0
- package/dist/lib/session-supervisor.d.ts.map +1 -0
- package/dist/lib/session-supervisor.js +520 -0
- package/dist/lib/session-supervisor.js.map +1 -0
- package/dist/lib/token-store.d.ts +50 -0
- package/dist/lib/token-store.d.ts.map +1 -0
- package/dist/lib/token-store.js +120 -0
- package/dist/lib/token-store.js.map +1 -0
- package/dist/lib/token-store.test.d.ts +2 -0
- package/dist/lib/token-store.test.d.ts.map +1 -0
- package/dist/lib/token-store.test.js +82 -0
- package/dist/lib/token-store.test.js.map +1 -0
- package/dist/lib/types.d.ts +280 -6
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/url-helpers.d.ts.map +1 -1
- package/dist/lib/url-helpers.js +3 -1
- package/dist/lib/url-helpers.js.map +1 -1
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +5 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/reachy-mini-sdk.d.ts +15 -1
- package/dist/reachy-mini-sdk.d.ts.map +1 -1
- package/dist/reachy-mini-sdk.js +12 -0
- package/dist/reachy-mini-sdk.js.map +1 -1
- package/host/dist/ReachyHost.d.ts.map +1 -1
- package/host/dist/chunks/centralRest-uXNMidlL.js +2315 -0
- package/host/dist/chunks/index-Beu4GNzi.js +821 -0
- package/host/dist/chunks/{mountHost-DjE5zE9R.js → mountHost-BTTF4Uu9.js} +24933 -27488
- package/host/dist/components/ConnectingView.d.ts.map +1 -1
- package/host/dist/components/DaemonUpdateGate.d.ts +65 -0
- package/host/dist/components/DaemonUpdateGate.d.ts.map +1 -0
- package/host/dist/components/ErrorView.d.ts.map +1 -1
- package/host/dist/components/HostErrorBoundary.d.ts +30 -0
- package/host/dist/components/HostErrorBoundary.d.ts.map +1 -0
- package/host/dist/components/LeavingView.d.ts.map +1 -1
- package/host/dist/components/PickerView.d.ts +16 -14
- package/host/dist/components/PickerView.d.ts.map +1 -1
- package/host/dist/components/PostOAuthSplash.d.ts +14 -9
- package/host/dist/components/PostOAuthSplash.d.ts.map +1 -1
- package/host/dist/components/ReachyHostShell.d.ts.map +1 -1
- package/host/dist/components/SignInView.d.ts.map +1 -1
- package/host/dist/components/TopBar.d.ts +5 -6
- package/host/dist/components/TopBar.d.ts.map +1 -1
- package/host/dist/components/WelcomeBackOverlay.d.ts.map +1 -1
- package/host/dist/embed/index.d.ts +68 -5
- package/host/dist/embed/index.d.ts.map +1 -1
- package/host/dist/entry/auto.js +2 -2
- package/host/dist/entry/embed.d.ts +5 -4
- package/host/dist/entry/embed.d.ts.map +1 -1
- package/host/dist/entry/embed.js +5 -4
- package/host/dist/hooks/useHostBridge.d.ts +35 -1
- package/host/dist/hooks/useHostBridge.d.ts.map +1 -1
- package/host/dist/hooks/useLatestDaemonVersion.d.ts +7 -0
- package/host/dist/hooks/useLatestDaemonVersion.d.ts.map +1 -0
- package/host/dist/hooks/useOAuth.d.ts +18 -0
- package/host/dist/hooks/useOAuth.d.ts.map +1 -1
- package/host/dist/hooks/useRobots.d.ts +0 -2
- package/host/dist/hooks/useRobots.d.ts.map +1 -1
- package/host/dist/index.d.ts +2 -2
- package/host/dist/index.d.ts.map +1 -1
- package/host/dist/index.js +11 -10
- package/host/dist/lib/daemonRelease.d.ts +84 -0
- package/host/dist/lib/daemonRelease.d.ts.map +1 -0
- package/host/dist/lib/protocol.d.ts +141 -7
- package/host/dist/lib/protocol.d.ts.map +1 -1
- package/host/dist/lib/rebootWatch.d.ts +51 -0
- package/host/dist/lib/rebootWatch.d.ts.map +1 -0
- package/host/dist/lib/sdk-types.d.ts +1 -1
- package/host/dist/lib/sdk-types.d.ts.map +1 -1
- package/host/dist/lib/sdkStaleness.d.ts +78 -0
- package/host/dist/lib/sdkStaleness.d.ts.map +1 -0
- package/host/dist/lib/settings.d.ts +16 -5
- package/host/dist/lib/settings.d.ts.map +1 -1
- package/package.json +11 -4
- package/host/dist/chunks/index-lMs2sCXG.js +0 -439
- package/host/dist/chunks/reachy-mini-CdsdQLIU.js +0 -1523
- package/host/dist/lib/centralListener.d.ts +0 -73
- package/host/dist/lib/centralListener.d.ts.map +0 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending-reply bookkeeping for the ReachyMini SDK.
|
|
3
|
+
*
|
|
4
|
+
* The data channel multiplexes four request/response disciplines, each
|
|
5
|
+
* with its own correlation rule:
|
|
6
|
+
*
|
|
7
|
+
* 1. Reply slots — legacy `{type: ...}` commands whose replies carry
|
|
8
|
+
* no request id, correlated by message TYPE. At most one call per
|
|
9
|
+
* slot is in flight; a newer call supersedes the older waiter.
|
|
10
|
+
* 2. JSON-RPC — app-control calls correlated by an `id` we generate.
|
|
11
|
+
* 3. Motion completions — `wake_up` / `goto_sleep` acks, correlated
|
|
12
|
+
* by command name, FIFO per command.
|
|
13
|
+
* 4. Broadcast waiters — daemon broadcasts (move / audio playback
|
|
14
|
+
* lifecycle) matched by caller-supplied predicate.
|
|
15
|
+
*
|
|
16
|
+
* This class owns the WAITERS only: sending stays with `ReachyMini`
|
|
17
|
+
* (it owns the data channel), which passes send actions in as
|
|
18
|
+
* callbacks. Every session-teardown path settles the whole ledger at
|
|
19
|
+
* once via `settleAll`, so no caller is ever left hanging on a dead
|
|
20
|
+
* channel — and no future mechanism can be forgotten on a teardown
|
|
21
|
+
* path, because there is only one.
|
|
22
|
+
*/
|
|
23
|
+
import type { FaceTarget, ImuData } from './types.js';
|
|
24
|
+
/**
|
|
25
|
+
* Fail-open ceiling for a single reply-slot request/response.
|
|
26
|
+
* Every slot command has a strict "one reply per request" contract, so
|
|
27
|
+
* a missing reply means the daemon either never got it or - crucially -
|
|
28
|
+
* predates that command entirely: an older daemon silently drops an
|
|
29
|
+
* unknown `type` and sends nothing back, which would otherwise leave the
|
|
30
|
+
* caller's promise pending forever (e.g. a newer SDK calling a command a
|
|
31
|
+
* 1.8.x daemon doesn't implement). Resolving `null` on timeout maps
|
|
32
|
+
* cleanly onto the "unsupported / failed" value every slot caller already
|
|
33
|
+
* handles. 4 s is comfortably above a WebRTC data-channel round trip on a
|
|
34
|
+
* congested phone link while still failing fast enough that a gated UI
|
|
35
|
+
* doesn't feel hung.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SLOT_ROUNDTRIP_TIMEOUT_MS = 4000;
|
|
38
|
+
/**
|
|
39
|
+
* Value type carried by each single-slot reply waiter. These daemon
|
|
40
|
+
* replies are correlated by message TYPE — the legacy command set
|
|
41
|
+
* carries no request ids — so at most one call per slot is in flight.
|
|
42
|
+
*/
|
|
43
|
+
export interface ReplySlotValues {
|
|
44
|
+
version: string | null;
|
|
45
|
+
hardware_id: string | null;
|
|
46
|
+
volume: number | null;
|
|
47
|
+
mic_volume: number | null;
|
|
48
|
+
first_wake_up: boolean | null;
|
|
49
|
+
tracked_face: FaceTarget | null;
|
|
50
|
+
robot_name: string | null;
|
|
51
|
+
delete_hf_token: boolean | null;
|
|
52
|
+
apply_audio_config: boolean | null;
|
|
53
|
+
read_audio_parameter: number[] | null;
|
|
54
|
+
imu: ImuData | null;
|
|
55
|
+
}
|
|
56
|
+
export type ReplySlotKey = keyof ReplySlotValues;
|
|
57
|
+
export type MotionCommand = 'wake_up' | 'goto_sleep';
|
|
58
|
+
/**
|
|
59
|
+
* Sentinel for a broadcast waiter that ran out its own timeout (as
|
|
60
|
+
* opposed to being rejected by a session teardown via `settleAll`).
|
|
61
|
+
* Callers that fail-open on timeout — `request()` — detect it by type
|
|
62
|
+
* instead of coupling to the message string.
|
|
63
|
+
*/
|
|
64
|
+
export declare class BroadcastTimeoutError extends Error {
|
|
65
|
+
}
|
|
66
|
+
export declare class PendingReplies {
|
|
67
|
+
private readonly _replySlots;
|
|
68
|
+
private _rpcCounter;
|
|
69
|
+
private readonly _pendingRpc;
|
|
70
|
+
private readonly _pendingMotion;
|
|
71
|
+
private _broadcastWaiters;
|
|
72
|
+
/**
|
|
73
|
+
* Register a slot waiter, run `send`, and await the matching daemon
|
|
74
|
+
* response. If a previous request on the same slot is still pending
|
|
75
|
+
* when a new one comes in, the older promise is resolved to `null`
|
|
76
|
+
* so its caller doesn't hang forever.
|
|
77
|
+
*/
|
|
78
|
+
slotRoundtrip<K extends ReplySlotKey>(slot: K, send: () => void): Promise<ReplySlotValues[K] | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Deliver a daemon reply to the pending waiter for `slot`, if any.
|
|
81
|
+
* Returns `true` when a waiter consumed the value — some message-handler
|
|
82
|
+
* branches use that to decide whether the message was theirs to swallow.
|
|
83
|
+
*/
|
|
84
|
+
settleReplySlot<K extends ReplySlotKey>(slot: K, value: ReplySlotValues[K]): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* One JSON-RPC round-trip: mint the correlation id, hand it to
|
|
87
|
+
* `send` (which reports `false` when the channel isn't open — the
|
|
88
|
+
* promise then rejects immediately), and await the matching
|
|
89
|
+
* response; rejects on timeout. Same shape as `slotRoundtrip`.
|
|
90
|
+
*/
|
|
91
|
+
rpcRoundtrip(method: string, timeoutMs: number, send: (id: string) => boolean): Promise<unknown>;
|
|
92
|
+
/**
|
|
93
|
+
* Route a JSON-RPC RESPONSE (id + result/error) to its waiter.
|
|
94
|
+
* Returns `true` when the message was response-shaped — even if the
|
|
95
|
+
* waiter already timed out — so the caller can stop processing it;
|
|
96
|
+
* `false` for notifications and anything else.
|
|
97
|
+
*/
|
|
98
|
+
settleRpcResponse(data: Record<string, unknown>): boolean;
|
|
99
|
+
/** FIFO waiter for one `wake_up` / `goto_sleep` ack; rejects on timeout. */
|
|
100
|
+
awaitMotion(command: MotionCommand, timeoutMs: number): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Deliver a `wake_up` / `goto_sleep` reply to the oldest waiter.
|
|
103
|
+
* Returns `true` when a waiter consumed it; `false` when nothing was
|
|
104
|
+
* pending or the reply is neither a completion nor an error (the
|
|
105
|
+
* message then falls through to the caller's other branches).
|
|
106
|
+
*/
|
|
107
|
+
settleMotion(command: MotionCommand, data: Record<string, unknown>): boolean;
|
|
108
|
+
/** Await the first broadcast matching `predicate`; rejects on timeout. */
|
|
109
|
+
awaitBroadcast(predicate: (m: Record<string, unknown>) => boolean, { timeoutMs, debugLabel }?: {
|
|
110
|
+
timeoutMs?: number;
|
|
111
|
+
debugLabel?: string;
|
|
112
|
+
}): Promise<Record<string, unknown>>;
|
|
113
|
+
/**
|
|
114
|
+
* Offer a broadcast to the waiters; the first (most recent) matching
|
|
115
|
+
* waiter consumes it. Returns `true` when one did.
|
|
116
|
+
*/
|
|
117
|
+
matchBroadcast(data: Record<string, unknown>): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Settle the whole ledger against a dead channel: reply slots
|
|
120
|
+
* resolve `null` (the "no answer" value all slot callers handle;
|
|
121
|
+
* public wrappers coerce where needed, e.g. `applyAudioConfig` maps
|
|
122
|
+
* it to `false`), everything else rejects with `err`. Called from
|
|
123
|
+
* every session-teardown path.
|
|
124
|
+
*/
|
|
125
|
+
settleAll(err: Error): void;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=pending-replies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-replies.d.ts","sourceRoot":"","sources":["../../lib/pending-replies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,oBAAoB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;CACvB;AACD,MAAM,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC;AAEjD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;AAErD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;CAAG;AAqBnD,qBAAa,cAAc;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiD;IAC7E,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiC;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAG7B;IACF,OAAO,CAAC,iBAAiB,CAAyB;IAIlD;;;;;OAKG;IACH,aAAa,CAAC,CAAC,SAAS,YAAY,EAChC,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,IAAI,GACjB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IA4BrC;;;;OAIG;IACH,eAAe,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,OAAO;IASpF;;;;;OAKG;IACH,YAAY,CACR,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAC9B,OAAO,CAAC,OAAO,CAAC;IAcnB;;;;;OAKG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAqBzD,4EAA4E;IAC5E,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBrE;;;;;OAKG;IACH,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAmB5E,0EAA0E;IAC1E,cAAc,CACV,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,EAClD,EAAE,SAAgB,EAAE,UAAe,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO,GACxF,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAgBnC;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAetD;;;;;;OAMG;IACH,SAAS,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI;CA2B9B"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending-reply bookkeeping for the ReachyMini SDK.
|
|
3
|
+
*
|
|
4
|
+
* The data channel multiplexes four request/response disciplines, each
|
|
5
|
+
* with its own correlation rule:
|
|
6
|
+
*
|
|
7
|
+
* 1. Reply slots — legacy `{type: ...}` commands whose replies carry
|
|
8
|
+
* no request id, correlated by message TYPE. At most one call per
|
|
9
|
+
* slot is in flight; a newer call supersedes the older waiter.
|
|
10
|
+
* 2. JSON-RPC — app-control calls correlated by an `id` we generate.
|
|
11
|
+
* 3. Motion completions — `wake_up` / `goto_sleep` acks, correlated
|
|
12
|
+
* by command name, FIFO per command.
|
|
13
|
+
* 4. Broadcast waiters — daemon broadcasts (move / audio playback
|
|
14
|
+
* lifecycle) matched by caller-supplied predicate.
|
|
15
|
+
*
|
|
16
|
+
* This class owns the WAITERS only: sending stays with `ReachyMini`
|
|
17
|
+
* (it owns the data channel), which passes send actions in as
|
|
18
|
+
* callbacks. Every session-teardown path settles the whole ledger at
|
|
19
|
+
* once via `settleAll`, so no caller is ever left hanging on a dead
|
|
20
|
+
* channel — and no future mechanism can be forgotten on a teardown
|
|
21
|
+
* path, because there is only one.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Fail-open ceiling for a single reply-slot request/response.
|
|
25
|
+
* Every slot command has a strict "one reply per request" contract, so
|
|
26
|
+
* a missing reply means the daemon either never got it or - crucially -
|
|
27
|
+
* predates that command entirely: an older daemon silently drops an
|
|
28
|
+
* unknown `type` and sends nothing back, which would otherwise leave the
|
|
29
|
+
* caller's promise pending forever (e.g. a newer SDK calling a command a
|
|
30
|
+
* 1.8.x daemon doesn't implement). Resolving `null` on timeout maps
|
|
31
|
+
* cleanly onto the "unsupported / failed" value every slot caller already
|
|
32
|
+
* handles. 4 s is comfortably above a WebRTC data-channel round trip on a
|
|
33
|
+
* congested phone link while still failing fast enough that a gated UI
|
|
34
|
+
* doesn't feel hung.
|
|
35
|
+
*/
|
|
36
|
+
export const SLOT_ROUNDTRIP_TIMEOUT_MS = 4000;
|
|
37
|
+
/**
|
|
38
|
+
* Sentinel for a broadcast waiter that ran out its own timeout (as
|
|
39
|
+
* opposed to being rejected by a session teardown via `settleAll`).
|
|
40
|
+
* Callers that fail-open on timeout — `request()` — detect it by type
|
|
41
|
+
* instead of coupling to the message string.
|
|
42
|
+
*/
|
|
43
|
+
export class BroadcastTimeoutError extends Error {
|
|
44
|
+
}
|
|
45
|
+
export class PendingReplies {
|
|
46
|
+
_replySlots = new Map();
|
|
47
|
+
_rpcCounter = 0;
|
|
48
|
+
_pendingRpc = new Map();
|
|
49
|
+
_pendingMotion = {
|
|
50
|
+
wake_up: [],
|
|
51
|
+
goto_sleep: [],
|
|
52
|
+
};
|
|
53
|
+
_broadcastWaiters = [];
|
|
54
|
+
/* ─── Reply slots ────────────────────────────────────────────────── */
|
|
55
|
+
/**
|
|
56
|
+
* Register a slot waiter, run `send`, and await the matching daemon
|
|
57
|
+
* response. If a previous request on the same slot is still pending
|
|
58
|
+
* when a new one comes in, the older promise is resolved to `null`
|
|
59
|
+
* so its caller doesn't hang forever.
|
|
60
|
+
*/
|
|
61
|
+
slotRoundtrip(slot, send) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const prev = this._replySlots.get(slot);
|
|
64
|
+
if (prev)
|
|
65
|
+
prev(null);
|
|
66
|
+
let timer;
|
|
67
|
+
// Single settle path shared by the daemon response, supersession
|
|
68
|
+
// by a newer call, and the fail-open timeout below. It clears the
|
|
69
|
+
// timer once and detaches itself from the slot only if it's still
|
|
70
|
+
// the current occupant, so a stale settle (timed-out or superseded)
|
|
71
|
+
// can't clear a newer call's slot registration.
|
|
72
|
+
// Note: slots are keyed by command type, not request id, so this
|
|
73
|
+
// does not prevent a genuinely late daemon reply from being routed
|
|
74
|
+
// to a newer same-command caller — that cross-talk is inherent to
|
|
75
|
+
// the single-flight slot design.
|
|
76
|
+
const settle = (v) => {
|
|
77
|
+
if (timer !== undefined) {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
timer = undefined;
|
|
80
|
+
}
|
|
81
|
+
if (this._replySlots.get(slot) === settle)
|
|
82
|
+
this._replySlots.delete(slot);
|
|
83
|
+
resolve(v);
|
|
84
|
+
};
|
|
85
|
+
this._replySlots.set(slot, settle);
|
|
86
|
+
timer = setTimeout(() => settle(null), SLOT_ROUNDTRIP_TIMEOUT_MS);
|
|
87
|
+
send();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Deliver a daemon reply to the pending waiter for `slot`, if any.
|
|
92
|
+
* Returns `true` when a waiter consumed the value — some message-handler
|
|
93
|
+
* branches use that to decide whether the message was theirs to swallow.
|
|
94
|
+
*/
|
|
95
|
+
settleReplySlot(slot, value) {
|
|
96
|
+
const waiter = this._replySlots.get(slot);
|
|
97
|
+
if (!waiter)
|
|
98
|
+
return false;
|
|
99
|
+
waiter(value);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/* ─── JSON-RPC ───────────────────────────────────────────────────── */
|
|
103
|
+
/**
|
|
104
|
+
* One JSON-RPC round-trip: mint the correlation id, hand it to
|
|
105
|
+
* `send` (which reports `false` when the channel isn't open — the
|
|
106
|
+
* promise then rejects immediately), and await the matching
|
|
107
|
+
* response; rejects on timeout. Same shape as `slotRoundtrip`.
|
|
108
|
+
*/
|
|
109
|
+
rpcRoundtrip(method, timeoutMs, send) {
|
|
110
|
+
const id = `rpc-${++this._rpcCounter}`;
|
|
111
|
+
if (!send(id)) {
|
|
112
|
+
return Promise.reject(new Error(`rpcCall(${method}): data channel not open`));
|
|
113
|
+
}
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const timer = setTimeout(() => {
|
|
116
|
+
this._pendingRpc.delete(id);
|
|
117
|
+
reject(new Error(`rpcCall(${method}) timed out after ${timeoutMs}ms`));
|
|
118
|
+
}, timeoutMs);
|
|
119
|
+
this._pendingRpc.set(id, { resolve, reject, timer });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Route a JSON-RPC RESPONSE (id + result/error) to its waiter.
|
|
124
|
+
* Returns `true` when the message was response-shaped — even if the
|
|
125
|
+
* waiter already timed out — so the caller can stop processing it;
|
|
126
|
+
* `false` for notifications and anything else.
|
|
127
|
+
*/
|
|
128
|
+
settleRpcResponse(data) {
|
|
129
|
+
if (!('id' in data) || data.id == null || !('result' in data || 'error' in data)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const pending = this._pendingRpc.get(data.id);
|
|
133
|
+
if (!pending)
|
|
134
|
+
return true; // late reply to a timed-out call
|
|
135
|
+
this._pendingRpc.delete(data.id);
|
|
136
|
+
clearTimeout(pending.timer);
|
|
137
|
+
if ('error' in data && data.error) {
|
|
138
|
+
const err = data.error;
|
|
139
|
+
const e = new Error(err.message ?? 'rpc error');
|
|
140
|
+
e.reason = err.data?.reason;
|
|
141
|
+
pending.reject(e);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
pending.resolve(data.result);
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
/* ─── Motion completions (wake_up / goto_sleep) ──────────────────── */
|
|
149
|
+
/** FIFO waiter for one `wake_up` / `goto_sleep` ack; rejects on timeout. */
|
|
150
|
+
awaitMotion(command, timeoutMs) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const entry = {
|
|
153
|
+
resolve,
|
|
154
|
+
reject,
|
|
155
|
+
timer: setTimeout(() => {
|
|
156
|
+
const queue = this._pendingMotion[command];
|
|
157
|
+
const idx = queue.indexOf(entry);
|
|
158
|
+
if (idx !== -1)
|
|
159
|
+
queue.splice(idx, 1);
|
|
160
|
+
reject(new Error(`${command} timed out after ${timeoutMs}ms`));
|
|
161
|
+
}, timeoutMs),
|
|
162
|
+
};
|
|
163
|
+
this._pendingMotion[command].push(entry);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Deliver a `wake_up` / `goto_sleep` reply to the oldest waiter.
|
|
168
|
+
* Returns `true` when a waiter consumed it; `false` when nothing was
|
|
169
|
+
* pending or the reply is neither a completion nor an error (the
|
|
170
|
+
* message then falls through to the caller's other branches).
|
|
171
|
+
*/
|
|
172
|
+
settleMotion(command, data) {
|
|
173
|
+
const queue = this._pendingMotion[command];
|
|
174
|
+
if (data.completed === true && queue.length > 0) {
|
|
175
|
+
const entry = queue.shift();
|
|
176
|
+
clearTimeout(entry.timer);
|
|
177
|
+
entry.resolve();
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
if (data.error && queue.length > 0) {
|
|
181
|
+
const entry = queue.shift();
|
|
182
|
+
clearTimeout(entry.timer);
|
|
183
|
+
entry.reject(new Error(`${command}: ${data.error}`));
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
/* ─── Broadcast waiters ──────────────────────────────────────────── */
|
|
189
|
+
/** Await the first broadcast matching `predicate`; rejects on timeout. */
|
|
190
|
+
awaitBroadcast(predicate, { timeoutMs = 5000, debugLabel = '' } = {}) {
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
const slot = {
|
|
193
|
+
predicate,
|
|
194
|
+
resolve,
|
|
195
|
+
reject,
|
|
196
|
+
timer: setTimeout(() => {
|
|
197
|
+
const i = this._broadcastWaiters.indexOf(slot);
|
|
198
|
+
if (i !== -1)
|
|
199
|
+
this._broadcastWaiters.splice(i, 1);
|
|
200
|
+
reject(new BroadcastTimeoutError(`broadcast timeout (${timeoutMs} ms): ${debugLabel}`));
|
|
201
|
+
}, timeoutMs),
|
|
202
|
+
};
|
|
203
|
+
this._broadcastWaiters.push(slot);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Offer a broadcast to the waiters; the first (most recent) matching
|
|
208
|
+
* waiter consumes it. Returns `true` when one did.
|
|
209
|
+
*/
|
|
210
|
+
matchBroadcast(data) {
|
|
211
|
+
for (let i = this._broadcastWaiters.length - 1; i >= 0; i--) {
|
|
212
|
+
const slot = this._broadcastWaiters[i];
|
|
213
|
+
if (slot.predicate(data)) {
|
|
214
|
+
this._broadcastWaiters.splice(i, 1);
|
|
215
|
+
clearTimeout(slot.timer);
|
|
216
|
+
slot.resolve(data);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
/* ─── Teardown ───────────────────────────────────────────────────── */
|
|
223
|
+
/**
|
|
224
|
+
* Settle the whole ledger against a dead channel: reply slots
|
|
225
|
+
* resolve `null` (the "no answer" value all slot callers handle;
|
|
226
|
+
* public wrappers coerce where needed, e.g. `applyAudioConfig` maps
|
|
227
|
+
* it to `false`), everything else rejects with `err`. Called from
|
|
228
|
+
* every session-teardown path.
|
|
229
|
+
*/
|
|
230
|
+
settleAll(err) {
|
|
231
|
+
for (const waiter of [...this._replySlots.values()]) {
|
|
232
|
+
waiter(null);
|
|
233
|
+
}
|
|
234
|
+
this._replySlots.clear();
|
|
235
|
+
for (const pending of this._pendingRpc.values()) {
|
|
236
|
+
clearTimeout(pending.timer);
|
|
237
|
+
pending.reject(err);
|
|
238
|
+
}
|
|
239
|
+
this._pendingRpc.clear();
|
|
240
|
+
for (const command of Object.keys(this._pendingMotion)) {
|
|
241
|
+
const queue = this._pendingMotion[command];
|
|
242
|
+
while (queue.length) {
|
|
243
|
+
const entry = queue.shift();
|
|
244
|
+
clearTimeout(entry.timer);
|
|
245
|
+
entry.reject(err);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const slot of this._broadcastWaiters) {
|
|
249
|
+
clearTimeout(slot.timer);
|
|
250
|
+
slot.reject(err);
|
|
251
|
+
}
|
|
252
|
+
this._broadcastWaiters = [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=pending-replies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-replies.js","sourceRoot":"","sources":["../../lib/pending-replies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-replies.test.d.ts","sourceRoot":"","sources":["../../lib/pending-replies.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PendingReplies ledger tests.
|
|
3
|
+
*
|
|
4
|
+
* The properties that must hold, per discipline:
|
|
5
|
+
* - reply slots: single-flight per slot — a newer call supersedes the
|
|
6
|
+
* older waiter with `null`; a missing daemon reply fails open to
|
|
7
|
+
* `null` after the roundtrip timeout; a stale settle can't clobber
|
|
8
|
+
* a newer call's registration;
|
|
9
|
+
* - JSON-RPC: responses are routed by id; error responses carry the
|
|
10
|
+
* message and machine-readable `reason`; a late reply to a
|
|
11
|
+
* timed-out call is swallowed (still counts as response-shaped);
|
|
12
|
+
* - motion completions: FIFO per command; replies that are neither a
|
|
13
|
+
* completion nor an error fall through to the caller;
|
|
14
|
+
* - broadcast waiters: predicate match consumes the most recent
|
|
15
|
+
* waiter; timeout carries the debug label;
|
|
16
|
+
* - settleAll(): one call settles all four ledgers — slots resolve
|
|
17
|
+
* `null`, everything else rejects — so no caller can be left
|
|
18
|
+
* hanging on a dead channel.
|
|
19
|
+
*/
|
|
20
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
21
|
+
import { PendingReplies } from './pending-replies.js';
|
|
22
|
+
let pending;
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.useFakeTimers();
|
|
25
|
+
pending = new PendingReplies();
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.useRealTimers();
|
|
29
|
+
});
|
|
30
|
+
describe('reply slots', () => {
|
|
31
|
+
it('resolves the waiter when the daemon reply lands', async () => {
|
|
32
|
+
const send = vi.fn();
|
|
33
|
+
const p = pending.slotRoundtrip('version', send);
|
|
34
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(pending.settleReplySlot('version', '1.2.3')).toBe(true);
|
|
36
|
+
await expect(p).resolves.toBe('1.2.3');
|
|
37
|
+
});
|
|
38
|
+
it('reports an unclaimed reply so the handler can fall through', () => {
|
|
39
|
+
expect(pending.settleReplySlot('version', '1.2.3')).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
it('supersedes a pending call on the same slot with null', async () => {
|
|
42
|
+
const first = pending.slotRoundtrip('volume', vi.fn());
|
|
43
|
+
const second = pending.slotRoundtrip('volume', vi.fn());
|
|
44
|
+
await expect(first).resolves.toBeNull();
|
|
45
|
+
pending.settleReplySlot('volume', 42);
|
|
46
|
+
await expect(second).resolves.toBe(42);
|
|
47
|
+
});
|
|
48
|
+
it('keeps unrelated slots independent', async () => {
|
|
49
|
+
const version = pending.slotRoundtrip('version', vi.fn());
|
|
50
|
+
const volume = pending.slotRoundtrip('volume', vi.fn());
|
|
51
|
+
pending.settleReplySlot('volume', 10);
|
|
52
|
+
pending.settleReplySlot('version', 'v');
|
|
53
|
+
await expect(volume).resolves.toBe(10);
|
|
54
|
+
await expect(version).resolves.toBe('v');
|
|
55
|
+
});
|
|
56
|
+
it('fails open to null when the daemon never answers (old daemon)', async () => {
|
|
57
|
+
const p = pending.slotRoundtrip('hardware_id', vi.fn());
|
|
58
|
+
await vi.advanceTimersByTimeAsync(4000);
|
|
59
|
+
await expect(p).resolves.toBeNull();
|
|
60
|
+
// The slot is free again: a late daemon reply finds no waiter.
|
|
61
|
+
expect(pending.settleReplySlot('hardware_id', 'late')).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it('a timed-out call does not clear a newer call on the same slot', async () => {
|
|
64
|
+
const first = pending.slotRoundtrip('robot_name', vi.fn());
|
|
65
|
+
// Just before the first call's timeout, a second call supersedes it…
|
|
66
|
+
await vi.advanceTimersByTimeAsync(3999);
|
|
67
|
+
const second = pending.slotRoundtrip('robot_name', vi.fn());
|
|
68
|
+
await expect(first).resolves.toBeNull();
|
|
69
|
+
// …and the second call's registration survives the first's timer.
|
|
70
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
71
|
+
expect(pending.settleReplySlot('robot_name', 'Reachy')).toBe(true);
|
|
72
|
+
await expect(second).resolves.toBe('Reachy');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('JSON-RPC', () => {
|
|
76
|
+
/** Capture the id `rpcRoundtrip` mints for the send callback. */
|
|
77
|
+
function roundtrip(method, timeoutMs) {
|
|
78
|
+
let id = '';
|
|
79
|
+
const p = pending.rpcRoundtrip(method, timeoutMs, (mintedId) => {
|
|
80
|
+
id = mintedId;
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
return { id, p };
|
|
84
|
+
}
|
|
85
|
+
it('routes a result response to its waiter by id', async () => {
|
|
86
|
+
const { id, p } = roundtrip('apps.start', 1000);
|
|
87
|
+
expect(pending.settleRpcResponse({ id, result: { ok: true } })).toBe(true);
|
|
88
|
+
await expect(p).resolves.toEqual({ ok: true });
|
|
89
|
+
});
|
|
90
|
+
it('rejects immediately when the send reports a closed channel', async () => {
|
|
91
|
+
await expect(pending.rpcRoundtrip('apps.start', 1000, () => false)).rejects.toThrow('rpcCall(apps.start): data channel not open');
|
|
92
|
+
});
|
|
93
|
+
it('rejects with message and machine-readable reason on an error response', async () => {
|
|
94
|
+
const { id, p } = roundtrip('apps.start', 1000);
|
|
95
|
+
expect(pending.settleRpcResponse({
|
|
96
|
+
id,
|
|
97
|
+
error: { message: 'nope', data: { reason: 'app_not_found' } },
|
|
98
|
+
})).toBe(true);
|
|
99
|
+
await expect(p).rejects.toMatchObject({
|
|
100
|
+
message: 'nope',
|
|
101
|
+
reason: 'app_not_found',
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('swallows a late reply to a timed-out call (still response-shaped)', async () => {
|
|
105
|
+
const { id, p } = roundtrip('conversation.say', 1000);
|
|
106
|
+
void p.catch(() => { });
|
|
107
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
108
|
+
await expect(p).rejects.toThrow(/timed out after 1000ms/);
|
|
109
|
+
// The late reply must not fall through to the notification path.
|
|
110
|
+
expect(pending.settleRpcResponse({ id, result: 'late' })).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
it('leaves notifications (no id) to the caller', () => {
|
|
113
|
+
expect(pending.settleRpcResponse({ method: 'conversation.turn', params: {} })).toBe(false);
|
|
114
|
+
// id present but neither result nor error: not a response either.
|
|
115
|
+
expect(pending.settleRpcResponse({ id: 'rpc-1', method: 'x' })).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
it('mints unique correlation ids', () => {
|
|
118
|
+
const first = roundtrip('a', 1000);
|
|
119
|
+
const second = roundtrip('b', 1000);
|
|
120
|
+
expect(first.id).not.toBe(second.id);
|
|
121
|
+
void first.p.catch(() => { });
|
|
122
|
+
void second.p.catch(() => { });
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('motion completions', () => {
|
|
126
|
+
it('resolves waiters in FIFO order per command', async () => {
|
|
127
|
+
const first = pending.awaitMotion('wake_up', 1000);
|
|
128
|
+
const second = pending.awaitMotion('wake_up', 1000);
|
|
129
|
+
const order = [];
|
|
130
|
+
void first.then(() => order.push('first'));
|
|
131
|
+
void second.then(() => order.push('second'));
|
|
132
|
+
expect(pending.settleMotion('wake_up', { completed: true })).toBe(true);
|
|
133
|
+
expect(pending.settleMotion('wake_up', { completed: true })).toBe(true);
|
|
134
|
+
await vi.runAllTimersAsync();
|
|
135
|
+
expect(order).toEqual(['first', 'second']);
|
|
136
|
+
});
|
|
137
|
+
it('rejects the oldest waiter on an error reply', async () => {
|
|
138
|
+
const p = pending.awaitMotion('goto_sleep', 1000);
|
|
139
|
+
expect(pending.settleMotion('goto_sleep', { error: 'motors off' })).toBe(true);
|
|
140
|
+
await expect(p).rejects.toThrow('goto_sleep: motors off');
|
|
141
|
+
});
|
|
142
|
+
it('falls through when the reply is neither completion nor error', () => {
|
|
143
|
+
void pending.awaitMotion('wake_up', 1000).catch(() => { });
|
|
144
|
+
// An intermediate ack without `completed` must not consume the waiter.
|
|
145
|
+
expect(pending.settleMotion('wake_up', { status: 'started' })).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
it('falls through when nothing is pending', () => {
|
|
148
|
+
expect(pending.settleMotion('wake_up', { completed: true })).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
it('rejects on timeout and forgets the waiter', async () => {
|
|
151
|
+
const p = pending.awaitMotion('wake_up', 1000);
|
|
152
|
+
void p.catch(() => { });
|
|
153
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
154
|
+
await expect(p).rejects.toThrow(/wake_up timed out/);
|
|
155
|
+
expect(pending.settleMotion('wake_up', { completed: true })).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('broadcast waiters', () => {
|
|
159
|
+
it('the first matching broadcast resolves the waiter', async () => {
|
|
160
|
+
const p = pending.awaitBroadcast((m) => m.type === 'move_done');
|
|
161
|
+
expect(pending.matchBroadcast({ type: 'other' })).toBe(false);
|
|
162
|
+
expect(pending.matchBroadcast({ type: 'move_done', id: 7 })).toBe(true);
|
|
163
|
+
await expect(p).resolves.toEqual({ type: 'move_done', id: 7 });
|
|
164
|
+
});
|
|
165
|
+
it('the most recent matching waiter wins', async () => {
|
|
166
|
+
const older = pending.awaitBroadcast((m) => m.type === 'x', { debugLabel: 'older' });
|
|
167
|
+
const newer = pending.awaitBroadcast((m) => m.type === 'x', { debugLabel: 'newer' });
|
|
168
|
+
expect(pending.matchBroadcast({ type: 'x', n: 1 })).toBe(true);
|
|
169
|
+
await expect(newer).resolves.toEqual({ type: 'x', n: 1 });
|
|
170
|
+
expect(pending.matchBroadcast({ type: 'x', n: 2 })).toBe(true);
|
|
171
|
+
await expect(older).resolves.toEqual({ type: 'x', n: 2 });
|
|
172
|
+
});
|
|
173
|
+
it('rejects on timeout with the debug label', async () => {
|
|
174
|
+
const p = pending.awaitBroadcast(() => false, { timeoutMs: 2000, debugLabel: 'move ack' });
|
|
175
|
+
void p.catch(() => { });
|
|
176
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
177
|
+
await expect(p).rejects.toThrow('broadcast timeout (2000 ms): move ack');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe('settleAll (session teardown)', () => {
|
|
181
|
+
it('settles all four ledgers in one call', async () => {
|
|
182
|
+
const slot = pending.slotRoundtrip('version', vi.fn());
|
|
183
|
+
const rpc = pending.rpcRoundtrip('apps.start', 60_000, () => true);
|
|
184
|
+
const motion = pending.awaitMotion('wake_up', 60_000);
|
|
185
|
+
const broadcast = pending.awaitBroadcast(() => true, { timeoutMs: 60_000 });
|
|
186
|
+
// Pre-attach handlers so the synchronous rejections below don't
|
|
187
|
+
// trip Node's late-handling warning before the assertions run.
|
|
188
|
+
for (const p of [rpc, motion, broadcast])
|
|
189
|
+
void p.catch(() => { });
|
|
190
|
+
pending.settleAll(new Error('Session stopped'));
|
|
191
|
+
// Slots fail open (all slot callers handle null)…
|
|
192
|
+
await expect(slot).resolves.toBeNull();
|
|
193
|
+
// …everything else rejects with the teardown error.
|
|
194
|
+
await expect(rpc).rejects.toThrow('Session stopped');
|
|
195
|
+
await expect(motion).rejects.toThrow('Session stopped');
|
|
196
|
+
await expect(broadcast).rejects.toThrow('Session stopped');
|
|
197
|
+
});
|
|
198
|
+
it('leaves the ledger empty afterwards', () => {
|
|
199
|
+
void pending.slotRoundtrip('version', vi.fn());
|
|
200
|
+
void pending.awaitMotion('wake_up', 1000).catch(() => { });
|
|
201
|
+
void pending.awaitBroadcast(() => true).catch(() => { });
|
|
202
|
+
pending.settleAll(new Error('gone'));
|
|
203
|
+
expect(pending.settleReplySlot('version', 'v')).toBe(false);
|
|
204
|
+
expect(pending.settleMotion('wake_up', { completed: true })).toBe(false);
|
|
205
|
+
expect(pending.matchBroadcast({ type: 'any' })).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
it('cancels the pending timers (no late rejection fires)', async () => {
|
|
208
|
+
// A rejection firing after settleAll would be an unhandled one —
|
|
209
|
+
// runAllTimersAsync below would surface it.
|
|
210
|
+
const motion = pending.awaitMotion('wake_up', 1000);
|
|
211
|
+
void motion.catch(() => { });
|
|
212
|
+
pending.settleAll(new Error('gone'));
|
|
213
|
+
await expect(motion).rejects.toThrow('gone');
|
|
214
|
+
await vi.runAllTimersAsync();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=pending-replies.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-replies.test.js","sourceRoot":"","sources":["../../lib/pending-replies.test.ts"],"names":[],"mappings":"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"}
|