homebridge-plugin-utils 1.34.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 +128 -0
  20. package/dist/backpressure.js +273 -0
  21. package/dist/backpressure.js.map +1 -0
  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 -257
  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 +70 -4
  55. package/dist/ffmpeg/fmp4.js +100 -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 +143 -91
  73. package/dist/ffmpeg/process.js +406 -265
  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 -560
  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 -244
  86. package/dist/ffmpeg/rtp.js.map +1 -1
  87. package/dist/ffmpeg/settings.d.ts +5 -1
  88. package/dist/ffmpeg/settings.js +20 -2
  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 -148
  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 -5
  100. package/dist/index.js +8 -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 +597 -45
  186. package/dist/util.js +787 -68
  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,141 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * clock-double.ts: A reusable, controllable Clock test double - virtual time a test advances explicitly - for the injectable Clock seam in clock.ts.
4
+ */
5
+ import { onAbort } from "./util.js";
6
+ /**
7
+ * Construct the rejection a {@link TestClock} `delay` produces when its signal aborts, matching `node:timers/promises` `setTimeout` exactly: a plain `Error` whose `name`
8
+ * is `"AbortError"` and whose `code` is the STRING `"ABORT_ERR"`. The real primitive's rejection is a dedicated internal class (not a `DOMException`, whose `code` is the
9
+ * numeric `20`, and there is no constructable `AbortError` global), so the double cannot match the constructor or prototype identity - it matches the observable `name`
10
+ * and `code` a consumer discriminates on, which is the contract that matters.
11
+ *
12
+ * @returns The `AbortError`-shaped rejection.
13
+ */
14
+ function abortError() {
15
+ const error = new Error("The operation was aborted");
16
+ error.name = "AbortError";
17
+ // `code` is not a standard `Error` field, so assign it through an indexed widening rather than declaring a one-off subclass. The STRING value is what the real
18
+ // `node:timers/promises` rejection carries and what a consumer's `error.code === "ABORT_ERR"` check reads.
19
+ error.code = "ABORT_ERR";
20
+ return error;
21
+ }
22
+ /**
23
+ * A controllable {@link Clock} double over virtual time. `now()` returns the current virtual time; `delay()` registers a pending wait that resolves only when
24
+ * {@link TestClock.advance} crosses its deadline (in ascending-deadline order), or rejects with an `AbortError` (matching `node:timers/promises` `setTimeout` - `name`
25
+ * `"AbortError"`, `code` `"ABORT_ERR"`, NOT the signal's reason) when its signal aborts. No real timers or wall-clock are used.
26
+ *
27
+ * The virtual time is a RELATIVE timeline seeded at `start` (default `0`), NOT real epoch milliseconds. A consumer that compares `now()` against an absolute real-epoch
28
+ * constant would diverge; consumers must only compare `now()` values to each other (deriving elapsed intervals from differences), which is the only use a
29
+ * `Date.now()`-style read serves in the consuming pacing path - all its time reads come from the one injected clock.
30
+ *
31
+ * @example
32
+ *
33
+ * ```ts
34
+ * import { TestClock } from "homebridge-plugin-utils";
35
+ *
36
+ * const clock = new TestClock();
37
+ *
38
+ * const waited = clock.delay(100);
39
+ *
40
+ * // Nothing resolves until virtual time crosses the deadline.
41
+ * clock.advance(100);
42
+ *
43
+ * await waited;
44
+ * ```
45
+ *
46
+ * @see Clock
47
+ *
48
+ * @category Testing
49
+ */
50
+ export class TestClock {
51
+ // The current virtual time. Seeded by the constructor and moved only by `advance`.
52
+ #now;
53
+ // The registered, not-yet-settled delays. An entry leaves this list exactly once - either when `advance` crosses its deadline or when its signal aborts - via
54
+ // `#remove`, which splices by identity so a mixed resolve-and-abort sequence never strands or mis-removes an entry.
55
+ #pending = [];
56
+ /**
57
+ * Construct a clock seeded at `start` (default `0`). The seed is the initial value `now()` returns; `advance` moves it forward (or back, for a negative delta).
58
+ *
59
+ * @param start - The initial virtual time, in the consumer's relative timeline. Defaults to `0`.
60
+ */
61
+ constructor(start = 0) {
62
+ this.#now = start;
63
+ }
64
+ /**
65
+ * Advance virtual time by `ms` and resolve every delay whose deadline the new time has reached. The delta is applied regardless of sign, so a negative `ms` moves time
66
+ * backward; `advance(0)` moves time nowhere but STILL flushes any already-due entry (a `delay(0)` or a `delay` with a non-positive `ms`), so a zero or negative delay
67
+ * is never a lost wakeup.
68
+ *
69
+ * Due entries resolve in ASCENDING deadline order; entries that share a deadline keep their FIFO registration order, because the snapshot is taken before any removal
70
+ * and the numeric sort is stable - matching how `setTimeout` fires equal-deadline timers in scheduling order. Each due entry is removed by identity and has its abort
71
+ * listener detached before it resolves, so the resolve path leaks no listener and the iteration is immune to the index shifts a forward in-place splice would cause.
72
+ *
73
+ * @param ms - The amount of virtual time to advance, in milliseconds. May be zero or negative.
74
+ */
75
+ advance(ms) {
76
+ this.#now += ms;
77
+ // Snapshot the due entries BEFORE mutating `#pending`, then sort them into deadline order. Filtering off a live array while removing from it would shift indices and
78
+ // strand entries; the snapshot first decouples the iteration from the removal. The sort is a stable numeric comparator, so equal deadlines preserve FIFO order.
79
+ const due = this.#pending.filter((entry) => entry.deadline <= this.#now).sort((a, b) => a.deadline - b.deadline);
80
+ for (const entry of due) {
81
+ // Remove by identity first so a re-entrant observer sees the correct `pending` count, then detach the abort listener (present only when this delay had a signal) so
82
+ // the resolve path leaves no listener on a long-lived signal, then settle the caller's promise.
83
+ this.#remove(entry);
84
+ entry.dispose?.[Symbol.dispose]();
85
+ entry.resolve();
86
+ }
87
+ }
88
+ /**
89
+ * Register a delay that resolves when virtual time reaches `this.now() + ms`, or rejects with an `AbortError` (matching `node:timers/promises`) if `init.signal` aborts
90
+ * first. A non-positive `ms` yields a deadline at or before the current time, which the very next {@link TestClock.advance} (including `advance(0)`) flushes.
91
+ *
92
+ * A pre-aborted signal rejects on the executor's microtask exactly as `systemClock` does (NOT a synchronous throw): {@link onAbort} fires the handler inline, which
93
+ * removes the just-registered entry and rejects, so the entry never lingers in `pending`.
94
+ *
95
+ * @param ms - The delay, in milliseconds. May be zero or negative (flushed by the next `advance`).
96
+ * @param init - Optional init options. A supplied `signal` rejects the wait with an `AbortError` when it aborts.
97
+ *
98
+ * @returns A promise that resolves when the deadline is crossed, or rejects with an `AbortError` if the signal aborts first.
99
+ */
100
+ delay(ms, init) {
101
+ const { promise, reject, resolve } = Promise.withResolvers();
102
+ const entry = { deadline: this.#now + ms, resolve };
103
+ // Register the entry FIRST so a pre-aborted signal's inline `onAbort` handler (below) can find and remove it. `onAbort` runs the handler synchronously when the
104
+ // signal is already aborted, so for a pre-aborted signal the entry is pushed and then immediately removed-and-rejected within this call - settling on the executor's
105
+ // microtask, identical to `systemClock`. When no signal is supplied, `dispose` stays undefined and the entry only ever leaves via `advance`.
106
+ this.#pending.push(entry);
107
+ if (init?.signal !== undefined) {
108
+ entry.dispose = onAbort(init.signal, () => {
109
+ this.#remove(entry);
110
+ reject(abortError());
111
+ });
112
+ }
113
+ return promise;
114
+ }
115
+ /**
116
+ * Return the current virtual time. Compare these values to each other to derive elapsed intervals - they are a relative timeline, not real epoch milliseconds.
117
+ *
118
+ * @returns The current virtual time.
119
+ */
120
+ now() {
121
+ return this.#now;
122
+ }
123
+ /**
124
+ * The number of registered delays that have neither resolved nor rejected. A test reads this to assert a consumer registered its waits and later cleared them (no
125
+ * leak).
126
+ *
127
+ * @returns The count of unsettled delays.
128
+ */
129
+ get pending() {
130
+ return this.#pending.length;
131
+ }
132
+ // Remove `entry` from `#pending` by identity. Idempotent: a guarded `indexOf` + `splice` makes a second removal (e.g. an abort that races a resolve) a safe no-op and
133
+ // never removes the wrong entry, so `#pending` stays consistent across any resolve-and-abort interleaving.
134
+ #remove(entry) {
135
+ const index = this.#pending.indexOf(entry);
136
+ if (index !== -1) {
137
+ this.#pending.splice(index, 1);
138
+ }
139
+ }
140
+ }
141
+ //# sourceMappingURL=clock-double.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock-double.js","sourceRoot":"","sources":["../src/clock-double.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiBH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC;;;;;;;GAOG;AACH,SAAS,UAAU;IAEjB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAErD,KAAK,CAAC,IAAI,GAAG,YAAY,CAAC;IAE1B,+JAA+J;IAC/J,2GAA2G;IAC1G,KAAkC,CAAC,IAAI,GAAG,WAAW,CAAC;IAEvD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,OAAO,SAAS;IAEpB,mFAAmF;IACnF,IAAI,CAAS;IAEb,8JAA8J;IAC9J,oHAAoH;IAC3G,QAAQ,GAAiB,EAAE,CAAC;IAErC;;;;OAIG;IACH,YAAmB,KAAK,GAAG,CAAC;QAE1B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;IACpB,CAAC;IAED;;;;;;;;;;OAUG;IACI,OAAO,CAAC,EAAU;QAEvB,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAEhB,qKAAqK;QACrK,gKAAgK;QAChK,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QAEjH,KAAI,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;YAEvB,oKAAoK;YACpK,gGAAgG;YAChG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACI,KAAK,CAAC,EAAU,EAAE,IAA+B;QAEtD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAA+B,OAAO,CAAC,aAAa,EAAE,CAAC;QACzF,MAAM,KAAK,GAAe,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC;QAEhE,gKAAgK;QAChK,qKAAqK;QACrK,6IAA6I;QAC7I,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE1B,IAAG,IAAI,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;YAE9B,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;gBAExC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACpB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACI,GAAG;QAER,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACH,IAAW,OAAO;QAEhB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,sKAAsK;IACtK,2GAA2G;IAC3G,OAAO,CAAC,KAAiB;QAEvB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAE3C,IAAG,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YAEhB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The injectable wall-clock contract: the platform time primitives time-dependent code reads. A consumer holds a `Clock` rather than calling `Date.now()` /
3
+ * `node:timers/promises` `setTimeout` directly, so a test can substitute a controllable double (`TestClock`) and drive time deterministically while production behavior
4
+ * stays unchanged through {@link systemClock}.
5
+ *
6
+ * @see systemClock
7
+ *
8
+ * @category Utilities
9
+ */
10
+ export interface Clock {
11
+ /**
12
+ * Resolve after `ms` milliseconds, or reject if `init.signal` aborts first. The production {@link systemClock} implements this as `node:timers/promises` `setTimeout`,
13
+ * so an abort rejects with that primitive's `AbortError` (`name` `"AbortError"`, `code` `"ABORT_ERR"`) rather than the signal's reason.
14
+ *
15
+ * @param ms - The delay, in milliseconds.
16
+ * @param init - Optional init options. `signal` cancels the delay - resolving the wait early with a rejection - when it aborts.
17
+ *
18
+ * @returns A promise that resolves after the delay, or rejects with an `AbortError` if the signal aborts first.
19
+ */
20
+ delay(ms: number, init?: {
21
+ signal?: AbortSignal;
22
+ }): Promise<void>;
23
+ /**
24
+ * Return the current time as epoch milliseconds. The production {@link systemClock} implements this as `Date.now()`.
25
+ *
26
+ * @returns The current time in epoch milliseconds.
27
+ */
28
+ now(): number;
29
+ }
30
+ /**
31
+ * The behavior-neutral production {@link Clock}: `now()` IS `Date.now()` and `delay()` IS `node:timers/promises` `setTimeout`. A consumer that routes its time reads
32
+ * through this clock instead of calling those primitives directly cannot observe any behavior change - it is the same two calls, one indirection removed at test time.
33
+ *
34
+ * @see Clock
35
+ *
36
+ * @category Utilities
37
+ */
38
+ export declare const systemClock: Clock;
39
+ //# sourceMappingURL=clock.d.ts.map
package/dist/clock.js ADDED
@@ -0,0 +1,34 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * clock.ts: An injectable wall-clock seam - the current time and an abortable delay - so time-dependent code can be driven deterministically under test.
4
+ */
5
+ /**
6
+ * An injectable wall-clock time seam.
7
+ *
8
+ * Time-dependent code reads the platform time primitives it needs: the current epoch time (`Date.now()`) and a delay that can be cancelled (`node:timers/promises`
9
+ * `setTimeout`). Calling those directly bakes real wall-clock time into the code, so a test cannot exercise a pacing or timeout path without multi-second real waits, and
10
+ * `node:test`'s mock timers do not patch the `node:timers/promises` primitives. Holding a {@link Clock} instead - the abstraction over those primitives - inverts the
11
+ * dependency: production wires {@link systemClock}, whose `now()` IS `Date.now()` and whose `delay()` IS `node:timers/promises` `setTimeout`, so routing through the seam
12
+ * is behavior-neutral; a test wires a `TestClock` (see `clock-double.ts`) that advances virtual time explicitly, so the consumer's time-dependent path runs
13
+ * deterministically and instantly.
14
+ *
15
+ * This module imports `node:timers/promises` and is therefore Node-only (not browser-safe), like `util.ts`. A browser-targeted consumer cannot resolve that import.
16
+ *
17
+ * @module
18
+ */
19
+ import { setTimeout as delay } from "node:timers/promises";
20
+ /**
21
+ * The behavior-neutral production {@link Clock}: `now()` IS `Date.now()` and `delay()` IS `node:timers/promises` `setTimeout`. A consumer that routes its time reads
22
+ * through this clock instead of calling those primitives directly cannot observe any behavior change - it is the same two calls, one indirection removed at test time.
23
+ *
24
+ * @see Clock
25
+ *
26
+ * @category Utilities
27
+ */
28
+ export const systemClock = {
29
+ // `setTimeout(ms, value, options)` from `node:timers/promises`: pass `undefined` for the resolution value and forward `init` as the options so the caller's signal
30
+ // cancels the wait. `setTimeout(ms, undefined, opts)` resolves to `Promise<undefined>`, which is assignable to the `Promise<void>` the contract declares.
31
+ delay: (ms, init) => delay(ms, undefined, init),
32
+ now: () => Date.now()
33
+ };
34
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAgC3D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAU;IAEhC,mKAAmK;IACnK,0JAA0J;IAC1J,KAAK,EAAE,CAAC,EAAU,EAAE,IAA+B,EAAiB,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC;IACjG,GAAG,EAAE,GAAW,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;CAC9B,CAAC"}
@@ -0,0 +1,193 @@
1
+ export default config;
2
+ /**
3
+ * Build a ready-to-use ESLint flat config array. Consumers call this function and export default the result.
4
+ *
5
+ * Every option is optional and defaults to an empty array, mirrored by the destructuring defaults in the signature below. The JSDoc marks them optional with the
6
+ * bracket form so the emitted declaration types each property as optional, matching how consumers actually call this - passing only the subset they need.
7
+ *
8
+ * @param {object} [options] - Configuration options.
9
+ * @param {string[]} [options.ts] - Glob patterns for TypeScript files (strict + stylistic type-checked rules).
10
+ * @param {string[]} [options.js] - Glob patterns for JavaScript files (disable-type-checked rules).
11
+ * @param {string[]} [options.ui] - Glob patterns for browser UI files (adds browser globals).
12
+ * @param {string[]} [options.allowDefaultProject] - Globs passed to parserOptions.projectService.allowDefaultProject.
13
+ * @param {string[]} [options.ignores] - Global ignore patterns (in addition to the hardcoded "dist" ignore on the common block).
14
+ * @param {object[]} [options.extraConfigs] - Additional flat config objects appended to the output.
15
+ * @returns {object[]} A flat config array suitable for `export default` in eslint.config.mjs. Block ordering is significant - do not reorder.
16
+ */
17
+ export function config({ allowDefaultProject, extraConfigs, ignores, js, ts: tsFiles, ui }?: {
18
+ ts?: string[] | undefined;
19
+ js?: string[] | undefined;
20
+ ui?: string[] | undefined;
21
+ allowDefaultProject?: string[] | undefined;
22
+ ignores?: string[] | undefined;
23
+ extraConfigs?: any[] | undefined;
24
+ }): object[];
25
+ /**
26
+ * Rule preset applied to every linted file regardless of language. Disables the base ESLint rules that are redundant once TypeScript's own compiler
27
+ * and syntax already catch the same errors, mirroring the `typescript-eslint` compatibility overlay it spreads in; the enabled ESLint-recommended
28
+ * baseline itself comes from the separate `eslintJs.configs.recommended` block pushed in {@link config}. Layers on top of that the full `@hjdhjd/*`
29
+ * rule set, the `@stylistic/*` whitespace and formatting rules, and the project's opinionated `sort-imports` / `sort-keys` / `quotes` / `eqeqeq` /
30
+ * `curly` constraints.
31
+ *
32
+ * Spread into the `rules:` slot of the all-files block of a flat config.
33
+ */
34
+ export const commonRules: {
35
+ "@hjdhjd/blank-line-after-open-brace": string;
36
+ "@hjdhjd/comment-style": string;
37
+ "@hjdhjd/enforce-node-protocol": string;
38
+ "@hjdhjd/paren-comparisons-in-logical": string;
39
+ "@hjdhjd/split-type-imports": string;
40
+ "@stylistic/array-bracket-spacing": (string | {
41
+ arraysInArrays: boolean;
42
+ objectsInArrays: boolean;
43
+ singleValue: boolean;
44
+ })[];
45
+ "@stylistic/block-spacing": string;
46
+ "@stylistic/brace-style": (string | {
47
+ allowSingleLine: boolean;
48
+ })[];
49
+ "@stylistic/comma-dangle": string;
50
+ "@stylistic/eol-last": string[];
51
+ "@stylistic/generator-star-spacing": string;
52
+ "@stylistic/implicit-arrow-linebreak": string;
53
+ "@stylistic/indent": (string | number | {
54
+ SwitchCase: number;
55
+ })[];
56
+ "@stylistic/keyword-spacing": (string | {
57
+ overrides: {
58
+ for: {
59
+ after: boolean;
60
+ };
61
+ if: {
62
+ after: boolean;
63
+ };
64
+ switch: {
65
+ after: boolean;
66
+ };
67
+ while: {
68
+ after: boolean;
69
+ };
70
+ };
71
+ })[];
72
+ "@stylistic/linebreak-style": string[];
73
+ "@stylistic/lines-between-class-members": (string | {
74
+ exceptAfterSingleLine: boolean;
75
+ })[];
76
+ "@stylistic/max-len": (string | number)[];
77
+ "@stylistic/no-tabs": string;
78
+ "@stylistic/no-trailing-spaces": string;
79
+ "@stylistic/operator-linebreak": (string | {
80
+ overrides: {
81
+ ":": string;
82
+ "?": string;
83
+ };
84
+ })[];
85
+ "@stylistic/padding-line-between-statements": (string | {
86
+ blankLine: string;
87
+ next: string[];
88
+ prev: string;
89
+ } | {
90
+ blankLine: string;
91
+ next: string;
92
+ prev: string[];
93
+ } | {
94
+ blankLine: string;
95
+ next: string[];
96
+ prev: string[];
97
+ } | {
98
+ blankLine: string;
99
+ next: string;
100
+ prev: string;
101
+ })[];
102
+ "@stylistic/semi": string[];
103
+ "@stylistic/space-before-function-paren": (string | {
104
+ anonymous: string;
105
+ asyncArrow: string;
106
+ catch: string;
107
+ named: string;
108
+ })[];
109
+ "@stylistic/space-in-parens": string;
110
+ "@stylistic/space-infix-ops": string;
111
+ "@stylistic/space-unary-ops": string;
112
+ "@typescript-eslint/dot-notation": (string | {
113
+ allowIndexSignaturePropertyAccess: boolean;
114
+ })[];
115
+ "@typescript-eslint/no-this-alias": string;
116
+ camelcase: string;
117
+ curly: string[];
118
+ "dot-notation": string;
119
+ eqeqeq: string;
120
+ "logical-assignment-operators": (string | {
121
+ enforceForIfStatements: boolean;
122
+ })[];
123
+ "no-await-in-loop": string;
124
+ "no-console": string;
125
+ "no-restricted-syntax": string[];
126
+ "prefer-arrow-callback": string;
127
+ "prefer-const": string;
128
+ quotes: (string | {
129
+ allowTemplateLiterals: boolean;
130
+ avoidEscape: boolean;
131
+ })[];
132
+ "sort-imports": string;
133
+ "sort-keys": string;
134
+ "sort-vars": string;
135
+ };
136
+ /**
137
+ * Browser-environment globals typically present in Homebridge webUI files - `window`, `document`, `fetch`, the Homebridge configuration API entry
138
+ * point, and a few timer functions. Each is declared as `"readonly"` so `no-undef` accepts references without permitting reassignment.
139
+ *
140
+ * Pass into the `languageOptions.globals` slot of a flat config block scoped to UI files.
141
+ */
142
+ export const globalsUi: {
143
+ [k: string]: string;
144
+ };
145
+ /**
146
+ * Rule preset for JavaScript source files. Starts from `typescript-eslint`'s `disableTypeChecked` set (so type-aware rules don't fire on plain JS), then
147
+ * re-enables `no-unused-vars` with the underscore-prefix ignore pattern. The `require-await` rule is left disabled here for the same reason the TypeScript
148
+ * preset omits its pair - see the omission paragraph in the {@link tsRules} JSDoc.
149
+ *
150
+ * Spread into the `rules:` slot of a flat config block scoped to `.js` / `.mjs` files.
151
+ */
152
+ export const jsRules: {
153
+ "@typescript-eslint/no-floating-promises": string;
154
+ "no-unused-vars": (string | {
155
+ argsIgnorePattern: string;
156
+ caughtErrors: string;
157
+ caughtErrorsIgnorePattern: string;
158
+ varsIgnorePattern: string;
159
+ })[];
160
+ };
161
+ /**
162
+ * The ESLint plugin namespace map used by the composed configuration: `@hjdhjd` (this package's rules), `@stylistic` (the official stylistic-rules
163
+ * plugin), and `@typescript-eslint`. Drop this object into the `plugins` slot of a flat config block to register every plugin namespace this preset
164
+ * composes at once.
165
+ *
166
+ * The type annotation is deliberately loose since consumers that read the named `plugins` export do their own narrowing before touching rule entries.
167
+ * It's annotated explicitly so the emitted type stays portable - the raw inference picks up typescript-eslint's exported `CompatiblePlugin` shape,
168
+ * and the `tsconfig.eslint-plugin.json` declaration-only build (TS2742) refuses to serialize it into the `.d.ts` without citing a deep module path.
169
+ *
170
+ * @type {Record<string, { meta?: { name: string; version?: string }; rules?: Record<string, unknown> }>}
171
+ */
172
+ export const plugins: Record<string, {
173
+ meta?: {
174
+ name: string;
175
+ version?: string;
176
+ };
177
+ rules?: Record<string, unknown>;
178
+ }>;
179
+ /**
180
+ * Rule preset for TypeScript source files. Builds on `typescript-eslint`'s strict and stylistic type-checked configs, then layers project-specific
181
+ * overrides: type-aware lint rules (`await-thenable`, `no-floating-promises`, `prefer-nullish-coalescing`, etc.) at warn level, explicit return-type and
182
+ * module-boundary requirements, and `@stylistic/member-delimiter-style`. Disables the JavaScript-recommended versions of rules that have TypeScript-aware
183
+ * equivalents to avoid double-flagging.
184
+ *
185
+ * Deliberately omits `@typescript-eslint/promise-function-async` and forces `@typescript-eslint/require-await` (and the base `require-await`) off. The pair
186
+ * encodes a tight coupling between Promise return types and the `async` keyword that predates the Disposable protocol: `[Symbol.asyncDispose]()` must be declared
187
+ * `async ...(): Promise<void>` to satisfy `await using` even when the body is synchronous, and identity-preserving Promise pass-throughs (e.g., `markHandled` in
188
+ * `src/util.ts`) cannot be marked `async` without wrapping the return in a fresh chain and breaking reference equality. Encoding the stance at the preset level
189
+ * rather than scattering per-site `eslint-disable` directives is the SSOT.
190
+ *
191
+ * Spread into the `rules:` slot of a flat config block scoped to `.ts` files.
192
+ */
193
+ export const tsRules: any;
@@ -0,0 +1,2 @@
1
+ export { default as plugin } from "./plugin.mjs";
2
+ export { commonRules, config, globalsUi, jsRules, plugins, tsRules, default } from "./config.mjs";
@@ -0,0 +1,87 @@
1
+ export default plugin;
2
+ declare namespace plugin {
3
+ namespace meta {
4
+ let name: string;
5
+ let version: string;
6
+ }
7
+ let rules: {
8
+ "blank-line-after-open-brace": {
9
+ create(context: any): {
10
+ BlockStatement(node: any): void;
11
+ ClassBody(node: any): void;
12
+ ObjectExpression(node: any): void;
13
+ TSInterfaceBody(node: any): void;
14
+ TSTypeAliasDeclaration(node: any): void;
15
+ };
16
+ meta: {
17
+ docs: {
18
+ description: string;
19
+ recommended: boolean;
20
+ };
21
+ fixable: string;
22
+ schema: never[];
23
+ type: string;
24
+ };
25
+ };
26
+ "comment-style": {
27
+ create(context: any): {
28
+ Program(): void;
29
+ };
30
+ meta: {
31
+ docs: {
32
+ description: string;
33
+ recommended: boolean;
34
+ };
35
+ fixable: string;
36
+ schema: never[];
37
+ type: string;
38
+ };
39
+ };
40
+ "enforce-node-protocol": {
41
+ create(context: any): {
42
+ ExportAllDeclaration(node: any): void;
43
+ ExportNamedDeclaration(node: any): void;
44
+ ImportDeclaration(node: any): void;
45
+ ImportExpression(node: any): void;
46
+ };
47
+ meta: {
48
+ docs: {
49
+ description: string;
50
+ recommended: boolean;
51
+ };
52
+ fixable: string;
53
+ schema: never[];
54
+ type: string;
55
+ };
56
+ };
57
+ "paren-comparisons-in-logical": {
58
+ create(context: any): {
59
+ LogicalExpression(node: any): void;
60
+ };
61
+ meta: {
62
+ docs: {
63
+ description: string;
64
+ recommended: boolean;
65
+ };
66
+ fixable: string;
67
+ schema: never[];
68
+ type: string;
69
+ };
70
+ };
71
+ "split-type-imports": {
72
+ create(context: any): {
73
+ ExportNamedDeclaration(node: any): void;
74
+ ImportDeclaration(node: any): void;
75
+ };
76
+ meta: {
77
+ docs: {
78
+ description: string;
79
+ recommended: boolean;
80
+ };
81
+ fixable: string;
82
+ schema: never[];
83
+ type: string;
84
+ };
85
+ };
86
+ };
87
+ }
@@ -0,0 +1,19 @@
1
+ export default ruleBlankAfterOpenBrace;
2
+ declare namespace ruleBlankAfterOpenBrace {
3
+ function create(context: any): {
4
+ BlockStatement(node: any): void;
5
+ ClassBody(node: any): void;
6
+ ObjectExpression(node: any): void;
7
+ TSInterfaceBody(node: any): void;
8
+ TSTypeAliasDeclaration(node: any): void;
9
+ };
10
+ namespace meta {
11
+ namespace docs {
12
+ let description: string;
13
+ let recommended: boolean;
14
+ }
15
+ let fixable: string;
16
+ let schema: never[];
17
+ let type: string;
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+ export default ruleCommentStyle;
2
+ declare namespace ruleCommentStyle {
3
+ function create(context: any): {
4
+ Program(): void;
5
+ };
6
+ namespace meta {
7
+ namespace docs {
8
+ let description: string;
9
+ let recommended: boolean;
10
+ }
11
+ let fixable: string;
12
+ let schema: never[];
13
+ let type: string;
14
+ }
15
+ }
@@ -0,0 +1,18 @@
1
+ export default ruleEnforceNodeProtocol;
2
+ declare namespace ruleEnforceNodeProtocol {
3
+ function create(context: any): {
4
+ ExportAllDeclaration(node: any): void;
5
+ ExportNamedDeclaration(node: any): void;
6
+ ImportDeclaration(node: any): void;
7
+ ImportExpression(node: any): void;
8
+ };
9
+ namespace meta {
10
+ namespace docs {
11
+ let description: string;
12
+ let recommended: boolean;
13
+ }
14
+ let fixable: string;
15
+ let schema: never[];
16
+ let type: string;
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ export default ruleParenComparisonsInLogical;
2
+ declare namespace ruleParenComparisonsInLogical {
3
+ function create(context: any): {
4
+ LogicalExpression(node: any): void;
5
+ };
6
+ namespace meta {
7
+ namespace docs {
8
+ let description: string;
9
+ let recommended: boolean;
10
+ }
11
+ let fixable: string;
12
+ let schema: never[];
13
+ let type: string;
14
+ }
15
+ }
@@ -0,0 +1,16 @@
1
+ export default ruleSplitTypeImports;
2
+ declare namespace ruleSplitTypeImports {
3
+ function create(context: any): {
4
+ ExportNamedDeclaration(node: any): void;
5
+ ImportDeclaration(node: any): void;
6
+ };
7
+ namespace meta {
8
+ namespace docs {
9
+ let description: string;
10
+ let recommended: boolean;
11
+ }
12
+ let fixable: string;
13
+ let schema: never[];
14
+ let type: string;
15
+ }
16
+ }