nvent 0.4.0

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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/dist/module.d.mts +193 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +974 -0
  6. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +33 -0
  7. package/dist/runtime/app/components/ConfirmDialog.vue +121 -0
  8. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +33 -0
  9. package/dist/runtime/app/components/FlowDiagram.d.vue.ts +64 -0
  10. package/dist/runtime/app/components/FlowDiagram.vue +338 -0
  11. package/dist/runtime/app/components/FlowDiagram.vue.d.ts +64 -0
  12. package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +29 -0
  13. package/dist/runtime/app/components/FlowNodeCard.vue +156 -0
  14. package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +29 -0
  15. package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +9 -0
  16. package/dist/runtime/app/components/FlowRunOverview.vue +291 -0
  17. package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +9 -0
  18. package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +14 -0
  19. package/dist/runtime/app/components/FlowRunStatusBadge.vue +60 -0
  20. package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +14 -0
  21. package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +12 -0
  22. package/dist/runtime/app/components/FlowRunTimeline.vue +127 -0
  23. package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +12 -0
  24. package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +16 -0
  25. package/dist/runtime/app/components/FlowScheduleDialog.vue +226 -0
  26. package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +16 -0
  27. package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +12 -0
  28. package/dist/runtime/app/components/FlowSchedulesList.vue +99 -0
  29. package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +12 -0
  30. package/dist/runtime/app/components/JobScheduling.d.vue.ts +6 -0
  31. package/dist/runtime/app/components/JobScheduling.vue +203 -0
  32. package/dist/runtime/app/components/JobScheduling.vue.d.ts +6 -0
  33. package/dist/runtime/app/components/ListItem.d.vue.ts +23 -0
  34. package/dist/runtime/app/components/ListItem.vue +70 -0
  35. package/dist/runtime/app/components/ListItem.vue.d.ts +23 -0
  36. package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +45 -0
  37. package/dist/runtime/app/components/QueueConfigDetails.vue +412 -0
  38. package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +45 -0
  39. package/dist/runtime/app/components/StatCounter.d.vue.ts +9 -0
  40. package/dist/runtime/app/components/StatCounter.vue +25 -0
  41. package/dist/runtime/app/components/StatCounter.vue.d.ts +9 -0
  42. package/dist/runtime/app/components/TimelineList.d.vue.ts +7 -0
  43. package/dist/runtime/app/components/TimelineList.vue +210 -0
  44. package/dist/runtime/app/components/TimelineList.vue.d.ts +7 -0
  45. package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +46 -0
  46. package/dist/runtime/app/components/nhealth/component-router.vue +26 -0
  47. package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +46 -0
  48. package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +24 -0
  49. package/dist/runtime/app/components/nhealth/component-shell.vue +89 -0
  50. package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +24 -0
  51. package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +14 -0
  52. package/dist/runtime/app/composables/useAnalyzedFlows.js +7 -0
  53. package/dist/runtime/app/composables/useComponentRouter.d.ts +38 -0
  54. package/dist/runtime/app/composables/useComponentRouter.js +240 -0
  55. package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +15 -0
  56. package/dist/runtime/app/composables/useFlowRunTimeline.js +66 -0
  57. package/dist/runtime/app/composables/useFlowRuns.d.ts +11 -0
  58. package/dist/runtime/app/composables/useFlowRuns.js +31 -0
  59. package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +24 -0
  60. package/dist/runtime/app/composables/useFlowRunsInfinite.js +123 -0
  61. package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +8 -0
  62. package/dist/runtime/app/composables/useFlowRunsPolling.js +26 -0
  63. package/dist/runtime/app/composables/useFlowState.d.ts +125 -0
  64. package/dist/runtime/app/composables/useFlowState.js +211 -0
  65. package/dist/runtime/app/composables/useFlowWebSocket.d.ts +27 -0
  66. package/dist/runtime/app/composables/useFlowWebSocket.js +205 -0
  67. package/dist/runtime/app/composables/useFlowsNavigation.d.ts +10 -0
  68. package/dist/runtime/app/composables/useFlowsNavigation.js +57 -0
  69. package/dist/runtime/app/composables/useQueueJobs.d.ts +20 -0
  70. package/dist/runtime/app/composables/useQueueJobs.js +20 -0
  71. package/dist/runtime/app/composables/useQueueUpdates.d.ts +26 -0
  72. package/dist/runtime/app/composables/useQueueUpdates.js +122 -0
  73. package/dist/runtime/app/composables/useQueues.d.ts +43 -0
  74. package/dist/runtime/app/composables/useQueues.js +26 -0
  75. package/dist/runtime/app/composables/useQueuesLive.d.ts +19 -0
  76. package/dist/runtime/app/composables/useQueuesLive.js +143 -0
  77. package/dist/runtime/app/pages/flows/index.d.vue.ts +3 -0
  78. package/dist/runtime/app/pages/flows/index.vue +645 -0
  79. package/dist/runtime/app/pages/flows/index.vue.d.ts +3 -0
  80. package/dist/runtime/app/pages/index.d.vue.ts +3 -0
  81. package/dist/runtime/app/pages/index.vue +34 -0
  82. package/dist/runtime/app/pages/index.vue.d.ts +3 -0
  83. package/dist/runtime/app/pages/queues/index.d.vue.ts +3 -0
  84. package/dist/runtime/app/pages/queues/index.vue +229 -0
  85. package/dist/runtime/app/pages/queues/index.vue.d.ts +3 -0
  86. package/dist/runtime/app/pages/queues/job.d.vue.ts +3 -0
  87. package/dist/runtime/app/pages/queues/job.vue +262 -0
  88. package/dist/runtime/app/pages/queues/job.vue.d.ts +3 -0
  89. package/dist/runtime/app/pages/queues/jobs.d.vue.ts +3 -0
  90. package/dist/runtime/app/pages/queues/jobs.vue +291 -0
  91. package/dist/runtime/app/pages/queues/jobs.vue.d.ts +3 -0
  92. package/dist/runtime/app/plugins/vueflow.client.d.ts +6 -0
  93. package/dist/runtime/app/plugins/vueflow.client.js +15 -0
  94. package/dist/runtime/constants.d.ts +11 -0
  95. package/dist/runtime/constants.js +11 -0
  96. package/dist/runtime/python/get_config.py +64 -0
  97. package/dist/runtime/schema.d.ts +37 -0
  98. package/dist/runtime/schema.js +20 -0
  99. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +10 -0
  100. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +44 -0
  101. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +7 -0
  102. package/dist/runtime/server/api/_flows/[name]/runs.get.js +53 -0
  103. package/dist/runtime/server/api/_flows/[name]/schedule.post.d.ts +2 -0
  104. package/dist/runtime/server/api/_flows/[name]/schedule.post.js +57 -0
  105. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.d.ts +2 -0
  106. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +42 -0
  107. package/dist/runtime/server/api/_flows/[name]/schedules.get.d.ts +2 -0
  108. package/dist/runtime/server/api/_flows/[name]/schedules.get.js +48 -0
  109. package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +2 -0
  110. package/dist/runtime/server/api/_flows/[name]/start.post.js +9 -0
  111. package/dist/runtime/server/api/_flows/index.get.d.ts +6 -0
  112. package/dist/runtime/server/api/_flows/index.get.js +5 -0
  113. package/dist/runtime/server/api/_flows/ws.d.ts +60 -0
  114. package/dist/runtime/server/api/_flows/ws.js +183 -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 +9 -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 +18 -0
  119. package/dist/runtime/server/api/_queues/index.get.d.ts +2 -0
  120. package/dist/runtime/server/api/_queues/index.get.js +63 -0
  121. package/dist/runtime/server/api/_queues/ws.d.ts +48 -0
  122. package/dist/runtime/server/api/_queues/ws.js +200 -0
  123. package/dist/runtime/server/events/adapters/fileAdapter.d.ts +2 -0
  124. package/dist/runtime/server/events/adapters/fileAdapter.js +382 -0
  125. package/dist/runtime/server/events/adapters/memoryAdapter.d.ts +2 -0
  126. package/dist/runtime/server/events/adapters/memoryAdapter.js +171 -0
  127. package/dist/runtime/server/events/adapters/redis/redisAdapter.d.ts +2 -0
  128. package/dist/runtime/server/events/adapters/redis/redisAdapter.js +348 -0
  129. package/dist/runtime/server/events/adapters/redis/redisPubSubGateway.d.ts +29 -0
  130. package/dist/runtime/server/events/adapters/redis/redisPubSubGateway.js +82 -0
  131. package/dist/runtime/server/events/eventBus.d.ts +20 -0
  132. package/dist/runtime/server/events/eventBus.js +35 -0
  133. package/dist/runtime/server/events/eventStoreFactory.d.ts +19 -0
  134. package/dist/runtime/server/events/eventStoreFactory.js +44 -0
  135. package/dist/runtime/server/events/streamNames.d.ts +17 -0
  136. package/dist/runtime/server/events/streamNames.js +17 -0
  137. package/dist/runtime/server/events/types.d.ts +63 -0
  138. package/dist/runtime/server/events/types.js +0 -0
  139. package/dist/runtime/server/events/wiring/flowWiring.d.ts +33 -0
  140. package/dist/runtime/server/events/wiring/flowWiring.js +406 -0
  141. package/dist/runtime/server/events/wiring/registry.d.ts +10 -0
  142. package/dist/runtime/server/events/wiring/registry.js +24 -0
  143. package/dist/runtime/server/plugins/00.event-store.d.ts +13 -0
  144. package/dist/runtime/server/plugins/00.event-store.js +16 -0
  145. package/dist/runtime/server/plugins/00.ws-lifecycle.d.ts +5 -0
  146. package/dist/runtime/server/plugins/00.ws-lifecycle.js +66 -0
  147. package/dist/runtime/server/plugins/flow-management.d.ts +13 -0
  148. package/dist/runtime/server/plugins/flow-management.js +65 -0
  149. package/dist/runtime/server/plugins/queue-management.d.ts +2 -0
  150. package/dist/runtime/server/plugins/queue-management.js +27 -0
  151. package/dist/runtime/server/plugins/state-cleanup.d.ts +11 -0
  152. package/dist/runtime/server/plugins/state-cleanup.js +93 -0
  153. package/dist/runtime/server/plugins/worker-management.d.ts +2 -0
  154. package/dist/runtime/server/plugins/worker-management.js +33 -0
  155. package/dist/runtime/server/queue/adapters/bullmq.d.ts +17 -0
  156. package/dist/runtime/server/queue/adapters/bullmq.js +164 -0
  157. package/dist/runtime/server/queue/queueFactory.d.ts +3 -0
  158. package/dist/runtime/server/queue/queueFactory.js +10 -0
  159. package/dist/runtime/server/queue/types.d.ts +47 -0
  160. package/dist/runtime/server/queue/types.js +0 -0
  161. package/dist/runtime/server/state/adapters/redis.d.ts +2 -0
  162. package/dist/runtime/server/state/adapters/redis.js +42 -0
  163. package/dist/runtime/server/state/stateFactory.d.ts +3 -0
  164. package/dist/runtime/server/state/stateFactory.js +17 -0
  165. package/dist/runtime/server/state/types.d.ts +23 -0
  166. package/dist/runtime/server/state/types.js +0 -0
  167. package/dist/runtime/server/tsconfig.json +3 -0
  168. package/dist/runtime/server/utils/defineQueueConfig.d.ts +154 -0
  169. package/dist/runtime/server/utils/defineQueueConfig.js +2 -0
  170. package/dist/runtime/server/utils/defineQueueWorker.d.ts +10 -0
  171. package/dist/runtime/server/utils/defineQueueWorker.js +17 -0
  172. package/dist/runtime/server/utils/useEventManager.d.ts +15 -0
  173. package/dist/runtime/server/utils/useEventManager.js +26 -0
  174. package/dist/runtime/server/utils/useEventStore.d.ts +20 -0
  175. package/dist/runtime/server/utils/useEventStore.js +119 -0
  176. package/dist/runtime/server/utils/useFlowEngine.d.ts +9 -0
  177. package/dist/runtime/server/utils/useFlowEngine.js +44 -0
  178. package/dist/runtime/server/utils/useLogs.d.ts +41 -0
  179. package/dist/runtime/server/utils/useLogs.js +74 -0
  180. package/dist/runtime/server/utils/useQueue.d.ts +31 -0
  181. package/dist/runtime/server/utils/useQueue.js +24 -0
  182. package/dist/runtime/server/utils/useServerLogger.d.ts +42 -0
  183. package/dist/runtime/server/utils/useServerLogger.js +54 -0
  184. package/dist/runtime/server/utils/wsPeerManager.d.ts +34 -0
  185. package/dist/runtime/server/utils/wsPeerManager.js +23 -0
  186. package/dist/runtime/server/worker/adapter.d.ts +4 -0
  187. package/dist/runtime/server/worker/adapter.js +65 -0
  188. package/dist/runtime/server/worker/runner/node.d.ts +27 -0
  189. package/dist/runtime/server/worker/runner/node.js +196 -0
  190. package/dist/runtime/types.d.ts +132 -0
  191. package/dist/types.d.mts +3 -0
  192. package/package.json +75 -0
