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.
Files changed (46) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +127 -23
  3. package/dist/runtime/adapters/builtin/memory-store.d.ts +2 -1
  4. package/dist/runtime/adapters/builtin/memory-store.js +28 -4
  5. package/dist/runtime/adapters/factory.js +8 -7
  6. package/dist/runtime/adapters/interfaces/queue.d.ts +5 -0
  7. package/dist/runtime/adapters/interfaces/store.d.ts +3 -1
  8. package/dist/runtime/config/index.js +14 -1
  9. package/dist/runtime/config/types.d.ts +42 -0
  10. package/dist/runtime/events/types.d.ts +0 -1
  11. package/dist/runtime/events/utils/stallDetector.d.ts +13 -77
  12. package/dist/runtime/events/utils/stallDetector.js +8 -192
  13. package/dist/runtime/events/wiring/flowWiring.js +347 -109
  14. package/dist/runtime/events/wiring/registry.js +9 -1
  15. package/dist/runtime/events/wiring/triggerWiring.js +11 -1
  16. package/dist/runtime/nitro/plugins/02.workers.js +31 -2
  17. package/dist/runtime/nitro/routes/webhook.await.js +28 -6
  18. package/dist/runtime/nitro/routes/webhook.trigger.d.ts +17 -0
  19. package/dist/runtime/nitro/routes/webhook.trigger.js +9 -0
  20. package/dist/runtime/nitro/utils/awaitPatterns/event.js +58 -50
  21. package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +6 -1
  22. package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +1 -1
  23. package/dist/runtime/nitro/utils/awaitPatterns/time.js +6 -2
  24. package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +53 -45
  25. package/dist/runtime/nitro/utils/defineFunction.d.ts +2 -9
  26. package/dist/runtime/nitro/utils/defineFunction.js +1 -14
  27. package/dist/runtime/nitro/utils/defineFunctionConfig.d.ts +84 -16
  28. package/dist/runtime/nitro/utils/defineHooks.d.ts +64 -10
  29. package/dist/runtime/nitro/utils/defineHooks.js +3 -0
  30. package/dist/runtime/nitro/utils/useAwait.d.ts +12 -0
  31. package/dist/runtime/nitro/utils/useAwait.js +34 -4
  32. package/dist/runtime/nitro/utils/useFlow.d.ts +39 -48
  33. package/dist/runtime/nitro/utils/useFlow.js +53 -14
  34. package/dist/runtime/nitro/utils/useHookRegistry.d.ts +10 -4
  35. package/dist/runtime/nitro/utils/useTrigger.js +7 -16
  36. package/dist/runtime/scheduler/index.js +5 -1
  37. package/dist/runtime/scheduler/scheduler.d.ts +19 -0
  38. package/dist/runtime/scheduler/scheduler.js +184 -8
  39. package/dist/runtime/scheduler/types.d.ts +6 -0
  40. package/dist/runtime/worker/node/runner.d.ts +44 -2
  41. package/dist/runtime/worker/node/runner.js +45 -100
  42. package/dist/runtime/worker/system/awaitHandlers.d.ts +27 -0
  43. package/dist/runtime/worker/system/awaitHandlers.js +230 -0
  44. package/dist/runtime/worker/system/index.d.ts +24 -0
  45. package/dist/runtime/worker/system/index.js +39 -0
  46. package/package.json +1 -1
@@ -110,6 +110,14 @@ export interface FlowConfig {
110
110
  */
111
111
  mode?: 'auto' | 'manual';
112
112
  };
113
+ /**
114
+ * Step execution timeout in milliseconds (v0.5.1)
115
+ * Overrides global flow.stepTimeout and queue.defaultJobOptions.timeout for this specific step
116
+ *
117
+ * @example 600000 // 10 minutes
118
+ * @example 3600000 // 1 hour
119
+ */
120
+ stepTimeout?: number;
113
121
  /**
114
122
  * Await pattern: Wait BEFORE step execution (v0.5)
115
123
  * Step won't execute until trigger fires
@@ -123,53 +131,113 @@ export interface FlowConfig {
123
131
  }
124
132
  /**
125
133
  * Await configuration (run-scoped triggers)
126
- * Declared in config, no functions allowed (AST-parsed)
134
+ *
135
+ * Pauses flow execution until a specific condition is met. Can be used with:
136
+ * - `awaitBefore`: Wait before step execution starts
137
+ * - `awaitAfter`: Wait after step completes before triggering next steps
138
+ *
139
+ * Declared in config, no functions allowed (AST-parsed at build time)
127
140
  */
