@tradejs/infra 3.1.28-beta.252 → 3.1.28-beta.254

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.
@@ -602,6 +602,9 @@ var redisKeys = {
602
602
  tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
603
603
  tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
604
604
  runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
605
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
606
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
607
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
605
608
  runtimeControls: (userName) => `users:${userName}:runtime:controls`,
606
609
  bots: (userName) => `users:${userName}:bots`,
607
610
  botsPrefix: () => "users:",
package/dist/redis.d.mts CHANGED
@@ -47,6 +47,9 @@ declare const redisKeys: {
47
47
  tradingAccounts: (userName: string) => string;
48
48
  tradingAccount: (userName: string, accountId: string) => string;
49
49
  runtimeDeploymentHeartbeat: (userName: string, deploymentId: string) => string;
50
+ runtimeDeploymentCompositionState: (userName: string, deploymentId: string) => string;
51
+ runtimeDeploymentCompositionEvents: (userName: string, deploymentId: string) => string;
52
+ runtimeDeploymentCompositionEvent: (userName: string, deploymentId: string, eventId: string) => string;
50
53
  runtimeControls: (userName: string) => string;
51
54
  bots: (userName: string) => string;
52
55
  botsPrefix: () => string;
package/dist/redis.d.ts CHANGED
@@ -47,6 +47,9 @@ declare const redisKeys: {
47
47
  tradingAccounts: (userName: string) => string;
48
48
  tradingAccount: (userName: string, accountId: string) => string;
49
49
  runtimeDeploymentHeartbeat: (userName: string, deploymentId: string) => string;
50
+ runtimeDeploymentCompositionState: (userName: string, deploymentId: string) => string;
51
+ runtimeDeploymentCompositionEvents: (userName: string, deploymentId: string) => string;
52
+ runtimeDeploymentCompositionEvent: (userName: string, deploymentId: string, eventId: string) => string;
50
53
  runtimeControls: (userName: string) => string;
51
54
  bots: (userName: string) => string;
52
55
  botsPrefix: () => string;
package/dist/redis.js CHANGED
@@ -657,6 +657,9 @@ var redisKeys = {
657
657
  tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
658
658
  tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
659
659
  runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
660
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
661
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
662
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
660
663
  runtimeControls: (userName) => `users:${userName}:runtime:controls`,
661
664
  bots: (userName) => `users:${userName}:bots`,
662
665
  botsPrefix: () => "users:",
package/dist/redis.mjs CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  setDataStrict,
22
22
  setHashJsonField,
23
23
  setHashJsonFields
24
- } from "./chunk-46XQGVDB.mjs";
24
+ } from "./chunk-GTZOMAFK.mjs";
25
25
  export {
26
26
  RedisWriteBlockedError,
27
27
  closeRedisConnection,
@@ -257,6 +257,9 @@ var redisKeys = {
257
257
  tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
258
258
  tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
259
259
  runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
260
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
261
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
262
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
260
263
  runtimeControls: (userName) => `users:${userName}:runtime:controls`,
261
264
  bots: (userName) => `users:${userName}:bots`,
262
265
  botsPrefix: () => "users:",
@@ -3,7 +3,7 @@ import {
3
3
  getDataStrict,
4
4
  redisKeys,
5
5
  setDataStrict
6
- } from "./chunk-46XQGVDB.mjs";
6
+ } from "./chunk-GTZOMAFK.mjs";
7
7
 
8
8
  // src/runtimeControls.ts
9
9
  import { randomUUID } from "crypto";
@@ -0,0 +1,11 @@
1
+ import { RuntimeDeploymentCompositionEvent, RuntimeDeployment } from '@tradejs/types';
2
+
3
+ declare const isRuntimeDeploymentCompositionEvent: (value: unknown) => value is RuntimeDeploymentCompositionEvent;
4
+ declare const observeRuntimeDeploymentComposition: ({ userName, deployment, observedAt, }: {
5
+ userName: string;
6
+ deployment: RuntimeDeployment;
7
+ observedAt?: number;
8
+ }) => Promise<RuntimeDeploymentCompositionEvent | null>;
9
+ declare const loadRuntimeDeploymentCompositionEvents: (userName: string, deploymentId: string) => Promise<RuntimeDeploymentCompositionEvent[]>;
10
+
11
+ export { isRuntimeDeploymentCompositionEvent, loadRuntimeDeploymentCompositionEvents, observeRuntimeDeploymentComposition };
@@ -0,0 +1,11 @@
1
+ import { RuntimeDeploymentCompositionEvent, RuntimeDeployment } from '@tradejs/types';
2
+
3
+ declare const isRuntimeDeploymentCompositionEvent: (value: unknown) => value is RuntimeDeploymentCompositionEvent;
4
+ declare const observeRuntimeDeploymentComposition: ({ userName, deployment, observedAt, }: {
5
+ userName: string;
6
+ deployment: RuntimeDeployment;
7
+ observedAt?: number;
8
+ }) => Promise<RuntimeDeploymentCompositionEvent | null>;
9
+ declare const loadRuntimeDeploymentCompositionEvents: (userName: string, deploymentId: string) => Promise<RuntimeDeploymentCompositionEvent[]>;
10
+
11
+ export { isRuntimeDeploymentCompositionEvent, loadRuntimeDeploymentCompositionEvents, observeRuntimeDeploymentComposition };
@@ -0,0 +1,446 @@
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/runtimeDeploymentEvents.ts
31
+ var runtimeDeploymentEvents_exports = {};
32
+ __export(runtimeDeploymentEvents_exports, {
33
+ isRuntimeDeploymentCompositionEvent: () => isRuntimeDeploymentCompositionEvent,
34
+ loadRuntimeDeploymentCompositionEvents: () => loadRuntimeDeploymentCompositionEvents,
35
+ observeRuntimeDeploymentComposition: () => observeRuntimeDeploymentComposition
36
+ });
37
+ module.exports = __toCommonJS(runtimeDeploymentEvents_exports);
38
+ var import_node_crypto = require("crypto");
39
+ var import_types = require("@tradejs/types");
40
+
41
+ // src/redis.ts
42
+ var import_ioredis = __toESM(require("ioredis"));
43
+ var TTL_1D = 86400;
44
+ var SCREENSHOT_TOKEN_TTL_SECONDS = 15 * 60;
45
+ var toJson = (value) => JSON.stringify(value);
46
+ var logger = {
47
+ log: (level, message, ...args) => {
48
+ const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
49
+ console[method](`[infra:redis] ${message}`, ...args);
50
+ }
51
+ };
52
+ var redisConnectionWarningShown = false;
53
+ var redisUnavailable = false;
54
+ var isRedisConnectivityError = (error) => {
55
+ const errorText = [error.name, error.message, String(error)].join(" ");
56
+ return /ECONNREFUSED|ECONNRESET|ECONNABORTED|EPIPE|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|MaxRetriesPerRequestError|Connection is closed|Stream isn't writeable/i.test(
57
+ errorText
58
+ );
59
+ };
60
+ var toNonNegativeInt = (value, fallback) => {
61
+ const parsed = Number.parseInt(String(value ?? ""), 10);
62
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
63
+ };
64
+ var toPositiveInt = (value, fallback) => {
65
+ const parsed = Number.parseInt(String(value ?? ""), 10);
66
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
67
+ };
68
+ var markRedisUnavailable = (error) => {
69
+ redisUnavailable = true;
70
+ if (redisConnectionWarningShown) return;
71
+ redisConnectionWarningShown = true;
72
+ logger.log(
73
+ "warn",
74
+ "Redis is unavailable: %s. Cache-dependent features are temporarily disabled.",
75
+ error.message
76
+ );
77
+ };
78
+ var getRedis = () => {
79
+ if (!global.__redis__) {
80
+ const host = process.env.REDIS_HOST || "127.0.0.1";
81
+ const port = toPositiveInt(process.env.REDIS_PORT, 6379);
82
+ const connectTimeout = toPositiveInt(
83
+ process.env.REDIS_CONNECT_TIMEOUT_MS,
84
+ 3e3
85
+ );
86
+ const maxRetriesPerRequest = toNonNegativeInt(
87
+ process.env.REDIS_MAX_RETRIES_PER_REQUEST,
88
+ 1
89
+ );
90
+ global.__redis__ = new import_ioredis.default({
91
+ host,
92
+ port,
93
+ connectTimeout,
94
+ maxRetriesPerRequest,
95
+ enableOfflineQueue: false,
96
+ retryStrategy: (attempt) => Math.min(attempt * 200, 2e3)
97
+ });
98
+ global.__redis__.on("error", (error) => {
99
+ if (isRedisConnectivityError(error)) {
100
+ markRedisUnavailable(error);
101
+ return;
102
+ }
103
+ logger.log("error", "Redis client error: %s", String(error));
104
+ });
105
+ global.__redis__.on("ready", () => {
106
+ redisUnavailable = false;
107
+ if (redisConnectionWarningShown) {
108
+ redisConnectionWarningShown = false;
109
+ logger.log("info", "Redis connection restored");
110
+ }
111
+ });
112
+ }
113
+ return global.__redis__;
114
+ };
115
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
116
+ var getRedisStatus = (redis) => String(redis.status ?? "ready");
117
+ var waitForRedisReady = async (redis) => {
118
+ if (getRedisStatus(redis) === "ready") {
119
+ return true;
120
+ }
121
+ const readyTimeoutMs = toPositiveInt(
122
+ process.env.REDIS_READY_TIMEOUT_MS,
123
+ toPositiveInt(process.env.REDIS_CONNECT_TIMEOUT_MS, 3e3)
124
+ );
125
+ const startedAt = Date.now();
126
+ while (Date.now() - startedAt < readyTimeoutMs) {
127
+ if (getRedisStatus(redis) === "ready") {
128
+ return true;
129
+ }
130
+ if (getRedisStatus(redis) === "end") {
131
+ return false;
132
+ }
133
+ await sleep(50);
134
+ }
135
+ return getRedisStatus(redis) === "ready";
136
+ };
137
+ var getReadyRedis = async () => {
138
+ if (redisUnavailable) return null;
139
+ const redis = getRedis();
140
+ const ready = await waitForRedisReady(redis);
141
+ if (ready) {
142
+ return redis;
143
+ }
144
+ markRedisUnavailable(
145
+ new Error(`Redis is not ready (status=${getRedisStatus(redis)})`)
146
+ );
147
+ return null;
148
+ };
149
+ var toResultString = (result) => {
150
+ if (result == null) return null;
151
+ if (typeof result === "string") return result;
152
+ if (Buffer.isBuffer(result)) return result.toString("utf8");
153
+ return String(result);
154
+ };
155
+ var DEFAULT_OPTIONS = {
156
+ expire: TTL_1D
157
+ };
158
+ var parseJsonOrDeleteKey = async (redis, key, raw, fallback) => {
159
+ try {
160
+ return JSON.parse(raw);
161
+ } catch (e) {
162
+ logger.log("error", "failed JSON.parse(%s): %s", key, String(e));
163
+ await redis.del(key);
164
+ return fallback;
165
+ }
166
+ };
167
+ var getKeys = async (prefix) => {
168
+ if (redisUnavailable) return [];
169
+ const redis = await getReadyRedis();
170
+ if (!redis) return [];
171
+ const keys = [];
172
+ try {
173
+ let cursor = "0";
174
+ do {
175
+ const [nextCursor, batch] = await redis.scan(
176
+ cursor,
177
+ "MATCH",
178
+ `${prefix}*`,
179
+ "COUNT",
180
+ "200"
181
+ );
182
+ cursor = nextCursor;
183
+ for (const key of batch) {
184
+ if (key.startsWith(prefix)) {
185
+ keys.push(key);
186
+ }
187
+ }
188
+ } while (cursor !== "0");
189
+ } catch (e) {
190
+ if (e instanceof Error && isRedisConnectivityError(e)) {
191
+ markRedisUnavailable(e);
192
+ return [];
193
+ }
194
+ logger.log("warn", "failed SCAN for %s: %s", prefix, String(e));
195
+ return [];
196
+ }
197
+ return keys;
198
+ };
199
+ var getData = async (key, fallback = []) => {
200
+ if (redisUnavailable) return fallback;
201
+ const redis = await getReadyRedis();
202
+ if (!redis) return fallback;
203
+ try {
204
+ const rawJson = await redis.call("JSON.GET", key);
205
+ const raw = toResultString(rawJson);
206
+ if (raw == null) return fallback;
207
+ return parseJsonOrDeleteKey(redis, key, raw, fallback);
208
+ } catch (e) {
209
+ if (e instanceof Error && isRedisConnectivityError(e)) {
210
+ markRedisUnavailable(e);
211
+ return fallback;
212
+ }
213
+ logger.log(
214
+ "error",
215
+ "failed JSON.GET %s: %s (fallback to GET)",
216
+ key,
217
+ String(e)
218
+ );
219
+ }
220
+ try {
221
+ const raw = await redis.get(key);
222
+ if (raw == null) return fallback;
223
+ return parseJsonOrDeleteKey(redis, key, raw, fallback);
224
+ } catch (e) {
225
+ if (e instanceof Error && isRedisConnectivityError(e)) {
226
+ markRedisUnavailable(e);
227
+ return fallback;
228
+ }
229
+ logger.log("error", "failed GET %s: %s", key, String(e));
230
+ return fallback;
231
+ }
232
+ };
233
+ var requireReadyRedis = async () => {
234
+ if (redisUnavailable) {
235
+ throw new Error("Redis is unavailable");
236
+ }
237
+ const redis = await getReadyRedis();
238
+ if (!redis) {
239
+ throw new Error("Redis is unavailable");
240
+ }
241
+ return redis;
242
+ };
243
+ var setDataStrict = async (key, data, options = {}) => {
244
+ const { expire } = { ...DEFAULT_OPTIONS, ...options };
245
+ const redis = await requireReadyRedis();
246
+ const value = toJson(data);
247
+ try {
248
+ await redis.call("JSON.SET", key, "$", value);
249
+ if (expire) {
250
+ await redis.expire(key, expire);
251
+ }
252
+ return;
253
+ } catch (error) {
254
+ if (error instanceof Error && isRedisConnectivityError(error)) {
255
+ markRedisUnavailable(error);
256
+ throw error;
257
+ }
258
+ logger.log(
259
+ "error",
260
+ "failed strict JSON.SET %s: %s (fallback to SET)",
261
+ key,
262
+ String(error)
263
+ );
264
+ }
265
+ try {
266
+ if (expire) {
267
+ await redis.set(key, value, "EX", expire);
268
+ } else {
269
+ await redis.set(key, value);
270
+ }
271
+ } catch (error) {
272
+ if (error instanceof Error && isRedisConnectivityError(error)) {
273
+ markRedisUnavailable(error);
274
+ }
275
+ throw error;
276
+ }
277
+ };
278
+ var redisKeys = {
279
+ users: () => "users:index:",
280
+ user: (userName) => `users:index:${userName}`,
281
+ tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
282
+ tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
283
+ runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
284
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
285
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
286
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
287
+ runtimeControls: (userName) => `users:${userName}:runtime:controls`,
288
+ bots: (userName) => `users:${userName}:bots`,
289
+ botsPrefix: () => "users:",
290
+ bot: (userName, botId) => `users:${userName}:bots:${botId}`,
291
+ backtestConfig: (userName, config) => `users:${userName}:backtests:configs:${config}`,
292
+ strategies: (userName) => `users:${userName}:strategies`,
293
+ strategyConfig: (userName, strategyName, configId = "config") => `users:${userName}:strategies:${strategyName}:${configId}`,
294
+ runtimeStrategyControlEvents: (userName) => `users:${userName}:runtime:strategy-control-events:`,
295
+ runtimeStrategyControlEvent: (userName, eventId) => `users:${userName}:runtime:strategy-control-events:${eventId}`,
296
+ strategyResults: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:results`,
297
+ strategyCharts: (userName, mode) => `users:${userName}:strategies:charts:${mode}`,
298
+ strategyChartCards: (userName, mode) => `users:${userName}:strategies:charts:${mode}:cards:`,
299
+ strategyChartCard: (userName, mode, cardId) => `users:${userName}:strategies:charts:${mode}:cards:${cardId}`,
300
+ tests: (userName, strategyName) => strategyName ? `users:${userName}:tests:${strategyName}` : `users:${userName}:tests:`,
301
+ testOrders: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:orders`,
302
+ testConfig: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:config`,
303
+ testStat: (userName, strategyName, testName) => `users:${userName}:tests:${strategyName}:${testName}:stat`,
304
+ testSummaries: (userName) => `users:${userName}:tests:index:summary`,
305
+ cacheChunk: (userName, chunkId) => `users:${userName}:cache:tests:chunks:${chunkId}`,
306
+ cacheOrders: (userName, orderLogId) => `users:${userName}:cache:tests:orders:${orderLogId}`,
307
+ cachePositions: (userName, orderLogId) => `users:${userName}:cache:tests:positions:${orderLogId}`,
308
+ tickerUniverse: (userName, connectorName, universe, accountId) => universe || accountId ? `users:${userName}:cache:tickers:${connectorName}:${universe ?? "crypto"}:${accountId ?? "default"}` : `users:${userName}:cache:tickers:${connectorName}`,
309
+ signal: (symbol, signalId) => `signals:${symbol}:${signalId}`,
310
+ signalsBySymbol: (symbol) => `signals:${symbol}:`,
311
+ storeSignal: (symbol, signalId) => `store:signals:${symbol}:${signalId}`,
312
+ runtimeSignals: (userName) => `users:${userName}:runtime:signals:`,
313
+ runtimeSignal: (userName, signalId) => `users:${userName}:runtime:signals:${signalId}`,
314
+ runtimeSignalBuckets: (userName) => `users:${userName}:runtime:signals:days:`,
315
+ runtimeSignalBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signals:days:${dayKey}:${strategyName}`,
316
+ runtimeSignalEvaluations: (userName) => `users:${userName}:runtime:signal-evaluations:`,
317
+ runtimeSignalEvaluation: (userName, evaluationId) => `users:${userName}:runtime:signal-evaluations:${evaluationId}`,
318
+ runtimeSignalEvaluationBuckets: (userName) => `users:${userName}:runtime:signal-evaluations:days:`,
319
+ runtimeSignalEvaluationBucket: (userName, dayKey, strategyName) => `users:${userName}:runtime:signal-evaluations:days:${dayKey}:${strategyName}`,
320
+ runtimeSignalEvaluationStatsBuckets: (userName) => `users:${userName}:runtime:signal-evaluation-stats:days:`,
321
+ runtimeSignalEvaluationStatsBucket: (userName, dayKey, strategyName, deploymentId) => `users:${userName}:runtime:signal-evaluation-stats:days:${dayKey}:${deploymentId ? `${deploymentId}:` : ""}${strategyName}`,
322
+ runtimeLineageScopeBucket: (userName, dayKey) => `users:${userName}:runtime:lineage-scopes:days:${dayKey}`,
323
+ runtimeTrades: (userName) => `users:${userName}:runtime:trade-records:`,
324
+ runtimeTrade: (userName, orderId) => `users:${userName}:runtime:trade-records:${orderId}`,
325
+ runtimeTradeBuckets: (userName) => `users:${userName}:runtime:trade-records:days:`,
326
+ runtimeTradeBucket: (userName, dayKey) => `users:${userName}:runtime:trade-records:days:${dayKey}`,
327
+ runtimeClosedTradeBuckets: (userName) => `users:${userName}:runtime:closed-trade-records:days:`,
328
+ runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
329
+ runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
330
+ runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
331
+ aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
332
+ analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
333
+ screenshotSessionToken: (token) => `auth:screenshot:${token}`,
334
+ backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
335
+ backtestJobs: (userName) => `users:${userName}:backtests:jobs:`,
336
+ backtestJob: (userName, jobId) => `users:${userName}:backtests:jobs:${jobId}`,
337
+ backtestRuns: (userName) => `users:${userName}:backtests:runs:`,
338
+ backtestRun: (userName, runId) => `users:${userName}:backtests:runs:${runId}`,
339
+ backtestRunResults: (userName, runId) => `users:${userName}:backtests:runs:${runId}:results`,
340
+ backtestLatestRun: (userName, config) => `users:${userName}:backtests:latest:${config}`,
341
+ researchRuns: (userName) => `users:${userName}:research:runs:`,
342
+ researchRun: (userName, runId) => `users:${userName}:research:runs:${runId}`,
343
+ researchLatestRun: (userName, strategyName) => `users:${userName}:research:latest:${strategyName}`,
344
+ mlSignalsByStrategy: (strategyName) => `ml:${strategyName}:signals:`,
345
+ mlSignals: () => "ml:",
346
+ mlSignal: (strategyName, signalId) => `ml:${strategyName}:signals:${signalId}`,
347
+ mlResultsByStrategy: (strategyName) => `ml:${strategyName}:results:`,
348
+ mlResults: () => "ml:",
349
+ mlResult: (strategyName, signalId) => `ml:${strategyName}:results:${signalId}`
350
+ };
351
+
352
+ // src/runtimeDeploymentEvents.ts
353
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
354
+ var normalizeDeploymentId = (value) => {
355
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
356
+ if (!normalized) throw new Error("Deployment id is required");
357
+ return normalized;
358
+ };
359
+ var isRuntimeDeploymentCompositionEvent = (value) => {
360
+ if (!isRecord(value) || Object.keys(value).some(
361
+ (key) => ![
362
+ "schema",
363
+ "eventId",
364
+ "deploymentId",
365
+ "deploymentCompositionId",
366
+ "observedAt",
367
+ "strategies"
368
+ ].includes(key)
369
+ ) || value.schema !== import_types.RUNTIME_DEPLOYMENT_COMPOSITION_EVENT_SCHEMA || typeof value.eventId !== "string" || !value.eventId.trim() || typeof value.deploymentId !== "string" || !value.deploymentId.trim() || typeof value.deploymentCompositionId !== "string" || !/^dc1:[a-f0-9]{16}$/.test(value.deploymentCompositionId) || typeof value.observedAt !== "number" || !Number.isFinite(value.observedAt) || value.observedAt < 0 || !Array.isArray(value.strategies)) {
370
+ return false;
371
+ }
372
+ const strategyNames = /* @__PURE__ */ new Set();
373
+ for (const strategy of value.strategies) {
374
+ if (!isRecord(strategy) || Object.keys(strategy).some(
375
+ (key) => !["strategyName", "strategyRevision"].includes(key)
376
+ ) || typeof strategy.strategyName !== "string" || !strategy.strategyName.trim() || strategyNames.has(strategy.strategyName) || typeof strategy.strategyRevision !== "string" || !/^sr1:[a-f0-9]{16}$/.test(strategy.strategyRevision)) {
377
+ return false;
378
+ }
379
+ strategyNames.add(strategy.strategyName);
380
+ }
381
+ return true;
382
+ };
383
+ var observeRuntimeDeploymentComposition = async ({
384
+ userName,
385
+ deployment,
386
+ observedAt = Date.now()
387
+ }) => {
388
+ if (!Number.isFinite(observedAt) || observedAt < 0) {
389
+ throw new Error("Invalid deployment composition observation time");
390
+ }
391
+ const deploymentId = normalizeDeploymentId(deployment.id);
392
+ const stateKey = redisKeys.runtimeDeploymentCompositionState(
393
+ userName,
394
+ deploymentId
395
+ );
396
+ const current = await getData(stateKey, null);
397
+ if (isRuntimeDeploymentCompositionEvent(current) && current.deploymentId === deploymentId && current.deploymentCompositionId === deployment.deploymentCompositionId) {
398
+ return null;
399
+ }
400
+ const event = {
401
+ schema: import_types.RUNTIME_DEPLOYMENT_COMPOSITION_EVENT_SCHEMA,
402
+ eventId: `${observedAt}-${(0, import_node_crypto.randomUUID)()}`,
403
+ deploymentId,
404
+ deploymentCompositionId: deployment.deploymentCompositionId,
405
+ observedAt,
406
+ strategies: deployment.strategies.map(({ strategyName, strategyRevision }) => ({
407
+ strategyName,
408
+ strategyRevision
409
+ })).sort(
410
+ (left, right) => left.strategyName.localeCompare(right.strategyName)
411
+ )
412
+ };
413
+ if (!isRuntimeDeploymentCompositionEvent(event)) {
414
+ throw new Error("Invalid runtime deployment composition event");
415
+ }
416
+ await setDataStrict(
417
+ redisKeys.runtimeDeploymentCompositionEvent(
418
+ userName,
419
+ deploymentId,
420
+ event.eventId
421
+ ),
422
+ event,
423
+ { expire: 0 }
424
+ );
425
+ await setDataStrict(stateKey, event, { expire: 0 });
426
+ return event;
427
+ };
428
+ var loadRuntimeDeploymentCompositionEvents = async (userName, deploymentId) => {
429
+ const normalizedDeploymentId = normalizeDeploymentId(deploymentId);
430
+ const keys = await getKeys(
431
+ redisKeys.runtimeDeploymentCompositionEvents(
432
+ userName,
433
+ normalizedDeploymentId
434
+ )
435
+ );
436
+ const values = await Promise.all(keys.map((key) => getData(key, null)));
437
+ return values.filter(isRuntimeDeploymentCompositionEvent).filter((event) => event.deploymentId === normalizedDeploymentId).sort(
438
+ (left, right) => left.observedAt - right.observedAt || left.eventId.localeCompare(right.eventId)
439
+ );
440
+ };
441
+ // Annotate the CommonJS export names for ESM import in node:
442
+ 0 && (module.exports = {
443
+ isRuntimeDeploymentCompositionEvent,
444
+ loadRuntimeDeploymentCompositionEvents,
445
+ observeRuntimeDeploymentComposition
446
+ });
@@ -0,0 +1,105 @@
1
+ import {
2
+ getData,
3
+ getKeys,
4
+ redisKeys,
5
+ setDataStrict
6
+ } from "./chunk-GTZOMAFK.mjs";
7
+
8
+ // src/runtimeDeploymentEvents.ts
9
+ import { randomUUID } from "crypto";
10
+ import {
11
+ RUNTIME_DEPLOYMENT_COMPOSITION_EVENT_SCHEMA
12
+ } from "@tradejs/types";
13
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
14
+ var normalizeDeploymentId = (value) => {
15
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
16
+ if (!normalized) throw new Error("Deployment id is required");
17
+ return normalized;
18
+ };
19
+ var isRuntimeDeploymentCompositionEvent = (value) => {
20
+ if (!isRecord(value) || Object.keys(value).some(
21
+ (key) => ![
22
+ "schema",
23
+ "eventId",
24
+ "deploymentId",
25
+ "deploymentCompositionId",
26
+ "observedAt",
27
+ "strategies"
28
+ ].includes(key)
29
+ ) || value.schema !== RUNTIME_DEPLOYMENT_COMPOSITION_EVENT_SCHEMA || typeof value.eventId !== "string" || !value.eventId.trim() || typeof value.deploymentId !== "string" || !value.deploymentId.trim() || typeof value.deploymentCompositionId !== "string" || !/^dc1:[a-f0-9]{16}$/.test(value.deploymentCompositionId) || typeof value.observedAt !== "number" || !Number.isFinite(value.observedAt) || value.observedAt < 0 || !Array.isArray(value.strategies)) {
30
+ return false;
31
+ }
32
+ const strategyNames = /* @__PURE__ */ new Set();
33
+ for (const strategy of value.strategies) {
34
+ if (!isRecord(strategy) || Object.keys(strategy).some(
35
+ (key) => !["strategyName", "strategyRevision"].includes(key)
36
+ ) || typeof strategy.strategyName !== "string" || !strategy.strategyName.trim() || strategyNames.has(strategy.strategyName) || typeof strategy.strategyRevision !== "string" || !/^sr1:[a-f0-9]{16}$/.test(strategy.strategyRevision)) {
37
+ return false;
38
+ }
39
+ strategyNames.add(strategy.strategyName);
40
+ }
41
+ return true;
42
+ };
43
+ var observeRuntimeDeploymentComposition = async ({
44
+ userName,
45
+ deployment,
46
+ observedAt = Date.now()
47
+ }) => {
48
+ if (!Number.isFinite(observedAt) || observedAt < 0) {
49
+ throw new Error("Invalid deployment composition observation time");
50
+ }
51
+ const deploymentId = normalizeDeploymentId(deployment.id);
52
+ const stateKey = redisKeys.runtimeDeploymentCompositionState(
53
+ userName,
54
+ deploymentId
55
+ );
56
+ const current = await getData(stateKey, null);
57
+ if (isRuntimeDeploymentCompositionEvent(current) && current.deploymentId === deploymentId && current.deploymentCompositionId === deployment.deploymentCompositionId) {
58
+ return null;
59
+ }
60
+ const event = {
61
+ schema: RUNTIME_DEPLOYMENT_COMPOSITION_EVENT_SCHEMA,
62
+ eventId: `${observedAt}-${randomUUID()}`,
63
+ deploymentId,
64
+ deploymentCompositionId: deployment.deploymentCompositionId,
65
+ observedAt,
66
+ strategies: deployment.strategies.map(({ strategyName, strategyRevision }) => ({
67
+ strategyName,
68
+ strategyRevision
69
+ })).sort(
70
+ (left, right) => left.strategyName.localeCompare(right.strategyName)
71
+ )
72
+ };
73
+ if (!isRuntimeDeploymentCompositionEvent(event)) {
74
+ throw new Error("Invalid runtime deployment composition event");
75
+ }
76
+ await setDataStrict(
77
+ redisKeys.runtimeDeploymentCompositionEvent(
78
+ userName,
79
+ deploymentId,
80
+ event.eventId
81
+ ),
82
+ event,
83
+ { expire: 0 }
84
+ );
85
+ await setDataStrict(stateKey, event, { expire: 0 });
86
+ return event;
87
+ };
88
+ var loadRuntimeDeploymentCompositionEvents = async (userName, deploymentId) => {
89
+ const normalizedDeploymentId = normalizeDeploymentId(deploymentId);
90
+ const keys = await getKeys(
91
+ redisKeys.runtimeDeploymentCompositionEvents(
92
+ userName,
93
+ normalizedDeploymentId
94
+ )
95
+ );
96
+ const values = await Promise.all(keys.map((key) => getData(key, null)));
97
+ return values.filter(isRuntimeDeploymentCompositionEvent).filter((event) => event.deploymentId === normalizedDeploymentId).sort(
98
+ (left, right) => left.observedAt - right.observedAt || left.eventId.localeCompare(right.eventId)
99
+ );
100
+ };
101
+ export {
102
+ isRuntimeDeploymentCompositionEvent,
103
+ loadRuntimeDeploymentCompositionEvents,
104
+ observeRuntimeDeploymentComposition
105
+ };
@@ -238,6 +238,9 @@ var redisKeys = {
238
238
  tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
239
239
  tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
240
240
  runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
241
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
242
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
243
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
241
244
  runtimeControls: (userName) => `users:${userName}:runtime:controls`,
242
245
  bots: (userName) => `users:${userName}:bots`,
243
246
  botsPrefix: () => "users:",
@@ -2,7 +2,7 @@ import {
2
2
  getData,
3
3
  redisKeys,
4
4
  setData
5
- } from "./chunk-46XQGVDB.mjs";
5
+ } from "./chunk-GTZOMAFK.mjs";
6
6
 
7
7
  // src/runtimeHeartbeats.ts
8
8
  var normalizeDeploymentId = (value) => {
@@ -276,6 +276,9 @@ var redisKeys = {
276
276
  tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
277
277
  tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
278
278
  runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
279
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
280
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
281
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
279
282
  runtimeControls: (userName) => `users:${userName}:runtime:controls`,
280
283
  bots: (userName) => `users:${userName}:bots`,
281
284
  botsPrefix: () => "users:",
@@ -3,7 +3,7 @@ import {
3
3
  getKeys,
4
4
  redisKeys,
5
5
  setData
6
- } from "./chunk-46XQGVDB.mjs";
6
+ } from "./chunk-GTZOMAFK.mjs";
7
7
 
8
8
  // src/runtimeStrategyConfigs.ts
9
9
  var RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
@@ -306,6 +306,9 @@ var redisKeys = {
306
306
  tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
307
307
  tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
308
308
  runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
309
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
310
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
311
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
309
312
  runtimeControls: (userName) => `users:${userName}:runtime:controls`,
310
313
  bots: (userName) => `users:${userName}:bots`,
311
314
  botsPrefix: () => "users:",
@@ -4,7 +4,7 @@ import {
4
4
  getKeys,
5
5
  redisKeys,
6
6
  setData
7
- } from "./chunk-46XQGVDB.mjs";
7
+ } from "./chunk-GTZOMAFK.mjs";
8
8
 
9
9
  // src/tradingAccounts.ts
10
10
  var normalizeId = (value, label) => {
@@ -239,6 +239,9 @@ var redisKeys = {
239
239
  tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
240
240
  tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
241
241
  runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
242
+ runtimeDeploymentCompositionState: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-state`,
243
+ runtimeDeploymentCompositionEvents: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:`,
244
+ runtimeDeploymentCompositionEvent: (userName, deploymentId, eventId) => `users:${userName}:runtime:deployments:${deploymentId}:composition-events:${eventId}`,
242
245
  runtimeControls: (userName) => `users:${userName}:runtime:controls`,
243
246
  bots: (userName) => `users:${userName}:bots`,
244
247
  botsPrefix: () => "users:",
@@ -2,7 +2,7 @@ import {
2
2
  getData,
3
3
  redisKeys,
4
4
  setData
5
- } from "./chunk-46XQGVDB.mjs";
5
+ } from "./chunk-GTZOMAFK.mjs";
6
6
 
7
7
  // src/userSettings.ts
8
8
  var readString = (value) => typeof value === "string" ? value.trim() : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/infra",
3
- "version": "3.1.28-beta.252",
3
+ "version": "3.1.28-beta.254",
4
4
  "description": "MIT-licensed server infrastructure adapters for TradeJS: Redis, Timescale, ML, logging, and IO.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -81,6 +81,11 @@
81
81
  "import": "./dist/runtimeControls.mjs",
82
82
  "require": "./dist/runtimeControls.js"
83
83
  },
84
+ "./runtimeDeploymentEvents": {
85
+ "types": "./dist/runtimeDeploymentEvents.d.ts",
86
+ "import": "./dist/runtimeDeploymentEvents.mjs",
87
+ "require": "./dist/runtimeDeploymentEvents.js"
88
+ },
84
89
  "./strategyReleaseEvidence": {
85
90
  "types": "./dist/strategyReleaseEvidence.d.ts",
86
91
  "import": "./dist/strategyReleaseEvidence.mjs",
@@ -130,7 +135,7 @@
130
135
  "dependencies": {
131
136
  "@grpc/grpc-js": "^1.14.4",
132
137
  "@grpc/proto-loader": "^0.8.1",
133
- "@tradejs/types": "^3.1.28-beta.252",
138
+ "@tradejs/types": "^3.1.28-beta.254",
134
139
  "chalk": "4.1.2",
135
140
  "date-fns": "^3.6.0",
136
141
  "ioredis": "5.11.1",