homebridge-plugin-utils 1.35.0 → 2.0.0

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.
Files changed (198) hide show
  1. package/README.md +130 -2
  2. package/build/eslint-plugin/README.md +164 -0
  3. package/build/eslint-plugin/config.mjs +308 -0
  4. package/build/eslint-plugin/index.mjs +7 -0
  5. package/build/eslint-plugin/plugin.mjs +46 -0
  6. package/build/eslint-plugin/rules/blank-line-after-open-brace.mjs +137 -0
  7. package/build/eslint-plugin/rules/blank-line-after-open-brace.test.mjs +112 -0
  8. package/build/eslint-plugin/rules/comment-style.mjs +190 -0
  9. package/build/eslint-plugin/rules/comment-style.test.mjs +190 -0
  10. package/build/eslint-plugin/rules/enforce-node-protocol.mjs +114 -0
  11. package/build/eslint-plugin/rules/enforce-node-protocol.test.mjs +116 -0
  12. package/build/eslint-plugin/rules/paren-comparisons-in-logical.mjs +94 -0
  13. package/build/eslint-plugin/rules/paren-comparisons-in-logical.test.mjs +107 -0
  14. package/build/eslint-plugin/rules/split-type-imports.mjs +354 -0
  15. package/build/eslint-plugin/rules/split-type-imports.test.mjs +268 -0
  16. package/build/eslint-plugin/test-setup.mjs +21 -0
  17. package/build/fs-ops.mjs +184 -0
  18. package/build/tsconfig.json +18 -3
  19. package/dist/backpressure.d.ts +94 -45
  20. package/dist/backpressure.js +229 -93
  21. package/dist/backpressure.js.map +1 -1
  22. package/dist/cli/index.d.ts +95 -0
  23. package/dist/cli/index.js +355 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/clock-double.d.ts +93 -0
  26. package/dist/clock-double.js +141 -0
  27. package/dist/clock-double.js.map +1 -0
  28. package/dist/clock.d.ts +39 -0
  29. package/dist/clock.js +34 -0
  30. package/dist/clock.js.map +1 -0
  31. package/dist/eslint-plugin/config.d.mts +193 -0
  32. package/dist/eslint-plugin/index.d.mts +2 -0
  33. package/dist/eslint-plugin/plugin.d.mts +87 -0
  34. package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
  35. package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
  36. package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
  37. package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
  38. package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
  39. package/dist/featureOptions-docs.d.ts +96 -0
  40. package/dist/featureOptions-docs.js +193 -0
  41. package/dist/featureOptions-docs.js.map +1 -0
  42. package/dist/featureOptions.d.ts +674 -0
  43. package/dist/featureOptions.js +870 -0
  44. package/dist/featureOptions.js.map +1 -0
  45. package/dist/ffmpeg/codecs.d.ts +256 -72
  46. package/dist/ffmpeg/codecs.js +477 -262
  47. package/dist/ffmpeg/codecs.js.map +1 -1
  48. package/dist/ffmpeg/dgram-util.d.ts +46 -0
  49. package/dist/ffmpeg/dgram-util.js +38 -0
  50. package/dist/ffmpeg/dgram-util.js.map +1 -0
  51. package/dist/ffmpeg/exec.d.ts +83 -64
  52. package/dist/ffmpeg/exec.js +77 -86
  53. package/dist/ffmpeg/exec.js.map +1 -1
  54. package/dist/ffmpeg/fmp4.d.ts +49 -2
  55. package/dist/ffmpeg/fmp4.js +47 -11
  56. package/dist/ffmpeg/fmp4.js.map +1 -1
  57. package/dist/ffmpeg/hap-enums.d.ts +214 -0
  58. package/dist/ffmpeg/hap-enums.js +92 -0
  59. package/dist/ffmpeg/hap-enums.js.map +1 -0
  60. package/dist/ffmpeg/index.d.ts +16 -9
  61. package/dist/ffmpeg/index.js +6 -0
  62. package/dist/ffmpeg/index.js.map +1 -1
  63. package/dist/ffmpeg/mp4-assembler.d.ts +120 -0
  64. package/dist/ffmpeg/mp4-assembler.js +387 -0
  65. package/dist/ffmpeg/mp4-assembler.js.map +1 -0
  66. package/dist/ffmpeg/mp4-parser.d.ts +94 -0
  67. package/dist/ffmpeg/mp4-parser.js +130 -0
  68. package/dist/ffmpeg/mp4-parser.js.map +1 -0
  69. package/dist/ffmpeg/options.d.ts +62 -149
  70. package/dist/ffmpeg/options.js +608 -499
  71. package/dist/ffmpeg/options.js.map +1 -1
  72. package/dist/ffmpeg/process.d.ts +142 -96
  73. package/dist/ffmpeg/process.js +406 -278
  74. package/dist/ffmpeg/process.js.map +1 -1
  75. package/dist/ffmpeg/record.d.ts +302 -188
  76. package/dist/ffmpeg/record.js +408 -567
  77. package/dist/ffmpeg/record.js.map +1 -1
  78. package/dist/ffmpeg/recording-process-double.d.ts +144 -0
  79. package/dist/ffmpeg/recording-process-double.js +168 -0
  80. package/dist/ffmpeg/recording-process-double.js.map +1 -0
  81. package/dist/ffmpeg/rtp-parser.d.ts +70 -0
  82. package/dist/ffmpeg/rtp-parser.js +77 -0
  83. package/dist/ffmpeg/rtp-parser.js.map +1 -0
  84. package/dist/ffmpeg/rtp.d.ts +198 -141
  85. package/dist/ffmpeg/rtp.js +473 -251
  86. package/dist/ffmpeg/rtp.js.map +1 -1
  87. package/dist/ffmpeg/settings.d.ts +5 -2
  88. package/dist/ffmpeg/settings.js +20 -5
  89. package/dist/ffmpeg/settings.js.map +1 -1
  90. package/dist/ffmpeg/stream.d.ts +57 -107
  91. package/dist/ffmpeg/stream.js +121 -150
  92. package/dist/ffmpeg/stream.js.map +1 -1
  93. package/dist/formatters.d.ts +106 -0
  94. package/dist/formatters.js +174 -0
  95. package/dist/formatters.js.map +1 -0
  96. package/dist/homebridge-enums.d.ts +30 -0
  97. package/dist/homebridge-enums.js +17 -0
  98. package/dist/homebridge-enums.js.map +1 -0
  99. package/dist/index.d.ts +12 -6
  100. package/dist/index.js +7 -2
  101. package/dist/index.js.map +1 -1
  102. package/dist/logclient/auth.d.ts +114 -0
  103. package/dist/logclient/auth.js +199 -0
  104. package/dist/logclient/auth.js.map +1 -0
  105. package/dist/logclient/cli-run.d.ts +76 -0
  106. package/dist/logclient/cli-run.js +639 -0
  107. package/dist/logclient/cli-run.js.map +1 -0
  108. package/dist/logclient/cli.d.ts +3 -0
  109. package/dist/logclient/cli.js +97 -0
  110. package/dist/logclient/cli.js.map +1 -0
  111. package/dist/logclient/client.d.ts +145 -0
  112. package/dist/logclient/client.js +600 -0
  113. package/dist/logclient/client.js.map +1 -0
  114. package/dist/logclient/config.d.ts +173 -0
  115. package/dist/logclient/config.js +199 -0
  116. package/dist/logclient/config.js.map +1 -0
  117. package/dist/logclient/endpoints.d.ts +54 -0
  118. package/dist/logclient/endpoints.js +73 -0
  119. package/dist/logclient/endpoints.js.map +1 -0
  120. package/dist/logclient/filter.d.ts +45 -0
  121. package/dist/logclient/filter.js +51 -0
  122. package/dist/logclient/filter.js.map +1 -0
  123. package/dist/logclient/frame.d.ts +93 -0
  124. package/dist/logclient/frame.js +203 -0
  125. package/dist/logclient/frame.js.map +1 -0
  126. package/dist/logclient/index.d.ts +31 -0
  127. package/dist/logclient/index.js +12 -0
  128. package/dist/logclient/index.js.map +1 -0
  129. package/dist/logclient/parser.d.ts +211 -0
  130. package/dist/logclient/parser.js +393 -0
  131. package/dist/logclient/parser.js.map +1 -0
  132. package/dist/logclient/rest.d.ts +41 -0
  133. package/dist/logclient/rest.js +111 -0
  134. package/dist/logclient/rest.js.map +1 -0
  135. package/dist/logclient/settings.d.ts +15 -0
  136. package/dist/logclient/settings.js +64 -0
  137. package/dist/logclient/settings.js.map +1 -0
  138. package/dist/logclient/socket-double.d.ts +201 -0
  139. package/dist/logclient/socket-double.js +384 -0
  140. package/dist/logclient/socket-double.js.map +1 -0
  141. package/dist/logclient/socket.d.ts +257 -0
  142. package/dist/logclient/socket.js +620 -0
  143. package/dist/logclient/socket.js.map +1 -0
  144. package/dist/logclient/stitch.d.ts +83 -0
  145. package/dist/logclient/stitch.js +146 -0
  146. package/dist/logclient/stitch.js.map +1 -0
  147. package/dist/logclient/time-expression.d.ts +42 -0
  148. package/dist/logclient/time-expression.js +181 -0
  149. package/dist/logclient/time-expression.js.map +1 -0
  150. package/dist/logclient/time-window.d.ts +38 -0
  151. package/dist/logclient/time-window.js +53 -0
  152. package/dist/logclient/time-window.js.map +1 -0
  153. package/dist/logclient/types.d.ts +107 -0
  154. package/dist/logclient/types.js +6 -0
  155. package/dist/logclient/types.js.map +1 -0
  156. package/dist/mqttClient.d.ts +287 -0
  157. package/dist/mqttClient.js +433 -0
  158. package/dist/mqttClient.js.map +1 -0
  159. package/dist/service.d.ts +64 -15
  160. package/dist/service.js +93 -66
  161. package/dist/service.js.map +1 -1
  162. package/dist/ui/featureOptions.js +870 -0
  163. package/dist/ui/featureOptions.js.map +1 -0
  164. package/dist/ui/formatters.js +174 -0
  165. package/dist/ui/formatters.js.map +1 -0
  166. package/dist/ui/pluginConfigSession.mjs +141 -0
  167. package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
  168. package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
  169. package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
  170. package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
  171. package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
  172. package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
  173. package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
  174. package/dist/ui/webUi-featureOptions/state.mjs +319 -0
  175. package/dist/ui/webUi-featureOptions/store.mjs +181 -0
  176. package/dist/ui/webUi-featureOptions/utils.mjs +200 -0
  177. package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
  178. package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
  179. package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
  180. package/dist/ui/webUi-featureOptions/views/nav.mjs +327 -0
  181. package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
  182. package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
  183. package/dist/ui/webUi-featureOptions.mjs +677 -0
  184. package/dist/ui/webUi.mjs +192 -87
  185. package/dist/util.d.ts +602 -45
  186. package/dist/util.js +783 -78
  187. package/dist/util.js.map +1 -1
  188. package/package.json +33 -15
  189. package/build/eslint-rules.mjs +0 -511
  190. package/dist/featureoptions.d.ts +0 -264
  191. package/dist/featureoptions.js +0 -480
  192. package/dist/featureoptions.js.map +0 -1
  193. package/dist/mqttclient.d.ts +0 -178
  194. package/dist/mqttclient.js +0 -310
  195. package/dist/mqttclient.js.map +0 -1
  196. package/dist/ui/featureoptions.js +0 -480
  197. package/dist/ui/featureoptions.js.map +0 -1
  198. package/dist/ui/webUi-featureoptions.mjs +0 -3765