128
141
  export interface AwaitConfig {
129
142
  /**
130
- * Trigger type
143
+ * Type of trigger to wait for
144
+ * - `webhook`: Wait for HTTP request to specific endpoint
145
+ * - `event`: Wait for custom event with optional data matching
146
+ * - `schedule`: Wait until specific cron schedule time
147
+ * - `time`: Wait for fixed time delay
131
148
  */
132
149
  type: 'webhook' | 'event' | 'schedule' | 'time';
133
150
  /**
134
- * URL path for webhook (supports template variables: {runId}, {stepId})
151
+ * URL path for webhook trigger (supports template variables)
152
+ *
153
+ * Only used when `type: 'webhook'`
154
+ *
155
+ * Template variables available:
156
+ * - `{runId}`: Current flow run ID
157
+ * - `{stepId}`: Current step execution ID
158
+ *
159
+ * @example '/webhooks/approve/{runId}'
160
+ * @example '/api/confirm/{stepId}'
135
161
  */
136
162
  path?: string;
137
163
  /**
138
- * HTTP method for webhook
164
+ * HTTP method for webhook endpoint
165
+ *
166
+ * Only used when `type: 'webhook'`
167
+ *
168
+ * @default 'POST'
139
169
  */
140
170
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
141
171
  /**
142
172
  * Event name to wait for
173
+ *
174
+ * Only used when `type: 'event'`
175
+ *
176
+ * @example 'payment.completed'
177
+ * @example 'order.shipped'
143
178
  */
144
179
  event?: string;
145
180
  /**
146
- * Key to match between event and step data
147
- * e.g., 'orderId' matches event.orderId === step.orderId
148
- * Supports nested paths: 'order.id' matches event.order.id === step.order.id
181
+ * Key to match between event data and step output
182
+ *
183
+ * Only used when `type: 'event'`
184
+ *
185
+ * Matches incoming event data against step output data.
186
+ * Supports nested paths using dot notation.
187
+ *
188
+ * @example 'orderId' - matches event.orderId === stepOutput.orderId
189
+ * @example 'order.id' - matches event.order.id === stepOutput.order.id
149
190
  */
150
191
  filterKey?: string;
151
192
  /**
152
- * Cron expression (e.g., '0 9 * * *' for 9 AM daily)
193
+ * Cron expression defining when to trigger (one-time schedule)
194
+ *
195
+ * Only used when `type: 'schedule'`
196
+ *
197
+ * Note: This is a one-time trigger, not recurring. The next occurrence
198
+ * of the cron expression after step completion will trigger continuation.
199
+ *
200
+ * @example '0 9 * * *' - 9 AM daily (triggers at next 9 AM occurrence)
201
+ * @example '0 0 * * MON' - Midnight every Monday
153
202
  */
154
203
  cron?: string;
155
204
  /**
156
- * Hours to add after step completion before calculating next cron occurrence
157
- */
158
- nextAfterHours?: number;
159
- /**
160
- * Timezone for cron expression
205
+ * Timezone for cron expression evaluation
206
+ *
207
+ * Only used when `type: 'schedule'`
208
+ *
209
+ * @example 'America/New_York'
210
+ * @example 'Europe/Berlin'
211
+ * @default 'UTC'
161
212
  */
162
213
  timezone?: string;
163
214
  /**
164
- * Delay in milliseconds
215
+ * Fixed delay in milliseconds before continuation
216
+ *
217
+ * Only used when `type: 'time'`
218
+ *
219
+ * @example 60000 - 1 minute
220
+ * @example 3600000 - 1 hour
165
221
  */
166
222
  delay?: number;
167
223
  /**
168
- * Maximum wait time in milliseconds
224
+ * Maximum wait time in milliseconds before timeout
225
+ *
226
+ * Applies to all trigger types. If the trigger doesn't fire within
227
+ * this duration, the timeout action will be taken.
228
+ *
229
+ * @example 86400000 - 24 hours
230
+ * @example 604800000 - 7 days
169
231
  */
170
232
  timeout?: number;
171
233
  /**
172
- * Action to take on timeout
234
+ * Action to take when timeout is reached
235
+ *
236
+ * - `fail`: Mark step/flow as failed
237
+ * - `continue`: Continue flow execution anyway
238
+ * - `retry`: Retry waiting (reset timeout)
239
+ *
240
+ * @default 'fail'
173
241
  */
174
242
  timeoutAction?: 'fail' | 'continue' | 'retry';
175
243
  }
