nvent 0.4.3 → 0.4.5

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 (234) hide show
  1. package/dist/module.d.mts +3 -184
  2. package/dist/module.json +3 -3
  3. package/dist/module.mjs +133 -197
  4. package/dist/runtime/adapters/builtin/file-queue.d.ts +53 -0
  5. package/dist/runtime/adapters/builtin/file-queue.js +435 -0
  6. package/dist/runtime/adapters/builtin/file-store.d.ts +46 -0
  7. package/dist/runtime/adapters/builtin/file-store.js +225 -0
  8. package/dist/runtime/adapters/builtin/file-stream.d.ts +39 -0
  9. package/dist/runtime/adapters/builtin/file-stream.js +56 -0
  10. package/dist/runtime/adapters/builtin/index.d.ts +10 -0
  11. package/dist/runtime/adapters/builtin/index.js +5 -0
  12. package/dist/runtime/adapters/builtin/memory-queue.d.ts +52 -0
  13. package/dist/runtime/adapters/builtin/memory-queue.js +239 -0
  14. package/dist/runtime/adapters/builtin/memory-store.d.ts +57 -0
  15. package/dist/runtime/adapters/builtin/memory-store.js +263 -0
  16. package/dist/runtime/adapters/builtin/memory-stream.d.ts +21 -0
  17. package/dist/runtime/adapters/builtin/memory-stream.js +56 -0
  18. package/dist/runtime/adapters/factory.d.ts +31 -0
  19. package/dist/runtime/adapters/factory.js +100 -0
  20. package/dist/runtime/adapters/index.d.ts +8 -0
  21. package/dist/runtime/adapters/index.js +3 -0
  22. package/dist/runtime/adapters/interfaces/index.d.ts +11 -0
  23. package/dist/runtime/adapters/interfaces/index.js +3 -0
  24. package/dist/runtime/adapters/interfaces/queue.d.ts +150 -0
  25. package/dist/runtime/adapters/interfaces/store.d.ts +233 -0
  26. package/dist/runtime/adapters/interfaces/stream.d.ts +62 -0
  27. package/dist/runtime/adapters/registry.d.ts +85 -0
  28. package/dist/runtime/adapters/registry.js +161 -0
  29. package/dist/runtime/config/index.d.ts +29 -0
  30. package/dist/runtime/config/index.js +167 -0
  31. package/dist/runtime/config/types.d.ts +367 -0
  32. package/dist/runtime/config/types.js +0 -0
  33. package/dist/runtime/events/types.d.ts +116 -0
  34. package/dist/runtime/events/types.js +0 -0
  35. package/dist/runtime/events/utils/stallDetector.d.ts +99 -0
  36. package/dist/runtime/events/utils/stallDetector.js +237 -0
  37. package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.d.ts +3 -8
  38. package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.js +119 -36
  39. package/dist/runtime/events/wiring/registry.d.ts +19 -0
  40. package/dist/runtime/events/wiring/registry.js +33 -0
  41. package/dist/runtime/events/wiring/stateWiring.d.ts +37 -0
  42. package/dist/runtime/events/wiring/stateWiring.js +92 -0
  43. package/dist/runtime/events/wiring/streamWiring.d.ts +32 -0
  44. package/dist/runtime/events/wiring/streamWiring.js +79 -0
  45. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +16 -5
  46. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +21 -0
  47. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +12 -2
  48. package/dist/runtime/server/api/_flows/[name]/runs.get.js +15 -4
  49. package/dist/runtime/server/api/_flows/[name]/schedule.post.js +11 -2
  50. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +21 -16
  51. package/dist/runtime/server/api/_flows/[name]/schedules.get.js +21 -19
  52. package/dist/runtime/server/api/_flows/ws.js +43 -22
  53. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +8 -3
  54. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +12 -3
  55. package/dist/runtime/server/api/_queues/index.get.js +66 -23
  56. package/dist/runtime/server/api/_queues/ws.js +14 -4
  57. package/dist/runtime/server/plugins/00.adapters.d.ts +14 -0
  58. package/dist/runtime/server/plugins/00.adapters.js +69 -0
  59. package/dist/runtime/server/plugins/02.workers.js +45 -0
  60. package/dist/runtime/tsconfig.json +8 -0
  61. package/dist/runtime/utils/adapters.d.ts +66 -0
  62. package/dist/runtime/utils/adapters.js +51 -0
  63. package/dist/runtime/utils/defineFunction.d.ts +10 -0
  64. package/dist/runtime/{server-utils/utils/defineQueueWorker.js → utils/defineFunction.js} +4 -4
  65. package/dist/runtime/{server-utils/utils/defineQueueConfig.d.ts → utils/defineFunctionConfig.d.ts} +3 -3
  66. package/dist/runtime/utils/defineFunctionConfig.js +2 -0
  67. package/dist/runtime/utils/registerAdapter.d.ts +59 -0
  68. package/dist/runtime/utils/registerAdapter.js +13 -0
  69. package/dist/runtime/utils/useFlowEngine.d.ts +19 -0
  70. package/dist/runtime/utils/useFlowEngine.js +108 -0
  71. package/dist/runtime/{server-utils/utils → utils}/useNventLogger.js +2 -2
  72. package/dist/runtime/utils/useStreamTopics.d.ts +72 -0
  73. package/dist/runtime/utils/useStreamTopics.js +47 -0
  74. package/dist/runtime/{server-utils/worker/runner/node.d.ts → worker/node/runner.d.ts} +18 -2
  75. package/dist/runtime/{server-utils/worker/runner/node.js → worker/node/runner.js} +44 -17
  76. package/dist/types.d.mts +2 -2
  77. package/package.json +14 -44
  78. package/LICENSE +0 -21
  79. package/README.md +0 -389
  80. package/dist/runtime/app/assets/vueflow.css +0 -1
  81. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +0 -33
  82. package/dist/runtime/app/components/ConfirmDialog.vue +0 -121
  83. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +0 -33
  84. package/dist/runtime/app/components/FlowDiagram.d.vue.ts +0 -64
  85. package/dist/runtime/app/components/FlowDiagram.vue +0 -338
  86. package/dist/runtime/app/components/FlowDiagram.vue.d.ts +0 -64
  87. package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +0 -29
  88. package/dist/runtime/app/components/FlowNodeCard.vue +0 -156
  89. package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +0 -29
  90. package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +0 -9
  91. package/dist/runtime/app/components/FlowRunOverview.vue +0 -291
  92. package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +0 -9
  93. package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +0 -14
  94. package/dist/runtime/app/components/FlowRunStatusBadge.vue +0 -60
  95. package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +0 -14
  96. package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +0 -12
  97. package/dist/runtime/app/components/FlowRunTimeline.vue +0 -127
  98. package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +0 -12
  99. package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +0 -16
  100. package/dist/runtime/app/components/FlowScheduleDialog.vue +0 -226
  101. package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +0 -16
  102. package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +0 -12
  103. package/dist/runtime/app/components/FlowSchedulesList.vue +0 -99
  104. package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +0 -12
  105. package/dist/runtime/app/components/JobScheduling.d.vue.ts +0 -6
  106. package/dist/runtime/app/components/JobScheduling.vue +0 -203
  107. package/dist/runtime/app/components/JobScheduling.vue.d.ts +0 -6
  108. package/dist/runtime/app/components/ListItem.d.vue.ts +0 -23
  109. package/dist/runtime/app/components/ListItem.vue +0 -70
  110. package/dist/runtime/app/components/ListItem.vue.d.ts +0 -23
  111. package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +0 -45
  112. package/dist/runtime/app/components/QueueConfigDetails.vue +0 -412
  113. package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +0 -45
  114. package/dist/runtime/app/components/StatCounter.d.vue.ts +0 -9
  115. package/dist/runtime/app/components/StatCounter.vue +0 -25
  116. package/dist/runtime/app/components/StatCounter.vue.d.ts +0 -9
  117. package/dist/runtime/app/components/TimelineList.d.vue.ts +0 -7
  118. package/dist/runtime/app/components/TimelineList.vue +0 -210
  119. package/dist/runtime/app/components/TimelineList.vue.d.ts +0 -7
  120. package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +0 -46
  121. package/dist/runtime/app/components/nhealth/component-router.vue +0 -26
  122. package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +0 -46
  123. package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +0 -24
  124. package/dist/runtime/app/components/nhealth/component-shell.vue +0 -89
  125. package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +0 -24
  126. package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +0 -14
  127. package/dist/runtime/app/composables/useAnalyzedFlows.js +0 -8
  128. package/dist/runtime/app/composables/useComponentRouter.d.ts +0 -38
  129. package/dist/runtime/app/composables/useComponentRouter.js +0 -240
  130. package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +0 -15
  131. package/dist/runtime/app/composables/useFlowRunTimeline.js +0 -66
  132. package/dist/runtime/app/composables/useFlowRuns.d.ts +0 -18
  133. package/dist/runtime/app/composables/useFlowRuns.js +0 -32
  134. package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +0 -24
  135. package/dist/runtime/app/composables/useFlowRunsInfinite.js +0 -123
  136. package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +0 -9
  137. package/dist/runtime/app/composables/useFlowRunsPolling.js +0 -33
  138. package/dist/runtime/app/composables/useFlowState.d.ts +0 -125
  139. package/dist/runtime/app/composables/useFlowState.js +0 -211
  140. package/dist/runtime/app/composables/useFlowWebSocket.d.ts +0 -27
  141. package/dist/runtime/app/composables/useFlowWebSocket.js +0 -205
  142. package/dist/runtime/app/composables/useFlowsNavigation.d.ts +0 -10
  143. package/dist/runtime/app/composables/useFlowsNavigation.js +0 -58
  144. package/dist/runtime/app/composables/useQueueJobs.d.ts +0 -20
  145. package/dist/runtime/app/composables/useQueueJobs.js +0 -20
  146. package/dist/runtime/app/composables/useQueueUpdates.d.ts +0 -26
  147. package/dist/runtime/app/composables/useQueueUpdates.js +0 -122
  148. package/dist/runtime/app/composables/useQueues.d.ts +0 -44
  149. package/dist/runtime/app/composables/useQueues.js +0 -26
  150. package/dist/runtime/app/composables/useQueuesLive.d.ts +0 -19
  151. package/dist/runtime/app/composables/useQueuesLive.js +0 -143
  152. package/dist/runtime/app/pages/flows/index.d.vue.ts +0 -3
  153. package/dist/runtime/app/pages/flows/index.vue +0 -645
  154. package/dist/runtime/app/pages/flows/index.vue.d.ts +0 -3
  155. package/dist/runtime/app/pages/index.d.vue.ts +0 -3
  156. package/dist/runtime/app/pages/index.vue +0 -34
  157. package/dist/runtime/app/pages/index.vue.d.ts +0 -3
  158. package/dist/runtime/app/pages/queues/index.d.vue.ts +0 -3
  159. package/dist/runtime/app/pages/queues/index.vue +0 -229
  160. package/dist/runtime/app/pages/queues/index.vue.d.ts +0 -3
  161. package/dist/runtime/app/pages/queues/job.d.vue.ts +0 -3
  162. package/dist/runtime/app/pages/queues/job.vue +0 -262
  163. package/dist/runtime/app/pages/queues/job.vue.d.ts +0 -3
  164. package/dist/runtime/app/pages/queues/jobs.d.vue.ts +0 -3
  165. package/dist/runtime/app/pages/queues/jobs.vue +0 -291
  166. package/dist/runtime/app/pages/queues/jobs.vue.d.ts +0 -3
  167. package/dist/runtime/app/plugins/vueflow.client.d.ts +0 -2
  168. package/dist/runtime/app/plugins/vueflow.client.js +0 -11
  169. package/dist/runtime/constants.d.ts +0 -11
  170. package/dist/runtime/constants.js +0 -11
  171. package/dist/runtime/schema.d.ts +0 -37
  172. package/dist/runtime/schema.js +0 -20
  173. package/dist/runtime/server/plugins/00.event-store.d.ts +0 -13
  174. package/dist/runtime/server/plugins/00.event-store.js +0 -16
  175. package/dist/runtime/server/plugins/flow-management.d.ts +0 -13
  176. package/dist/runtime/server/plugins/flow-management.js +0 -65
  177. package/dist/runtime/server/plugins/queue-management.js +0 -27
  178. package/dist/runtime/server/plugins/state-cleanup.d.ts +0 -11
  179. package/dist/runtime/server/plugins/state-cleanup.js +0 -93
  180. package/dist/runtime/server/plugins/worker-management.js +0 -33
  181. package/dist/runtime/server/tsconfig.json +0 -3
  182. package/dist/runtime/server-utils/events/adapters/fileAdapter.d.ts +0 -2
  183. package/dist/runtime/server-utils/events/adapters/fileAdapter.js +0 -382
  184. package/dist/runtime/server-utils/events/adapters/memoryAdapter.d.ts +0 -2
  185. package/dist/runtime/server-utils/events/adapters/memoryAdapter.js +0 -171
  186. package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.d.ts +0 -2
  187. package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.js +0 -348
  188. package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.d.ts +0 -30
  189. package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.js +0 -82
  190. package/dist/runtime/server-utils/events/eventStoreFactory.d.ts +0 -19
  191. package/dist/runtime/server-utils/events/eventStoreFactory.js +0 -44
  192. package/dist/runtime/server-utils/events/streamNames.d.ts +0 -17
  193. package/dist/runtime/server-utils/events/streamNames.js +0 -17
  194. package/dist/runtime/server-utils/events/types.d.ts +0 -63
  195. package/dist/runtime/server-utils/events/wiring/registry.d.ts +0 -10
  196. package/dist/runtime/server-utils/events/wiring/registry.js +0 -24
  197. package/dist/runtime/server-utils/queue/adapters/bullmq.d.ts +0 -18
  198. package/dist/runtime/server-utils/queue/adapters/bullmq.js +0 -164
  199. package/dist/runtime/server-utils/queue/queueFactory.d.ts +0 -3
  200. package/dist/runtime/server-utils/queue/queueFactory.js +0 -10
  201. package/dist/runtime/server-utils/queue/types.d.ts +0 -47
  202. package/dist/runtime/server-utils/state/adapters/redis.d.ts +0 -2
  203. package/dist/runtime/server-utils/state/adapters/redis.js +0 -42
  204. package/dist/runtime/server-utils/state/stateFactory.d.ts +0 -3
  205. package/dist/runtime/server-utils/state/stateFactory.js +0 -17
  206. package/dist/runtime/server-utils/state/types.d.ts +0 -23
  207. package/dist/runtime/server-utils/utils/defineQueueConfig.js +0 -2
  208. package/dist/runtime/server-utils/utils/defineQueueWorker.d.ts +0 -10
  209. package/dist/runtime/server-utils/utils/useEventStore.d.ts +0 -20
  210. package/dist/runtime/server-utils/utils/useEventStore.js +0 -119
  211. package/dist/runtime/server-utils/utils/useFlowEngine.d.ts +0 -9
  212. package/dist/runtime/server-utils/utils/useFlowEngine.js +0 -44
  213. package/dist/runtime/server-utils/utils/useLogs.d.ts +0 -41
  214. package/dist/runtime/server-utils/utils/useLogs.js +0 -74
  215. package/dist/runtime/server-utils/utils/useQueue.d.ts +0 -31
  216. package/dist/runtime/server-utils/utils/useQueue.js +0 -24
  217. package/dist/runtime/server-utils/worker/adapter.d.ts +0 -4
  218. package/dist/runtime/server-utils/worker/adapter.js +0 -66
  219. package/dist/runtime/types.d.ts +0 -132
  220. /package/dist/runtime/{server-utils/events/types.js → adapters/interfaces/queue.js} +0 -0
  221. /package/dist/runtime/{server-utils/queue/types.js → adapters/interfaces/store.js} +0 -0
  222. /package/dist/runtime/{server-utils/state/types.js → adapters/interfaces/stream.js} +0 -0
  223. /package/dist/runtime/{server-utils/events → events}/eventBus.d.ts +0 -0
  224. /package/dist/runtime/{server-utils/events → events}/eventBus.js +0 -0
  225. /package/dist/runtime/server/{plugins/queue-management.d.ts → api/_flows/[name]/runs/[runId]/cancel.post.d.ts} +0 -0
  226. /package/dist/runtime/server/plugins/{00.ws-lifecycle.d.ts → 01.ws-lifecycle.d.ts} +0 -0
  227. /package/dist/runtime/server/plugins/{00.ws-lifecycle.js → 01.ws-lifecycle.js} +0 -0
  228. /package/dist/runtime/server/plugins/{worker-management.d.ts → 02.workers.d.ts} +0 -0
  229. /package/dist/runtime/{server-utils/utils → utils}/useEventManager.d.ts +0 -0
  230. /package/dist/runtime/{server-utils/utils → utils}/useEventManager.js +0 -0
  231. /package/dist/runtime/{server-utils/utils → utils}/useNventLogger.d.ts +0 -0
  232. /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.d.ts +0 -0
  233. /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.js +0 -0
  234. /package/dist/runtime/{python → worker/python}/get_config.py +0 -0
