@jango-blockchained/hoox-shared 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics.d.ts +0 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/colors.d.ts +0 -1
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +0 -1
- package/dist/cron-handler.d.ts +34 -0
- package/dist/cron-handler.d.ts.map +1 -0
- package/dist/cron-handler.js +48 -0
- package/dist/d1/index.js +49 -6
- package/dist/d1/repository.d.ts +5 -0
- package/dist/d1/repository.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +48 -5
- package/dist/exchanges/base-exchange-client.d.ts +68 -0
- package/dist/exchanges/base-exchange-client.d.ts.map +1 -0
- package/dist/exchanges/index.d.ts +3 -0
- package/dist/exchanges/index.d.ts.map +1 -0
- package/dist/exchanges/index.js +120 -0
- package/dist/exchanges/types.d.ts +19 -0
- package/dist/exchanges/types.d.ts.map +1 -0
- package/dist/health.js +48 -5
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +480 -15
- package/dist/kvUtils.js +48 -5
- package/dist/middleware/auth.d.ts +6 -3
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/index.js +193 -18
- package/dist/middleware/logger.d.ts +2 -1
- package/dist/middleware/logger.d.ts.map +1 -1
- package/dist/middleware/rate-limit.d.ts +3 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/path-utils.d.ts +147 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/path-utils.js +101 -0
- package/dist/queue-handler.d.ts +43 -0
- package/dist/queue-handler.d.ts.map +1 -0
- package/dist/queue-handler.js +46 -0
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +15 -8
- package/dist/service-bindings.d.ts +5 -28
- package/dist/service-bindings.d.ts.map +1 -1
- package/dist/service-bindings.js +3 -1
- package/dist/session.d.ts +9 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +83 -3
- package/dist/sse.js +1 -1
- package/dist/stores/service-store.d.ts +50 -1
- package/dist/stores/service-store.d.ts.map +1 -1
- package/dist/stores/service-store.js +67 -3
- package/dist/test-utils.d.ts +132 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +128 -0
- package/dist/types/router.d.ts +18 -8
- package/dist/types/router.d.ts.map +1 -1
- package/dist/types.d.ts +59 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/wizard/engine.d.ts.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { MessageBatch } from "@cloudflare/workers-types";
|
|
2
|
+
export interface QueueHandlerOptions<T> {
|
|
3
|
+
/** Maximum number of retry attempts */
|
|
4
|
+
maxRetries: number;
|
|
5
|
+
/** Array of delay times in seconds for exponential backoff */
|
|
6
|
+
backoffDelays: number[];
|
|
7
|
+
/** Handler function called for each message */
|
|
8
|
+
onMessage: (message: T, attemptNumber: number) => Promise<any> | any;
|
|
9
|
+
/** Called when a message fails and will be retried */
|
|
10
|
+
onRetry?: (message: T, attemptNumber: number, error: string, delaySeconds: number) => void | Promise<void>;
|
|
11
|
+
/** Called when message is moved to DLQ (max retries exceeded) */
|
|
12
|
+
onDLQ?: (message: T, attemptNumber: number, error: string) => void | Promise<void>;
|
|
13
|
+
/** Logger function for debugging */
|
|
14
|
+
logger?: {
|
|
15
|
+
info(msg: string, data?: any): void;
|
|
16
|
+
error(msg: string, data?: any): void;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a reusable queue handler with retry + exponential backoff logic.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const handler = createQueueHandler({
|
|
25
|
+
* maxRetries: 5,
|
|
26
|
+
* backoffDelays: [0, 30, 60, 300, 900],
|
|
27
|
+
* onMessage: async (msg) => {
|
|
28
|
+
* await executeTask(msg);
|
|
29
|
+
* },
|
|
30
|
+
* onDLQ: async (msg, attempts, error) => {
|
|
31
|
+
* await logFailedTask(msg, error);
|
|
32
|
+
* }
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* export default {
|
|
36
|
+
* async queue(batch, env, ctx) {
|
|
37
|
+
* return await handler(batch);
|
|
38
|
+
* }
|
|
39
|
+
* };
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function createQueueHandler<T>(options: QueueHandlerOptions<T>): (batch: MessageBatch<T>) => Promise<void>;
|
|
43
|
+
//# sourceMappingURL=queue-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-handler.d.ts","sourceRoot":"","sources":["../src/queue-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,+CAA+C;IAC/C,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACrE,sDAAsD;IACtD,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,CAAC,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,KACjB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,iEAAiE;IACjE,KAAK,CAAC,EAAE,CACN,OAAO,EAAE,CAAC,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,KACV,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,oCAAoC;IACpC,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;QACpC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;KACtC,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,IAIrD,OAAO,YAAY,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAmCrD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
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/queue-handler.ts
|
|
19
|
+
function createQueueHandler(options) {
|
|
20
|
+
const { maxRetries, backoffDelays, onMessage, onRetry, onDLQ, logger } = options;
|
|
21
|
+
return async (batch) => {
|
|
22
|
+
for (const msg of batch.messages) {
|
|
23
|
+
const attemptNumber = msg.attempts || 0;
|
|
24
|
+
const logId = `[${msg.id}]`;
|
|
25
|
+
try {
|
|
26
|
+
logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
|
|
27
|
+
await onMessage(msg.body, attemptNumber);
|
|
28
|
+
logger?.info(`${logId} Message processed successfully`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
31
|
+
if (attemptNumber < maxRetries) {
|
|
32
|
+
const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1];
|
|
33
|
+
logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
|
|
34
|
+
await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
|
|
35
|
+
msg.retry({ delaySeconds });
|
|
36
|
+
} else {
|
|
37
|
+
logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
|
|
38
|
+
await onDLQ?.(msg.body, attemptNumber, errorMsg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
createQueueHandler
|
|
46
|
+
};
|
package/dist/router.d.ts
CHANGED
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Router } from "./types/router";
|
|
9
9
|
export type { Handler, RouteParams } from "./types/router";
|
|
10
|
-
export declare function createRouter<TEnv =
|
|
10
|
+
export declare function createRouter<TEnv = any>(): Router<TEnv>;
|
|
11
11
|
//# sourceMappingURL=router.d.ts.map
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAKV,MAAM,EACP,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wBAAgB,YAAY,CAAC,IAAI,GAAG,GAAG,KAAK,MAAM,CAAC,IAAI,CAAC,CAsMvD"}
|
package/dist/router.js
CHANGED
|
@@ -52,7 +52,7 @@ function createRouter() {
|
|
|
52
52
|
routes.push(route);
|
|
53
53
|
}
|
|
54
54
|
function matchRoute(path, method) {
|
|
55
|
-
const exact = routes.
|
|
55
|
+
const exact = routes.reduceRight((found, r) => found ?? (r.path === path && r.method === method ? r : null), null);
|
|
56
56
|
if (exact)
|
|
57
57
|
return { route: exact };
|
|
58
58
|
for (let i = routes.length - 1;i >= 0; i--) {
|
|
@@ -85,14 +85,20 @@ function createRouter() {
|
|
|
85
85
|
const method = request.method;
|
|
86
86
|
const match = matchRoute(path, method);
|
|
87
87
|
if (!match) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
let pathExists = false;
|
|
89
|
+
for (let i = routes.length - 1;i >= 0; i--) {
|
|
90
|
+
const r = routes[i];
|
|
91
|
+
if (r.path === path) {
|
|
92
|
+
pathExists = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
91
95
|
if (r.params && r.params.length > 0 && r.regex) {
|
|
92
|
-
|
|
96
|
+
if (r.regex.test(path)) {
|
|
97
|
+
pathExists = true;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
93
100
|
}
|
|
94
|
-
|
|
95
|
-
});
|
|
101
|
+
}
|
|
96
102
|
if (pathExists) {
|
|
97
103
|
return new Response(JSON.stringify({
|
|
98
104
|
success: false,
|
|
@@ -114,7 +120,8 @@ function createRouter() {
|
|
|
114
120
|
return result;
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
|
-
|
|
123
|
+
const handlerResult = await match.route.handler(request, env, ctx, match.params);
|
|
124
|
+
return handlerResult ?? new Response(null, { status: 204 });
|
|
118
125
|
}
|
|
119
126
|
};
|
|
120
127
|
}
|
|
@@ -1,32 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* anti-pattern and inconsistent URL schemes used across workers
|
|
6
|
-
* (http://trade-service/webhook, http://localhost/query, https://internal/webhook, etc.).
|
|
7
|
-
*
|
|
8
|
-
* Convention: URL path matches the route the target worker serves.
|
|
9
|
-
* Defaults to POST with JSON Content-Type body.
|
|
10
|
-
*
|
|
11
|
-
* Note: Uses `any` for the binding parameter to avoid `@cloudflare/workers-types`
|
|
12
|
-
* `Fetcher` type incompatibilities that can arise when different workspace
|
|
13
|
-
* packages resolve distinct instances of the types package. At runtime, service
|
|
14
|
-
* bindings from the Workers runtime always have a compatible `fetch` method.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* // POST with JSON body (default)
|
|
18
|
-
* const response = await serviceFetch(env.TRADE_SERVICE, "/webhook", payload);
|
|
19
|
-
*
|
|
20
|
-
* // POST with extra headers
|
|
21
|
-
* const response = await serviceFetch(env.D1_SERVICE, "/query", { query, params }, {
|
|
22
|
-
* headers: { "X-Request-ID": requestId },
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* // GET request (no body)
|
|
26
|
-
* const response = await serviceFetch(env.D1_SERVICE, "/health", undefined, { method: "GET" });
|
|
27
|
-
*/
|
|
28
|
-
export declare function serviceFetch(binding: any, path: string, body?: unknown, options?: {
|
|
1
|
+
export interface ServiceBinding {
|
|
2
|
+
fetch(request: Request | string, init?: RequestInit): Promise<Response>;
|
|
3
|
+
}
|
|
4
|
+
export declare function serviceFetch(binding: ServiceBinding, path: string, body?: unknown, options?: {
|
|
29
5
|
method?: string;
|
|
30
6
|
headers?: Record<string, string>;
|
|
7
|
+
timeout?: number;
|
|
31
8
|
}): Promise<Response>;
|
|
32
9
|
//# sourceMappingURL=service-bindings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-bindings.d.ts","sourceRoot":"","sources":["../src/service-bindings.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"service-bindings.d.ts","sourceRoot":"","sources":["../src/service-bindings.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzE;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,QAAQ,CAAC,CAWnB"}
|
package/dist/service-bindings.js
CHANGED
|
@@ -17,13 +17,15 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
17
17
|
|
|
18
18
|
// src/service-bindings.ts
|
|
19
19
|
function serviceFetch(binding, path, body, options) {
|
|
20
|
+
const timeout = options?.timeout ?? 30000;
|
|
20
21
|
return binding.fetch(`http://internal${path}`, {
|
|
21
22
|
method: options?.method ?? "POST",
|
|
22
23
|
headers: {
|
|
23
24
|
"Content-Type": "application/json",
|
|
24
25
|
...options?.headers
|
|
25
26
|
},
|
|
26
|
-
body: body != null ? JSON.stringify(body) : undefined
|
|
27
|
+
body: body != null ? JSON.stringify(body) : undefined,
|
|
28
|
+
signal: AbortSignal.timeout(timeout)
|
|
27
29
|
});
|
|
28
30
|
}
|
|
29
31
|
export {
|
package/dist/session.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session persistence — saves/restores TUI state to
|
|
2
|
+
* Session persistence — saves/restores TUI state to $HOME/.hoox/.tui-state/session.json.
|
|
3
3
|
*
|
|
4
4
|
* On destroy (app exit): serializes { activeView, sidebarExpanded, windowSize,
|
|
5
5
|
* lastData } so the user returns to the same state on next launch.
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* On startup: restores previous activeView and sidebarExpanded state.
|
|
8
8
|
* Window size and lastData are saved for reference but re-detected/fetched fresh.
|
|
9
9
|
*
|
|
10
|
+
* Uses the shared getHooxHome() utility for cross-OS home directory resolution,
|
|
11
|
+
* and stores session data in the .tui-state subdirectory. Falls back to
|
|
12
|
+
* current working directory if $HOME is not available.
|
|
13
|
+
*
|
|
10
14
|
* Uses Bun's native file API for async read/write.
|
|
11
15
|
*/
|
|
12
16
|
import type { ViewId } from "./types";
|
|
@@ -26,18 +30,18 @@ export interface SessionState {
|
|
|
26
30
|
savedAt: string;
|
|
27
31
|
}
|
|
28
32
|
/**
|
|
29
|
-
* Persist the current UI state to
|
|
33
|
+
* Persist the current UI state to $HOME/.hoox/.tui-state/session.json.
|
|
30
34
|
* Call this on app destroy / clean shutdown.
|
|
31
35
|
*
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
36
|
+
* The $HOME/.hoox/.tui-state directory is created automatically by
|
|
37
|
+
* Bun.write if needed (Bun creates parent directories).
|
|
34
38
|
*/
|
|
35
39
|
export declare function saveSession(activeView: ViewId, sidebarExpanded: boolean, windowSize: {
|
|
36
40
|
cols: number;
|
|
37
41
|
rows: number;
|
|
38
42
|
}, lastData: number): Promise<void>;
|
|
39
43
|
/**
|
|
40
|
-
* Restore the previous session state from
|
|
44
|
+
* Restore the previous session state from $HOME/.hoox/.tui-state/session.json.
|
|
41
45
|
* Returns defaults if the file doesn't exist or can't be parsed.
|
|
42
46
|
*
|
|
43
47
|
* @returns The saved session state, or defaults
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAItC,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,eAAe,EAAE,OAAO,CAAC;IACzB,+CAA+C;IAC/C,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB;AA2BD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,EACxB,UAAU,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAkBf;AAID;;;;;GAKG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC,CA6B5D"}
|
package/dist/session.js
CHANGED
|
@@ -15,10 +15,90 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
|
-
// src/
|
|
18
|
+
// src/path-utils.ts
|
|
19
19
|
import { homedir } from "os";
|
|
20
|
-
import { join } from "path";
|
|
21
|
-
|
|
20
|
+
import { join, resolve } from "path";
|
|
21
|
+
function createHooxPath(path) {
|
|
22
|
+
return path;
|
|
23
|
+
}
|
|
24
|
+
function getHooxHome() {
|
|
25
|
+
try {
|
|
26
|
+
const home = homedir();
|
|
27
|
+
if (!home || home.length === 0) {
|
|
28
|
+
return createHooxPath(resolve(process.cwd(), ".hoox"));
|
|
29
|
+
}
|
|
30
|
+
return createHooxPath(join(home, ".hoox"));
|
|
31
|
+
} catch {
|
|
32
|
+
return createHooxPath(resolve(process.cwd(), ".hoox"));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function resolveHooxPath(relativePath) {
|
|
36
|
+
if (!relativePath || typeof relativePath !== "string") {
|
|
37
|
+
throw new Error("relativePath must be a non-empty string");
|
|
38
|
+
}
|
|
39
|
+
if (relativePath.includes("..")) {
|
|
40
|
+
throw new Error(`Path traversal detected in relativePath: "${relativePath}"`);
|
|
41
|
+
}
|
|
42
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
43
|
+
const hooxHome = getHooxHome();
|
|
44
|
+
const fullPath = join(hooxHome, normalizedPath);
|
|
45
|
+
const resolvedPath = resolve(fullPath);
|
|
46
|
+
const resolvedHome = resolve(hooxHome);
|
|
47
|
+
if (!resolvedPath.startsWith(resolvedHome)) {
|
|
48
|
+
throw new Error(`Resolved path "${resolvedPath}" is outside Hoox home directory`);
|
|
49
|
+
}
|
|
50
|
+
return createHooxPath(resolvedPath);
|
|
51
|
+
}
|
|
52
|
+
function isWithinHooxHome(path) {
|
|
53
|
+
try {
|
|
54
|
+
const hooxHome = getHooxHome();
|
|
55
|
+
const resolvedPath = resolve(path);
|
|
56
|
+
const resolvedHome = resolve(hooxHome);
|
|
57
|
+
return resolvedPath.startsWith(resolvedHome);
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getRelativeHooxPath(absolutePath) {
|
|
63
|
+
try {
|
|
64
|
+
if (!isWithinHooxHome(absolutePath)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const hooxHome = getHooxHome();
|
|
68
|
+
const resolvedPath = resolve(absolutePath);
|
|
69
|
+
const resolvedHome = resolve(hooxHome);
|
|
70
|
+
const relativePath = resolvedPath.slice(resolvedHome.length);
|
|
71
|
+
return relativePath.startsWith("/") || relativePath.startsWith("\\") ? relativePath.slice(1) : relativePath;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function getHooxRepoPath() {
|
|
77
|
+
return resolveHooxPath("repo");
|
|
78
|
+
}
|
|
79
|
+
function getHooxConfigDir() {
|
|
80
|
+
return resolveHooxPath("config");
|
|
81
|
+
}
|
|
82
|
+
function getHooxDataDir() {
|
|
83
|
+
return resolveHooxPath("data");
|
|
84
|
+
}
|
|
85
|
+
function getHooxWranglerPath() {
|
|
86
|
+
return resolveHooxPath("config/wrangler.jsonc");
|
|
87
|
+
}
|
|
88
|
+
function getHooxStatePath() {
|
|
89
|
+
return resolveHooxPath("data/state.json");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/session.ts
|
|
93
|
+
var TUI_STATE_DIR = ".tui-state";
|
|
94
|
+
var SESSION_FILE = (() => {
|
|
95
|
+
try {
|
|
96
|
+
const hooxHome = getHooxHome();
|
|
97
|
+
return `${hooxHome}/${TUI_STATE_DIR}/session.json`;
|
|
98
|
+
} catch {
|
|
99
|
+
return `${process.cwd()}/${TUI_STATE_DIR}/session.json`;
|
|
100
|
+
}
|
|
101
|
+
})();
|
|
22
102
|
var DEFAULT_SESSION = {
|
|
23
103
|
activeView: "dashboard",
|
|
24
104
|
sidebarExpanded: true,
|
package/dist/sse.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorkerInfo, Trade, Alert, LogEntry, SystemMetrics, ConnectionStatus } from "../types";
|
|
1
|
+
import type { WorkerInfo, Trade, Alert, LogEntry, SystemMetrics, ConnectionStatus, CliErrorDetails } from "../types";
|
|
2
2
|
export interface ServiceState {
|
|
3
3
|
workers: WorkerInfo[];
|
|
4
4
|
tradeStream: Trade[];
|
|
@@ -12,6 +12,13 @@ export interface ServiceState {
|
|
|
12
12
|
retryCount: number;
|
|
13
13
|
/** Last connection error message (for display) */
|
|
14
14
|
lastError: string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Structured CLI bridge error details. Populated when a `hoox` command
|
|
17
|
+
* fails so the status bar can render the full diagnostic context
|
|
18
|
+
* (command, exit code, stderr, error type) — not just a one-line summary.
|
|
19
|
+
* Cleared together with `lastError` on success or retry reset.
|
|
20
|
+
*/
|
|
21
|
+
lastErrorDetails: CliErrorDetails | null;
|
|
15
22
|
/** Timestamp (ms) of the last successful API fetch */
|
|
16
23
|
lastSuccessfulFetch: number;
|
|
17
24
|
/** Current backoff delay in ms (0 when not reconnecting) */
|
|
@@ -39,6 +46,24 @@ interface ServiceActions {
|
|
|
39
46
|
resetRetries: () => void;
|
|
40
47
|
/** Force a connection retry from offline state */
|
|
41
48
|
forceRetry: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Record a structured CLI bridge failure. Stores both the short
|
|
51
|
+
* `lastError` string (for the one-line summary) and the full
|
|
52
|
+
* `lastErrorDetails` (for click-to-expand). Pass `null` to clear.
|
|
53
|
+
*/
|
|
54
|
+
setLastErrorDetails: (details: CliErrorDetails | null) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Clear the last error and last error details. Convenience alias for
|
|
57
|
+
* `setLastErrorDetails(null)` — clearer at call sites where the
|
|
58
|
+
* intent is to acknowledge an error rather than record a new one.
|
|
59
|
+
*/
|
|
60
|
+
clearError: () => void;
|
|
61
|
+
/**
|
|
62
|
+
* Record a CLI bridge failure **and** add a matching high-severity
|
|
63
|
+
* alert to the alerts ring buffer. Use this when a failure should
|
|
64
|
+
* also be visible in the alerts panel (not just the status bar).
|
|
65
|
+
*/
|
|
66
|
+
addCliErrorAlert: (details: CliErrorDetails) => void;
|
|
42
67
|
}
|
|
43
68
|
export declare const useServiceStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ServiceState & ServiceActions>, "setState"> & {
|
|
44
69
|
setState(nextStateOrUpdater: (ServiceState & ServiceActions) | Partial<ServiceState & ServiceActions> | ((state: {
|
|
@@ -102,6 +127,15 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
|
|
|
102
127
|
selectedWorkerId: string | null;
|
|
103
128
|
retryCount: number;
|
|
104
129
|
lastError: string | null;
|
|
130
|
+
lastErrorDetails: {
|
|
131
|
+
command: string;
|
|
132
|
+
exitCode: number;
|
|
133
|
+
stderr: string;
|
|
134
|
+
stdout: string;
|
|
135
|
+
errorType: import("..").CliErrorType;
|
|
136
|
+
timestamp: number;
|
|
137
|
+
duration: number;
|
|
138
|
+
} | null;
|
|
105
139
|
lastSuccessfulFetch: number;
|
|
106
140
|
reconnectDelay: number;
|
|
107
141
|
disconnectedAt: number | null;
|
|
@@ -120,6 +154,9 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
|
|
|
120
154
|
handleConnectionFailure: (errorMessage: string) => void;
|
|
121
155
|
resetRetries: () => void;
|
|
122
156
|
forceRetry: () => void;
|
|
157
|
+
setLastErrorDetails: (details: CliErrorDetails | null) => void;
|
|
158
|
+
clearError: () => void;
|
|
159
|
+
addCliErrorAlert: (details: CliErrorDetails) => void;
|
|
123
160
|
}) => void), shouldReplace?: false): void;
|
|
124
161
|
setState(nextStateOrUpdater: (ServiceState & ServiceActions) | ((state: {
|
|
125
162
|
workers: {
|
|
@@ -182,6 +219,15 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
|
|
|
182
219
|
selectedWorkerId: string | null;
|
|
183
220
|
retryCount: number;
|
|
184
221
|
lastError: string | null;
|
|
222
|
+
lastErrorDetails: {
|
|
223
|
+
command: string;
|
|
224
|
+
exitCode: number;
|
|
225
|
+
stderr: string;
|
|
226
|
+
stdout: string;
|
|
227
|
+
errorType: import("..").CliErrorType;
|
|
228
|
+
timestamp: number;
|
|
229
|
+
duration: number;
|
|
230
|
+
} | null;
|
|
185
231
|
lastSuccessfulFetch: number;
|
|
186
232
|
reconnectDelay: number;
|
|
187
233
|
disconnectedAt: number | null;
|
|
@@ -200,6 +246,9 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
|
|
|
200
246
|
handleConnectionFailure: (errorMessage: string) => void;
|
|
201
247
|
resetRetries: () => void;
|
|
202
248
|
forceRetry: () => void;
|
|
249
|
+
setLastErrorDetails: (details: CliErrorDetails | null) => void;
|
|
250
|
+
clearError: () => void;
|
|
251
|
+
addCliErrorAlert: (details: CliErrorDetails) => void;
|
|
203
252
|
}) => void), shouldReplace: true): void;
|
|
204
253
|
}>;
|
|
205
254
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-store.d.ts","sourceRoot":"","sources":["../../src/stores/service-store.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,UAAU,EACV,KAAK,EACL,KAAK,EACL,QAAQ,EACR,aAAa,EACb,gBAAgB,
|
|
1
|
+
{"version":3,"file":"service-store.d.ts","sourceRoot":"","sources":["../../src/stores/service-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -161,7 +161,7 @@ function subscribeSSE(path, callback, onStatus) {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
-
} catch (
|
|
164
|
+
} catch (_error) {
|
|
165
165
|
if (aborted)
|
|
166
166
|
break;
|
|
167
167
|
onStatus?.("reconnecting");
|
|
@@ -190,6 +190,13 @@ var init_sse = __esm(() => {
|
|
|
190
190
|
// src/stores/service-store.ts
|
|
191
191
|
import { create } from "zustand";
|
|
192
192
|
import { immer } from "zustand/middleware/immer";
|
|
193
|
+
function summarizeCliError(details) {
|
|
194
|
+
const raw = details.stderr.trim() || details.stdout.trim() || details.command || "Unknown CLI error";
|
|
195
|
+
const firstLine = raw.split(`
|
|
196
|
+
`, 1)[0] ?? "";
|
|
197
|
+
const trimmed = firstLine.slice(0, 120).trimEnd();
|
|
198
|
+
return trimmed.length > 0 ? trimmed : details.command;
|
|
199
|
+
}
|
|
193
200
|
var BACKOFF_SEQUENCE = [1000, 2000, 4000, 8000, 16000];
|
|
194
201
|
var MAX_RETRIES2 = 5;
|
|
195
202
|
function ringPush(arr, item, max) {
|
|
@@ -210,6 +217,7 @@ var initialState = {
|
|
|
210
217
|
selectedWorkerId: null,
|
|
211
218
|
retryCount: 0,
|
|
212
219
|
lastError: null,
|
|
220
|
+
lastErrorDetails: null,
|
|
213
221
|
lastSuccessfulFetch: 0,
|
|
214
222
|
reconnectDelay: 0,
|
|
215
223
|
disconnectedAt: null
|
|
@@ -234,12 +242,21 @@ var useServiceStore = create()(immer((set, get) => ({
|
|
|
234
242
|
state.connectionStatus = "connected";
|
|
235
243
|
state.retryCount = 0;
|
|
236
244
|
state.lastError = null;
|
|
245
|
+
state.lastErrorDetails = null;
|
|
237
246
|
state.reconnectDelay = 0;
|
|
238
247
|
state.disconnectedAt = null;
|
|
239
248
|
}
|
|
240
249
|
});
|
|
241
250
|
} catch (error) {
|
|
242
251
|
const msg = error instanceof Error ? error.message : "Unknown fetch error";
|
|
252
|
+
get().addAlert({
|
|
253
|
+
id: crypto.randomUUID(),
|
|
254
|
+
type: "connection",
|
|
255
|
+
severity: "error",
|
|
256
|
+
message: msg,
|
|
257
|
+
timestamp: Date.now(),
|
|
258
|
+
acknowledged: false
|
|
259
|
+
});
|
|
243
260
|
set((state) => {
|
|
244
261
|
if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
|
|
245
262
|
state.connectionStatus = "reconnecting";
|
|
@@ -265,7 +282,18 @@ var useServiceStore = create()(immer((set, get) => ({
|
|
|
265
282
|
await subscribeSSE2("/trades/stream", (trade) => {
|
|
266
283
|
get().pushTrade(trade);
|
|
267
284
|
});
|
|
268
|
-
} catch {
|
|
285
|
+
} catch (error) {
|
|
286
|
+
const msg = error instanceof Error ? error.message : "SSE trade stream failed";
|
|
287
|
+
get().addAlert({
|
|
288
|
+
id: crypto.randomUUID(),
|
|
289
|
+
type: "connection",
|
|
290
|
+
severity: "error",
|
|
291
|
+
message: msg,
|
|
292
|
+
timestamp: Date.now(),
|
|
293
|
+
acknowledged: false
|
|
294
|
+
});
|
|
295
|
+
get().handleConnectionFailure(msg);
|
|
296
|
+
}
|
|
269
297
|
},
|
|
270
298
|
streamLogs: async () => {
|
|
271
299
|
const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
|
|
@@ -273,7 +301,18 @@ var useServiceStore = create()(immer((set, get) => ({
|
|
|
273
301
|
await subscribeSSE2("/logs/stream", (log) => {
|
|
274
302
|
get().pushLog(log);
|
|
275
303
|
});
|
|
276
|
-
} catch {
|
|
304
|
+
} catch (error) {
|
|
305
|
+
const msg = error instanceof Error ? error.message : "SSE log stream failed";
|
|
306
|
+
get().addAlert({
|
|
307
|
+
id: crypto.randomUUID(),
|
|
308
|
+
type: "connection",
|
|
309
|
+
severity: "error",
|
|
310
|
+
message: msg,
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
acknowledged: false
|
|
313
|
+
});
|
|
314
|
+
get().handleConnectionFailure(msg);
|
|
315
|
+
}
|
|
277
316
|
},
|
|
278
317
|
addAlert: (alert) => set((state) => {
|
|
279
318
|
state.alerts = ringPush(state.alerts, alert, MAX_ALERTS);
|
|
@@ -286,6 +325,7 @@ var useServiceStore = create()(immer((set, get) => ({
|
|
|
286
325
|
if (status === "connected") {
|
|
287
326
|
state.retryCount = 0;
|
|
288
327
|
state.lastError = null;
|
|
328
|
+
state.lastErrorDetails = null;
|
|
289
329
|
state.reconnectDelay = 0;
|
|
290
330
|
state.disconnectedAt = null;
|
|
291
331
|
}
|
|
@@ -315,6 +355,7 @@ var useServiceStore = create()(immer((set, get) => ({
|
|
|
315
355
|
state.lastSuccessfulFetch = Date.now();
|
|
316
356
|
state.retryCount = 0;
|
|
317
357
|
state.lastError = null;
|
|
358
|
+
state.lastErrorDetails = null;
|
|
318
359
|
state.reconnectDelay = 0;
|
|
319
360
|
state.disconnectedAt = null;
|
|
320
361
|
if (state.connectionStatus === "reconnecting" || state.connectionStatus === "polling") {
|
|
@@ -342,6 +383,7 @@ var useServiceStore = create()(immer((set, get) => ({
|
|
|
342
383
|
resetRetries: () => set((state) => {
|
|
343
384
|
state.retryCount = 0;
|
|
344
385
|
state.lastError = null;
|
|
386
|
+
state.lastErrorDetails = null;
|
|
345
387
|
state.reconnectDelay = 0;
|
|
346
388
|
state.disconnectedAt = null;
|
|
347
389
|
}),
|
|
@@ -350,8 +392,30 @@ var useServiceStore = create()(immer((set, get) => ({
|
|
|
350
392
|
state.connectionStatus = "polling";
|
|
351
393
|
state.retryCount = 0;
|
|
352
394
|
state.lastError = null;
|
|
395
|
+
state.lastErrorDetails = null;
|
|
353
396
|
state.reconnectDelay = 0;
|
|
354
397
|
}
|
|
398
|
+
}),
|
|
399
|
+
setLastErrorDetails: (details) => set((state) => {
|
|
400
|
+
state.lastErrorDetails = details;
|
|
401
|
+
state.lastError = details === null ? null : summarizeCliError(details);
|
|
402
|
+
}),
|
|
403
|
+
clearError: () => set((state) => {
|
|
404
|
+
state.lastError = null;
|
|
405
|
+
state.lastErrorDetails = null;
|
|
406
|
+
}),
|
|
407
|
+
addCliErrorAlert: (details) => set((state) => {
|
|
408
|
+
state.lastErrorDetails = details;
|
|
409
|
+
state.lastError = summarizeCliError(details);
|
|
410
|
+
const summary = summarizeCliError(details);
|
|
411
|
+
state.alerts = ringPush(state.alerts, {
|
|
412
|
+
id: crypto.randomUUID(),
|
|
413
|
+
type: "connection",
|
|
414
|
+
severity: "error",
|
|
415
|
+
message: `CLI failure: ${summary}`,
|
|
416
|
+
timestamp: Date.now(),
|
|
417
|
+
acknowledged: false
|
|
418
|
+
}, MAX_ALERTS);
|
|
355
419
|
})
|
|
356
420
|
})));
|
|
357
421
|
export {
|