@nvent-addon/app 0.4.5 → 0.5.1

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 (153) hide show
  1. package/dist/module.d.mts +19 -1
  2. package/dist/module.mjs +20 -8
  3. package/dist/runtime/app/components/{nhealth/component-router.d.vue.ts → ComponentRouter.d.vue.ts} +1 -5
  4. package/dist/runtime/app/components/{nhealth/component-router.vue.d.ts → ComponentRouter.vue.d.ts} +1 -5
  5. package/dist/runtime/app/components/{nhealth/component-shell.d.vue.ts → ComponentShell.d.vue.ts} +4 -9
  6. package/dist/runtime/app/components/ComponentShell.vue +87 -0
  7. package/dist/runtime/app/components/{nhealth/component-shell.vue.d.ts → ComponentShell.vue.d.ts} +4 -9
  8. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +1 -6
  9. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +1 -6
  10. package/dist/runtime/app/components/ListItem.d.vue.ts +3 -6
  11. package/dist/runtime/app/components/ListItem.vue.d.ts +3 -6
  12. package/dist/runtime/app/components/LiveIndicator.d.vue.ts +7 -0
  13. package/dist/runtime/app/components/LiveIndicator.vue +30 -0
  14. package/dist/runtime/app/components/LiveIndicator.vue.d.ts +7 -0
  15. package/dist/runtime/app/components/{QueueConfigDetails.d.vue.ts → QueueConfiguration.d.vue.ts} +1 -10
  16. package/dist/runtime/app/components/QueueConfiguration.vue +387 -0
  17. package/dist/runtime/app/components/{QueueConfigDetails.vue.d.ts → QueueConfiguration.vue.d.ts} +1 -10
  18. package/dist/runtime/app/components/StatCard.d.vue.ts +9 -0
  19. package/dist/runtime/app/components/StatCard.vue +57 -0
  20. package/dist/runtime/app/components/StatCard.vue.d.ts +9 -0
  21. package/dist/runtime/app/components/TimelineList.vue +67 -0
  22. package/dist/runtime/app/components/flow/AwaitNode.d.vue.ts +18 -0
  23. package/dist/runtime/app/components/flow/AwaitNode.vue +91 -0
  24. package/dist/runtime/app/components/flow/AwaitNode.vue.d.ts +18 -0
  25. package/dist/runtime/app/components/{FlowDiagram.d.vue.ts → flow/Diagram.d.vue.ts} +12 -1
  26. package/dist/runtime/app/components/{FlowDiagram.vue → flow/Diagram.vue} +92 -11
  27. package/dist/runtime/app/components/{FlowDiagram.vue.d.ts → flow/Diagram.vue.d.ts} +12 -1
  28. package/dist/runtime/app/components/{FlowRunOverview.d.vue.ts → flow/RunOverview.d.vue.ts} +3 -0
  29. package/dist/runtime/app/components/{FlowRunOverview.vue → flow/RunOverview.vue} +94 -8
  30. package/dist/runtime/app/components/{FlowRunOverview.vue.d.ts → flow/RunOverview.vue.d.ts} +3 -0
  31. package/dist/runtime/app/components/{FlowRunStatusBadge.d.vue.ts → flow/RunStatusBadge.d.vue.ts} +2 -8
  32. package/dist/runtime/app/components/{FlowRunStatusBadge.vue → flow/RunStatusBadge.vue} +8 -1
  33. package/dist/runtime/app/components/{FlowRunStatusBadge.vue.d.ts → flow/RunStatusBadge.vue.d.ts} +2 -8
  34. package/dist/runtime/app/components/{FlowRunTimeline.vue → flow/RunTimeline.vue} +1 -1
  35. package/dist/runtime/app/components/{FlowStepSelector.d.vue.ts → flow/StepSelector.d.vue.ts} +1 -0
  36. package/dist/runtime/app/components/flow/StepSelector.vue +553 -0
  37. package/dist/runtime/app/components/{FlowStepSelector.vue.d.ts → flow/StepSelector.vue.d.ts} +1 -0
  38. package/dist/runtime/app/components/trigger/BasicInfoCard.d.vue.ts +33 -0
  39. package/dist/runtime/app/components/trigger/BasicInfoCard.vue +168 -0
  40. package/dist/runtime/app/components/trigger/BasicInfoCard.vue.d.ts +33 -0
  41. package/dist/runtime/app/components/{FlowSchedulesList.d.vue.ts → trigger/DangerZone.d.vue.ts} +4 -6
  42. package/dist/runtime/app/components/trigger/DangerZone.vue +46 -0
  43. package/dist/runtime/app/components/{FlowSchedulesList.vue.d.ts → trigger/DangerZone.vue.d.ts} +4 -6
  44. package/dist/runtime/app/components/trigger/EditHeader.d.vue.ts +15 -0
  45. package/dist/runtime/app/components/trigger/EditHeader.vue +55 -0
  46. package/dist/runtime/app/components/trigger/EditHeader.vue.d.ts +15 -0
  47. package/dist/runtime/app/components/trigger/EventConfig.d.vue.ts +24 -0
  48. package/dist/runtime/app/components/trigger/EventConfig.vue +68 -0
  49. package/dist/runtime/app/components/trigger/EventConfig.vue.d.ts +24 -0
  50. package/dist/runtime/app/components/trigger/FlowSubscriptions.d.vue.ts +14 -0
  51. package/dist/runtime/app/components/trigger/FlowSubscriptions.vue +128 -0
  52. package/dist/runtime/app/components/trigger/FlowSubscriptions.vue.d.ts +14 -0
  53. package/dist/runtime/app/components/trigger/ScheduleConfig.d.vue.ts +27 -0
  54. package/dist/runtime/app/components/trigger/ScheduleConfig.vue +375 -0
  55. package/dist/runtime/app/components/trigger/ScheduleConfig.vue.d.ts +27 -0
  56. package/dist/runtime/app/components/{FlowScheduleDialog.d.vue.ts → trigger/StatusConfig.d.vue.ts} +6 -6
  57. package/dist/runtime/app/components/trigger/StatusConfig.vue +78 -0
  58. package/dist/runtime/app/components/{FlowScheduleDialog.vue.d.ts → trigger/StatusConfig.vue.d.ts} +6 -6
  59. package/dist/runtime/app/components/trigger/WebhookConfig.d.vue.ts +30 -0
  60. package/dist/runtime/app/components/trigger/WebhookConfig.vue +97 -0
  61. package/dist/runtime/app/components/trigger/WebhookConfig.vue.d.ts +30 -0
  62. package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +5 -0
  63. package/dist/runtime/app/composables/useAnalyzedFlows.js +15 -1
  64. package/dist/runtime/app/composables/useComponentRouter.d.ts +8 -0
  65. package/dist/runtime/app/composables/useComponentRouter.js +10 -2
  66. package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +1 -1
  67. package/dist/runtime/app/composables/useFlowState.js +65 -0
  68. package/dist/runtime/app/composables/useFlowWebSocket.d.ts +11 -2
  69. package/dist/runtime/app/composables/useFlowWebSocket.js +181 -65
  70. package/dist/runtime/app/composables/useQueueJobs.d.ts +12 -1
  71. package/dist/runtime/app/composables/useQueueJobs.js +13 -7
  72. package/dist/runtime/app/composables/useTrigger.d.ts +137 -0
  73. package/dist/runtime/app/composables/useTrigger.js +116 -0
  74. package/dist/runtime/app/composables/useTriggerWebSocket.d.ts +35 -0
  75. package/dist/runtime/app/composables/useTriggerWebSocket.js +333 -0
  76. package/dist/runtime/app/pages/dashboard.d.vue.ts +3 -0
  77. package/dist/runtime/app/pages/dashboard.vue +738 -0
  78. package/dist/runtime/app/pages/dashboard.vue.d.ts +3 -0
  79. package/dist/runtime/app/pages/flows/[name].d.vue.ts +3 -0
  80. package/dist/runtime/app/pages/flows/[name].vue +680 -0
  81. package/dist/runtime/app/pages/flows/[name].vue.d.ts +3 -0
  82. package/dist/runtime/app/pages/flows/index.vue +321 -620
  83. package/dist/runtime/app/pages/index.vue +39 -9
  84. package/dist/runtime/app/pages/queues/index.vue +202 -194
  85. package/dist/runtime/app/pages/queues/jobs.vue +534 -207
  86. package/dist/runtime/app/pages/settings/scheduler.d.vue.ts +3 -0
  87. package/dist/runtime/app/pages/settings/scheduler.vue +310 -0
  88. package/dist/runtime/app/pages/settings/scheduler.vue.d.ts +3 -0
  89. package/dist/runtime/app/pages/triggers/[name]/edit.d.vue.ts +3 -0
  90. package/dist/runtime/app/pages/triggers/[name]/edit.vue +429 -0
  91. package/dist/runtime/app/pages/triggers/[name]/edit.vue.d.ts +3 -0
  92. package/dist/runtime/app/pages/triggers/[name].d.vue.ts +3 -0
  93. package/dist/runtime/app/pages/triggers/[name].vue +898 -0
  94. package/dist/runtime/app/pages/triggers/[name].vue.d.ts +3 -0
  95. package/dist/runtime/app/pages/triggers/index.d.vue.ts +3 -0
  96. package/dist/runtime/app/pages/triggers/index.vue +528 -0
  97. package/dist/runtime/app/pages/triggers/index.vue.d.ts +3 -0
  98. package/dist/runtime/app/pages/triggers/new.d.vue.ts +3 -0
  99. package/dist/runtime/app/pages/triggers/new.vue +610 -0
  100. package/dist/runtime/app/pages/triggers/new.vue.d.ts +3 -0
  101. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +10 -0
  102. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +49 -0
  103. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.d.ts +2 -0
  104. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +21 -0
  105. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +17 -0
  106. package/dist/runtime/server/api/_flows/[name]/runs.get.js +64 -0
  107. package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +2 -0
  108. package/dist/runtime/server/api/_flows/[name]/start.post.js +9 -0
  109. package/dist/runtime/server/api/_flows/index.get.d.ts +7 -0
  110. package/dist/runtime/server/api/_flows/index.get.js +5 -0
  111. package/dist/runtime/server/api/_flows/recent-runs.get.d.ts +15 -0
  112. package/dist/runtime/server/api/_flows/recent-runs.get.js +67 -0
  113. package/dist/runtime/server/api/_flows/ws.d.ts +80 -0
  114. package/dist/runtime/server/api/_flows/ws.js +309 -0
  115. package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +2 -0
  116. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +14 -0
  117. package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +2 -0
  118. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +39 -0
  119. package/dist/runtime/server/api/_queues/index.get.d.ts +2 -0
  120. package/dist/runtime/server/api/_queues/index.get.js +106 -0
  121. package/dist/runtime/server/api/_queues/ws.d.ts +48 -0
  122. package/dist/runtime/server/api/_queues/ws.js +215 -0
  123. package/dist/runtime/server/api/_scheduler/jobs.get.d.ts +19 -0
  124. package/dist/runtime/server/api/_scheduler/jobs.get.js +36 -0
  125. package/dist/runtime/server/api/_triggers/[name]/events.get.d.ts +6 -0
  126. package/dist/runtime/server/api/_triggers/[name]/events.get.js +43 -0
  127. package/dist/runtime/server/api/_triggers/[name]/index.get.d.ts +6 -0
  128. package/dist/runtime/server/api/_triggers/[name]/index.get.js +76 -0
  129. package/dist/runtime/server/api/_triggers/[name].delete.d.ts +7 -0
  130. package/dist/runtime/server/api/_triggers/[name].delete.js +37 -0
  131. package/dist/runtime/server/api/_triggers/[name].patch.d.ts +7 -0
  132. package/dist/runtime/server/api/_triggers/[name].patch.js +117 -0
  133. package/dist/runtime/server/api/_triggers/index.get.d.ts +6 -0
  134. package/dist/runtime/server/api/_triggers/index.get.js +44 -0
  135. package/dist/runtime/server/api/_triggers/index.post.d.ts +7 -0
  136. package/dist/runtime/server/api/_triggers/index.post.js +124 -0
  137. package/dist/runtime/server/api/_triggers/stats.get.d.ts +6 -0
  138. package/dist/runtime/server/api/_triggers/stats.get.js +41 -0
  139. package/dist/runtime/server/api/_triggers/ws.d.ts +74 -0
  140. package/dist/runtime/server/api/_triggers/ws.js +315 -0
  141. package/dist/runtime/server/tsconfig.json +7 -0
  142. package/package.json +8 -8
  143. package/dist/runtime/app/components/FlowScheduleDialog.vue +0 -226
  144. package/dist/runtime/app/components/FlowSchedulesList.vue +0 -99
  145. package/dist/runtime/app/components/FlowStepSelector.vue +0 -238
  146. package/dist/runtime/app/components/QueueConfigDetails.vue +0 -412
  147. package/dist/runtime/app/components/nhealth/component-shell.vue +0 -89
  148. /package/dist/runtime/app/components/{nhealth/component-router.vue → ComponentRouter.vue} +0 -0
  149. /package/dist/runtime/app/components/{FlowNodeCard.d.vue.ts → flow/NodeCard.d.vue.ts} +0 -0
  150. /package/dist/runtime/app/components/{FlowNodeCard.vue → flow/NodeCard.vue} +0 -0
  151. /package/dist/runtime/app/components/{FlowNodeCard.vue.d.ts → flow/NodeCard.vue.d.ts} +0 -0
  152. /package/dist/runtime/app/components/{FlowRunTimeline.d.vue.ts → flow/RunTimeline.d.vue.ts} +0 -0
  153. /package/dist/runtime/app/components/{FlowRunTimeline.vue.d.ts → flow/RunTimeline.vue.d.ts} +0 -0
