nvent 0.5.4 → 0.5.6
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/module.json +1 -1
- package/dist/module.mjs +127 -23
- package/dist/runtime/adapters/builtin/memory-store.d.ts +2 -1
- package/dist/runtime/adapters/builtin/memory-store.js +28 -4
- package/dist/runtime/adapters/factory.js +8 -7
- package/dist/runtime/adapters/interfaces/queue.d.ts +5 -0
- package/dist/runtime/adapters/interfaces/store.d.ts +3 -1
- package/dist/runtime/config/index.js +14 -1
- package/dist/runtime/config/types.d.ts +42 -0
- package/dist/runtime/events/types.d.ts +0 -1
- package/dist/runtime/events/utils/stallDetector.d.ts +13 -77
- package/dist/runtime/events/utils/stallDetector.js +8 -192
- package/dist/runtime/events/wiring/flowWiring.js +347 -109
- package/dist/runtime/events/wiring/registry.js +9 -1
- package/dist/runtime/events/wiring/triggerWiring.js +11 -1
- package/dist/runtime/nitro/plugins/02.workers.js +31 -2
- package/dist/runtime/nitro/routes/webhook.await.js +28 -6
- package/dist/runtime/nitro/routes/webhook.trigger.d.ts +17 -0
- package/dist/runtime/nitro/routes/webhook.trigger.js +9 -0
- package/dist/runtime/nitro/utils/awaitPatterns/event.js +58 -50
- package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +6 -1
- package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +1 -1
- package/dist/runtime/nitro/utils/awaitPatterns/time.js +6 -2
- package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +53 -45
- package/dist/runtime/nitro/utils/defineFunction.d.ts +2 -9
- package/dist/runtime/nitro/utils/defineFunction.js +1 -14
- package/dist/runtime/nitro/utils/defineFunctionConfig.d.ts +84 -16
- package/dist/runtime/nitro/utils/defineHooks.d.ts +64 -10
- package/dist/runtime/nitro/utils/defineHooks.js +3 -0
- package/dist/runtime/nitro/utils/useAwait.d.ts +12 -0
- package/dist/runtime/nitro/utils/useAwait.js +34 -4
- package/dist/runtime/nitro/utils/useFlow.d.ts +39 -48
- package/dist/runtime/nitro/utils/useFlow.js +53 -14
- package/dist/runtime/nitro/utils/useHookRegistry.d.ts +10 -4
- package/dist/runtime/nitro/utils/useTrigger.js +7 -16
- package/dist/runtime/scheduler/index.js +5 -1
- package/dist/runtime/scheduler/scheduler.d.ts +19 -0
- package/dist/runtime/scheduler/scheduler.js +184 -8
- package/dist/runtime/scheduler/types.d.ts +6 -0
- package/dist/runtime/worker/node/runner.d.ts +44 -2
- package/dist/runtime/worker/node/runner.js +45 -100
- package/dist/runtime/worker/system/awaitHandlers.d.ts +27 -0
- package/dist/runtime/worker/system/awaitHandlers.js +230 -0
- package/dist/runtime/worker/system/index.d.ts +24 -0
- package/dist/runtime/worker/system/index.js +39 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dirname, join, extname, relative } from 'node:path';
|
|
2
2
|
import { useLogger, defineNuxtModule, createResolver, addTemplate, addTypeTemplate, addServerPlugin, addServerHandler, addServerImports, updateTemplates } from '@nuxt/kit';
|
|
3
3
|
import defu from 'defu';
|
|
4
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { existsSync, realpathSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { globby } from 'globby';
|
|
6
6
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
7
7
|
import { readFile } from 'node:fs/promises';
|
|
@@ -32,6 +32,7 @@ async function loadJsConfig(absPath) {
|
|
|
32
32
|
emits: flowCfg.emits,
|
|
33
33
|
subscribes,
|
|
34
34
|
triggers: flowCfg.triggers,
|
|
35
|
+
stepTimeout: flowCfg.stepTimeout,
|
|
35
36
|
awaitBefore: flowCfg.awaitBefore,
|
|
36
37
|
awaitAfter: flowCfg.awaitAfter
|
|
37
38
|
};
|
|
@@ -45,7 +46,7 @@ async function loadJsConfig(absPath) {
|
|
|
45
46
|
} : void 0;
|
|
46
47
|
const workerCfg = cfg?.worker && typeof cfg.worker === "object" ? { ...cfg.worker } : void 0;
|
|
47
48
|
const hasDefaultExport = !!(mod && mod.default);
|
|
48
|
-
const hasHooks = !!(mod && typeof mod.onAwaitRegister === "function" || mod && typeof mod.onAwaitResolve === "function");
|
|
49
|
+
const hasHooks = !!(mod && typeof mod.onAwaitRegister === "function" || mod && typeof mod.onAwaitResolve === "function" || mod && typeof mod.onAwaitTimeout === "function");
|
|
49
50
|
return { queueName, flow, runtype, queue: queueCfg, worker: workerCfg, hasDefaultExport, hasHooks };
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -75,6 +76,7 @@ async function loadTsConfig(absPath) {
|
|
|
75
76
|
emits: flowCfg.emits,
|
|
76
77
|
subscribes,
|
|
77
78
|
triggers: flowCfg.triggers,
|
|
79
|
+
stepTimeout: flowCfg.stepTimeout,
|
|
78
80
|
awaitBefore: flowCfg.awaitBefore,
|
|
79
81
|
awaitAfter: flowCfg.awaitAfter
|
|
80
82
|
};
|
|
@@ -87,7 +89,7 @@ async function loadTsConfig(absPath) {
|
|
|
87
89
|
limiter: cfg.queue.limiter
|
|
88
90
|
} : void 0;
|
|
89
91
|
const workerCfg = cfg?.worker && typeof cfg.worker === "object" ? { ...cfg.worker } : void 0;
|
|
90
|
-
const hasHooks = !!(mod.exports.onAwaitRegister || mod.exports.onAwaitResolve);
|
|
92
|
+
const hasHooks = !!(mod.exports.onAwaitRegister || mod.exports.onAwaitResolve || mod.exports.onAwaitTimeout);
|
|
91
93
|
return { queueName, flow, runtype, queue: queueCfg, worker: workerCfg, hasDefaultExport, hasHooks };
|
|
92
94
|
} catch (error) {
|
|
93
95
|
throw new Error(`Failed to parse config from ${absPath}: ${error}`);
|
|
@@ -185,6 +187,7 @@ async function loadPyConfig(absPath, logger) {
|
|
|
185
187
|
emits: flowCfg.emits,
|
|
186
188
|
subscribes,
|
|
187
189
|
triggers: flowCfg.triggers,
|
|
190
|
+
stepTimeout: flowCfg.stepTimeout,
|
|
188
191
|
awaitBefore: flowCfg.awaitBefore,
|
|
189
192
|
awaitAfter: flowCfg.awaitAfter
|
|
190
193
|
};
|
|
@@ -334,6 +337,7 @@ function buildFlows(flowSources) {
|
|
|
334
337
|
queue,
|
|
335
338
|
workerId: id,
|
|
336
339
|
emits: f.emits,
|
|
340
|
+
stepTimeout: f.stepTimeout,
|
|
337
341
|
awaitBefore: f.awaitBefore,
|
|
338
342
|
awaitAfter: f.awaitAfter
|
|
339
343
|
};
|
|
@@ -347,6 +351,7 @@ function buildFlows(flowSources) {
|
|
|
347
351
|
workerId: id,
|
|
348
352
|
subscribes: f.subscribes,
|
|
349
353
|
emits: f.emits,
|
|
354
|
+
stepTimeout: f.stepTimeout,
|
|
350
355
|
awaitBefore: f.awaitBefore,
|
|
351
356
|
awaitAfter: f.awaitAfter
|
|
352
357
|
};
|
|
@@ -361,6 +366,7 @@ function buildFlows(flowSources) {
|
|
|
361
366
|
workerId: id,
|
|
362
367
|
subscribes: f.subscribes,
|
|
363
368
|
emits: f.emits,
|
|
369
|
+
stepTimeout: f.stepTimeout,
|
|
364
370
|
awaitBefore: f.awaitBefore,
|
|
365
371
|
awaitAfter: f.awaitAfter
|
|
366
372
|
};
|
|
@@ -407,10 +413,10 @@ function parseSubscription(token) {
|
|
|
407
413
|
}
|
|
408
414
|
return { type: "implicit", value: token };
|
|
409
415
|
}
|
|
410
|
-
function findEmitter(token, entryStep, steps) {
|
|
416
|
+
function findEmitter(token, entryStep, steps, entry) {
|
|
411
417
|
const { type, value } = parseSubscription(token);
|
|
412
|
-
if (entryStep) {
|
|
413
|
-
const entryEmits =
|
|
418
|
+
if (entryStep && entry) {
|
|
419
|
+
const entryEmits = entry.emits || [];
|
|
414
420
|
if (entryEmits.includes(token) || entryEmits.includes(value)) {
|
|
415
421
|
return entryStep;
|
|
416
422
|
}
|
|
@@ -439,13 +445,13 @@ function findEmitter(token, entryStep, steps) {
|
|
|
439
445
|
}
|
|
440
446
|
return null;
|
|
441
447
|
}
|
|
442
|
-
function buildDependencyGraph(entryStep, steps) {
|
|
448
|
+
function buildDependencyGraph(entryStep, steps, entry) {
|
|
443
449
|
const dependencies = {};
|
|
444
450
|
for (const [stepName, step] of Object.entries(steps)) {
|
|
445
451
|
const deps = /* @__PURE__ */ new Set();
|
|
446
452
|
const subscribes = step.subscribes || [];
|
|
447
453
|
for (const token of subscribes) {
|
|
448
|
-
const emitter = findEmitter(token, entryStep, steps);
|
|
454
|
+
const emitter = findEmitter(token, entryStep, steps, entry);
|
|
449
455
|
if (emitter && emitter !== stepName) {
|
|
450
456
|
deps.add(emitter);
|
|
451
457
|
}
|
|
@@ -520,27 +526,67 @@ function findTriggeredSteps(stepName, step, allSteps) {
|
|
|
520
526
|
}
|
|
521
527
|
return Array.from(triggered);
|
|
522
528
|
}
|
|
529
|
+
function getAwaitDefaultTimeout(awaitConfig) {
|
|
530
|
+
if (!awaitConfig) return 0;
|
|
531
|
+
if (awaitConfig.timeout && awaitConfig.timeout > 0) {
|
|
532
|
+
return awaitConfig.timeout;
|
|
533
|
+
}
|
|
534
|
+
const type = awaitConfig.type;
|
|
535
|
+
switch (type) {
|
|
536
|
+
case "webhook":
|
|
537
|
+
return 24 * 60 * 60 * 1e3;
|
|
538
|
+
// 24 hours (matches flow.awaitDefaults.webhookTimeout)
|
|
539
|
+
case "event":
|
|
540
|
+
return 24 * 60 * 60 * 1e3;
|
|
541
|
+
// 24 hours (matches flow.awaitDefaults.eventTimeout)
|
|
542
|
+
case "time":
|
|
543
|
+
return 0;
|
|
544
|
+
// No timeout for time awaits by default
|
|
545
|
+
case "schedule":
|
|
546
|
+
return 0;
|
|
547
|
+
// No timeout for schedule awaits by default
|
|
548
|
+
default:
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
523
552
|
function getStepAwaitTimeout(step) {
|
|
524
553
|
let timeout = 0;
|
|
525
|
-
if (step.awaitBefore
|
|
526
|
-
|
|
554
|
+
if (step.awaitBefore) {
|
|
555
|
+
timeout += getAwaitDefaultTimeout(step.awaitBefore);
|
|
556
|
+
}
|
|
557
|
+
if (step.awaitAfter) {
|
|
558
|
+
timeout += getAwaitDefaultTimeout(step.awaitAfter);
|
|
559
|
+
}
|
|
527
560
|
return timeout;
|
|
528
561
|
}
|
|
562
|
+
function getStepExecutionTimeout(step, config) {
|
|
563
|
+
if (step.stepTimeout !== void 0) {
|
|
564
|
+
return step.stepTimeout;
|
|
565
|
+
}
|
|
566
|
+
if (config?.flow?.stepTimeout !== void 0) {
|
|
567
|
+
return config.flow.stepTimeout;
|
|
568
|
+
}
|
|
569
|
+
if (config?.queue?.defaultJobOptions?.timeout !== void 0) {
|
|
570
|
+
return config.queue.defaultJobOptions.timeout;
|
|
571
|
+
}
|
|
572
|
+
return void 0;
|
|
573
|
+
}
|
|
529
574
|
function calculateFlowStallTimeout(steps, levels) {
|
|
530
575
|
const DEFAULT_STALL_TIMEOUT = 30 * 60 * 1e3;
|
|
531
576
|
const MIN_BUFFER = 5 * 60 * 1e3;
|
|
532
577
|
const BUFFER_PERCENTAGE = 0.1;
|
|
578
|
+
const DEFAULT_STEP_TIMEOUT = 5 * 60 * 1e3;
|
|
533
579
|
const levelTimeouts = [];
|
|
534
|
-
let awaitCount = 0;
|
|
535
580
|
for (const levelSteps of levels) {
|
|
536
581
|
let maxLevelTimeout = 0;
|
|
537
582
|
for (const stepName of levelSteps) {
|
|
538
583
|
const step = steps[stepName];
|
|
539
584
|
if (!step) continue;
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
585
|
+
const stepExecTimeout = step.stepTimeout ?? DEFAULT_STEP_TIMEOUT;
|
|
586
|
+
const awaitTimeout = getStepAwaitTimeout(step);
|
|
587
|
+
const totalStepTimeout = stepExecTimeout + awaitTimeout;
|
|
588
|
+
if (totalStepTimeout > 0) {
|
|
589
|
+
maxLevelTimeout = Math.max(maxLevelTimeout, totalStepTimeout);
|
|
544
590
|
}
|
|
545
591
|
}
|
|
546
592
|
if (maxLevelTimeout > 0) {
|
|
@@ -555,31 +601,57 @@ function calculateFlowStallTimeout(steps, levels) {
|
|
|
555
601
|
const calculatedTimeout = totalAwaitTimeout + buffer;
|
|
556
602
|
if (calculatedTimeout > DEFAULT_STALL_TIMEOUT * 2) {
|
|
557
603
|
console.log(
|
|
558
|
-
`[flow-analyzer] Flow
|
|
604
|
+
`[flow-analyzer] Flow stall timeout calculated across ${levelTimeouts.length} levels: ${calculatedTimeout / 1e3}s (total time: ${totalAwaitTimeout / 1e3}s, level timeouts: [${levelTimeouts.map((t) => `${t / 1e3}s`).join(", ")}])`
|
|
559
605
|
);
|
|
560
606
|
}
|
|
561
607
|
return calculatedTimeout;
|
|
562
608
|
}
|
|
563
|
-
function analyzeFlow(flow) {
|
|
609
|
+
function analyzeFlow(flow, config) {
|
|
564
610
|
const entryStepName = flow.entry?.step;
|
|
565
611
|
const steps = flow.steps || {};
|
|
566
|
-
const dependencies = buildDependencyGraph(entryStepName, steps);
|
|
612
|
+
const dependencies = buildDependencyGraph(entryStepName, steps, flow.entry);
|
|
567
613
|
const levels = calculateLevels(entryStepName, dependencies);
|
|
568
614
|
const analyzedSteps = {};
|
|
615
|
+
if (flow.entry && entryStepName) {
|
|
616
|
+
const hasAwaitPattern = !!(flow.entry.awaitBefore || flow.entry.awaitAfter);
|
|
617
|
+
const entryStep = {
|
|
618
|
+
queue: flow.entry.queue,
|
|
619
|
+
workerId: flow.entry.workerId,
|
|
620
|
+
emits: flow.entry.emits,
|
|
621
|
+
stepTimeout: flow.entry.stepTimeout,
|
|
622
|
+
// Include stepTimeout from flow metadata
|
|
623
|
+
awaitBefore: flow.entry.awaitBefore,
|
|
624
|
+
awaitAfter: flow.entry.awaitAfter,
|
|
625
|
+
name: entryStepName,
|
|
626
|
+
dependsOn: [],
|
|
627
|
+
triggers: findTriggeredSteps(entryStepName, flow.entry, steps),
|
|
628
|
+
level: 0,
|
|
629
|
+
hasAwaitPattern
|
|
630
|
+
};
|
|
631
|
+
entryStep.stepTimeout = getStepExecutionTimeout(entryStep, config);
|
|
632
|
+
analyzedSteps[entryStepName] = entryStep;
|
|
633
|
+
}
|
|
569
634
|
for (const [stepName, step] of Object.entries(steps)) {
|
|
570
635
|
const hasAwaitPattern = !!(step.awaitBefore || step.awaitAfter);
|
|
571
|
-
|
|
636
|
+
const analyzedStep = {
|
|
572
637
|
...step,
|
|
573
638
|
name: stepName,
|
|
574
639
|
dependsOn: dependencies[stepName] || [],
|
|
575
640
|
triggers: findTriggeredSteps(stepName, step, steps),
|
|
576
641
|
level: levels[stepName] ?? 1,
|
|
577
642
|
hasAwaitPattern
|
|
643
|
+
// stepTimeout from ...step spread above (per-function config)
|
|
578
644
|
};
|
|
645
|
+
analyzedStep.stepTimeout = getStepExecutionTimeout(analyzedStep, config);
|
|
646
|
+
analyzedSteps[stepName] = analyzedStep;
|
|
579
647
|
}
|
|
580
648
|
const maxLevel = Math.max(0, ...Object.values(levels));
|
|
581
649
|
const levelGroups = Array.from({ length: maxLevel + 1 }, () => []);
|
|
650
|
+
if (entryStepName && flow.entry) {
|
|
651
|
+
levelGroups[0]?.push(entryStepName);
|
|
652
|
+
}
|
|
582
653
|
for (const [stepName, level] of Object.entries(levels)) {
|
|
654
|
+
if (stepName === entryStepName) continue;
|
|
583
655
|
const levelArray = levelGroups[level];
|
|
584
656
|
if (levelArray) {
|
|
585
657
|
levelArray.push(stepName);
|
|
@@ -822,7 +894,7 @@ function generateAnalyzedFlowsTemplate(registry) {
|
|
|
822
894
|
entry,
|
|
823
895
|
steps
|
|
824
896
|
};
|
|
825
|
-
const analyzed = analyzeFlow(flowMeta);
|
|
897
|
+
const analyzed = analyzeFlow(flowMeta, registry.config);
|
|
826
898
|
return {
|
|
827
899
|
...flowMeta,
|
|
828
900
|
analyzed: {
|
|
@@ -909,6 +981,25 @@ export type {
|
|
|
909
981
|
ListOptions,
|
|
910
982
|
} from ${JSON.stringify(resolverFn("./runtime/adapters/interfaces/store"))}
|
|
911
983
|
|
|
984
|
+
// Flow Types
|
|
985
|
+
export type {
|
|
986
|
+
FlowStats,
|
|
987
|
+
StartFlowResult,
|
|
988
|
+
CancelFlowResult,
|
|
989
|
+
RunningFlow,
|
|
990
|
+
FlowComposable,
|
|
991
|
+
} from ${JSON.stringify(resolverFn("./runtime/nitro/utils/useFlow"))}
|
|
992
|
+
|
|
993
|
+
// Runner Context Types
|
|
994
|
+
export type {
|
|
995
|
+
QueueJob,
|
|
996
|
+
RunLogger,
|
|
997
|
+
RunState,
|
|
998
|
+
RunContextFlow,
|
|
999
|
+
RunContext,
|
|
1000
|
+
NodeHandler,
|
|
1001
|
+
} from ${JSON.stringify(resolverFn("./runtime/worker/node/runner"))}
|
|
1002
|
+
|
|
912
1003
|
// Event Types
|
|
913
1004
|
export type {
|
|
914
1005
|
EventType,
|
|
@@ -998,6 +1089,10 @@ function getServerImports(resolverFn, buildDir) {
|
|
|
998
1089
|
name: "defineAwaitResolveHook",
|
|
999
1090
|
from: resolverFn("./runtime/nitro/utils/defineHooks")
|
|
1000
1091
|
},
|
|
1092
|
+
{
|
|
1093
|
+
name: "defineAwaitTimeoutHook",
|
|
1094
|
+
from: resolverFn("./runtime/nitro/utils/defineHooks")
|
|
1095
|
+
},
|
|
1001
1096
|
// Adapter composables
|
|
1002
1097
|
{
|
|
1003
1098
|
name: "useQueueAdapter",
|
|
@@ -1070,9 +1165,10 @@ function getServerImports(resolverFn, buildDir) {
|
|
|
1070
1165
|
];
|
|
1071
1166
|
}
|
|
1072
1167
|
|
|
1168
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
1073
1169
|
const meta = {
|
|
1074
1170
|
name: "nvent",
|
|
1075
|
-
version:
|
|
1171
|
+
version: packageJson.version,
|
|
1076
1172
|
configKey: "nvent"
|
|
1077
1173
|
};
|
|
1078
1174
|
const module$1 = defineNuxtModule().with({
|
|
@@ -1117,7 +1213,11 @@ const module$1 = defineNuxtModule().with({
|
|
|
1117
1213
|
logger: { name: "console", level: "info" },
|
|
1118
1214
|
runner: { ts: { isolate: "inprocess" }, py: { enabled: false, cmd: "python3", importMode: "file" } },
|
|
1119
1215
|
flows: {},
|
|
1120
|
-
eventIndex: {}
|
|
1216
|
+
eventIndex: {},
|
|
1217
|
+
config: {
|
|
1218
|
+
flow: config.flow,
|
|
1219
|
+
queue: config.queue
|
|
1220
|
+
}
|
|
1121
1221
|
});
|
|
1122
1222
|
const compiledSnapshot = JSON.parse(JSON.stringify(compiledWithMeta));
|
|
1123
1223
|
let lastCompiledRegistry = compiledSnapshot;
|
|
@@ -1177,7 +1277,11 @@ const module$1 = defineNuxtModule().with({
|
|
|
1177
1277
|
logger: { name: "console", level: "info" },
|
|
1178
1278
|
runner: { ts: { isolate: "inprocess" }, py: { enabled: false, cmd: "python3", importMode: "file" } },
|
|
1179
1279
|
flows: {},
|
|
1180
|
-
eventIndex: {}
|
|
1280
|
+
eventIndex: {},
|
|
1281
|
+
config: {
|
|
1282
|
+
flow: config.flow,
|
|
1283
|
+
queue: config.queue
|
|
1284
|
+
}
|
|
1181
1285
|
})));
|
|
1182
1286
|
console.log(`[nvent] registry refreshed (${reason})`, changedPath || "");
|
|
1183
1287
|
console.log(`[nvent] new registry has ${lastCompiledRegistry.workers?.length || 0} workers`);
|
|
@@ -43,6 +43,7 @@ export declare class MemoryStoreAdapter implements StoreAdapter {
|
|
|
43
43
|
read: (key: string, opts?: {
|
|
44
44
|
offset?: number;
|
|
45
45
|
limit?: number;
|
|
46
|
+
filter?: Record<string, any>;
|
|
46
47
|
}) => Promise<Array<{
|
|
47
48
|
id: string;
|
|
48
49
|
score: number;
|
|
@@ -61,7 +62,7 @@ export declare class MemoryStoreAdapter implements StoreAdapter {
|
|
|
61
62
|
/**
|
|
62
63
|
* Convert dot notation keys to nested objects
|
|
63
64
|
* e.g., { 'stats.totalFires': 5 } -> { stats: { totalFires: 5 } }
|
|
64
|
-
* null values are
|
|
65
|
+
* null values are tracked for deletion after merge
|
|
65
66
|
*/
|
|
66
67
|
private expandDotNotation;
|
|
67
68
|
private generateId;
|
|
@@ -173,7 +173,19 @@ export class MemoryStoreAdapter {
|
|
|
173
173
|
return entry ? { ...entry } : null;
|
|
174
174
|
},
|
|
175
175
|
read: async (key, opts) => {
|
|
176
|
-
|
|
176
|
+
let index = this.sortedIndices.get(key) || [];
|
|
177
|
+
if (opts?.filter) {
|
|
178
|
+
index = index.filter((entry) => {
|
|
179
|
+
for (const [field, value] of Object.entries(opts.filter)) {
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
if (!value.includes(entry.metadata?.[field])) return false;
|
|
182
|
+
} else if (entry.metadata?.[field] !== value) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
177
189
|
const offset = opts?.offset || 0;
|
|
178
190
|
const limit = opts?.limit || 50;
|
|
179
191
|
return index.slice(offset, offset + limit).map((e) => ({ ...e }));
|
|
@@ -291,9 +303,9 @@ export class MemoryStoreAdapter {
|
|
|
291
303
|
/**
|
|
292
304
|
* Convert dot notation keys to nested objects
|
|
293
305
|
* e.g., { 'stats.totalFires': 5 } -> { stats: { totalFires: 5 } }
|
|
294
|
-
* null values are
|
|
306
|
+
* null values are tracked for deletion after merge
|
|
295
307
|
*/
|
|
296
|
-
expandDotNotation(obj) {
|
|
308
|
+
expandDotNotation(obj, parentPath = []) {
|
|
297
309
|
const result = {};
|
|
298
310
|
const deleteMarkers = [];
|
|
299
311
|
for (const [key, value] of Object.entries(obj)) {
|
|
@@ -303,7 +315,7 @@ export class MemoryStoreAdapter {
|
|
|
303
315
|
if (key.includes(".")) {
|
|
304
316
|
const keys = key.split(".");
|
|
305
317
|
if (value === null || value === void 0) {
|
|
306
|
-
deleteMarkers.push({ path: keys, delete: true });
|
|
318
|
+
deleteMarkers.push({ path: [...parentPath, ...keys], delete: true });
|
|
307
319
|
continue;
|
|
308
320
|
}
|
|
309
321
|
let current = result;
|
|
@@ -315,6 +327,18 @@ export class MemoryStoreAdapter {
|
|
|
315
327
|
current = current[k];
|
|
316
328
|
}
|
|
317
329
|
current[keys[keys.length - 1]] = value;
|
|
330
|
+
} else if (value === null || value === void 0) {
|
|
331
|
+
deleteMarkers.push({ path: [...parentPath, key], delete: true });
|
|
332
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
333
|
+
const nested = this.expandDotNotation(value, [...parentPath, key]);
|
|
334
|
+
const nestedDeleteMarkers = nested.__deleteMarkers;
|
|
335
|
+
delete nested.__deleteMarkers;
|
|
336
|
+
if (nestedDeleteMarkers) {
|
|
337
|
+
deleteMarkers.push(...nestedDeleteMarkers);
|
|
338
|
+
}
|
|
339
|
+
if (Object.keys(nested).length > 0) {
|
|
340
|
+
result[key] = nested;
|
|
341
|
+
}
|
|
318
342
|
} else {
|
|
319
343
|
result[key] = value;
|
|
320
344
|
}
|
|
@@ -69,13 +69,14 @@ async function createStoreAdapter(config) {
|
|
|
69
69
|
try {
|
|
70
70
|
const { StoreSubjects } = useStreamTopics();
|
|
71
71
|
const flowIndexKey = StoreSubjects.flowIndex();
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
const analyzedFlows = $useAnalyzedFlows();
|
|
73
|
+
if (analyzedFlows && analyzedFlows.length > 0) {
|
|
74
|
+
const existingFlows = await adapter.index.read(flowIndexKey, { limit: 100 });
|
|
75
|
+
const existingFlowIds = new Set(existingFlows.map((f) => f.id));
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
for (const flow of analyzedFlows) {
|
|
78
|
+
if (!existingFlowIds.has(flow.id)) {
|
|
79
|
+
await adapter.index.add(flowIndexKey, flow.id, now, {
|
|
79
80
|
name: flow.id,
|
|
80
81
|
displayName: flow.id,
|
|
81
82
|
registeredAt: now,
|
|
@@ -49,6 +49,11 @@ export interface QueueAdapter {
|
|
|
49
49
|
* @returns true if removed, false if not found
|
|
50
50
|
*/
|
|
51
51
|
removeScheduledJob?(scheduleId: string): Promise<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Remove/cancel a job by ID
|
|
54
|
+
* @returns true if removed, false if not found
|
|
55
|
+
*/
|
|
56
|
+
removeJob?(queueName: string, jobId: string): Promise<boolean>;
|
|
52
57
|
/**
|
|
53
58
|
* Pause the queue
|
|
54
59
|
*/
|
|
@@ -167,14 +167,16 @@ export interface StoreAdapter {
|
|
|
167
167
|
/**
|
|
168
168
|
* Read entries from a sorted index (ordered by score descending)
|
|
169
169
|
* @param key - Index key
|
|
170
|
-
* @param opts - Pagination options
|
|
170
|
+
* @param opts - Pagination and filter options
|
|
171
171
|
* @param opts.offset - Number of entries to skip
|
|
172
172
|
* @param opts.limit - Maximum number of entries to return
|
|
173
|
+
* @param opts.filter - Optional filter criteria for metadata fields (adapter-dependent efficiency)
|
|
173
174
|
* @returns Array of entries with scores and metadata
|
|
174
175
|
*/
|
|
175
176
|
read(key: string, opts?: {
|
|
176
177
|
offset?: number;
|
|
177
178
|
limit?: number;
|
|
179
|
+
filter?: Record<string, any>;
|
|
178
180
|
}): Promise<Array<{
|
|
179
181
|
id: string;
|
|
180
182
|
score: number;
|
|
@@ -45,7 +45,20 @@ export function normalizeModuleOptions(options) {
|
|
|
45
45
|
checkInterval: 15 * 60 * 1e3,
|
|
46
46
|
// 15 minutes
|
|
47
47
|
enablePeriodicCheck: true
|
|
48
|
-
}
|
|
48
|
+
},
|
|
49
|
+
awaitDefaults: {
|
|
50
|
+
webhookTimeout: 24 * 60 * 60 * 1e3,
|
|
51
|
+
// 24 hours
|
|
52
|
+
eventTimeout: 24 * 60 * 60 * 1e3,
|
|
53
|
+
// 24 hours
|
|
54
|
+
timeTimeout: void 0,
|
|
55
|
+
// No default timeout for time awaits
|
|
56
|
+
scheduleTimeout: void 0,
|
|
57
|
+
// No default timeout for schedule awaits
|
|
58
|
+
timeoutAction: "fail"
|
|
59
|
+
},
|
|
60
|
+
stepTimeout: 5 * 60 * 1e3
|
|
61
|
+
// 5 minutes default step execution timeout
|
|
49
62
|
},
|
|
50
63
|
webhooks: {
|
|
51
64
|
// baseUrl will be determined at runtime from Nitro context
|
|
@@ -238,6 +238,48 @@ export interface FlowConfig {
|
|
|
238
238
|
*/
|
|
239
239
|
enablePeriodicCheck?: boolean;
|
|
240
240
|
};
|
|
241
|
+
/**
|
|
242
|
+
* Default timeout values for await patterns
|
|
243
|
+
* These are used when developers don't specify explicit timeouts
|
|
244
|
+
* @since v0.5.0
|
|
245
|
+
*/
|
|
246
|
+
awaitDefaults?: {
|
|
247
|
+
/**
|
|
248
|
+
* Default timeout for webhook await patterns in milliseconds
|
|
249
|
+
* @default 86400000 (24 hours)
|
|
250
|
+
*/
|
|
251
|
+
webhookTimeout?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Default timeout for event await patterns in milliseconds
|
|
254
|
+
* @default 86400000 (24 hours)
|
|
255
|
+
*/
|
|
256
|
+
eventTimeout?: number;
|
|
257
|
+
/**
|
|
258
|
+
* Default timeout for time await patterns in milliseconds
|
|
259
|
+
* Time awaits typically don't need a timeout since they resolve based on delay
|
|
260
|
+
* @default undefined (no timeout)
|
|
261
|
+
*/
|
|
262
|
+
timeTimeout?: number;
|
|
263
|
+
/**
|
|
264
|
+
* Default timeout for schedule await patterns in milliseconds
|
|
265
|
+
* Schedule awaits typically don't need a timeout since they resolve based on cron
|
|
266
|
+
* @default undefined (no timeout)
|
|
267
|
+
*/
|
|
268
|
+
scheduleTimeout?: number;
|
|
269
|
+
/**
|
|
270
|
+
* Default timeout action when await times out
|
|
271
|
+
* @default 'fail'
|
|
272
|
+
*/
|
|
273
|
+
timeoutAction?: 'fail' | 'continue' | 'retry';
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Default step execution timeout in milliseconds
|
|
277
|
+
* This is the maximum time a step function can run before timing out
|
|
278
|
+
* Priority: defineFunctionConfig > flow.stepTimeout > queue.defaultJobOptions.timeout
|
|
279
|
+
* @default 300000 (5 minutes)
|
|
280
|
+
* @since v0.5.0
|
|
281
|
+
*/
|
|
282
|
+
stepTimeout?: number;
|
|
241
283
|
}
|
|
242
284
|
/**
|
|
243
285
|
* State management configuration
|
|
@@ -2,102 +2,45 @@
|
|
|
2
2
|
* Flow Stall Detection System
|
|
3
3
|
*
|
|
4
4
|
* Detects and marks flows that have been in "running" state for too long without activity.
|
|
5
|
-
* Uses
|
|
6
|
-
* 1. Lazy detection: Check stall status when flows are queried (zero overhead)
|
|
7
|
-
* 2. Periodic cleanup: Background job that checks all running flows periodically (safety net)
|
|
5
|
+
* Uses per-flow scheduler jobs for precise timeout tracking:
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
7
|
+
* - On flow.start: Schedule timeout job with flow-specific deadline
|
|
8
|
+
* - On step events: Reschedule to extend timeout from current time
|
|
9
|
+
* - On flow end: Unschedule the timeout job
|
|
10
|
+
* - When timeout fires: Mark flow as stalled
|
|
11
|
+
*
|
|
12
|
+
* Startup recovery handles flows left running from previous server instance.
|
|
13
13
|
*/
|
|
14
14
|
import type { StoreAdapter } from '../../adapters/interfaces/store.js';
|
|
15
15
|
export interface StallDetectorConfig {
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @default 1800000 (30 minutes)
|
|
19
|
-
*/
|
|
20
|
-
stallTimeout?: number;
|
|
21
|
-
/**
|
|
22
|
-
* Interval in milliseconds for periodic stall checks
|
|
23
|
-
* @default 900000 (15 minutes)
|
|
24
|
-
*/
|
|
25
|
-
checkInterval?: number;
|
|
26
|
-
/**
|
|
27
|
-
* Enable periodic background checks
|
|
28
|
-
* Set to false to use only lazy detection
|
|
17
|
+
* Enable stall detection system
|
|
29
18
|
* @default true
|
|
30
19
|
*/
|
|
31
|
-
|
|
20
|
+
enabled?: boolean;
|
|
32
21
|
}
|
|
33
22
|
export type FlowStatus = 'running' | 'completed' | 'failed' | 'canceled' | 'stalled';
|
|
34
|
-
export interface FlowActivity {
|
|
35
|
-
runId: string;
|
|
36
|
-
flowName: string;
|
|
37
|
-
status: FlowStatus;
|
|
38
|
-
startedAt: number;
|
|
39
|
-
lastActivityAt: number;
|
|
40
|
-
metadata?: any;
|
|
41
|
-
}
|
|
42
23
|
export declare class FlowStallDetector {
|
|
43
24
|
private store;
|
|
44
25
|
private config;
|
|
45
26
|
private logger;
|
|
46
|
-
private schedulerJobId?;
|
|
47
27
|
private started;
|
|
48
28
|
constructor(store: StoreAdapter, config?: StallDetectorConfig);
|
|
49
29
|
/**
|
|
50
|
-
* Start the
|
|
51
|
-
* Should be called once per instance after adapters are initialized
|
|
30
|
+
* Start the stall detector
|
|
52
31
|
* Runs startup recovery to clean up flows from previous server instances
|
|
32
|
+
* Note: Periodic checking removed - now uses per-flow scheduler jobs
|
|
53
33
|
*/
|
|
54
34
|
start(): Promise<void>;
|
|
55
35
|
/**
|
|
56
|
-
*
|
|
57
|
-
* Returns config needed by flowWiring to register the scheduler job
|
|
58
|
-
*/
|
|
59
|
-
getScheduleConfig(): {
|
|
60
|
-
enabled: boolean;
|
|
61
|
-
interval: number;
|
|
62
|
-
stallTimeout: number;
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* Set the scheduler job ID (called from flowWiring after scheduling)
|
|
66
|
-
*/
|
|
67
|
-
setSchedulerJobId(jobId: string): void;
|
|
68
|
-
/**
|
|
69
|
-
* Stop the periodic stall detector
|
|
36
|
+
* Stop the stall detector
|
|
70
37
|
*/
|
|
71
38
|
stop(): Promise<void>;
|
|
72
|
-
/**
|
|
73
|
-
* Get stall timeout for a specific flow
|
|
74
|
-
* Uses flow-specific timeout from analyzed metadata, falls back to global config
|
|
75
|
-
*/
|
|
76
|
-
private getFlowStallTimeout;
|
|
77
|
-
/**
|
|
78
|
-
* Update activity timestamp for a flow
|
|
79
|
-
* Should be called on every step event (started, completed, failed, retry)
|
|
80
|
-
*/
|
|
81
|
-
updateActivity(flowName: string, runId: string): Promise<void>;
|
|
82
|
-
/**
|
|
83
|
-
* Check if a specific flow is stalled (lazy detection)
|
|
84
|
-
* Returns true if the flow should be marked as stalled
|
|
85
|
-
* v0.5: Await-aware - uses flow-specific timeout and skips awaiting flows
|
|
86
|
-
*/
|
|
87
|
-
isStalled(flowName: string, runId: string): Promise<boolean>;
|
|
88
39
|
/**
|
|
89
40
|
* Mark a flow as stalled
|
|
90
41
|
* Emits a flow.stalled event and updates the flow status
|
|
91
42
|
*/
|
|
92
43
|
markAsStalled(flowName: string, runId: string, reason?: string): Promise<void>;
|
|
93
|
-
/**
|
|
94
|
-
* Check all running flows and mark stalled ones
|
|
95
|
-
* This is called by the periodic background job
|
|
96
|
-
*
|
|
97
|
-
* Note: This method requires knowledge of which flows exist.
|
|
98
|
-
* For now, we'll need to pass flow names to check, or iterate known flows from registry.
|
|
99
|
-
*/
|
|
100
|
-
checkFlowsForStalls(flowNames: string[]): Promise<void>;
|
|
101
44
|
/**
|
|
102
45
|
* Run startup recovery to clean up flows left in running state from previous server instance
|
|
103
46
|
* This marks all running flows as stalled since their in-memory state is lost
|
|
@@ -119,19 +62,12 @@ export declare class FlowStallDetector {
|
|
|
119
62
|
* - Minor discrepancies don't affect runtime behavior
|
|
120
63
|
*/
|
|
121
64
|
private validateFlowStats;
|
|
122
|
-
/**
|
|
123
|
-
* Internal method for periodic checks
|
|
124
|
-
* Gets flow names from registry and checks them
|
|
125
|
-
*/
|
|
126
|
-
private checkAllRunningFlows;
|
|
127
65
|
/**
|
|
128
66
|
* Get stall detector statistics
|
|
129
67
|
*/
|
|
130
68
|
getStats(): {
|
|
131
69
|
enabled: boolean;
|
|
132
|
-
|
|
133
|
-
stallTimeout: number;
|
|
134
|
-
checkInterval: number;
|
|
70
|
+
mode: string;
|
|
135
71
|
};
|
|
136
72
|
}
|
|
137
73
|
/**
|