@@ -2,30 +2,65 @@
2
2
  * Type-safe hook definitions for await patterns
3
3
  * v0.5 - Await Integration
4
4
  */
5
- import type { RunContext } from '../worker/node/runner.js';
6
- export interface AwaitRegisterContext extends Pick<RunContext, 'flowId' | 'flowName' | 'stepName' | 'logger' | 'state'> {
7
- awaitType: 'webhook' | 'event' | 'schedule' | 'time';
5
+ import type { RunContext } from '../../worker/node/runner.js';
6
+ export type AwaitType = 'webhook' | 'event' | 'schedule' | 'time';
7
+ /**
8
+ * Hook data types specific to each await type
9
+ */
10
+ export interface WebhookHookData {
11
+ webhookUrl: string;
12
+ }
13
+ export interface EventHookData {
14
+ eventName: string;
15
+ }
16
+ export interface ScheduleHookData {
17
+ cronExpression: string;
18
+ nextOccurrence: Date;
19
+ }
20
+ export interface TimeHookData {
21
+ delayMs: number;
22
+ }
23
+ /**
24
+ * Conditional type to get the correct hook data based on await type
25
+ */
26
+ export type HookDataForAwaitType<T extends AwaitType> = T extends 'webhook' ? WebhookHookData : T extends 'event' ? EventHookData : T extends 'schedule' ? ScheduleHookData : T extends 'time' ? TimeHookData : never;
27
+ export interface AwaitRegisterContext<T extends AwaitType = AwaitType> extends Pick<RunContext, 'flowId' | 'flowName' | 'stepName' | 'logger' | 'state'> {
28
+ awaitType: T;
8
29
  awaitConfig: any;
30
+ position: 'before' | 'after';
9
31
  }
10
- export interface AwaitResolveContext extends Pick<RunContext, 'flowId' | 'flowName' | 'stepName' | 'logger' | 'state'> {
11
- awaitType: 'webhook' | 'event' | 'schedule' | 'time';
32
+ export interface AwaitResolveContext<T extends AwaitType = AwaitType> extends Pick<RunContext, 'flowId' | 'flowName' | 'stepName' | 'logger' | 'state'> {
33
+ awaitType: T;
12
34
  resolvedData: any;
35
+ position: 'before' | 'after';
36
+ }
37
+ export interface AwaitTimeoutContext<T extends AwaitType = AwaitType> extends Pick<RunContext, 'flowId' | 'flowName' | 'stepName' | 'logger' | 'state'> {
38
+ awaitType: T;
39
+ timeoutAction: 'fail' | 'continue' | 'retry';
40
+ position: 'before' | 'after';
13
41
  }
14
42
  /**
15
43
  * Define onAwaitRegister hook with proper types
16
44
  * Called when an await pattern is registered (before handler execution or after completion)
17
45
  *
18
46
  * @example
19
- * export const onAwaitRegister = defineAwaitRegisterHook(async (webhookUrl, stepData, ctx) => {
20
- * // Send notification email with webhook URL
47
+ * // Webhook example
48
+ * export const onAwaitRegister = defineAwaitRegisterHook(async (hookData, stepData, ctx) => {
49
+ * // hookData.webhookUrl is typed correctly for webhook awaits
21
50
  * await sendEmail({
22
51
  * to: stepData.reviewerEmail,
23
52
  * subject: 'Approval Required',
24
- * approveUrl: webhookUrl
53
+ * approveUrl: hookData.webhookUrl
25
54
  * })
26
55
  * })
56
+ *
57
+ * // Event example
58
+ * export const onAwaitRegister = defineAwaitRegisterHook(async (hookData, stepData, ctx) => {
59
+ * // hookData.eventName is available for event awaits
60
+ * console.log(`Waiting for event: ${hookData.eventName}`)
61
+ * })
27
62
  */
28
- export declare function defineAwaitRegisterHook(hook: (webhookUrl: string, stepData: any, ctx: AwaitRegisterContext) => Promise<void>): (webhookUrl: string, stepData: any, ctx: AwaitRegisterContext) => Promise<void>;
63
+ export declare function defineAwaitRegisterHook<T extends AwaitType = AwaitType>(hook: (hookData: HookDataForAwaitType<T>, stepData: any, ctx: AwaitRegisterContext<T>) => Promise<void>): (hookData: HookDataForAwaitType<T>, stepData: any, ctx: AwaitRegisterContext<T>) => Promise<void>;
29
64
  /**
30
65
  * Define onAwaitResolve hook with proper types
31
66
  * Called when an await pattern is resolved (external trigger fired)
@@ -38,4 +73,23 @@ export declare function defineAwaitRegisterHook(hook: (webhookUrl: string, stepD
38
73
  * await updateTicketStatus(stepData.ticketId, resolvedData.approved ? 'approved' : 'rejected')
39
74
  * })
40
75
  */
41
- export declare function defineAwaitResolveHook(hook: (resolvedData: any, stepData: any, ctx: AwaitResolveContext) => Promise<void>): (resolvedData: any, stepData: any, ctx: AwaitResolveContext) => Promise<void>;
76
+ export declare function defineAwaitResolveHook<T extends AwaitType = AwaitType>(hook: (resolvedData: any, stepData: any, ctx: AwaitResolveContext<T>) => Promise<void>): (resolvedData: any, stepData: any, ctx: AwaitResolveContext<T>) => Promise<void>;
77
+ /**
78
+ * Define onAwaitTimeout hook with proper types
79
+ * Called when an await pattern times out (timeout exceeded without resolution)
80
+ *
81
+ * @example
82
+ * export const onAwaitTimeout = defineAwaitTimeoutHook(async (stepData, ctx) => {
83
+ * ctx.logger.log('warn', 'Approval request timed out', {
84
+ * action: ctx.timeoutAction
85
+ * })
86
+ *
87
+ * // Send timeout notification
88
+ * await sendEmail({
89
+ * to: stepData.reviewerEmail,
90
+ * subject: 'Approval Request Expired',
91
+ * message: 'The approval request was not completed in time'
92
+ * })
93
+ * })
94
+ */
95
+ export declare function defineAwaitTimeoutHook<T extends AwaitType = AwaitType>(hook: (stepData: any, ctx: AwaitTimeoutContext<T>) => Promise<void>): (stepData: any, ctx: AwaitTimeoutContext<T>) => Promise<void>;
@@ -4,3 +4,6 @@ export function defineAwaitRegisterHook(hook) {
4
4
  export function defineAwaitResolveHook(hook) {
5
5
  return hook;
6
6
  }
7
+ export function defineAwaitTimeoutHook(hook) {
8
+ return hook;
9
+ }
@@ -1,5 +1,17 @@
1
1
  import type { AwaitConfig } from '../../../registry/types.js';
2
2
  import { registerAwaitPattern, resolveAwaitPattern, registerWebhookAwait, resolveWebhookAwait, registerEventAwait, resolveEventAwait, registerScheduleAwait, resolveScheduleAwait, registerTimeAwait, resolveTimeAwait } from './awaitPatterns/index.js';
3
+ /**
4
+ * Get default timeout values for await patterns from runtime config
5
+ * These are configurable via module options and used throughout the system
6
+ * Exported for use by await pattern implementations
7
+ */
8
+ export declare function useAwaitDefaults(): {
9
+ webhookTimeout: any;
10
+ eventTimeout: any;
11
+ timeTimeout: any;
12
+ scheduleTimeout: any;
13
+ timeoutAction: any;
14
+ };
3
15
  /**
4
16
  * Await pattern composable
5
17
  * Provides unified API for managing await patterns in flows
@@ -10,7 +10,34 @@ import {
10
10
  registerTimeAwait,
11
11
  resolveTimeAwait
12
12
  } from "./awaitPatterns/index.js";
13
- import { useStoreAdapter, useStreamTopics, useNventLogger } from "#imports";
13
+ import { useStoreAdapter, useStreamTopics, useNventLogger, useRuntimeConfig } from "#imports";
14
+ export function useAwaitDefaults() {
15
+ try {
16
+ const config = useRuntimeConfig();
17
+ const awaitDefaults = config?.nvent?.flow?.awaitDefaults;
18
+ return {
19
+ webhookTimeout: awaitDefaults?.webhookTimeout ?? 24 * 60 * 60 * 1e3,
20
+ // 24 hours
21
+ eventTimeout: awaitDefaults?.eventTimeout ?? 24 * 60 * 60 * 1e3,
22
+ // 24 hours
23
+ timeTimeout: awaitDefaults?.timeTimeout,
24
+ // undefined by default
25
+ scheduleTimeout: awaitDefaults?.scheduleTimeout,
26
+ // undefined by default
27
+ timeoutAction: awaitDefaults?.timeoutAction ?? "fail"
28
+ };
29
+ } catch {
30
+ return {
31
+ webhookTimeout: 24 * 60 * 60 * 1e3,
32
+ // 24 hours
33
+ eventTimeout: 24 * 60 * 60 * 1e3,
34
+ // 24 hours
35
+ timeTimeout: void 0,
36
+ scheduleTimeout: void 0,
37
+ timeoutAction: "fail"
38
+ };
39
+ }
40
+ }
14
41
  export function useAwait() {
15
42
  const logger = useNventLogger("await");
16
43
  const store = useStoreAdapter();
@@ -62,7 +89,10 @@ export function useAwait() {
62
89
  }
63
90
  const awaitingSteps = entry.metadata.awaitingSteps || {};
64
91
  if (stepName) {
65
- return awaitingSteps[stepName] || null;
92
+ const awaitKeyBefore = `${stepName}:before`;
93
+ const awaitKeyAfter = `${stepName}:after`;
94
+ const awaitState = awaitingSteps[awaitKeyBefore] || awaitingSteps[awaitKeyAfter] || awaitingSteps[stepName];
95
+ return awaitState || null;
66
96
  }
67
97
  return awaitingSteps;
68
98
  },
@@ -78,8 +108,8 @@ export function useAwait() {
78
108
  */
79
109
  async getAllActiveAwaits(flowName) {
80
110
  const activeAwaits = [];
81
- if (!store.indexScan) {
82
- logger.warn("Store does not support indexScan");
111
+ if (!store.index.read) {
112
+ logger.warn("Store does not support index read");
83
113
  return activeAwaits;
84
114
  }
85
115
  if (flowName) {
@@ -14,55 +14,46 @@ export interface FlowStats {
14
14
  };
15
15
  version?: number;
16
16
  }
17
+ export interface StartFlowResult {
18
+ id: string;
19
+ queue: string;
20
+ step: string;
21
+ flowId: string;
22
+ }
23
+ export interface CancelFlowResult {
24
+ success: boolean;
25
+ runId: string;
26
+ flowName: string;
27
+ }
28
+ export interface RunningFlow {
29
+ id: string;
30
+ flowName: string;
31
+ status: string;
32
+ startedAt: string | undefined;
33
+ stepCount: number;
34
+ completedSteps: number;
35
+ }
36
+ export interface FlowComposable {
37
+ startFlow: (flowName: string, payload?: any) => Promise<StartFlowResult>;
38
+ emit: (trigger: string, payload?: any) => Promise<any[]>;
39
+ cancelFlow: (flowName: string, runId: string) => Promise<CancelFlowResult>;
40
+ isRunning: (flowName: string, runId?: string, options?: {
41
+ excludeRunIds?: string[];
42
+ }) => Promise<boolean>;
43
+ getRunningFlows: (flowName: string, options?: {
44
+ excludeRunIds?: string[];
45
+ }) => Promise<RunningFlow[]>;
46
+ getFlowStats: (flowName: string) => Promise<FlowStats | null>;
47
+ getAllFlowStats: (options?: {
48
+ sortBy?: 'registeredAt' | 'lastRunAt' | 'name';
49
+ order?: 'asc' | 'desc';
50
+ limit?: number;
51
+ offset?: number;
52
+ }) => Promise<FlowStats[]>;
53
+ hasFlowStats: (flowName: string) => Promise<boolean>;
54
+ }
17
55
  /**
18
56
  * Flow composable for managing flows and accessing flow statistics
19
57
  * Provides methods for starting, canceling, and querying flows
20
58
  */
21
- export declare function useFlow(): {
22
- /**
23
- * Start a flow with the given payload
24
- */
25
- startFlow(flowName: string, payload?: any): Promise<{
26
- id: any;
27
- queue: any;
28
- step: any;
29
- flowId: `${string}-${string}-${string}-${string}-${string}`;
30
- }>;
31
- /**
32
- * Emit a trigger event for flow coordination
33
- */
34
- emit(trigger: string, payload?: any): Promise<never[]>;
35
- /**
36
- * Cancel a running flow
37
- */
38
- cancelFlow(flowName: string, runId: string): Promise<{
39
- success: boolean;
40
- runId: string;
41
- flowName: string;
42
- }>;
43
- /**
44
- * Check if a flow is currently running
45
- */
46
- isRunning(flowName: string, runId?: string): Promise<any>;
47
- /**
48
- * Get all currently running flows
49
- */
50
- getRunningFlows(flowName: string): Promise<any>;
51
- /**
52
- * Get flow statistics by name
53
- */
54
- getFlowStats(flowName: string): Promise<FlowStats | null>;
55
- /**
56
- * Get all flows with their statistics
57
- */
58
- getAllFlowStats(options?: {
59
- sortBy?: "registeredAt" | "lastRunAt" | "name";
60
- order?: "asc" | "desc";
61
- limit?: number;
62
- offset?: number;
63
- }): Promise<FlowStats[]>;
64
- /**
65
- * Check if a flow has statistics in the index
66
- */
67
- hasFlowStats(flowName: string): Promise<boolean>;
68
- };
59
+ export declare function useFlow(): FlowComposable;
@@ -20,9 +20,20 @@ export function useFlow() {
20
20
  );
21
21
  const opts = entryWorker?.queue?.defaultJobOptions || {};
22
22
  const flowId = randomUUID();
23
- const id = await queueAdapter.enqueue(queueName, { name: flow.entry.step, data: { ...payload, flowId, flowName }, opts });
23
+ const id = await queueAdapter.enqueue(queueName, {
24
+ name: flow.entry.step,
25
+ data: { ...payload, flowId, flowName },
26
+ opts
27
+ });
24
28
  try {
25
- await eventsManager.publishBus({ type: "flow.start", runId: flowId, flowName, data: { input: payload } });
29
+ await eventsManager.publishBus({
30
+ type: "flow.start",
31
+ runId: flowId,
32
+ flowName,
33
+ data: {
34
+ input: payload
35
+ }
36
+ });
26
37
  } catch {
27
38
  }
28
39
  return { id, queue: queueName, step: flow.entry.step, flowId };
@@ -59,15 +70,24 @@ export function useFlow() {
59
70
  */
60
71
  async cancelFlow(flowName, runId) {
61
72
  try {
73
+ let previousStatus;
74
+ try {
75
+ const runIndexKey = StoreSubjects.flowRunIndex(flowName);
76
+ const entry = await store.index.get(runIndexKey, runId);
77
+ previousStatus = entry?.metadata?.status;
78
+ } catch {
79
+ }
62
80
  await eventsManager.publishBus({
63
81
  type: "flow.cancel",
64
82
  runId,
65
83
  flowName,
66
84
  data: {
67
- canceledAt: (/* @__PURE__ */ new Date()).toISOString()
85
+ canceledAt: (/* @__PURE__ */ new Date()).toISOString(),
86
+ previousStatus
87
+ // Include for correct stats counter decrement
68
88
  }
69
89
  });
70
- logger.info("Flow canceled", { flowName, runId });
90
+ logger.info("Flow canceled", { flowName, runId, previousStatus });
71
91
  return { success: true, runId, flowName };
72
92
  } catch (err) {
73
93
  logger.error("Failed to cancel flow", { flowName, runId, error: err });
@@ -75,36 +95,55 @@ export function useFlow() {
75
95
  }
76
96
  },
77
97
  /**
78
- * Check if a flow is currently running
98
+ * Check if a flow is currently running (includes 'running' and 'awaiting' status)
99
+ * @param flowName - The name of the flow to check
100
+ * @param runId - Optional specific run ID to check (if provided, only checks that run)
101
+ * @param options - Optional configuration
102
+ * @param options.excludeRunIds - Exclude these run IDs from the check (useful when called from within a flow)
79
103
  */
80
- async isRunning(flowName, runId) {
104
+ async isRunning(flowName, runId, options) {
81
105
  try {
82
106
  if (!store.index.read) {
83
107
  return false;
84
108
  }
85
109
  const runIndexKey = StoreSubjects.flowRunIndex(flowName);
86
- const entries = await store.index.read(runIndexKey, { limit: 1e3 });
110
+ const activeStatuses = ["running", "awaiting"];
87
111
  if (runId) {
88
- const run = entries.find((e) => e.id === runId);
89
- return run?.metadata?.status === "running";
112
+ const run = await store.index.get(runIndexKey, runId);
113
+ return activeStatuses.includes(run?.metadata?.status);
90
114
  }
91
- return entries.some((e) => e.metadata?.status === "running");
115
+ const entries = await store.index.read(runIndexKey, {
116
+ limit: 1e3,
117
+ filter: { status: activeStatuses }
118
+ });
119
+ const excludeSet = new Set(options?.excludeRunIds || []);
120
+ const matchingRuns = entries.filter((e) => !excludeSet.has(e.id));
121
+ return matchingRuns.length > 0;
92
122
  } catch (err) {
93
123
  logger.error("Error checking flow status:", err);
94
124
  return false;
95
125
  }
96
126
  },
97
127
  /**
98
- * Get all currently running flows
128
+ * Get all currently running flows (includes 'running' and 'awaiting' status)
129
+ * @param flowName - The name of the flow to check
130
+ * @param options - Optional configuration
131
+ * @param options.excludeRunIds - Exclude these run IDs from the results (useful when called from within a flow)
99
132
  */
100
- async getRunningFlows(flowName) {
133
+ async getRunningFlows(flowName, options) {
101
134
  try {
102
135
  if (!store.index.read) {
103
136
  return [];
104
137
  }
105
138
  const runIndexKey = StoreSubjects.flowRunIndex(flowName);
106
- const entries = await store.index.read(runIndexKey, { limit: 1e3 });
107
- return entries.filter((e) => e.metadata?.status === "running").map((e) => ({
139
+ const activeStatuses = ["running", "awaiting"];
140
+ const entries = await store.index.read(runIndexKey, {
141
+ limit: 1e3,
142
+ filter: { status: activeStatuses }
143
+ });
144
+ const excludeSet = new Set(options?.excludeRunIds || []);
145
+ const filteredEntries = entries.filter((e) => !excludeSet.has(e.id));
146
+ return filteredEntries.map((e) => ({
108
147
  id: e.id,
109
148
  flowName,
110
149
  status: e.metadata?.status,
@@ -5,18 +5,24 @@
5
5
  export interface LifecycleHooks {
6
6
  /**
7
7
  * Called when await pattern is registered
8
- * @param webhookUrl - Generated webhook URL (for webhook awaits) or event/schedule info
8
+ * @param hookData - Type-safe data based on await type (webhookUrl, eventName, etc.)
9
9
  * @param stepData - Current step data
10
- * @param ctx - Worker context
10
+ * @param ctx - Worker context with awaitType and awaitConfig
11
11
  */
12
- onAwaitRegister?: (webhookUrl: string, stepData: any, ctx: any) => Promise<void>;
12
+ onAwaitRegister?: (hookData: any, stepData: any, ctx: any) => Promise<void>;
13
13
  /**
14
14
  * Called when await pattern is resolved
15
15
  * @param resolvedData - Data from the trigger that resolved the await
16
16
  * @param stepData - Current step data
17
- * @param ctx - Worker context
17
+ * @param ctx - Worker context with awaitType
18
18
  */
19
19
  onAwaitResolve?: (resolvedData: any, stepData: any, ctx: any) => Promise<void>;
20
+ /**
21
+ * Called when await pattern times out
22
+ * @param stepData - Current step data
23
+ * @param ctx - Worker context with awaitType and timeoutAction
24
+ */
25
+ onAwaitTimeout?: (stepData: any, ctx: any) => Promise<void>;
20
26
  }
21
27
  export declare function useHookRegistry(): {
22
28
  /**
@@ -110,6 +110,12 @@ export function useTrigger() {
110
110
  `Emitting unregistered trigger '${name}'. Consider registering it first with registerTrigger().`
111
111
  );
112
112
  }
113
+ if (trigger && trigger.status !== "active") {
114
+ logger.info(
115
+ `Trigger '${name}' is ${trigger.status}, not emitting. Update status to 'active' to enable firing.`
116
+ );
117
+ return;
118
+ }
113
119
  const threshold = opts?.payloadThreshold || trigger?.config?.payloadThreshold || 10 * 1024;
114
120
  const eventData = await runtime.handleLargePayload(name, data, threshold);
115
121
  logger.debug(`Emitting trigger: ${name}`, {
@@ -208,6 +214,7 @@ export function useTrigger() {
208
214
  }
209
215
  if (metadata.subscriptions) {
210
216
  for (const [flowName, subData] of Object.entries(metadata.subscriptions)) {
217
+ if (!subData) continue;
211
218
  const subscription = {
212
219
  triggerName: entry.id,
213
220
  flowName,
@@ -223,22 +230,6 @@ export function useTrigger() {
223
230
  logger.info(
224
231
  `Loaded ${activeCount} active triggers with ${totalSubscriptions} subscriptions from index`
225
232
  );
226
- } else {
227
- logger.warn("Store does not support indexRead, falling back to doc-based loading");
228
- if (store.list) {
229
- const triggers = await store.list("triggers");
230
- for (const { id, doc } of triggers) {
231
- runtime.addTrigger(id, doc);
232
- }
233
- const subscriptions = await store.list("trigger-subscriptions");
234
- for (const { doc } of subscriptions) {
235
- const sub = doc;
236
- runtime.addSubscription(sub.triggerName, sub.flowName, sub);
237
- }
238
- logger.info(
239
- `Loaded ${triggers.length} triggers and ${subscriptions.length} subscriptions from doc store (legacy)`
240
- );
241
- }
242
233
  }
243
234
  runtime.setInitialized(true);
244
235
  },
@@ -1,6 +1,7 @@
1
1
  import { Scheduler } from "./scheduler.js";
2
2
  import { useRuntimeConfig } from "#imports";
3
- let schedulerInstance = null;
3
+ const SCHEDULER_KEY = "__nvent_scheduler__";
4
+ let schedulerInstance = globalThis[SCHEDULER_KEY] || null;
4
5
  export function createScheduler(store) {
5
6
  const config = useRuntimeConfig();
6
7
  const prefix = config.nvent.store?.prefix || "nvent";
@@ -24,6 +25,7 @@ export async function initializeScheduler(store) {
24
25
  return schedulerInstance;
25
26
  }
26
27
  schedulerInstance = createScheduler(store);
28
+ globalThis[SCHEDULER_KEY] = schedulerInstance;
27
29
  await schedulerInstance.start();
28
30
  return schedulerInstance;
29
31
  }
@@ -31,8 +33,10 @@ export async function shutdownScheduler() {
31
33
  if (schedulerInstance) {
32
34
  await schedulerInstance.stop();
33
35
  schedulerInstance = null;
36
+ globalThis[SCHEDULER_KEY] = null;
34
37
  }
35
38
  }
36
39
  export function resetScheduler() {
37
40
  schedulerInstance = null;
41
+ globalThis[SCHEDULER_KEY] = null;
38
42
  }