@@ -12,3 +12,8 @@ export declare function useAnalyzedFlows(): any;
12
12
  * Use this when you don't need reactivity.
13
13
  */
14
14
  export declare function getAnalyzedFlows(): AnalyzedFlows;
15
+ /**
16
+ * Get analyzed flows with flattened structure for UI consumption.
17
+ * Merges analyzed properties to top level for easier access.
18
+ */
19
+ export declare function useFlattenedAnalyzedFlows(): import("vue").ComputedRef<any>;
@@ -1,8 +1,22 @@
1
1
  import { analyzedFlows } from "#build/analyzed-flows";
2
- import { readonly, ref } from "#imports";
2
+ import { readonly, ref, computed } from "#imports";
3
3
  export function useAnalyzedFlows() {
4
4
  return readonly(ref(analyzedFlows));
5
5
  }
6
6
  export function getAnalyzedFlows() {
7
7
  return analyzedFlows;
8
8
  }
9
+ export function useFlattenedAnalyzedFlows() {
10
+ return computed(() => {
11
+ return analyzedFlows.map((flow) => ({
12
+ id: flow.id,
13
+ entry: flow.entry,
14
+ steps: flow.analyzed?.steps || flow.steps || {},
15
+ levels: flow.analyzed?.levels || [],
16
+ maxLevel: flow.analyzed?.maxLevel || 0,
17
+ stallTimeout: flow.analyzed?.stallTimeout,
18
+ awaitPatterns: flow.analyzed?.awaitPatterns,
19
+ hasAwait: flow.analyzed?.awaitPatterns?.steps?.length > 0 || false
20
+ }));
21
+ });
22
+ }
@@ -1,4 +1,5 @@
1
1
  import { shallowRef } from '#imports';
