@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.
- package/README.md +44 -0
- package/dist/agent-config-fields.d.ts +32 -0
- package/dist/agent-config-fields.d.ts.map +1 -0
- package/dist/agent-config-fields.js +220 -0
- package/dist/analytics.d.ts +47 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +45 -0
- package/dist/api-client.d.ts +48 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +191 -0
- package/dist/colors.d.ts +76 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +82 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +146 -0
- package/dist/cron-handler.d.ts +38 -0
- package/dist/cron-handler.d.ts.map +1 -0
- package/dist/cron-handler.js +48 -0
- package/dist/d1/index.d.ts +9 -0
- package/dist/d1/index.d.ts.map +1 -0
- package/dist/d1/index.js +360 -0
- package/dist/d1/repository.d.ts +86 -0
- package/dist/d1/repository.d.ts.map +1 -0
- package/dist/d1/repository.js +297 -0
- package/dist/d1/schemas.d.ts +89 -0
- package/dist/d1/schemas.d.ts.map +1 -0
- package/dist/d1/schemas.js +81 -0
- package/dist/dashboard-manifest.d.ts +135 -0
- package/dist/dashboard-manifest.d.ts.map +1 -0
- package/dist/dashboard-manifest.js +401 -0
- package/dist/errors.d.ts +44 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +149 -0
- package/dist/exchange-client.d.ts +98 -0
- package/dist/exchange-client.d.ts.map +1 -0
- package/dist/exchange-client.js +107 -0
- package/dist/exchanges/base-exchange-client.d.ts +81 -0
- package/dist/exchanges/base-exchange-client.d.ts.map +1 -0
- package/dist/exchanges/base-exchange-client.js +123 -0
- package/dist/exchanges/index.d.ts +7 -0
- package/dist/exchanges/index.d.ts.map +1 -0
- package/dist/exchanges/index.js +123 -0
- package/dist/exchanges/types.d.ts +23 -0
- package/dist/exchanges/types.d.ts.map +1 -0
- package/dist/exchanges/types.js +1 -0
- package/dist/format-time.d.ts +28 -0
- package/dist/format-time.d.ts.map +1 -0
- package/dist/format-time.js +57 -0
- package/dist/formatters.d.ts +36 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +155 -0
- package/dist/health.d.ts +19 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +160 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4198 -0
- package/dist/kvKeys.d.ts +76 -0
- package/dist/kvKeys.d.ts.map +1 -0
- package/dist/kvKeys.js +107 -0
- package/dist/kvUtils.d.ts +30 -0
- package/dist/kvUtils.d.ts.map +1 -0
- package/dist/kvUtils.js +170 -0
- package/dist/legal.d.ts +15 -0
- package/dist/legal.d.ts.map +1 -0
- package/dist/legal.js +40 -0
- package/dist/middleware/auth.d.ts +102 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +151 -0
- package/dist/middleware/cors.d.ts +58 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +77 -0
- package/dist/middleware/index.d.ts +14 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +635 -0
- package/dist/middleware/logger.d.ts +17 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +202 -0
- package/dist/middleware/rate-limit.d.ts +30 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +181 -0
- package/dist/middleware/security-headers.d.ts +43 -0
- package/dist/middleware/security-headers.d.ts.map +1 -0
- package/dist/middleware/security-headers.js +58 -0
- package/dist/middleware/validate.d.ts +25 -0
- package/dist/middleware/validate.d.ts.map +1 -0
- package/dist/middleware/validate.js +56 -0
- package/dist/operator-transport.d.ts +65 -0
- package/dist/operator-transport.d.ts.map +1 -0
- package/dist/operator-transport.js +87 -0
- package/dist/path-utils.d.ts +190 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/path-utils.js +188 -0
- package/dist/queue-handler.d.ts +47 -0
- package/dist/queue-handler.d.ts.map +1 -0
- package/dist/queue-handler.js +46 -0
- package/dist/router.d.ts +15 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +130 -0
- package/dist/schemas/index.d.ts +8 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +705 -0
- package/dist/schemas/registry.d.ts +12 -0
- package/dist/schemas/registry.d.ts.map +1 -0
- package/dist/schemas/registry.js +433 -0
- package/dist/schemas/types.d.ts +83 -0
- package/dist/schemas/types.d.ts.map +1 -0
- package/dist/schemas/types.js +1 -0
- package/dist/schemas/validators.d.ts +39 -0
- package/dist/schemas/validators.d.ts.map +1 -0
- package/dist/schemas/validators.js +290 -0
- package/dist/service-bindings.d.ts +78 -0
- package/dist/service-bindings.d.ts.map +1 -0
- package/dist/service-bindings.js +248 -0
- package/dist/session.d.ts +39 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +261 -0
- package/dist/sse.d.ts +39 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +154 -0
- package/dist/stores/config-store.d.ts +91 -0
- package/dist/stores/config-store.d.ts.map +1 -0
- package/dist/stores/config-store.js +255 -0
- package/dist/stores/service-store.d.ts +266 -0
- package/dist/stores/service-store.d.ts.map +1 -0
- package/dist/stores/service-store.js +516 -0
- package/dist/stores/ui-store.d.ts +67 -0
- package/dist/stores/ui-store.d.ts.map +1 -0
- package/dist/stores/ui-store.js +62 -0
- package/dist/test-utils.d.ts +136 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +128 -0
- package/dist/types/errors.d.ts +20 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +1 -0
- package/dist/types/router.d.ts +36 -0
- package/dist/types/router.d.ts.map +1 -0
- package/dist/types/router.js +1 -0
- package/dist/types.d.ts +442 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +83 -0
- package/dist/wizard/engine.d.ts +46 -0
- package/dist/wizard/engine.d.ts.map +1 -0
- package/dist/wizard/engine.js +525 -0
- package/dist/wizard/index.d.ts +11 -0
- package/dist/wizard/index.d.ts.map +1 -0
- package/dist/wizard/index.js +544 -0
- package/dist/wizard/persistence.d.ts +18 -0
- package/dist/wizard/persistence.d.ts.map +1 -0
- package/dist/wizard/persistence.js +31 -0
- package/dist/wizard/presets.d.ts +38 -0
- package/dist/wizard/presets.d.ts.map +1 -0
- package/dist/wizard/presets.js +223 -0
- package/dist/wizard/provisioner.d.ts +15 -0
- package/dist/wizard/provisioner.d.ts.map +1 -0
- package/dist/wizard/provisioner.js +1 -0
- package/dist/wizard/types.d.ts +76 -0
- package/dist/wizard/types.d.ts.map +1 -0
- package/dist/wizard/types.js +1 -0
- package/package.json +117 -0
|
@@ -0,0 +1,401 @@
|
|
|
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/dashboard-manifest.ts
|
|
19
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
var DASHBOARD_WORKER_PREFIX = {
|
|
22
|
+
hoox: "global:",
|
|
23
|
+
"trade-worker": "trade:",
|
|
24
|
+
"agent-worker": "agent:",
|
|
25
|
+
"telegram-worker": "bot:",
|
|
26
|
+
"d1-worker": "database:",
|
|
27
|
+
"email-worker": "email:",
|
|
28
|
+
"web3-wallet-worker": "wallet:",
|
|
29
|
+
"analytics-worker": "ai:",
|
|
30
|
+
"report-worker": "report:",
|
|
31
|
+
"pyne-worker": "pyne:"
|
|
32
|
+
};
|
|
33
|
+
var DASHBOARD_SECTION_PREFIX = {
|
|
34
|
+
global: "global:",
|
|
35
|
+
webhook: "webhook:",
|
|
36
|
+
routing: "routing:",
|
|
37
|
+
security: "webhook:",
|
|
38
|
+
trade: "trade:",
|
|
39
|
+
agent: "agent:",
|
|
40
|
+
bot: "bot:",
|
|
41
|
+
email: "email:",
|
|
42
|
+
signal: "email:",
|
|
43
|
+
database: "database:",
|
|
44
|
+
retention: "retention:",
|
|
45
|
+
cron: "cron:",
|
|
46
|
+
behavior: "behavior:",
|
|
47
|
+
risk: "trade:",
|
|
48
|
+
exchanges: "trade:",
|
|
49
|
+
fees: "trade:",
|
|
50
|
+
providers: "agent:",
|
|
51
|
+
models: "agent:",
|
|
52
|
+
ai: "ai:",
|
|
53
|
+
report: "report:",
|
|
54
|
+
pyne: "pyne:",
|
|
55
|
+
pyne_alerts: "pyne:"
|
|
56
|
+
};
|
|
57
|
+
var DASHBOARD_FIELD_KV_OVERRIDES = {
|
|
58
|
+
"webhook:tradingview_ip_check_enabled": "webhook:tradingview:ip_check_enabled",
|
|
59
|
+
"webhook:tradingview_allowed_ips": "webhook:tradingview:allowed_ips",
|
|
60
|
+
"exchanges:binance_enabled": "exchange:binance:enabled",
|
|
61
|
+
"exchanges:mexc_enabled": "exchange:mexc:enabled",
|
|
62
|
+
"exchanges:bybit_enabled": "exchange:bybit:enabled"
|
|
63
|
+
};
|
|
64
|
+
var DASHBOARD_SECTIONS_NOT_FLAT_KV = new Set([
|
|
65
|
+
"providers",
|
|
66
|
+
"models",
|
|
67
|
+
"cron",
|
|
68
|
+
"behavior"
|
|
69
|
+
]);
|
|
70
|
+
var DASHBOARD_SECTIONS_UI_SKIP = new Set([
|
|
71
|
+
"cron",
|
|
72
|
+
"behavior"
|
|
73
|
+
]);
|
|
74
|
+
function isDashboardSectionFlatKv(sectionId) {
|
|
75
|
+
return !DASHBOARD_SECTIONS_NOT_FLAT_KV.has(sectionId);
|
|
76
|
+
}
|
|
77
|
+
function isDashboardSectionEditable(sectionId) {
|
|
78
|
+
return !DASHBOARD_SECTIONS_UI_SKIP.has(sectionId);
|
|
79
|
+
}
|
|
80
|
+
function buildDashboardKvKey(worker, fieldKey) {
|
|
81
|
+
const override = DASHBOARD_FIELD_KV_OVERRIDES[fieldKey];
|
|
82
|
+
if (override)
|
|
83
|
+
return override;
|
|
84
|
+
if (fieldKey.includes(":")) {
|
|
85
|
+
const [section, ...rest] = fieldKey.split(":");
|
|
86
|
+
const fieldName = rest.join(":");
|
|
87
|
+
if (section === "exchanges" && fieldName.endsWith("_enabled")) {
|
|
88
|
+
const exchange = fieldName.slice(0, -"_enabled".length);
|
|
89
|
+
if (exchange)
|
|
90
|
+
return `exchange:${exchange}:enabled`;
|
|
91
|
+
}
|
|
92
|
+
const mapped = DASHBOARD_SECTION_PREFIX[section ?? ""];
|
|
93
|
+
if (mapped)
|
|
94
|
+
return `${mapped}${fieldName}`;
|
|
95
|
+
return `${section}:${fieldName}`;
|
|
96
|
+
}
|
|
97
|
+
const workerPrefix = DASHBOARD_WORKER_PREFIX[worker] ?? "";
|
|
98
|
+
return `${workerPrefix}${fieldKey}`;
|
|
99
|
+
}
|
|
100
|
+
function stripJsonc(input) {
|
|
101
|
+
let out = "";
|
|
102
|
+
let i = 0;
|
|
103
|
+
let inString = false;
|
|
104
|
+
let stringChar = "";
|
|
105
|
+
while (i < input.length) {
|
|
106
|
+
const c = input[i];
|
|
107
|
+
const next = input[i + 1];
|
|
108
|
+
if (inString) {
|
|
109
|
+
out += c;
|
|
110
|
+
if (c === "\\" && i + 1 < input.length) {
|
|
111
|
+
out += next;
|
|
112
|
+
i += 2;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (c === stringChar)
|
|
116
|
+
inString = false;
|
|
117
|
+
i++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (c === '"' || c === "'") {
|
|
121
|
+
inString = true;
|
|
122
|
+
stringChar = c;
|
|
123
|
+
out += c;
|
|
124
|
+
i++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (c === "/" && next === "/") {
|
|
128
|
+
while (i < input.length && input[i] !== `
|
|
129
|
+
`)
|
|
130
|
+
i++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (c === "/" && next === "*") {
|
|
134
|
+
i += 2;
|
|
135
|
+
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/"))
|
|
136
|
+
i++;
|
|
137
|
+
i += 2;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (c === ",") {
|
|
141
|
+
let j = i + 1;
|
|
142
|
+
while (j < input.length && /\s/.test(input[j]))
|
|
143
|
+
j++;
|
|
144
|
+
if (j < input.length && (input[j] === "}" || input[j] === "]")) {
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
out += c;
|
|
150
|
+
i++;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
function parseFieldType(value) {
|
|
155
|
+
if (typeof value === "boolean")
|
|
156
|
+
return "boolean";
|
|
157
|
+
if (typeof value === "number")
|
|
158
|
+
return "number";
|
|
159
|
+
if (value === "true" || value === "false")
|
|
160
|
+
return "boolean";
|
|
161
|
+
if (value.trim().startsWith("{") || value.trim().startsWith("["))
|
|
162
|
+
return "json";
|
|
163
|
+
if (!Number.isNaN(Number(value)) && value.trim() !== "")
|
|
164
|
+
return "number";
|
|
165
|
+
return "text";
|
|
166
|
+
}
|
|
167
|
+
function labelFromKey(key) {
|
|
168
|
+
return key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
169
|
+
}
|
|
170
|
+
function parseDashboardManifest(content, workerName, onError) {
|
|
171
|
+
try {
|
|
172
|
+
const raw = JSON.parse(stripJsonc(content));
|
|
173
|
+
const displayName = raw.display_name || raw.displayName || workerName;
|
|
174
|
+
const description = raw.description || "";
|
|
175
|
+
const sections = [];
|
|
176
|
+
if (raw.sections) {
|
|
177
|
+
for (const [sectionId, sectionData] of Object.entries(raw.sections)) {
|
|
178
|
+
const fields = [];
|
|
179
|
+
const sectionFields = sectionData.fields || {};
|
|
180
|
+
const sectionOptions = sectionData.options || {};
|
|
181
|
+
const sectionDescriptions = sectionData.descriptions || {};
|
|
182
|
+
const sectionValidation = sectionData.validation || {};
|
|
183
|
+
const sectionSecrets = sectionData.secrets || {};
|
|
184
|
+
const sectionSecretCommands = sectionData.secret_commands || {};
|
|
185
|
+
for (const [key, value] of Object.entries(sectionFields)) {
|
|
186
|
+
const field = {
|
|
187
|
+
key: `${sectionId}:${key}`,
|
|
188
|
+
label: labelFromKey(key),
|
|
189
|
+
type: parseFieldType(value),
|
|
190
|
+
default: value
|
|
191
|
+
};
|
|
192
|
+
if (sectionOptions[key]) {
|
|
193
|
+
field.type = "select";
|
|
194
|
+
field.options = sectionOptions[key].map((opt) => ({
|
|
195
|
+
value: String(opt),
|
|
196
|
+
label: String(opt)
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
if (sectionDescriptions[key]) {
|
|
200
|
+
field.description = String(sectionDescriptions[key]);
|
|
201
|
+
}
|
|
202
|
+
if (sectionValidation[key]) {
|
|
203
|
+
field.validation = sectionValidation[key];
|
|
204
|
+
}
|
|
205
|
+
if (sectionSecrets[key]) {
|
|
206
|
+
field.kind = "secret";
|
|
207
|
+
field.cliCommand = sectionSecretCommands[key];
|
|
208
|
+
}
|
|
209
|
+
if (key.includes("kill_switch") || key.includes("drawdown") || key === "enabled" && sectionId === "global") {
|
|
210
|
+
if (field.kind !== "secret")
|
|
211
|
+
field.kind = "dangerous";
|
|
212
|
+
}
|
|
213
|
+
fields.push(field);
|
|
214
|
+
}
|
|
215
|
+
sections.push({
|
|
216
|
+
id: sectionId,
|
|
217
|
+
title: sectionData.title || labelFromKey(sectionId),
|
|
218
|
+
description: sectionData.description || `Configure ${sectionId} settings`,
|
|
219
|
+
icon: sectionData.icon,
|
|
220
|
+
priority: sectionData.priority !== undefined ? sectionData.priority : sections.length * 10,
|
|
221
|
+
fields
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
worker: workerName,
|
|
227
|
+
displayName,
|
|
228
|
+
description,
|
|
229
|
+
sections: sections.sort((a, b) => a.priority - b.priority)
|
|
230
|
+
};
|
|
231
|
+
} catch (err) {
|
|
232
|
+
const message = err instanceof Error ? err.message : `parse failed: ${String(err)}`;
|
|
233
|
+
onError?.(`dashboard.jsonc for ${workerName}: ${message}`);
|
|
234
|
+
return {
|
|
235
|
+
worker: workerName,
|
|
236
|
+
displayName: workerName,
|
|
237
|
+
description: "Failed to load configuration",
|
|
238
|
+
sections: []
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
var DASHBOARD_WORKER_IDS = [
|
|
243
|
+
"hoox",
|
|
244
|
+
"trade-worker",
|
|
245
|
+
"agent-worker",
|
|
246
|
+
"telegram-worker",
|
|
247
|
+
"d1-worker",
|
|
248
|
+
"email-worker",
|
|
249
|
+
"web3-wallet-worker",
|
|
250
|
+
"analytics-worker",
|
|
251
|
+
"report-worker",
|
|
252
|
+
"pyne-worker"
|
|
253
|
+
];
|
|
254
|
+
function dashboardWorkerDir(workerId) {
|
|
255
|
+
if (workerId === "hoox")
|
|
256
|
+
return "hoox-worker";
|
|
257
|
+
return workerId;
|
|
258
|
+
}
|
|
259
|
+
function fieldTypeToKv(t) {
|
|
260
|
+
if (t === "boolean")
|
|
261
|
+
return "boolean";
|
|
262
|
+
if (t === "number")
|
|
263
|
+
return "number";
|
|
264
|
+
return "string";
|
|
265
|
+
}
|
|
266
|
+
function defaultToString(value) {
|
|
267
|
+
if (typeof value === "string")
|
|
268
|
+
return value;
|
|
269
|
+
return JSON.stringify(value);
|
|
270
|
+
}
|
|
271
|
+
function isDocumentationField(fieldKey) {
|
|
272
|
+
const name = fieldKey.includes(":") ? fieldKey.slice(fieldKey.indexOf(":") + 1) : fieldKey;
|
|
273
|
+
return /^(GET|POST|PUT|PATCH|DELETE)\s+\//i.test(name);
|
|
274
|
+
}
|
|
275
|
+
function kvManifestFromDashboardManifests(manifests) {
|
|
276
|
+
const byKey = new Map;
|
|
277
|
+
for (const manifest of manifests) {
|
|
278
|
+
for (const section of manifest.sections) {
|
|
279
|
+
if (!isDashboardSectionFlatKv(section.id))
|
|
280
|
+
continue;
|
|
281
|
+
for (const field of section.fields) {
|
|
282
|
+
if (isDocumentationField(field.key))
|
|
283
|
+
continue;
|
|
284
|
+
const key = buildDashboardKvKey(manifest.worker, field.key);
|
|
285
|
+
if (!key || key.endsWith(":"))
|
|
286
|
+
continue;
|
|
287
|
+
const next = {
|
|
288
|
+
key,
|
|
289
|
+
type: fieldTypeToKv(field.type),
|
|
290
|
+
default: defaultToString(field.default),
|
|
291
|
+
description: field.description || `${manifest.displayName}: ${field.label}`,
|
|
292
|
+
secret: field.kind === "secret" ? true : undefined,
|
|
293
|
+
worker: manifest.worker
|
|
294
|
+
};
|
|
295
|
+
const prev = byKey.get(key);
|
|
296
|
+
if (!prev) {
|
|
297
|
+
byKey.set(key, next);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (!prev.secret && next.secret)
|
|
301
|
+
prev.secret = true;
|
|
302
|
+
if ((!prev.description || prev.description.length < 8) && next.description) {
|
|
303
|
+
prev.description = next.description;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const keys = [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key));
|
|
309
|
+
return { namespace: "CONFIG_KV", keys };
|
|
310
|
+
}
|
|
311
|
+
function loadDashboardKvManifestFromRoot(root, onError) {
|
|
312
|
+
return kvManifestFromDashboardManifests(loadDashboardManifestsFromRoot(root, onError));
|
|
313
|
+
}
|
|
314
|
+
function tryReadFile(path) {
|
|
315
|
+
try {
|
|
316
|
+
if (!existsSync(path))
|
|
317
|
+
return null;
|
|
318
|
+
return readFileSync(path, "utf-8");
|
|
319
|
+
} catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function loadDashboardManifestsFromRoot(root, onError) {
|
|
324
|
+
const manifests = [];
|
|
325
|
+
const seen = new Set;
|
|
326
|
+
const workersDir = join(root, "workers");
|
|
327
|
+
if (!existsSync(workersDir))
|
|
328
|
+
return manifests;
|
|
329
|
+
const warn = onError ?? ((message) => {
|
|
330
|
+
console.warn(`[dashboard-manifest] ${message}`);
|
|
331
|
+
});
|
|
332
|
+
for (const workerId of DASHBOARD_WORKER_IDS) {
|
|
333
|
+
const dir = dashboardWorkerDir(workerId);
|
|
334
|
+
const path = join(workersDir, dir, "dashboard.jsonc");
|
|
335
|
+
const content = tryReadFile(path);
|
|
336
|
+
if (!content)
|
|
337
|
+
continue;
|
|
338
|
+
const m = parseDashboardManifest(content, workerId, warn);
|
|
339
|
+
if (m.sections.length > 0) {
|
|
340
|
+
manifests.push(m);
|
|
341
|
+
seen.add(workerId);
|
|
342
|
+
} else if (m.description === "Failed to load configuration") {}
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
for (const entry of readdirSync(workersDir)) {
|
|
346
|
+
if (entry === "dashboard")
|
|
347
|
+
continue;
|
|
348
|
+
const workerId = entry === "hoox-worker" || entry === "hoox" ? "hoox" : entry;
|
|
349
|
+
if (seen.has(workerId))
|
|
350
|
+
continue;
|
|
351
|
+
const path = join(workersDir, entry, "dashboard.jsonc");
|
|
352
|
+
const content = tryReadFile(path);
|
|
353
|
+
if (!content)
|
|
354
|
+
continue;
|
|
355
|
+
const m = parseDashboardManifest(content, workerId, warn);
|
|
356
|
+
if (m.sections.length > 0) {
|
|
357
|
+
manifests.push(m);
|
|
358
|
+
seen.add(workerId);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
} catch {}
|
|
362
|
+
const publicDir = join(workersDir, "dashboard", "public", "workers");
|
|
363
|
+
if (existsSync(publicDir)) {
|
|
364
|
+
try {
|
|
365
|
+
for (const file of readdirSync(publicDir)) {
|
|
366
|
+
if (!file.endsWith(".jsonc") && !file.endsWith(".json"))
|
|
367
|
+
continue;
|
|
368
|
+
const base = file.replace(/\.jsonc?$/, "");
|
|
369
|
+
const workerId = base === "hoox-worker" || base === "hoox" ? "hoox" : base;
|
|
370
|
+
if (seen.has(workerId))
|
|
371
|
+
continue;
|
|
372
|
+
const content = tryReadFile(join(publicDir, file));
|
|
373
|
+
if (!content)
|
|
374
|
+
continue;
|
|
375
|
+
const m = parseDashboardManifest(content, workerId, warn);
|
|
376
|
+
if (m.sections.length > 0) {
|
|
377
|
+
manifests.push(m);
|
|
378
|
+
seen.add(workerId);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch {}
|
|
382
|
+
}
|
|
383
|
+
return manifests;
|
|
384
|
+
}
|
|
385
|
+
export {
|
|
386
|
+
stripJsonc,
|
|
387
|
+
parseDashboardManifest,
|
|
388
|
+
loadDashboardManifestsFromRoot,
|
|
389
|
+
loadDashboardKvManifestFromRoot,
|
|
390
|
+
kvManifestFromDashboardManifests,
|
|
391
|
+
isDashboardSectionFlatKv,
|
|
392
|
+
isDashboardSectionEditable,
|
|
393
|
+
dashboardWorkerDir,
|
|
394
|
+
buildDashboardKvKey,
|
|
395
|
+
DASHBOARD_WORKER_PREFIX,
|
|
396
|
+
DASHBOARD_WORKER_IDS,
|
|
397
|
+
DASHBOARD_SECTION_PREFIX,
|
|
398
|
+
DASHBOARD_SECTIONS_UI_SKIP,
|
|
399
|
+
DASHBOARD_SECTIONS_NOT_FLAT_KV,
|
|
400
|
+
DASHBOARD_FIELD_KV_OVERRIDES
|
|
401
|
+
};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Error handling utilities for Cloudflare Workers and Dashboard
|
|
7
|
+
* Centralizes error response creation across the monorepo
|
|
8
|
+
*/
|
|
9
|
+
import type { AppError } from "./types/errors";
|
|
10
|
+
export type { AppError };
|
|
11
|
+
/**
|
|
12
|
+
* Safely extract an error message from any caught value.
|
|
13
|
+
* Eliminates the duplicated `error instanceof Error ? error.message : String(error)`
|
|
14
|
+
* pattern that appears ~40+ times across the codebase.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* try { ... } catch (err) {
|
|
18
|
+
* console.error(toError(err));
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
export declare function toError(err: unknown, fallback?: string): string;
|
|
22
|
+
export declare function createJsonResponse(data: unknown, status?: number): Response;
|
|
23
|
+
/**
|
|
24
|
+
* Create a success response with standard format
|
|
25
|
+
*/
|
|
26
|
+
export declare function createSuccessResponse(result?: unknown): Response;
|
|
27
|
+
/**
|
|
28
|
+
* Create a Cloudflare Worker error response
|
|
29
|
+
* Includes `success: false` in the response body for consistent client-side handling
|
|
30
|
+
*/
|
|
31
|
+
export declare function createErrorResponse(error: AppError | string, status?: number): Response;
|
|
32
|
+
/**
|
|
33
|
+
* Predefined error factories for common error types
|
|
34
|
+
*/
|
|
35
|
+
export declare const Errors: {
|
|
36
|
+
badRequest: (message: string) => Response;
|
|
37
|
+
unauthorized: (message?: string) => Response;
|
|
38
|
+
forbidden: (message?: string) => Response;
|
|
39
|
+
notFound: (message?: string) => Response;
|
|
40
|
+
methodNotAllowed: (message?: string) => Response;
|
|
41
|
+
rateLimited: (retryAfter?: number) => Response;
|
|
42
|
+
internal: (err?: unknown) => Response;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,CAAC;AAEzB;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,SAAkB,GAAG,MAAM,CAkBxE;AA4FD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,SAAM,GAAG,QAAQ,CAKxE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAEhE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,GAAG,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAcV;AAED;;GAEG;AACH,eAAO,MAAM,MAAM;0BACK,MAAM;;;;;+BAUD,MAAM;qBAShB,OAAO;CAUzB,CAAC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
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/errors.ts
|
|
19
|
+
function toError(err, fallback = "Unknown error") {
|
|
20
|
+
if (err instanceof Error)
|
|
21
|
+
return err.message;
|
|
22
|
+
if (typeof err === "string")
|
|
23
|
+
return err;
|
|
24
|
+
if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
|
|
25
|
+
return err.message;
|
|
26
|
+
}
|
|
27
|
+
if (err === null || err === undefined)
|
|
28
|
+
return fallback;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.stringify(err) || fallback;
|
|
31
|
+
} catch {
|
|
32
|
+
return fallback;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
var SENSITIVE_FIELDS = new Set([
|
|
36
|
+
"password",
|
|
37
|
+
"token",
|
|
38
|
+
"secret",
|
|
39
|
+
"api_key",
|
|
40
|
+
"apiKey",
|
|
41
|
+
"authorization"
|
|
42
|
+
]);
|
|
43
|
+
function hasSensitiveFields(obj) {
|
|
44
|
+
for (const key of Object.keys(obj)) {
|
|
45
|
+
if (SENSITIVE_FIELDS.has(key))
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function hasErrorValues(obj) {
|
|
51
|
+
for (const v of Object.values(obj)) {
|
|
52
|
+
if (v instanceof Error)
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
function sanitizeOutput(data) {
|
|
58
|
+
if (data === null || data === undefined)
|
|
59
|
+
return data;
|
|
60
|
+
const t = typeof data;
|
|
61
|
+
if (t === "string" || t === "number" || t === "boolean")
|
|
62
|
+
return data;
|
|
63
|
+
if (data instanceof Error) {
|
|
64
|
+
return { name: data.name, message: data.message };
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(data)) {
|
|
67
|
+
return data.map(sanitizeOutput);
|
|
68
|
+
}
|
|
69
|
+
if (t === "object") {
|
|
70
|
+
const obj = data;
|
|
71
|
+
const keys = Object.keys(obj);
|
|
72
|
+
if (keys.length < 10) {
|
|
73
|
+
const sanitized2 = {};
|
|
74
|
+
for (const k of keys) {
|
|
75
|
+
if (k === "stack" || k === "cause")
|
|
76
|
+
continue;
|
|
77
|
+
const v = obj[k];
|
|
78
|
+
sanitized2[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
79
|
+
}
|
|
80
|
+
return sanitized2;
|
|
81
|
+
}
|
|
82
|
+
if (!hasSensitiveFields(obj) && !hasErrorValues(obj)) {
|
|
83
|
+
return obj;
|
|
84
|
+
}
|
|
85
|
+
const sanitized = {};
|
|
86
|
+
for (const k of keys) {
|
|
87
|
+
if (k === "stack" || k === "cause")
|
|
88
|
+
continue;
|
|
89
|
+
const v = obj[k];
|
|
90
|
+
sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
91
|
+
}
|
|
92
|
+
return sanitized;
|
|
93
|
+
}
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
function createJsonResponse(data, status = 200) {
|
|
97
|
+
return new Response(JSON.stringify(sanitizeOutput(data)), {
|
|
98
|
+
status,
|
|
99
|
+
headers: { "Content-Type": "application/json" }
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function createSuccessResponse(result) {
|
|
103
|
+
return createJsonResponse({ success: true, result }, 200);
|
|
104
|
+
}
|
|
105
|
+
function createErrorResponse(error, status) {
|
|
106
|
+
const message = typeof error === "string" ? error : error.message;
|
|
107
|
+
const statusCode = typeof error === "string" ? status ?? 500 : error.status ?? 500;
|
|
108
|
+
const body = { success: false, error: message };
|
|
109
|
+
if (typeof error !== "string" && error.code)
|
|
110
|
+
body.code = error.code;
|
|
111
|
+
if (typeof error !== "string" && error.details !== undefined)
|
|
112
|
+
body.details = error.details;
|
|
113
|
+
return new Response(JSON.stringify(body), {
|
|
114
|
+
status: statusCode,
|
|
115
|
+
headers: { "Content-Type": "application/json" }
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
var Errors = {
|
|
119
|
+
badRequest: (message) => createErrorResponse({ message, status: 400, code: "BAD_REQUEST" }),
|
|
120
|
+
unauthorized: (message = "Unauthorized") => createErrorResponse({ message, status: 401, code: "UNAUTHORIZED" }),
|
|
121
|
+
forbidden: (message = "Forbidden") => createErrorResponse({ message, status: 403, code: "FORBIDDEN" }),
|
|
122
|
+
notFound: (message = "Not found") => createErrorResponse({ message, status: 404, code: "NOT_FOUND" }),
|
|
123
|
+
methodNotAllowed: (message = "Method not allowed") => createErrorResponse({ message, status: 405, code: "METHOD_NOT_ALLOWED" }),
|
|
124
|
+
rateLimited: (retryAfter) => {
|
|
125
|
+
const res = createErrorResponse({
|
|
126
|
+
message: "Rate limit exceeded",
|
|
127
|
+
status: 429,
|
|
128
|
+
code: "RATE_LIMITED"
|
|
129
|
+
});
|
|
130
|
+
if (retryAfter)
|
|
131
|
+
res.headers.set("Retry-After", String(retryAfter));
|
|
132
|
+
return res;
|
|
133
|
+
},
|
|
134
|
+
internal: (err) => {
|
|
135
|
+
const message = err ? toError(err, "Internal server error") : "Internal server error";
|
|
136
|
+
return createErrorResponse({
|
|
137
|
+
message,
|
|
138
|
+
status: 500,
|
|
139
|
+
code: "INTERNAL_ERROR"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
export {
|
|
144
|
+
toError,
|
|
145
|
+
createSuccessResponse,
|
|
146
|
+
createJsonResponse,
|
|
147
|
+
createErrorResponse,
|
|
148
|
+
Errors
|
|
149
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
export type ExchangeName = "binance" | "mexc" | "bybit";
|
|
6
|
+
export interface ExchangeConfig {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
apiSecret: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
testnet?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface TradeParams {
|
|
13
|
+
symbol: string;
|
|
14
|
+
side: "long" | "short";
|
|
15
|
+
orderType?: string;
|
|
16
|
+
quantity: number;
|
|
17
|
+
price?: number;
|
|
18
|
+
reduceOnly?: boolean;
|
|
19
|
+
leverage?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface OrderResponse {
|
|
22
|
+
orderId: string;
|
|
23
|
+
symbol: string;
|
|
24
|
+
status: string;
|
|
25
|
+
executedQty?: string;
|
|
26
|
+
price?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Position {
|
|
29
|
+
symbol: string;
|
|
30
|
+
side: "long" | "short";
|
|
31
|
+
quantity: number;
|
|
32
|
+
entryPrice: number;
|
|
33
|
+
unrealizedPnl?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generic exchange provider interface.
|
|
37
|
+
* Workers implement this to provide exchange-specific client creation and credential validation.
|
|
38
|
+
*
|
|
39
|
+
* @typeParam TClient - The exchange client type (e.g., IExchangeClient)
|
|
40
|
+
* @typeParam TEnv - The worker's environment/bindings type (e.g., Env)
|
|
41
|
+
*/
|
|
42
|
+
/** Options forwarded when constructing an exchange client. */
|
|
43
|
+
export interface ClientCreateOptions {
|
|
44
|
+
/** Route orders to the exchange testnet/sandbox API. */
|
|
45
|
+
testnet?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface IExchangeProvider<TClient, TEnv> {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
createClient(env: TEnv, options?: ClientCreateOptions): TClient;
|
|
50
|
+
hasCredentials(env: TEnv): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Whether this exchange exposes a testnet/sandbox trading API.
|
|
53
|
+
* Defaults to true when omitted; set false for live-only exchanges.
|
|
54
|
+
*/
|
|
55
|
+
readonly supportsTestTrading?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generic exchange router that maps exchange names to providers.
|
|
59
|
+
* Workers extend or compose this to add worker-specific routing logic.
|
|
60
|
+
*
|
|
61
|
+
* @typeParam TClient - The exchange client type
|
|
62
|
+
* @typeParam TEnv - The worker's environment type
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const router = new ExchangeRouter<IExchangeClient, Env>();
|
|
67
|
+
* router.registerProvider(new BinanceProvider());
|
|
68
|
+
* const { client } = router.route("binance", env);
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare class ExchangeRouter<TClient, TEnv> {
|
|
72
|
+
private readonly providers;
|
|
73
|
+
registerProvider(provider: IExchangeProvider<TClient, TEnv>): void;
|
|
74
|
+
route(exchange: string, env: TEnv, options?: ClientCreateOptions): {
|
|
75
|
+
exchange: string;
|
|
76
|
+
client: TClient;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export declare abstract class BaseExchangeClient {
|
|
80
|
+
protected readonly apiKey: string;
|
|
81
|
+
protected readonly apiSecret: string;
|
|
82
|
+
protected readonly baseUrl: string;
|
|
83
|
+
protected readonly isTestnet: boolean;
|
|
84
|
+
constructor(config: ExchangeConfig);
|
|
85
|
+
protected abstract getDefaultBaseUrl(): string;
|
|
86
|
+
protected abstract generateSignature(params: Record<string, string | number | boolean>): Promise<string>;
|
|
87
|
+
protected abstract buildHeaders(method: string, path: string, params?: Record<string, string | number | boolean>): Headers;
|
|
88
|
+
protected fetch<T>(method: string, path: string, params?: Record<string, string | number | boolean>): Promise<T>;
|
|
89
|
+
abstract setLeverage(symbol: string, leverage: number): Promise<void>;
|
|
90
|
+
abstract executeTrade(params: TradeParams): Promise<OrderResponse>;
|
|
91
|
+
abstract getAccountInfo(): Promise<Record<string, unknown>>;
|
|
92
|
+
abstract getPositions(symbol?: string): Promise<Position[]>;
|
|
93
|
+
openLong(symbol: string, quantity: number, price?: number, orderType?: string): Promise<OrderResponse>;
|
|
94
|
+
openShort(symbol: string, quantity: number, price?: number, orderType?: string): Promise<OrderResponse>;
|
|
95
|
+
closeLong(symbol: string, quantity: number): Promise<OrderResponse>;
|
|
96
|
+
closeShort(symbol: string, quantity: number): Promise<OrderResponse>;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=exchange-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange-client.d.ts","sourceRoot":"","sources":["../src/exchange-client.ts"],"names":[],"mappings":"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"}
|