@tradejs/infra 1.0.9 → 1.0.11

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.
@@ -0,0 +1,20 @@
1
+ import { RuntimeDeployment, RuntimeDeploymentHeartbeat, TradingAccountRef, MarketUniverse } from '@tradejs/types';
2
+
3
+ declare const listTradingAccounts: (userName: string) => Promise<TradingAccountRef[]>;
4
+ declare const getTradingAccount: (userName: string, accountId: string) => Promise<TradingAccountRef | null>;
5
+ declare const saveTradingAccount: (userName: string, account: TradingAccountRef) => Promise<TradingAccountRef>;
6
+ declare const deleteTradingAccount: (userName: string, accountId: string) => Promise<boolean>;
7
+ declare const resolveTradingAccount: ({ userName, accountId, provider, universe, }: {
8
+ userName: string;
9
+ accountId?: string;
10
+ provider: string;
11
+ universe?: MarketUniverse;
12
+ }) => Promise<TradingAccountRef | null>;
13
+ declare const listRuntimeDeployments: (userName: string) => Promise<RuntimeDeployment[]>;
14
+ declare const getRuntimeDeployment: (userName: string, deploymentId: string) => Promise<RuntimeDeployment | null>;
15
+ declare const saveRuntimeDeployment: (userName: string, deployment: RuntimeDeployment) => Promise<RuntimeDeployment>;
16
+ declare const deleteRuntimeDeployment: (userName: string, deploymentId: string) => Promise<void>;
17
+ declare const getRuntimeDeploymentHeartbeat: (userName: string, deploymentId: string) => Promise<RuntimeDeploymentHeartbeat | null>;
18
+ declare const saveRuntimeDeploymentHeartbeat: (userName: string, heartbeat: RuntimeDeploymentHeartbeat) => Promise<RuntimeDeploymentHeartbeat>;
19
+
20
+ export { deleteRuntimeDeployment, deleteTradingAccount, getRuntimeDeployment, getRuntimeDeploymentHeartbeat, getTradingAccount, listRuntimeDeployments, listTradingAccounts, resolveTradingAccount, saveRuntimeDeployment, saveRuntimeDeploymentHeartbeat, saveTradingAccount };
@@ -0,0 +1,20 @@
1
+ import { RuntimeDeployment, RuntimeDeploymentHeartbeat, TradingAccountRef, MarketUniverse } from '@tradejs/types';
2
+
3
+ declare const listTradingAccounts: (userName: string) => Promise<TradingAccountRef[]>;
4
+ declare const getTradingAccount: (userName: string, accountId: string) => Promise<TradingAccountRef | null>;
5
+ declare const saveTradingAccount: (userName: string, account: TradingAccountRef) => Promise<TradingAccountRef>;
6
+ declare const deleteTradingAccount: (userName: string, accountId: string) => Promise<boolean>;
7
+ declare const resolveTradingAccount: ({ userName, accountId, provider, universe, }: {
8
+ userName: string;
9
+ accountId?: string;
10
+ provider: string;
11
+ universe?: MarketUniverse;
12
+ }) => Promise<TradingAccountRef | null>;
13
+ declare const listRuntimeDeployments: (userName: string) => Promise<RuntimeDeployment[]>;
14
+ declare const getRuntimeDeployment: (userName: string, deploymentId: string) => Promise<RuntimeDeployment | null>;
15
+ declare const saveRuntimeDeployment: (userName: string, deployment: RuntimeDeployment) => Promise<RuntimeDeployment>;
16
+ declare const deleteRuntimeDeployment: (userName: string, deploymentId: string) => Promise<void>;
17
+ declare const getRuntimeDeploymentHeartbeat: (userName: string, deploymentId: string) => Promise<RuntimeDeploymentHeartbeat | null>;
18
+ declare const saveRuntimeDeploymentHeartbeat: (userName: string, heartbeat: RuntimeDeploymentHeartbeat) => Promise<RuntimeDeploymentHeartbeat>;
19
+
20
+ export { deleteRuntimeDeployment, deleteTradingAccount, getRuntimeDeployment, getRuntimeDeploymentHeartbeat, getTradingAccount, listRuntimeDeployments, listTradingAccounts, resolveTradingAccount, saveRuntimeDeployment, saveRuntimeDeploymentHeartbeat, saveTradingAccount };
@@ -0,0 +1,558 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/tradingAccounts.ts
31
+ var tradingAccounts_exports = {};
32
+ __export(tradingAccounts_exports, {
33
+ deleteRuntimeDeployment: () => deleteRuntimeDeployment,
34
+ deleteTradingAccount: () => deleteTradingAccount,
35
+ getRuntimeDeployment: () => getRuntimeDeployment,
36
+ getRuntimeDeploymentHeartbeat: () => getRuntimeDeploymentHeartbeat,
37
+ getTradingAccount: () => getTradingAccount,
38
+ listRuntimeDeployments: () => listRuntimeDeployments,
39
+ listTradingAccounts: () => listTradingAccounts,
40
+ resolveTradingAccount: () => resolveTradingAccount,
41
+ saveRuntimeDeployment: () => saveRuntimeDeployment,
42
+ saveRuntimeDeploymentHeartbeat: () => saveRuntimeDeploymentHeartbeat,
43
+ saveTradingAccount: () => saveTradingAccount
44
+ });
45
+ module.exports = __toCommonJS(tradingAccounts_exports);
46
+
47
+ // src/redis.ts
48
+ var import_ioredis = __toESM(require("ioredis"));
49
+ var TTL_1D = 86400;
50
+ var SCREENSHOT_TOKEN_TTL_SECONDS = 15 * 60;
51
+ var toJson = (value) => JSON.stringify(value);
52
+ var logger = {
53
+ log: (level, message, ...args) => {
54
+ const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
55
+ console[method](`[infra:redis] ${message}`, ...args);
56
+ }
57
+ };
58
+ var redisConnectionWarningShown = false;
59
+ var redisUnavailable = false;
60
+ var isRedisConnectivityError = (error) => {
61
+ const errorText = [error.name, error.message, String(error)].join(" ");
62
+ return /ECONNREFUSED|ECONNRESET|ECONNABORTED|EPIPE|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|MaxRetriesPerRequestError|Connection is closed|Stream isn't writeable/i.test(
63
+ errorText
64
+ );
65
+ };
66
+ var toNonNegativeInt = (value, fallback) => {
67
+ const parsed = Number.parseInt(String(value ?? ""), 10);
68
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
69
+ };
70
+ var toPositiveInt = (value, fallback) => {
71
+ const parsed = Number.parseInt(String(value ?? ""), 10);
72
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
73
+ };
74
+ var markRedisUnavailable = (error) => {
75
+ redisUnavailable = true;
76
+ if (redisConnectionWarningShown) return;
77
+ redisConnectionWarningShown = true;
78
+ logger.log(
79
+ "warn",
80
+ "Redis is unavailable: %s. Cache-dependent features are temporarily disabled.",
81
+ error.message
82
+ );
83
+ };
84
+ var getRedis = () => {
85
+ if (!global.__redis__) {
86
+ const host = process.env.REDIS_HOST || "127.0.0.1";
87
+ const port = toPositiveInt(process.env.REDIS_PORT, 6379);
88
+ const connectTimeout = toPositiveInt(
89
+ process.env.REDIS_CONNECT_TIMEOUT_MS,
90
+ 3e3
91
+ );
92
+ const maxRetriesPerRequest = toNonNegativeInt(
93
+ process.env.REDIS_MAX_RETRIES_PER_REQUEST,
94
+ 1
95
+ );
96
+ global.__redis__ = new import_ioredis.default({
97
+ host,
98
+ port,
99
+ connectTimeout,
100
+ maxRetriesPerRequest,
101
+ enableOfflineQueue: false,
102
+ retryStrategy: (attempt) => Math.min(attempt * 200, 2e3)
103
+ });
104
+ global.__redis__.on("error", (error) => {
105
+ if (isRedisConnectivityError(error)) {
106
+ markRedisUnavailable(error);
107
+ return;
108
+ }
109
+ logger.log("error", "Redis client error: %s", String(error));
110
+ });
111
+ global.__redis__.on("ready", () => {
112
+ redisUnavailable = false;
113
+ if (redisConnectionWarningShown) {
114
+ redisConnectionWarningShown = false;
115
+ logger.log("info", "Redis connection restored");
116
+ }
117
+ });
118
+ }
119
+ return global.__redis__;
120
+ };
121
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
122
+ var getRedisStatus = (redis) => String(redis.status ?? "ready");
123
+ var waitForRedisReady = async (redis) => {
124
+ if (getRedisStatus(redis) === "ready") {
125
+ return true;
126
+ }
127
+ const readyTimeoutMs = toPositiveInt(
128
+ process.env.REDIS_READY_TIMEOUT_MS,
129
+ toPositiveInt(process.env.REDIS_CONNECT_TIMEOUT_MS, 3e3)
130
+ );
131
+ const startedAt = Date.now();
132
+ while (Date.now() - startedAt < readyTimeoutMs) {
133
+ if (getRedisStatus(redis) === "ready") {
134
+ return true;
135
+ }
136
+ if (getRedisStatus(redis) === "end") {
137
+ return false;
138
+ }
139
+ await sleep(50);
140
+ }
141
+ return getRedisStatus(redis) === "ready";
142
+ };
143
+ var getReadyRedis = async () => {
144
+ if (redisUnavailable) return null;
145
+ const redis = getRedis();
146
+ const ready = await waitForRedisReady(redis);
147
+ if (ready) {
148
+ return redis;
149
+ }
150
+ markRedisUnavailable(
151
+ new Error(`Redis is not ready (status=${getRedisStatus(redis)})`)
152
+ );
153
+ return null;
154
+ };
155
+ var toResultString = (result) => {
156
+ if (result == null) return null;
157
+ if (typeof result === "string") return result;
158
+ if (Buffer.isBuffer(result)) return result.toString("utf8");
159
+ return String(result);
160
+ };
161
+ var DEFAULT_OPTIONS = {
162
+ expire: TTL_1D
163
+ };
164
+ var parseJsonOrDeleteKey = async (redis, key, raw, fallback) => {
165
+ try {
166
+ return JSON.parse(raw);
167
+ } catch (e) {
168
+ logger.log("error", "failed JSON.parse(%s): %s", key, String(e));
169
+ await redis.del(key);
170
+ return fallback;
171
+ }
172
+ };
173
+ var getKeys = async (prefix) => {
174
+ if (redisUnavailable) return [];
175
+ const redis = await getReadyRedis();
176
+ if (!redis) return [];
177
+ const keys = [];
178
+ try {
179
+ let cursor = "0";
180
+ do {
181
+ const [nextCursor, batch] = await redis.scan(
182
+ cursor,
183
+ "MATCH",
184
+ `${prefix}*`,
185
+ "COUNT",
186
+ "200"
187
+ );
188
+ cursor = nextCursor;
189
+ for (const key of batch) {
190
+ if (key.startsWith(prefix)) {
191
+ keys.push(key);
192
+ }
193
+ }
194
+ } while (cursor !== "0");
195
+ } catch (e) {
196
+ if (e instanceof Error && isRedisConnectivityError(e)) {
197
+ markRedisUnavailable(e);
198
+ return [];
199
+ }
200
+ logger.log("warn", "failed SCAN for %s: %s", prefix, String(e));
201
+ return [];
202
+ }
203
+ return keys;
204
+ };
205
+ var getData = async (key, fallback = []) => {
206
+ if (redisUnavailable) return fallback;
207
+ const redis = await getReadyRedis();
208
+ if (!redis) return fallback;
209
+ try {
210
+ const rawJson = await redis.call("JSON.GET", key);
211
+ const raw = toResultString(rawJson);
212
+ if (raw == null) return fallback;
213
+ return parseJsonOrDeleteKey(redis, key, raw, fallback);
214
+ } catch (e) {
215
+ if (e instanceof Error && isRedisConnectivityError(e)) {
216
+ markRedisUnavailable(e);
217
+ return fallback;
218
+ }
219
+ logger.log(
220
+ "error",
221
+ "failed JSON.GET %s: %s (fallback to GET)",
222
+ key,
223
+ String(e)
224
+ );
225
+ }
226
+ try {
227
+ const raw = await redis.get(key);
228
+ if (raw == null) return fallback;
229
+ return parseJsonOrDeleteKey(redis, key, raw, fallback);
230
+ } catch (e) {
231
+ if (e instanceof Error && isRedisConnectivityError(e)) {
232
+ markRedisUnavailable(e);
233
+ return fallback;
234
+ }
235
+ logger.log("error", "failed GET %s: %s", key, String(e));
236
+ return fallback;
237
+ }
238
+ };
239
+ var delKey = async (key) => {
240
+ return delKeyWithOptions(key);
241
+ };
242
+ var RedisWriteBlockedError = class extends Error {
243
+ constructor(message) {
244
+ super(message);
245
+ this.name = "RedisWriteBlockedError";
246
+ }
247
+ };
248
+ var delKeyWithOptions = async (key, options = {}) => {
249
+ if (redisUnavailable) return false;
250
+ const { raiseOnMisconf = false } = options;
251
+ const redis = await getReadyRedis();
252
+ if (!redis) return false;
253
+ try {
254
+ const result = await redis.del(key);
255
+ if (result === 1) {
256
+ return true;
257
+ }
258
+ return false;
259
+ } catch (e) {
260
+ if (e instanceof Error && isRedisConnectivityError(e)) {
261
+ markRedisUnavailable(e);
262
+ return false;
263
+ }
264
+ const msg = String(e);
265
+ if (raiseOnMisconf && msg.includes("MISCONF")) {
266
+ throw new RedisWriteBlockedError(msg);
267
+ }
268
+ logger.log("error", "failed DEL %s: %s", key, String(e));
269
+ return false;
270
+ }
271
+ };
272
+ var setData = async (key, data, options = {}) => {
273
+ if (redisUnavailable) return;
274
+ const { expire } = { ...DEFAULT_OPTIONS, ...options };
275
+ const redis = await getReadyRedis();
276
+ if (!redis) return;
277
+ const value = toJson(data);
278
+ try {
279
+ await redis.call("JSON.SET", key, "$", value);
280
+ if (expire) {
281
+ await redis.expire(key, expire);
282
+ }
283
+ } catch (e) {
284
+ if (e instanceof Error && isRedisConnectivityError(e)) {
285
+ markRedisUnavailable(e);
286
+ return;
287
+ }
288
+ logger.log(
289
+ "error",
290
+ "failed JSON.SET %s: %s (fallback to SET)",
291
+ key,
292
+ String(e)
293
+ );
294
+ try {
295
+ if (expire) {
296
+ await redis.set(key, value, "EX", expire);
297
+ } else {
298
+ await redis.set(key, value);
299
+ }
300
+ } catch (e2) {
301
+ if (e2 instanceof Error && isRedisConnectivityError(e2)) {
302
+ markRedisUnavailable(e2);
303
+ return;
304
+ }
305
+ logger.log("error", "failed SET %s: %s", key, String(e2));
306
+ }
307
+ }
308
+ };
309
+ var redisKeys = {
310
+ users: () => "users:index:",
311
+ user: (userName) => `users:index:${userName}`,
312
+ tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
313
+ tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
314
+ runtimeDeployments: (userName) => `users:${userName}:runtime:deployments:`,
315
+ runtimeDeployment: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}`,
316
+ runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
317
+ bots: (userName) => `users:${userName}:bots`,
318
+ botsPrefix: () => "users:",
319
+ bot: (userName, botId) => `users:${userName}:bots:${botId}`,
320
+ backtestConfig: (userName, config) => `users:${userName}:backtests:configs:${config}`,
321
+ strategies: (userName) => `users:${userName}:strategies`,
322
+ strategyConfig: (userName, strategyName, configId = "config") => `users:${userName}:strategies:${strategyName}:${configId}`,
323
+ strategyResults: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:results`,
324
+ strategyCharts: (userName, mode) => `users:${userName}:strategies:charts:${mode}`,
325
+ strategyChartCards: (userName, mode) => `users:${userName}:strategies:charts:${mode}:cards:`,
326
+ strategyChartCard: (userName, mode, cardId) => `users:${userName}:strategies:charts:${mode}:cards:${cardId}`,
327
+ tests: (userName, strategyName) => strategyName ? `users:${userName}:tests:${strategyName}` : `users:${userName}:tests:`,
328
+ testOrders: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:orders`,
329
+ testConfig: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:config`,
330
+ testStat: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:stat`,
331
+ testSummaries: (userName) => `users:${userName}:tests:index:summary`,
332
+ cacheChunk: (userName, chunkId) => `users:${userName}:cache:tests:chunks:${chunkId}`,
333
+ cacheOrders: (userName, orderLogId) => `users:${userName}:cache:tests:orders:${orderLogId}`,
334
+ cachePositions: (userName, orderLogId) => `users:${userName}:cache:tests:positions:${orderLogId}`,
335
+ tickerUniverse: (userName, connectorName, universe, accountId) => universe || accountId ? `users:${userName}:cache:tickers:${connectorName}:${universe ?? "crypto"}:${accountId ?? "default"}` : `users:${userName}:cache:tickers:${connectorName}`,
336
+ signal: (symbol, signalId) => `signals:${symbol}:${signalId}`,
337
+ signalsBySymbol: (symbol) => `signals:${symbol}:`,
338
+ storeSignal: (symbol, signalId) => `store:signals:${symbol}:${signalId}`,
339
+ runtimeSignals: (userName) => `users:${userName}:runtime:signals:`,
340
+ runtimeSignal: (userName, signalId) => `users:${userName}:runtime:signals:${signalId}`,
341
+ runtimeSignalBuckets: (userName) => `users:${userName}:runtime:signals:days:`,
342
+ runtimeSignalBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signals:days:${dayKey}:${strategyName}`,
343
+ runtimeSignalEvaluations: (userName) => `users:${userName}:runtime:signal-evaluations:`,
344
+ runtimeSignalEvaluation: (userName, evaluationId) => `users:${userName}:runtime:signal-evaluations:${evaluationId}`,
345
+ runtimeSignalEvaluationBuckets: (userName) => `users:${userName}:runtime:signal-evaluations:days:`,
346
+ runtimeSignalEvaluationBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signal-evaluations:days:${dayKey}:${strategyName}`,
347
+ runtimeSignalEvaluationStatsBuckets: (userName) => `users:${userName}:runtime:signal-evaluation-stats:days:`,
348
+ runtimeSignalEvaluationStatsBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signal-evaluation-stats:days:${dayKey}:${strategyName}`,
349
+ runtimeTrades: (userName) => `users:${userName}:runtime:trade-records:`,
350
+ runtimeTrade: (userName, orderId) => `users:${userName}:runtime:trade-records:${orderId}`,
351
+ runtimeTradeBuckets: (userName) => `users:${userName}:runtime:trade-records:days:`,
352
+ runtimeTradeBucket: (userName, dayKey) => `users:${userName}:runtime:trade-records:days:${dayKey}`,
353
+ runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
354
+ runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
355
+ aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
356
+ analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
357
+ screenshotSessionToken: (token) => `auth:screenshot:${token}`,
358
+ backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
359
+ backtestJobs: (userName) => `users:${userName}:backtests:jobs:`,
360
+ backtestJob: (userName, jobId) => `users:${userName}:backtests:jobs:${jobId}`,
361
+ backtestRuns: (userName) => `users:${userName}:backtests:runs:`,
362
+ backtestRun: (userName, runId) => `users:${userName}:backtests:runs:${runId}`,
363
+ backtestRunResults: (userName, runId) => `users:${userName}:backtests:runs:${runId}:results`,
364
+ backtestLatestRun: (userName, config) => `users:${userName}:backtests:latest:${config}`,
365
+ researchRuns: (userName) => `users:${userName}:research:runs:`,
366
+ researchRun: (userName, runId) => `users:${userName}:research:runs:${runId}`,
367
+ researchLatestRun: (userName, strategyName) => `users:${userName}:research:latest:${strategyName}`,
368
+ mlSignalsByStrategy: (strategyName) => `ml:${strategyName}:signals:`,
369
+ mlSignals: () => "ml:",
370
+ mlSignal: (strategyName, signalId) => `ml:${strategyName}:signals:${signalId}`,
371
+ mlResultsByStrategy: (strategyName) => `ml:${strategyName}:results:`,
372
+ mlResults: () => "ml:",
373
+ mlResult: (strategyName, signalId) => `ml:${strategyName}:results:${signalId}`
374
+ };
375
+
376
+ // src/tradingAccounts.ts
377
+ var normalizeId = (value, label) => {
378
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
379
+ if (!normalized) {
380
+ throw new Error(`${label} is required`);
381
+ }
382
+ return normalized;
383
+ };
384
+ var isTradingAccount = (value) => Boolean(
385
+ value && typeof value === "object" && typeof value.id === "string" && typeof value.provider === "string"
386
+ );
387
+ var isRuntimeDeployment = (value) => Boolean(
388
+ value && typeof value === "object" && typeof value.id === "string" && typeof value.accountId === "string"
389
+ );
390
+ var withProviderUniverses = (account) => account.provider.toLowerCase() === "bybit" ? { ...account, universes: ["crypto", "tradfi"] } : account;
391
+ var listTradingAccounts = async (userName) => {
392
+ if (typeof redisKeys.tradingAccounts !== "function") return [];
393
+ const keys = await getKeys(redisKeys.tradingAccounts(userName));
394
+ const values = await Promise.all(keys.map((key) => getData(key, null)));
395
+ return values.filter(isTradingAccount).map(withProviderUniverses).sort((left, right) => left.label.localeCompare(right.label));
396
+ };
397
+ var getTradingAccount = async (userName, accountId) => {
398
+ const normalizedId = normalizeId(accountId, "Account id");
399
+ const value = await getData(
400
+ redisKeys.tradingAccount(userName, normalizedId),
401
+ null
402
+ );
403
+ return isTradingAccount(value) ? withProviderUniverses(value) : null;
404
+ };
405
+ var saveTradingAccount = async (userName, account) => {
406
+ const normalized = withProviderUniverses({
407
+ ...account,
408
+ id: normalizeId(account.id, "Account id"),
409
+ label: account.label.trim(),
410
+ provider: account.provider.trim().toLowerCase(),
411
+ universes: [...new Set(account.universes)]
412
+ });
413
+ if (normalized.isDefault) {
414
+ const previousDefaults = (await listTradingAccounts(userName)).filter(
415
+ (candidate) => candidate.provider === normalized.provider && candidate.id !== normalized.id && candidate.isDefault && candidate.universes.some(
416
+ (universe) => normalized.universes.includes(universe)
417
+ )
418
+ );
419
+ await Promise.all(
420
+ previousDefaults.map(
421
+ (candidate) => setData(
422
+ redisKeys.tradingAccount(userName, candidate.id),
423
+ { ...candidate, isDefault: false },
424
+ { expire: 0 }
425
+ )
426
+ )
427
+ );
428
+ }
429
+ await setData(redisKeys.tradingAccount(userName, normalized.id), normalized, {
430
+ expire: 0
431
+ });
432
+ return normalized;
433
+ };
434
+ var deleteTradingAccount = async (userName, accountId) => delKey(
435
+ redisKeys.tradingAccount(userName, normalizeId(accountId, "Account id"))
436
+ );
437
+ var resolveTradingAccount = async ({
438
+ userName,
439
+ accountId,
440
+ provider,
441
+ universe
442
+ }) => {
443
+ if (accountId) {
444
+ const account = await getTradingAccount(userName, accountId);
445
+ if (!account) {
446
+ throw new Error(`Trading account not found: ${accountId}`);
447
+ }
448
+ if (account.provider !== provider.toLowerCase()) {
449
+ throw new Error(
450
+ `Trading account ${accountId} belongs to ${account.provider}, not ${provider}`
451
+ );
452
+ }
453
+ if (!account.enabled) {
454
+ throw new Error(`Trading account is disabled: ${accountId}`);
455
+ }
456
+ if (universe && !account.universes.includes(universe)) {
457
+ throw new Error(
458
+ `Trading account ${accountId} does not support universe ${universe}`
459
+ );
460
+ }
461
+ return account;
462
+ }
463
+ const accounts = (await listTradingAccounts(userName)).filter(
464
+ (account) => account.enabled && account.provider === provider.toLowerCase() && (!universe || account.universes.includes(universe))
465
+ );
466
+ const selected = accounts.find((account) => account.isDefault) ?? (accounts.length === 1 ? accounts[0] : null);
467
+ if (selected) return selected;
468
+ if (provider.toLowerCase() !== "bybit" || accounts.length > 1) {
469
+ return null;
470
+ }
471
+ const legacy = await getData(redisKeys.user(userName));
472
+ const legacyApiKey = String(legacy?.BYBIT_API_KEY ?? "").trim();
473
+ const legacyApiSecret = String(legacy?.BYBIT_API_SECRET ?? "").trim();
474
+ if (!legacyApiKey || !legacyApiSecret) {
475
+ return null;
476
+ }
477
+ return {
478
+ id: "bybit-default",
479
+ label: "Bybit Default",
480
+ provider: "bybit",
481
+ enabled: true,
482
+ isDefault: true,
483
+ universes: ["crypto", "tradfi"],
484
+ environment: "mainnet",
485
+ apiKey: legacyApiKey,
486
+ apiSecret: legacyApiSecret
487
+ };
488
+ };
489
+ var listRuntimeDeployments = async (userName) => {
490
+ const keys = await getKeys(redisKeys.runtimeDeployments(userName));
491
+ const values = await Promise.all(keys.map((key) => getData(key, null)));
492
+ return values.filter(isRuntimeDeployment).sort((left, right) => left.label.localeCompare(right.label));
493
+ };
494
+ var getRuntimeDeployment = async (userName, deploymentId) => {
495
+ const normalizedId = normalizeId(deploymentId, "Deployment id");
496
+ const value = await getData(
497
+ redisKeys.runtimeDeployment(userName, normalizedId),
498
+ null
499
+ );
500
+ return isRuntimeDeployment(value) ? value : null;
501
+ };
502
+ var saveRuntimeDeployment = async (userName, deployment) => {
503
+ const normalized = {
504
+ ...deployment,
505
+ id: normalizeId(deployment.id, "Deployment id"),
506
+ label: deployment.label.trim(),
507
+ provider: deployment.provider.trim().toLowerCase(),
508
+ accountId: normalizeId(deployment.accountId, "Account id")
509
+ };
510
+ await setData(
511
+ redisKeys.runtimeDeployment(userName, normalized.id),
512
+ normalized,
513
+ { expire: 0 }
514
+ );
515
+ return normalized;
516
+ };
517
+ var deleteRuntimeDeployment = async (userName, deploymentId) => {
518
+ const normalizedId = normalizeId(deploymentId, "Deployment id");
519
+ await Promise.all([
520
+ delKey(redisKeys.runtimeDeployment(userName, normalizedId)),
521
+ delKey(redisKeys.runtimeDeploymentHeartbeat(userName, normalizedId))
522
+ ]);
523
+ };
524
+ var getRuntimeDeploymentHeartbeat = async (userName, deploymentId) => {
525
+ const value = await getData(
526
+ redisKeys.runtimeDeploymentHeartbeat(
527
+ userName,
528
+ normalizeId(deploymentId, "Deployment id")
529
+ ),
530
+ null
531
+ );
532
+ return value && typeof value === "object" ? value : null;
533
+ };
534
+ var saveRuntimeDeploymentHeartbeat = async (userName, heartbeat) => {
535
+ await setData(
536
+ redisKeys.runtimeDeploymentHeartbeat(
537
+ userName,
538
+ normalizeId(heartbeat.deploymentId, "Deployment id")
539
+ ),
540
+ heartbeat,
541
+ { expire: 0 }
542
+ );
543
+ return heartbeat;
544
+ };
545
+ // Annotate the CommonJS export names for ESM import in node:
546
+ 0 && (module.exports = {
547
+ deleteRuntimeDeployment,
548
+ deleteTradingAccount,
549
+ getRuntimeDeployment,
550
+ getRuntimeDeploymentHeartbeat,
551
+ getTradingAccount,
552
+ listRuntimeDeployments,
553
+ listTradingAccounts,
554
+ resolveTradingAccount,
555
+ saveRuntimeDeployment,
556
+ saveRuntimeDeploymentHeartbeat,
557
+ saveTradingAccount
558
+ });