ravenflow 0.2.0 → 0.3.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/LICENSE +21 -0
- package/README.md +22 -0
- package/dist/index.d.mts +475 -19
- package/dist/index.d.ts +475 -19
- package/dist/index.js +1664 -150
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1628 -153
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Job, Queue, QueueEvents, ConnectionOptions, Worker } from 'bullmq';
|
|
2
|
+
import { Job, Queue, QueueEvents, ConnectionOptions, Worker, WorkerOptions } from 'bullmq';
|
|
3
3
|
import pino from 'pino';
|
|
4
|
+
import { IndexSpecification, CreateIndexesOptions, Document, Collection, Db, AnyBulkWriteOperation } from 'mongodb';
|
|
4
5
|
|
|
5
6
|
interface BaseJobConfig {
|
|
6
7
|
queue?: string;
|
|
@@ -95,28 +96,22 @@ interface StatefulEtlJobConfig<TRaw, TProcessed, TCursor = string> extends BaseJ
|
|
|
95
96
|
}
|
|
96
97
|
declare function defineStatefulEtlJob<TRaw, TProcessed, TCursor = string>(config: Omit<StatefulEtlJobConfig<TRaw, TProcessed, TCursor>, "type">): StatefulEtlJobConfig<TRaw, TProcessed, TCursor>;
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
type TokenBucketInterval = "second" | "minute" | "hour" | "day" | "week";
|
|
100
|
+
interface TokenBucketJobConfig extends BaseJobConfig {
|
|
99
101
|
type: "token-bucket";
|
|
100
102
|
cron: string;
|
|
101
|
-
rate
|
|
103
|
+
rate?: {
|
|
102
104
|
amount: number;
|
|
103
|
-
interval:
|
|
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;
|
|
105
|
+
interval: TokenBucketInterval;
|
|
115
106
|
};
|
|
107
|
+
getBudget?: () => Promise<number> | number;
|
|
108
|
+
maxCarryOver?: number;
|
|
109
|
+
consumeTimeout?: number;
|
|
110
|
+
consume: (tokens: number) => Promise<number | void> | number | void;
|
|
116
111
|
}
|
|
117
|
-
declare function defineTokenBucketJob
|
|
112
|
+
declare function defineTokenBucketJob(config: Omit<TokenBucketJobConfig, "type">): TokenBucketJobConfig;
|
|
118
113
|
|
|
119
|
-
type JobConfig = SimpleJobConfig<any, any> | MemoryEtlJobConfig<any, any, any> | DistributedEtlJobConfig<any, any, any> | StatefulEtlJobConfig<any, any, any> | TokenBucketJobConfig
|
|
114
|
+
type JobConfig = SimpleJobConfig<any, any> | MemoryEtlJobConfig<any, any, any> | DistributedEtlJobConfig<any, any, any> | StatefulEtlJobConfig<any, any, any> | TokenBucketJobConfig;
|
|
120
115
|
|
|
121
116
|
interface RetentionConfig {
|
|
122
117
|
/** Number of completed jobs to keep per queue. Set to 0 to remove immediately. */
|
|
@@ -148,7 +143,7 @@ type JobProxy<T extends JobConfig> = T extends SimpleJobConfig<infer TData, infe
|
|
|
148
143
|
} : T extends StatefulEtlJobConfig<any, any, any> ? {
|
|
149
144
|
send: () => Promise<void>;
|
|
150
145
|
sendAndWait: () => Promise<void>;
|
|
151
|
-
} : T extends TokenBucketJobConfig
|
|
146
|
+
} : T extends TokenBucketJobConfig ? {
|
|
152
147
|
send: () => Promise<void>;
|
|
153
148
|
sendAndWait: () => Promise<void>;
|
|
154
149
|
} : never;
|
|
@@ -182,6 +177,354 @@ declare class StallWatcher {
|
|
|
182
177
|
process(job: Job): Promise<void>;
|
|
183
178
|
}
|
|
184
179
|
|
|
180
|
+
interface IndexDescriptor {
|
|
181
|
+
keys: IndexSpecification;
|
|
182
|
+
options?: CreateIndexesOptions;
|
|
183
|
+
}
|
|
184
|
+
type BucketGranularity = "hour" | "day";
|
|
185
|
+
interface IncrementStatsInput {
|
|
186
|
+
success: boolean;
|
|
187
|
+
durationMs?: number;
|
|
188
|
+
}
|
|
189
|
+
interface JobStatsDoc {
|
|
190
|
+
jobType: string;
|
|
191
|
+
bucketStart: Date;
|
|
192
|
+
granularity: BucketGranularity;
|
|
193
|
+
count: number;
|
|
194
|
+
success: number;
|
|
195
|
+
failed: number;
|
|
196
|
+
totalDurationMs: number;
|
|
197
|
+
createdAt: Date;
|
|
198
|
+
updatedAt: Date;
|
|
199
|
+
}
|
|
200
|
+
interface JobErrorDoc {
|
|
201
|
+
signature: string;
|
|
202
|
+
jobType: string;
|
|
203
|
+
message: string;
|
|
204
|
+
stack: string;
|
|
205
|
+
count: number;
|
|
206
|
+
firstSeenAt: Date;
|
|
207
|
+
lastSeenAt: Date;
|
|
208
|
+
createdAt: Date;
|
|
209
|
+
updatedAt: Date;
|
|
210
|
+
/** Normalized `Error.cause` chain beyond the top-level error. OMITTED when empty. */
|
|
211
|
+
causeChain?: Array<{
|
|
212
|
+
message: string;
|
|
213
|
+
name: string;
|
|
214
|
+
}>;
|
|
215
|
+
}
|
|
216
|
+
interface RecordOccurrenceInput {
|
|
217
|
+
jobType: string;
|
|
218
|
+
error: unknown;
|
|
219
|
+
}
|
|
220
|
+
interface TopErrorsOptions {
|
|
221
|
+
limit: number;
|
|
222
|
+
jobType?: string;
|
|
223
|
+
/**
|
|
224
|
+
* When set, restricts results to errors whose `lastSeenAt >= since`.
|
|
225
|
+
* Dashboard health views pass the range start (e.g. 7 days ago, UTC midnight)
|
|
226
|
+
* so "top errors" matches the displayed range; omitting returns all errors
|
|
227
|
+
* back to the collection's 90-day TTL boundary.
|
|
228
|
+
*/
|
|
229
|
+
since?: Date;
|
|
230
|
+
}
|
|
231
|
+
interface JobFailureDoc {
|
|
232
|
+
jobId: string;
|
|
233
|
+
jobType: string;
|
|
234
|
+
input: string;
|
|
235
|
+
errorMessage: string;
|
|
236
|
+
errorStack: string;
|
|
237
|
+
logs: string;
|
|
238
|
+
failedAt: Date;
|
|
239
|
+
createdAt: Date;
|
|
240
|
+
/** Normalized `Error.cause` chain beyond the top-level error. OMITTED when empty. */
|
|
241
|
+
causeChain?: Array<{
|
|
242
|
+
message: string;
|
|
243
|
+
name: string;
|
|
244
|
+
}>;
|
|
245
|
+
}
|
|
246
|
+
interface RecordFailureInput {
|
|
247
|
+
jobId: string;
|
|
248
|
+
jobType: string;
|
|
249
|
+
input: unknown;
|
|
250
|
+
error: unknown;
|
|
251
|
+
failedAt: Date;
|
|
252
|
+
logs?: string;
|
|
253
|
+
}
|
|
254
|
+
interface ListFailuresOptions {
|
|
255
|
+
jobType?: string;
|
|
256
|
+
fromDate?: Date | null;
|
|
257
|
+
toDate?: Date | null;
|
|
258
|
+
skip?: number;
|
|
259
|
+
limit?: number;
|
|
260
|
+
}
|
|
261
|
+
declare const JOB_FAILURES_INPUT_MAX_CHARS = 100000;
|
|
262
|
+
declare const JOB_FAILURES_MESSAGE_MAX_CHARS = 2000;
|
|
263
|
+
declare const JOB_FAILURES_STACK_MAX_CHARS = 8000;
|
|
264
|
+
declare const JOB_FAILURES_LOGS_MAX_CHARS = 500000;
|
|
265
|
+
interface WorkerQueueEntry {
|
|
266
|
+
name: string;
|
|
267
|
+
concurrency: number;
|
|
268
|
+
}
|
|
269
|
+
interface WorkerDoc {
|
|
270
|
+
workerId: string;
|
|
271
|
+
hostname: string;
|
|
272
|
+
pid: number;
|
|
273
|
+
queues: WorkerQueueEntry[];
|
|
274
|
+
startedAt: Date;
|
|
275
|
+
lastHeartbeatAt: Date;
|
|
276
|
+
createdAt: Date;
|
|
277
|
+
updatedAt: Date;
|
|
278
|
+
}
|
|
279
|
+
interface RegisterWorkerInput {
|
|
280
|
+
workerId: string;
|
|
281
|
+
hostname: string;
|
|
282
|
+
pid: number;
|
|
283
|
+
queues: WorkerQueueEntry[];
|
|
284
|
+
startedAt: Date;
|
|
285
|
+
}
|
|
286
|
+
interface UpsertWorkerQueueInput {
|
|
287
|
+
workerId: string;
|
|
288
|
+
queue: WorkerQueueEntry;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Read shape for the `job_configs` collection — what `JobConfigsRepo.getConfig`
|
|
292
|
+
* returns (with persistence timestamps stripped, or synthesized for absent records).
|
|
293
|
+
*
|
|
294
|
+
* Distinct from `JobConfig` (in `src/types/job/unions.ts`), which is the discriminated
|
|
295
|
+
* union of job-definition types (simple / memory-etl / distributed-etl / etc.) used
|
|
296
|
+
* at app-registration time. `JobConfigEntry` is the runtime persistence shape for
|
|
297
|
+
* dashboard-managed per-job-type settings (enabled flag + free-form metadata).
|
|
298
|
+
*/
|
|
299
|
+
interface JobConfigEntry {
|
|
300
|
+
jobType: string;
|
|
301
|
+
enabled: boolean;
|
|
302
|
+
metadata: Record<string, unknown>;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Persisted shape for the `job_configs` collection — the read shape plus
|
|
306
|
+
* MongoDB-managed timestamps. Returned by `JobConfigsRepo.listAll` (every row
|
|
307
|
+
* there is, by definition, persisted, so the timestamps are always real).
|
|
308
|
+
*/
|
|
309
|
+
interface JobConfigDoc extends JobConfigEntry {
|
|
310
|
+
createdAt: Date;
|
|
311
|
+
updatedAt: Date;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
declare abstract class Repository<T extends Document> {
|
|
315
|
+
readonly collection: Collection<T>;
|
|
316
|
+
constructor(db: Db, rawCollectionName: string);
|
|
317
|
+
protected abstract indexes(): IndexDescriptor[];
|
|
318
|
+
init(): Promise<void>;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
declare class WorkersRepo extends Repository<WorkerDoc> {
|
|
322
|
+
constructor(db: Db);
|
|
323
|
+
protected indexes(): IndexDescriptor[];
|
|
324
|
+
registerWorker(input: RegisterWorkerInput): Promise<void>;
|
|
325
|
+
updateHeartbeat(workerId: string): Promise<void>;
|
|
326
|
+
/**
|
|
327
|
+
* Appends or replaces a queue entry on the worker's record.
|
|
328
|
+
*
|
|
329
|
+
* Single-writer invariant: this method is NOT atomic under concurrent writers to the
|
|
330
|
+
* SAME `workerId`. WorkerRegistry is a process-local singleton, and `workerId` is
|
|
331
|
+
* unique per process by contract (default: `<hostname>:<pid>:<uuid>`). Sharing a
|
|
332
|
+
* stable `workerId` across processes violates AC #6 and will race last-writer-wins.
|
|
333
|
+
*/
|
|
334
|
+
addQueue(input: UpsertWorkerQueueInput): Promise<void>;
|
|
335
|
+
deregisterWorker(workerId: string): Promise<void>;
|
|
336
|
+
findAlive(maxHeartbeatAgeMs: number): Promise<WorkerDoc[]>;
|
|
337
|
+
cleanupStale(maxHeartbeatAgeMs: number): Promise<number>;
|
|
338
|
+
private validateRegisterInput;
|
|
339
|
+
private validateQueueEntry;
|
|
340
|
+
private validateMaxHeartbeatAgeMs;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
declare class JobStatsRepo extends Repository<JobStatsDoc> {
|
|
344
|
+
constructor(db: Db);
|
|
345
|
+
protected indexes(): IndexDescriptor[];
|
|
346
|
+
/**
|
|
347
|
+
* Builds the bulk write operations for a stats increment WITHOUT executing them.
|
|
348
|
+
* Returns TWO ops — one for the hour bucket, one for the day bucket — matching
|
|
349
|
+
* `incrementStats`'s direct-call shape bit-for-bit. Intended to be pushed through
|
|
350
|
+
* a `BatchWriter` by the event-hooks layer (Story 1.7). Throws synchronously on
|
|
351
|
+
* invalid `durationMs`; otherwise no side effects.
|
|
352
|
+
*
|
|
353
|
+
* NOTE: `now` is captured at builder invocation time, NOT at BatchWriter flush
|
|
354
|
+
* time — `createdAt`, `updatedAt`, and `bucketStart` all reflect the event's
|
|
355
|
+
* wall-clock, not the flush wall-clock. Aggregations are therefore correctly
|
|
356
|
+
* attributed to the hour/day the event occurred, even if the batch flush is
|
|
357
|
+
* delayed by the buffer timer.
|
|
358
|
+
*/
|
|
359
|
+
toIncrementStatsOps(jobType: string, input: IncrementStatsInput): AnyBulkWriteOperation<JobStatsDoc>[];
|
|
360
|
+
incrementStats(jobType: string, input: IncrementStatsInput): Promise<void>;
|
|
361
|
+
getStats(jobType: string, fromDate: Date, toDate: Date, granularity: BucketGranularity): Promise<JobStatsDoc[]>;
|
|
362
|
+
/**
|
|
363
|
+
* Fetches every stats document in the given range + granularity, across ALL
|
|
364
|
+
* jobTypes. Intended for dashboard views that need a cross-jobType overview
|
|
365
|
+
* (e.g., "7-day health per job type") without a prior enumeration step.
|
|
366
|
+
*
|
|
367
|
+
* Result order: `bucketStart` ascending, then `jobType` ascending. Caller is
|
|
368
|
+
* responsible for grouping by jobType client-side if desired.
|
|
369
|
+
*
|
|
370
|
+
* Applies the same `MAX_GET_STATS_RESULTS` cap as `getStats` — a single query
|
|
371
|
+
* across all jobTypes × buckets × granularity is bounded, but the cap keeps
|
|
372
|
+
* the method safe under accidental wide-range queries.
|
|
373
|
+
*/
|
|
374
|
+
getStatsAllJobTypes(fromDate: Date, toDate: Date, granularity: BucketGranularity): Promise<JobStatsDoc[]>;
|
|
375
|
+
private upsertBucket;
|
|
376
|
+
private buildBucketOp;
|
|
377
|
+
private buildBucketPayload;
|
|
378
|
+
private validateDurationMs;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
declare class JobErrorsRepo extends Repository<JobErrorDoc> {
|
|
382
|
+
constructor(db: Db);
|
|
383
|
+
protected indexes(): IndexDescriptor[];
|
|
384
|
+
/**
|
|
385
|
+
* Records one occurrence of an error, deduplicated by its stable signature.
|
|
386
|
+
*
|
|
387
|
+
* Invariant: `jobType` is set **only on insert** (`$setOnInsert`). Once a signature
|
|
388
|
+
* is first observed under jobType A, subsequent occurrences under jobType B do NOT
|
|
389
|
+
* overwrite the stored value. The signature is the identity of the error; a signature
|
|
390
|
+
* appearing across multiple job types indicates shared infrastructure failure (e.g.
|
|
391
|
+
* ECONNRESET from the shared DB) and is worth surfacing on the first-observed job.
|
|
392
|
+
*/
|
|
393
|
+
recordOccurrence(input: RecordOccurrenceInput): Promise<void>;
|
|
394
|
+
/**
|
|
395
|
+
* Builds the bulk write operation for `recordOccurrence` WITHOUT executing it.
|
|
396
|
+
* Returns a single upsert op matching the direct-call shape bit-for-bit.
|
|
397
|
+
*
|
|
398
|
+
* NOTE: `now` is captured at builder invocation time, NOT at BatchWriter flush
|
|
399
|
+
* time — firstSeenAt / lastSeenAt reflect the event's wall-clock, not the
|
|
400
|
+
* flush wall-clock. This preserves the semantic that these timestamps track
|
|
401
|
+
* the error's occurrence, not the observability pipeline's delay.
|
|
402
|
+
*/
|
|
403
|
+
toRecordOccurrenceOp(input: RecordOccurrenceInput): AnyBulkWriteOperation<JobErrorDoc>;
|
|
404
|
+
private buildRecordOccurrencePayload;
|
|
405
|
+
topErrors(options: TopErrorsOptions): Promise<JobErrorDoc[]>;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
declare class JobFailuresRepo extends Repository<JobFailureDoc> {
|
|
409
|
+
constructor(db: Db);
|
|
410
|
+
protected indexes(): IndexDescriptor[];
|
|
411
|
+
recordFailure(input: RecordFailureInput): Promise<void>;
|
|
412
|
+
/**
|
|
413
|
+
* Builds the bulk write operation for `recordFailure` WITHOUT executing it.
|
|
414
|
+
* Returns a single insertOne op matching the direct-call document shape
|
|
415
|
+
* bit-for-bit. Intended for the event-hooks layer (Story 1.7) to push through
|
|
416
|
+
* a `BatchWriter`. Throws synchronously on invalid input using the same error
|
|
417
|
+
* messages as `recordFailure`.
|
|
418
|
+
*/
|
|
419
|
+
toRecordFailureOp(input: RecordFailureInput): AnyBulkWriteOperation<JobFailureDoc>;
|
|
420
|
+
private buildFailureDoc;
|
|
421
|
+
private validateFailureInput;
|
|
422
|
+
listFailures(options: ListFailuresOptions): Promise<{
|
|
423
|
+
items: JobFailureDoc[];
|
|
424
|
+
total: number;
|
|
425
|
+
}>;
|
|
426
|
+
getFailure(jobId: string): Promise<JobFailureDoc | null>;
|
|
427
|
+
private serializeInputValue;
|
|
428
|
+
private capLogs;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
declare class JobConfigsRepo extends Repository<JobConfigDoc> {
|
|
432
|
+
constructor(db: Db);
|
|
433
|
+
protected indexes(): IndexDescriptor[];
|
|
434
|
+
getConfig(jobType: string): Promise<JobConfigEntry>;
|
|
435
|
+
setEnabled(jobType: string, enabled: boolean): Promise<void>;
|
|
436
|
+
listAll(): Promise<JobConfigDoc[]>;
|
|
437
|
+
isEnabled(jobType: string): Promise<boolean>;
|
|
438
|
+
/**
|
|
439
|
+
* Idempotent bulk upsert of every registered jobType + its metadata
|
|
440
|
+
* projection (Story 5.1 D5.1-a). Called once per `RavenflowApp` instance
|
|
441
|
+
* inside `initializeMonitoring()`.
|
|
442
|
+
*
|
|
443
|
+
* Semantics:
|
|
444
|
+
* - `$setOnInsert { enabled: true, createdAt }` — the enabled flag is
|
|
445
|
+
* set to the default ONLY on initial insert. An operator-toggled
|
|
446
|
+
* `enabled: false` is NEVER overwritten by a subsequent boot.
|
|
447
|
+
* - `$set { metadata }` — metadata is always refreshed from code config
|
|
448
|
+
* (code is source of truth for non-user-writable fields).
|
|
449
|
+
* - `$currentDate { updatedAt }` — timestamp bumps every call.
|
|
450
|
+
*
|
|
451
|
+
* Metadata projection (narrow + stable — see D5.1-h):
|
|
452
|
+
* - `type`: jobConfig.type (always present in the discriminated union).
|
|
453
|
+
* - `queue`: jobConfig.queue ?? null (null = inherits defaultQueue).
|
|
454
|
+
* - `cron`: jobConfig.cron if present (token-bucket only today), else null.
|
|
455
|
+
*
|
|
456
|
+
* Additional type-specific fields (token-bucket's `rate`, distributed-etl's
|
|
457
|
+
* `steps`) are NOT extracted — BSON can't serialize the function references
|
|
458
|
+
* they contain, and the dashboard surface consumes only `type`/`queue`/`cron`
|
|
459
|
+
* in V1. Future stories can widen this projection without a schema change.
|
|
460
|
+
*
|
|
461
|
+
* Failure mode: `bulkWrite` is the only I/O. Empty input short-circuits
|
|
462
|
+
* because the driver rejects empty ops arrays.
|
|
463
|
+
*/
|
|
464
|
+
ensureEntries(jobs: Record<string, JobConfig>): Promise<void>;
|
|
465
|
+
private validateJobType;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
interface WorkerRegistryOptions {
|
|
469
|
+
heartbeatIntervalMs?: number;
|
|
470
|
+
/** @internal Injected for testability; real callers should rely on the default. */
|
|
471
|
+
hostnameProvider?: () => string;
|
|
472
|
+
/** @internal Injected for testability; real callers should rely on the default. */
|
|
473
|
+
pidProvider?: () => number;
|
|
474
|
+
/** @internal Injected for testability; real callers should rely on the default. */
|
|
475
|
+
workerIdProvider?: () => string;
|
|
476
|
+
/** @internal Injected for testability; real callers should rely on the default. */
|
|
477
|
+
clock?: () => Date;
|
|
478
|
+
}
|
|
479
|
+
declare class WorkerRegistry {
|
|
480
|
+
private readonly repo;
|
|
481
|
+
private readonly heartbeatIntervalMs;
|
|
482
|
+
private readonly hostname;
|
|
483
|
+
private readonly pid;
|
|
484
|
+
private readonly _workerId;
|
|
485
|
+
private readonly startedAt;
|
|
486
|
+
private readonly clock;
|
|
487
|
+
private timer;
|
|
488
|
+
private started;
|
|
489
|
+
private registered;
|
|
490
|
+
constructor(repo: WorkersRepo, options?: WorkerRegistryOptions);
|
|
491
|
+
get workerId(): string;
|
|
492
|
+
registerQueue(queue: WorkerQueueEntry): Promise<void>;
|
|
493
|
+
start(): Promise<void>;
|
|
494
|
+
stop(): Promise<void>;
|
|
495
|
+
private doHeartbeat;
|
|
496
|
+
private static validateHeartbeatInterval;
|
|
497
|
+
private static validateQueueEntry;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
interface BatchWriterConfig {
|
|
501
|
+
/** Max ops buffered before a size-triggered flush. Must be in [1000, 5000]. */
|
|
502
|
+
maxBufferSize: number;
|
|
503
|
+
/** Interval between time-triggered flushes in ms. Default 30000. Also used as `maxTimeMS` on bulkWrite. */
|
|
504
|
+
flushIntervalMs?: number;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
declare class BatchWriter<T extends Document = Document> {
|
|
508
|
+
private readonly collection;
|
|
509
|
+
private readonly maxBufferSize;
|
|
510
|
+
private readonly hardCapSize;
|
|
511
|
+
private readonly flushIntervalMs;
|
|
512
|
+
private buffer;
|
|
513
|
+
private timer;
|
|
514
|
+
private flushing;
|
|
515
|
+
private drained;
|
|
516
|
+
constructor(collection: Collection<T>, config: BatchWriterConfig);
|
|
517
|
+
push(operation: AnyBulkWriteOperation<T>): void;
|
|
518
|
+
drain(): Promise<void>;
|
|
519
|
+
private flush;
|
|
520
|
+
private doFlush;
|
|
521
|
+
private enforceHardCap;
|
|
522
|
+
private startTimer;
|
|
523
|
+
private stopTimer;
|
|
524
|
+
private resetTimer;
|
|
525
|
+
private validateConfig;
|
|
526
|
+
}
|
|
527
|
+
|
|
185
528
|
interface MongoConnectionObject {
|
|
186
529
|
host: string;
|
|
187
530
|
port?: number;
|
|
@@ -194,6 +537,14 @@ interface RavenflowAppOptions {
|
|
|
194
537
|
mongo?: string | MongoConnectionObject;
|
|
195
538
|
retention?: RetentionConfig;
|
|
196
539
|
logger?: LoggerOptions;
|
|
540
|
+
monitoring?: {
|
|
541
|
+
enabled?: boolean;
|
|
542
|
+
heartbeatIntervalMs?: number;
|
|
543
|
+
batchWriter?: {
|
|
544
|
+
maxBufferSize?: number;
|
|
545
|
+
flushIntervalMs?: number;
|
|
546
|
+
};
|
|
547
|
+
};
|
|
197
548
|
}
|
|
198
549
|
declare class RavenflowApp<TConfig extends RavenflowConfig = RavenflowConfig> {
|
|
199
550
|
protected redisConnection: ConnectionOptions;
|
|
@@ -205,13 +556,118 @@ declare class RavenflowApp<TConfig extends RavenflowConfig = RavenflowConfig> {
|
|
|
205
556
|
protected queues: QueueStore;
|
|
206
557
|
protected workers: Worker[];
|
|
207
558
|
protected stallWatcher: StallWatcher | null;
|
|
559
|
+
protected workersRepo: WorkersRepo | null;
|
|
560
|
+
protected workerRegistry: WorkerRegistry | null;
|
|
561
|
+
protected jobStatsRepo: JobStatsRepo | null;
|
|
562
|
+
protected jobErrorsRepo: JobErrorsRepo | null;
|
|
563
|
+
protected jobFailuresRepo: JobFailuresRepo | null;
|
|
564
|
+
protected jobConfigsRepo: JobConfigsRepo | null;
|
|
565
|
+
protected jobStatsBatchWriter: BatchWriter<JobStatsDoc> | null;
|
|
566
|
+
protected jobErrorsBatchWriter: BatchWriter<JobErrorDoc> | null;
|
|
567
|
+
protected jobFailuresBatchWriter: BatchWriter<JobFailureDoc> | null;
|
|
568
|
+
protected monitoringInitPromise: Promise<void> | null;
|
|
569
|
+
protected readonly monitoringOptions: {
|
|
570
|
+
enabled: boolean;
|
|
571
|
+
heartbeatIntervalMs: number | undefined;
|
|
572
|
+
batchWriter: BatchWriterConfig;
|
|
573
|
+
};
|
|
208
574
|
constructor(config: TConfig, options: RavenflowAppOptions);
|
|
209
575
|
startWorker(queueName: string, options?: {
|
|
210
576
|
concurrency?: number;
|
|
211
577
|
}): Promise<void>;
|
|
578
|
+
protected initializeMonitoring(queueName: string, concurrency: number): Promise<void>;
|
|
212
579
|
close(): Promise<void>;
|
|
213
580
|
}
|
|
214
581
|
|
|
582
|
+
declare function errorSignature(err: unknown): string;
|
|
583
|
+
|
|
584
|
+
declare function captureJobLogs(queue: Queue, jobId: string, maxChars: number): Promise<string>;
|
|
585
|
+
|
|
586
|
+
interface EventHooksOptions {
|
|
587
|
+
queue: Queue;
|
|
588
|
+
jobConfigs: Record<string, JobConfig>;
|
|
589
|
+
jobStatsRepo: JobStatsRepo;
|
|
590
|
+
jobErrorsRepo: JobErrorsRepo;
|
|
591
|
+
jobFailuresRepo: JobFailuresRepo;
|
|
592
|
+
jobStatsBatchWriter: BatchWriter<JobStatsDoc>;
|
|
593
|
+
jobErrorsBatchWriter: BatchWriter<JobErrorDoc>;
|
|
594
|
+
jobFailuresBatchWriter: BatchWriter<JobFailureDoc>;
|
|
595
|
+
logsMaxChars?: number;
|
|
596
|
+
}
|
|
597
|
+
declare function attachEventHooks(worker: Worker, options: EventHooksOptions): void;
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Inlined minute-count constants. Mirrors BullMQ's `MetricsTime` enum
|
|
601
|
+
* (bullmq@5.63.1) but severs the import edge so consumers of this constant
|
|
602
|
+
* don't pull BullMQ into their bundle. Values are pure math (minutes in
|
|
603
|
+
* the named window) and therefore stable across BullMQ versions. Lives
|
|
604
|
+
* here instead of `index.ts` to avoid an import cycle
|
|
605
|
+
* (`index.ts` → `./app/index` → `./app/app` → `worker-manager`).
|
|
606
|
+
*/
|
|
607
|
+
declare const METRICS_TIME: {
|
|
608
|
+
readonly ONE_MINUTE: 1;
|
|
609
|
+
readonly FIVE_MINUTES: 5;
|
|
610
|
+
readonly FIFTEEN_MINUTES: 15;
|
|
611
|
+
readonly THIRTY_MINUTES: 30;
|
|
612
|
+
readonly ONE_HOUR: 60;
|
|
613
|
+
readonly ONE_DAY: 1440;
|
|
614
|
+
readonly ONE_WEEK: 10080;
|
|
615
|
+
readonly TWO_WEEKS: 20160;
|
|
616
|
+
readonly ONE_MONTH: 80640;
|
|
617
|
+
};
|
|
618
|
+
/**
|
|
619
|
+
* Default BullMQ metrics configuration applied to every ravenflow-managed
|
|
620
|
+
* Worker. Enables the per-minute data-point collection read by the
|
|
621
|
+
* ravenflow-dashboard Jobs-per-Second indicator (Epic 4 Story 4.2).
|
|
622
|
+
*
|
|
623
|
+
* Without this default, BullMQ's `collectMetrics` Lua function is a no-op on
|
|
624
|
+
* every `moveToFinished` — `queue.getMetrics('completed')` would return
|
|
625
|
+
* `{ meta: { count: 0 }, data: [] }` indefinitely for Ravenflow-managed
|
|
626
|
+
* queues (silent false-negative under real throughput).
|
|
627
|
+
*
|
|
628
|
+
* `ONE_WEEK = 10_080` minutes ≈ 7 days of minute-resolution history at a
|
|
629
|
+
* Redis footprint of ~10 KB/queue/week. Callers may override via the
|
|
630
|
+
* `workerOpts.metrics` parameter on `createWorker` (e.g. to disable
|
|
631
|
+
* collection or pick a shorter window).
|
|
632
|
+
*
|
|
633
|
+
* Frozen so the exported reference cannot be mutated by a caller (shared
|
|
634
|
+
* default would otherwise bleed globally across `createWorker` calls).
|
|
635
|
+
*/
|
|
636
|
+
declare const DEFAULT_WORKER_METRICS: NonNullable<WorkerOptions["metrics"]>;
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Delay (ms) applied to jobs whose jobType is `enabled: false`. After this
|
|
640
|
+
* window BullMQ returns the job to the `waiting` set and a worker picks it
|
|
641
|
+
* up again; the gate re-evaluates. When the jobType re-enables the next
|
|
642
|
+
* attempt passes the check and dispatches normally.
|
|
643
|
+
*
|
|
644
|
+
* 60 seconds strikes the balance between "pause takes effect quickly" and
|
|
645
|
+
* "don't hammer Redis / Mongo with redefer churn" (D5.1-b).
|
|
646
|
+
*/
|
|
647
|
+
declare const DISABLED_JOB_DEFER_DELAY_MS = 60000;
|
|
648
|
+
/**
|
|
649
|
+
* Worker-side defer gate for disabled jobTypes (Story 5.1 D5.1-b).
|
|
650
|
+
*
|
|
651
|
+
* When the jobType's `enabled` flag is `false` in `job_configs`, this
|
|
652
|
+
* helper moves the job to `delayed` with a +60s timestamp, increments
|
|
653
|
+
* the cumulative `disabledDeferCount` on `job.progress`, and throws
|
|
654
|
+
* `DelayedError` — the BullMQ-canonical signal for "I moved this job to
|
|
655
|
+
* delayed, don't mark completed/failed" (verified against
|
|
656
|
+
* node_modules/bullmq/dist/esm/classes/errors/delayed-error.d.ts:9
|
|
657
|
+
* bullmq@5.63.1).
|
|
658
|
+
*
|
|
659
|
+
* When the jobType is enabled (or no row exists — default is `true`),
|
|
660
|
+
* this is a no-op and the caller proceeds to normal dispatch.
|
|
661
|
+
*
|
|
662
|
+
* The `token` argument is the processor lambda's 2nd BullMQ argument
|
|
663
|
+
* (verified against node_modules/bullmq/dist/esm/classes/job.d.ts:444
|
|
664
|
+
* bullmq@5.63.1 — moveToDelayed(timestamp, token?) — passing undefined
|
|
665
|
+
* is accepted but may fail under lock contention with renewLock).
|
|
666
|
+
*/
|
|
667
|
+
declare function deferIfDisabled(job: Job, jobConfigs: Record<string, JobConfig>, jobConfigsRepo: JobConfigsRepo, token: string | undefined): Promise<void>;
|
|
668
|
+
|
|
669
|
+
declare function computeBucketStart(ts: Date, granularity: BucketGranularity): Date;
|
|
670
|
+
|
|
215
671
|
declare function createRavenflowApp<TConfig extends RavenflowConfig>(config: TConfig, options: RavenflowAppOptions): RavenflowApp<TConfig>;
|
|
216
672
|
|
|
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 };
|
|
673
|
+
export { BatchWriter, type BatchWriterConfig, type BucketGranularity, DEFAULT_WORKER_METRICS, DISABLED_JOB_DEFER_DELAY_MS, type DistributedEtlJobConfig, type EtlExtractResult, type EtlResult, type EventHooksOptions, type IncrementStatsInput, type IndexDescriptor, JOB_FAILURES_INPUT_MAX_CHARS, JOB_FAILURES_LOGS_MAX_CHARS, JOB_FAILURES_MESSAGE_MAX_CHARS, JOB_FAILURES_STACK_MAX_CHARS, type JobConfig, type JobConfigDoc, type JobConfigEntry, JobConfigsRepo, type JobErrorDoc, JobErrorsRepo, type JobFailureDoc, JobFailuresRepo, type JobStatsDoc, JobStatsRepo, type JobStatus, type ListFailuresOptions, type Logger, type LoggerOptions, METRICS_TIME, type MemoryEtlJobConfig, type MemoryEtlStepConfig, type MongoConnectionObject, type RateLimitConfig, RavenflowApp, type RavenflowAppOptions, type RavenflowConfig, type RecordFailureInput, type RecordOccurrenceInput, type RegisterWorkerInput, Repository, type RetentionConfig, type SimpleJobConfig, type StatefulEtlJobConfig, type StepConfig, type TokenBucketInterval, type TokenBucketJobConfig, type TopErrorsOptions, type UpsertWorkerQueueInput, type WorkerDoc, type WorkerQueueEntry, WorkerRegistry, type WorkerRegistryOptions, WorkersRepo, attachEventHooks, captureJobLogs, computeBucketStart, createRavenflowApp, deferIfDisabled, defineConfig, defineDistributedEtlJob, defineMemoryEtlJob, defineSimpleJob, defineStatefulEtlJob, defineTokenBucketJob, errorSignature, getLogger, initLogger };
|