@@ -0,0 +1,600 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * logclient/client.ts: AsyncDisposable Homebridge UI log client composing the auth, REST, socket, parser, and stitch transports into history/follow/tail streams.
4
+ */
5
+ /**
6
+ * AsyncDisposable client for the Homebridge UI log stream.
7
+ *
8
+ * {@link HomebridgeLogClient} is the subsystem-local composition root: it holds the connection configuration and the credentials, and it composes the pure leaf modules
9
+ * (`parser.ts`, `stitch.ts`, `time-window.ts`) with the transports (`auth.ts`, `rest.ts`, `socket.ts`) into the consumer-facing channels below, each returning a
10
+ * {@link LogStream}:
11
+ *
12
+ * - {@link HomebridgeLogClient.history} - a one-shot REST whole-file download parsed into records, optionally trimmed to the most recent N lines.
13
+ * - {@link HomebridgeLogClient.follow} - a live socket tail: the server's ~500-line seed followed by genuinely new lines, streamed indefinitely.
14
+ * - {@link HomebridgeLogClient.tail} - a {@link TailRequest}-driven dispatcher that selects `history`, `follow`, the socket-first `follow-history` join, or the
15
+ * hedged-seed time-bounded `window` channel.
16
+ *
17
+ * The following design points are load-bearing:
18
+ *
19
+ * - **Token lifecycle via a closure.** The client builds one {@link TokenProvider} closure over {@link acquireToken} and the stored credentials. The socket invokes it on
20
+ * every connect attempt, so a `password`/`noauth` credential re-authenticates from the stored credentials on each reconnect (surviving token expiry across a drop). A
21
+ * static `token` credential is returned verbatim with no network call, so it is never "refreshed"; an expired static token instead surfaces later when the socket
22
+ * handshake is rejected.
23
+ * - **Leak-free per-call teardown.** Each channel is an async generator wrapper, mirroring `mp4-assembler.ts`. It builds a per-call {@link LogSocketLike} through the
24
+ * injected {@link LogSocketFactory} seam under a per-call abort controller composed with the client's lifetime, and its `finally` disposes that socket and aborts the
25
+ * per-call controller. Both `await using stream = client.follow()` and an early `break` out of the iteration therefore tear the per-call socket down with no leak.
26
+ *
27
+ * Lifetime is a composed {@link AbortSignal}: the optional caller `signal` composed with the client's own controller. Disposing the client (or aborting the caller
28
+ * signal) aborts every in-flight channel, because each channel composes its per-call signal under the client's lifetime signal.
29
+ *
30
+ * @module
31
+ */
32
+ import { DEFAULT_HOST, DEFAULT_PORT, SEED_GATE_MAX_SKIP, SEED_QUIESCENCE_MS, SEED_SETTLE_MS, SEED_WINDOW_MAX_MS } from "./settings.js";
33
+ import { HbpuAbortError, Watchdog, composeSignals, noOpLog, takeLast } from "../util.js";
34
+ import { SeedGate, parseLogLine, parseLogTimestamp } from "./parser.js";
35
+ import { acquireToken } from "./auth.js";
36
+ import { downloadLog } from "./rest.js";
37
+ import { logSocketFactory } from "./socket.js";
38
+ import { stitchLive } from "./stitch.js";
39
+ import { timeWindow } from "./time-window.js";
40
+ /**
41
+ * AsyncDisposable client for the Homebridge UI log stream.
42
+ *
43
+ * @example
44
+ *
45
+ * ```ts
46
+ * import { HomebridgeLogClient } from "homebridge-plugin-utils";
47
+ *
48
+ * await using client = new HomebridgeLogClient({ credentials: { kind: "password", password: "secret", username: "admin" }, host: "localhost" });
49
+ *
50
+ * await using stream = client.tail({ mode: "follow-history", quantity: 200 });
51
+ *
52
+ * for await (const record of stream) {
53
+ *
54
+ * process.stdout.write(record.raw + "\n");
55
+ * }
56
+ * ```
57
+ *
58
+ * @category Log Client
59
+ */
60
+ export class HomebridgeLogClient {
61
+ /**
62
+ * The composed abort signal representing this client's lifetime. Aborts exactly once - when the client is disposed (`[Symbol.asyncDispose]`) or the parent
63
+ * signal fires - and `signal.reason` names the cause. Every channel composes its per-call signal under this one, so disposing the client tears down all in-flight
64
+ * channels.
65
+ */
66
+ signal;
67
+ // The private controller whose signal is composed into `this.signal`. Owned internally so the only teardown verb is disposal.
68
+ #controller;
69
+ #credentials;
70
+ #fetch;
71
+ #host;
72
+ #log;
73
+ #port;
74
+ // Whether the configured credential can mint a fresh token on a reconnect. A `password`/`noauth` credential re-authenticates from the stored credentials on each
75
+ // connect (refreshable); a static `token` is returned verbatim with nothing to refresh. Derived once at construction and handed to every per-call socket so a handshake
76
+ // auth rejection is classified terminal for a static token (fail fast) and retryable for refreshable credentials (the next attempt re-auths).
77
+ #refreshable;
78
+ #socketFactory;
79
+ #tls;
80
+ // The single token provider closure, built once at construction over the stored credentials. The socket invokes it on every connect attempt; `history()` invokes it
81
+ // once before the REST download. A `password`/`noauth` credential re-authenticates from the stored credentials on each call (surviving expiry across a reconnect); a
82
+ // static `token` is returned verbatim by `acquireToken` with no network call, so it is implicitly never refreshed.
83
+ #tokenProvider;
84
+ /**
85
+ * Construct a new log client.
86
+ *
87
+ * Construction performs no I/O: no connection is opened and no token is acquired until a channel is invoked. The token provider closure is built here so every channel
88
+ * shares one authentication path.
89
+ *
90
+ * @param options - Required options. See {@link HomebridgeLogClientOptions}.
91
+ */
92
+ constructor(options) {
93
+ this.#credentials = options.credentials;
94
+ this.#fetch = options.fetch;
95
+ this.#host = options.host ?? DEFAULT_HOST;
96
+ this.#log = options.log ?? noOpLog;
97
+ this.#port = options.port ?? DEFAULT_PORT;
98
+ // A static `token` credential has nothing to re-acquire, so it is non-refreshable; the other credential arms (`password`, `noauth`) re-authenticate on each connect.
99
+ this.#refreshable = options.credentials.kind !== "token";
100
+ this.#socketFactory = options.socketFactory ?? logSocketFactory;
101
+ this.#tls = options.tls ?? false;
102
+ this.#controller = new AbortController();
103
+ this.signal = composeSignals(options.signal, this.#controller.signal);
104
+ // Build the token provider once. The `signal` parameter is part of the `TokenProvider` contract (the socket forwards each connect attempt's signal so an in-flight
105
+ // acquisition can be cancelled), but `acquireToken` exposes no signal seam of its own today, so the closure does not forward it - hence the underscore prefix. Each
106
+ // invocation re-derives the token from the stored credentials, which is exactly the "re-auth on reconnect for password/noauth, return-verbatim for token" behavior
107
+ // the token lifecycle requires.
108
+ this.#tokenProvider = (_signal) => acquireToken(this.#credentials, { fetch: this.#fetch, host: this.#host, port: this.#port,
109
+ tls: this.#tls });
110
+ }
111
+ /**
112
+ * `AsyncDisposable` implementation. Aborts the client (defaulting to `"shutdown"`), which aborts every in-flight channel's per-call signal, so callers using
113
+ * `await using` are guaranteed all channels have begun tearing down by the time the block exits.
114
+ *
115
+ * @returns A resolved promise once the abort has been issued.
116
+ */
117
+ async [Symbol.asyncDispose]() {
118
+ if (!this.signal.aborted) {
119
+ this.#controller.abort(new HbpuAbortError("shutdown"));
120
+ }
121
+ }
122
+ /**
123
+ * `true` once `this.signal` has aborted. Derived from the signal; no independent state.
124
+ */
125
+ get aborted() {
126
+ return this.signal.aborted;
127
+ }
128
+ /**
129
+ * Retrieve historical log lines over the REST whole-file download channel.
130
+ *
131
+ * Streams `GET .../log/download` through the parser and yields each parsed {@link LogRecord}. When `quantity` is a number, only the most recent N records are retained
132
+ * (via {@link takeLast}, a bounded ring so a multi-MB log is never fully materialized); when it is `"all"` (the default), every record passes through. This is the
133
+ * deep-history channel paid only when the caller explicitly wants history beyond the socket's ~500-line seed.
134
+ *
135
+ * @param options - Optional per-call options.
136
+ * @param options.quantity - How many of the most recent records to retain. Defaults to `"all"`.
137
+ * @param options.signal - Optional per-call abort signal composed with the client's lifetime; aborting it terminates only this stream.
138
+ *
139
+ * @returns A {@link LogStream} of historical records, oldest first.
140
+ */
141
+ history(options = {}) {
142
+ const quantity = options.quantity ?? "all";
143
+ return this.#stream(options.signal, async function* (callSignal) {
144
+ yield* this.#history(quantity, callSignal);
145
+ });
146
+ }
147
+ /**
148
+ * Live-tail the log over the socket channel.
149
+ *
150
+ * Builds a {@link LogSocketLike} through the injected factory seam and yields each parsed {@link LogRecord} the server streams - the ~500-line seed first, then
151
+ * genuinely new lines indefinitely. The stream terminates only when the caller stops iterating (an early `break`, which disposes the socket), the per-call signal
152
+ * aborts, or the client is disposed.
153
+ *
154
+ * @param options - Optional per-call options.
155
+ * @param options.signal - Optional per-call abort signal composed with the client's lifetime; aborting it terminates only this stream.
156
+ *
157
+ * @returns A {@link LogStream} of live records.
158
+ */
159
+ follow(options = {}) {
160
+ return this.#stream(options.signal, async function* (callSignal) {
161
+ yield* this.#follow(callSignal);
162
+ });
163
+ }
164
+ /**
165
+ * Deliver log content over the channel selected by the {@link TailRequest} discriminated union.
166
+ *
167
+ * - `history` - delegates to {@link HomebridgeLogClient.history} with the request's quantity.
168
+ * - `follow` - delegates to {@link HomebridgeLogClient.follow}.
169
+ * - `follow-history` - the socket-first join: the socket connects and buffers its seed plus any live lines that arrive during the REST download into a bounded ring,
170
+ * then the REST history is downloaded and trimmed to the request's quantity, then the two are joined by {@link stitchLive} so the boundary overlap is removed without
171
+ * dropping a distinct live line, and finally the live stream continues. Connecting the socket first is what guarantees no live line produced during the download is
172
+ * lost.
173
+ * - `window` - the hedged-seed time-bounded channel: the socket connects and buffers its seed while a parallel abortable whole-file download runs, a strict-coverage
174
+ * gate decides whether the seed covers `[since, until]` (serve from the seed and abort the download) or not (fall back to the download, stitched with the seed),
175
+ * and the merged output is time-window-filtered. A one-shot window terminates when its content has been served; a `follow` window continues live.
176
+ *
177
+ * @param request - The request describing which content to deliver and over which channel. See {@link TailRequest}.
178
+ * @param options - Optional per-call options.
179
+ * @param options.signal - Optional per-call abort signal composed with the client's lifetime; aborting it terminates only this stream.
180
+ *
181
+ * @returns A {@link LogStream} for the selected channel.
182
+ */
183
+ tail(request, options = {}) {
184
+ switch (request.mode) {
185
+ case "follow": {
186
+ return this.follow(options);
187
+ }
188
+ case "follow-history": {
189
+ return this.#stream(options.signal, async function* (callSignal) {
190
+ yield* this.#followHistory(request.quantity, callSignal);
191
+ });
192
+ }
193
+ case "history": {
194
+ return this.history({ quantity: request.quantity, signal: options.signal });
195
+ }
196
+ case "window": {
197
+ return this.#stream(options.signal, async function* (callSignal) {
198
+ yield* this.#window(request, callSignal);
199
+ });
200
+ }
201
+ default: {
202
+ // The union is exhausted above; this satisfies exhaustiveness and guards against a future mode being added without a handler here.
203
+ throw new Error("Unsupported tail request mode.");
204
+ }
205
+ }
206
+ }
207
+ // Build a per-call abort controller composed with the client's lifetime and the caller's optional signal, then run `body` as the channel generator. The returned async
208
+ // generator is the `LogStream`: its `finally` aborts the per-call controller, so disposal (or an early `break`, which calls the generator's `return()`) tears the call
209
+ // down. Each channel body builds its own socket under `callSignal` and disposes it in its own `finally`; aborting the per-call controller here is the belt-and-braces
210
+ // upper bound that also unwinds a channel body that parked on a wait rather than on the socket. `body` is bound to `this` so the channel implementations read the
211
+ // client's private fields directly.
212
+ async *#stream(callerSignal, body) {
213
+ const callController = new AbortController();
214
+ const callSignal = composeSignals(this.signal, callerSignal, callController.signal);
215
+ try {
216
+ yield* body.call(this, callSignal);
217
+ }
218
+ finally {
219
+ // Abort the per-call controller on every exit path - normal completion, an early `break` (the generator's `return()` runs this `finally`), a thrown error, or
220
+ // client disposal. The channel body's own `finally` has already disposed its socket; this guarantees the per-call signal is settled regardless.
221
+ callController.abort(new HbpuAbortError("shutdown"));
222
+ }
223
+ }
224
+ // History channel implementation. Acquires a token, streams the REST whole-file download through the parser, and yields the records - all of them for `"all"`, or only
225
+ // the most recent N (via the bounded `takeLast` ring) for a numeric quantity. Shared by `history()` and by `tail()`'s `history` mode.
226
+ async *#history(quantity, callSignal) {
227
+ const records = this.#downloadRecords(callSignal);
228
+ if (typeof quantity === "number") {
229
+ // Retain only the most recent N records. `takeLast` drains the source into a fixed-capacity ring, so even a multi-MB history never grows the working set beyond N.
230
+ for (const record of await takeLast(records, quantity)) {
231
+ yield record;
232
+ }
233
+ return;
234
+ }
235
+ // `"all"` - pass every record through in file order.
236
+ yield* records;
237
+ }
238
+ // Wrap a socket's raw `stdout()` in a per-stream seed gate, dropping the leading lines the server's byte-offset seed makes unusable - the truncated first fragment and
239
+ // the `Loading logs...` / `File: ...` preamble - up to the first genuine log entry, then yielding every line thereafter. Every socket-sourced channel (follow,
240
+ // follow-history, window) funnels its live lines through here, so the seed hygiene is defined ONCE; the REST download path is clean from byte 0 and is deliberately not
241
+ // gated. The gate is a single per-call object, so it latches open once at the start of the stream and is a boolean short-circuit for the rest of the stream's life. It
242
+ // wraps a single `stdout()` iterator, preserving the socket's single-consumer contract, and forwards cleanup: an early `return` on this generator propagates to the
243
+ // underlying `stdout()` iteration, and disposing the socket ends `stdout()` and so ends this wrapper.
244
+ async *#gatedStdout(socket) {
245
+ const gate = new SeedGate(SEED_GATE_MAX_SKIP);
246
+ for await (const line of socket.stdout()) {
247
+ if (gate.admit(line)) {
248
+ yield line;
249
+ }
250
+ }
251
+ }
252
+ // Follow channel implementation. Builds a per-call socket through the factory seam and yields each parsed line. The socket is disposed in the `finally` so an early
253
+ // `break` or client disposal tears it down leak-free, mirroring the `await using` discipline `mp4-assembler.ts` relies on for its source.
254
+ async *#follow(callSignal) {
255
+ const socket = this.#createSocket(callSignal);
256
+ try {
257
+ for await (const line of this.#gatedStdout(socket)) {
258
+ yield parseLogLine(line);
259
+ }
260
+ }
261
+ finally {
262
+ // Dispose the per-call socket on every exit path. Disposal aborts the socket and awaits its reconnect loop, so the WebSocket is closed by the time this returns.
263
+ await socket[Symbol.asyncDispose]();
264
+ }
265
+ }
266
+ // Follow-history channel implementation - the socket-first join. The socket connects first (so no live line produced during the REST download is lost) and its seed
267
+ // plus any live lines that arrive during the download are buffered into a bounded ring; the REST history is downloaded and trimmed to `quantity`; the two are joined by
268
+ // `stitchLive` (minimal overlap, so no distinct live line is dropped); the stitched result is yielded; then the live stream continues. A single in-flight live pull is
269
+ // carried across the stitch boundary so a line that was requested-but-not-yet-arrived when the download finished becomes the first live-continuation line rather than
270
+ // being lost or double-counted.
271
+ async *#followHistory(quantity, callSignal) {
272
+ const socket = this.#createSocket(callSignal);
273
+ try {
274
+ // The single live iterator. The class is single-consumer, so exactly one iterator is pulled - here and, after the stitch, for the live continuation. We hold the
275
+ // in-flight `next()` promise across the buffering loop so the pull that is outstanding when history finishes is not discarded. The gate wrapper drops the seed's
276
+ // leading fragment/preamble so the stitch aligns clean seed lines against the clean REST history.
277
+ const liveIterator = this.#gatedStdout(socket)[Symbol.asyncIterator]();
278
+ // Start the REST history download in parallel with buffering the socket's seed-plus-live. Trimming to `quantity` happens inside so the resolved value is already
279
+ // the history tail to stitch against. The promise is observed below; until then it runs concurrently with the buffering race.
280
+ const historyPromise = this.#collectHistory(quantity, callSignal);
281
+ // Buffer the socket's seed plus any live lines that arrive while history downloads, into an ordered list. We race the outstanding live pull against the history
282
+ // download: each time the live pull resolves first, we record the line and issue the next pull; when the download resolves first, we stop buffering and carry the
283
+ // still-outstanding live pull forward as the first continuation pull.
284
+ const bufferedLive = [];
285
+ let pendingNext = liveIterator.next();
286
+ let liveDone = false;
287
+ const historyMarker = historyPromise.then(() => "history");
288
+ for (;;) {
289
+ // eslint-disable-next-line no-await-in-loop
290
+ const winner = await Promise.race([pendingNext.then(() => "live"), historyMarker]);
291
+ if (winner === "history") {
292
+ // History finished first. Stop buffering; `pendingNext` may still be outstanding and is carried into the continuation phase below.
293
+ break;
294
+ }
295
+ // eslint-disable-next-line no-await-in-loop
296
+ const result = await pendingNext;
297
+ if (result.done) {
298
+ // The socket ended before history finished (an abort or a terminal failure). There is no more live to buffer or continue.
299
+ liveDone = true;
300
+ break;
301
+ }
302
+ // Buffer the resolved live line (oldest-first) and carry the freshly-issued pull forward. This is the small primitive the windowed gate shares with this loop.
303
+ pendingNext = this.#bufferLine(bufferedLive, result.value, liveIterator);
304
+ }
305
+ // Join history and the buffered live at their minimal overlap, then yield the stitched result. `stitchLive` never drops a distinct live line; it may emit a bounded
306
+ // run of duplicates or a visible gap marker when no overlap is found.
307
+ const history = await historyPromise;
308
+ for (const record of stitchLive(history, bufferedLive)) {
309
+ yield record;
310
+ }
311
+ // Live continuation. First drain the pull that was outstanding when history finished (so the in-flight line is not lost), then continue pulling the live stream
312
+ // until it ends or the call is torn down.
313
+ if (!liveDone) {
314
+ const result = await pendingNext;
315
+ if (!result.done) {
316
+ yield parseLogLine(result.value);
317
+ for (;;) {
318
+ // eslint-disable-next-line no-await-in-loop
319
+ const next = await liveIterator.next();
320
+ if (next.done) {
321
+ break;
322
+ }
323
+ yield parseLogLine(next.value);
324
+ }
325
+ }
326
+ }
327
+ }
328
+ finally {
329
+ // Dispose the per-call socket on every exit path, exactly as the simple follow channel does.
330
+ await socket[Symbol.asyncDispose]();
331
+ }
332
+ }
333
+ // Windowed channel implementation - the hedged-seed time-bounded query. Connects the socket, captures the one-shot snapshot horizon, and wraps the merged record stream
334
+ // in the engine-owned `timeWindow` transform so the whole output is filtered to `[since, until]` in one pass (the carry-forward survives the seed -> live and
335
+ // the stitch -> live boundaries). The raw merged stream - the hedge, the strict-coverage gate, the seed-served-vs-download decision, and the wall-clock one-shot
336
+ // termination - is produced by `#windowRecords`; this wrapper owns only the socket lifetime and the time-window upper bound. `effectiveUntil` fills a null `until` with
337
+ // the snapshot horizon for a one-shot (so a bare `--since` is bounded at "now") but leaves an explicit `until` and a `follow` window's `until` untouched, so a future
338
+ // `--until` is never narrowed into emptiness.
339
+ async *#window(request, callSignal) {
340
+ const horizonNow = Date.now();
341
+ const effectiveUntil = request.follow ? request.until : (request.until ?? horizonNow);
342
+ const socket = this.#createSocket(callSignal);
343
+ try {
344
+ yield* timeWindow(this.#windowRecords(request, horizonNow, socket, callSignal), { since: request.since, until: effectiveUntil });
345
+ }
346
+ finally {
347
+ // Dispose the per-call socket on every exit path. `#windowRecords` has already cleared its own wall-clock timers in its own finally by the time this runs, so no
348
+ // `setTimeout` outlives the channel.
349
+ await socket[Symbol.asyncDispose]();
350
+ }
351
+ }
352
+ // The raw (pre-time-window) record stream for the windowed channel: the hedge setup, the bounded multi-phase coverage gate, the seed-vs-download branch, and the
353
+ // wall-clock one-shot terminator. Separate from `#window` so the gate's exit semantics - which diverge from `#followHistory` and ARE the channel's essence - read as
354
+ // their own dedicated race loops rather than being forced behind a shared callback; only the small buffer-and-carry primitive (`#bufferLine`), the download collector
355
+ // (`#collectHistory`), and the `Watchdog` are reused. `socket` is owned by `#window`; this method owns the download child controller and the terminator timers.
356
+ async *#windowRecords(request, horizonNow, socket, callSignal) {
357
+ const { follow, since } = request;
358
+ // The single live iterator. The socket is single-consumer, so exactly one iterator is pulled - here through the coverage gate and, on the seed-served path, for the
359
+ // live continuation. One in-flight `next()` is carried across the coverage gate so the pull outstanding when it decides is never discarded. The seed gate wrapper
360
+ // drops the seed's leading fragment/preamble upstream, so the first line this channel sees is a genuine entry and the coverage decision reads a real timestamp; the
361
+ // channel's own leading-orphan handling below remains as defense in depth and never assumes the gate ran.
362
+ const liveIterator = this.#gatedStdout(socket)[Symbol.asyncIterator]();
363
+ // A dedicated child controller composed under the call signal, so aborting it cancels ONLY the speculative download (reason `"replaced"`) and never the socket or the
364
+ // call - the socket must survive a seed-covers abort to keep serving the window.
365
+ const downloadController = new AbortController();
366
+ const downloadSignal = composeSignals(callSignal, downloadController.signal);
367
+ // Start the speculative whole-file download in parallel with buffering the seed. It is reused verbatim from the history path (`#collectHistory("all", ...)`), made
368
+ // abortable because `#collectHistory` forwards the download child controller's signal to `downloadLog`. Every observer attaches AT CREATION so none floats on any
369
+ // exit: the base `.catch` absorbs the rejection that arrives on a seed-covers abort or a genuine failure, and the derived `downloadTag` turns completion into a
370
+ // non-rejecting value the gate's race can win on.
371
+ const downloadPromise = this.#collectHistory("all", downloadSignal);
372
+ downloadPromise.catch(() => { });
373
+ const downloadTag = downloadPromise.then(() => ({ kind: "done" }), (error) => ({ error, kind: "failed" }));
374
+ // The seed buffered (oldest-first) while the gate decides. `liveDone` records the socket ending before the gate could leave the buffering loop.
375
+ const bufferedSeed = [];
376
+ let pendingNext = liveIterator.next();
377
+ let liveDone = false;
378
+ // The terminator state for a seed-served one-shot (armed only there). Held here so the finally clears both timers on every exit path - no `setTimeout` outlives
379
+ // the channel.
380
+ let watchdog;
381
+ let capTimer;
382
+ try {
383
+ // The gate, fused with the seed-plus-live buffering. A single loop buffers the seed (oldest-first) like `#followHistory`, while watching for the coverage
384
+ // decision: the first parseable-timestamp seed line decides whether the seed strictly covers `[since, until]`. If it covers, short-circuit to the seed branch
385
+ // and abort the download; if it does NOT (a deep window), keep buffering the FULL seed so the no-cover stitch has a real overlap region to align against - a
386
+ // single-line buffer would manufacture a spurious gap marker. The bounded Phase 2 handles a download that FAILS before coverage is decided (the seed may still
387
+ // cover); a failure once a deep / unbounded-below window is established surfaces the actionable error, because then neither source can serve.
388
+ let decision = "no-cover";
389
+ // An unbounded-below window (a bare `--until`) can never be covered by the recent ~500-line seed, so coverage is settled (no-cover) up front and seed timestamps
390
+ // are never inspected; otherwise the first parseable-timestamp seed line decides.
391
+ let coverageDecided = since === null;
392
+ for (;;) {
393
+ // eslint-disable-next-line no-await-in-loop
394
+ const winner = await Promise.race([pendingNext.then(() => "live"), downloadTag]);
395
+ if (winner !== "live") {
396
+ if (winner.kind === "done") {
397
+ // The whole-file download completed. It is authoritative and there is no stall left to save, so stop buffering and serve the no-cover fallback - never keep
398
+ // racing the now-settled tag (which would spin).
399
+ break;
400
+ }
401
+ // `kind === "failed"` (a systemd 400, a dead token, the server down). If coverage is not yet decided, the seed might still cover, so hand off to the bounded
402
+ // seed-only Phase 2. If a deep / unbounded-below window is already established, neither source can serve it, so surface the actionable error.
403
+ if (!coverageDecided && (since !== null)) {
404
+ // eslint-disable-next-line no-await-in-loop
405
+ const phase2 = await this.#windowGatePhase2({ bufferedSeed, horizonNow, latchedError: winner.error, liveIterator, pendingNext, since });
406
+ pendingNext = phase2.pendingNext;
407
+ decision = "seed-covers";
408
+ break;
409
+ }
410
+ throw winner.error;
411
+ }
412
+ // eslint-disable-next-line no-await-in-loop
413
+ const result = await pendingNext;
414
+ if (result.done) {
415
+ // The socket ended before the download finished. There is no more live to buffer or continue; fall to the no-cover fallback (which awaits the download).
416
+ liveDone = true;
417
+ break;
418
+ }
419
+ if (!coverageDecided) {
420
+ const record = parseLogLine(result.value);
421
+ const seedOldest = (record.timestamp !== null) ? parseLogTimestamp(record.timestamp) : null;
422
+ // Skip a leading null-epoch orphan (a continuation line, or an unrecognized-locale head); only a parseable timestamp decides coverage.
423
+ if (seedOldest !== null) {
424
+ coverageDecided = true;
425
+ // STRICT `<`: the seed is count-bounded and timestamps are whole-second, so an equal-boundary second could have older same-second lines truncated out of the
426
+ // seed; strict `<` guarantees nothing in-window was truncated, and an exact boundary falls back to the download.
427
+ if ((since !== null) && (seedOldest < since)) {
428
+ decision = "seed-covers";
429
+ // The seed covers the window, so the speculative download is wasted: abort ONLY it (the socket survives) with the supersession reason, consume the deciding
430
+ // line, and break to the seed-served branch.
431
+ downloadController.abort(new HbpuAbortError("replaced"));
432
+ pendingNext = this.#bufferLine(bufferedSeed, result.value, liveIterator);
433
+ break;
434
+ }
435
+ // A deep window: the seed does not reach back to `since`, so keep buffering the full seed for a correct stitch, like `#followHistory`. Fall through.
436
+ }
437
+ }
438
+ pendingNext = this.#bufferLine(bufferedSeed, result.value, liveIterator);
439
+ }
440
+ // Serve the decided branch.
441
+ if (decision === "seed-covers") {
442
+ // Seed-covered: serve the window from the socket - the buffered seed, then continue live from the SAME iterator. CONCATENATE; never `stitchLive` (there is a
443
+ // single source with no overlap, so a stitch would manufacture a spurious gap). The download is aborted (seed-covers) or failed (Phase 2) and is not awaited.
444
+ if (!follow) {
445
+ // Arm the wall-clock one-shot terminator: a re-armable quiescence `Watchdog` (held off until the settle floor so a sub-floor stall cannot truncate the
446
+ // seed) plus a hard cap, both ending the channel by aborting the socket so the live pull resolves `done`. Both timers are cleared in the finally.
447
+ const terminate = () => {
448
+ if (capTimer !== undefined) {
449
+ clearTimeout(capTimer);
450
+ capTimer = undefined;
451
+ }
452
+ watchdog?.[Symbol.dispose]();
453
+ socket.abort(new HbpuAbortError("timeout"));
454
+ };
455
+ watchdog = new Watchdog({ onFire: () => {
456
+ // Hold the terminator off until the settle floor so an intra-burst stall shorter than the floor cannot be mistaken for the end of the seed burst; past
457
+ // the floor, a full quiescence gap with no new source line ends the one-shot.
458
+ if (Date.now() < (horizonNow + SEED_SETTLE_MS)) {
459
+ watchdog?.arm();
460
+ return;
461
+ }
462
+ terminate();
463
+ }, signal: callSignal, timeoutMs: SEED_QUIESCENCE_MS });
464
+ // The hard cap fires SEED_WINDOW_MAX_MS after the horizon regardless of activity, so a perpetually-chatty log (whose post-horizon lines are filtered out
465
+ // of the window but still re-arm quiescence) cannot keep the one-shot open forever.
466
+ capTimer = setTimeout(terminate, Math.max(0, (horizonNow + SEED_WINDOW_MAX_MS) - Date.now()));
467
+ watchdog.arm();
468
+ }
469
+ for (const record of bufferedSeed) {
470
+ // Re-arm the quiescence terminator on every source line (a no-op for a `follow` window, which arms no terminator).
471
+ watchdog?.arm();
472
+ yield record;
473
+ }
474
+ if (!liveDone) {
475
+ // Drain the carried pull first (so the in-flight line is not lost), then continue the live stream. On a one-shot the terminator aborts the socket when the
476
+ // window has gone quiescent or the cap fires, which resolves the outstanding pull `done` and ends the loop; a `follow` window runs until the socket ends or the
477
+ // call is torn down.
478
+ let result = await pendingNext;
479
+ while (!result.done) {
480
+ watchdog?.arm();
481
+ yield parseLogLine(result.value);
482
+ // eslint-disable-next-line no-await-in-loop
483
+ result = await liveIterator.next();
484
+ }
485
+ }
486
+ }
487
+ else {
488
+ // No-cover fallback: the seed cannot cover the window (or unbounded-below, or the download already completed). Await the download - if it errored, its
489
+ // actionable message surfaces here - then join it with the buffered seed at their overlap and serve. `stitchLive` never drops a distinct live line.
490
+ const history = await downloadPromise;
491
+ for (const record of stitchLive(history, bufferedSeed)) {
492
+ yield record;
493
+ }
494
+ // A `follow` window continues live from the iterator; a one-shot ends here - the finite download already carries `[since, ~now]`, so the generator ends
495
+ // naturally once the stitched records are served, and there is no live tail to wall-clock-bound.
496
+ if (follow && !liveDone) {
497
+ let result = await pendingNext;
498
+ while (!result.done) {
499
+ yield parseLogLine(result.value);
500
+ // eslint-disable-next-line no-await-in-loop
501
+ result = await liveIterator.next();
502
+ }
503
+ }
504
+ }
505
+ }
506
+ finally {
507
+ // Clear BOTH wall-clock timers on every exit path - normal completion, the terminator firing, an early break, a thrown error, or call teardown - so no `setTimeout`
508
+ // survives to fire onto a torn-down channel. Disposal is idempotent, so a timer the terminator already cleared is harmless to clear again.
509
+ if (capTimer !== undefined) {
510
+ clearTimeout(capTimer);
511
+ }
512
+ watchdog?.[Symbol.dispose]();
513
+ }
514
+ }
515
+ // Phase 2 of the windowed gate, reached ONLY after the speculative download has failed: the seed is now the sole hope, so await the SAME outstanding pull alone (the
516
+ // settled download tag has been dropped from the race - a single-consumer iterator forbids a second concurrent `next()`), bounded by a wall-clock gate deadline set
517
+ // a hard cap (`SEED_WINDOW_MAX_MS`) past the snapshot horizon. A parseable seed line that strictly covers the window serves from the seed (returning the carried pull);
518
+ // a line that cannot cover, the deadline firing, or the socket ending with no parseable line all THROW the latched error (so an all-null / non-en-US seed against a
519
+ // dead download cannot hang). The deciding line is consumed into `bufferedSeed`.
520
+ async #windowGatePhase2(state) {
521
+ const { bufferedSeed, horizonNow, latchedError, liveIterator, since } = state;
522
+ // Continue with the outstanding pull Phase 1 left in flight; re-issue locally as the scan advances.
523
+ let pendingNext = state.pendingNext;
524
+ // The wall-clock gate deadline: bound the seed-only wait to the same hard cap, measured from the snapshot horizon, so a never-arriving parseable line cannot hang.
525
+ const deadline = Promise.withResolvers();
526
+ const deadlineTimer = setTimeout(() => deadline.resolve("deadline"), Math.max(0, (horizonNow + SEED_WINDOW_MAX_MS) - Date.now()));
527
+ try {
528
+ for (;;) {
529
+ // eslint-disable-next-line no-await-in-loop
530
+ const winner = await Promise.race([pendingNext.then(() => "live"), deadline.promise]);
531
+ if (winner === "deadline") {
532
+ // The seed never produced a parseable line within the cap. With the download already failed, there is nothing left to serve.
533
+ throw latchedError;
534
+ }
535
+ // eslint-disable-next-line no-await-in-loop
536
+ const result = await pendingNext;
537
+ if (result.done) {
538
+ // The socket ended with no parseable seed line and the download failed - surface the actionable error rather than hang.
539
+ throw latchedError;
540
+ }
541
+ const record = parseLogLine(result.value);
542
+ const seedOldest = (record.timestamp !== null) ? parseLogTimestamp(record.timestamp) : null;
543
+ if (seedOldest === null) {
544
+ // Skip a leading null-epoch orphan and keep scanning for the first parseable-timestamp line.
545
+ pendingNext = this.#bufferLine(bufferedSeed, result.value, liveIterator);
546
+ continue;
547
+ }
548
+ if (seedOldest < since) {
549
+ // The seed strictly covers the window: serve from the seed. Consume the deciding line, then return the fresh carried pull - exactly as Phase 1
550
+ // does before its own break. The pull is wrapped so the async return does NOT await it (the carry must stay in flight).
551
+ return { pendingNext: this.#bufferLine(bufferedSeed, result.value, liveIterator) };
552
+ }
553
+ // The seed cannot cover the window and the download failed - surface the actionable error.
554
+ throw latchedError;
555
+ }
556
+ }
557
+ finally {
558
+ clearTimeout(deadlineTimer);
559
+ }
560
+ }
561
+ // Buffer a resolved live line into `buffer` (oldest-first) and issue the next pull, returning the in-flight `next()`. The single primitive shared between
562
+ // the windowed gate and `#followHistory`: each carries exactly one outstanding pull across its buffering loop, so the parse-and-advance step lives in one place.
563
+ #bufferLine(buffer, value, liveIterator) {
564
+ buffer.push(parseLogLine(value));
565
+ return liveIterator.next();
566
+ }
567
+ // Collect the REST history into the tail to stitch against: the most recent N records for a numeric quantity (via the bounded `takeLast` ring), or every record for
568
+ // `"all"`. Returns a materialized array because the stitch needs random access to history's trailing window. For `"all"` the array is the whole file, which is the cost
569
+ // the caller accepted by asking for all of history.
570
+ async #collectHistory(quantity, callSignal) {
571
+ const records = this.#downloadRecords(callSignal);
572
+ if (typeof quantity === "number") {
573
+ return takeLast(records, quantity);
574
+ }
575
+ const all = [];
576
+ for await (const record of records) {
577
+ all.push(record);
578
+ }
579
+ return all;
580
+ }
581
+ // Acquire a token and stream the REST whole-file download through the parser, yielding each parsed record. The shared REST-to-records pipeline for both history paths.
582
+ // The call signal is forwarded to `downloadLog` so an aborted call (the windowed hedge superseding the speculative download, or a per-call teardown) cancels the
583
+ // in-flight request and releases the connection rather than letting it drain in the background.
584
+ async *#downloadRecords(callSignal) {
585
+ const token = await this.#tokenProvider(callSignal);
586
+ for await (const line of downloadLog({ fetch: this.#fetch, host: this.#host, port: this.#port, signal: callSignal, tls: this.#tls, token })) {
587
+ yield parseLogLine(line);
588
+ }
589
+ }
590
+ // Construct a per-call live-log socket through the injected factory seam, wired with the client's connection target, the shared token provider, and the per-call signal
591
+ // so the socket's lifetime is bounded by the call's. Routing construction through the seam (rather than `new LogSocket(...)`) is what lets a test substitute a fake
592
+ // socket and exercise the client without a WebSocket. `refreshable` is derived from the credential DU once at construction (a `password`/`noauth` credential re-
593
+ // authenticates on each connect, so a handshake rejection is transient; a static `token` cannot be refreshed, so a handshake rejection is permanent and the socket
594
+ // fails fast instead of retrying the same doomed token forever).
595
+ #createSocket(callSignal) {
596
+ return this.#socketFactory.create({ host: this.#host, log: this.#log, port: this.#port, refreshable: this.#refreshable, signal: callSignal, tls: this.#tls,
597
+ tokenProvider: this.#tokenProvider });
598
+ }
599
+ }
600
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/logclient/client.ts"],"names":[],"mappings":"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"}