@hoox-sh/hoox-shared 1.2.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 (161) hide show
  1. package/README.md +44 -0
  2. package/dist/agent-config-fields.d.ts +32 -0
  3. package/dist/agent-config-fields.d.ts.map +1 -0
  4. package/dist/agent-config-fields.js +220 -0
  5. package/dist/analytics.d.ts +47 -0
  6. package/dist/analytics.d.ts.map +1 -0
  7. package/dist/analytics.js +45 -0
  8. package/dist/api-client.d.ts +48 -0
  9. package/dist/api-client.d.ts.map +1 -0
  10. package/dist/api-client.js +191 -0
  11. package/dist/colors.d.ts +76 -0
  12. package/dist/colors.d.ts.map +1 -0
  13. package/dist/colors.js +82 -0
  14. package/dist/config.d.ts +61 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +146 -0
  17. package/dist/cron-handler.d.ts +38 -0
  18. package/dist/cron-handler.d.ts.map +1 -0
  19. package/dist/cron-handler.js +48 -0
  20. package/dist/d1/index.d.ts +9 -0
  21. package/dist/d1/index.d.ts.map +1 -0
  22. package/dist/d1/index.js +360 -0
  23. package/dist/d1/repository.d.ts +86 -0
  24. package/dist/d1/repository.d.ts.map +1 -0
  25. package/dist/d1/repository.js +297 -0
  26. package/dist/d1/schemas.d.ts +89 -0
  27. package/dist/d1/schemas.d.ts.map +1 -0
  28. package/dist/d1/schemas.js +81 -0
  29. package/dist/dashboard-manifest.d.ts +135 -0
  30. package/dist/dashboard-manifest.d.ts.map +1 -0
  31. package/dist/dashboard-manifest.js +401 -0
  32. package/dist/errors.d.ts +44 -0
  33. package/dist/errors.d.ts.map +1 -0
  34. package/dist/errors.js +149 -0
  35. package/dist/exchange-client.d.ts +98 -0
  36. package/dist/exchange-client.d.ts.map +1 -0
  37. package/dist/exchange-client.js +107 -0
  38. package/dist/exchanges/base-exchange-client.d.ts +81 -0
  39. package/dist/exchanges/base-exchange-client.d.ts.map +1 -0
  40. package/dist/exchanges/base-exchange-client.js +123 -0
  41. package/dist/exchanges/index.d.ts +7 -0
  42. package/dist/exchanges/index.d.ts.map +1 -0
  43. package/dist/exchanges/index.js +123 -0
  44. package/dist/exchanges/types.d.ts +23 -0
  45. package/dist/exchanges/types.d.ts.map +1 -0
  46. package/dist/exchanges/types.js +1 -0
  47. package/dist/format-time.d.ts +28 -0
  48. package/dist/format-time.d.ts.map +1 -0
  49. package/dist/format-time.js +57 -0
  50. package/dist/formatters.d.ts +36 -0
  51. package/dist/formatters.d.ts.map +1 -0
  52. package/dist/formatters.js +155 -0
  53. package/dist/health.d.ts +19 -0
  54. package/dist/health.d.ts.map +1 -0
  55. package/dist/health.js +160 -0
  56. package/dist/index.d.ts +72 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +4198 -0
  59. package/dist/kvKeys.d.ts +76 -0
  60. package/dist/kvKeys.d.ts.map +1 -0
  61. package/dist/kvKeys.js +107 -0
  62. package/dist/kvUtils.d.ts +30 -0
  63. package/dist/kvUtils.d.ts.map +1 -0
  64. package/dist/kvUtils.js +170 -0
  65. package/dist/legal.d.ts +15 -0
  66. package/dist/legal.d.ts.map +1 -0
  67. package/dist/legal.js +40 -0
  68. package/dist/middleware/auth.d.ts +102 -0
  69. package/dist/middleware/auth.d.ts.map +1 -0
  70. package/dist/middleware/auth.js +151 -0
  71. package/dist/middleware/cors.d.ts +58 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +77 -0
  74. package/dist/middleware/index.d.ts +14 -0
  75. package/dist/middleware/index.d.ts.map +1 -0
  76. package/dist/middleware/index.js +635 -0
  77. package/dist/middleware/logger.d.ts +17 -0
  78. package/dist/middleware/logger.d.ts.map +1 -0
  79. package/dist/middleware/logger.js +202 -0
  80. package/dist/middleware/rate-limit.d.ts +30 -0
  81. package/dist/middleware/rate-limit.d.ts.map +1 -0
  82. package/dist/middleware/rate-limit.js +181 -0
  83. package/dist/middleware/security-headers.d.ts +43 -0
  84. package/dist/middleware/security-headers.d.ts.map +1 -0
  85. package/dist/middleware/security-headers.js +58 -0
  86. package/dist/middleware/validate.d.ts +25 -0
  87. package/dist/middleware/validate.d.ts.map +1 -0
  88. package/dist/middleware/validate.js +56 -0
  89. package/dist/operator-transport.d.ts +65 -0
  90. package/dist/operator-transport.d.ts.map +1 -0
  91. package/dist/operator-transport.js +87 -0
  92. package/dist/path-utils.d.ts +190 -0
  93. package/dist/path-utils.d.ts.map +1 -0
  94. package/dist/path-utils.js +188 -0
  95. package/dist/queue-handler.d.ts +47 -0
  96. package/dist/queue-handler.d.ts.map +1 -0
  97. package/dist/queue-handler.js +46 -0
  98. package/dist/router.d.ts +15 -0
  99. package/dist/router.d.ts.map +1 -0
  100. package/dist/router.js +130 -0
  101. package/dist/schemas/index.d.ts +8 -0
  102. package/dist/schemas/index.d.ts.map +1 -0
  103. package/dist/schemas/index.js +705 -0
  104. package/dist/schemas/registry.d.ts +12 -0
  105. package/dist/schemas/registry.d.ts.map +1 -0
  106. package/dist/schemas/registry.js +433 -0
  107. package/dist/schemas/types.d.ts +83 -0
  108. package/dist/schemas/types.d.ts.map +1 -0
  109. package/dist/schemas/types.js +1 -0
  110. package/dist/schemas/validators.d.ts +39 -0
  111. package/dist/schemas/validators.d.ts.map +1 -0
  112. package/dist/schemas/validators.js +290 -0
  113. package/dist/service-bindings.d.ts +78 -0
  114. package/dist/service-bindings.d.ts.map +1 -0
  115. package/dist/service-bindings.js +248 -0
  116. package/dist/session.d.ts +39 -0
  117. package/dist/session.d.ts.map +1 -0
  118. package/dist/session.js +261 -0
  119. package/dist/sse.d.ts +39 -0
  120. package/dist/sse.d.ts.map +1 -0
  121. package/dist/sse.js +154 -0
  122. package/dist/stores/config-store.d.ts +91 -0
  123. package/dist/stores/config-store.d.ts.map +1 -0
  124. package/dist/stores/config-store.js +255 -0
  125. package/dist/stores/service-store.d.ts +266 -0
  126. package/dist/stores/service-store.d.ts.map +1 -0
  127. package/dist/stores/service-store.js +516 -0
  128. package/dist/stores/ui-store.d.ts +67 -0
  129. package/dist/stores/ui-store.d.ts.map +1 -0
  130. package/dist/stores/ui-store.js +62 -0
  131. package/dist/test-utils.d.ts +136 -0
  132. package/dist/test-utils.d.ts.map +1 -0
  133. package/dist/test-utils.js +128 -0
  134. package/dist/types/errors.d.ts +20 -0
  135. package/dist/types/errors.d.ts.map +1 -0
  136. package/dist/types/errors.js +1 -0
  137. package/dist/types/router.d.ts +36 -0
  138. package/dist/types/router.d.ts.map +1 -0
  139. package/dist/types/router.js +1 -0
  140. package/dist/types.d.ts +442 -0
  141. package/dist/types.d.ts.map +1 -0
  142. package/dist/types.js +83 -0
  143. package/dist/wizard/engine.d.ts +46 -0
  144. package/dist/wizard/engine.d.ts.map +1 -0
  145. package/dist/wizard/engine.js +525 -0
  146. package/dist/wizard/index.d.ts +11 -0
  147. package/dist/wizard/index.d.ts.map +1 -0
  148. package/dist/wizard/index.js +544 -0
  149. package/dist/wizard/persistence.d.ts +18 -0
  150. package/dist/wizard/persistence.d.ts.map +1 -0
  151. package/dist/wizard/persistence.js +31 -0
  152. package/dist/wizard/presets.d.ts +38 -0
  153. package/dist/wizard/presets.d.ts.map +1 -0
  154. package/dist/wizard/presets.js +223 -0
  155. package/dist/wizard/provisioner.d.ts +15 -0
  156. package/dist/wizard/provisioner.d.ts.map +1 -0
  157. package/dist/wizard/provisioner.js +1 -0
  158. package/dist/wizard/types.d.ts +76 -0
  159. package/dist/wizard/types.d.ts.map +1 -0
  160. package/dist/wizard/types.js +1 -0
  161. package/package.json +117 -0
