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,171 @@
1
+ const GLOBAL_KEY = "__nuxt_queue_memory_adapter__";
2
+ function getStore() {
3
+ if (!globalThis[GLOBAL_KEY]) {
4
+ globalThis[GLOBAL_KEY] = {
5
+ events: /* @__PURE__ */ new Map(),
6
+ listeners: /* @__PURE__ */ new Map(),
7
+ indices: /* @__PURE__ */ new Map()
8
+ };
9
+ }
10
+ return globalThis[GLOBAL_KEY];
11
+ }
12
+ export function createMemoryAdapter() {
13
+ const { events, listeners, indices } = getStore();
14
+ return {
15
+ async append(stream, e) {
16
+ const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
17
+ const rec = { ...e, id, ts: (/* @__PURE__ */ new Date()).toISOString() };
18
+ const list = events.get(stream) || [];
19
+ list.push(rec);
20
+ events.set(stream, list);
21
+ const set = listeners.get(stream);
22
+ if (set) set.forEach((cb) => cb(rec));
23
+ return rec;
24
+ },
25
+ async read(stream, opts) {
26
+ const list = events.get(stream) || [];
27
+ const dir = opts?.direction || "forward";
28
+ if (dir === "backward") {
29
+ let end = list.length;
30
+ if (opts?.fromId) {
31
+ const idx2 = list.findIndex((e) => e.id === opts.fromId);
32
+ end = idx2 >= 0 ? idx2 : list.length;
33
+ }
34
+ const count = opts?.limit && opts.limit > 0 ? opts.limit : end;
35
+ const start = Math.max(0, end - count);
36
+ const slice = list.slice(start, end);
37
+ return slice.reverse();
38
+ }
39
+ if (!opts?.fromId) {
40
+ if (opts?.limit && opts.limit > 0) return list.slice(0, opts.limit);
41
+ return list;
42
+ }
43
+ const idx = list.findIndex((e) => e.id === opts.fromId);
44
+ const out = idx >= 0 ? list.slice(idx + 1) : list;
45
+ if (opts?.limit && opts.limit > 0) return out.slice(0, opts.limit);
46
+ return out;
47
+ },
48
+ async subscribe(stream, onEvent) {
49
+ let set = listeners.get(stream);
50
+ if (!set) {
51
+ set = /* @__PURE__ */ new Set();
52
+ listeners.set(stream, set);
53
+ }
54
+ set.add(onEvent);
55
+ return {
56
+ unsubscribe() {
57
+ set.delete(onEvent);
58
+ }
59
+ };
60
+ },
61
+ async indexAdd(key, id, score, metadata) {
62
+ const data = indices.get(key) || [];
63
+ const existing = data.findIndex((entry) => entry.id === id);
64
+ if (existing >= 0) {
65
+ data[existing].score = score;
66
+ if (metadata) {
67
+ data[existing].metadata = metadata;
68
+ }
69
+ } else {
70
+ data.push({ id, score, metadata });
71
+ }
72
+ indices.set(key, data);
73
+ },
74
+ async indexRead(key, opts) {
75
+ const data = indices.get(key) || [];
76
+ const sorted = [...data].sort((a, b) => b.score - a.score);
77
+ const offset = opts?.offset || 0;
78
+ const limit = opts?.limit || 50;
79
+ return sorted.slice(offset, offset + limit);
80
+ },
81
+ async indexGet(key, id) {
82
+ const data = indices.get(key) || [];
83
+ const entry = data.find((item) => item.id === id);
84
+ if (!entry) return null;
85
+ return {
86
+ id: entry.id,
87
+ score: entry.score,
88
+ metadata: entry.metadata
89
+ };
90
+ },
91
+ async indexUpdate(key, id, metadata) {
92
+ const data = indices.get(key) || [];
93
+ const entryIndex = data.findIndex((item) => item.id === id);
94
+ if (entryIndex === -1) {
95
+ data.push({ id, score: Date.now(), metadata: { ...metadata, version: 1 } });
96
+ indices.set(key, data);
97
+ return true;
98
+ }
99
+ const entry = data[entryIndex];
100
+ const currentVersion = entry.metadata?.version || 0;
101
+ const expectedVersion = metadata.version !== void 0 ? metadata.version - 1 : currentVersion;
102
+ if (currentVersion !== expectedVersion) {
103
+ return false;
104
+ }
105
+ entry.metadata = {
106
+ ...entry.metadata,
107
+ ...metadata,
108
+ version: currentVersion + 1
109
+ };
110
+ indices.set(key, data);
111
+ return true;
112
+ },
113
+ async indexUpdateWithRetry(key, id, metadata, maxRetries = 3) {
114
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
115
+ const success = await this.indexUpdate(key, id, metadata);
116
+ if (success) return;
117
+ await new Promise((resolve) => setTimeout(resolve, 10 * Math.pow(2, attempt)));
118
+ }
119
+ throw new Error(`Failed to update index after ${maxRetries} retries`);
120
+ },
121
+ async indexIncrement(key, id, field, increment = 1) {
122
+ const data = indices.get(key) || [];
123
+ const entryIndex = data.findIndex((item) => item.id === id);
124
+ if (entryIndex === -1) {
125
+ const newEntry = {
126
+ id,
127
+ score: Date.now(),
128
+ metadata: { [field]: increment, version: 1 }
129
+ };
130
+ data.push(newEntry);
131
+ indices.set(key, data);
132
+ return increment;
133
+ }
134
+ const entry = data[entryIndex];
135
+ if (!entry.metadata) {
136
+ entry.metadata = { version: 0 };
137
+ }
138
+ const currentValue = entry.metadata[field] || 0;
139
+ const newValue = currentValue + increment;
140
+ entry.metadata[field] = newValue;
141
+ entry.metadata.version = (entry.metadata.version || 0) + 1;
142
+ indices.set(key, data);
143
+ return newValue;
144
+ },
145
+ async deleteStream(subject) {
146
+ events.delete(subject);
147
+ listeners.delete(subject);
148
+ },
149
+ async deleteByPattern(pattern) {
150
+ const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
151
+ const regex = new RegExp(`^${regexPattern}$`);
152
+ let count = 0;
153
+ for (const key of events.keys()) {
154
+ if (regex.test(key)) {
155
+ events.delete(key);
156
+ listeners.delete(key);
157
+ count++;
158
+ }
159
+ }
160
+ return count;
161
+ },
162
+ async deleteIndex(key) {
163
+ indices.delete(key);
164
+ },
165
+ async close() {
166
+ listeners.clear();
167
+ events.clear();
168
+ indices.clear();
169
+ }
170
+ };
171
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventStoreAdapter } from '../../types.js';
2
+ export declare function createRedisAdapter(): EventStoreAdapter;
@@ -0,0 +1,348 @@
1
+ import { useRuntimeConfig, useServerLogger } from "#imports";
2
+ import IORedis from "ioredis";
3
+ import { RedisPubSubGateway } from "./redisPubSubGateway.js";
4
+ const logger = useServerLogger("redis-adapter");
5
+ function nowIso() {
6
+ return (/* @__PURE__ */ new Date()).toISOString();
7
+ }
8
+ export function createRedisAdapter() {
9
+ const rc = useRuntimeConfig();
10
+ const conn = rc?.queue?.eventStore?.redis || {};
11
+ const rsOpts = rc?.queue?.eventStore?.options?.redisStreams || {};
12
+ const redis = new IORedis({
13
+ host: conn.host,
14
+ port: conn.port,
15
+ username: conn.username,
16
+ password: conn.password,
17
+ lazyConnect: true,
18
+ enableReadyCheck: false
19
+ // Disable ready check to avoid INFO command conflicts
20
+ });
21
+ const subscriber = new IORedis({
22
+ host: conn.host,
23
+ port: conn.port,
24
+ username: conn.username,
25
+ password: conn.password,
26
+ lazyConnect: true,
27
+ enableReadyCheck: false
28
+ });
29
+ const gateway = new RedisPubSubGateway(subscriber);
30
+ function buildFields(e) {
31
+ const dataStr = e.data !== void 0 ? JSON.stringify(e.data) : "";
32
+ const fields = [
33
+ "type",
34
+ String(e.type || ""),
35
+ "runId",
36
+ String(e.runId || ""),
37
+ "flowName",
38
+ String(e.flowName || ""),
39
+ "data",
40
+ dataStr,
41
+ "ts",
42
+ e.ts || nowIso()
43
+ ];
44
+ if (e.stepName) {
45
+ fields.push("stepName", String(e.stepName));
46
+ }
47
+ if (e.stepId) {
48
+ fields.push("stepId", String(e.stepId));
49
+ }
50
+ if (e.attempt !== void 0) {
51
+ fields.push("attempt", String(e.attempt));
52
+ }
53
+ return fields;
54
+ }
55
+ function parseFieldsToRecord(id, arr) {
56
+ const obj = {};
57
+ for (let i = 0; i < arr.length; i += 2) {
58
+ const k = String(arr[i]);
59
+ const v = String(arr[i + 1] ?? "");
60
+ obj[k] = v;
61
+ }
62
+ let data;
63
+ try {
64
+ data = obj.data ? JSON.parse(obj.data) : void 0;
65
+ } catch {
66
+ data = void 0;
67
+ }
68
+ const rec = {
69
+ id,
70
+ ts: obj.ts || nowIso(),
71
+ type: obj.type || "event",
72
+ runId: obj.runId || "",
73
+ flowName: obj.flowName || "",
74
+ data
75
+ };
76
+ if (obj.stepName) rec.stepName = obj.stepName;
77
+ if (obj.stepId) rec.stepId = obj.stepId;
78
+ if (obj.attempt) rec.attempt = Number.parseInt(obj.attempt, 10);
79
+ return rec;
80
+ }
81
+ return {
82
+ async append(subject, e) {
83
+ if (!redis.status || redis.status === "end") await redis.connect();
84
+ const ts = nowIso();
85
+ const payloadFields = buildFields({ ...e, ts });
86
+ const stream = subject;
87
+ let id;
88
+ if (rsOpts?.trim?.maxLen && rsOpts.trim.maxLen > 0) {
89
+ const approx = rsOpts?.trim?.approx !== false;
90
+ const args = approx ? ["MAXLEN", "~", String(rsOpts.trim.maxLen)] : ["MAXLEN", String(rsOpts.trim.maxLen)];
91
+ id = await redis.xadd(stream, ...args, "*", ...payloadFields);
92
+ } else {
93
+ id = await redis.xadd(stream, "*", ...payloadFields);
94
+ }
95
+ const channel = `nq:events:${subject}`;
96
+ await redis.publish(channel, id);
97
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
98
+ logger.info("[redis-streams] appended and published", { stream, id, channel, type: e.type });
99
+ }
100
+ const rec = { ...e, id, ts, subject };
101
+ return rec;
102
+ },
103
+ async read(subject, opts) {
104
+ if (!redis.status || redis.status === "end") await redis.connect();
105
+ const from = opts?.fromId ? opts.fromId : "0-0";
106
+ const count = opts?.limit || 100;
107
+ const dir = opts?.direction || "forward";
108
+ const stream = subject;
109
+ let resp = [];
110
+ if (dir === "backward") {
111
+ resp = await redis.xrevrange(stream, from === "0-0" ? "+" : `(${from}`, "-", "COUNT", count);
112
+ } else {
113
+ resp = await redis.xrange(stream, from === "0-0" ? "-" : `(${from}`, "+", "COUNT", count);
114
+ }
115
+ const out = [];
116
+ for (const [id, arr] of resp) {
117
+ try {
118
+ const rec = parseFieldsToRecord(id, arr);
119
+ out.push(rec);
120
+ } catch {
121
+ }
122
+ }
123
+ return out;
124
+ },
125
+ async subscribe(subject, onEvent) {
126
+ const stream = subject;
127
+ const channel = `nq:events:${subject}`;
128
+ if (!redis.status || redis.status === "end") await redis.connect();
129
+ let running = true;
130
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
131
+ logger.info("[redis-streams] subscribing", { stream, channel });
132
+ }
133
+ const messageHandler = async (messageId) => {
134
+ if (!running) return;
135
+ try {
136
+ const entries = await redis.xrange(stream, messageId, messageId, "COUNT", 1);
137
+ if (entries && entries.length > 0) {
138
+ const [id, arr] = entries[0];
139
+ const rec = parseFieldsToRecord(id, arr);
140
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
141
+ logger.info("[redis-streams] received event", { stream, id, type: rec.type });
142
+ }
143
+ onEvent(rec);
144
+ }
145
+ } catch (err) {
146
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
147
+ logger.error("[redis-streams] message handling error:", err);
148
+ }
149
+ }
150
+ };
151
+ const unsubscribe = await gateway.subscribe(channel, messageHandler);
152
+ return {
153
+ unsubscribe() {
154
+ running = false;
155
+ unsubscribe();
156
+ }
157
+ };
158
+ },
159
+ async indexAdd(key, id, score, metadata) {
160
+ if (!redis.status || redis.status === "end") await redis.connect();
161
+ await redis.zadd(key, score, id);
162
+ if (metadata) {
163
+ const metaKey = `${key}:meta:${id}`;
164
+ const toStore = { version: "0" };
165
+ for (const [k, v] of Object.entries(metadata)) {
166
+ toStore[k] = Array.isArray(v) ? JSON.stringify(v) : String(v);
167
+ }
168
+ await redis.hset(metaKey, toStore);
169
+ }
170
+ },
171
+ async indexRead(key, opts) {
172
+ if (!redis.status || redis.status === "end") await redis.connect();
173
+ const offset = opts?.offset || 0;
174
+ const limit = opts?.limit || 50;
175
+ const end = offset + limit - 1;
176
+ const results = await redis.zrevrange(key, offset, end, "WITHSCORES");
177
+ const entries = [];
178
+ for (let i = 0; i < results.length; i += 2) {
179
+ const id = results[i];
180
+ const score = Number.parseInt(results[i + 1]);
181
+ const metaKey = `${key}:meta:${id}`;
182
+ const metadata = await redis.hgetall(metaKey);
183
+ let parsedMetadata = void 0;
184
+ if (Object.keys(metadata).length > 0) {
185
+ parsedMetadata = { ...metadata };
186
+ if (parsedMetadata.emittedEvents) {
187
+ parsedMetadata.emittedEvents = JSON.parse(parsedMetadata.emittedEvents);
188
+ }
189
+ if (parsedMetadata.version !== void 0) {
190
+ parsedMetadata.version = Number.parseInt(parsedMetadata.version, 10);
191
+ }
192
+ if (parsedMetadata.startedAt !== void 0) {
193
+ parsedMetadata.startedAt = Number.parseInt(parsedMetadata.startedAt, 10);
194
+ }
195
+ if (parsedMetadata.completedAt !== void 0) {
196
+ parsedMetadata.completedAt = Number.parseInt(parsedMetadata.completedAt, 10);
197
+ }
198
+ if (parsedMetadata.stepCount !== void 0) {
199
+ parsedMetadata.stepCount = Number.parseInt(parsedMetadata.stepCount, 10);
200
+ }
201
+ if (parsedMetadata.completedSteps !== void 0) {
202
+ parsedMetadata.completedSteps = Number.parseInt(parsedMetadata.completedSteps, 10);
203
+ }
204
+ }
205
+ entries.push({
206
+ id,
207
+ score,
208
+ metadata: parsedMetadata
209
+ });
210
+ }
211
+ return entries;
212
+ },
213
+ async indexGet(key, id) {
214
+ if (!redis.status || redis.status === "end") await redis.connect();
215
+ const score = await redis.zscore(key, id);
216
+ if (!score) return null;
217
+ const metaKey = `${key}:meta:${id}`;
218
+ const metadata = await redis.hgetall(metaKey);
219
+ if (Object.keys(metadata).length === 0) {
220
+ return { id, score: Number.parseFloat(score) };
221
+ }
222
+ const parsed = { ...metadata };
223
+ if (parsed.emittedEvents) {
224
+ parsed.emittedEvents = JSON.parse(parsed.emittedEvents);
225
+ }
226
+ if (parsed.version !== void 0) {
227
+ parsed.version = Number.parseInt(parsed.version, 10);
228
+ }
229
+ if (parsed.startedAt !== void 0) {
230
+ parsed.startedAt = Number.parseInt(parsed.startedAt, 10);
231
+ }
232
+ if (parsed.completedAt !== void 0) {
233
+ parsed.completedAt = Number.parseInt(parsed.completedAt, 10);
234
+ }
235
+ if (parsed.stepCount !== void 0) {
236
+ parsed.stepCount = Number.parseInt(parsed.stepCount, 10);
237
+ }
238
+ if (parsed.completedSteps !== void 0) {
239
+ parsed.completedSteps = Number.parseInt(parsed.completedSteps, 10);
240
+ }
241
+ return {
242
+ id,
243
+ score: Number.parseFloat(score),
244
+ metadata: parsed
245
+ };
246
+ },
247
+ async indexUpdate(key, id, metadata) {
248
+ if (!redis.status || redis.status === "end") await redis.connect();
249
+ const metaKey = `${key}:meta:${id}`;
250
+ const current = await redis.hget(metaKey, "version");
251
+ const currentVersion = current ? Number.parseInt(current, 10) : 0;
252
+ const script = `
253
+ local current = redis.call('HGET', KEYS[1], 'version')
254
+ if current == ARGV[1] then
255
+ for i = 2, #ARGV, 2 do
256
+ redis.call('HSET', KEYS[1], ARGV[i], ARGV[i + 1])
257
+ end
258
+ redis.call('HSET', KEYS[1], 'version', tonumber(ARGV[1]) + 1)
259
+ return 1
260
+ else
261
+ return 0
262
+ end
263
+ `;
264
+ const args = [currentVersion.toString()];
265
+ for (const [k, v] of Object.entries(metadata)) {
266
+ if (v === void 0) continue;
267
+ args.push(k);
268
+ if (Array.isArray(v)) {
269
+ args.push(JSON.stringify(v.filter((item) => item != null)));
270
+ } else {
271
+ args.push(String(v));
272
+ }
273
+ }
274
+ const result = await redis.eval(script, 1, metaKey, ...args);
275
+ return result === 1;
276
+ },
277
+ async indexUpdateWithRetry(key, id, metadata, maxRetries = 3) {
278
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
279
+ const success = await this.indexUpdate(key, id, metadata);
280
+ if (success) return;
281
+ await new Promise((resolve) => setTimeout(resolve, 10 * Math.pow(2, attempt)));
282
+ }
283
+ throw new Error(`Failed to update index after ${maxRetries} retries`);
284
+ },
285
+ async indexIncrement(key, id, field, increment = 1) {
286
+ if (!redis.status || redis.status === "end") await redis.connect();
287
+ const metaKey = `${key}:meta:${id}`;
288
+ const newValue = await redis.hincrby(metaKey, field, increment);
289
+ await redis.hincrby(metaKey, "version", 1);
290
+ return newValue;
291
+ },
292
+ async setMetadataTTL(flowName, runId, ttlSeconds) {
293
+ if (!redis.status || redis.status === "end") await redis.connect();
294
+ const metaKey = `nq:flow:idx:${flowName}:meta:${runId}`;
295
+ await redis.expire(metaKey, ttlSeconds);
296
+ },
297
+ async cleanupCompletedFlows(key, retentionSeconds) {
298
+ if (!redis.status || redis.status === "end") await redis.connect();
299
+ const now = Date.now();
300
+ const cutoffTime = now - retentionSeconds * 1e3;
301
+ const oldEntries = await redis.zrangebyscore(key, "-inf", cutoffTime);
302
+ let removedCount = 0;
303
+ for (const runId of oldEntries) {
304
+ const metaKey = `${key}:meta:${runId}`;
305
+ const metadata = await redis.hgetall(metaKey);
306
+ if (metadata && (metadata.status === "completed" || metadata.status === "failed")) {
307
+ await redis.zrem(key, runId);
308
+ await redis.del(metaKey);
309
+ removedCount++;
310
+ }
311
+ }
312
+ return removedCount;
313
+ },
314
+ async deleteStream(subject) {
315
+ if (!redis.status || redis.status === "end") await redis.connect();
316
+ await redis.del(subject);
317
+ },
318
+ async deleteByPattern(pattern) {
319
+ if (!redis.status || redis.status === "end") await redis.connect();
320
+ let cursor = "0";
321
+ const keysToDelete = [];
322
+ do {
323
+ const result = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
324
+ cursor = result[0];
325
+ const keys = result[1];
326
+ if (keys.length > 0) {
327
+ keysToDelete.push(...keys);
328
+ }
329
+ } while (cursor !== "0");
330
+ if (keysToDelete.length > 0) {
331
+ await redis.del(...keysToDelete);
332
+ }
333
+ return keysToDelete.length;
334
+ },
335
+ async deleteIndex(key) {
336
+ if (!redis.status || redis.status === "end") await redis.connect();
337
+ await redis.del(key);
338
+ },
339
+ async close() {
340
+ try {
341
+ await gateway.cleanup();
342
+ await redis.quit();
343
+ await subscriber.quit();
344
+ } catch {
345
+ }
346
+ }
347
+ };
348
+ }
@@ -0,0 +1,29 @@
1
+ import type IORedis from 'ioredis';
2
+ /**
3
+ * Gateway pattern for Redis Pub/Sub
4
+ * Maintains a single 'message' listener and routes to channel-specific handlers
5
+ */
6
+ export declare class RedisPubSubGateway {
7
+ private subscriber;
8
+ private channelSubscribers;
9
+ private initialized;
10
+ constructor(subscriber: IORedis);
11
+ private initialize;
12
+ /**
13
+ * Subscribe to a channel with a handler
14
+ * Returns unsubscribe function
15
+ */
16
+ subscribe(channel: string, handler: (messageId: string) => void): Promise<() => void>;
17
+ /**
18
+ * Get subscriber count for a channel (for debugging)
19
+ */
20
+ getSubscriberCount(channel: string): number;
21
+ /**
22
+ * Get total subscriber count across all channels
23
+ */
24
+ getTotalSubscriberCount(): number;
25
+ /**
26
+ * Cleanup all subscriptions
27
+ */
28
+ cleanup(): Promise<void>;
29
+ }
@@ -0,0 +1,82 @@
1
+ import { useServerLogger } from "#imports";
2
+ const logger = useServerLogger("redis-adapter");
3
+ export class RedisPubSubGateway {
4
+ constructor(subscriber) {
5
+ this.subscriber = subscriber;
6
+ }
7
+ channelSubscribers = /* @__PURE__ */ new Map();
8
+ initialized = false;
9
+ initialize() {
10
+ if (this.initialized) return;
11
+ this.initialized = true;
12
+ this.subscriber.on("message", (channel, messageId) => {
13
+ const handlers = this.channelSubscribers.get(channel);
14
+ if (!handlers || handlers.size === 0) return;
15
+ for (const handler of handlers) {
16
+ try {
17
+ handler(messageId);
18
+ } catch (err) {
19
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
20
+ logger.error("[redis-pubsub-gateway] Handler error:", err);
21
+ }
22
+ }
23
+ }
24
+ });
25
+ }
26
+ /**
27
+ * Subscribe to a channel with a handler
28
+ * Returns unsubscribe function
29
+ */
30
+ async subscribe(channel, handler) {
31
+ this.initialize();
32
+ if (!this.channelSubscribers.has(channel)) {
33
+ this.channelSubscribers.set(channel, /* @__PURE__ */ new Set());
34
+ await this.subscriber.subscribe(channel);
35
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
36
+ logger.info("[redis-pubsub-gateway] Subscribed to channel:", channel);
37
+ }
38
+ }
39
+ this.channelSubscribers.get(channel).add(handler);
40
+ return () => {
41
+ const handlers = this.channelSubscribers.get(channel);
42
+ if (handlers) {
43
+ handlers.delete(handler);
44
+ if (handlers.size === 0) {
45
+ this.channelSubscribers.delete(channel);
46
+ this.subscriber.unsubscribe(channel).catch(() => {
47
+ });
48
+ if (process.env.NQ_DEBUG_EVENTS === "1") {
49
+ logger.info("[redis-pubsub-gateway] Unsubscribed from channel:", channel);
50
+ }
51
+ }
52
+ }
53
+ };
54
+ }
55
+ /**
56
+ * Get subscriber count for a channel (for debugging)
57
+ */
58
+ getSubscriberCount(channel) {
59
+ return this.channelSubscribers.get(channel)?.size ?? 0;
60
+ }
61
+ /**
62
+ * Get total subscriber count across all channels
63
+ */
64
+ getTotalSubscriberCount() {
65
+ let count = 0;
66
+ for (const handlers of this.channelSubscribers.values()) {
67
+ count += handlers.size;
68
+ }
69
+ return count;
70
+ }
71
+ /**
72
+ * Cleanup all subscriptions
73
+ */
74
+ async cleanup() {
75
+ const channels = Array.from(this.channelSubscribers.keys());
76
+ this.channelSubscribers.clear();
77
+ if (channels.length > 0) {
78
+ await this.subscriber.unsubscribe(...channels).catch(() => {
79
+ });
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,20 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { EventRecord } from '../../types.js';
3
+ type Handler = (e: EventRecord) => void;
4
+ declare global {
5
+ var __nq_bus_emitter: EventEmitter | undefined;
6
+ }
7
+ declare function publish(event: EventRecord): void;
8
+ declare function subscribeRunId(runId: string, handler: Handler): () => void;
9
+ declare function onType(type: string, handler: Handler): () => void;
10
+ export declare const eventBus: {
11
+ publish: typeof publish;
12
+ subscribeRunId: typeof subscribeRunId;
13
+ onType: typeof onType;
14
+ };
15
+ export declare function getEventBus(): {
16
+ publish: typeof publish;
17
+ subscribeRunId: typeof subscribeRunId;
18
+ onType: typeof onType;
19
+ };
20
+ export {};
@@ -0,0 +1,35 @@
1
+ import { EventEmitter } from "node:events";
2
+ const emitter = globalThis.__nq_bus_emitter ??= new EventEmitter();
3
+ try {
4
+ const max = Number(process.env.NQ_BUS_MAX_LISTENERS || 100);
5
+ if (!Number.isNaN(max) && max > 0) emitter.setMaxListeners(max);
6
+ } catch {
7
+ }
8
+ function eventNameForRunId(runId) {
9
+ return `runId:${runId}`;
10
+ }
11
+ function eventNameForType(type) {
12
+ return `type:${type}`;
13
+ }
14
+ function publish(event) {
15
+ emitter.emit(eventNameForRunId(event.runId), event);
16
+ emitter.emit(eventNameForType(event.type), event);
17
+ }
18
+ function subscribeRunId(runId, handler) {
19
+ const name = eventNameForRunId(runId);
20
+ emitter.on(name, handler);
21
+ return () => {
22
+ emitter.off(name, handler);
23
+ };
24
+ }
25
+ function onType(type, handler) {
26
+ const name = eventNameForType(type);
27
+ emitter.on(name, handler);
28
+ return () => {
29
+ emitter.off(name, handler);
30
+ };
31
+ }
32
+ export const eventBus = { publish, subscribeRunId, onType };
33
+ export function getEventBus() {
34
+ return eventBus;
35
+ }