@stepflowjs/storage-mongodb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +33 -0
- package/dist/index.js +277 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { StorageAdapter, QueueOperations, ExecutionOperations, EventOperations, LeaderOperations, RealtimeOperations } from '@stepflowjs/core/storage';
|
|
2
|
+
|
|
3
|
+
interface MongoStorageOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
database?: string;
|
|
6
|
+
collectionPrefix?: string;
|
|
7
|
+
}
|
|
8
|
+
declare class MongoStorageAdapter implements StorageAdapter {
|
|
9
|
+
private options;
|
|
10
|
+
private client;
|
|
11
|
+
private db;
|
|
12
|
+
private connected;
|
|
13
|
+
private emitter;
|
|
14
|
+
private changeStream?;
|
|
15
|
+
private jobs;
|
|
16
|
+
private executions;
|
|
17
|
+
private stepResults;
|
|
18
|
+
private eventWaiters;
|
|
19
|
+
private leaderLocks;
|
|
20
|
+
constructor(options: MongoStorageOptions);
|
|
21
|
+
private getCollectionName;
|
|
22
|
+
connect(): Promise<void>;
|
|
23
|
+
private createIndexes;
|
|
24
|
+
disconnect(): Promise<void>;
|
|
25
|
+
healthCheck(): Promise<boolean>;
|
|
26
|
+
queue: QueueOperations;
|
|
27
|
+
execution: ExecutionOperations;
|
|
28
|
+
events: EventOperations;
|
|
29
|
+
leader: LeaderOperations;
|
|
30
|
+
realtime: RealtimeOperations;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { MongoStorageAdapter, type MongoStorageOptions, MongoStorageAdapter as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import {
|
|
4
|
+
MongoClient
|
|
5
|
+
} from "mongodb";
|
|
6
|
+
var MongoStorageAdapter = class {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.client = new MongoClient(options.url);
|
|
10
|
+
}
|
|
11
|
+
client;
|
|
12
|
+
db;
|
|
13
|
+
connected = false;
|
|
14
|
+
emitter = new EventEmitter();
|
|
15
|
+
changeStream;
|
|
16
|
+
// Collections
|
|
17
|
+
jobs;
|
|
18
|
+
executions;
|
|
19
|
+
stepResults;
|
|
20
|
+
eventWaiters;
|
|
21
|
+
leaderLocks;
|
|
22
|
+
getCollectionName(name) {
|
|
23
|
+
const prefix = this.options.collectionPrefix || "";
|
|
24
|
+
return `${prefix}${name}`;
|
|
25
|
+
}
|
|
26
|
+
async connect() {
|
|
27
|
+
await this.client.connect();
|
|
28
|
+
this.db = this.client.db(this.options.database || "stepflow");
|
|
29
|
+
this.jobs = this.db.collection(this.getCollectionName("jobs"));
|
|
30
|
+
this.executions = this.db.collection(this.getCollectionName("executions"));
|
|
31
|
+
this.stepResults = this.db.collection(
|
|
32
|
+
this.getCollectionName("stepResults")
|
|
33
|
+
);
|
|
34
|
+
this.eventWaiters = this.db.collection(
|
|
35
|
+
this.getCollectionName("eventWaiters")
|
|
36
|
+
);
|
|
37
|
+
this.leaderLocks = this.db.collection(
|
|
38
|
+
this.getCollectionName("leaderLocks")
|
|
39
|
+
);
|
|
40
|
+
await this.createIndexes();
|
|
41
|
+
this.changeStream = this.executions.watch();
|
|
42
|
+
this.changeStream.on("change", (change) => {
|
|
43
|
+
if (change.operationType === "insert" || change.operationType === "update") {
|
|
44
|
+
const doc = "fullDocument" in change ? change.fullDocument : null;
|
|
45
|
+
if (doc) {
|
|
46
|
+
this.emitter.emit(`execution:${doc.id}`, doc);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
this.connected = true;
|
|
51
|
+
}
|
|
52
|
+
async createIndexes() {
|
|
53
|
+
await this.jobs.createIndex({ workflowId: 1 });
|
|
54
|
+
await this.jobs.createIndex({ scheduledFor: 1 });
|
|
55
|
+
await this.jobs.createIndex({ priority: -1, createdAt: 1 });
|
|
56
|
+
await this.executions.createIndex({ runId: 1 });
|
|
57
|
+
await this.executions.createIndex({ workflowId: 1 });
|
|
58
|
+
await this.executions.createIndex({ status: 1 });
|
|
59
|
+
await this.executions.createIndex(
|
|
60
|
+
{ workflowId: 1, "metadata.idempotencyKey": 1 },
|
|
61
|
+
{ sparse: true }
|
|
62
|
+
);
|
|
63
|
+
await this.stepResults.createIndex(
|
|
64
|
+
{ executionId: 1, stepName: 1 },
|
|
65
|
+
{ unique: true }
|
|
66
|
+
);
|
|
67
|
+
await this.eventWaiters.createIndex({ eventId: 1 });
|
|
68
|
+
await this.leaderLocks.createIndex({ lockId: 1 }, { unique: true });
|
|
69
|
+
await this.leaderLocks.createIndex(
|
|
70
|
+
{ expiresAt: 1 },
|
|
71
|
+
{ expireAfterSeconds: 0 }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
async disconnect() {
|
|
75
|
+
if (this.changeStream) {
|
|
76
|
+
await this.changeStream.close();
|
|
77
|
+
}
|
|
78
|
+
await this.client.close();
|
|
79
|
+
this.emitter.removeAllListeners();
|
|
80
|
+
this.connected = false;
|
|
81
|
+
}
|
|
82
|
+
async healthCheck() {
|
|
83
|
+
if (!this.connected) return false;
|
|
84
|
+
try {
|
|
85
|
+
await this.db.admin().ping();
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Queue Operations
|
|
93
|
+
// ============================================================================
|
|
94
|
+
queue = {
|
|
95
|
+
push: async (job) => {
|
|
96
|
+
await this.jobs.insertOne(job);
|
|
97
|
+
return job.id;
|
|
98
|
+
},
|
|
99
|
+
pop: async (options) => {
|
|
100
|
+
const now = /* @__PURE__ */ new Date();
|
|
101
|
+
const result = await this.jobs.findOneAndUpdate(
|
|
102
|
+
{
|
|
103
|
+
$or: [
|
|
104
|
+
{ scheduledFor: { $lte: now } },
|
|
105
|
+
{ scheduledFor: { $exists: false } }
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{ $set: { lockedAt: now } },
|
|
109
|
+
{
|
|
110
|
+
sort: { priority: -1, createdAt: 1 },
|
|
111
|
+
returnDocument: "after"
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
return result ? result : null;
|
|
115
|
+
},
|
|
116
|
+
ack: async (jobId) => {
|
|
117
|
+
await this.jobs.deleteOne({ id: jobId });
|
|
118
|
+
},
|
|
119
|
+
nack: async (jobId, options) => {
|
|
120
|
+
const update = {
|
|
121
|
+
$inc: { attempts: 1 },
|
|
122
|
+
$unset: { lockedAt: "" }
|
|
123
|
+
};
|
|
124
|
+
if (options?.delay) {
|
|
125
|
+
update.$set = {
|
|
126
|
+
scheduledFor: new Date(Date.now() + options.delay)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
await this.jobs.updateOne({ id: jobId }, update);
|
|
130
|
+
},
|
|
131
|
+
schedule: async (job, executeAt) => {
|
|
132
|
+
job.scheduledFor = executeAt;
|
|
133
|
+
await this.jobs.insertOne(job);
|
|
134
|
+
return job.id;
|
|
135
|
+
},
|
|
136
|
+
getDelayed: async () => {
|
|
137
|
+
const now = /* @__PURE__ */ new Date();
|
|
138
|
+
const results = await this.jobs.find({ scheduledFor: { $gt: now } }).toArray();
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Execution Operations
|
|
144
|
+
// ============================================================================
|
|
145
|
+
execution = {
|
|
146
|
+
create: async (execution) => {
|
|
147
|
+
await this.executions.insertOne(execution);
|
|
148
|
+
this.emitter.emit(`execution:${execution.id}`, execution);
|
|
149
|
+
return execution.id;
|
|
150
|
+
},
|
|
151
|
+
get: async (executionId) => {
|
|
152
|
+
const execution = await this.executions.findOne({ id: executionId });
|
|
153
|
+
return execution ? execution : null;
|
|
154
|
+
},
|
|
155
|
+
getByIdempotencyKey: async (workflowId, idempotencyKey) => {
|
|
156
|
+
const execution = await this.executions.findOne({
|
|
157
|
+
workflowId,
|
|
158
|
+
"metadata.idempotencyKey": idempotencyKey
|
|
159
|
+
});
|
|
160
|
+
return execution ? execution : null;
|
|
161
|
+
},
|
|
162
|
+
update: async (executionId, updates) => {
|
|
163
|
+
await this.executions.updateOne({ id: executionId }, { $set: updates });
|
|
164
|
+
const execution = await this.executions.findOne({ id: executionId });
|
|
165
|
+
if (execution) {
|
|
166
|
+
this.emitter.emit(`execution:${executionId}`, execution);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
list: async (options) => {
|
|
170
|
+
const filter = {};
|
|
171
|
+
if (options.workflowId) {
|
|
172
|
+
filter.workflowId = options.workflowId;
|
|
173
|
+
}
|
|
174
|
+
if (options.status) {
|
|
175
|
+
filter.status = options.status;
|
|
176
|
+
}
|
|
177
|
+
if (options.runId) {
|
|
178
|
+
filter.runId = options.runId;
|
|
179
|
+
}
|
|
180
|
+
const offset = options.offset ?? 0;
|
|
181
|
+
const limit = options.limit ?? 50;
|
|
182
|
+
const results = await this.executions.find(filter).sort({ startedAt: -1 }).skip(offset).limit(limit).toArray();
|
|
183
|
+
return results;
|
|
184
|
+
},
|
|
185
|
+
getStepResult: async (executionId, stepName) => {
|
|
186
|
+
const doc = await this.stepResults.findOne({ executionId, stepName });
|
|
187
|
+
return doc ? doc.result : null;
|
|
188
|
+
},
|
|
189
|
+
saveStepResult: async (executionId, stepName, result) => {
|
|
190
|
+
await this.stepResults.updateOne(
|
|
191
|
+
{ executionId, stepName },
|
|
192
|
+
{ $set: { executionId, stepName, result } },
|
|
193
|
+
{ upsert: true }
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// Event Operations
|
|
199
|
+
// ============================================================================
|
|
200
|
+
events = {
|
|
201
|
+
publish: async (eventId, data) => {
|
|
202
|
+
const waiters = await this.eventWaiters.find({ eventId }).toArray();
|
|
203
|
+
await this.eventWaiters.deleteMany({ eventId });
|
|
204
|
+
for (const waiter of waiters) {
|
|
205
|
+
this.emitter.emit(`event:${eventId}:${waiter.executionId}`, data);
|
|
206
|
+
}
|
|
207
|
+
return waiters.length;
|
|
208
|
+
},
|
|
209
|
+
subscribe: async (eventId, executionId, timeout) => {
|
|
210
|
+
await this.eventWaiters.insertOne({
|
|
211
|
+
eventId,
|
|
212
|
+
executionId,
|
|
213
|
+
timeoutAt: timeout
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
getWaiters: async (eventId) => {
|
|
217
|
+
const waiters = await this.eventWaiters.find({ eventId }).toArray();
|
|
218
|
+
return waiters.map((w) => ({
|
|
219
|
+
executionId: w.executionId,
|
|
220
|
+
timeoutAt: w.timeoutAt
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// Leader Operations
|
|
226
|
+
// ============================================================================
|
|
227
|
+
leader = {
|
|
228
|
+
acquire: async (lockId, ttlSeconds) => {
|
|
229
|
+
const owner = `${process.pid}-${Date.now()}`;
|
|
230
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1e3);
|
|
231
|
+
try {
|
|
232
|
+
await this.leaderLocks.insertOne({
|
|
233
|
+
lockId,
|
|
234
|
+
owner,
|
|
235
|
+
expiresAt
|
|
236
|
+
});
|
|
237
|
+
return true;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (error.code === 11e3) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
release: async (lockId) => {
|
|
246
|
+
await this.leaderLocks.deleteOne({ lockId });
|
|
247
|
+
},
|
|
248
|
+
renew: async (lockId, ttlSeconds) => {
|
|
249
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1e3);
|
|
250
|
+
const result = await this.leaderLocks.updateOne(
|
|
251
|
+
{ lockId },
|
|
252
|
+
{ $set: { expiresAt } }
|
|
253
|
+
);
|
|
254
|
+
return result.matchedCount > 0;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
// ============================================================================
|
|
258
|
+
// Realtime Operations
|
|
259
|
+
// ============================================================================
|
|
260
|
+
realtime = {
|
|
261
|
+
subscribe: (channel, callback) => {
|
|
262
|
+
this.emitter.on(channel, callback);
|
|
263
|
+
return () => {
|
|
264
|
+
this.emitter.off(channel, callback);
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
publish: async (channel, data) => {
|
|
268
|
+
this.emitter.emit(channel, data);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
var index_default = MongoStorageAdapter;
|
|
273
|
+
export {
|
|
274
|
+
MongoStorageAdapter,
|
|
275
|
+
index_default as default
|
|
276
|
+
};
|
|
277
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// ============================================================================\n// MongoDB Storage Adapter\n// Production-ready storage with Change Streams for real-time updates\n// ============================================================================\n\nimport { EventEmitter } from \"events\";\nimport {\n MongoClient,\n type Db,\n type Collection,\n type ChangeStream,\n} from \"mongodb\";\nimport type {\n StorageAdapter,\n QueueOperations,\n ExecutionOperations,\n EventOperations,\n LeaderOperations,\n RealtimeOperations,\n} from \"@stepflowjs/core/storage\";\nimport type {\n QueueJob,\n Execution,\n StepResult,\n EventWaiter,\n ListOptions,\n PopOptions,\n NackOptions,\n Unsubscribe,\n} from \"@stepflowjs/core\";\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface MongoStorageOptions {\n url: string;\n database?: string;\n collectionPrefix?: string;\n}\n\n// ============================================================================\n// MongoDB Storage Adapter\n// ============================================================================\n\nexport class MongoStorageAdapter implements StorageAdapter {\n private client: MongoClient;\n private db!: Db;\n private connected = false;\n private emitter = new EventEmitter();\n private changeStream?: ChangeStream;\n\n // Collections\n private jobs!: Collection<QueueJob>;\n private executions!: Collection<Execution>;\n private stepResults!: Collection<{\n executionId: string;\n stepName: string;\n result: StepResult;\n }>;\n private eventWaiters!: Collection<{\n eventId: string;\n executionId: string;\n timeoutAt?: Date;\n }>;\n private leaderLocks!: Collection<{\n lockId: string;\n owner: string;\n expiresAt: Date;\n }>;\n\n constructor(private options: MongoStorageOptions) {\n this.client = new MongoClient(options.url);\n }\n\n private getCollectionName(name: string): string {\n const prefix = this.options.collectionPrefix || \"\";\n return `${prefix}${name}`;\n }\n\n async connect(): Promise<void> {\n await this.client.connect();\n this.db = this.client.db(this.options.database || \"stepflow\");\n\n // Initialize collections\n this.jobs = this.db.collection(this.getCollectionName(\"jobs\"));\n this.executions = this.db.collection(this.getCollectionName(\"executions\"));\n this.stepResults = this.db.collection(\n this.getCollectionName(\"stepResults\"),\n );\n this.eventWaiters = this.db.collection(\n this.getCollectionName(\"eventWaiters\"),\n );\n this.leaderLocks = this.db.collection(\n this.getCollectionName(\"leaderLocks\"),\n );\n\n // Create indexes\n await this.createIndexes();\n\n // Set up change stream for real-time updates\n this.changeStream = this.executions.watch();\n this.changeStream.on(\"change\", (change: any) => {\n if (\n change.operationType === \"insert\" ||\n change.operationType === \"update\"\n ) {\n const doc = \"fullDocument\" in change ? change.fullDocument : null;\n if (doc) {\n this.emitter.emit(`execution:${doc.id}`, doc);\n }\n }\n });\n\n this.connected = true;\n }\n\n private async createIndexes(): Promise<void> {\n // Jobs collection indexes\n await this.jobs.createIndex({ workflowId: 1 });\n await this.jobs.createIndex({ scheduledFor: 1 });\n await this.jobs.createIndex({ priority: -1, createdAt: 1 });\n\n // Executions collection indexes\n await this.executions.createIndex({ runId: 1 });\n await this.executions.createIndex({ workflowId: 1 });\n await this.executions.createIndex({ status: 1 });\n await this.executions.createIndex(\n { workflowId: 1, \"metadata.idempotencyKey\": 1 },\n { sparse: true },\n );\n\n // Step results collection indexes\n await this.stepResults.createIndex(\n { executionId: 1, stepName: 1 },\n { unique: true },\n );\n\n // Event waiters collection indexes\n await this.eventWaiters.createIndex({ eventId: 1 });\n\n // Leader locks collection indexes\n await this.leaderLocks.createIndex({ lockId: 1 }, { unique: true });\n await this.leaderLocks.createIndex(\n { expiresAt: 1 },\n { expireAfterSeconds: 0 },\n );\n }\n\n async disconnect(): Promise<void> {\n if (this.changeStream) {\n await this.changeStream.close();\n }\n await this.client.close();\n this.emitter.removeAllListeners();\n this.connected = false;\n }\n\n async healthCheck(): Promise<boolean> {\n if (!this.connected) return false;\n try {\n await this.db.admin().ping();\n return true;\n } catch {\n return false;\n }\n }\n\n // ============================================================================\n // Queue Operations\n // ============================================================================\n\n queue: QueueOperations = {\n push: async (job: QueueJob): Promise<string> => {\n await this.jobs.insertOne(job as any);\n return job.id;\n },\n\n pop: async (options?: PopOptions): Promise<QueueJob | null> => {\n const now = new Date();\n const result = await this.jobs.findOneAndUpdate(\n {\n $or: [\n { scheduledFor: { $lte: now } },\n { scheduledFor: { $exists: false } },\n ],\n },\n { $set: { lockedAt: now } },\n {\n sort: { priority: -1, createdAt: 1 },\n returnDocument: \"after\",\n },\n );\n\n return result ? (result as any) : null;\n },\n\n ack: async (jobId: string): Promise<void> => {\n await this.jobs.deleteOne({ id: jobId });\n },\n\n nack: async (jobId: string, options?: NackOptions): Promise<void> => {\n const update: any = {\n $inc: { attempts: 1 },\n $unset: { lockedAt: \"\" },\n };\n\n if (options?.delay) {\n update.$set = {\n scheduledFor: new Date(Date.now() + options.delay),\n };\n }\n\n await this.jobs.updateOne({ id: jobId }, update);\n },\n\n schedule: async (job: QueueJob, executeAt: Date): Promise<string> => {\n job.scheduledFor = executeAt;\n await this.jobs.insertOne(job as any);\n return job.id;\n },\n\n getDelayed: async (): Promise<QueueJob[]> => {\n const now = new Date();\n const results = await this.jobs\n .find({ scheduledFor: { $gt: now } })\n .toArray();\n return results as any[];\n },\n };\n\n // ============================================================================\n // Execution Operations\n // ============================================================================\n\n execution: ExecutionOperations = {\n create: async (execution: Execution): Promise<string> => {\n await this.executions.insertOne(execution as any);\n this.emitter.emit(`execution:${execution.id}`, execution);\n return execution.id;\n },\n\n get: async (executionId: string): Promise<Execution | null> => {\n const execution = await this.executions.findOne({ id: executionId });\n return execution ? (execution as any) : null;\n },\n\n getByIdempotencyKey: async (\n workflowId: string,\n idempotencyKey: string,\n ): Promise<Execution | null> => {\n const execution = await this.executions.findOne({\n workflowId,\n \"metadata.idempotencyKey\": idempotencyKey,\n });\n return execution ? (execution as any) : null;\n },\n\n update: async (\n executionId: string,\n updates: Partial<Execution>,\n ): Promise<void> => {\n await this.executions.updateOne({ id: executionId }, { $set: updates });\n\n // Emit update event\n const execution = await this.executions.findOne({ id: executionId });\n if (execution) {\n this.emitter.emit(`execution:${executionId}`, execution);\n }\n },\n\n list: async (options: ListOptions): Promise<Execution[]> => {\n const filter: any = {};\n\n if (options.workflowId) {\n filter.workflowId = options.workflowId;\n }\n if (options.status) {\n filter.status = options.status;\n }\n if (options.runId) {\n filter.runId = options.runId;\n }\n\n const offset = options.offset ?? 0;\n const limit = options.limit ?? 50;\n\n const results = await this.executions\n .find(filter)\n .sort({ startedAt: -1 })\n .skip(offset)\n .limit(limit)\n .toArray();\n\n return results as any[];\n },\n\n getStepResult: async <T = unknown>(\n executionId: string,\n stepName: string,\n ): Promise<StepResult<T> | null> => {\n const doc = await this.stepResults.findOne({ executionId, stepName });\n return doc ? (doc.result as StepResult<T>) : null;\n },\n\n saveStepResult: async (\n executionId: string,\n stepName: string,\n result: StepResult,\n ): Promise<void> => {\n await this.stepResults.updateOne(\n { executionId, stepName },\n { $set: { executionId, stepName, result } },\n { upsert: true },\n );\n },\n };\n\n // ============================================================================\n // Event Operations\n // ============================================================================\n\n events: EventOperations = {\n publish: async (eventId: string, data: unknown): Promise<number> => {\n const waiters = await this.eventWaiters.find({ eventId }).toArray();\n\n // Delete all waiters for this event\n await this.eventWaiters.deleteMany({ eventId });\n\n // Emit events to all waiters\n for (const waiter of waiters) {\n this.emitter.emit(`event:${eventId}:${waiter.executionId}`, data);\n }\n\n return waiters.length;\n },\n\n subscribe: async (\n eventId: string,\n executionId: string,\n timeout: Date,\n ): Promise<void> => {\n await this.eventWaiters.insertOne({\n eventId,\n executionId,\n timeoutAt: timeout,\n });\n },\n\n getWaiters: async (eventId: string): Promise<EventWaiter[]> => {\n const waiters = await this.eventWaiters.find({ eventId }).toArray();\n return waiters.map((w: any) => ({\n executionId: w.executionId,\n timeoutAt: w.timeoutAt,\n }));\n },\n };\n\n // ============================================================================\n // Leader Operations\n // ============================================================================\n\n leader: LeaderOperations = {\n acquire: async (lockId: string, ttlSeconds: number): Promise<boolean> => {\n const owner = `${process.pid}-${Date.now()}`;\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000);\n\n try {\n await this.leaderLocks.insertOne({\n lockId,\n owner,\n expiresAt,\n });\n return true;\n } catch (error) {\n // Duplicate key error means lock is already held\n if ((error as any).code === 11000) {\n return false;\n }\n throw error;\n }\n },\n\n release: async (lockId: string): Promise<void> => {\n await this.leaderLocks.deleteOne({ lockId });\n },\n\n renew: async (lockId: string, ttlSeconds: number): Promise<boolean> => {\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000);\n const result = await this.leaderLocks.updateOne(\n { lockId },\n { $set: { expiresAt } },\n );\n return result.matchedCount > 0;\n },\n };\n\n // ============================================================================\n // Realtime Operations\n // ============================================================================\n\n realtime: RealtimeOperations = {\n subscribe: (\n channel: string,\n callback: (data: unknown) => void,\n ): Unsubscribe => {\n this.emitter.on(channel, callback);\n return () => {\n this.emitter.off(channel, callback);\n };\n },\n\n publish: async (channel: string, data: unknown): Promise<void> => {\n this.emitter.emit(channel, data);\n },\n };\n}\n\n// Default export\nexport default MongoStorageAdapter;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stepflowjs/storage-mongodb",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MongoDB storage adapter for Stepflow",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"mongodb": "^6.12.0",
|
|
20
|
+
"@stepflowjs/core": "0.0.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.5.1",
|
|
24
|
+
"vitest": "^4.0.17",
|
|
25
|
+
"@stepflowjs/storage-tests": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"author": "Stepflow Contributors",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://stepflow-production.up.railway.app",
|
|
35
|
+
"directory": "packages/storage/mongodb"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://stepflow-production.up.railway.app",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://stepflow-production.up.railway.app"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"stepflow",
|
|
43
|
+
"storage",
|
|
44
|
+
"mongodb",
|
|
45
|
+
"adapter",
|
|
46
|
+
"workflow",
|
|
47
|
+
"orchestration"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"dev": "tsup --watch",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "vitest",
|
|
57
|
+
"clean": "rm -rf dist"
|
|
58
|
+
}
|
|
59
|
+
}
|