@@ -0,0 +1,516 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // src/operator-transport.ts
19
+ function stripTrailingSlashes(url) {
20
+ return url.replace(/\/+$/, "");
21
+ }
22
+ function parseTransport(raw) {
23
+ if (!raw)
24
+ return null;
25
+ const v = raw.trim().toLowerCase();
26
+ if (VALID_TRANSPORTS.has(v)) {
27
+ return v;
28
+ }
29
+ return null;
30
+ }
31
+ function resolveOperatorTransportProfile(env = process.env, options = {}) {
32
+ const accessClientId = env.CF_ACCESS_CLIENT_ID?.trim() ?? "";
33
+ const accessClientSecret = env.CF_ACCESS_CLIENT_SECRET?.trim() ?? "";
34
+ const hasAccess = Boolean(accessClientId && accessClientSecret);
35
+ const explicit = parseTransport(env.HOOX_TRANSPORT) ?? parseTransport(options.configTransport);
36
+ const transport = explicit ?? (hasAccess ? "access" : "public");
37
+ const apiFromEnv = env.HOOX_API_URL?.trim();
38
+ const apiFromConfig = options.configApiUrl?.trim();
39
+ const tokenFromEnv = env.HOOX_API_TOKEN?.trim();
40
+ const tokenFromConfig = options.configApiToken?.trim();
41
+ return {
42
+ transport,
43
+ apiBase: stripTrailingSlashes(apiFromEnv || apiFromConfig || DEFAULT_API_BASE),
44
+ bearerToken: tokenFromEnv || tokenFromConfig || "",
45
+ accessClientId,
46
+ accessClientSecret
47
+ };
48
+ }
49
+ function buildOperatorAuthHeaders(profile) {
50
+ const headers = {};
51
+ if (profile.bearerToken) {
52
+ headers.Authorization = `Bearer ${profile.bearerToken}`;
53
+ }
54
+ const useAccessHeaders = profile.transport === "access" || Boolean(profile.accessClientId) && Boolean(profile.accessClientSecret);
55
+ if (useAccessHeaders && profile.accessClientId && profile.accessClientSecret) {
56
+ headers["CF-Access-Client-Id"] = profile.accessClientId;
57
+ headers["CF-Access-Client-Secret"] = profile.accessClientSecret;
58
+ }
59
+ return headers;
60
+ }
61
+ function hasOperatorClientCredentials(profile) {
62
+ if (profile.bearerToken)
63
+ return true;
64
+ return Boolean(profile.accessClientId && profile.accessClientSecret);
65
+ }
66
+ function operatorUrl(profile, path) {
67
+ const base = profile.apiBase.replace(/\/+$/, "");
68
+ const p = path.startsWith("/") ? path : `/${path}`;
69
+ return `${base}${p}`;
70
+ }
71
+ var DEFAULT_API_BASE = "http://localhost:8787", VALID_TRANSPORTS;
72
+ var init_operator_transport = __esm(() => {
73
+ VALID_TRANSPORTS = new Set([
74
+ "public",
75
+ "access",
76
+ "mtls",
77
+ "tunnel"
78
+ ]);
79
+ });
80
+
81
+ // src/api-client.ts
82
+ var exports_api_client = {};
83
+ __export(exports_api_client, {
84
+ hooxFetch: () => hooxFetch,
85
+ WorkerAPIError: () => WorkerAPIError
86
+ });
87
+ function isRetryableStatus(status) {
88
+ if (status === 401 || status === 403 || status === 429)
89
+ return false;
90
+ if (status >= 500)
91
+ return true;
92
+ if (status === 408)
93
+ return true;
94
+ return false;
95
+ }
96
+ function isNetworkError(error) {
97
+ if (error instanceof Error) {
98
+ const msg = error.message.toLowerCase();
99
+ return error.name === "AbortError" || error.name === "TimeoutError" || msg.includes("fetch") || msg.includes("network") || msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("dns") || msg.includes("timeout");
100
+ }
101
+ return false;
102
+ }
103
+ function sleep(ms) {
104
+ return new Promise((resolve) => setTimeout(resolve, ms));
105
+ }
106
+ function activeProfile(override) {
107
+ return override ?? resolveOperatorTransportProfile();
108
+ }
109
+ async function hooxFetch(path, options) {
110
+ const profile = options?.transport ?? (options?.transportEnv ? resolveOperatorTransportProfile(options.transportEnv) : activeProfile());
111
+ const { transport: _t, transportEnv: _e, ...fetchInit } = options ?? {};
112
+ let lastError;
113
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
114
+ const controller = new AbortController;
115
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
116
+ try {
117
+ const url = operatorUrl(profile, path);
118
+ const headers = {
119
+ "Content-Type": "application/json",
120
+ ...buildOperatorAuthHeaders(profile),
121
+ ...fetchInit.headers
122
+ };
123
+ const response = await fetch(url, {
124
+ ...fetchInit,
125
+ headers,
126
+ signal: controller.signal
127
+ });
128
+ if (response.status === 429) {
129
+ throw new WorkerAPIError("API rate limited \u2014 backing off", {
130
+ status: 429,
131
+ retryable: false
132
+ });
133
+ }
134
+ if (response.status === 401 || response.status === 403) {
135
+ throw new WorkerAPIError(`Authentication failed (HTTP ${response.status})`, { status: response.status, retryable: false });
136
+ }
137
+ if (!response.ok) {
138
+ if (attempt < MAX_RETRIES && isRetryableStatus(response.status)) {
139
+ const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
140
+ await sleep(delay);
141
+ continue;
142
+ }
143
+ throw new WorkerAPIError(`HTTP ${response.status}: ${response.statusText}`, {
144
+ status: response.status,
145
+ retryable: isRetryableStatus(response.status)
146
+ });
147
+ }
148
+ return await response.json();
149
+ } catch (error) {
150
+ lastError = error;
151
+ if (error instanceof WorkerAPIError && !error.retryable) {
152
+ throw error;
153
+ }
154
+ if (attempt < MAX_RETRIES && isNetworkError(error)) {
155
+ const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
156
+ await sleep(delay);
157
+ continue;
158
+ }
159
+ if (!(error instanceof WorkerAPIError)) {
160
+ throw new WorkerAPIError(`Network request failed: ${error.message}`, { cause: error, retryable: false, status: 0 });
161
+ }
162
+ throw error;
163
+ } finally {
164
+ clearTimeout(timeoutId);
165
+ }
166
+ }
167
+ throw new WorkerAPIError("Max retries exhausted", {
168
+ cause: lastError,
169
+ retryable: false
170
+ });
171
+ }
172
+ var FETCH_TIMEOUT_MS = 5000, MAX_RETRIES = 3, BASE_RETRY_DELAY_MS = 1000, MAX_RETRY_DELAY_MS = 16000, WorkerAPIError;
173
+ var init_api_client = __esm(() => {
174
+ init_operator_transport();
175
+ WorkerAPIError = class WorkerAPIError extends Error {
176
+ status;
177
+ retryable;
178
+ constructor(message, options) {
179
+ super(message, { cause: options?.cause });
180
+ this.name = "WorkerAPIError";
181
+ this.status = options?.status ?? 0;
182
+ this.retryable = options?.retryable ?? false;
183
+ }
184
+ };
185
+ });
186
+
187
+ // src/sse.ts
188
+ var exports_sse = {};
189
+ __export(exports_sse, {
190
+ subscribeSSE: () => subscribeSSE
191
+ });
192
+ function subscribeSSE(path, callback, onStatus, options) {
193
+ let aborted = false;
194
+ const profile = options?.transport ?? (options?.transportEnv ? resolveOperatorTransportProfile(options.transportEnv) : resolveOperatorTransportProfile());
195
+ const run = async () => {
196
+ let attempt = 0;
197
+ while (!aborted && attempt < MAX_RECONNECT_ATTEMPTS) {
198
+ try {
199
+ const url = operatorUrl(profile, path);
200
+ const response = await fetch(url, {
201
+ headers: {
202
+ Accept: "text/event-stream",
203
+ ...buildOperatorAuthHeaders(profile)
204
+ }
205
+ });
206
+ if (!response.ok || !response.body) {
207
+ throw new Error(`SSE connection failed: HTTP ${response.status}`);
208
+ }
209
+ attempt = 0;
210
+ onStatus?.("connected");
211
+ const reader = response.body.getReader();
212
+ const decoder = new TextDecoder;
213
+ let buffer = "";
214
+ while (!aborted) {
215
+ const { done, value } = await reader.read();
216
+ if (done)
217
+ break;
218
+ buffer += decoder.decode(value, { stream: true });
219
+ const lines = buffer.split(`
220
+ `);
221
+ buffer = lines.pop() || "";
222
+ for (const line of lines) {
223
+ const trimmed = line.trim();
224
+ if (trimmed.startsWith("data: ")) {
225
+ try {
226
+ const parsed = JSON.parse(trimmed.slice(6));
227
+ callback(parsed);
228
+ } catch {}
229
+ }
230
+ }
231
+ }
232
+ } catch {
233
+ if (aborted)
234
+ break;
235
+ onStatus?.("reconnecting");
236
+ const delay = Math.min(RECONNECT_BASE_MS * Math.pow(2, attempt), RECONNECT_MAX_MS);
237
+ attempt++;
238
+ await new Promise((resolve) => setTimeout(resolve, delay));
239
+ }
240
+ }
241
+ if (!aborted) {
242
+ onStatus?.("disconnected");
243
+ }
244
+ };
245
+ run();
246
+ return {
247
+ abort: () => {
248
+ aborted = true;
249
+ }
250
+ };
251
+ }
252
+ var RECONNECT_BASE_MS = 1000, RECONNECT_MAX_MS = 16000, MAX_RECONNECT_ATTEMPTS = 10;
253
+ var init_sse = __esm(() => {
254
+ init_operator_transport();
255
+ });
256
+
257
+ // src/stores/service-store.ts
258
+ import { create } from "zustand";
259
+ import { immer } from "zustand/middleware/immer";
260
+ function summarizeCliError(details) {
261
+ const raw = details.stderr.trim() || details.stdout.trim() || details.command || "Unknown CLI error";
262
+ const firstLine = raw.split(`
263
+ `, 1)[0] ?? "";
264
+ const trimmed = firstLine.slice(0, 120).trimEnd();
265
+ return trimmed.length > 0 ? trimmed : details.command;
266
+ }
267
+ var BACKOFF_SEQUENCE = [1000, 2000, 4000, 8000, 16000];
268
+ var MAX_RETRIES2 = 5;
269
+ function ringPush(arr, item, max) {
270
+ const next = [...arr, item];
271
+ return next.length > max ? next.slice(next.length - max) : next;
272
+ }
273
+ var tradeStreamAbort = null;
274
+ var logStreamAbort = null;
275
+ var fetchWorkersGeneration = 0;
276
+ var MAX_TRADES = 500;
277
+ var MAX_ALERTS = 100;
278
+ var MAX_LOGS = 1000;
279
+ var initialState = {
280
+ workers: [],
281
+ tradeStream: [],
282
+ alerts: [],
283
+ logs: [],
284
+ metrics: null,
285
+ connectionStatus: "offline",
286
+ lastUpdated: 0,
287
+ selectedWorkerId: null,
288
+ retryCount: 0,
289
+ lastError: null,
290
+ lastErrorDetails: null,
291
+ lastSuccessfulFetch: 0,
292
+ reconnectDelay: 0,
293
+ disconnectedAt: null
294
+ };
295
+ function getBackoffDelay(retryCount) {
296
+ if (retryCount <= 0)
297
+ return 0;
298
+ const index = Math.min(retryCount - 1, BACKOFF_SEQUENCE.length - 1);
299
+ return BACKOFF_SEQUENCE[index];
300
+ }
301
+ var useServiceStore = create()(immer((set, get) => ({
302
+ ...initialState,
303
+ fetchWorkers: async () => {
304
+ const gen = ++fetchWorkersGeneration;
305
+ const { hooxFetch: hooxFetch2 } = await Promise.resolve().then(() => (init_api_client(), exports_api_client));
306
+ try {
307
+ const data = await hooxFetch2("/v1/workers");
308
+ if (gen !== fetchWorkersGeneration)
309
+ return;
310
+ set((state) => {
311
+ state.workers = data;
312
+ state.lastUpdated = Date.now();
313
+ state.lastSuccessfulFetch = Date.now();
314
+ if (state.connectionStatus === "polling" || state.connectionStatus === "reconnecting" || state.connectionStatus === "offline") {
315
+ state.connectionStatus = "connected";
316
+ state.retryCount = 0;
317
+ state.lastError = null;
318
+ state.lastErrorDetails = null;
319
+ state.reconnectDelay = 0;
320
+ state.disconnectedAt = null;
321
+ }
322
+ });
323
+ } catch (error) {
324
+ if (gen !== fetchWorkersGeneration)
325
+ return;
326
+ const msg = error instanceof Error ? error.message : "Unknown fetch error";
327
+ get().addAlert({
328
+ id: crypto.randomUUID(),
329
+ type: "connection",
330
+ severity: "error",
331
+ message: msg,
332
+ timestamp: Date.now(),
333
+ acknowledged: false
334
+ });
335
+ set((state) => {
336
+ state.lastError = msg;
337
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
338
+ if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
339
+ state.connectionStatus = "reconnecting";
340
+ state.retryCount = state.retryCount + 1;
341
+ if (state.retryCount >= MAX_RETRIES2) {
342
+ state.connectionStatus = "offline";
343
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
344
+ } else {
345
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
346
+ }
347
+ } else if (state.connectionStatus === "reconnecting") {
348
+ state.retryCount = state.retryCount + 1;
349
+ if (state.retryCount >= MAX_RETRIES2) {
350
+ state.connectionStatus = "offline";
351
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
352
+ state.reconnectDelay = 0;
353
+ } else {
354
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
355
+ }
356
+ }
357
+ });
358
+ }
359
+ },
360
+ streamTrades: async () => {
361
+ const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
362
+ tradeStreamAbort?.();
363
+ tradeStreamAbort = null;
364
+ try {
365
+ const sub = subscribeSSE2("/v1/trades/stream", (trade) => {
366
+ get().pushTrade(trade);
367
+ });
368
+ const resolved = await Promise.resolve(sub);
369
+ tradeStreamAbort = resolved.abort.bind(resolved);
370
+ } catch (error) {
371
+ const msg = error instanceof Error ? error.message : "SSE trade stream failed";
372
+ get().addAlert({
373
+ id: crypto.randomUUID(),
374
+ type: "connection",
375
+ severity: "error",
376
+ message: msg,
377
+ timestamp: Date.now(),
378
+ acknowledged: false
379
+ });
380
+ get().handleConnectionFailure(msg);
381
+ }
382
+ },
383
+ streamLogs: async () => {
384
+ const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
385
+ logStreamAbort?.();
386
+ logStreamAbort = null;
387
+ try {
388
+ const sub = subscribeSSE2("/v1/logs/stream", (log) => {
389
+ get().pushLog(log);
390
+ });
391
+ const resolved = await Promise.resolve(sub);
392
+ logStreamAbort = resolved.abort.bind(resolved);
393
+ } catch (error) {
394
+ const msg = error instanceof Error ? error.message : "SSE log stream failed";
395
+ get().addAlert({
396
+ id: crypto.randomUUID(),
397
+ type: "connection",
398
+ severity: "error",
399
+ message: msg,
400
+ timestamp: Date.now(),
401
+ acknowledged: false
402
+ });
403
+ get().handleConnectionFailure(msg);
404
+ }
405
+ },
406
+ stopStreams: () => {
407
+ tradeStreamAbort?.();
408
+ logStreamAbort?.();
409
+ tradeStreamAbort = null;
410
+ logStreamAbort = null;
411
+ },
412
+ addAlert: (alert) => set((state) => {
413
+ state.alerts = ringPush(state.alerts, alert, MAX_ALERTS);
414
+ }),
415
+ addAlerts: (alerts) => set((state) => {
416
+ const merged = [...state.alerts, ...alerts];
417
+ state.alerts = merged.length > MAX_ALERTS ? merged.slice(merged.length - MAX_ALERTS) : merged;
418
+ }),
419
+ setConnectionStatus: (status) => set((state) => {
420
+ if (status === "connected") {
421
+ state.retryCount = 0;
422
+ state.lastError = null;
423
+ state.lastErrorDetails = null;
424
+ state.reconnectDelay = 0;
425
+ state.disconnectedAt = null;
426
+ }
427
+ if (status === "offline" || status === "reconnecting") {
428
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
429
+ }
430
+ state.connectionStatus = status;
431
+ }),
432
+ selectWorker: (id) => set((state) => {
433
+ state.selectedWorkerId = id;
434
+ }),
435
+ setWorkers: (workers) => set((state) => {
436
+ state.workers = workers;
437
+ state.lastUpdated = Date.now();
438
+ }),
439
+ pushTrade: (trade) => set((state) => {
440
+ state.tradeStream = ringPush(state.tradeStream, trade, MAX_TRADES);
441
+ }),
442
+ pushLog: (log) => set((state) => {
443
+ state.logs = ringPush(state.logs, log, MAX_LOGS);
444
+ }),
445
+ setMetrics: (metrics) => set((state) => {
446
+ state.metrics = metrics;
447
+ state.lastUpdated = metrics.lastUpdated;
448
+ }),
449
+ handleConnectionSuccess: () => set((state) => {
450
+ state.lastSuccessfulFetch = Date.now();
451
+ state.retryCount = 0;
452
+ state.lastError = null;
453
+ state.lastErrorDetails = null;
454
+ state.reconnectDelay = 0;
455
+ state.disconnectedAt = null;
456
+ state.connectionStatus = "connected";
457
+ }),
458
+ handleConnectionFailure: (errorMessage) => set((state) => {
459
+ state.lastError = errorMessage;
460
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
461
+ if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
462
+ state.connectionStatus = "reconnecting";
463
+ state.retryCount = 1;
464
+ state.reconnectDelay = getBackoffDelay(1);
465
+ } else if (state.connectionStatus === "reconnecting") {
466
+ state.retryCount = state.retryCount + 1;
467
+ if (state.retryCount >= MAX_RETRIES2) {
468
+ state.connectionStatus = "offline";
469
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
470
+ state.reconnectDelay = 0;
471
+ } else {
472
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
473
+ }
474
+ }
475
+ }),
476
+ resetRetries: () => set((state) => {
477
+ state.retryCount = 0;
478
+ state.lastError = null;
479
+ state.lastErrorDetails = null;
480
+ state.reconnectDelay = 0;
481
+ state.disconnectedAt = null;
482
+ }),
483
+ forceRetry: () => set((state) => {
484
+ if (state.connectionStatus === "offline") {
485
+ state.connectionStatus = "polling";
486
+ state.retryCount = 0;
487
+ state.lastError = null;
488
+ state.lastErrorDetails = null;
489
+ state.reconnectDelay = 0;
490
+ }
491
+ }),
492
+ setLastErrorDetails: (details) => set((state) => {
493
+ state.lastErrorDetails = details;
494
+ state.lastError = details === null ? null : summarizeCliError(details);
495
+ }),
496
+ clearError: () => set((state) => {
497
+ state.lastError = null;
498
+ state.lastErrorDetails = null;
499
+ }),
500
+ addCliErrorAlert: (details) => set((state) => {
501
+ state.lastErrorDetails = details;
502
+ state.lastError = summarizeCliError(details);
503
+ const summary = summarizeCliError(details);
504
+ state.alerts = ringPush(state.alerts, {
505
+ id: crypto.randomUUID(),
506
+ type: "connection",
507
+ severity: "error",
508
+ message: `CLI failure: ${summary}`,
509
+ timestamp: Date.now(),
510
+ acknowledged: false
511
+ }, MAX_ALERTS);
512
+ })
513
+ })));
514
+ export {
515
+ useServiceStore
516
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import type { ViewId, ModalState } from "../types";
6
+ export interface UIState {
7
+ activeView: ViewId;
8
+ sidebarExpanded: boolean;
9
+ modal: ModalState | null;
10
+ commandPaletteOpen: boolean;
11
+ previousView: ViewId | null;
12
+ }
13
+ interface UIActions {
14
+ setView: (view: ViewId) => void;
15
+ toggleSidebar: () => void;
16
+ openPalette: () => void;
17
+ closePalette: () => void;
18
+ showModal: (modal: ModalState) => void;
19
+ dismissModal: () => void;
20
+ goBack: () => void;
21
+ }
22
+ export declare const useUIStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<UIState & UIActions>, "setState"> & {
23
+ setState(nextStateOrUpdater: (UIState & UIActions) | Partial<UIState & UIActions> | ((state: {
24
+ activeView: ViewId;
25
+ sidebarExpanded: boolean;
26
+ modal: {
27
+ type: "confirm" | "alert" | "prompt" | "custom";
28
+ title: string;
29
+ message?: string | undefined;
30
+ data?: unknown;
31
+ onConfirm?: (() => void) | undefined;
32
+ onCancel?: (() => void) | undefined;
33
+ } | null;
34
+ commandPaletteOpen: boolean;
35
+ previousView: ViewId | null;
36
+ setView: (view: ViewId) => void;
37
+ toggleSidebar: () => void;
38
+ openPalette: () => void;
39
+ closePalette: () => void;
40
+ showModal: (modal: ModalState) => void;
41
+ dismissModal: () => void;
42
+ goBack: () => void;
43
+ }) => void), shouldReplace?: false): void;
44
+ setState(nextStateOrUpdater: (UIState & UIActions) | ((state: {
45
+ activeView: ViewId;
46
+ sidebarExpanded: boolean;
47
+ modal: {
48
+ type: "confirm" | "alert" | "prompt" | "custom";
49
+ title: string;
50
+ message?: string | undefined;
51
+ data?: unknown;
52
+ onConfirm?: (() => void) | undefined;
53
+ onCancel?: (() => void) | undefined;
54
+ } | null;
55
+ commandPaletteOpen: boolean;
56
+ previousView: ViewId | null;
57
+ setView: (view: ViewId) => void;
58
+ toggleSidebar: () => void;
59
+ openPalette: () => void;
60
+ closePalette: () => void;
61
+ showModal: (modal: ModalState) => void;
62
+ dismissModal: () => void;
63
+ goBack: () => void;
64
+ }) => void), shouldReplace: true): void;
65
+ }>;
66
+ export {};
67
+ //# sourceMappingURL=ui-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-store.d.ts","sourceRoot":"","sources":["../../src/stores/ui-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAInD,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAID,UAAU,SAAS;IACjB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAgBD,eAAO,MAAM,UAAU;;oBAjCT,MAAM;yBACD,OAAO;;;;;;;;;4BAEJ,OAAO;sBACb,MAAM,GAAG,IAAI;iBAMlB,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;uBAChB,MAAM,IAAI;qBACZ,MAAM,IAAI;sBACT,MAAM,IAAI;mBACb,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI;sBACxB,MAAM,IAAI;gBAChB,MAAM,IAAI;;;oBAhBN,MAAM;yBACD,OAAO;;;;;;;;;4BAEJ,OAAO;sBACb,MAAM,GAAG,IAAI;iBAMlB,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;uBAChB,MAAM,IAAI;qBACZ,MAAM,IAAI;sBACT,MAAM,IAAI;mBACb,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI;sBACxB,MAAM,IAAI;gBAChB,MAAM,IAAI;;EAiEnB,CAAC"}
@@ -0,0 +1,62 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // src/stores/ui-store.ts
19
+ import { create } from "zustand";
20
+ import { immer } from "zustand/middleware/immer";
21
+ var DEFAULT_VIEW = "dashboard";
22
+ var initialState = {
23
+ activeView: DEFAULT_VIEW,
24
+ sidebarExpanded: true,
25
+ modal: null,
26
+ commandPaletteOpen: false,
27
+ previousView: null
28
+ };
29
+ var useUIStore = create()(immer((set) => ({
30
+ ...initialState,
31
+ setView: (view) => set((state) => {
32
+ if (state.activeView !== view) {
33
+ state.previousView = state.activeView;
34
+ }
35
+ state.activeView = view;
36
+ state.commandPaletteOpen = false;
37
+ }),
38
+ toggleSidebar: () => set((state) => {
39
+ state.sidebarExpanded = !state.sidebarExpanded;
40
+ }),
41
+ openPalette: () => set((state) => {
42
+ state.commandPaletteOpen = true;
43
+ }),
44
+ closePalette: () => set((state) => {
45
+ state.commandPaletteOpen = false;
46
+ }),
47
+ showModal: (modal) => set((state) => {
48
+ state.modal = modal;
49
+ }),
50
+ dismissModal: () => set((state) => {
51
+ state.modal = null;
52
+ }),
53
+ goBack: () => set((state) => {
54
+ if (state.previousView) {
55
+ state.activeView = state.previousView;
56
+ state.previousView = null;
57
+ }
58
+ })
59
+ })));
60
+ export {
61
+ useUIStore
62
+ };