@tradejs/infra 3.1.8-beta.207 → 3.1.8-beta.210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JLVCBKE3.mjs → chunk-NN4CAPDW.mjs} +97 -5
- package/dist/redis.d.mts +10 -6
- package/dist/redis.d.ts +10 -6
- package/dist/redis.js +100 -5
- package/dist/redis.mjs +7 -1
- package/dist/runtimeControls.d.mts +28 -0
- package/dist/runtimeControls.d.ts +28 -0
- package/dist/{runtimeStrategyReleases.js → runtimeControls.js} +141 -266
- package/dist/runtimeControls.mjs +133 -0
- package/dist/runtimeHeartbeats.d.mts +6 -0
- package/dist/runtimeHeartbeats.d.ts +6 -0
- package/dist/{runtimeDeployments.js → runtimeHeartbeats.js} +12 -181
- package/dist/runtimeHeartbeats.mjs +37 -0
- package/dist/runtimeStrategyConfigs.js +1 -5
- package/dist/runtimeStrategyConfigs.mjs +1 -1
- package/dist/strategyReleaseEvidence.js +1 -1
- package/dist/strategyReleaseEvidence.mjs +1 -1
- package/dist/tradingAccounts.js +1 -5
- package/dist/tradingAccounts.mjs +1 -1
- package/dist/userSettings.js +1 -5
- package/dist/userSettings.mjs +1 -1
- package/package.json +10 -10
- package/dist/runtimeDeployments.d.mts +0 -11
- package/dist/runtimeDeployments.d.ts +0 -11
- package/dist/runtimeDeployments.mjs +0 -134
- package/dist/runtimeStrategyReleases.d.mts +0 -26
- package/dist/runtimeStrategyReleases.d.ts +0 -26
- package/dist/runtimeStrategyReleases.mjs +0 -196
|
@@ -27,17 +27,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
));
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
|
|
30
|
-
// src/
|
|
31
|
-
var
|
|
32
|
-
__export(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
publishRuntimeStrategyRelease: () => publishRuntimeStrategyRelease,
|
|
30
|
+
// src/runtimeControls.ts
|
|
31
|
+
var runtimeControls_exports = {};
|
|
32
|
+
__export(runtimeControls_exports, {
|
|
33
|
+
emptyRuntimeControls: () => emptyRuntimeControls,
|
|
34
|
+
getRuntimeControls: () => getRuntimeControls,
|
|
35
|
+
pauseRuntimeStrategy: () => pauseRuntimeStrategy,
|
|
37
36
|
recordRuntimeStrategyControlEvent: () => recordRuntimeStrategyControlEvent,
|
|
38
|
-
|
|
37
|
+
resumeRuntimeStrategy: () => resumeRuntimeStrategy,
|
|
38
|
+
verifyRuntimeControls: () => verifyRuntimeControls
|
|
39
39
|
});
|
|
40
|
-
module.exports = __toCommonJS(
|
|
40
|
+
module.exports = __toCommonJS(runtimeControls_exports);
|
|
41
41
|
var import_node_crypto = require("crypto");
|
|
42
42
|
var import_types = require("@tradejs/types");
|
|
43
43
|
|
|
@@ -158,152 +158,92 @@ var toResultString = (result) => {
|
|
|
158
158
|
var DEFAULT_OPTIONS = {
|
|
159
159
|
expire: TTL_1D
|
|
160
160
|
};
|
|
161
|
-
var
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} catch (e) {
|
|
165
|
-
logger.log("error", "failed JSON.parse(%s): %s", key, String(e));
|
|
166
|
-
await redis.del(key);
|
|
167
|
-
return fallback;
|
|
161
|
+
var requireReadyRedis = async () => {
|
|
162
|
+
if (redisUnavailable) {
|
|
163
|
+
throw new Error("Redis is unavailable");
|
|
168
164
|
}
|
|
169
|
-
};
|
|
170
|
-
var getKeys = async (prefix) => {
|
|
171
|
-
if (redisUnavailable) return [];
|
|
172
165
|
const redis = await getReadyRedis();
|
|
173
|
-
if (!redis)
|
|
174
|
-
|
|
166
|
+
if (!redis) {
|
|
167
|
+
throw new Error("Redis is unavailable");
|
|
168
|
+
}
|
|
169
|
+
return redis;
|
|
170
|
+
};
|
|
171
|
+
var parseJsonStrict = (key, raw) => {
|
|
175
172
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
cursor,
|
|
180
|
-
"MATCH",
|
|
181
|
-
`${prefix}*`,
|
|
182
|
-
"COUNT",
|
|
183
|
-
"200"
|
|
184
|
-
);
|
|
185
|
-
cursor = nextCursor;
|
|
186
|
-
for (const key of batch) {
|
|
187
|
-
if (key.startsWith(prefix)) {
|
|
188
|
-
keys.push(key);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} while (cursor !== "0");
|
|
192
|
-
} catch (e) {
|
|
193
|
-
if (e instanceof Error && isRedisConnectivityError(e)) {
|
|
194
|
-
markRedisUnavailable(e);
|
|
195
|
-
return [];
|
|
196
|
-
}
|
|
197
|
-
logger.log("warn", "failed SCAN for %s: %s", prefix, String(e));
|
|
198
|
-
return [];
|
|
173
|
+
return JSON.parse(raw);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new Error(`Invalid JSON stored at ${key}`, { cause: error });
|
|
199
176
|
}
|
|
200
|
-
return keys;
|
|
201
177
|
};
|
|
202
|
-
var
|
|
203
|
-
|
|
204
|
-
const redis = await getReadyRedis();
|
|
205
|
-
if (!redis) return fallback;
|
|
178
|
+
var getDataStrict = async (key) => {
|
|
179
|
+
const redis = await requireReadyRedis();
|
|
206
180
|
try {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
181
|
+
const raw = toResultString(await redis.call("JSON.GET", key));
|
|
182
|
+
return raw == null ? null : parseJsonStrict(key, raw);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (error instanceof Error && isRedisConnectivityError(error)) {
|
|
185
|
+
markRedisUnavailable(error);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
if (error instanceof Error && error.message.startsWith("Invalid JSON")) {
|
|
189
|
+
throw error;
|
|
215
190
|
}
|
|
216
191
|
logger.log(
|
|
217
192
|
"error",
|
|
218
|
-
"failed JSON.GET %s: %s (fallback to GET)",
|
|
193
|
+
"failed strict JSON.GET %s: %s (fallback to GET)",
|
|
219
194
|
key,
|
|
220
|
-
String(
|
|
195
|
+
String(error)
|
|
221
196
|
);
|
|
222
197
|
}
|
|
223
198
|
try {
|
|
224
199
|
const raw = await redis.get(key);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
200
|
+
return raw == null ? null : parseJsonStrict(key, raw);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (error instanceof Error && isRedisConnectivityError(error)) {
|
|
203
|
+
markRedisUnavailable(error);
|
|
204
|
+
}
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
var delKeyStrict = async (key) => {
|
|
209
|
+
const redis = await requireReadyRedis();
|
|
210
|
+
try {
|
|
211
|
+
return await redis.del(key) === 1;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error instanceof Error && isRedisConnectivityError(error)) {
|
|
214
|
+
markRedisUnavailable(error);
|
|
231
215
|
}
|
|
232
|
-
|
|
233
|
-
return fallback;
|
|
216
|
+
throw error;
|
|
234
217
|
}
|
|
235
218
|
};
|
|
236
|
-
var
|
|
237
|
-
if (redisUnavailable) return;
|
|
219
|
+
var setDataStrict = async (key, data, options = {}) => {
|
|
238
220
|
const { expire } = { ...DEFAULT_OPTIONS, ...options };
|
|
239
|
-
const redis = await
|
|
240
|
-
if (!redis) return;
|
|
221
|
+
const redis = await requireReadyRedis();
|
|
241
222
|
const value = toJson(data);
|
|
242
223
|
try {
|
|
243
224
|
await redis.call("JSON.SET", key, "$", value);
|
|
244
225
|
if (expire) {
|
|
245
226
|
await redis.expire(key, expire);
|
|
246
227
|
}
|
|
247
|
-
|
|
248
|
-
if (e instanceof Error && isRedisConnectivityError(e)) {
|
|
249
|
-
markRedisUnavailable(e);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
logger.log(
|
|
253
|
-
"error",
|
|
254
|
-
"failed JSON.SET %s: %s (fallback to SET)",
|
|
255
|
-
key,
|
|
256
|
-
String(e)
|
|
257
|
-
);
|
|
258
|
-
try {
|
|
259
|
-
if (expire) {
|
|
260
|
-
await redis.set(key, value, "EX", expire);
|
|
261
|
-
} else {
|
|
262
|
-
await redis.set(key, value);
|
|
263
|
-
}
|
|
264
|
-
} catch (e2) {
|
|
265
|
-
if (e2 instanceof Error && isRedisConnectivityError(e2)) {
|
|
266
|
-
markRedisUnavailable(e2);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
logger.log("error", "failed SET %s: %s", key, String(e2));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
var setDataIfAbsent = async (key, data) => {
|
|
274
|
-
if (redisUnavailable) return false;
|
|
275
|
-
const redis = await getReadyRedis();
|
|
276
|
-
if (!redis) return false;
|
|
277
|
-
try {
|
|
278
|
-
return await redis.call("JSON.SET", key, "$", toJson(data), "NX") === "OK";
|
|
228
|
+
return;
|
|
279
229
|
} catch (error) {
|
|
280
230
|
if (error instanceof Error && isRedisConnectivityError(error)) {
|
|
281
231
|
markRedisUnavailable(error);
|
|
282
|
-
|
|
232
|
+
throw error;
|
|
283
233
|
}
|
|
284
234
|
logger.log(
|
|
285
|
-
"
|
|
286
|
-
"failed JSON.SET
|
|
235
|
+
"error",
|
|
236
|
+
"failed strict JSON.SET %s: %s (fallback to SET)",
|
|
287
237
|
key,
|
|
288
238
|
String(error)
|
|
289
239
|
);
|
|
290
|
-
try {
|
|
291
|
-
return await redis.set(key, toJson(data), "NX") === "OK";
|
|
292
|
-
} catch (fallbackError) {
|
|
293
|
-
if (fallbackError instanceof Error && isRedisConnectivityError(fallbackError)) {
|
|
294
|
-
markRedisUnavailable(fallbackError);
|
|
295
|
-
}
|
|
296
|
-
logger.log("error", "failed SET NX %s: %s", key, String(fallbackError));
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
240
|
}
|
|
300
|
-
};
|
|
301
|
-
var incrementKey = async (key) => {
|
|
302
|
-
if (redisUnavailable) throw new Error("Redis is unavailable");
|
|
303
|
-
const redis = await getReadyRedis();
|
|
304
|
-
if (!redis) throw new Error("Redis is unavailable");
|
|
305
241
|
try {
|
|
306
|
-
|
|
242
|
+
if (expire) {
|
|
243
|
+
await redis.set(key, value, "EX", expire);
|
|
244
|
+
} else {
|
|
245
|
+
await redis.set(key, value);
|
|
246
|
+
}
|
|
307
247
|
} catch (error) {
|
|
308
248
|
if (error instanceof Error && isRedisConnectivityError(error)) {
|
|
309
249
|
markRedisUnavailable(error);
|
|
@@ -316,18 +256,14 @@ var redisKeys = {
|
|
|
316
256
|
user: (userName) => `users:index:${userName}`,
|
|
317
257
|
tradingAccounts: (userName) => `users:${userName}:trading-accounts:`,
|
|
318
258
|
tradingAccount: (userName, accountId) => `users:${userName}:trading-accounts:${accountId}`,
|
|
319
|
-
runtimeDeployments: (userName) => `users:${userName}:runtime:deployments:`,
|
|
320
|
-
runtimeDeployment: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}`,
|
|
321
259
|
runtimeDeploymentHeartbeat: (userName, deploymentId) => `users:${userName}:runtime:deployments:${deploymentId}:heartbeat`,
|
|
260
|
+
runtimeControls: (userName) => `users:${userName}:runtime:controls`,
|
|
322
261
|
bots: (userName) => `users:${userName}:bots`,
|
|
323
262
|
botsPrefix: () => "users:",
|
|
324
263
|
bot: (userName, botId) => `users:${userName}:bots:${botId}`,
|
|
325
264
|
backtestConfig: (userName, config) => `users:${userName}:backtests:configs:${config}`,
|
|
326
265
|
strategies: (userName) => `users:${userName}:strategies`,
|
|
327
266
|
strategyConfig: (userName, strategyName, configId = "config") => `users:${userName}:strategies:${strategyName}:${configId}`,
|
|
328
|
-
runtimeStrategyReleases: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:releases:`,
|
|
329
|
-
runtimeStrategyRelease: (userName, strategyName, releaseVersion) => `users:${userName}:strategies:${strategyName}:releases:${releaseVersion}`,
|
|
330
|
-
runtimeStrategyReleaseSequence: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:release-seq`,
|
|
331
267
|
runtimeStrategyControlEvents: (userName) => `users:${userName}:runtime:strategy-control-events:`,
|
|
332
268
|
runtimeStrategyControlEvent: (userName, eventId) => `users:${userName}:runtime:strategy-control-events:${eventId}`,
|
|
333
269
|
strategyResults: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:results`,
|
|
@@ -386,157 +322,96 @@ var redisKeys = {
|
|
|
386
322
|
mlResult: (strategyName, signalId) => `ml:${strategyName}:results:${signalId}`
|
|
387
323
|
};
|
|
388
324
|
|
|
389
|
-
// src/
|
|
390
|
-
var
|
|
391
|
-
var
|
|
392
|
-
"
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
])
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
var canonicalJson = (value) => {
|
|
404
|
-
const normalize = (current) => {
|
|
405
|
-
if (Array.isArray(current)) return current.map(normalize);
|
|
406
|
-
const record = asRecord(current);
|
|
407
|
-
if (!record) return current;
|
|
408
|
-
return Object.fromEntries(
|
|
409
|
-
Object.entries(record).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalize(entry)])
|
|
410
|
-
);
|
|
411
|
-
};
|
|
412
|
-
return JSON.stringify(normalize(value));
|
|
413
|
-
};
|
|
414
|
-
var releaseContentSha256 = (release) => (0, import_node_crypto.createHash)("sha256").update(canonicalJson(release)).digest("hex");
|
|
415
|
-
var assertStrategyName = (strategyName) => {
|
|
416
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(strategyName)) {
|
|
417
|
-
throw new Error(`Invalid strategy name: ${strategyName}`);
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
var assertPackageIdentity = (value, label) => {
|
|
421
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
422
|
-
throw new Error(`${label} is required for a runtime strategy release`);
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
var assertRuntimeStrategyReleaseConfig = (config) => {
|
|
426
|
-
const record = asRecord(config);
|
|
427
|
-
if (!record) throw new Error("Strategy release config must be an object");
|
|
428
|
-
for (const key of FORBIDDEN_RELEASE_CONFIG_KEYS) {
|
|
429
|
-
if (record[key] !== void 0) {
|
|
430
|
-
throw new Error(`${key} is a deployment binding, not release config`);
|
|
431
|
-
}
|
|
325
|
+
// src/runtimeControls.ts
|
|
326
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
327
|
+
var verifyPauseOverride = (value) => isRecord(value) && Object.keys(value).every(
|
|
328
|
+
(key) => ["entriesPaused", "updatedAt", "updatedBy"].includes(key)
|
|
329
|
+
) && value.entriesPaused === true && typeof value.updatedAt === "string" && Number.isFinite(Date.parse(value.updatedAt)) && typeof value.updatedBy === "string" && Boolean(value.updatedBy.trim());
|
|
330
|
+
var emptyRuntimeControls = () => ({
|
|
331
|
+
schema: import_types.RUNTIME_CONTROLS_SCHEMA,
|
|
332
|
+
deployments: {}
|
|
333
|
+
});
|
|
334
|
+
var verifyRuntimeControls = (value) => {
|
|
335
|
+
if (!isRecord(value) || Object.keys(value).some(
|
|
336
|
+
(key) => !["schema", "deployments"].includes(key)
|
|
337
|
+
) || value.schema !== import_types.RUNTIME_CONTROLS_SCHEMA || !isRecord(value.deployments)) {
|
|
338
|
+
throw new Error("Invalid runtime controls");
|
|
432
339
|
}
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
);
|
|
438
|
-
return;
|
|
340
|
+
for (const [deploymentId, strategyOverrides] of Object.entries(
|
|
341
|
+
value.deployments
|
|
342
|
+
)) {
|
|
343
|
+
if (!deploymentId.trim() || !isRecord(strategyOverrides)) {
|
|
344
|
+
throw new Error("Invalid runtime controls");
|
|
439
345
|
}
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const keyPath = parentPath ? `${parentPath}.${key}` : key;
|
|
444
|
-
if (SECRET_CONFIG_KEY_RE.test(key)) {
|
|
445
|
-
throw new Error(`${keyPath} is secret material, not release config`);
|
|
346
|
+
for (const [strategyName, override] of Object.entries(strategyOverrides)) {
|
|
347
|
+
if (!strategyName.trim() || !verifyPauseOverride(override)) {
|
|
348
|
+
throw new Error("Invalid runtime controls");
|
|
446
349
|
}
|
|
447
|
-
inspect(entry, keyPath);
|
|
448
350
|
}
|
|
449
|
-
};
|
|
450
|
-
inspect(record);
|
|
451
|
-
if (typeof record.INTERVAL !== "string" || !record.INTERVAL.trim()) {
|
|
452
|
-
throw new Error("Published strategy config must define INTERVAL");
|
|
453
351
|
}
|
|
454
|
-
if (record.UNIVERSE !== "crypto" && record.UNIVERSE !== "tradfi") {
|
|
455
|
-
throw new Error("Published strategy config must define a valid UNIVERSE");
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
var isRelease = (value) => {
|
|
459
|
-
const release = asRecord(value);
|
|
460
|
-
return Boolean(
|
|
461
|
-
release?.schema === import_types.RUNTIME_STRATEGY_RELEASE_SCHEMA && typeof release.strategyName === "string" && Number.isSafeInteger(release.releaseVersion) && Number(release.releaseVersion) > 0 && asRecord(release.config) && typeof release.strategyPackage === "string" && Boolean(release.strategyPackage.trim()) && typeof release.strategyPackageVersion === "string" && Boolean(release.strategyPackageVersion.trim()) && typeof release.runtimePackageVersion === "string" && Boolean(release.runtimePackageVersion.trim()) && typeof release.createdAt === "number" && typeof release.createdBy === "string" && typeof release.contentSha256 === "string" && SHA256_RE.test(release.contentSha256)
|
|
462
|
-
);
|
|
463
|
-
};
|
|
464
|
-
var verifyRuntimeStrategyRelease = (value) => {
|
|
465
|
-
if (!isRelease(value)) throw new Error("Invalid runtime strategy release");
|
|
466
|
-
const { contentSha256, ...content } = value;
|
|
467
|
-
if (releaseContentSha256(content) !== contentSha256) {
|
|
468
|
-
throw new Error(
|
|
469
|
-
`${value.strategyName} v${value.releaseVersion} content checksum mismatch`
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
assertRuntimeStrategyReleaseConfig(value.config);
|
|
473
352
|
return value;
|
|
474
353
|
};
|
|
475
|
-
var
|
|
476
|
-
const value = await
|
|
477
|
-
|
|
478
|
-
null
|
|
479
|
-
);
|
|
480
|
-
return value == null ? null : verifyRuntimeStrategyRelease(value);
|
|
481
|
-
};
|
|
482
|
-
var listRuntimeStrategyReleases = async (userName, strategyName) => {
|
|
483
|
-
const keys = await getKeys(
|
|
484
|
-
redisKeys.runtimeStrategyReleases(userName, strategyName)
|
|
485
|
-
);
|
|
486
|
-
const releases = await Promise.all(
|
|
487
|
-
keys.map(async (key) => {
|
|
488
|
-
const value = await getData(key, null);
|
|
489
|
-
return value == null ? null : verifyRuntimeStrategyRelease(value);
|
|
490
|
-
})
|
|
491
|
-
);
|
|
492
|
-
return releases.filter((release) => release != null).sort((left, right) => right.releaseVersion - left.releaseVersion);
|
|
354
|
+
var getRuntimeControls = async (userName) => {
|
|
355
|
+
const value = await getDataStrict(redisKeys.runtimeControls(userName));
|
|
356
|
+
return value == null ? emptyRuntimeControls() : verifyRuntimeControls(value);
|
|
493
357
|
};
|
|
494
|
-
var
|
|
358
|
+
var pauseRuntimeStrategy = async ({
|
|
495
359
|
userName,
|
|
360
|
+
deploymentId,
|
|
496
361
|
strategyName,
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
strategyPackageVersion,
|
|
500
|
-
runtimePackageVersion,
|
|
501
|
-
createdBy
|
|
362
|
+
updatedBy,
|
|
363
|
+
updatedAt = (/* @__PURE__ */ new Date()).toISOString()
|
|
502
364
|
}) => {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
strategyPackageVersion,
|
|
518
|
-
runtimePackageVersion,
|
|
519
|
-
createdAt: Date.now(),
|
|
520
|
-
createdBy
|
|
521
|
-
};
|
|
522
|
-
const release = {
|
|
523
|
-
...content,
|
|
524
|
-
contentSha256: releaseContentSha256(content)
|
|
365
|
+
const controls = await getRuntimeControls(userName);
|
|
366
|
+
const next = {
|
|
367
|
+
...controls,
|
|
368
|
+
deployments: {
|
|
369
|
+
...controls.deployments,
|
|
370
|
+
[deploymentId]: {
|
|
371
|
+
...controls.deployments[deploymentId],
|
|
372
|
+
[strategyName]: {
|
|
373
|
+
entriesPaused: true,
|
|
374
|
+
updatedAt,
|
|
375
|
+
updatedBy: updatedBy.trim()
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
525
379
|
};
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
|
|
380
|
+
const verified = verifyRuntimeControls(next);
|
|
381
|
+
await setDataStrict(redisKeys.runtimeControls(userName), verified, {
|
|
382
|
+
expire: 0
|
|
383
|
+
});
|
|
384
|
+
return verified;
|
|
385
|
+
};
|
|
386
|
+
var resumeRuntimeStrategy = async ({
|
|
387
|
+
userName,
|
|
388
|
+
deploymentId,
|
|
389
|
+
strategyName
|
|
390
|
+
}) => {
|
|
391
|
+
const controls = await getRuntimeControls(userName);
|
|
392
|
+
const deployment = { ...controls.deployments[deploymentId] };
|
|
393
|
+
delete deployment[strategyName];
|
|
394
|
+
const deployments = { ...controls.deployments };
|
|
395
|
+
if (Object.keys(deployment).length) {
|
|
396
|
+
deployments[deploymentId] = deployment;
|
|
397
|
+
} else {
|
|
398
|
+
delete deployments[deploymentId];
|
|
399
|
+
}
|
|
400
|
+
const next = verifyRuntimeControls({ ...controls, deployments });
|
|
401
|
+
if (Object.keys(deployments).length) {
|
|
402
|
+
await setDataStrict(redisKeys.runtimeControls(userName), next, {
|
|
403
|
+
expire: 0
|
|
404
|
+
});
|
|
405
|
+
} else {
|
|
406
|
+
await delKeyStrict(redisKeys.runtimeControls(userName));
|
|
532
407
|
}
|
|
533
|
-
return
|
|
408
|
+
return next;
|
|
534
409
|
};
|
|
535
410
|
var recordRuntimeStrategyControlEvent = async ({
|
|
536
411
|
userName,
|
|
537
412
|
deploymentId,
|
|
538
413
|
strategyName,
|
|
539
|
-
|
|
414
|
+
version,
|
|
540
415
|
previousState,
|
|
541
416
|
nextState,
|
|
542
417
|
createdBy
|
|
@@ -547,14 +422,14 @@ var recordRuntimeStrategyControlEvent = async ({
|
|
|
547
422
|
eventId: `${createdAt}-${(0, import_node_crypto.randomUUID)()}`,
|
|
548
423
|
deploymentId,
|
|
549
424
|
strategyName,
|
|
550
|
-
|
|
425
|
+
version,
|
|
551
426
|
action,
|
|
552
427
|
previousState,
|
|
553
428
|
nextState,
|
|
554
429
|
createdAt,
|
|
555
430
|
createdBy
|
|
556
431
|
};
|
|
557
|
-
await
|
|
432
|
+
await setDataStrict(
|
|
558
433
|
redisKeys.runtimeStrategyControlEvent(userName, event.eventId),
|
|
559
434
|
event,
|
|
560
435
|
{ expire: 0 }
|
|
@@ -563,10 +438,10 @@ var recordRuntimeStrategyControlEvent = async ({
|
|
|
563
438
|
};
|
|
564
439
|
// Annotate the CommonJS export names for ESM import in node:
|
|
565
440
|
0 && (module.exports = {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
publishRuntimeStrategyRelease,
|
|
441
|
+
emptyRuntimeControls,
|
|
442
|
+
getRuntimeControls,
|
|
443
|
+
pauseRuntimeStrategy,
|
|
570
444
|
recordRuntimeStrategyControlEvent,
|
|
571
|
-
|
|
445
|
+
resumeRuntimeStrategy,
|
|
446
|
+
verifyRuntimeControls
|
|
572
447
|
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
delKeyStrict,
|
|
3
|
+
getDataStrict,
|
|
4
|
+
redisKeys,
|
|
5
|
+
setDataStrict
|
|
6
|
+
} from "./chunk-NN4CAPDW.mjs";
|
|
7
|
+
|
|
8
|
+
// src/runtimeControls.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
import {
|
|
11
|
+
RUNTIME_CONTROLS_SCHEMA
|
|
12
|
+
} from "@tradejs/types";
|
|
13
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
14
|
+
var verifyPauseOverride = (value) => isRecord(value) && Object.keys(value).every(
|
|
15
|
+
(key) => ["entriesPaused", "updatedAt", "updatedBy"].includes(key)
|
|
16
|
+
) && value.entriesPaused === true && typeof value.updatedAt === "string" && Number.isFinite(Date.parse(value.updatedAt)) && typeof value.updatedBy === "string" && Boolean(value.updatedBy.trim());
|
|
17
|
+
var emptyRuntimeControls = () => ({
|
|
18
|
+
schema: RUNTIME_CONTROLS_SCHEMA,
|
|
19
|
+
deployments: {}
|
|
20
|
+
});
|
|
21
|
+
var verifyRuntimeControls = (value) => {
|
|
22
|
+
if (!isRecord(value) || Object.keys(value).some(
|
|
23
|
+
(key) => !["schema", "deployments"].includes(key)
|
|
24
|
+
) || value.schema !== RUNTIME_CONTROLS_SCHEMA || !isRecord(value.deployments)) {
|
|
25
|
+
throw new Error("Invalid runtime controls");
|
|
26
|
+
}
|
|
27
|
+
for (const [deploymentId, strategyOverrides] of Object.entries(
|
|
28
|
+
value.deployments
|
|
29
|
+
)) {
|
|
30
|
+
if (!deploymentId.trim() || !isRecord(strategyOverrides)) {
|
|
31
|
+
throw new Error("Invalid runtime controls");
|
|
32
|
+
}
|
|
33
|
+
for (const [strategyName, override] of Object.entries(strategyOverrides)) {
|
|
34
|
+
if (!strategyName.trim() || !verifyPauseOverride(override)) {
|
|
35
|
+
throw new Error("Invalid runtime controls");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
};
|
|
41
|
+
var getRuntimeControls = async (userName) => {
|
|
42
|
+
const value = await getDataStrict(redisKeys.runtimeControls(userName));
|
|
43
|
+
return value == null ? emptyRuntimeControls() : verifyRuntimeControls(value);
|
|
44
|
+
};
|
|
45
|
+
var pauseRuntimeStrategy = async ({
|
|
46
|
+
userName,
|
|
47
|
+
deploymentId,
|
|
48
|
+
strategyName,
|
|
49
|
+
updatedBy,
|
|
50
|
+
updatedAt = (/* @__PURE__ */ new Date()).toISOString()
|
|
51
|
+
}) => {
|
|
52
|
+
const controls = await getRuntimeControls(userName);
|
|
53
|
+
const next = {
|
|
54
|
+
...controls,
|
|
55
|
+
deployments: {
|
|
56
|
+
...controls.deployments,
|
|
57
|
+
[deploymentId]: {
|
|
58
|
+
...controls.deployments[deploymentId],
|
|
59
|
+
[strategyName]: {
|
|
60
|
+
entriesPaused: true,
|
|
61
|
+
updatedAt,
|
|
62
|
+
updatedBy: updatedBy.trim()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const verified = verifyRuntimeControls(next);
|
|
68
|
+
await setDataStrict(redisKeys.runtimeControls(userName), verified, {
|
|
69
|
+
expire: 0
|
|
70
|
+
});
|
|
71
|
+
return verified;
|
|
72
|
+
};
|
|
73
|
+
var resumeRuntimeStrategy = async ({
|
|
74
|
+
userName,
|
|
75
|
+
deploymentId,
|
|
76
|
+
strategyName
|
|
77
|
+
}) => {
|
|
78
|
+
const controls = await getRuntimeControls(userName);
|
|
79
|
+
const deployment = { ...controls.deployments[deploymentId] };
|
|
80
|
+
delete deployment[strategyName];
|
|
81
|
+
const deployments = { ...controls.deployments };
|
|
82
|
+
if (Object.keys(deployment).length) {
|
|
83
|
+
deployments[deploymentId] = deployment;
|
|
84
|
+
} else {
|
|
85
|
+
delete deployments[deploymentId];
|
|
86
|
+
}
|
|
87
|
+
const next = verifyRuntimeControls({ ...controls, deployments });
|
|
88
|
+
if (Object.keys(deployments).length) {
|
|
89
|
+
await setDataStrict(redisKeys.runtimeControls(userName), next, {
|
|
90
|
+
expire: 0
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
await delKeyStrict(redisKeys.runtimeControls(userName));
|
|
94
|
+
}
|
|
95
|
+
return next;
|
|
96
|
+
};
|
|
97
|
+
var recordRuntimeStrategyControlEvent = async ({
|
|
98
|
+
userName,
|
|
99
|
+
deploymentId,
|
|
100
|
+
strategyName,
|
|
101
|
+
version,
|
|
102
|
+
previousState,
|
|
103
|
+
nextState,
|
|
104
|
+
createdBy
|
|
105
|
+
}) => {
|
|
106
|
+
const action = nextState === "entries_paused" ? "pause_entries" : "resume";
|
|
107
|
+
const createdAt = Date.now();
|
|
108
|
+
const event = {
|
|
109
|
+
eventId: `${createdAt}-${randomUUID()}`,
|
|
110
|
+
deploymentId,
|
|
111
|
+
strategyName,
|
|
112
|
+
version,
|
|
113
|
+
action,
|
|
114
|
+
previousState,
|
|
115
|
+
nextState,
|
|
116
|
+
createdAt,
|
|
117
|
+
createdBy
|
|
118
|
+
};
|
|
119
|
+
await setDataStrict(
|
|
120
|
+
redisKeys.runtimeStrategyControlEvent(userName, event.eventId),
|
|
121
|
+
event,
|
|
122
|
+
{ expire: 0 }
|
|
123
|
+
);
|
|
124
|
+
return event;
|
|
125
|
+
};
|
|
126
|
+
export {
|
|
127
|
+
emptyRuntimeControls,
|
|
128
|
+
getRuntimeControls,
|
|
129
|
+
pauseRuntimeStrategy,
|
|
130
|
+
recordRuntimeStrategyControlEvent,
|
|
131
|
+
resumeRuntimeStrategy,
|
|
132
|
+
verifyRuntimeControls
|
|
133
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { RuntimeDeploymentHeartbeat } from '@tradejs/types';
|
|
2
|
+
|
|
3
|
+
declare const getRuntimeDeploymentHeartbeat: (userName: string, deploymentId: string) => Promise<RuntimeDeploymentHeartbeat | null>;
|
|
4
|
+
declare const saveRuntimeDeploymentHeartbeat: (userName: string, heartbeat: RuntimeDeploymentHeartbeat) => Promise<RuntimeDeploymentHeartbeat>;
|
|
5
|
+
|
|
6
|
+
export { getRuntimeDeploymentHeartbeat, saveRuntimeDeploymentHeartbeat };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { RuntimeDeploymentHeartbeat } from '@tradejs/types';
|
|
2
|
+
|
|
3
|
+
declare const getRuntimeDeploymentHeartbeat: (userName: string, deploymentId: string) => Promise<RuntimeDeploymentHeartbeat | null>;
|
|
4
|
+
declare const saveRuntimeDeploymentHeartbeat: (userName: string, heartbeat: RuntimeDeploymentHeartbeat) => Promise<RuntimeDeploymentHeartbeat>;
|
|
5
|
+
|
|
6
|
+
export { getRuntimeDeploymentHeartbeat, saveRuntimeDeploymentHeartbeat };
|