@@ -0,0 +1,435 @@
1
+ import * as fastq from "fastq";
2
+ import { promises as fs } from "node:fs";
3
+ import { join } from "node:path";
4
+ import cronParser from "cron-parser";
5
+ export class FileQueueAdapter {
6
+ jobs = /* @__PURE__ */ new Map();
7
+ eventListeners = /* @__PURE__ */ new Map();
8
+ workers = /* @__PURE__ */ new Map();
9
+ scheduledJobs = /* @__PURE__ */ new Map();
10
+ options;
11
+ initialized = false;
12
+ constructor(options) {
13
+ this.options = {
14
+ dataDir: options.dataDir,
15
+ maxQueueSize: options.maxQueueSize || 1e4
16
+ };
17
+ }
18
+ async init() {
19
+ if (this.initialized) return;
20
+ await fs.mkdir(join(this.options.dataDir, "queues"), { recursive: true });
21
+ await this.loadJobsFromDisk();
22
+ this.initialized = true;
23
+ }
24
+ async loadJobsFromDisk() {
25
+ const queuesDir = join(this.options.dataDir, "queues");
26
+ try {
27
+ const queueNames = await fs.readdir(queuesDir);
28
+ for (const queueName of queueNames) {
29
+ const jobsDir = join(queuesDir, queueName, "jobs");
30
+ try {
31
+ const jobFiles = await fs.readdir(jobsDir);
32
+ for (const jobFile of jobFiles) {
33
+ if (!jobFile.endsWith(".json")) continue;
34
+ const jobPath = join(jobsDir, jobFile);
35
+ const jobData = await fs.readFile(jobPath, "utf-8");
36
+ const job = JSON.parse(jobData);
37
+ this.jobs.set(job.id, job);
38
+ }
39
+ } catch {
40
+ }
41
+ }
42
+ } catch {
43
+ }
44
+ }
45
+ async persistJob(queueName, job) {
46
+ const jobsDir = join(this.options.dataDir, "queues", queueName, "jobs");
47
+ await fs.mkdir(jobsDir, { recursive: true });
48
+ const jobPath = join(jobsDir, `${job.id}.json`);
49
+ await fs.writeFile(jobPath, JSON.stringify(job, null, 2));
50
+ }
51
+ async deleteJobFile(queueName, jobId) {
52
+ const jobPath = join(this.options.dataDir, "queues", queueName, "jobs", `${jobId}.json`);
53
+ try {
54
+ await fs.unlink(jobPath);
55
+ } catch {
56
+ }
57
+ }
58
+ async enqueue(queueName, job) {
59
+ const jobId = job.opts?.jobId || this.generateId();
60
+ if (this.jobs.has(jobId)) {
61
+ return jobId;
62
+ }
63
+ if (this.jobs.size >= this.options.maxQueueSize) {
64
+ throw new Error(`Queue ${queueName} is full (max: ${this.options.maxQueueSize})`);
65
+ }
66
+ const internalJob = {
67
+ id: jobId,
68
+ name: job.name,
69
+ data: { ...job.data, __queueName: queueName },
70
+ state: "waiting",
71
+ timestamp: Date.now(),
72
+ attemptsMade: 0,
73
+ opts: job.opts
74
+ // Store job options for retry logic
75
+ };
76
+ this.jobs.set(jobId, internalJob);
77
+ await this.persistJob(queueName, internalJob);
78
+ this.emitEvent(queueName, "waiting", { jobId, job: internalJob });
79
+ const workerInfo = this.workers.get(queueName);
80
+ if (workerInfo && !workerInfo.paused) {
81
+ workerInfo.queue.push({ jobId, jobName: job.name, data: job.data }).catch(() => {
82
+ });
83
+ }
84
+ return jobId;
85
+ }
86
+ async schedule(queueName, job, opts) {
87
+ if (opts?.delay) {
88
+ const jobId = this.generateId();
89
+ const internalJob = {
90
+ id: jobId,
91
+ name: job.name,
92
+ data: { ...job.data, __queueName: queueName },
93
+ state: "delayed",
94
+ timestamp: Date.now()
95
+ };
96
+ this.jobs.set(jobId, internalJob);
97
+ await this.persistJob(queueName, internalJob);
98
+ this.emitEvent(queueName, "delayed", { jobId, job: internalJob, delay: opts.delay });
99
+ setTimeout(() => {
100
+ this.enqueue(queueName, job);
101
+ }, opts.delay);
102
+ return jobId;
103
+ }
104
+ if (opts?.cron || opts?.repeat) {
105
+ const scheduleId = this.generateId();
106
+ const cronPattern = opts.cron || opts.repeat?.pattern;
107
+ if (!cronPattern) {
108
+ throw new Error("Cron pattern is required for scheduled jobs");
109
+ }
110
+ const scheduledJob = {
111
+ queueName,
112
+ job,
113
+ opts,
114
+ repeatCount: 0
115
+ };
116
+ this.scheduledJobs.set(scheduleId, scheduledJob);
117
+ this.scheduleCronJob(scheduleId, scheduledJob);
118
+ console.info(`[FileQueue] Scheduled job "${job.name}" with cron pattern "${cronPattern}" (id: ${scheduleId})`);
119
+ return scheduleId;
120
+ }
121
+ return this.enqueue(queueName, job);
122
+ }
123
+ async getJob(_queueName, id) {
124
+ return this.jobs.get(id) || null;
125
+ }
126
+ async getJobs(queueName, query) {
127
+ let jobs = Array.from(this.jobs.values()).filter((j) => j.data?.__queueName === queueName);
128
+ if (query?.state && query.state.length > 0) {
129
+ jobs = jobs.filter((j) => query.state.includes(j.state));
130
+ }
131
+ if (query?.offset) {
132
+ jobs = jobs.slice(query.offset);
133
+ }
134
+ if (query?.limit) {
135
+ jobs = jobs.slice(0, query.limit);
136
+ }
137
+ return jobs;
138
+ }
139
+ on(queueName, event, callback) {
140
+ const key = `${queueName}:${event}`;
141
+ if (!this.eventListeners.has(key)) {
142
+ this.eventListeners.set(key, []);
143
+ }
144
+ this.eventListeners.get(key).push(callback);
145
+ return () => {
146
+ const listeners = this.eventListeners.get(key);
147
+ if (listeners) {
148
+ const index = listeners.indexOf(callback);
149
+ if (index > -1) {
150
+ listeners.splice(index, 1);
151
+ }
152
+ }
153
+ };
154
+ }
155
+ async isPaused(queueName) {
156
+ const workerInfo = this.workers.get(queueName);
157
+ return workerInfo?.paused || false;
158
+ }
159
+ async getJobCounts(queueName) {
160
+ const jobs = Array.from(this.jobs.values()).filter((j) => j.data?.__queueName === queueName);
161
+ return {
162
+ active: jobs.filter((j) => j.state === "active").length,
163
+ completed: jobs.filter((j) => j.state === "completed").length,
164
+ failed: jobs.filter((j) => j.state === "failed").length,
165
+ delayed: jobs.filter((j) => j.state === "delayed").length,
166
+ waiting: jobs.filter((j) => j.state === "waiting").length,
167
+ paused: jobs.filter((j) => j.state === "paused").length
168
+ };
169
+ }
170
+ async getScheduledJobs(queueName) {
171
+ const scheduled = [];
172
+ for (const [scheduleId, scheduledJob] of this.scheduledJobs.entries()) {
173
+ if (scheduledJob.queueName === queueName) {
174
+ const cronPattern = scheduledJob.opts.cron || scheduledJob.opts.repeat?.pattern;
175
+ let nextRun;
176
+ if (cronPattern) {
177
+ try {
178
+ const interval = cronParser.parseExpression(cronPattern);
179
+ nextRun = interval.next().toDate();
180
+ } catch {
181
+ }
182
+ }
183
+ scheduled.push({
184
+ id: scheduleId,
185
+ jobName: scheduledJob.job.name,
186
+ queueName: scheduledJob.queueName,
187
+ cron: scheduledJob.opts.cron,
188
+ pattern: scheduledJob.opts.repeat?.pattern,
189
+ nextRun,
190
+ repeatCount: scheduledJob.repeatCount,
191
+ limit: scheduledJob.opts.repeat?.limit
192
+ });
193
+ }
194
+ }
195
+ return scheduled;
196
+ }
197
+ async removeScheduledJob(scheduleId) {
198
+ const scheduledJob = this.scheduledJobs.get(scheduleId);
199
+ if (!scheduledJob) {
200
+ return false;
201
+ }
202
+ if (scheduledJob.timerId) {
203
+ clearTimeout(scheduledJob.timerId);
204
+ }
205
+ this.scheduledJobs.delete(scheduleId);
206
+ console.info(`[FileQueue] Removed scheduled job: ${scheduleId}`);
207
+ return true;
208
+ }
209
+ async pause(queueName) {
210
+ const workerInfo = this.workers.get(queueName);
211
+ if (workerInfo) {
212
+ workerInfo.paused = true;
213
+ workerInfo.queue.pause();
214
+ }
215
+ }
216
+ async resume(queueName) {
217
+ const workerInfo = this.workers.get(queueName);
218
+ if (workerInfo) {
219
+ workerInfo.paused = false;
220
+ workerInfo.queue.resume();
221
+ }
222
+ }
223
+ async close() {
224
+ for (const [scheduleId, scheduledJob] of this.scheduledJobs.entries()) {
225
+ if (scheduledJob.timerId) {
226
+ clearTimeout(scheduledJob.timerId);
227
+ }
228
+ console.info(`[FileQueue] Stopped scheduled job: ${scheduleId}`);
229
+ }
230
+ this.scheduledJobs.clear();
231
+ const drainPromises = Array.from(this.workers.values()).map((w) => w.queue.drained());
232
+ await Promise.all(drainPromises);
233
+ const persistPromises = [];
234
+ for (const [_jobId, job] of Array.from(this.jobs.entries())) {
235
+ const queueName = job.data?.__queueName || "default";
236
+ persistPromises.push(this.persistJob(queueName, job));
237
+ }
238
+ await Promise.all(persistPromises);
239
+ this.jobs.clear();
240
+ this.eventListeners.clear();
241
+ this.workers.clear();
242
+ }
243
+ // ============================================================
244
+ // Worker Management
245
+ // ============================================================
246
+ registerWorker(queueName, jobName, handler, opts) {
247
+ const requestedConcurrency = opts?.concurrency || 1;
248
+ let workerInfo = this.workers.get(queueName);
249
+ if (workerInfo) {
250
+ console.info(`[FileQueue] Adding handler for job "${jobName}" to queue "${queueName}"`);
251
+ workerInfo.handlers.set(jobName, handler);
252
+ if (requestedConcurrency > workerInfo.concurrency) {
253
+ workerInfo.concurrency = requestedConcurrency;
254
+ workerInfo.queue.concurrency = requestedConcurrency;
255
+ console.info(`[FileQueue] Updated queue "${queueName}" concurrency to ${requestedConcurrency}`);
256
+ }
257
+ return;
258
+ }
259
+ console.info(`[FileQueue] Creating new worker for queue: ${queueName}`);
260
+ const handlers = /* @__PURE__ */ new Map();
261
+ handlers.set(jobName, handler);
262
+ const dispatcher = async (task) => {
263
+ const handler2 = handlers.get(task.jobName);
264
+ if (!handler2) {
265
+ const error = `No handler for job "${task.jobName}" on queue "${queueName}". Available: ${Array.from(handlers.keys()).join(", ")}`;
266
+ console.error(error);
267
+ throw new Error(error);
268
+ }
269
+ const storedJob = this.jobs.get(task.jobId);
270
+ if (!storedJob) {
271
+ throw new Error(`Job ${task.jobId} not found`);
272
+ }
273
+ const currentAttempts = storedJob.attemptsMade || 0;
274
+ const maxAttempts = storedJob.opts?.attempts || 1;
275
+ await this.updateJobState(queueName, task.jobId, "active", {
276
+ processedOn: Date.now()
277
+ });
278
+ this.emitEvent(queueName, "active", { jobId: task.jobId });
279
+ try {
280
+ const jobLike = {
281
+ id: task.jobId,
282
+ name: task.jobName,
283
+ data: task.data,
284
+ attemptsMade: currentAttempts,
285
+ opts: { attempts: maxAttempts, ...storedJob.opts }
286
+ };
287
+ const result = await handler2(jobLike, {});
288
+ await this.updateJobState(queueName, task.jobId, "completed", {
289
+ returnvalue: result,
290
+ finishedOn: Date.now()
291
+ });
292
+ this.emitEvent(queueName, "completed", { jobId: task.jobId, returnvalue: result });
293
+ await this.deleteJobFile(queueName, task.jobId);
294
+ return result;
295
+ } catch (err) {
296
+ const newAttemptCount = currentAttempts + 1;
297
+ if (newAttemptCount < maxAttempts) {
298
+ let retryDelay = 0;
299
+ if (storedJob.opts?.backoff) {
300
+ const { type, delay } = storedJob.opts.backoff;
301
+ if (type === "exponential") {
302
+ retryDelay = delay * Math.pow(2, newAttemptCount - 1);
303
+ } else {
304
+ retryDelay = delay;
305
+ }
306
+ }
307
+ await this.updateJobState(queueName, task.jobId, "waiting", {
308
+ attemptsMade: newAttemptCount,
309
+ failedReason: `Attempt ${newAttemptCount}/${maxAttempts} failed: ${err.message}`
310
+ });
311
+ console.info(
312
+ `[FileQueue] Retrying job ${task.jobId} (attempt ${newAttemptCount + 1}/${maxAttempts}) after ${retryDelay}ms delay`
313
+ );
314
+ const currentWorkerInfo = this.workers.get(queueName);
315
+ if (currentWorkerInfo) {
316
+ setTimeout(() => {
317
+ currentWorkerInfo.queue.push(task).catch(() => {
318
+ });
319
+ }, retryDelay);
320
+ }
321
+ throw err;
322
+ }
323
+ await this.updateJobState(queueName, task.jobId, "failed", {
324
+ failedReason: `Failed after ${newAttemptCount} attempts: ${err.message}`,
325
+ finishedOn: Date.now(),
326
+ attemptsMade: newAttemptCount
327
+ });
328
+ this.emitEvent(queueName, "failed", {
329
+ jobId: task.jobId,
330
+ failedReason: err.message,
331
+ attemptsMade: newAttemptCount,
332
+ maxAttempts
333
+ });
334
+ throw err;
335
+ }
336
+ };
337
+ const concurrency = opts?.concurrency || 1;
338
+ const queue = fastq.promise(dispatcher, concurrency);
339
+ const shouldPause = opts?.autorun === false;
340
+ workerInfo = {
341
+ queue,
342
+ handlers,
343
+ paused: shouldPause || false,
344
+ concurrency
345
+ };
346
+ if (shouldPause) {
347
+ queue.pause();
348
+ console.info(`[FileQueue] Worker for "${queueName}" created but paused`);
349
+ }
350
+ this.workers.set(queueName, workerInfo);
351
+ }
352
+ /**
353
+ * Start processing waiting jobs for a queue
354
+ * Should be called after all handlers are registered
355
+ */
356
+ startProcessingQueue(queueName) {
357
+ const workerInfo = this.workers.get(queueName);
358
+ if (!workerInfo || workerInfo.paused) return;
359
+ const waitingJobs = Array.from(this.jobs.values()).filter(
360
+ (j) => j.state === "waiting" && j.data?.__queueName === queueName
361
+ );
362
+ for (const job of waitingJobs) {
363
+ workerInfo.queue.push({ jobId: job.id, jobName: job.name, data: job.data }).catch(() => {
364
+ });
365
+ }
366
+ }
367
+ // ============================================================
368
+ // Helper Methods
369
+ // ============================================================
370
+ async updateJobState(queueName, jobId, state, extra) {
371
+ const job = this.jobs.get(jobId);
372
+ if (job) {
373
+ job.state = state;
374
+ if (extra) {
375
+ Object.assign(job, extra);
376
+ }
377
+ await this.persistJob(queueName, job);
378
+ }
379
+ }
380
+ emitEvent(queueName, event, payload) {
381
+ const key = `${queueName}:${event}`;
382
+ const listeners = this.eventListeners.get(key) || [];
383
+ for (const callback of listeners) {
384
+ try {
385
+ callback(payload);
386
+ } catch (error) {
387
+ console.error(`[FileQueueAdapter] Error in event listener for ${key}:`, error);
388
+ }
389
+ }
390
+ }
391
+ generateId() {
392
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
393
+ }
394
+ scheduleCronJob(scheduleId, scheduledJob) {
395
+ const cronPattern = scheduledJob.opts.cron || scheduledJob.opts.repeat?.pattern;
396
+ if (!cronPattern) {
397
+ console.error(`[FileQueue] No cron pattern found for schedule ${scheduleId}`);
398
+ return;
399
+ }
400
+ try {
401
+ const interval = cronParser.parseExpression(cronPattern);
402
+ const nextRun = interval.next().toDate();
403
+ const now = /* @__PURE__ */ new Date();
404
+ const delay = nextRun.getTime() - now.getTime();
405
+ console.info(
406
+ `[FileQueue] Next run for schedule ${scheduleId}: ${nextRun.toISOString()} (in ${Math.round(delay / 1e3)}s)`
407
+ );
408
+ scheduledJob.timerId = setTimeout(async () => {
409
+ const limit = scheduledJob.opts.repeat?.limit;
410
+ if (limit !== void 0 && scheduledJob.repeatCount >= limit) {
411
+ console.info(`[FileQueue] Schedule ${scheduleId} reached repeat limit (${limit})`);
412
+ this.scheduledJobs.delete(scheduleId);
413
+ return;
414
+ }
415
+ try {
416
+ await this.enqueue(scheduledJob.queueName, scheduledJob.job);
417
+ scheduledJob.repeatCount++;
418
+ const shouldContinue = limit === void 0 || scheduledJob.repeatCount < limit;
419
+ if (shouldContinue) {
420
+ this.scheduleCronJob(scheduleId, scheduledJob);
421
+ } else {
422
+ console.info(`[FileQueue] Schedule ${scheduleId} completed all ${limit} runs`);
423
+ this.scheduledJobs.delete(scheduleId);
424
+ }
425
+ } catch (error) {
426
+ console.error(`[FileQueue] Error executing scheduled job ${scheduleId}:`, error);
427
+ this.scheduleCronJob(scheduleId, scheduledJob);
428
+ }
429
+ }, delay);
430
+ } catch (error) {
431
+ console.error(`[FileQueue] Error parsing cron expression "${cronPattern}":`, error);
432
+ throw new Error(`Invalid cron pattern: ${cronPattern}`);
433
+ }
434
+ }
435
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * File Store Adapter
3
+ *
4
+ * File-based storage for development/small deployments
5
+ * - Extends MemoryStoreAdapter with persistence
6
+ * - Fast in-memory access backed by file system
7
+ * - Survives restarts
8
+ * - Single instance only
9
+ *
10
+ * Storage format (matches existing file adapter):
11
+ * - {dataDir}/{subject}.ndjson - Event streams (append-only NDJSON)
12
+ * - {dataDir}/indices/{key}.json - Sorted indices (JSON arrays)
13
+ * - {dataDir}/docs/{collection}/{id}.json - Documents (individual JSON files)
14
+ * - {dataDir}/kv/{key}.json - KV store (individual JSON files)
15
+ */
16
+ import { MemoryStoreAdapter, type MemoryStoreAdapterOptions } from './memory-store.js';
17
+ export interface FileStoreAdapterOptions extends MemoryStoreAdapterOptions {
18
+ dataDir: string;
19
+ }
20
+ /**
21
+ * File-backed store adapter
22
+ * Extends memory store and persists on every write using same format as existing file adapter
23
+ */
24
+ export declare class FileStoreAdapter extends MemoryStoreAdapter {
25
+ private options;
26
+ private parentKvGet;
27
+ private parentKvSet;
28
+ private parentKvDelete;
29
+ private parentKvClear;
30
+ private parentKvIncrement;
31
+ constructor(options: FileStoreAdapterOptions);
32
+ private streamPath;
33
+ private indexPath;
34
+ private docPath;
35
+ private kvPath;
36
+ init(): Promise<void>;
37
+ private loadFromDisk;
38
+ append(subject: string, event: Omit<import('../interfaces/store').EventRecord, 'id' | 'ts'>): Promise<import("..").EventRecord>;
39
+ save(collection: string, id: string, doc: Record<string, any>): Promise<void>;
40
+ delete(collection: string, id: string): Promise<void>;
41
+ indexAdd(key: string, id: string, score: number, metadata?: Record<string, any>): Promise<void>;
42
+ indexUpdate(key: string, id: string, metadata: Record<string, any>): Promise<boolean>;
43
+ indexUpdateWithRetry(key: string, id: string, metadata: Record<string, any>, maxRetries?: number): Promise<void>;
44
+ indexIncrement(key: string, id: string, field: string, increment?: number): Promise<number>;
45
+ close(): Promise<void>;
46
+ }
@@ -0,0 +1,225 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { MemoryStoreAdapter } from "./memory-store.js";
4
+ function sanitize(name) {
5
+ return name.replace(/[^\w.-]/g, "_");
6
+ }
7
+ async function ensureDir(path) {
8
+ try {
9
+ await fs.mkdir(path, { recursive: true });
10
+ } catch {
11
+ }
12
+ }
13
+ export class FileStoreAdapter extends MemoryStoreAdapter {
14
+ options;
15
+ parentKvGet;
16
+ parentKvSet;
17
+ parentKvDelete;
18
+ parentKvClear;
19
+ parentKvIncrement;
20
+ constructor(options) {
21
+ super(options);
22
+ this.options = options;
23
+ const tempKv = this.kv;
24
+ this.parentKvGet = tempKv.get;
25
+ this.parentKvSet = tempKv.set;
26
+ this.parentKvDelete = tempKv.delete;
27
+ this.parentKvClear = tempKv.clear;
28
+ this.parentKvIncrement = tempKv.increment;
29
+ this.kv = {
30
+ get: async (key) => {
31
+ return this.parentKvGet(key);
32
+ },
33
+ set: async (key, value, ttl) => {
34
+ await this.parentKvSet(key, value, ttl);
35
+ const path = this.kvPath(key);
36
+ await ensureDir(dirname(path));
37
+ await fs.writeFile(path, JSON.stringify(value), "utf-8");
38
+ },
39
+ delete: async (key) => {
40
+ await this.parentKvDelete(key);
41
+ const path = this.kvPath(key);
42
+ try {
43
+ await fs.unlink(path);
44
+ } catch {
45
+ }
46
+ },
47
+ clear: async (pattern) => {
48
+ const count = await this.parentKvClear(pattern);
49
+ try {
50
+ const kvDir = join(this.options.dataDir, "kv");
51
+ const files = await fs.readdir(kvDir);
52
+ const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
53
+ for (const file of files) {
54
+ if (!file.endsWith(".json")) continue;
55
+ const key = file.replace(".json", "").replace(/_/g, ":");
56
+ if (regex.test(key)) {
57
+ await fs.unlink(join(kvDir, file));
58
+ }
59
+ }
60
+ } catch {
61
+ }
62
+ return count;
63
+ },
64
+ increment: async (key, by = 1) => {
65
+ const result = await this.parentKvIncrement(key, by);
66
+ const path = this.kvPath(key);
67
+ await ensureDir(dirname(path));
68
+ await fs.writeFile(path, JSON.stringify(result), "utf-8");
69
+ return result;
70
+ }
71
+ };
72
+ }
73
+ streamPath(subject) {
74
+ return join(this.options.dataDir, "streams", sanitize(subject) + ".ndjson");
75
+ }
76
+ indexPath(key) {
77
+ return join(this.options.dataDir, "indices", sanitize(key) + ".json");
78
+ }
79
+ docPath(collection, id) {
80
+ return join(this.options.dataDir, "docs", collection, `${id}.json`);
81
+ }
82
+ kvPath(key) {
83
+ return join(this.options.dataDir, "kv", `${sanitize(key)}.json`);
84
+ }
85
+ async init() {
86
+ await ensureDir(this.options.dataDir);
87
+ await ensureDir(join(this.options.dataDir, "streams"));
88
+ await ensureDir(join(this.options.dataDir, "indices"));
89
+ await ensureDir(join(this.options.dataDir, "docs"));
90
+ await ensureDir(join(this.options.dataDir, "kv"));
91
+ await this.loadFromDisk();
92
+ }
93
+ async loadFromDisk() {
94
+ const self = this;
95
+ try {
96
+ const streamsDir = join(this.options.dataDir, "streams");
97
+ const files = await fs.readdir(streamsDir);
98
+ for (const file of files) {
99
+ if (!file.endsWith(".ndjson")) continue;
100
+ const subject = file.replace(".ndjson", "").replace(/_/g, ":");
101
+ const content = await fs.readFile(join(streamsDir, file), "utf-8");
102
+ const lines = content.trim().split("\n").filter((l) => l.length > 0);
103
+ const events = lines.map((line) => JSON.parse(line));
104
+ if (!self.eventStreams) self.eventStreams = /* @__PURE__ */ new Map();
105
+ self.eventStreams.set(subject, events);
106
+ }
107
+ } catch {
108
+ }
109
+ try {
110
+ const indicesDir = join(this.options.dataDir, "indices");
111
+ const indexFiles = await fs.readdir(indicesDir);
112
+ for (const file of indexFiles) {
113
+ if (!file.endsWith(".json")) continue;
114
+ const key = file.replace(".json", "").replace(/_/g, ":");
115
+ const content = await fs.readFile(join(indicesDir, file), "utf-8");
116
+ const entries = JSON.parse(content);
117
+ if (!self.sortedIndices) self.sortedIndices = /* @__PURE__ */ new Map();
118
+ self.sortedIndices.set(key, entries);
119
+ }
120
+ } catch {
121
+ }
122
+ try {
123
+ const docsDir = join(this.options.dataDir, "docs");
124
+ const collections = await fs.readdir(docsDir);
125
+ for (const collection of collections) {
126
+ const collectionDir = join(docsDir, collection);
127
+ const docFiles = await fs.readdir(collectionDir);
128
+ if (!self.documents) self.documents = /* @__PURE__ */ new Map();
129
+ if (!self.documents.has(collection)) {
130
+ self.documents.set(collection, /* @__PURE__ */ new Map());
131
+ }
132
+ const collectionMap = self.documents.get(collection);
133
+ for (const file of docFiles) {
134
+ if (!file.endsWith(".json")) continue;
135
+ const id = file.replace(".json", "");
136
+ const content = await fs.readFile(join(collectionDir, file), "utf-8");
137
+ const doc = JSON.parse(content);
138
+ collectionMap.set(id, doc);
139
+ }
140
+ }
141
+ } catch {
142
+ }
143
+ try {
144
+ const kvDir = join(this.options.dataDir, "kv");
145
+ const kvFiles = await fs.readdir(kvDir);
146
+ for (const file of kvFiles) {
147
+ if (!file.endsWith(".json")) continue;
148
+ const key = file.replace(".json", "").replace(/_/g, ":");
149
+ const content = await fs.readFile(join(kvDir, file), "utf-8");
150
+ const value = JSON.parse(content);
151
+ if (!self.kvStore) self.kvStore = /* @__PURE__ */ new Map();
152
+ self.kvStore.set(key, value);
153
+ }
154
+ } catch {
155
+ }
156
+ }
157
+ // Override methods to add persistence
158
+ async append(subject, event) {
159
+ const result = await super.append(subject, event);
160
+ const path = this.streamPath(subject);
161
+ await ensureDir(dirname(path));
162
+ await fs.appendFile(path, JSON.stringify(result) + "\n", "utf-8");
163
+ return result;
164
+ }
165
+ async save(collection, id, doc) {
166
+ await super.save(collection, id, doc);
167
+ const path = this.docPath(collection, id);
168
+ await ensureDir(dirname(path));
169
+ await fs.writeFile(path, JSON.stringify(doc, null, 2), "utf-8");
170
+ }
171
+ async delete(collection, id) {
172
+ await super.delete(collection, id);
173
+ const path = this.docPath(collection, id);
174
+ try {
175
+ await fs.unlink(path);
176
+ } catch {
177
+ }
178
+ }
179
+ // Override index operations
180
+ async indexAdd(key, id, score, metadata) {
181
+ await super.indexAdd(key, id, score, metadata);
182
+ const self = this;
183
+ const index = self.sortedIndices?.get(key);
184
+ if (index) {
185
+ const path = this.indexPath(key);
186
+ await ensureDir(dirname(path));
187
+ await fs.writeFile(path, JSON.stringify(index, null, 2), "utf-8");
188
+ }
189
+ }
190
+ async indexUpdate(key, id, metadata) {
191
+ const result = await super.indexUpdate(key, id, metadata);
192
+ const self = this;
193
+ const index = self.sortedIndices?.get(key);
194
+ if (index) {
195
+ const path = this.indexPath(key);
196
+ await ensureDir(dirname(path));
197
+ await fs.writeFile(path, JSON.stringify(index, null, 2), "utf-8");
198
+ }
199
+ return result;
200
+ }
201
+ async indexUpdateWithRetry(key, id, metadata, maxRetries) {
202
+ await super.indexUpdateWithRetry(key, id, metadata, maxRetries);
203
+ const self = this;
204
+ const index = self.sortedIndices?.get(key);
205
+ if (index) {
206
+ const path = this.indexPath(key);
207
+ await ensureDir(dirname(path));
208
+ await fs.writeFile(path, JSON.stringify(index, null, 2), "utf-8");
209
+ }
210
+ }
211
+ async indexIncrement(key, id, field, increment) {
212
+ const result = await super.indexIncrement(key, id, field, increment);
213
+ const self = this;
214
+ const index = self.sortedIndices?.get(key);
215
+ if (index) {
216
+ const path = this.indexPath(key);
217
+ await ensureDir(dirname(path));
218
+ await fs.writeFile(path, JSON.stringify(index, null, 2), "utf-8");
219
+ }
220
+ return result;
221
+ }
222
+ async close() {
223
+ await super.close();
224
+ }
225
+ }