ravenflow 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -30
- package/dist/index.d.mts +181 -47
- package/dist/index.d.ts +181 -47
- package/dist/index.js +1369 -203
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1362 -203
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -11
package/README.md
CHANGED
|
@@ -1,49 +1,313 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ravenflow
|
|
2
2
|
|
|
3
|
-
Type-safe job queue
|
|
3
|
+
Type-safe job queue framework for TypeScript, built on [BullMQ](https://docs.bullmq.io/) and [Redis](https://redis.io/).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Define jobs as plain functions, ravenflow handles scheduling, retries, rate limiting, distributed processing, and crash recovery.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm install ravenflow
|
|
10
|
+
npm install ravenflow
|
|
11
|
+
# or
|
|
12
|
+
pnpm add ravenflow
|
|
9
13
|
```
|
|
10
14
|
|
|
15
|
+
**Requirements:** Redis 6+, Node.js 18+. MongoDB required for stateful-etl and token-bucket jobs.
|
|
16
|
+
|
|
11
17
|
## Quick Start
|
|
12
18
|
|
|
13
|
-
```
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
createRavenflowApp,
|
|
22
|
+
defineConfig,
|
|
23
|
+
defineSimpleJob,
|
|
24
|
+
} from "ravenflow";
|
|
25
|
+
|
|
26
|
+
// 1. Define your jobs
|
|
27
|
+
const config = defineConfig({
|
|
28
|
+
defaultQueue: "default",
|
|
29
|
+
jobs: {
|
|
30
|
+
sendEmail: defineSimpleJob({
|
|
31
|
+
handler: async (job, params: { to: string; subject: string }) => {
|
|
32
|
+
await sendEmail(params.to, params.subject);
|
|
33
|
+
return { sent: true };
|
|
28
34
|
},
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 2. Create the app
|
|
40
|
+
const app = createRavenflowApp(config, {
|
|
41
|
+
redis: { host: "localhost", port: 6379 },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 3. Start the worker
|
|
45
|
+
await app.startWorker("default", { concurrency: 10 });
|
|
46
|
+
|
|
47
|
+
// 4. Send jobs
|
|
48
|
+
await app.jobs.sendEmail.send({ to: "user@example.com", subject: "Hello" });
|
|
49
|
+
|
|
50
|
+
// Or send and wait for the result
|
|
51
|
+
const result = await app.jobs.sendEmail.sendAndWait({ to: "user@example.com", subject: "Hello" });
|
|
52
|
+
console.log(result); // { sent: true }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Job Types
|
|
56
|
+
|
|
57
|
+
### Simple Job
|
|
58
|
+
|
|
59
|
+
One-shot function execution. Ideal for notifications, cleanup tasks, webhooks.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { defineSimpleJob } from "ravenflow";
|
|
63
|
+
|
|
64
|
+
const sendEmail = defineSimpleJob({
|
|
65
|
+
cron: "*/5 * * * *", // optional: run every 5 minutes
|
|
66
|
+
handler: async (job, params: { to: string; text: string }) => {
|
|
67
|
+
const result = await emailService.send(params.to, params.text);
|
|
68
|
+
return { messageId: result.id };
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Memory ETL
|
|
74
|
+
|
|
75
|
+
Extract-Transform-Load pipeline that runs entirely in-memory. Cursor-based pagination, parallel transforms with rate limiting.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { defineMemoryEtlJob } from "ravenflow";
|
|
79
|
+
|
|
80
|
+
const syncUsers = defineMemoryEtlJob({
|
|
81
|
+
cron: "0 2 * * *", // daily at 2 AM
|
|
82
|
+
|
|
83
|
+
extract: async (cursor: number | null) => {
|
|
84
|
+
const offset = cursor ?? 0;
|
|
85
|
+
const users = await api.getUsers({ offset, limit: 100 });
|
|
86
|
+
return {
|
|
87
|
+
data: users,
|
|
88
|
+
cursor: offset + 100,
|
|
89
|
+
hasMore: users.length === 100,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
transform: async (user) => ({
|
|
94
|
+
id: user.id,
|
|
95
|
+
email: user.email,
|
|
96
|
+
normalizedName: user.name.trim().toLowerCase(),
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
load: async (users) => {
|
|
100
|
+
await db.users.insertMany(users);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
steps: {
|
|
104
|
+
transform: {
|
|
105
|
+
rateLimit: { perSecond: 50 }, // even spacing: 1 every 20ms
|
|
29
106
|
},
|
|
30
107
|
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Distributed ETL
|
|
36
112
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
113
|
+
Same extract/transform/load pattern, but transforms are dispatched as independent BullMQ sub-jobs for parallel processing across workers.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { defineDistributedEtlJob } from "ravenflow";
|
|
117
|
+
|
|
118
|
+
const processOrders = defineDistributedEtlJob({
|
|
119
|
+
cron: "*/10 * * * *",
|
|
120
|
+
|
|
121
|
+
extract: async (cursor: number | null) => {
|
|
122
|
+
const orders = await db.orders
|
|
123
|
+
.find({ status: "pending" })
|
|
124
|
+
.skip(cursor ?? 0)
|
|
125
|
+
.limit(50)
|
|
126
|
+
.toArray();
|
|
127
|
+
return {
|
|
128
|
+
data: orders,
|
|
129
|
+
cursor: (cursor ?? 0) + 50,
|
|
130
|
+
hasMore: orders.length === 50,
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
transform: async (order) => {
|
|
135
|
+
const enriched = await paymentGateway.verify(order.paymentId);
|
|
136
|
+
return { ...order, verified: enriched.status === "ok" };
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
load: async (orders) => {
|
|
140
|
+
await db.processedOrders.insertMany(orders);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
steps: {
|
|
144
|
+
transform: {
|
|
145
|
+
rateLimit: { perSecond: 10 },
|
|
146
|
+
attempts: 3,
|
|
147
|
+
backoff: { type: "exponential", delay: 1000 },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
41
150
|
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Stateful ETL
|
|
154
|
+
|
|
155
|
+
Distributed ETL with MongoDB-backed checkpoints and automatic crash recovery. If a worker dies mid-processing, the Stall Watcher detects it and resumes from the last checkpoint.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { defineStatefulEtlJob } from "ravenflow";
|
|
159
|
+
|
|
160
|
+
const migrateLegacyData = defineStatefulEtlJob({
|
|
161
|
+
cron: "0 3 * * *",
|
|
162
|
+
singletonRun: true, // only one run at a time
|
|
163
|
+
stallTimeoutMs: 60_000, // detect stall after 60s without heartbeat
|
|
164
|
+
maxStallRetries: 3,
|
|
165
|
+
|
|
166
|
+
extract: async (cursor: string | null) => {
|
|
167
|
+
const page = await legacyApi.fetch({ after: cursor, limit: 100 });
|
|
168
|
+
return {
|
|
169
|
+
data: page.items,
|
|
170
|
+
cursor: page.nextCursor,
|
|
171
|
+
hasMore: page.hasMore,
|
|
172
|
+
};
|
|
173
|
+
},
|
|
42
174
|
|
|
43
|
-
|
|
44
|
-
|
|
175
|
+
transform: async (item) => {
|
|
176
|
+
return mapToNewSchema(item);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
load: async (items) => {
|
|
180
|
+
await newDb.collection("records").insertMany(items);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
steps: {
|
|
184
|
+
transform: {
|
|
185
|
+
rateLimit: { perSecond: 5 },
|
|
186
|
+
attempts: 3,
|
|
187
|
+
backoff: { type: "exponential", delay: 2000 },
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
});
|
|
45
191
|
```
|
|
46
192
|
|
|
193
|
+
**How crash recovery works:**
|
|
194
|
+
1. During processing, the worker sends heartbeats to MongoDB
|
|
195
|
+
2. A `__stall_check` cron job monitors all active runs
|
|
196
|
+
3. If a heartbeat goes stale (exceeds `stallTimeoutMs`), the run is claimed as "stalled"
|
|
197
|
+
4. A `__stall_resume` job re-enqueues the original job with the last checkpoint
|
|
198
|
+
5. Processing resumes from where it left off (no duplicate work)
|
|
199
|
+
|
|
200
|
+
## Configuration
|
|
201
|
+
|
|
202
|
+
### App Options
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
const app = createRavenflowApp(config, {
|
|
206
|
+
// Required: Redis connection
|
|
207
|
+
redis: { host: "localhost", port: 6379, password: "secret", db: 0 },
|
|
208
|
+
|
|
209
|
+
// Optional: MongoDB (required for stateful-etl and token-bucket)
|
|
210
|
+
// String form:
|
|
211
|
+
mongo: "mongodb://localhost:27017/myapp",
|
|
212
|
+
// Or object form:
|
|
213
|
+
mongo: { host: "localhost", port: 27017, username: "admin", password: "secret", database: "myapp" },
|
|
214
|
+
|
|
215
|
+
// Optional: job retention policy
|
|
216
|
+
retention: {
|
|
217
|
+
completed: 1000, // keep last 1000 completed jobs per queue
|
|
218
|
+
failed: 5000, // keep last 5000 failed jobs per queue
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// Optional: logger config (pino)
|
|
222
|
+
logger: { level: "info", pretty: true },
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Rate Limiting
|
|
227
|
+
|
|
228
|
+
Even-spacing rate limiter. `perSecond: 10` means 1 request every 100ms (not a burst of 10 then wait).
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
steps: {
|
|
232
|
+
extract: { rateLimit: { perMinute: 60 } }, // 1 per second
|
|
233
|
+
transform: { rateLimit: { perSecond: 20 } }, // 1 every 50ms
|
|
234
|
+
load: { rateLimit: { perSecond: 5 } }, // 1 every 200ms
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Step Config (Distributed & Stateful ETL)
|
|
239
|
+
|
|
240
|
+
Each step can have its own retry strategy, rate limit, and target queue:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
steps: {
|
|
244
|
+
transform: {
|
|
245
|
+
rateLimit: { perSecond: 10 },
|
|
246
|
+
attempts: 3,
|
|
247
|
+
backoff: { type: "exponential", delay: 1000 },
|
|
248
|
+
queue: "heavy-transforms", // route to a different queue
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Cron Scheduling
|
|
254
|
+
|
|
255
|
+
Jobs with a `cron` field are automatically scheduled when the worker starts:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
defineSimpleJob({
|
|
259
|
+
cron: "*/5 * * * *", // every 5 minutes
|
|
260
|
+
handler: async () => { /* ... */ },
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Sending Jobs
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
// Fire and forget — returns BullMQ Job object
|
|
268
|
+
const job = await app.jobs.sendEmail.send({ to: "user@example.com", subject: "Hi" });
|
|
269
|
+
|
|
270
|
+
// Wait for the result
|
|
271
|
+
const result = await app.jobs.sendEmail.sendAndWait({ to: "user@example.com", subject: "Hi" });
|
|
272
|
+
|
|
273
|
+
// ETL jobs don't take parameters
|
|
274
|
+
await app.jobs.syncUsers.send();
|
|
275
|
+
|
|
276
|
+
// ETL sendAndWait returns counts
|
|
277
|
+
const counts = await app.jobs.syncUsers.sendAndWait();
|
|
278
|
+
// { extractedCount: 500, transformedCount: 500, loadedCount: 500 }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Worker
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
// Start processing jobs from a queue
|
|
285
|
+
await app.startWorker("default", { concurrency: 20 });
|
|
286
|
+
|
|
287
|
+
// Graceful shutdown
|
|
288
|
+
await app.close();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
When `startWorker` is called:
|
|
292
|
+
- Cron schedules are synced to Redis
|
|
293
|
+
- The Stall Watcher starts automatically if stateful-etl jobs are registered
|
|
294
|
+
- The worker begins processing jobs
|
|
295
|
+
|
|
296
|
+
## Config Validation
|
|
297
|
+
|
|
298
|
+
Ravenflow validates your config at startup and throws clear errors:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
[ravenflow] Config validation failed:
|
|
302
|
+
- "migrateLegacy" (stateful-etl) requires a mongo connection.
|
|
303
|
+
- "rateLimiter" (token-bucket) requires a cron schedule.
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Companion Packages
|
|
307
|
+
|
|
308
|
+
- [`@ravenflow/mongo`](https://www.npmjs.com/package/@ravenflow/mongo) — Typed MongoDB collections, base repository, Zod schema integration
|
|
309
|
+
- [`@ravenflow/http`](https://www.npmjs.com/package/@ravenflow/http) — HTTP client utilities built on undici
|
|
310
|
+
|
|
47
311
|
## License
|
|
48
312
|
|
|
49
313
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Job, Queue, QueueEvents, ConnectionOptions, Worker } from 'bullmq';
|
|
3
|
+
import pino from 'pino';
|
|
3
4
|
|
|
4
|
-
interface RavenflowConfig<TQueues extends string = string, TJobs extends string = string> {
|
|
5
|
-
defaultQueue: TQueues;
|
|
6
|
-
queues: TQueues[];
|
|
7
|
-
jobs: Record<TJobs, JobConfig>;
|
|
8
|
-
}
|
|
9
|
-
type JobNames<T extends RavenflowConfig> = T extends RavenflowConfig<infer _, infer TJobs> ? TJobs : never;
|
|
10
|
-
type QueueNames<T extends RavenflowConfig> = T extends RavenflowConfig<infer TQueues, infer _> ? TQueues : never;
|
|
11
5
|
interface BaseJobConfig {
|
|
12
|
-
|
|
6
|
+
queue?: string;
|
|
13
7
|
cron?: string;
|
|
14
8
|
attempts?: number;
|
|
15
9
|
backoff?: {
|
|
@@ -17,67 +11,207 @@ interface BaseJobConfig {
|
|
|
17
11
|
delay: number;
|
|
18
12
|
};
|
|
19
13
|
}
|
|
14
|
+
declare const zRateLimitConfig: z.ZodUnion<readonly [z.ZodObject<{
|
|
15
|
+
perSecond: z.ZodNumber;
|
|
16
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
17
|
+
perMinute: z.ZodNumber;
|
|
18
|
+
}, z.core.$strip>]>;
|
|
19
|
+
type RateLimitConfig = z.infer<typeof zRateLimitConfig>;
|
|
20
|
+
interface MemoryEtlStepConfig {
|
|
21
|
+
rateLimit?: RateLimitConfig;
|
|
22
|
+
}
|
|
23
|
+
interface StepConfig extends MemoryEtlStepConfig {
|
|
24
|
+
attempts?: number;
|
|
25
|
+
backoff?: {
|
|
26
|
+
type: "fixed" | "exponential";
|
|
27
|
+
delay: number;
|
|
28
|
+
};
|
|
29
|
+
queue?: string;
|
|
30
|
+
}
|
|
31
|
+
interface EtlExtractResult<TRaw, TCursor> {
|
|
32
|
+
data: TRaw[];
|
|
33
|
+
cursor: TCursor | null;
|
|
34
|
+
hasMore: boolean;
|
|
35
|
+
}
|
|
36
|
+
declare const zJobStatus: z.ZodEnum<{
|
|
37
|
+
pending: "pending";
|
|
38
|
+
in_progress: "in_progress";
|
|
39
|
+
completed: "completed";
|
|
40
|
+
failed: "failed";
|
|
41
|
+
stalled: "stalled";
|
|
42
|
+
}>;
|
|
43
|
+
type JobStatus = z.infer<typeof zJobStatus>;
|
|
44
|
+
|
|
20
45
|
interface SimpleJobConfig<TData, TReturn> extends BaseJobConfig {
|
|
21
|
-
type: "
|
|
46
|
+
type: "simple";
|
|
22
47
|
handler: (job: Job, params: TData) => Promise<TReturn>;
|
|
23
48
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
declare function defineSimpleJob<TData, TReturn>(config: Omit<SimpleJobConfig<TData, TReturn>, "type">): SimpleJobConfig<TData, TReturn>;
|
|
50
|
+
|
|
51
|
+
interface MemoryEtlJobConfig<TRaw, TProcessed, TCursor = number> extends BaseJobConfig {
|
|
52
|
+
type: "memory-etl";
|
|
53
|
+
extract: (cursor: TCursor | null) => Promise<EtlExtractResult<TRaw, TCursor>>;
|
|
54
|
+
transform: (data: TRaw) => Promise<TProcessed>;
|
|
55
|
+
load: (data: TProcessed[]) => Promise<void>;
|
|
56
|
+
steps?: {
|
|
57
|
+
extract?: MemoryEtlStepConfig;
|
|
58
|
+
transform?: MemoryEtlStepConfig;
|
|
59
|
+
load?: MemoryEtlStepConfig;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
declare function defineMemoryEtlJob<TRaw, TProcessed, TCursor = number>(config: Omit<MemoryEtlJobConfig<TRaw, TProcessed, TCursor>, "type">): MemoryEtlJobConfig<TRaw, TProcessed, TCursor>;
|
|
63
|
+
|
|
64
|
+
interface DistributedEtlJobConfig<TRaw, TProcessed, TCursor = number> extends BaseJobConfig {
|
|
65
|
+
type: "distributed-etl";
|
|
66
|
+
extract: (cursor: TCursor | null) => Promise<EtlExtractResult<TRaw, TCursor>>;
|
|
67
|
+
transform: (data: TRaw) => Promise<TProcessed>;
|
|
68
|
+
load: (data: TProcessed[]) => Promise<void>;
|
|
69
|
+
steps?: {
|
|
70
|
+
extract?: StepConfig;
|
|
71
|
+
transform?: StepConfig;
|
|
72
|
+
load?: StepConfig;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
declare function defineDistributedEtlJob<TRaw, TProcessed, TCursor = number>(config: Omit<DistributedEtlJobConfig<TRaw, TProcessed, TCursor>, "type">): DistributedEtlJobConfig<TRaw, TProcessed, TCursor>;
|
|
76
|
+
|
|
77
|
+
interface StatefulEtlJobConfig<TRaw, TProcessed, TCursor = string> extends BaseJobConfig {
|
|
78
|
+
type: "stateful-etl";
|
|
79
|
+
extract: (cursor: TCursor | null) => Promise<EtlExtractResult<TRaw, TCursor>>;
|
|
30
80
|
transform: (data: TRaw) => Promise<TProcessed>;
|
|
31
81
|
load: (data: TProcessed[]) => Promise<void>;
|
|
32
|
-
|
|
82
|
+
/**
|
|
83
|
+
* When true, only one run of this job can be active at a time.
|
|
84
|
+
* If a run is already pending or in_progress (with a live heartbeat), the new trigger is skipped.
|
|
85
|
+
* Defaults to false — each trigger creates an independent run.
|
|
86
|
+
*/
|
|
87
|
+
singletonRun?: boolean;
|
|
88
|
+
stallTimeoutMs?: number;
|
|
89
|
+
maxStallRetries?: number;
|
|
90
|
+
steps?: {
|
|
91
|
+
extract?: StepConfig;
|
|
92
|
+
transform?: StepConfig;
|
|
93
|
+
load?: StepConfig;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
declare function defineStatefulEtlJob<TRaw, TProcessed, TCursor = string>(config: Omit<StatefulEtlJobConfig<TRaw, TProcessed, TCursor>, "type">): StatefulEtlJobConfig<TRaw, TProcessed, TCursor>;
|
|
97
|
+
|
|
98
|
+
interface TokenBucketJobConfig<TItem, TResult> extends BaseJobConfig {
|
|
99
|
+
type: "token-bucket";
|
|
100
|
+
cron: string;
|
|
101
|
+
rate: {
|
|
102
|
+
amount: number;
|
|
103
|
+
interval: "minute" | "hour" | "day";
|
|
104
|
+
};
|
|
105
|
+
activeHours?: {
|
|
106
|
+
start: number;
|
|
107
|
+
end: number;
|
|
108
|
+
timezone?: string;
|
|
109
|
+
};
|
|
110
|
+
maxPerBeat?: number;
|
|
111
|
+
getItems: (budget: number) => Promise<TItem[]>;
|
|
112
|
+
process: (item: TItem) => Promise<TResult>;
|
|
113
|
+
steps?: {
|
|
114
|
+
process?: StepConfig;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
declare function defineTokenBucketJob<TItem, TResult>(config: Omit<TokenBucketJobConfig<TItem, TResult>, "type">): TokenBucketJobConfig<TItem, TResult>;
|
|
118
|
+
|
|
119
|
+
type JobConfig = SimpleJobConfig<any, any> | MemoryEtlJobConfig<any, any, any> | DistributedEtlJobConfig<any, any, any> | StatefulEtlJobConfig<any, any, any> | TokenBucketJobConfig<any, any>;
|
|
120
|
+
|
|
121
|
+
interface RetentionConfig {
|
|
122
|
+
/** Number of completed jobs to keep per queue. Set to 0 to remove immediately. */
|
|
123
|
+
completed?: number;
|
|
124
|
+
/** Number of failed jobs to keep per queue. Set to 0 to remove immediately. */
|
|
125
|
+
failed?: number;
|
|
126
|
+
}
|
|
127
|
+
interface RavenflowConfig<TJobs extends string = string> {
|
|
128
|
+
defaultQueue: string;
|
|
129
|
+
jobs: Record<TJobs, JobConfig>;
|
|
130
|
+
}
|
|
131
|
+
type JobNames<T extends RavenflowConfig> = T extends RavenflowConfig<infer TJobs> ? TJobs : never;
|
|
132
|
+
declare function defineConfig<const T extends RavenflowConfig>(config: T): T;
|
|
133
|
+
|
|
134
|
+
interface EtlResult {
|
|
135
|
+
extractedCount: number;
|
|
136
|
+
transformedCount: number;
|
|
137
|
+
loadedCount: number;
|
|
33
138
|
}
|
|
34
|
-
type JobConfig = SimpleJobConfig<any, any> | EtlJobConfig<any, any>;
|
|
35
139
|
type JobProxy<T extends JobConfig> = T extends SimpleJobConfig<infer TData, infer TReturn> ? {
|
|
36
140
|
send: (params: TData) => Promise<Job<TData, TReturn>>;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
141
|
+
sendAndWait: (params: TData) => Promise<TReturn>;
|
|
142
|
+
} : T extends MemoryEtlJobConfig<any, any, any> ? {
|
|
143
|
+
send: () => Promise<void>;
|
|
144
|
+
sendAndWait: () => Promise<EtlResult>;
|
|
145
|
+
} : T extends DistributedEtlJobConfig<any, any> ? {
|
|
146
|
+
send: () => Promise<void>;
|
|
147
|
+
sendAndWait: () => Promise<EtlResult>;
|
|
148
|
+
} : T extends StatefulEtlJobConfig<any, any, any> ? {
|
|
149
|
+
send: () => Promise<void>;
|
|
150
|
+
sendAndWait: () => Promise<void>;
|
|
151
|
+
} : T extends TokenBucketJobConfig<any, any> ? {
|
|
152
|
+
send: () => Promise<void>;
|
|
153
|
+
sendAndWait: () => Promise<void>;
|
|
42
154
|
} : never;
|
|
43
155
|
type JobsProxy<TJobs extends Record<string, JobConfig>> = {
|
|
44
156
|
[K in keyof TJobs]: JobProxy<TJobs[K]>;
|
|
45
157
|
};
|
|
46
158
|
|
|
159
|
+
type Logger = pino.Logger;
|
|
47
160
|
interface LoggerOptions {
|
|
48
161
|
level?: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
49
162
|
pretty?: boolean;
|
|
50
163
|
}
|
|
51
|
-
declare function
|
|
52
|
-
|
|
164
|
+
declare function initLogger(options?: LoggerOptions): Logger;
|
|
165
|
+
declare function getLogger(): Logger;
|
|
166
|
+
|
|
167
|
+
type QueueStore = Record<string, {
|
|
168
|
+
queue: Queue;
|
|
169
|
+
events: QueueEvents;
|
|
170
|
+
}>;
|
|
171
|
+
|
|
172
|
+
declare class StallWatcher {
|
|
173
|
+
private readonly workerQueue;
|
|
174
|
+
private readonly statefulEtlConfigs;
|
|
175
|
+
private readonly userQueues;
|
|
176
|
+
constructor(jobConfigs: Record<string, JobConfig>, userQueues: Record<string, {
|
|
177
|
+
queue: Queue;
|
|
178
|
+
}>, defaultQueue: string, workerQueue: Queue);
|
|
179
|
+
canProcess(jobName: string): boolean;
|
|
180
|
+
start(): Promise<void>;
|
|
181
|
+
stop(): Promise<void>;
|
|
182
|
+
process(job: Job): Promise<void>;
|
|
183
|
+
}
|
|
53
184
|
|
|
185
|
+
interface MongoConnectionObject {
|
|
186
|
+
host: string;
|
|
187
|
+
port?: number;
|
|
188
|
+
username?: string;
|
|
189
|
+
password?: string;
|
|
190
|
+
database?: string;
|
|
191
|
+
}
|
|
192
|
+
interface RavenflowAppOptions {
|
|
193
|
+
redis: ConnectionOptions;
|
|
194
|
+
mongo?: string | MongoConnectionObject;
|
|
195
|
+
retention?: RetentionConfig;
|
|
196
|
+
logger?: LoggerOptions;
|
|
197
|
+
}
|
|
54
198
|
declare class RavenflowApp<TConfig extends RavenflowConfig = RavenflowConfig> {
|
|
55
|
-
protected
|
|
199
|
+
protected redisConnection: ConnectionOptions;
|
|
56
200
|
protected defaultConcurrency: number;
|
|
57
|
-
protected logger: Logger;
|
|
58
201
|
protected config: TConfig;
|
|
202
|
+
protected mongoUri: string | null;
|
|
59
203
|
jobs: JobsProxy<TConfig["jobs"]>;
|
|
60
204
|
protected jobConfigs: Record<JobNames<TConfig>, JobConfig>;
|
|
61
|
-
protected queues:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
private initQueues;
|
|
67
|
-
private initJobs;
|
|
68
|
-
private sendJob;
|
|
69
|
-
startWorker(queueName: QueueNames<TConfig>, options?: {
|
|
205
|
+
protected queues: QueueStore;
|
|
206
|
+
protected workers: Worker[];
|
|
207
|
+
protected stallWatcher: StallWatcher | null;
|
|
208
|
+
constructor(config: TConfig, options: RavenflowAppOptions);
|
|
209
|
+
startWorker(queueName: string, options?: {
|
|
70
210
|
concurrency?: number;
|
|
71
|
-
}): void
|
|
72
|
-
|
|
73
|
-
private setupWorkerEvents;
|
|
74
|
-
private syncJobSchedulers;
|
|
75
|
-
processJob(job: Job): Promise<any>;
|
|
211
|
+
}): Promise<void>;
|
|
212
|
+
close(): Promise<void>;
|
|
76
213
|
}
|
|
77
214
|
|
|
78
|
-
declare function createRavenflowApp<TConfig extends RavenflowConfig>(config: TConfig,
|
|
79
|
-
level?: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
80
|
-
pretty?: boolean;
|
|
81
|
-
}): RavenflowApp<TConfig>;
|
|
215
|
+
declare function createRavenflowApp<TConfig extends RavenflowConfig>(config: TConfig, options: RavenflowAppOptions): RavenflowApp<TConfig>;
|
|
82
216
|
|
|
83
|
-
export { type
|
|
217
|
+
export { type DistributedEtlJobConfig, type EtlExtractResult, type EtlResult, type JobConfig, type JobStatus, type Logger, type LoggerOptions, type MemoryEtlJobConfig, type MemoryEtlStepConfig, type MongoConnectionObject, type RateLimitConfig, RavenflowApp, type RavenflowAppOptions, type RavenflowConfig, type RetentionConfig, type SimpleJobConfig, type StatefulEtlJobConfig, type StepConfig, type TokenBucketJobConfig, createRavenflowApp, defineConfig, defineDistributedEtlJob, defineMemoryEtlJob, defineSimpleJob, defineStatefulEtlJob, defineTokenBucketJob, getLogger, initLogger };
|