nvent 0.4.4 → 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.
- package/dist/module.d.mts +3 -184
- package/dist/module.json +3 -3
- package/dist/module.mjs +133 -197
- package/dist/runtime/adapters/builtin/file-queue.d.ts +53 -0
- package/dist/runtime/adapters/builtin/file-queue.js +435 -0
- package/dist/runtime/adapters/builtin/file-store.d.ts +46 -0
- package/dist/runtime/adapters/builtin/file-store.js +225 -0
- package/dist/runtime/adapters/builtin/file-stream.d.ts +39 -0
- package/dist/runtime/adapters/builtin/file-stream.js +56 -0
- package/dist/runtime/adapters/builtin/index.d.ts +10 -0
- package/dist/runtime/adapters/builtin/index.js +5 -0
- package/dist/runtime/adapters/builtin/memory-queue.d.ts +52 -0
- package/dist/runtime/adapters/builtin/memory-queue.js +239 -0
- package/dist/runtime/adapters/builtin/memory-store.d.ts +57 -0
- package/dist/runtime/adapters/builtin/memory-store.js +263 -0
- package/dist/runtime/adapters/builtin/memory-stream.d.ts +21 -0
- package/dist/runtime/adapters/builtin/memory-stream.js +56 -0
- package/dist/runtime/adapters/factory.d.ts +31 -0
- package/dist/runtime/adapters/factory.js +100 -0
- package/dist/runtime/adapters/index.d.ts +8 -0
- package/dist/runtime/adapters/index.js +3 -0
- package/dist/runtime/adapters/interfaces/index.d.ts +11 -0
- package/dist/runtime/adapters/interfaces/index.js +3 -0
- package/dist/runtime/adapters/interfaces/queue.d.ts +150 -0
- package/dist/runtime/adapters/interfaces/store.d.ts +233 -0
- package/dist/runtime/adapters/interfaces/stream.d.ts +62 -0
- package/dist/runtime/adapters/registry.d.ts +85 -0
- package/dist/runtime/adapters/registry.js +161 -0
- package/dist/runtime/config/index.d.ts +29 -0
- package/dist/runtime/config/index.js +167 -0
- package/dist/runtime/config/types.d.ts +367 -0
- package/dist/runtime/config/types.js +0 -0
- package/dist/runtime/events/types.d.ts +116 -0
- package/dist/runtime/events/types.js +0 -0
- package/dist/runtime/events/utils/stallDetector.d.ts +99 -0
- package/dist/runtime/events/utils/stallDetector.js +237 -0
- package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.d.ts +3 -8
- package/dist/runtime/{server-utils/events → events}/wiring/flowWiring.js +119 -36
- package/dist/runtime/events/wiring/registry.d.ts +19 -0
- package/dist/runtime/events/wiring/registry.js +33 -0
- package/dist/runtime/events/wiring/stateWiring.d.ts +37 -0
- package/dist/runtime/events/wiring/stateWiring.js +92 -0
- package/dist/runtime/events/wiring/streamWiring.d.ts +32 -0
- package/dist/runtime/events/wiring/streamWiring.js +79 -0
- package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +16 -5
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +21 -0
- package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +12 -2
- package/dist/runtime/server/api/_flows/[name]/runs.get.js +15 -4
- package/dist/runtime/server/api/_flows/[name]/schedule.post.js +11 -2
- package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +21 -16
- package/dist/runtime/server/api/_flows/[name]/schedules.get.js +21 -19
- package/dist/runtime/server/api/_flows/ws.js +43 -22
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +8 -3
- package/dist/runtime/server/api/_queues/[name]/job/index.get.js +12 -3
- package/dist/runtime/server/api/_queues/index.get.js +66 -23
- package/dist/runtime/server/api/_queues/ws.js +14 -4
- package/dist/runtime/server/plugins/00.adapters.d.ts +14 -0
- package/dist/runtime/server/plugins/00.adapters.js +69 -0
- package/dist/runtime/server/plugins/02.workers.js +45 -0
- package/dist/runtime/tsconfig.json +8 -0
- package/dist/runtime/utils/adapters.d.ts +66 -0
- package/dist/runtime/utils/adapters.js +51 -0
- package/dist/runtime/utils/defineFunction.d.ts +10 -0
- package/dist/runtime/{server-utils/utils/defineQueueWorker.js → utils/defineFunction.js} +4 -4
- package/dist/runtime/{server-utils/utils/defineQueueConfig.d.ts → utils/defineFunctionConfig.d.ts} +3 -3
- package/dist/runtime/utils/defineFunctionConfig.js +2 -0
- package/dist/runtime/utils/registerAdapter.d.ts +59 -0
- package/dist/runtime/utils/registerAdapter.js +13 -0
- package/dist/runtime/utils/useFlowEngine.d.ts +19 -0
- package/dist/runtime/utils/useFlowEngine.js +108 -0
- package/dist/runtime/{server-utils/utils → utils}/useNventLogger.js +2 -2
- package/dist/runtime/utils/useStreamTopics.d.ts +72 -0
- package/dist/runtime/utils/useStreamTopics.js +47 -0
- package/dist/runtime/{server-utils/worker/runner/node.d.ts → worker/node/runner.d.ts} +18 -2
- package/dist/runtime/{server-utils/worker/runner/node.js → worker/node/runner.js} +44 -17
- package/dist/types.d.mts +2 -2
- package/package.json +14 -44
- package/LICENSE +0 -21
- package/README.md +0 -389
- package/dist/runtime/app/assets/vueflow.css +0 -1
- package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +0 -33
- package/dist/runtime/app/components/ConfirmDialog.vue +0 -121
- package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +0 -33
- package/dist/runtime/app/components/FlowDiagram.d.vue.ts +0 -64
- package/dist/runtime/app/components/FlowDiagram.vue +0 -338
- package/dist/runtime/app/components/FlowDiagram.vue.d.ts +0 -64
- package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +0 -29
- package/dist/runtime/app/components/FlowNodeCard.vue +0 -156
- package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +0 -29
- package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +0 -9
- package/dist/runtime/app/components/FlowRunOverview.vue +0 -291
- package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +0 -9
- package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +0 -14
- package/dist/runtime/app/components/FlowRunStatusBadge.vue +0 -60
- package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +0 -14
- package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +0 -12
- package/dist/runtime/app/components/FlowRunTimeline.vue +0 -127
- package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +0 -12
- package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +0 -16
- package/dist/runtime/app/components/FlowScheduleDialog.vue +0 -226
- package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +0 -16
- package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +0 -12
- package/dist/runtime/app/components/FlowSchedulesList.vue +0 -99
- package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +0 -12
- package/dist/runtime/app/components/JobScheduling.d.vue.ts +0 -6
- package/dist/runtime/app/components/JobScheduling.vue +0 -203
- package/dist/runtime/app/components/JobScheduling.vue.d.ts +0 -6
- package/dist/runtime/app/components/ListItem.d.vue.ts +0 -23
- package/dist/runtime/app/components/ListItem.vue +0 -70
- package/dist/runtime/app/components/ListItem.vue.d.ts +0 -23
- package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +0 -45
- package/dist/runtime/app/components/QueueConfigDetails.vue +0 -412
- package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +0 -45
- package/dist/runtime/app/components/StatCounter.d.vue.ts +0 -9
- package/dist/runtime/app/components/StatCounter.vue +0 -25
- package/dist/runtime/app/components/StatCounter.vue.d.ts +0 -9
- package/dist/runtime/app/components/TimelineList.d.vue.ts +0 -7
- package/dist/runtime/app/components/TimelineList.vue +0 -210
- package/dist/runtime/app/components/TimelineList.vue.d.ts +0 -7
- package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +0 -46
- package/dist/runtime/app/components/nhealth/component-router.vue +0 -26
- package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +0 -46
- package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +0 -24
- package/dist/runtime/app/components/nhealth/component-shell.vue +0 -89
- package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +0 -24
- package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +0 -14
- package/dist/runtime/app/composables/useAnalyzedFlows.js +0 -8
- package/dist/runtime/app/composables/useComponentRouter.d.ts +0 -38
- package/dist/runtime/app/composables/useComponentRouter.js +0 -240
- package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +0 -80
- package/dist/runtime/app/composables/useFlowRunTimeline.js +0 -68
- package/dist/runtime/app/composables/useFlowRuns.d.ts +0 -18
- package/dist/runtime/app/composables/useFlowRuns.js +0 -32
- package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +0 -24
- package/dist/runtime/app/composables/useFlowRunsInfinite.js +0 -123
- package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +0 -9
- package/dist/runtime/app/composables/useFlowRunsPolling.js +0 -33
- package/dist/runtime/app/composables/useFlowState.d.ts +0 -125
- package/dist/runtime/app/composables/useFlowState.js +0 -211
- package/dist/runtime/app/composables/useFlowWebSocket.d.ts +0 -27
- package/dist/runtime/app/composables/useFlowWebSocket.js +0 -205
- package/dist/runtime/app/composables/useFlowsNavigation.d.ts +0 -10
- package/dist/runtime/app/composables/useFlowsNavigation.js +0 -58
- package/dist/runtime/app/composables/useQueueJobs.d.ts +0 -26
- package/dist/runtime/app/composables/useQueueJobs.js +0 -20
- package/dist/runtime/app/composables/useQueueUpdates.d.ts +0 -26
- package/dist/runtime/app/composables/useQueueUpdates.js +0 -122
- package/dist/runtime/app/composables/useQueues.d.ts +0 -45
- package/dist/runtime/app/composables/useQueues.js +0 -26
- package/dist/runtime/app/composables/useQueuesLive.d.ts +0 -19
- package/dist/runtime/app/composables/useQueuesLive.js +0 -143
- package/dist/runtime/app/pages/flows/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/flows/index.vue +0 -645
- package/dist/runtime/app/pages/flows/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/index.vue +0 -34
- package/dist/runtime/app/pages/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/index.vue +0 -229
- package/dist/runtime/app/pages/queues/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/job.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/job.vue +0 -262
- package/dist/runtime/app/pages/queues/job.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/jobs.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/jobs.vue +0 -291
- package/dist/runtime/app/pages/queues/jobs.vue.d.ts +0 -3
- package/dist/runtime/app/plugins/vueflow.client.d.ts +0 -2
- package/dist/runtime/app/plugins/vueflow.client.js +0 -11
- package/dist/runtime/constants.d.ts +0 -11
- package/dist/runtime/constants.js +0 -11
- package/dist/runtime/schema.d.ts +0 -37
- package/dist/runtime/schema.js +0 -20
- package/dist/runtime/server/plugins/00.event-store.d.ts +0 -13
- package/dist/runtime/server/plugins/00.event-store.js +0 -16
- package/dist/runtime/server/plugins/flow-management.d.ts +0 -13
- package/dist/runtime/server/plugins/flow-management.js +0 -65
- package/dist/runtime/server/plugins/queue-management.js +0 -27
- package/dist/runtime/server/plugins/state-cleanup.d.ts +0 -11
- package/dist/runtime/server/plugins/state-cleanup.js +0 -93
- package/dist/runtime/server/plugins/worker-management.js +0 -33
- package/dist/runtime/server/tsconfig.json +0 -3
- package/dist/runtime/server-utils/events/adapters/fileAdapter.d.ts +0 -2
- package/dist/runtime/server-utils/events/adapters/fileAdapter.js +0 -382
- package/dist/runtime/server-utils/events/adapters/memoryAdapter.d.ts +0 -2
- package/dist/runtime/server-utils/events/adapters/memoryAdapter.js +0 -171
- package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.d.ts +0 -2
- package/dist/runtime/server-utils/events/adapters/redis/redisAdapter.js +0 -348
- package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.d.ts +0 -30
- package/dist/runtime/server-utils/events/adapters/redis/redisPubSubGateway.js +0 -82
- package/dist/runtime/server-utils/events/eventStoreFactory.d.ts +0 -19
- package/dist/runtime/server-utils/events/eventStoreFactory.js +0 -44
- package/dist/runtime/server-utils/events/streamNames.d.ts +0 -17
- package/dist/runtime/server-utils/events/streamNames.js +0 -17
- package/dist/runtime/server-utils/events/types.d.ts +0 -63
- package/dist/runtime/server-utils/events/wiring/registry.d.ts +0 -10
- package/dist/runtime/server-utils/events/wiring/registry.js +0 -24
- package/dist/runtime/server-utils/queue/adapters/bullmq.d.ts +0 -18
- package/dist/runtime/server-utils/queue/adapters/bullmq.js +0 -164
- package/dist/runtime/server-utils/queue/queueFactory.d.ts +0 -3
- package/dist/runtime/server-utils/queue/queueFactory.js +0 -10
- package/dist/runtime/server-utils/queue/types.d.ts +0 -47
- package/dist/runtime/server-utils/state/adapters/redis.d.ts +0 -2
- package/dist/runtime/server-utils/state/adapters/redis.js +0 -42
- package/dist/runtime/server-utils/state/stateFactory.d.ts +0 -3
- package/dist/runtime/server-utils/state/stateFactory.js +0 -17
- package/dist/runtime/server-utils/state/types.d.ts +0 -23
- package/dist/runtime/server-utils/utils/defineQueueConfig.js +0 -2
- package/dist/runtime/server-utils/utils/defineQueueWorker.d.ts +0 -10
- package/dist/runtime/server-utils/utils/useEventStore.d.ts +0 -20
- package/dist/runtime/server-utils/utils/useEventStore.js +0 -119
- package/dist/runtime/server-utils/utils/useFlowEngine.d.ts +0 -9
- package/dist/runtime/server-utils/utils/useFlowEngine.js +0 -44
- package/dist/runtime/server-utils/utils/useLogs.d.ts +0 -41
- package/dist/runtime/server-utils/utils/useLogs.js +0 -74
- package/dist/runtime/server-utils/utils/useQueue.d.ts +0 -31
- package/dist/runtime/server-utils/utils/useQueue.js +0 -24
- package/dist/runtime/server-utils/worker/adapter.d.ts +0 -4
- package/dist/runtime/server-utils/worker/adapter.js +0 -66
- package/dist/runtime/types.d.ts +0 -132
- /package/dist/runtime/{server-utils/events/types.js → adapters/interfaces/queue.js} +0 -0
- /package/dist/runtime/{server-utils/queue/types.js → adapters/interfaces/store.js} +0 -0
- /package/dist/runtime/{server-utils/state/types.js → adapters/interfaces/stream.js} +0 -0
- /package/dist/runtime/{server-utils/events → events}/eventBus.d.ts +0 -0
- /package/dist/runtime/{server-utils/events → events}/eventBus.js +0 -0
- /package/dist/runtime/server/{plugins/queue-management.d.ts → api/_flows/[name]/runs/[runId]/cancel.post.d.ts} +0 -0
- /package/dist/runtime/server/plugins/{00.ws-lifecycle.d.ts → 01.ws-lifecycle.d.ts} +0 -0
- /package/dist/runtime/server/plugins/{00.ws-lifecycle.js → 01.ws-lifecycle.js} +0 -0
- /package/dist/runtime/server/plugins/{worker-management.d.ts → 02.workers.d.ts} +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/useEventManager.d.ts +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/useEventManager.js +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/useNventLogger.d.ts +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.d.ts +0 -0
- /package/dist/runtime/{server-utils/utils → utils}/wsPeerManager.js +0 -0
- /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
|
+
}
|