@@ -0,0 +1,66 @@
1
+ import {
2
+ defineNitroPlugin,
3
+ getActivePeerCount,
4
+ getActivePeers,
5
+ setShuttingDown,
6
+ clearAllPeers,
7
+ isServerShuttingDown,
8
+ useServerLogger
9
+ } from "#imports";
10
+ const logger = useServerLogger("plugin-ws-lifecycle");
11
+ export default defineNitroPlugin((nitroApp) => {
12
+ logger.info("[ws-lifecycle] WebSocket lifecycle plugin initialized");
13
+ const originalUnhandledRejection = process.listeners("unhandledRejection");
14
+ nitroApp.hooks.hook("close", async () => {
15
+ setShuttingDown(true);
16
+ const peerCount = getActivePeerCount();
17
+ if (peerCount === 0) {
18
+ logger.info("[ws-lifecycle] No active WebSocket connections to close");
19
+ return;
20
+ }
21
+ logger.info(`[ws-lifecycle] Closing ${peerCount} active WebSocket connections...`);
22
+ const closePromises = [];
23
+ const peers = getActivePeers();
24
+ for (const peer of peers) {
25
+ closePromises.push(
26
+ new Promise((resolve) => {
27
+ try {
28
+ peer.send(JSON.stringify({
29
+ type: "server-restart",
30
+ message: "Server is restarting (HMR)"
31
+ }));
32
+ setTimeout(() => {
33
+ try {
34
+ peer.close(1001, "Server restarting");
35
+ } catch {
36
+ }
37
+ resolve();
38
+ }, 200);
39
+ } catch {
40
+ try {
41
+ peer.close(1001, "Server restarting");
42
+ } catch {
43
+ }
44
+ resolve();
45
+ }
46
+ })
47
+ );
48
+ }
49
+ await Promise.all(closePromises);
50
+ clearAllPeers();
51
+ logger.info("[ws-lifecycle] All WebSocket connections closed");
52
+ setTimeout(() => {
53
+ setShuttingDown(false);
54
+ }, 500);
55
+ });
56
+ process.on("unhandledRejection", (reason) => {
57
+ if (isServerShuttingDown() && reason?.code === "ECONNRESET") {
58
+ return;
59
+ }
60
+ for (const handler of originalUnhandledRejection) {
61
+ if (typeof handler === "function") {
62
+ handler(reason, Promise.reject(reason));
63
+ }
64
+ }
65
+ });
66
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Flow Management Plugin
3
+ *
4
+ * Handles NEW flow starts from external triggers:
5
+ * - Processes external triggers to start new flows
6
+ * - Emits flow.start events for new flow runs
7
+ * - Maps flowIds to flowNames
8
+ *
9
+ * Note: Step orchestration (checking dependencies, triggering next steps)
10
+ * is handled by flowWiring.ts which subscribes to emit/step.completed events
11
+ */
12
+ declare const _default: any;
13
+ export default _default;
@@ -0,0 +1,65 @@
1
+ import { defineNitroPlugin, useEventManager, $useQueueRegistry, useQueue, useServerLogger } from "#imports";
2
+ const logger = useServerLogger("plugin-flow-management");
3
+ export default defineNitroPlugin((nitro) => {
4
+ const { onType, publishBus } = useEventManager();
5
+ const registry = $useQueueRegistry();
6
+ const flowIdToName = /* @__PURE__ */ new Map();
7
+ const getFlowNameById = (id) => {
8
+ const cached = flowIdToName.get(id);
9
+ if (cached) return cached;
10
+ try {
11
+ const flows = registry?.flows || {};
12
+ for (const [name, def] of Object.entries(flows)) {
13
+ if (def?.id === id) {
14
+ flowIdToName.set(id, name);
15
+ return name;
16
+ }
17
+ }
18
+ } catch {
19
+ }
20
+ return void 0;
21
+ };
22
+ const unsubscribes = [];
23
+ unsubscribes.push(onType("emit", async (e) => {
24
+ if (e.runId) return;
25
+ const emitName = e.data?.name;
26
+ if (!emitName) return;
27
+ const triggers = Object.keys(registry?.eventIndex || {});
28
+ if (!triggers.includes(emitName)) return;
29
+ const { enqueue } = useQueue();
30
+ const targets = (registry?.eventIndex)[emitName] || [];
31
+ for (const t of targets) {
32
+ const payload = { ...e.data };
33
+ try {
34
+ const id = await enqueue(t.queue, { name: t.step, data: payload });
35
+ try {
36
+ const targetFlowName = getFlowNameById(String(t.flowId)) || String(t.flowId);
37
+ const newRunId = String(id);
38
+ await publishBus({
39
+ type: "flow.start",
40
+ runId: newRunId,
41
+ flowName: targetFlowName,
42
+ data: { input: payload }
43
+ });
44
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
45
+ logger.info("[flow-lifecycle] started new flow:", {
46
+ flowName: targetFlowName,
47
+ runId: newRunId,
48
+ entryStep: t.step
49
+ });
50
+ }
51
+ } catch {
52
+ }
53
+ } catch (err) {
54
+ logger.warn("[flow-lifecycle] failed to start flow:", {
55
+ step: t.step,
56
+ error: err?.message
57
+ });
58
+ }
59
+ }
60
+ }));
61
+ nitro.hooks.hook("close", () => {
62
+ unsubscribes.forEach((fn) => fn());
63
+ flowIdToName.clear();
64
+ });
65
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { defineNitroPlugin, useServerLogger } from "#imports";
2
+ import { BullMQProvider } from "../queue/adapters/bullmq.js";
3
+ import { setQueueProvider, getQueueProvider } from "../queue/queueFactory.js";
4
+ const logger = useServerLogger("plugin-queue-management");
5
+ export default defineNitroPlugin(async (nitroApp) => {
6
+ try {
7
+ const existingProvider = getQueueProvider();
8
+ if (existingProvider) {
9
+ logger.info("[queues plugin] Closing existing queue provider before creating new one...");
10
+ await existingProvider.close();
11
+ await new Promise((resolve) => setTimeout(resolve, 100));
12
+ }
13
+ } catch {
14
+ }
15
+ const provider = new BullMQProvider();
16
+ await provider.init();
17
+ setQueueProvider(provider);
18
+ nitroApp.hooks.hook("close", async () => {
19
+ logger.info("[queues plugin] Closing queue provider...");
20
+ await provider.close();
21
+ });
22
+ return {
23
+ async close() {
24
+ await provider.close();
25
+ }
26
+ };
27
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * State Cleanup Plugin
3
+ *
4
+ * Handles automatic cleanup of flow state based on configuration strategy:
5
+ * - 'never': State persists indefinitely
6
+ * - 'immediate': Cleanup after each step (original behavior, not recommended)
7
+ * - 'on-complete': Cleanup when flow completes (recommended, uses lifecycle tracking)
8
+ * - 'ttl': State expires automatically via TTL (handled by storage provider)
9
+ */
10
+ declare const _default: any;
11
+ export default _default;
@@ -0,0 +1,93 @@
1
+ import { useServerLogger, defineNitroPlugin, useEventManager, useRuntimeConfig } from "#imports";
2
+ import { getStateProvider } from "../state/stateFactory.js";
3
+ const logger = useServerLogger("plugin-state-cleanup");
4
+ export default defineNitroPlugin(() => {
5
+ const rc = useRuntimeConfig();
6
+ const cleanup = rc?.queue?.state?.cleanup || { strategy: "never" };
7
+ if (cleanup.strategy === "never" || cleanup.strategy === "ttl") {
8
+ return;
9
+ }
10
+ const { onType } = useEventManager();
11
+ const unsubs = [];
12
+ if (cleanup.strategy === "on-complete") {
13
+ unsubs.push(onType("flow.completed", async (event) => {
14
+ const flowId = event.runId;
15
+ if (!flowId) return;
16
+ try {
17
+ const sp = getStateProvider();
18
+ const prefix = `flow:${flowId}:`;
19
+ const { keys } = await sp.list(prefix);
20
+ if (keys.length > 0) {
21
+ const rc2 = useRuntimeConfig();
22
+ const ns = rc2?.queue?.state?.namespace || "nq";
23
+ const nsPrefix = `${ns}:`;
24
+ await Promise.all(keys.map((k) => {
25
+ const keyWithoutNs = k.startsWith(nsPrefix) ? k.substring(nsPrefix.length) : k;
26
+ return sp.delete(keyWithoutNs);
27
+ }));
28
+ logger.info(`Cleaned up ${keys.length} state keys after flow completion`, { flowId, keys });
29
+ }
30
+ } catch (error) {
31
+ logger.error("Error cleaning up state after flow completion", { flowId, error });
32
+ }
33
+ }));
34
+ unsubs.push(onType("flow.failed", async (event) => {
35
+ const flowId = event.runId;
36
+ if (!flowId) return;
37
+ try {
38
+ const sp = getStateProvider();
39
+ const prefix = `flow:${flowId}:`;
40
+ const { keys } = await sp.list(prefix);
41
+ if (keys.length > 0) {
42
+ const rc2 = useRuntimeConfig();
43
+ const ns = rc2?.queue?.state?.namespace || "nq";
44
+ const nsPrefix = `${ns}:`;
45
+ await Promise.all(keys.map((k) => {
46
+ const keyWithoutNs = k.startsWith(nsPrefix) ? k.substring(nsPrefix.length) : k;
47
+ return sp.delete(keyWithoutNs);
48
+ }));
49
+ logger.info(`Cleaned up ${keys.length} state keys after flow failure`, { flowId, keys });
50
+ }
51
+ } catch (error) {
52
+ logger.error("Error cleaning up state after flow failure", { flowId, error });
53
+ }
54
+ }));
55
+ logger.debug("Plugin initialized with strategy: on-complete");
56
+ } else if (cleanup.strategy === "immediate") {
57
+ unsubs.push(onType("step.completed", async (event) => {
58
+ const flowId = event.runId;
59
+ if (!flowId) return;
60
+ try {
61
+ const sp = getStateProvider();
62
+ const prefix = `flow:${flowId}:`;
63
+ const { keys } = await sp.list(prefix);
64
+ if (keys.length > 0) {
65
+ const rc2 = useRuntimeConfig();
66
+ const ns = rc2?.queue?.state?.namespace || "nq";
67
+ const nsPrefix = `${ns}:`;
68
+ await Promise.all(keys.map((k) => {
69
+ const keyWithoutNs = k.startsWith(nsPrefix) ? k.substring(nsPrefix.length) : k;
70
+ return sp.delete(keyWithoutNs);
71
+ }));
72
+ logger.info(`Cleaned up ${keys.length} state keys after step completion`, { flowId, keys });
73
+ }
74
+ } catch (error) {
75
+ logger.error("Error cleaning up state after step", { flowId, error });
76
+ }
77
+ }));
78
+ logger.debug("Plugin initialized with strategy: immediate");
79
+ }
80
+ return {
81
+ hooks: {
82
+ close: async () => {
83
+ for (const u of unsubs) {
84
+ try {
85
+ u();
86
+ } catch {
87
+ }
88
+ }
89
+ unsubs.length = 0;
90
+ }
91
+ }
92
+ };
93
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import { defineNitroPlugin, $useWorkerHandlers, $useQueueRegistry } from "#imports";
2
+ import { registerTsWorker, closeAllWorkers } from "../worker/adapter.js";
3
+ export default defineNitroPlugin(async (nitroApp) => {
4
+ nitroApp.hooks.hook("close", async () => {
5
+ await closeAllWorkers();
6
+ });
7
+ try {
8
+ const handlers = $useWorkerHandlers();
9
+ const registry = $useQueueRegistry() || { workers: [] };
10
+ for (const entry of handlers) {
11
+ const { queue, id, handler } = entry;
12
+ const w = registry.workers.find((rw) => rw?.id === id || rw?.queue?.name === queue && rw?.absPath === entry.absPath);
13
+ let jobName;
14
+ if (w?.flow?.step) {
15
+ jobName = Array.isArray(w.flow.step) ? w.flow.step[0] : w.flow.step;
16
+ } else {
17
+ jobName = id.includes("/") ? id.split("/").pop() : id;
18
+ }
19
+ if (typeof handler === "function") {
20
+ const cfg = w && w.worker || {};
21
+ const opts = {};
22
+ if (typeof cfg.concurrency === "number") opts.concurrency = cfg.concurrency;
23
+ if (typeof cfg.lockDurationMs === "number") opts.lockDuration = cfg.lockDurationMs;
24
+ if (typeof cfg.maxStalledCount === "number") opts.maxStalledCount = cfg.maxStalledCount;
25
+ if (typeof cfg.drainDelayMs === "number") opts.drainDelay = cfg.drainDelayMs;
26
+ if (typeof cfg.autorun === "boolean") opts.autorun = cfg.autorun;
27
+ if (w?.queue?.prefix) opts.prefix = w.queue.prefix;
28
+ await registerTsWorker(queue, jobName, handler, opts);
29
+ }
30
+ }
31
+ } catch {
32
+ }
33
+ });
@@ -0,0 +1,17 @@
1
+ import type { QueueProvider, JobInput, Job, JobsQuery, ScheduleOptions, QueueEvent, JobCounts } from '../types.js';
2
+ export declare class BullMQProvider implements QueueProvider {
3
+ private queues;
4
+ init(): Promise<void>;
5
+ private ensureQueue;
6
+ enqueue(queueName: string, job: JobInput): Promise<string>;
7
+ schedule(queueName: string, job: JobInput, opts?: ScheduleOptions): Promise<string>;
8
+ getJob(queueName: string, id: string): Promise<Job | null>;
9
+ getJobs(queueName: string, _q?: JobsQuery): Promise<Job[]>;
10
+ on(queueName: string, event: QueueEvent, cb: (p: any) => void): () => void;
11
+ isPaused(queueName: string): Promise<boolean>;
12
+ getJobCounts(queueName: string): Promise<JobCounts>;
13
+ pause(queueName: string): Promise<void>;
14
+ resume(queueName: string): Promise<void>;
15
+ close(): Promise<void>;
16
+ private toJob;
17
+ }
@@ -0,0 +1,164 @@
1
+ import { Queue, QueueEvents } from "bullmq";
2
+ import defu from "defu";
3
+ import { useServerLogger, useRuntimeConfig, $useQueueRegistry, useEventManager } from "#imports";
4
+ const logger = useServerLogger("bullmq-adapter");
5
+ export class BullMQProvider {
6
+ queues = /* @__PURE__ */ new Map();
7
+ async init() {
8
+ }
9
+ ensureQueue(name) {
10
+ let cached = this.queues.get(name);
11
+ if (cached) return cached;
12
+ const { publishBus } = useEventManager();
13
+ const rc = useRuntimeConfig();
14
+ const connection = rc.queue?.queue?.redis;
15
+ let queueDefaults = void 0;
16
+ let prefix;
17
+ let limiter = void 0;
18
+ try {
19
+ const registry = $useQueueRegistry();
20
+ if (registry && Array.isArray(registry.workers)) {
21
+ const w = registry.workers.find((w2) => w2?.queue?.name === name);
22
+ if (w?.queue) {
23
+ queueDefaults = w.queue.defaultJobOptions;
24
+ prefix = w.queue.prefix;
25
+ limiter = w.queue.limiter;
26
+ }
27
+ }
28
+ } catch {
29
+ }
30
+ const queueOpts = { connection, prefix, defaultJobOptions: queueDefaults };
31
+ if (limiter) {
32
+ queueOpts.limiter = {
33
+ max: limiter.max,
34
+ duration: limiter.duration,
35
+ groupKey: limiter.groupKey
36
+ };
37
+ }
38
+ const queue = new Queue(name, queueOpts);
39
+ const events = new QueueEvents(name, { connection, prefix });
40
+ events.setMaxListeners(50);
41
+ cached = { queue, events, wired: false, defaults: queueDefaults };
42
+ this.queues.set(name, cached);
43
+ const forward = async (kind, payload) => {
44
+ const jobId = payload?.jobId || "unknown";
45
+ let runId = "";
46
+ try {
47
+ if (jobId && jobId !== "unknown") {
48
+ const job = await queue.getJob(jobId);
49
+ if (job?.data?.flowId) {
50
+ runId = job.data.flowId;
51
+ }
52
+ }
53
+ } catch {
54
+ }
55
+ const rec = {
56
+ type: `job.${kind}`,
57
+ runId,
58
+ data: { ...payload, queue: name, jobId }
59
+ };
60
+ await publishBus(rec);
61
+ };
62
+ for (const ev of ["waiting", "active", "progress", "completed", "failed", "delayed"]) {
63
+ events.on(ev, (p) => {
64
+ void forward(ev, p);
65
+ });
66
+ }
67
+ cached.wired = true;
68
+ return cached;
69
+ }
70
+ async enqueue(queueName, job) {
71
+ const { queue, defaults } = this.ensureQueue(queueName);
72
+ const opts = defu(job.opts || {}, defaults || {});
73
+ const bullJob = await queue.add(job.name, job.data, opts);
74
+ return bullJob.id;
75
+ }
76
+ async schedule(queueName, job, opts) {
77
+ const { queue, defaults } = this.ensureQueue(queueName);
78
+ const jobsOpts = defu({ ...job.opts }, defaults || {});
79
+ if (opts?.delay) jobsOpts.delay = opts.delay;
80
+ if (opts?.cron) jobsOpts.repeat = { pattern: opts.cron };
81
+ const bullJob = await queue.add(job.name, job.data, jobsOpts);
82
+ return bullJob.id;
83
+ }
84
+ async getJob(queueName, id) {
85
+ const { queue } = this.ensureQueue(queueName);
86
+ const j = await queue.getJob(id);
87
+ if (!j) return null;
88
+ return await this.toJob(j);
89
+ }
90
+ async getJobs(queueName, _q) {
91
+ const { queue } = this.ensureQueue(queueName);
92
+ const states = _q?.state && _q.state.length > 0 ? _q.state : ["waiting", "active", "completed", "failed", "delayed", "paused"];
93
+ const limit = _q?.limit || 1e3;
94
+ const jobs = await queue.getJobs(states, 0, limit - 1);
95
+ return await Promise.all(jobs.map((j) => this.toJob(j)));
96
+ }
97
+ on(queueName, event, cb) {
98
+ const { events } = this.ensureQueue(queueName);
99
+ const handler = (payload) => cb(payload);
100
+ events.on(event, handler);
101
+ return () => {
102
+ events.off(event, handler);
103
+ };
104
+ }
105
+ async isPaused(queueName) {
106
+ const { queue } = this.ensureQueue(queueName);
107
+ return await queue.isPaused();
108
+ }
109
+ async getJobCounts(queueName) {
110
+ const { queue } = this.ensureQueue(queueName);
111
+ const counts = await queue.getJobCounts("waiting", "active", "completed", "failed", "delayed", "paused");
112
+ return {
113
+ active: counts.active || 0,
114
+ completed: counts.completed || 0,
115
+ failed: counts.failed || 0,
116
+ delayed: counts.delayed || 0,
117
+ waiting: counts.waiting || 0,
118
+ paused: counts.paused || 0
119
+ };
120
+ }
121
+ async pause(queueName) {
122
+ const { queue } = this.ensureQueue(queueName);
123
+ await queue.pause();
124
+ }
125
+ async resume(queueName) {
126
+ const { queue } = this.ensureQueue(queueName);
127
+ await queue.resume();
128
+ }
129
+ async close() {
130
+ const closePromises = [];
131
+ for (const [queueName, { queue, events }] of this.queues.entries()) {
132
+ closePromises.push(
133
+ queue.close().catch((err) => {
134
+ if (err.code !== "EPIPE" && !err.message?.includes("Connection is closed")) {
135
+ logger.warn("Error closing queue", { queueName, error: err });
136
+ }
137
+ })
138
+ );
139
+ closePromises.push(
140
+ events.close().catch((err) => {
141
+ if (err.code !== "EPIPE" && !err.message?.includes("Connection is closed")) {
142
+ logger.warn("Error closing events for queue", { queueName, error: err });
143
+ }
144
+ })
145
+ );
146
+ }
147
+ await Promise.allSettled(closePromises);
148
+ this.queues.clear();
149
+ }
150
+ async toJob(j) {
151
+ const state = await j.getState();
152
+ return {
153
+ id: j.id,
154
+ name: j.name,
155
+ data: j.data,
156
+ returnvalue: j.returnvalue,
157
+ failedReason: j.failedReason,
158
+ state,
159
+ timestamp: j.timestamp,
160
+ processedOn: j.processedOn,
161
+ finishedOn: j.finishedOn
162
+ };
163
+ }
164
+ }
@@ -0,0 +1,3 @@
1
+ import type { QueueProvider } from './types.js';
2
+ export declare function setQueueProvider(p: QueueProvider): void;
3
+ export declare function getQueueProvider(): QueueProvider;
@@ -0,0 +1,10 @@
1
+ let currentProvider = null;
2
+ export function setQueueProvider(p) {
3
+ currentProvider = p;
4
+ }
5
+ export function getQueueProvider() {
6
+ if (!currentProvider) {
7
+ throw new Error("[nuxt-queue] QueueProvider not initialized");
8
+ }
9
+ return currentProvider;
10
+ }
@@ -0,0 +1,47 @@
1
+ export type JobCounts = {
2
+ active: number;
3
+ completed: number;
4
+ failed: number;
5
+ delayed: number;
6
+ waiting: number;
7
+ paused: number;
8
+ };
9
+ export type QueueEvent = 'added' | 'waiting' | 'active' | 'progress' | 'completed' | 'failed' | 'paused' | 'resumed';
10
+ export interface JobInput {
11
+ name: string;
12
+ data: any;
13
+ opts?: Record<string, any>;
14
+ }
15
+ export interface Job {
16
+ id: string;
17
+ name: string;
18
+ data: any;
19
+ state?: 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | 'paused';
20
+ returnvalue?: any;
21
+ failedReason?: string;
22
+ timestamp?: number;
23
+ processedOn?: number;
24
+ finishedOn?: number;
25
+ }
26
+ export interface JobsQuery {
27
+ state?: Array<Job['state']>;
28
+ limit?: number;
29
+ cursor?: string;
30
+ }
31
+ export interface ScheduleOptions {
32
+ delay?: number;
33
+ cron?: string;
34
+ }
35
+ export interface QueueProvider {
36
+ init(): Promise<void>;
37
+ enqueue(queue: string, job: JobInput): Promise<string>;
38
+ schedule(queue: string, job: JobInput, opts?: ScheduleOptions): Promise<string>;
39
+ getJob(queue: string, id: string): Promise<Job | null>;
40
+ getJobs(queue: string, q?: JobsQuery): Promise<Job[]>;
41
+ on(queue: string, event: QueueEvent, cb: (p: any) => void): () => void;
42
+ getJobCounts?(queue: string): Promise<Record<string, number>>;
43
+ isPaused?(queue: string): Promise<boolean>;
44
+ pause(queue: string): Promise<void>;
45
+ resume(queue: string): Promise<void>;
46
+ close(): Promise<void>;
47
+ }
File without changes
@@ -0,0 +1,2 @@
1
+ import type { StateProvider } from '../types.js';
2
+ export declare function createRedisStateProvider(namespace?: string): StateProvider;
@@ -0,0 +1,42 @@
1
+ import { useStorage, useRuntimeConfig } from "#imports";
2
+ function nsKey(ns, key) {
3
+ if (!key) return ns;
4
+ return key.startsWith(ns + ":") ? key : `${ns}:${key}`;
5
+ }
6
+ export function createRedisStateProvider(namespace) {
7
+ const storage = useStorage("redis");
8
+ const ns = namespace || useRuntimeConfig()?.queue?.state?.namespace || "nq";
9
+ return {
10
+ async get(key) {
11
+ return storage.getItem(nsKey(ns, key));
12
+ },
13
+ async set(key, value, opts) {
14
+ await storage.setItem(nsKey(ns, key), value, { ttl: opts?.ttl });
15
+ },
16
+ async delete(key) {
17
+ await storage.removeItem(nsKey(ns, key));
18
+ },
19
+ async list(prefix, opts) {
20
+ const base = nsKey(ns, prefix);
21
+ const keys = await storage.getKeys(base);
22
+ const filtered = keys.filter((k) => k.startsWith(base)).slice(0, opts?.limit || keys.length);
23
+ return { keys: filtered };
24
+ },
25
+ async patch(key, updater, opts) {
26
+ const retries = Math.max(0, opts?.retries ?? 3);
27
+ const k = nsKey(ns, key);
28
+ let lastErr;
29
+ for (let i = 0; i <= retries; i++) {
30
+ try {
31
+ const prev = await storage.getItem(k);
32
+ const next = updater(prev);
33
+ await storage.setItem(k, next);
34
+ return next;
35
+ } catch (err) {
36
+ lastErr = err;
37
+ }
38
+ }
39
+ throw lastErr || new Error("patch failed");
40
+ }
41
+ };
42
+ }
@@ -0,0 +1,3 @@
1
+ import type { StateProvider } from './types.js';
2
+ export declare function getStateProvider(): StateProvider;
3
+ export declare function setStateProvider(p: StateProvider): void;
@@ -0,0 +1,17 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { createRedisStateProvider } from "./adapters/redis.js";
3
+ let currentStateProvider = null;
4
+ export function getStateProvider() {
5
+ if (currentStateProvider) return currentStateProvider;
6
+ const rc = useRuntimeConfig();
7
+ const adapter = rc?.queue?.state?.adapter || "redis";
8
+ const ns = rc?.queue?.state?.namespace || "nq";
9
+ if (adapter === "redis") {
10
+ currentStateProvider = createRedisStateProvider(ns);
11
+ return currentStateProvider;
12
+ }
13
+ throw new Error(`[nuxt-queue] Unsupported StateProvider: ${adapter}`);
14
+ }
15
+ export function setStateProvider(p) {
16
+ currentStateProvider = p;
17
+ }
@@ -0,0 +1,23 @@
1
+ export interface StateProvider {
2
+ get<T = any>(key: string): Promise<T | null>;
3
+ set<T = any>(key: string, value: T, opts?: {
4
+ ttl?: number;
5
+ }): Promise<void>;
6
+ delete(key: string): Promise<void>;
7
+ list(prefix: string, opts?: {
8
+ limit?: number;
9
+ }): Promise<{
10
+ keys: string[];
11
+ }>;
12
+ patch<T = any>(key: string, updater: (prev: T | null) => T, opts?: {
13
+ retries?: number;
14
+ }): Promise<T>;
15
+ }
16
+ export interface StorageLike {
17
+ getItem<T = any>(key: string): Promise<T | null>;
18
+ setItem<T = any>(key: string, value: T, opts?: {
19
+ ttl?: number;
20
+ }): Promise<void>;
21
+ removeItem(key: string): Promise<void>;
22
+ getKeys(base?: string): Promise<string[]>;
23
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../playground/.nuxt/tsconfig.server.json",
3
+ }