2
+ import { createHooks } from 'hookable';
2
3
  export type ComponentRouteRecord = {
3
4
  path: string;
4
5
  component: any;
@@ -13,6 +14,12 @@ export interface UseComponentRouterOptions {
13
14
  provideKey?: string;
14
15
  debug?: boolean;
15
16
  }
17
+ type RouterHooks = {
18
+ navigated: (data: {
19
+ path: string;
20
+ params: Record<string, string>;
21
+ }) => void;
22
+ };
16
23
  type RouterContext = {
17
24
  route: ReturnType<typeof shallowRef<{
18
25
  path: string;
@@ -25,6 +32,7 @@ type RouterContext = {
25
32
  makeHref: (patternOrPath: string, params?: Record<string, any>) => string;
26
33
  pushTo: (patternOrPath: string, params?: Record<string, any>) => Promise<void>;
27
34
  replaceTo: (patternOrPath: string, params?: Record<string, any>) => Promise<void>;
35
+ hooks: ReturnType<typeof createHooks<RouterHooks>>;
28
36
  };
29
37
  export declare function useComponentRouter(): RouterContext & {
30
38
  component?: any;
@@ -8,6 +8,7 @@ import {
8
8
  watch,
9
9
  defineAsyncComponent
10
10
  } from "#imports";
11
+ import { createHooks } from "hookable";
11
12
  export function useComponentRouter(opts) {
12
13
  const CTX_KEY = "component-router";
13
14
  if (!opts || !("routes" in opts) || !opts.routes) {
@@ -23,6 +24,7 @@ export function useComponentRouter(opts) {
23
24
  );
24
25
  const noopMake = (p, _params) => p;
25
26
  const noopHref = (p, _params) => p;
27
+ const emptyHooks = createHooks();
26
28
  return {
27
29
  route: emptyRoute,
28
30
  push: warnNav,
@@ -31,6 +33,7 @@ export function useComponentRouter(opts) {
31
33
  makeHref: noopHref,
32
34
  pushTo: async (p, _params) => warnNav(p),
33
35
  replaceTo: async (p, _params) => warnNav(p),
36
+ hooks: emptyHooks,
34
37
  component: void 0
35
38
  };
36
39
  }
@@ -55,6 +58,7 @@ export function useComponentRouter(opts) {
55
58
  params: {},
56
59
  query: {}
57
60
  });
61
+ const hooks = createHooks();
58
62
  const dbg = (...args) => {
59
63
  if (props.debug) console.info("[component-router]", ...args);
60
64
  };
@@ -119,6 +123,8 @@ export function useComponentRouter(opts) {
119
123
  // Only use query params from the current path, not from nuxtRoute
120
124
  };
121
125
  dbg("current", route.value);
126
+ dbg("calling navigated hook with", { path: pathOnly || path, params: m.params });
127
+ hooks.callHook("navigated", { path: pathOnly || path, params: m.params });
122
128
  }
123
129
  function readFromHost() {
124
130
  if (props.mode === "query") {
@@ -137,6 +143,8 @@ export function useComponentRouter(opts) {
137
143
  }
138
144
  async function writeToHost(path, replace2 = false) {
139
145
  dbg("writeToHost", path, { replace: replace2, mode: props.mode });
146
+ const prevPath = route.value.path;
147
+ dbg("current path before write:", prevPath, "new path:", path);
140
148
  if (props.mode === "query") {
141
149
  const [pathOnly, queryString] = path.split("?");
142
150
  const pathQuery = {};
@@ -199,7 +207,7 @@ export function useComponentRouter(opts) {
199
207
  const path = makePath(patternOrPath, params);
200
208
  await replace(path);
201
209
  }
202
- const exposed = { push, replace, route, makePath, makeHref, pushTo, replaceTo };
210
+ const exposed = { push, replace, route, makePath, makeHref, pushTo, replaceTo, hooks };
203
211
  provide(props.provideKey, exposed);
204
212
  const initialPath = readFromHost() || props.initial || props.routes[0]?.path || "/";
205
213
  dbg("init", { initialPath });
@@ -215,7 +223,7 @@ export function useComponentRouter(opts) {
215
223
  if (path) setCurrent(path);
216
224
  }
217
225
  );
218
- return { component, route, push, replace, makePath, makeHref, pushTo, replaceTo };
226
+ return { component, route, push, replace, makePath, makeHref, pushTo, replaceTo, hooks };
219
227
  }
220
228
  export default useComponentRouter;
221
229
  export function createComponentRoutes(glob, options) {
@@ -6,7 +6,7 @@ export declare function useFlowRunsInfinite(flowId: Ref<string>): {
6
6
  items: import("vue").ComputedRef<{
7
7
  id: string;
8
8
  flowName: string;
9
- status: "running" | "completed" | "failed" | "canceled" | "stalled" | "unknown";
9
+ status: "running" | "completed" | "failed" | "canceled" | "stalled" | "awaiting" | "unknown";
10
10
  createdAt: string;
11
11
  startedAt?: string | undefined;
12
12
  completedAt?: string | undefined;
@@ -16,6 +16,13 @@ export function reduceFlowState(events) {
16
16
  if (e.flowName) state.meta = { ...state.meta, flowName: e.flowName };
17
17
  if (e.data?.flowName) state.meta = { ...state.meta, flowName: e.data.flowName };
18
18
  if (e.data?.input) state.meta = { ...state.meta, input: e.data.input };
19
+ if (e.data?.trigger) {
20
+ state.meta = {
21
+ ...state.meta,
22
+ triggerName: e.data.trigger.name,
23
+ triggerType: e.data.trigger.type || "manual"
24
+ };
25
+ }
19
26
  break;
20
27
  case "flow.complete":
21
28
  case "flow.completed":
@@ -119,6 +126,64 @@ export function reduceFlowState(events) {
119
126
  state.steps[stepKey].completedAt = e.ts;
120
127
  break;
121
128
  }
129
+ case "await.registered": {
130
+ if (!stepKey) break;
131
+ const awaitKeyAfter = `${stepKey}:await-after`;
132
+ const awaitKeyBefore = `${stepKey}:await-before`;
133
+ if (!state.steps[awaitKeyAfter]) {
134
+ state.steps[awaitKeyAfter] = { status: "waiting", attempt: 1 };
135
+ }
136
+ state.steps[awaitKeyAfter].status = "waiting";
137
+ state.steps[awaitKeyAfter].awaitType = e.data?.awaitType;
138
+ state.steps[awaitKeyAfter].awaitData = e.data;
139
+ if (!state.steps[awaitKeyBefore]) {
140
+ state.steps[awaitKeyBefore] = { status: "waiting", attempt: 1 };
141
+ }
142
+ state.steps[awaitKeyBefore].status = "waiting";
143
+ state.steps[awaitKeyBefore].awaitType = e.data?.awaitType;
144
+ state.steps[awaitKeyBefore].awaitData = e.data;
145
+ break;
146
+ }
147
+ case "await.resolved": {
148
+ if (!stepKey) break;
149
+ const awaitKeyAfter = `${stepKey}:await-after`;
150
+ const awaitKeyBefore = `${stepKey}:await-before`;
151
+ if (!state.steps[awaitKeyAfter]) {
152
+ state.steps[awaitKeyAfter] = { status: "completed", attempt: 1 };
153
+ }
154
+ state.steps[awaitKeyAfter].status = "completed";
155
+ state.steps[awaitKeyAfter].completedAt = e.ts;
156
+ state.steps[awaitKeyAfter].awaitType = e.data?.awaitType;
157
+ if (e.data?.triggerData) state.steps[awaitKeyAfter].result = e.data.triggerData;
158
+ if (!state.steps[awaitKeyBefore]) {
159
+ state.steps[awaitKeyBefore] = { status: "completed", attempt: 1 };
160
+ }
161
+ state.steps[awaitKeyBefore].status = "completed";
162
+ state.steps[awaitKeyBefore].completedAt = e.ts;
163
+ state.steps[awaitKeyBefore].awaitType = e.data?.awaitType;
164
+ if (e.data?.triggerData) state.steps[awaitKeyBefore].result = e.data.triggerData;
165
+ break;
166
+ }
167
+ case "await.timeout": {
168
+ if (!stepKey) break;
169
+ const awaitKeyAfter = `${stepKey}:await-after`;
170
+ const awaitKeyBefore = `${stepKey}:await-before`;
171
+ if (!state.steps[awaitKeyAfter]) {
172
+ state.steps[awaitKeyAfter] = { status: "timeout", attempt: 1 };
173
+ }
174
+ state.steps[awaitKeyAfter].status = "timeout";
175
+ state.steps[awaitKeyAfter].error = `Await timeout`;
176
+ state.steps[awaitKeyAfter].completedAt = e.ts;
177
+ state.steps[awaitKeyAfter].awaitType = e.data?.awaitType;
178
+ if (!state.steps[awaitKeyBefore]) {
179
+ state.steps[awaitKeyBefore] = { status: "timeout", attempt: 1 };
180
+ }
181
+ state.steps[awaitKeyBefore].status = "timeout";
182
+ state.steps[awaitKeyBefore].error = `Await timeout`;
183
+ state.steps[awaitKeyBefore].completedAt = e.ts;
184
+ state.steps[awaitKeyBefore].awaitType = e.data?.awaitType;
185
+ break;
186
+ }
122
187
  case "runner.log":
123
188
  case "log": {
124
189
  state.logs.push({
@@ -13,14 +13,23 @@ export interface FlowSubscription {
13
13
  onEvent: (event: any) => void;
14
14
  onHistory?: (events: any[]) => void;
15
15
  }
16
+ export interface StatsSubscription {
17
+ onInitial?: (data: any) => void;
18
+ onUpdate?: (data: any) => void;
19
+ }
16
20
  /**
17
- * WebSocket composable for flow run events
18
- * Replaces the SSE-based useEventSSE with a more reliable WebSocket implementation
21
+ * WebSocket composable for flow run events and flow stats
22
+ * Architecture: Client (this) WebSocket Server Handler StreamAdapter.subscribe(StreamTopics.flowEvents)
23
+ * Supports subscribing to specific flow runs and global flow statistics
24
+ * Uses a singleton connection shared across all instances
19
25
  */
20
26
  export declare function useFlowWebSocket(): {
21
27
  subscribe: (subscription: FlowSubscription, opts?: UseFlowWebSocketOptions) => void;
22
28
  unsubscribe: () => void;
29
+ subscribeStats: (subscription: StatsSubscription, opts?: UseFlowWebSocketOptions) => void;
30
+ unsubscribeStats: () => void;
23
31
  stop: () => void;
32
+ forceClose: () => void;
24
33
  connected: import("vue").Ref<boolean, boolean>;
25
34
  reconnecting: import("vue").Ref<boolean, boolean>;
26
35
  };
@@ -1,14 +1,23 @@
1
1
  import { ref, onBeforeUnmount } from "#imports";
2
+ let sharedWs = null;
3
+ let sharedConnected = false;
4
+ let sharedReconnecting = false;
5
+ let retry = 0;
6
+ let reconnectTimer = null;
7
+ let currentOptions;
8
+ let currentSubscription = null;
9
+ let currentStatsSubscription = null;
10
+ let pendingStatsSubscription = false;
11
+ let isStatsSubscribed = false;
12
+ let statsCache = [];
13
+ let pingInterval = null;
14
+ let isServerRestarting = false;
15
+ let refCount = 0;
2
16
  export function useFlowWebSocket() {
3
- const ws = ref(null);
4
- const connected = ref(false);
5
- const reconnecting = ref(false);
6
- let retry = 0;
7
- let reconnectTimer = null;
8
- let currentOptions;
9
- let currentSubscription = null;
10
- let pingInterval = null;
11
- let isServerRestarting = false;
17
+ const ws = ref(sharedWs);
18
+ const connected = ref(sharedConnected);
19
+ const reconnecting = ref(sharedReconnecting);
20
+ refCount++;
12
21
  const computeDelay = (opts) => {
13
22
  const base = Math.max(100, opts?.baseDelayMs ?? 1e3);
14
23
  const max = Math.max(base, opts?.maxDelayMs ?? 1e4);
@@ -32,34 +41,55 @@ export function useFlowWebSocket() {
32
41
  pingInterval = null;
33
42
  }
34
43
  };
44
+ const updateRefs = () => {
45
+ ws.value = sharedWs;
46
+ connected.value = sharedConnected;
47
+ reconnecting.value = sharedReconnecting;
48
+ };
35
49
  const send = (data) => {
36
- if (ws.value && ws.value.readyState === WebSocket.OPEN) {
37
- ws.value.send(JSON.stringify(data));
50
+ if (sharedWs && sharedWs.readyState === WebSocket.OPEN) {
51
+ try {
52
+ sharedWs.send(JSON.stringify(data));
53
+ return true;
54
+ } catch (err) {
55
+ console.error("[useFlowWebSocket] Error sending message:", err);
56
+ return false;
57
+ }
38
58
  }
59
+ return false;
39
60
  };
40
61
  const startPingInterval = () => {
41
62
  clearTimers();
42
63
  pingInterval = setInterval(() => {
43
- if (ws.value && ws.value.readyState === WebSocket.OPEN) {
64
+ if (sharedWs && sharedWs.readyState === WebSocket.OPEN) {
44
65
  send({ type: "ping" });
45
66
  }
46
67
  }, 3e4);
47
68
  };
48
69
  const stop = () => {
70
+ refCount = Math.max(0, refCount - 1);
71
+ };
72
+ const forceClose = () => {
49
73
  clearTimers();
50
74
  isServerRestarting = false;
51
75
  try {
52
- if (ws.value) {
53
- ws.value.close(1e3, "Client closing");
76
+ if (sharedWs) {
77
+ sharedWs.close(1e3, "Client closing");
54
78
  }
55
79
  } catch (err) {
56
80
  console.warn("[useFlowWebSocket] Error closing WebSocket:", err);
57
81
  }
58
- ws.value = null;
59
- connected.value = false;
60
- reconnecting.value = false;
82
+ sharedWs = null;
83
+ sharedConnected = false;
84
+ sharedReconnecting = false;
85
+ updateRefs();
61
86
  retry = 0;
87
+ refCount = 0;
62
88
  currentSubscription = null;
89
+ currentStatsSubscription = null;
90
+ isStatsSubscribed = false;
91
+ pendingStatsSubscription = false;
92
+ statsCache = [];
63
93
  };
64
94
  const attemptReconnect = () => {
65
95
  if (!currentOptions?.autoReconnect) {
@@ -73,29 +103,38 @@ export function useFlowWebSocket() {
73
103
  return;
74
104
  }
75
105
  retry++;
76
- reconnecting.value = true;
106
+ sharedReconnecting = true;
107
+ updateRefs();
77
108
  const baseDelay = isServerRestarting ? 2e3 : computeDelay(currentOptions);
78
109
  const delay = baseDelay;
79
- console.log(`[useFlowWebSocket] Will attempt reconnection in ${delay}ms (attempt ${retry}/${max})${isServerRestarting ? " [server restart]" : ""}`);
80
110
  clearTimers();
81
111
  reconnectTimer = setTimeout(() => {
82
- if (currentSubscription) {
83
- innerSubscribe(currentSubscription, currentOptions);
112
+ if (currentSubscription || currentStatsSubscription) {
113
+ connect(currentOptions);
84
114
  }
85
115
  }, delay);
86
116
  };
87
- const setupWebSocket = (socket, subscription, opts) => {
117
+ const setupWebSocket = (socket, opts) => {
88
118
  socket.onopen = () => {
89
119
  console.log("[useFlowWebSocket] Connected");
90
- connected.value = true;
91
- reconnecting.value = false;
120
+ sharedConnected = true;
121
+ sharedReconnecting = false;
122
+ updateRefs();
92
123
  retry = 0;
93
124
  startPingInterval();
94
- send({
95
- type: "subscribe",
96
- flowName: subscription.flowName,
97
- runId: subscription.runId
98
- });
125
+ if (currentSubscription) {
126
+ send({
127
+ type: "subscribe",
128
+ flowName: currentSubscription.flowName,
129
+ runId: currentSubscription.runId
130
+ });
131
+ }
132
+ if (currentStatsSubscription || pendingStatsSubscription) {
133
+ send({
134
+ type: "subscribe.stats"
135
+ });
136
+ pendingStatsSubscription = false;
137
+ }
99
138
  opts?.onOpen?.();
100
139
  };
101
140
  socket.onmessage = (event) => {
@@ -103,30 +142,64 @@ export function useFlowWebSocket() {
103
142
  const data = JSON.parse(event.data);
104
143
  switch (data.type) {
105
144
  case "connected":
106
- console.log("[useFlowWebSocket] Server acknowledged connection");
107
145
  break;
108
146
  case "subscribed":
109
- console.log("[useFlowWebSocket] Subscribed to flow:", data.flowName, data.runId);
110
147
  break;
111
148
  case "unsubscribed":
112
- console.log("[useFlowWebSocket] Unsubscribed from flow:", data.flowName, data.runId);
113
149
  break;
114
- case "history":
115
- if (subscription.onHistory) {
116
- subscription.onHistory(data.events);
150
+ case "stats.subscribed":
151
+ isStatsSubscribed = true;
152
+ break;
153
+ case "stats.unsubscribed":
154
+ isStatsSubscribed = false;
155
+ break;
156
+ case "flow.stats.initial": {
157
+ const existingIndex = statsCache.findIndex((s) => s.id === data.data.id);
158
+ if (existingIndex >= 0) {
159
+ statsCache[existingIndex] = data.data;
160
+ } else {
161
+ statsCache.push(data.data);
162
+ }
163
+ if (currentStatsSubscription?.onInitial) {
164
+ currentStatsSubscription.onInitial(data.data);
165
+ } else {
166
+ console.warn("[useFlowWebSocket] No onInitial handler for stats:", data.data);
167
+ }
168
+ break;
169
+ }
170
+ case "flow.stats.update": {
171
+ const cacheIndex = statsCache.findIndex((s) => s.id === data.data.id);
172
+ if (cacheIndex >= 0) {
173
+ statsCache[cacheIndex] = data.data;
174
+ } else {
175
+ statsCache.push(data.data);
176
+ }
177
+ if (currentStatsSubscription?.onUpdate) {
178
+ currentStatsSubscription.onUpdate(data.data);
117
179
  } else {
118
- for (const eventData of data.events) {
119
- subscription.onEvent(eventData);
180
+ console.warn("[useFlowWebSocket] No onUpdate handler for stats:", data.data);
181
+ }
182
+ break;
183
+ }
184
+ case "history":
185
+ if (currentSubscription) {
186
+ if (currentSubscription.onHistory) {
187
+ currentSubscription.onHistory(data.events);
188
+ } else {
189
+ for (const eventData of data.events) {
190
+ currentSubscription.onEvent(eventData);
191
+ }
120
192
  }
121
193
  }
122
194
  break;
123
195
  case "event":
124
- subscription.onEvent(data.event);
196
+ if (currentSubscription) {
197
+ currentSubscription.onEvent(data.event);
198
+ }
125
199
  break;
126
200
  case "pong":
127
201
  break;
128
202
  case "server-restart":
129
- console.log("[useFlowWebSocket] Server is restarting (HMR)");
130
203
  isServerRestarting = true;
131
204
  break;
132
205
  case "error":
@@ -146,51 +219,40 @@ export function useFlowWebSocket() {
146
219
  };
147
220
  socket.onclose = (event) => {
148
221
  console.log("[useFlowWebSocket] Connection closed:", event.code, event.reason);
149
- connected.value = false;
222
+ sharedConnected = false;
223
+ updateRefs();
150
224
  clearTimers();
151
225
  opts?.onClose?.(event);
152
226
  const shouldReconnect = event.code !== 1e3 && opts?.autoReconnect;
153
227
  if (shouldReconnect) {
154
- console.log("[useFlowWebSocket] Will attempt reconnection (code:", event.code, ")");
155
228
  attemptReconnect();
156
229
  } else {
157
230
  isServerRestarting = false;
158
231
  }
159
232
  };
160
233
  };
161
- const innerSubscribe = (subscription, opts) => {
234
+ const connect = (opts) => {
162
235
  if (import.meta.server || typeof WebSocket === "undefined") {
163
236
  console.warn("[useFlowWebSocket] WebSocket not available (SSR context)");
164
237
  return;
165
238
  }
166
- if (ws.value && ws.value.readyState === WebSocket.OPEN) {
167
- console.log("[useFlowWebSocket] Reusing connection, switching subscription");
168
- if (currentSubscription) {
169
- send({
170
- type: "unsubscribe",
171
- flowName: currentSubscription.flowName,
172
- runId: currentSubscription.runId
173
- });
174
- }
175
- currentSubscription = subscription;
176
- send({
177
- type: "subscribe",
178
- flowName: subscription.flowName,
179
- runId: subscription.runId
180
- });
239
+ if (sharedWs && (sharedWs.readyState === WebSocket.OPEN || sharedWs.readyState === WebSocket.CONNECTING)) {
181
240
  return;
182
241
  }
183
- if (ws.value) {
184
- stop();
242
+ if (sharedWs) {
243
+ try {
244
+ sharedWs.close();
245
+ } catch {
246
+ }
185
247
  }
186
248
  currentOptions = opts;
187
- currentSubscription = subscription;
188
249
  try {
189
250
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
190
251
  const wsUrl = `${protocol}//${window.location.host}/api/_flows/ws`;
191
252
  const socket = new WebSocket(wsUrl);
192
- ws.value = socket;
193
- setupWebSocket(socket, subscription, opts);
253
+ sharedWs = socket;
254
+ updateRefs();
255
+ setupWebSocket(socket, opts);
194
256
  } catch (err) {
195
257
  console.error("[useFlowWebSocket] Error creating WebSocket:", err);
196
258
  opts?.onError?.(err);
@@ -198,10 +260,26 @@ export function useFlowWebSocket() {
198
260
  }
199
261
  };
200
262
  const subscribe = (subscription, opts) => {
201
- innerSubscribe(subscription, opts);
263
+ if (currentSubscription && sharedWs && sharedWs.readyState === WebSocket.OPEN) {
264
+ send({
265
+ type: "unsubscribe",
266
+ flowName: currentSubscription.flowName,
267
+ runId: currentSubscription.runId
268
+ });
269
+ }
270
+ currentSubscription = subscription;
271
+ if (sharedWs && sharedWs.readyState === WebSocket.OPEN) {
272
+ send({
273
+ type: "subscribe",
274
+ flowName: subscription.flowName,
275
+ runId: subscription.runId
276
+ });
277
+ return;
278
+ }
279
+ connect(opts);
202
280
  };
203
281
  const unsubscribe = () => {
204
- if (currentSubscription && ws.value && ws.value.readyState === WebSocket.OPEN) {
282
+ if (currentSubscription && sharedWs && sharedWs.readyState === WebSocket.OPEN) {
205
283
  send({
206
284
  type: "unsubscribe",
207
285
  flowName: currentSubscription.flowName,
@@ -210,11 +288,49 @@ export function useFlowWebSocket() {
210
288
  }
211
289
  currentSubscription = null;
212
290
  };
213
- onBeforeUnmount(() => stop());
291
+ const subscribeStats = (subscription, opts) => {
292
+ currentStatsSubscription = subscription;
293
+ if (sharedWs && sharedWs.readyState === WebSocket.OPEN) {
294
+ if (isStatsSubscribed) {
295
+ if (subscription.onInitial && statsCache.length > 0) {
296
+ for (const cachedStat of statsCache) {
297
+ subscription.onInitial(cachedStat);
298
+ }
299
+ }
300
+ return;
301
+ }
302
+ send({
303
+ type: "subscribe.stats"
304
+ });
305
+ return;
306
+ }
307
+ if (sharedWs && sharedWs.readyState === WebSocket.CONNECTING) {
308
+ pendingStatsSubscription = true;
309
+ return;
310
+ }
311
+ pendingStatsSubscription = true;
312
+ connect(opts);
313
+ };
314
+ const unsubscribeStats = () => {
315
+ if (currentStatsSubscription && sharedWs && sharedWs.readyState === WebSocket.OPEN) {
316
+ send({
317
+ type: "unsubscribe.stats"
318
+ });
319
+ }
320
+ currentStatsSubscription = null;
321
+ isStatsSubscribed = false;
322
+ };
323
+ onBeforeUnmount(() => {
324
+ stop();
325
+ });
214
326
  return {
215
327
  subscribe,
216
328
  unsubscribe,
329
+ subscribeStats,
330
+ unsubscribeStats,
217
331
  stop,
332
+ forceClose,
333
+ // Exposed for debugging/manual cleanup
218
334
  connected,
219
335
  reconnecting
220
336
  };
@@ -13,12 +13,23 @@ export interface Job {
13
13
  }
14
14
  export interface JobsResponse {
15
15
  jobs: Job[];
16
+ count: number;
17
+ total: number;
18
+ hasMore: boolean;
16
19
  }
17
20
  /**
18
21
  * Composable for fetching jobs for a queue
19
22
  * Client-only to avoid hydration mismatches
20
23
  */
21
- export declare function useQueueJobs(queueName: Ref<string>, state?: Ref<string | null>): {
24
+ export declare function useQueueJobs(queueName: Ref<string>, options?: Ref<{
25
+ state?: string | null;
26
+ limit?: number;
27
+ offset?: number;
28
+ }> | {
29
+ state?: string | null;
30
+ limit?: number;
31
+ offset?: number;
32
+ }): {
22
33
  data: Ref<JobsResponse | null | undefined>;
23
34
  refresh: () => Promise<void>;
24
35
  status: Ref<'idle' | 'pending' | 'success' | 'error'>;
@@ -1,20 +1,26 @@
1
- import { ref, useFetch } from "#imports";
2
- export function useQueueJobs(queueName, state = ref(null)) {
1
+ import { ref, useFetch, isRef } from "#imports";
2
+ export function useQueueJobs(queueName, options) {
3
+ const opts = isRef(options) ? options : ref(options || {});
3
4
  return useFetch(
4
5
  () => {
5
6
  const params = new URLSearchParams();
6
- if (state.value) {
7
- params.append("state", state.value);
7
+ if (opts.value.state) {
8
+ params.append("state", opts.value.state);
9
+ }
10
+ if (opts.value.limit) {
11
+ params.append("limit", opts.value.limit.toString());
12
+ }
13
+ if (opts.value.offset) {
14
+ params.append("offset", opts.value.offset.toString());
8
15
  }
9
16
  const queryString = params.toString();
10
17
  return `/api/_queues/${encodeURIComponent(queueName.value)}/job${queryString ? `?${queryString}` : ""}`;
11
18
  },
12
19
  {
13
- key: () => `queue-jobs-${queueName.value}-${state.value || "all"}`,
14
- watch: [queueName, state],
20
+ key: () => `queue-jobs-${queueName.value}-${opts.value.state || "all"}-${opts.value.offset || 0}`,
21
+ watch: false,
15
22
  immediate: true,
16
23
  server: false
17
- // Client-only
18
24
  }
19
25
  );
20
26
  }