@rpcbase/worker 0.32.0 → 0.33.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/dist/index.js +116 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,7 +22,9 @@ const getConcurrency = () => {
|
|
|
22
22
|
if (!Number.isFinite(value) || value <= 0) return DEFAULT_CONCURRENCY;
|
|
23
23
|
return Math.floor(value);
|
|
24
24
|
};
|
|
25
|
-
const getConnection = () => ({
|
|
25
|
+
const getConnection = () => ({
|
|
26
|
+
url: getRedisUrl()
|
|
27
|
+
});
|
|
26
28
|
const ensureQueue = () => {
|
|
27
29
|
if (queueInstance) return queueInstance;
|
|
28
30
|
queueInstance = new Queue(getQueueName(), {
|
|
@@ -49,26 +51,26 @@ const start = async () => {
|
|
|
49
51
|
hasStarted = true;
|
|
50
52
|
const queue = ensureQueue();
|
|
51
53
|
const concurrency = getConcurrency();
|
|
52
|
-
console.log("start worker queue", {
|
|
54
|
+
console.log("start worker queue", {
|
|
55
|
+
queue: queue.name,
|
|
56
|
+
redisUrl: getRedisUrl(),
|
|
57
|
+
concurrency
|
|
58
|
+
});
|
|
53
59
|
queue.on("error", (err) => {
|
|
54
60
|
console.log(`queue error: ${err.message}`);
|
|
55
61
|
});
|
|
56
62
|
if (!workerInstance) {
|
|
57
|
-
workerInstance = new Worker(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!handler) {
|
|
63
|
-
throw new Error(`No task registered for '${taskName}'`);
|
|
64
|
-
}
|
|
65
|
-
return await handler(job.data, job);
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
concurrency,
|
|
69
|
-
connection: getConnection()
|
|
63
|
+
workerInstance = new Worker(queue.name, async (job) => {
|
|
64
|
+
const taskName = job.name;
|
|
65
|
+
const handler = tasksByName[taskName];
|
|
66
|
+
if (!handler) {
|
|
67
|
+
throw new Error(`No task registered for '${taskName}'`);
|
|
70
68
|
}
|
|
71
|
-
|
|
69
|
+
return await handler(job.data, job);
|
|
70
|
+
}, {
|
|
71
|
+
concurrency,
|
|
72
|
+
connection: getConnection()
|
|
73
|
+
});
|
|
72
74
|
}
|
|
73
75
|
workerInstance.on("error", (err) => {
|
|
74
76
|
console.log(`worker error: ${err.message}`);
|
|
@@ -95,7 +97,11 @@ const close = async () => {
|
|
|
95
97
|
};
|
|
96
98
|
const getUrl = () => getRedisUrl();
|
|
97
99
|
async function scheduleTask(taskName, payload, options) {
|
|
98
|
-
const {
|
|
100
|
+
const {
|
|
101
|
+
jobId,
|
|
102
|
+
repeat,
|
|
103
|
+
...rest
|
|
104
|
+
} = options;
|
|
99
105
|
return add(taskName, payload, {
|
|
100
106
|
...rest,
|
|
101
107
|
jobId: jobId ?? `schedule|${taskName}`,
|
|
@@ -186,7 +192,10 @@ const dispatchWorkerQueue = async ({
|
|
|
186
192
|
const handlerName = `on-${taskOp}-${modelName}`;
|
|
187
193
|
if (!tasks[handlerName]) return;
|
|
188
194
|
const normalizedUpdateDescription = normalizeUpdateDescription(updateDescription);
|
|
189
|
-
await queueApi.add(handlerName, {
|
|
195
|
+
await queueApi.add(handlerName, {
|
|
196
|
+
doc,
|
|
197
|
+
updateDescription: normalizedUpdateDescription
|
|
198
|
+
}, {
|
|
190
199
|
jobId: `${dbName}|${taskOp}-${getDocumentId(doc) ?? "unknown"}`,
|
|
191
200
|
removeOnComplete: true,
|
|
192
201
|
removeOnFail: true
|
|
@@ -200,7 +209,11 @@ const normalizeRetryDelays = (input) => {
|
|
|
200
209
|
const rawMaxMs = Math.max(0, Math.floor(input?.maxMs ?? RETRY_MAXIMUM_DELAY_MS));
|
|
201
210
|
const maxMs = Math.max(minMs, rawMaxMs);
|
|
202
211
|
const factor = Math.max(1, Number.isFinite(input?.factor) ? input?.factor ?? RETRY_DEFAULT_FACTOR : RETRY_DEFAULT_FACTOR);
|
|
203
|
-
return {
|
|
212
|
+
return {
|
|
213
|
+
minMs,
|
|
214
|
+
maxMs,
|
|
215
|
+
factor
|
|
216
|
+
};
|
|
204
217
|
};
|
|
205
218
|
const getRetryDelayMs = (attempt, delays) => Math.min(delays.maxMs, Math.round(delays.minMs * Math.pow(delays.factor, Math.max(0, attempt - 1))));
|
|
206
219
|
const normalizeMaxRetries = (value) => {
|
|
@@ -229,7 +242,10 @@ const registerQueueListener = async (options = {}) => {
|
|
|
229
242
|
let stream = null;
|
|
230
243
|
let resumeAfter = null;
|
|
231
244
|
let processing = Promise.resolve();
|
|
232
|
-
let status = {
|
|
245
|
+
let status = {
|
|
246
|
+
state: "connecting",
|
|
247
|
+
attempt: 1
|
|
248
|
+
};
|
|
233
249
|
const setStatus = (next) => {
|
|
234
250
|
status = next;
|
|
235
251
|
try {
|
|
@@ -279,7 +295,9 @@ const registerQueueListener = async (options = {}) => {
|
|
|
279
295
|
if (!readySettled) {
|
|
280
296
|
rejectReady(new Error("queue listener closed before ready"));
|
|
281
297
|
}
|
|
282
|
-
setStatus({
|
|
298
|
+
setStatus({
|
|
299
|
+
state: "closed"
|
|
300
|
+
});
|
|
283
301
|
};
|
|
284
302
|
const closeResources = async () => {
|
|
285
303
|
try {
|
|
@@ -295,7 +313,11 @@ const registerQueueListener = async (options = {}) => {
|
|
|
295
313
|
client = null;
|
|
296
314
|
};
|
|
297
315
|
const startStream = async () => {
|
|
298
|
-
if (stopped) return {
|
|
316
|
+
if (stopped) return {
|
|
317
|
+
stoppedPromise: Promise.resolve({
|
|
318
|
+
reason: "close"
|
|
319
|
+
})
|
|
320
|
+
};
|
|
299
321
|
if (stream) {
|
|
300
322
|
try {
|
|
301
323
|
stream.removeAllListeners();
|
|
@@ -313,23 +335,37 @@ const registerQueueListener = async (options = {}) => {
|
|
|
313
335
|
}
|
|
314
336
|
client = new MongoClient(mongoUrl, mongoClientOptions);
|
|
315
337
|
await client.connect();
|
|
316
|
-
const dbMatch = {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
338
|
+
const dbMatch = {
|
|
339
|
+
"ns.db": {
|
|
340
|
+
$regex: `^${escapeRegex(appName)}-.*-db$`
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
const pipeline = [{
|
|
344
|
+
$match: dbMatch
|
|
345
|
+
}, {
|
|
346
|
+
$match: {
|
|
347
|
+
operationType: {
|
|
348
|
+
$in: ["insert", "update", "replace", "delete"]
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}, {
|
|
352
|
+
$match: {
|
|
353
|
+
"ns.coll": {
|
|
354
|
+
$nin: Array.from(INTERNAL_IGNORED_COLLECTION_NAMES)
|
|
322
355
|
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
];
|
|
356
|
+
}
|
|
357
|
+
}];
|
|
326
358
|
stream = client.watch(pipeline, {
|
|
327
359
|
fullDocument: "updateLookup",
|
|
328
|
-
...resumeAfter ? {
|
|
360
|
+
...resumeAfter ? {
|
|
361
|
+
resumeAfter
|
|
362
|
+
} : {}
|
|
329
363
|
});
|
|
330
364
|
const stoppedPromise = new Promise((resolve) => {
|
|
331
365
|
let settled = false;
|
|
332
|
-
const onAbort = () => settle({
|
|
366
|
+
const onAbort = () => settle({
|
|
367
|
+
reason: "close"
|
|
368
|
+
});
|
|
333
369
|
const settle = (result) => {
|
|
334
370
|
if (settled) return;
|
|
335
371
|
settled = true;
|
|
@@ -337,8 +373,13 @@ const registerQueueListener = async (options = {}) => {
|
|
|
337
373
|
resolve(result);
|
|
338
374
|
};
|
|
339
375
|
abortController.signal.addEventListener("abort", onAbort);
|
|
340
|
-
stream?.once("close", () => settle({
|
|
341
|
-
|
|
376
|
+
stream?.once("close", () => settle({
|
|
377
|
+
reason: "close"
|
|
378
|
+
}));
|
|
379
|
+
stream?.once("error", (err) => settle({
|
|
380
|
+
reason: "error",
|
|
381
|
+
error: err
|
|
382
|
+
}));
|
|
342
383
|
});
|
|
343
384
|
stream.on("change", (change) => {
|
|
344
385
|
const streamRef = stream;
|
|
@@ -393,23 +434,36 @@ const registerQueueListener = async (options = {}) => {
|
|
|
393
434
|
} catch {
|
|
394
435
|
}
|
|
395
436
|
});
|
|
396
|
-
return {
|
|
437
|
+
return {
|
|
438
|
+
stoppedPromise
|
|
439
|
+
};
|
|
397
440
|
};
|
|
398
441
|
const run = async () => {
|
|
399
442
|
let retryCounter = 0;
|
|
400
443
|
while (!stopped) {
|
|
401
444
|
try {
|
|
402
|
-
setStatus({
|
|
403
|
-
|
|
445
|
+
setStatus({
|
|
446
|
+
state: "connecting",
|
|
447
|
+
attempt: retryCounter + 1
|
|
448
|
+
});
|
|
449
|
+
const {
|
|
450
|
+
stoppedPromise
|
|
451
|
+
} = await startStream();
|
|
404
452
|
retryCounter = 0;
|
|
405
|
-
setStatus({
|
|
453
|
+
setStatus({
|
|
454
|
+
state: "ready"
|
|
455
|
+
});
|
|
406
456
|
resolveReady();
|
|
407
457
|
const end = await stoppedPromise;
|
|
408
458
|
if (stopped) return;
|
|
409
459
|
retryCounter += 1;
|
|
410
460
|
if (maxRetries !== "infinite" && retryCounter > maxRetries) {
|
|
411
461
|
const err2 = end.reason === "error" ? end.error : new Error("queue listener closed");
|
|
412
|
-
setStatus({
|
|
462
|
+
setStatus({
|
|
463
|
+
state: "failed",
|
|
464
|
+
attempt: retryCounter,
|
|
465
|
+
error: err2
|
|
466
|
+
});
|
|
413
467
|
if (fatalOnMaxRetries) {
|
|
414
468
|
try {
|
|
415
469
|
options.onFatal?.(err2);
|
|
@@ -425,14 +479,23 @@ const registerQueueListener = async (options = {}) => {
|
|
|
425
479
|
const delayMs = getRetryDelayMs(retryCounter, retryDelays);
|
|
426
480
|
const err = end.reason === "error" ? end.error : new Error("queue listener closed");
|
|
427
481
|
console.warn("queue listener not ready, retrying in", delayMs, err);
|
|
428
|
-
setStatus({
|
|
482
|
+
setStatus({
|
|
483
|
+
state: "error",
|
|
484
|
+
attempt: retryCounter,
|
|
485
|
+
error: err,
|
|
486
|
+
nextRetryInMs: delayMs
|
|
487
|
+
});
|
|
429
488
|
await closeResources();
|
|
430
489
|
await sleep(delayMs, abortController.signal);
|
|
431
490
|
} catch (err) {
|
|
432
491
|
if (stopped) return;
|
|
433
492
|
retryCounter += 1;
|
|
434
493
|
if (maxRetries !== "infinite" && retryCounter > maxRetries) {
|
|
435
|
-
setStatus({
|
|
494
|
+
setStatus({
|
|
495
|
+
state: "failed",
|
|
496
|
+
attempt: retryCounter,
|
|
497
|
+
error: err
|
|
498
|
+
});
|
|
436
499
|
if (fatalOnMaxRetries) {
|
|
437
500
|
try {
|
|
438
501
|
options.onFatal?.(err);
|
|
@@ -447,7 +510,12 @@ const registerQueueListener = async (options = {}) => {
|
|
|
447
510
|
}
|
|
448
511
|
const delayMs = getRetryDelayMs(retryCounter, retryDelays);
|
|
449
512
|
console.warn("queue listener not ready, retrying in", delayMs, err);
|
|
450
|
-
setStatus({
|
|
513
|
+
setStatus({
|
|
514
|
+
state: "error",
|
|
515
|
+
attempt: retryCounter,
|
|
516
|
+
error: err,
|
|
517
|
+
nextRetryInMs: delayMs
|
|
518
|
+
});
|
|
451
519
|
await closeResources();
|
|
452
520
|
await sleep(delayMs, abortController.signal);
|
|
453
521
|
}
|
|
@@ -460,7 +528,11 @@ const registerQueueListener = async (options = {}) => {
|
|
|
460
528
|
};
|
|
461
529
|
void run().catch(async (err) => {
|
|
462
530
|
if (stopped) return;
|
|
463
|
-
setStatus({
|
|
531
|
+
setStatus({
|
|
532
|
+
state: "failed",
|
|
533
|
+
attempt: 0,
|
|
534
|
+
error: err
|
|
535
|
+
});
|
|
464
536
|
rejectReady(err);
|
|
465
537
|
await closeResources();
|
|
466
538
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/queue.ts","../src/queueListener.ts","../src/taskNames.ts"],"sourcesContent":["import { Queue, Worker, type Job, type JobsOptions as JobOptions } from \"bullmq\"\n\nimport type { WorkerTasksMap } from \"./tasksMap\"\n\n\nexport type TaskHandler<TPayload = unknown> = (payload: TPayload, job: Job) => unknown | Promise<unknown>\n\nconst DEFAULT_QUEUE_NAME = \"rb-queue-default\"\nconst DEFAULT_CONCURRENCY = 2\nconst DEFAULT_RENTENTION = 128\n\nconst tasksByName: Record<string, TaskHandler<unknown>> = Object.create(null)\n\nlet queueInstance: Queue | null = null\nlet workerInstance: Worker | null = null\nlet hasStarted = false\n\nconst getRedisUrl = (): string => {\n const redisUrl = process.env.REDIS_URL?.trim()\n if (!redisUrl) {\n throw new Error(\"Missing REDIS_URL (required for @rpcbase/worker queue)\")\n }\n return redisUrl\n}\n\nconst getQueueName = (): string => process.env.RB_QUEUE_NAME?.trim() || DEFAULT_QUEUE_NAME\n\nconst getConcurrency = (): number => {\n const raw = process.env.RB_QUEUE_CONCURRENCY?.trim()\n const value = raw ? Number(raw) : DEFAULT_CONCURRENCY\n if (!Number.isFinite(value) || value <= 0) return DEFAULT_CONCURRENCY\n return Math.floor(value)\n}\n\nconst getConnection = () => ({ url: getRedisUrl() })\n\nconst ensureQueue = (): Queue => {\n if (queueInstance) return queueInstance\n queueInstance = new Queue(getQueueName(), {\n connection: getConnection(),\n defaultJobOptions: {\n removeOnComplete: DEFAULT_RENTENTION,\n removeOnFail: DEFAULT_RENTENTION\n },\n })\n\n return queueInstance\n}\n\nexport function registerTask<TName extends keyof WorkerTasksMap & string>(\n name: TName,\n handler: TaskHandler<WorkerTasksMap[TName]>,\n): void\nexport function registerTask(name: string, handler: TaskHandler<unknown>): void\nexport function registerTask(name: string, handler: TaskHandler<unknown>): void {\n tasksByName[name] = handler\n}\n\nexport const getTasks = (): Record<string, TaskHandler<unknown>> => tasksByName\n\nexport function add<TName extends keyof WorkerTasksMap & string>(\n taskName: TName,\n payload: WorkerTasksMap[TName],\n options?: JobOptions,\n): Promise<Job>\nexport function add(taskName: string, payload: unknown, options?: JobOptions): Promise<Job>\nexport function add(taskName: string, payload: unknown, options?: JobOptions): Promise<Job> {\n return ensureQueue().add(taskName, payload, options)\n}\n\nexport const getJob = async (jobId: string): Promise<Job | null> => (await ensureQueue().getJob(jobId)) ?? null\n\nexport const getJobs = async (...args: Parameters<Queue[\"getJobs\"]>): Promise<Job[]> =>\n ensureQueue().getJobs(...args) as unknown as Job[]\n\nexport const getInstance = (): Queue => ensureQueue()\n\nexport const start = async (): Promise<Queue> => {\n if (hasStarted) return ensureQueue()\n hasStarted = true\n\n const queue = ensureQueue()\n const concurrency = getConcurrency()\n\n console.log(\"start worker queue\", { queue: queue.name, redisUrl: getRedisUrl(), concurrency })\n\n queue.on(\"error\", (err) => {\n console.log(`queue error: ${err.message}`)\n })\n\n if (!workerInstance) {\n workerInstance = new Worker(\n queue.name,\n async (job) => {\n const taskName = job.name\n\n const handler = tasksByName[taskName]\n if (!handler) {\n throw new Error(`No task registered for '${taskName}'`)\n }\n\n return await handler(job.data, job)\n },\n {\n concurrency,\n connection: getConnection(),\n },\n )\n }\n\n workerInstance.on(\"error\", (err) => {\n console.log(`worker error: ${err.message}`)\n })\n\n workerInstance.on(\"stalled\", (jobId) => {\n console.log(`job ${jobId} stalled`)\n })\n\n workerInstance.on(\"failed\", (job, err) => {\n console.log(`job ${job?.id ?? \"unknown\"} failed`, err)\n })\n\n await workerInstance.waitUntilReady()\n\n return queue\n}\n\nexport const close = async (): Promise<void> => {\n if (!queueInstance && !workerInstance) return\n try {\n await workerInstance?.close()\n await queueInstance?.close()\n } finally {\n workerInstance = null\n queueInstance = null\n hasStarted = false\n }\n}\n\nexport const getUrl = (): string => getRedisUrl()\n\nexport type ScheduleTaskOptions = Omit<JobOptions, \"repeat\"> & {\n jobId?: string\n repeat: NonNullable<JobOptions[\"repeat\"]>\n}\n\nexport function scheduleTask<TName extends keyof WorkerTasksMap & string>(\n taskName: TName,\n payload: WorkerTasksMap[TName],\n options: ScheduleTaskOptions,\n): Promise<Job>\nexport function scheduleTask(taskName: string, payload: unknown, options: ScheduleTaskOptions): Promise<Job>\nexport async function scheduleTask(taskName: string, payload: unknown, options: ScheduleTaskOptions): Promise<Job> {\n const { jobId, repeat, ...rest } = options\n return add(taskName, payload, {\n ...rest,\n jobId: jobId ?? `schedule|${taskName}`,\n repeat,\n })\n}\n\nconst queueApi = {\n start,\n close,\n registerTask,\n getTasks,\n add,\n scheduleTask,\n getJob,\n getJobs,\n getInstance,\n getUrl,\n}\n\nexport default queueApi\n","import {\n MongoClient,\n type ChangeStream,\n type ChangeStreamDocument,\n type Document,\n type MongoClientOptions,\n} from \"mongodb\"\nimport mongoose from \"mongoose\"\n\nimport queue from \"./queue\"\n\n\nexport type QueueListenerRetryDelays = {\n minMs?: number\n maxMs?: number\n factor?: number\n}\n\nexport type QueueListenerStatus =\n | { state: \"connecting\"; attempt: number }\n | { state: \"ready\" }\n | { state: \"error\"; attempt: number; error: unknown; nextRetryInMs: number }\n | { state: \"failed\"; attempt: number; error: unknown }\n | { state: \"closed\" }\n\nexport type QueueListenerHandle = {\n ready: Promise<void>\n close: () => Promise<void>\n getStatus: () => QueueListenerStatus\n}\n\nexport type QueueListenerOptions = {\n maxRetries?: number | \"infinite\"\n fatalOnMaxRetries?: boolean\n onStateChange?: (status: QueueListenerStatus) => void\n onFatal?: (err: unknown) => void\n retryDelays?: QueueListenerRetryDelays\n mongoClientOptions?: MongoClientOptions\n}\n\nconst RETRY_MAXIMUM_DELAY_MS = 3000\nconst RETRY_MINIMUM_DELAY_MS = 50\nconst RETRY_DEFAULT_FACTOR = 2\n\nconst sleep = async (ms: number, signal?: AbortSignal): Promise<void> => new Promise((resolve) => {\n if (signal?.aborted) {\n resolve()\n return\n }\n\n let timeout: ReturnType<typeof setTimeout> | null = null\n\n const cleanup = () => {\n if (timeout) clearTimeout(timeout)\n signal?.removeEventListener(\"abort\", onAbort)\n }\n\n const onAbort = () => {\n cleanup()\n resolve()\n }\n\n timeout = setTimeout(() => {\n cleanup()\n resolve()\n }, ms)\n timeout.unref?.()\n\n signal?.addEventListener(\"abort\", onAbort)\n})\n\nconst getMongoUrl = (): string => {\n const explicit =\n process.env.MONGODB_URL\n ?? process.env.MONGO_URL\n ?? process.env.MONGODB_URI\n ?? process.env.DB_URL\n\n if (explicit && explicit.trim()) return explicit.trim()\n\n const port = process.env.DB_PORT?.trim()\n if (!port) throw new Error(\"Missing Mongo connection details (MONGODB_URL/MONGO_URL/MONGODB_URI/DB_URL/DB_PORT)\")\n\n const host = process.env.DB_HOST?.trim() || \"localhost\"\n return `mongodb://${host}:${port}`\n}\n\nconst escapeRegex = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n\ntype ModelWithCollection = {\n collection?: {\n collectionName?: string\n name?: string\n }\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null\n\nconst getDocumentId = (doc: unknown): unknown => {\n if (!isRecord(doc)) return undefined\n return doc._id\n}\n\nconst resolveModelNameFromCollection = (collName: string): string | null => {\n const models = mongoose.models\n\n for (const modelName of Object.keys(models)) {\n const model = models[modelName] as ModelWithCollection\n const collectionName =\n model.collection?.collectionName\n ?? model.collection?.name\n\n if (collectionName === collName) {\n return modelName\n }\n }\n\n return null\n}\n\nconst normalizeUpdateDescription = (updateDescription: unknown): unknown => {\n if (!isRecord(updateDescription)) return updateDescription\n if (isRecord(updateDescription.updatedFields)) {\n return {\n ...updateDescription,\n updatedFieldsKeys: Object.keys(updateDescription.updatedFields),\n }\n }\n\n return updateDescription\n}\n\nconst normalizeOpForTaskName = (op: string): string => (op === \"replace\" ? \"update\" : op)\n\nconst dispatchWorkerQueue = async ({\n dbName,\n modelName,\n op,\n doc,\n updateDescription,\n}: {\n dbName: string\n modelName: string\n op: string\n doc: unknown\n updateDescription?: unknown\n}): Promise<void> => {\n const tasks = queue.getTasks()\n const taskOp = normalizeOpForTaskName(op)\n const handlerName = `on-${taskOp}-${modelName}`\n\n if (!tasks[handlerName]) return\n\n const normalizedUpdateDescription = normalizeUpdateDescription(updateDescription)\n\n await queue.add(handlerName, { doc, updateDescription: normalizedUpdateDescription }, {\n jobId: `${dbName}|${taskOp}-${getDocumentId(doc) ?? \"unknown\"}`,\n removeOnComplete: true,\n removeOnFail: true,\n })\n}\n\nconst shouldSkipCollection = (collName: string): boolean =>\n collName.endsWith(\".files\") || collName.endsWith(\".chunks\")\n\nconst INTERNAL_IGNORED_MODEL_NAMES = new Set([\"RBRtsChange\", \"RBRtsCounter\"])\n\nconst INTERNAL_IGNORED_COLLECTION_NAMES = new Set([\"rtschanges\", \"rtscounters\"])\n\nconst normalizeRetryDelays = (input: QueueListenerRetryDelays | undefined): Required<QueueListenerRetryDelays> => {\n const minMs = Math.max(0, Math.floor(input?.minMs ?? RETRY_MINIMUM_DELAY_MS))\n const rawMaxMs = Math.max(0, Math.floor(input?.maxMs ?? RETRY_MAXIMUM_DELAY_MS))\n const maxMs = Math.max(minMs, rawMaxMs)\n const factor = Math.max(1, Number.isFinite(input?.factor) ? (input?.factor ?? RETRY_DEFAULT_FACTOR) : RETRY_DEFAULT_FACTOR)\n return { minMs, maxMs, factor }\n}\n\nconst getRetryDelayMs = (attempt: number, delays: Required<QueueListenerRetryDelays>): number =>\n Math.min(delays.maxMs, Math.round(delays.minMs * Math.pow(delays.factor, Math.max(0, attempt - 1))))\n\nconst normalizeMaxRetries = (value: number | \"infinite\"): number | \"infinite\" => {\n if (value === \"infinite\") return value\n const parsed = Math.floor(value)\n return Number.isFinite(parsed) ? Math.max(0, parsed) : 0\n}\n\nexport const registerQueueListener = async (options: QueueListenerOptions = {}): Promise<QueueListenerHandle> => {\n const maxRetries = normalizeMaxRetries(options.maxRetries ?? \"infinite\")\n const fatalOnMaxRetries = options.fatalOnMaxRetries ?? false\n const retryDelays = normalizeRetryDelays(options.retryDelays)\n\n const appName = process.env.APP_NAME?.trim()\n if (!appName) {\n throw new Error(\"Missing APP_NAME (required to configure the worker DB change listener)\")\n }\n\n const mongoUrl = getMongoUrl()\n const mongoClientOptions: MongoClientOptions = {\n family: 4,\n serverSelectionTimeoutMS: 2000,\n connectTimeoutMS: 2000,\n ...options.mongoClientOptions,\n }\n\n let stopped = false\n const abortController = new AbortController()\n let client: MongoClient | null = null\n let stream: ChangeStream<Document> | null = null\n let resumeAfter: Document | null = null\n let processing = Promise.resolve()\n\n let status: QueueListenerStatus = { state: \"connecting\", attempt: 1 }\n const setStatus = (next: QueueListenerStatus): void => {\n status = next\n try {\n options.onStateChange?.(next)\n } catch (err) {\n console.warn(\"queue listener onStateChange failed\", err)\n }\n }\n\n let readySettled = false\n let readyResolve: (() => void) | null = null\n let readyReject: ((err: unknown) => void) | null = null\n const ready = new Promise<void>((resolve, reject) => {\n readyResolve = resolve\n readyReject = reject\n })\n\n const resolveReady = (): void => {\n if (readySettled) return\n readySettled = true\n readyResolve?.()\n }\n\n const rejectReady = (err: unknown): void => {\n if (readySettled) return\n readySettled = true\n readyReject?.(err)\n }\n\n const isChangeStreamHistoryLost = (err: unknown): boolean => {\n const maybeErr = err as { code?: unknown; codeName?: unknown }\n const code = typeof maybeErr.code === \"number\" ? maybeErr.code : null\n const codeName = typeof maybeErr.codeName === \"string\" ? maybeErr.codeName : \"\"\n const message = err instanceof Error ? err.message : String(err ?? \"\")\n\n return (\n code === 286\n || codeName === \"ChangeStreamHistoryLost\"\n || message.includes(\"ChangeStreamHistoryLost\")\n || message.includes(\"resume token\")\n || message.includes(\"Resume token\")\n || message.includes(\"resume of change stream was not possible\")\n || message.includes(\"cannot resume\")\n )\n }\n\n const close = async (): Promise<void> => {\n stopped = true\n abortController.abort()\n try {\n stream?.removeAllListeners()\n await stream?.close()\n } catch {\n // ignore\n }\n stream = null\n\n try {\n await client?.close()\n } catch {\n // ignore\n }\n client = null\n\n if (!readySettled) {\n rejectReady(new Error(\"queue listener closed before ready\"))\n }\n\n setStatus({ state: \"closed\" })\n }\n\n const closeResources = async (): Promise<void> => {\n try {\n stream?.removeAllListeners()\n await stream?.close()\n } catch {\n // ignore\n }\n stream = null\n\n try {\n await client?.close()\n } catch {\n // ignore\n }\n client = null\n }\n\n const startStream = async (): Promise<{ stoppedPromise: Promise<{ reason: \"close\" | \"error\"; error?: unknown }> }> => {\n if (stopped) return { stoppedPromise: Promise.resolve({ reason: \"close\" }) }\n\n if (stream) {\n try {\n stream.removeAllListeners()\n await stream.close()\n } catch {\n // ignore\n }\n stream = null\n }\n\n if (client) {\n try {\n await client.close()\n } catch {\n // ignore\n }\n client = null\n }\n\n client = new MongoClient(mongoUrl, mongoClientOptions)\n\n await client.connect()\n\n const dbMatch = { \"ns.db\": { $regex: `^${escapeRegex(appName)}-.*-db$` } }\n\n const pipeline: Document[] = [\n { $match: dbMatch },\n {\n $match: {\n operationType: { $in: [\"insert\", \"update\", \"replace\", \"delete\"] },\n }\n },\n { $match: { \"ns.coll\": { $nin: Array.from(INTERNAL_IGNORED_COLLECTION_NAMES) } } },\n ]\n\n stream = client.watch(pipeline, {\n fullDocument: \"updateLookup\",\n ...(resumeAfter ? { resumeAfter } : {}),\n })\n\n const stoppedPromise = new Promise<{ reason: \"close\" | \"error\"; error?: unknown }>((resolve) => {\n let settled = false\n const onAbort = () => settle({ reason: \"close\" })\n\n const settle = (result: { reason: \"close\" | \"error\"; error?: unknown }) => {\n if (settled) return\n settled = true\n abortController.signal.removeEventListener(\"abort\", onAbort)\n resolve(result)\n }\n\n abortController.signal.addEventListener(\"abort\", onAbort)\n stream?.once(\"close\", () => settle({ reason: \"close\" }))\n stream?.once(\"error\", (err) => settle({ reason: \"error\", error: err }))\n })\n\n stream.on(\"change\", (change: ChangeStreamDocument<Document>) => {\n const streamRef = stream\n processing = processing.then(async () => {\n const ns = \"ns\" in change ? change.ns : undefined\n const dbName = String(ns?.db ?? \"\")\n if (!dbName) return\n\n const collName = String(ns && \"coll\" in ns ? ns.coll : \"\")\n if (!collName) return\n if (shouldSkipCollection(collName)) return\n if (INTERNAL_IGNORED_COLLECTION_NAMES.has(collName)) return\n\n const modelName = resolveModelNameFromCollection(collName)\n if (!modelName) return\n if (INTERNAL_IGNORED_MODEL_NAMES.has(modelName)) return\n\n const op = String(change.operationType ?? \"\")\n if (!op) return\n const normalizedOp = normalizeOpForTaskName(op)\n\n let doc = \"fullDocument\" in change ? change.fullDocument : undefined\n if (!doc && normalizedOp === \"delete\") {\n doc = \"documentKey\" in change ? change.documentKey : undefined\n }\n if (!doc) return\n\n const updateDescription = \"updateDescription\" in change ? change.updateDescription : undefined\n\n try {\n await dispatchWorkerQueue({\n dbName,\n modelName,\n op: normalizedOp,\n doc,\n updateDescription,\n })\n\n resumeAfter = change?._id ?? resumeAfter\n } catch (err) {\n console.warn(\"queue listener failed to dispatch change\", err)\n try {\n await streamRef?.close()\n } catch {\n // ignore\n }\n }\n }).catch((err) => {\n console.warn(\"queue listener change handler failed\", err)\n })\n })\n\n stream.on(\"error\", (err) => {\n if (stopped) return\n if (resumeAfter && isChangeStreamHistoryLost(err)) {\n resumeAfter = null\n }\n try {\n void Promise.resolve(stream?.close()).catch(() => {})\n } catch {\n // ignore\n }\n })\n\n return { stoppedPromise }\n }\n\n const run = async (): Promise<void> => {\n let retryCounter = 0\n\n while (!stopped) {\n try {\n setStatus({ state: \"connecting\", attempt: retryCounter + 1 })\n\n const { stoppedPromise } = await startStream()\n retryCounter = 0\n setStatus({ state: \"ready\" })\n resolveReady()\n\n const end = await stoppedPromise\n if (stopped) return\n\n retryCounter += 1\n if (maxRetries !== \"infinite\" && retryCounter > maxRetries) {\n const err = end.reason === \"error\" ? end.error : new Error(\"queue listener closed\")\n setStatus({ state: \"failed\", attempt: retryCounter, error: err })\n if (fatalOnMaxRetries) {\n try {\n options.onFatal?.(err)\n } catch (fatalErr) {\n console.warn(\"queue listener onFatal failed\", fatalErr)\n }\n }\n rejectReady(err)\n abortController.abort()\n await closeResources()\n return\n }\n\n const delayMs = getRetryDelayMs(retryCounter, retryDelays)\n const err = end.reason === \"error\" ? end.error : new Error(\"queue listener closed\")\n console.warn(\"queue listener not ready, retrying in\", delayMs, err)\n setStatus({ state: \"error\", attempt: retryCounter, error: err, nextRetryInMs: delayMs })\n await closeResources()\n await sleep(delayMs, abortController.signal)\n } catch (err) {\n if (stopped) return\n\n retryCounter += 1\n if (maxRetries !== \"infinite\" && retryCounter > maxRetries) {\n setStatus({ state: \"failed\", attempt: retryCounter, error: err })\n if (fatalOnMaxRetries) {\n try {\n options.onFatal?.(err)\n } catch (fatalErr) {\n console.warn(\"queue listener onFatal failed\", fatalErr)\n }\n }\n rejectReady(err)\n abortController.abort()\n await closeResources()\n return\n }\n\n const delayMs = getRetryDelayMs(retryCounter, retryDelays)\n console.warn(\"queue listener not ready, retrying in\", delayMs, err)\n setStatus({ state: \"error\", attempt: retryCounter, error: err, nextRetryInMs: delayMs })\n await closeResources()\n await sleep(delayMs, abortController.signal)\n }\n }\n }\n\n const handle: QueueListenerHandle = {\n ready,\n close,\n getStatus: () => status,\n }\n\n void run().catch(async (err) => {\n if (stopped) return\n setStatus({ state: \"failed\", attempt: 0, error: err })\n rejectReady(err)\n await closeResources()\n })\n\n return handle\n}\n","export type DbEventOp = \"insert\" | \"update\" | \"delete\"\n\nexport type DbEventTaskName<TOp extends DbEventOp = DbEventOp, TModelName extends string = string> =\n `on-${TOp}-${TModelName}`\n\nexport type DbEventTaskPayload<TDoc = unknown, TUpdateDescription = unknown> = {\n doc: TDoc\n updateDescription?: TUpdateDescription\n}\n\nexport const dbEventTaskName = <TOp extends DbEventOp, TModelName extends string>(\n op: TOp,\n modelName: TModelName,\n): DbEventTaskName<TOp, TModelName> => `on-${op}-${modelName}`\n"],"names":["queue","close","err"],"mappings":";;;AAOA,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAE3B,MAAM,cAAoD,uBAAO,OAAO,IAAI;AAE5E,IAAI,gBAA8B;AAClC,IAAI,iBAAgC;AACpC,IAAI,aAAa;AAEjB,MAAM,cAAc,MAAc;AAChC,QAAM,WAAW,QAAQ,IAAI,WAAW,KAAA;AACxC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,SAAO;AACT;AAEA,MAAM,eAAe,MAAc,QAAQ,IAAI,eAAe,UAAU;AAExE,MAAM,iBAAiB,MAAc;AACnC,QAAM,MAAM,QAAQ,IAAI,sBAAsB,KAAA;AAC9C,QAAM,QAAQ,MAAM,OAAO,GAAG,IAAI;AAClC,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,EAAG,QAAO;AAClD,SAAO,KAAK,MAAM,KAAK;AACzB;AAEA,MAAM,gBAAgB,OAAO,EAAE,KAAK,cAAY;AAEhD,MAAM,cAAc,MAAa;AAC/B,MAAI,cAAe,QAAO;AAC1B,kBAAgB,IAAI,MAAM,gBAAgB;AAAA,IACxC,YAAY,cAAA;AAAA,IACZ,mBAAmB;AAAA,MACjB,kBAAkB;AAAA,MAClB,cAAc;AAAA,IAAA;AAAA,EAChB,CACD;AAED,SAAO;AACT;AAOO,SAAS,aAAa,MAAc,SAAqC;AAC9E,cAAY,IAAI,IAAI;AACtB;AAEO,MAAM,WAAW,MAA4C;AAQ7D,SAAS,IAAI,UAAkB,SAAkB,SAAoC;AAC1F,SAAO,YAAA,EAAc,IAAI,UAAU,SAAS,OAAO;AACrD;AAEO,MAAM,SAAS,OAAO,UAAwC,MAAM,cAAc,OAAO,KAAK,KAAM;AAEpG,MAAM,UAAU,UAAU,SAC/B,cAAc,QAAQ,GAAG,IAAI;AAExB,MAAM,cAAc,MAAa,YAAA;AAEjC,MAAM,QAAQ,YAA4B;AAC/C,MAAI,mBAAmB,YAAA;AACvB,eAAa;AAEb,QAAM,QAAQ,YAAA;AACd,QAAM,cAAc,eAAA;AAEpB,UAAQ,IAAI,sBAAsB,EAAE,OAAO,MAAM,MAAM,UAAU,eAAe,aAAa;AAE7F,QAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,YAAQ,IAAI,gBAAgB,IAAI,OAAO,EAAE;AAAA,EAC3C,CAAC;AAED,MAAI,CAAC,gBAAgB;AACnB,qBAAiB,IAAI;AAAA,MACnB,MAAM;AAAA,MACN,OAAO,QAAQ;AACb,cAAM,WAAW,IAAI;AAErB,cAAM,UAAU,YAAY,QAAQ;AACpC,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,2BAA2B,QAAQ,GAAG;AAAA,QACxD;AAEA,eAAO,MAAM,QAAQ,IAAI,MAAM,GAAG;AAAA,MACpC;AAAA,MACA;AAAA,QACE;AAAA,QACA,YAAY,cAAA;AAAA,MAAc;AAAA,IAC5B;AAAA,EAEJ;AAEA,iBAAe,GAAG,SAAS,CAAC,QAAQ;AAClC,YAAQ,IAAI,iBAAiB,IAAI,OAAO,EAAE;AAAA,EAC5C,CAAC;AAED,iBAAe,GAAG,WAAW,CAAC,UAAU;AACtC,YAAQ,IAAI,OAAO,KAAK,UAAU;AAAA,EACpC,CAAC;AAED,iBAAe,GAAG,UAAU,CAAC,KAAK,QAAQ;AACxC,YAAQ,IAAI,OAAO,KAAK,MAAM,SAAS,WAAW,GAAG;AAAA,EACvD,CAAC;AAED,QAAM,eAAe,eAAA;AAErB,SAAO;AACT;AAEO,MAAM,QAAQ,YAA2B;AAC9C,MAAI,CAAC,iBAAiB,CAAC,eAAgB;AACvC,MAAI;AACF,UAAM,gBAAgB,MAAA;AACtB,UAAM,eAAe,MAAA;AAAA,EACvB,UAAA;AACE,qBAAiB;AACjB,oBAAgB;AAChB,iBAAa;AAAA,EACf;AACF;AAEO,MAAM,SAAS,MAAc,YAAA;AAapC,eAAsB,aAAa,UAAkB,SAAkB,SAA4C;AACjH,QAAM,EAAE,OAAO,QAAQ,GAAG,SAAS;AACnC,SAAO,IAAI,UAAU,SAAS;AAAA,IAC5B,GAAG;AAAA,IACH,OAAO,SAAS,YAAY,QAAQ;AAAA,IACpC;AAAA,EAAA,CACD;AACH;AAEA,MAAM,WAAW;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;ACpIA,MAAM,yBAAyB;AAC/B,MAAM,yBAAyB;AAC/B,MAAM,uBAAuB;AAE7B,MAAM,QAAQ,OAAO,IAAY,WAAwC,IAAI,QAAQ,CAAC,YAAY;AAChG,MAAI,QAAQ,SAAS;AACnB,YAAA;AACA;AAAA,EACF;AAEA,MAAI,UAAgD;AAEpD,QAAM,UAAU,MAAM;AACpB,QAAI,sBAAsB,OAAO;AACjC,YAAQ,oBAAoB,SAAS,OAAO;AAAA,EAC9C;AAEA,QAAM,UAAU,MAAM;AACpB,YAAA;AACA,YAAA;AAAA,EACF;AAEA,YAAU,WAAW,MAAM;AACzB,YAAA;AACA,YAAA;AAAA,EACF,GAAG,EAAE;AACL,UAAQ,QAAA;AAER,UAAQ,iBAAiB,SAAS,OAAO;AAC3C,CAAC;AAED,MAAM,cAAc,MAAc;AAChC,QAAM,WACJ,QAAQ,IAAI,eACT,QAAQ,IAAI,aACZ,QAAQ,IAAI,eACZ,QAAQ,IAAI;AAEjB,MAAI,YAAY,SAAS,KAAA,EAAQ,QAAO,SAAS,KAAA;AAEjD,QAAM,OAAO,QAAQ,IAAI,SAAS,KAAA;AAClC,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,qFAAqF;AAEhH,QAAM,OAAO,QAAQ,IAAI,SAAS,UAAU;AAC5C,SAAO,aAAa,IAAI,IAAI,IAAI;AAClC;AAEA,MAAM,cAAc,CAAC,UAA0B,MAAM,QAAQ,uBAAuB,MAAM;AAS1F,MAAM,WAAW,CAAC,UAChB,OAAO,UAAU,YAAY,UAAU;AAEzC,MAAM,gBAAgB,CAAC,QAA0B;AAC/C,MAAI,CAAC,SAAS,GAAG,EAAG,QAAO;AAC3B,SAAO,IAAI;AACb;AAEA,MAAM,iCAAiC,CAAC,aAAoC;AAC1E,QAAM,SAAS,SAAS;AAExB,aAAW,aAAa,OAAO,KAAK,MAAM,GAAG;AAC3C,UAAM,QAAQ,OAAO,SAAS;AAC9B,UAAM,iBACJ,MAAM,YAAY,kBACf,MAAM,YAAY;AAEvB,QAAI,mBAAmB,UAAU;AAC/B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,6BAA6B,CAAC,sBAAwC;AAC1E,MAAI,CAAC,SAAS,iBAAiB,EAAG,QAAO;AACzC,MAAI,SAAS,kBAAkB,aAAa,GAAG;AAC7C,WAAO;AAAA,MACL,GAAG;AAAA,MACH,mBAAmB,OAAO,KAAK,kBAAkB,aAAa;AAAA,IAAA;AAAA,EAElE;AAEA,SAAO;AACT;AAEA,MAAM,yBAAyB,CAAC,OAAwB,OAAO,YAAY,WAAW;AAEtF,MAAM,sBAAsB,OAAO;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMqB;AACnB,QAAM,QAAQA,SAAM,SAAA;AACpB,QAAM,SAAS,uBAAuB,EAAE;AACxC,QAAM,cAAc,MAAM,MAAM,IAAI,SAAS;AAE7C,MAAI,CAAC,MAAM,WAAW,EAAG;AAEzB,QAAM,8BAA8B,2BAA2B,iBAAiB;AAEhF,QAAMA,SAAM,IAAI,aAAa,EAAE,KAAK,mBAAmB,+BAA+B;AAAA,IACpF,OAAO,GAAG,MAAM,IAAI,MAAM,IAAI,cAAc,GAAG,KAAK,SAAS;AAAA,IAC7D,kBAAkB;AAAA,IAClB,cAAc;AAAA,EAAA,CACf;AACH;AAEA,MAAM,uBAAuB,CAAC,aAC5B,SAAS,SAAS,QAAQ,KAAK,SAAS,SAAS,SAAS;AAE5D,MAAM,+BAA+B,oBAAI,IAAI,CAAC,eAAe,cAAc,CAAC;AAE5E,MAAM,oCAAoC,oBAAI,IAAI,CAAC,cAAc,aAAa,CAAC;AAE/E,MAAM,uBAAuB,CAAC,UAAoF;AAChH,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,SAAS,sBAAsB,CAAC;AAC5E,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,SAAS,sBAAsB,CAAC;AAC/E,QAAM,QAAQ,KAAK,IAAI,OAAO,QAAQ;AACtC,QAAM,SAAS,KAAK,IAAI,GAAG,OAAO,SAAS,OAAO,MAAM,IAAK,OAAO,UAAU,uBAAwB,oBAAoB;AAC1H,SAAO,EAAE,OAAO,OAAO,OAAA;AACzB;AAEA,MAAM,kBAAkB,CAAC,SAAiB,WACxC,KAAK,IAAI,OAAO,OAAO,KAAK,MAAM,OAAO,QAAQ,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;AAErG,MAAM,sBAAsB,CAAC,UAAoD;AAC/E,MAAI,UAAU,WAAY,QAAO;AACjC,QAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,SAAO,OAAO,SAAS,MAAM,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;AACzD;AAEO,MAAM,wBAAwB,OAAO,UAAgC,OAAqC;AAC/G,QAAM,aAAa,oBAAoB,QAAQ,cAAc,UAAU;AACvE,QAAM,oBAAoB,QAAQ,qBAAqB;AACvD,QAAM,cAAc,qBAAqB,QAAQ,WAAW;AAE5D,QAAM,UAAU,QAAQ,IAAI,UAAU,KAAA;AACtC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,wEAAwE;AAAA,EAC1F;AAEA,QAAM,WAAW,YAAA;AACjB,QAAM,qBAAyC;AAAA,IAC7C,QAAQ;AAAA,IACR,0BAA0B;AAAA,IAC1B,kBAAkB;AAAA,IAClB,GAAG,QAAQ;AAAA,EAAA;AAGb,MAAI,UAAU;AACd,QAAM,kBAAkB,IAAI,gBAAA;AAC5B,MAAI,SAA6B;AACjC,MAAI,SAAwC;AAC5C,MAAI,cAA+B;AACnC,MAAI,aAAa,QAAQ,QAAA;AAEzB,MAAI,SAA8B,EAAE,OAAO,cAAc,SAAS,EAAA;AAClE,QAAM,YAAY,CAAC,SAAoC;AACrD,aAAS;AACT,QAAI;AACF,cAAQ,gBAAgB,IAAI;AAAA,IAC9B,SAAS,KAAK;AACZ,cAAQ,KAAK,uCAAuC,GAAG;AAAA,IACzD;AAAA,EACF;AAEA,MAAI,eAAe;AACnB,MAAI,eAAoC;AACxC,MAAI,cAA+C;AACnD,QAAM,QAAQ,IAAI,QAAc,CAAC,SAAS,WAAW;AACnD,mBAAe;AACf,kBAAc;AAAA,EAChB,CAAC;AAED,QAAM,eAAe,MAAY;AAC/B,QAAI,aAAc;AAClB,mBAAe;AACf,mBAAA;AAAA,EACF;AAEA,QAAM,cAAc,CAAC,QAAuB;AAC1C,QAAI,aAAc;AAClB,mBAAe;AACf,kBAAc,GAAG;AAAA,EACnB;AAEA,QAAM,4BAA4B,CAAC,QAA0B;AAC3D,UAAM,WAAW;AACjB,UAAM,OAAO,OAAO,SAAS,SAAS,WAAW,SAAS,OAAO;AACjE,UAAM,WAAW,OAAO,SAAS,aAAa,WAAW,SAAS,WAAW;AAC7E,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,OAAO,EAAE;AAErE,WACE,SAAS,OACN,aAAa,6BACb,QAAQ,SAAS,yBAAyB,KAC1C,QAAQ,SAAS,cAAc,KAC/B,QAAQ,SAAS,cAAc,KAC/B,QAAQ,SAAS,0CAA0C,KAC3D,QAAQ,SAAS,eAAe;AAAA,EAEvC;AAEA,QAAMC,SAAQ,YAA2B;AACvC,cAAU;AACV,oBAAgB,MAAA;AAChB,QAAI;AACF,cAAQ,mBAAA;AACR,YAAM,QAAQ,MAAA;AAAA,IAChB,QAAQ;AAAA,IAER;AACA,aAAS;AAET,QAAI;AACF,YAAM,QAAQ,MAAA;AAAA,IAChB,QAAQ;AAAA,IAER;AACA,aAAS;AAET,QAAI,CAAC,cAAc;AACjB,kBAAY,IAAI,MAAM,oCAAoC,CAAC;AAAA,IAC7D;AAEA,cAAU,EAAE,OAAO,UAAU;AAAA,EAC/B;AAEA,QAAM,iBAAiB,YAA2B;AAChD,QAAI;AACF,cAAQ,mBAAA;AACR,YAAM,QAAQ,MAAA;AAAA,IAChB,QAAQ;AAAA,IAER;AACA,aAAS;AAET,QAAI;AACF,YAAM,QAAQ,MAAA;AAAA,IAChB,QAAQ;AAAA,IAER;AACA,aAAS;AAAA,EACX;AAEA,QAAM,cAAc,YAAkG;AACpH,QAAI,QAAS,QAAO,EAAE,gBAAgB,QAAQ,QAAQ,EAAE,QAAQ,QAAA,CAAS,EAAA;AAEzE,QAAI,QAAQ;AACV,UAAI;AACF,eAAO,mBAAA;AACP,cAAM,OAAO,MAAA;AAAA,MACf,QAAQ;AAAA,MAER;AACA,eAAS;AAAA,IACX;AAEA,QAAI,QAAQ;AACV,UAAI;AACF,cAAM,OAAO,MAAA;AAAA,MACf,QAAQ;AAAA,MAER;AACA,eAAS;AAAA,IACX;AAEA,aAAS,IAAI,YAAY,UAAU,kBAAkB;AAErD,UAAM,OAAO,QAAA;AAEb,UAAM,UAAU,EAAE,SAAS,EAAE,QAAQ,IAAI,YAAY,OAAO,CAAC,YAAU;AAEvE,UAAM,WAAuB;AAAA,MAC3B,EAAE,QAAQ,QAAA;AAAA,MACV;AAAA,QACE,QAAQ;AAAA,UACN,eAAe,EAAE,KAAK,CAAC,UAAU,UAAU,WAAW,QAAQ,EAAA;AAAA,QAAE;AAAA,MAClE;AAAA,MAEF,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,MAAM,KAAK,iCAAiC,IAAE,EAAE;AAAA,IAAE;AAGnF,aAAS,OAAO,MAAM,UAAU;AAAA,MAC9B,cAAc;AAAA,MACd,GAAI,cAAc,EAAE,gBAAgB,CAAA;AAAA,IAAC,CACtC;AAED,UAAM,iBAAiB,IAAI,QAAwD,CAAC,YAAY;AAC9F,UAAI,UAAU;AACd,YAAM,UAAU,MAAM,OAAO,EAAE,QAAQ,SAAS;AAEhD,YAAM,SAAS,CAAC,WAA2D;AACzE,YAAI,QAAS;AACb,kBAAU;AACV,wBAAgB,OAAO,oBAAoB,SAAS,OAAO;AAC3D,gBAAQ,MAAM;AAAA,MAChB;AAEA,sBAAgB,OAAO,iBAAiB,SAAS,OAAO;AACxD,cAAQ,KAAK,SAAS,MAAM,OAAO,EAAE,QAAQ,QAAA,CAAS,CAAC;AACvD,cAAQ,KAAK,SAAS,CAAC,QAAQ,OAAO,EAAE,QAAQ,SAAS,OAAO,IAAA,CAAK,CAAC;AAAA,IACxE,CAAC;AAED,WAAO,GAAG,UAAU,CAAC,WAA2C;AAC9D,YAAM,YAAY;AAClB,mBAAa,WAAW,KAAK,YAAY;AACvC,cAAM,KAAK,QAAQ,SAAS,OAAO,KAAK;AACxC,cAAM,SAAS,OAAO,IAAI,MAAM,EAAE;AAClC,YAAI,CAAC,OAAQ;AAEb,cAAM,WAAW,OAAO,MAAM,UAAU,KAAK,GAAG,OAAO,EAAE;AACzD,YAAI,CAAC,SAAU;AACf,YAAI,qBAAqB,QAAQ,EAAG;AACpC,YAAI,kCAAkC,IAAI,QAAQ,EAAG;AAErD,cAAM,YAAY,+BAA+B,QAAQ;AACzD,YAAI,CAAC,UAAW;AAChB,YAAI,6BAA6B,IAAI,SAAS,EAAG;AAEjD,cAAM,KAAK,OAAO,OAAO,iBAAiB,EAAE;AAC5C,YAAI,CAAC,GAAI;AACT,cAAM,eAAe,uBAAuB,EAAE;AAE9C,YAAI,MAAM,kBAAkB,SAAS,OAAO,eAAe;AAC3D,YAAI,CAAC,OAAO,iBAAiB,UAAU;AACrC,gBAAM,iBAAiB,SAAS,OAAO,cAAc;AAAA,QACvD;AACA,YAAI,CAAC,IAAK;AAEV,cAAM,oBAAoB,uBAAuB,SAAS,OAAO,oBAAoB;AAErF,YAAI;AACF,gBAAM,oBAAoB;AAAA,YACxB;AAAA,YACA;AAAA,YACA,IAAI;AAAA,YACJ;AAAA,YACA;AAAA,UAAA,CACD;AAED,wBAAc,QAAQ,OAAO;AAAA,QAC/B,SAAS,KAAK;AACZ,kBAAQ,KAAK,4CAA4C,GAAG;AAC5D,cAAI;AACF,kBAAM,WAAW,MAAA;AAAA,UACnB,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,gBAAQ,KAAK,wCAAwC,GAAG;AAAA,MAC1D,CAAC;AAAA,IACH,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,UAAI,QAAS;AACb,UAAI,eAAe,0BAA0B,GAAG,GAAG;AACjD,sBAAc;AAAA,MAChB;AACA,UAAI;AACF,aAAK,QAAQ,QAAQ,QAAQ,OAAO,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACtD,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAED,WAAO,EAAE,eAAA;AAAA,EACX;AAEA,QAAM,MAAM,YAA2B;AACrC,QAAI,eAAe;AAEnB,WAAO,CAAC,SAAS;AACf,UAAI;AACF,kBAAU,EAAE,OAAO,cAAc,SAAS,eAAe,GAAG;AAE5D,cAAM,EAAE,mBAAmB,MAAM,YAAA;AACjC,uBAAe;AACf,kBAAU,EAAE,OAAO,SAAS;AAC5B,qBAAA;AAEA,cAAM,MAAM,MAAM;AAClB,YAAI,QAAS;AAEb,wBAAgB;AAChB,YAAI,eAAe,cAAc,eAAe,YAAY;AAC1D,gBAAMC,OAAM,IAAI,WAAW,UAAU,IAAI,QAAQ,IAAI,MAAM,uBAAuB;AAClF,oBAAU,EAAE,OAAO,UAAU,SAAS,cAAc,OAAOA,MAAK;AAChE,cAAI,mBAAmB;AACrB,gBAAI;AACF,sBAAQ,UAAUA,IAAG;AAAA,YACvB,SAAS,UAAU;AACjB,sBAAQ,KAAK,iCAAiC,QAAQ;AAAA,YACxD;AAAA,UACF;AACA,sBAAYA,IAAG;AACf,0BAAgB,MAAA;AAChB,gBAAM,eAAA;AACN;AAAA,QACF;AAEA,cAAM,UAAU,gBAAgB,cAAc,WAAW;AACzD,cAAM,MAAM,IAAI,WAAW,UAAU,IAAI,QAAQ,IAAI,MAAM,uBAAuB;AAClF,gBAAQ,KAAK,yCAAyC,SAAS,GAAG;AAClE,kBAAU,EAAE,OAAO,SAAS,SAAS,cAAc,OAAO,KAAK,eAAe,SAAS;AACvF,cAAM,eAAA;AACN,cAAM,MAAM,SAAS,gBAAgB,MAAM;AAAA,MAC7C,SAAS,KAAK;AACZ,YAAI,QAAS;AAEb,wBAAgB;AAChB,YAAI,eAAe,cAAc,eAAe,YAAY;AAC1D,oBAAU,EAAE,OAAO,UAAU,SAAS,cAAc,OAAO,KAAK;AAChE,cAAI,mBAAmB;AACrB,gBAAI;AACF,sBAAQ,UAAU,GAAG;AAAA,YACvB,SAAS,UAAU;AACjB,sBAAQ,KAAK,iCAAiC,QAAQ;AAAA,YACxD;AAAA,UACF;AACA,sBAAY,GAAG;AACf,0BAAgB,MAAA;AAChB,gBAAM,eAAA;AACN;AAAA,QACF;AAEA,cAAM,UAAU,gBAAgB,cAAc,WAAW;AACzD,gBAAQ,KAAK,yCAAyC,SAAS,GAAG;AAClE,kBAAU,EAAE,OAAO,SAAS,SAAS,cAAc,OAAO,KAAK,eAAe,SAAS;AACvF,cAAM,eAAA;AACN,cAAM,MAAM,SAAS,gBAAgB,MAAM;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAA8B;AAAA,IAClC;AAAA,IACA,OAAAD;AAAA,IACA,WAAW,MAAM;AAAA,EAAA;AAGnB,OAAK,IAAA,EAAM,MAAM,OAAO,QAAQ;AAC9B,QAAI,QAAS;AACb,cAAU,EAAE,OAAO,UAAU,SAAS,GAAG,OAAO,KAAK;AACrD,gBAAY,GAAG;AACf,UAAM,eAAA;AAAA,EACR,CAAC;AAED,SAAO;AACT;AChfO,MAAM,kBAAkB,CAC7B,IACA,cACqC,MAAM,EAAE,IAAI,SAAS;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/queue.ts","../src/queueListener.ts","../src/taskNames.ts"],"sourcesContent":["import { Queue, Worker, type Job, type JobsOptions as JobOptions } from \"bullmq\"\n\nimport type { WorkerTasksMap } from \"./tasksMap\"\n\n\nexport type TaskHandler<TPayload = unknown> = (payload: TPayload, job: Job) => unknown | Promise<unknown>\n\nconst DEFAULT_QUEUE_NAME = \"rb-queue-default\"\nconst DEFAULT_CONCURRENCY = 2\nconst DEFAULT_RENTENTION = 128\n\nconst tasksByName: Record<string, TaskHandler<unknown>> = Object.create(null)\n\nlet queueInstance: Queue | null = null\nlet workerInstance: Worker | null = null\nlet hasStarted = false\n\nconst getRedisUrl = (): string => {\n const redisUrl = process.env.REDIS_URL?.trim()\n if (!redisUrl) {\n throw new Error(\"Missing REDIS_URL (required for @rpcbase/worker queue)\")\n }\n return redisUrl\n}\n\nconst getQueueName = (): string => process.env.RB_QUEUE_NAME?.trim() || DEFAULT_QUEUE_NAME\n\nconst getConcurrency = (): number => {\n const raw = process.env.RB_QUEUE_CONCURRENCY?.trim()\n const value = raw ? Number(raw) : DEFAULT_CONCURRENCY\n if (!Number.isFinite(value) || value <= 0) return DEFAULT_CONCURRENCY\n return Math.floor(value)\n}\n\nconst getConnection = () => ({ url: getRedisUrl() })\n\nconst ensureQueue = (): Queue => {\n if (queueInstance) return queueInstance\n queueInstance = new Queue(getQueueName(), {\n connection: getConnection(),\n defaultJobOptions: {\n removeOnComplete: DEFAULT_RENTENTION,\n removeOnFail: DEFAULT_RENTENTION\n },\n })\n\n return queueInstance\n}\n\nexport function registerTask<TName extends keyof WorkerTasksMap & string>(\n name: TName,\n handler: TaskHandler<WorkerTasksMap[TName]>,\n): void\nexport function registerTask(name: string, handler: TaskHandler<unknown>): void\nexport function registerTask(name: string, handler: TaskHandler<unknown>): void {\n tasksByName[name] = handler\n}\n\nexport const getTasks = (): Record<string, TaskHandler<unknown>> => tasksByName\n\nexport function add<TName extends keyof WorkerTasksMap & string>(\n taskName: TName,\n payload: WorkerTasksMap[TName],\n options?: JobOptions,\n): Promise<Job>\nexport function add(taskName: string, payload: unknown, options?: JobOptions): Promise<Job>\nexport function add(taskName: string, payload: unknown, options?: JobOptions): Promise<Job> {\n return ensureQueue().add(taskName, payload, options)\n}\n\nexport const getJob = async (jobId: string): Promise<Job | null> => (await ensureQueue().getJob(jobId)) ?? null\n\nexport const getJobs = async (...args: Parameters<Queue[\"getJobs\"]>): Promise<Job[]> =>\n ensureQueue().getJobs(...args) as unknown as Job[]\n\nexport const getInstance = (): Queue => ensureQueue()\n\nexport const start = async (): Promise<Queue> => {\n if (hasStarted) return ensureQueue()\n hasStarted = true\n\n const queue = ensureQueue()\n const concurrency = getConcurrency()\n\n console.log(\"start worker queue\", { queue: queue.name, redisUrl: getRedisUrl(), concurrency })\n\n queue.on(\"error\", (err) => {\n console.log(`queue error: ${err.message}`)\n })\n\n if (!workerInstance) {\n workerInstance = new Worker(\n queue.name,\n async (job) => {\n const taskName = job.name\n\n const handler = tasksByName[taskName]\n if (!handler) {\n throw new Error(`No task registered for '${taskName}'`)\n }\n\n return await handler(job.data, job)\n },\n {\n concurrency,\n connection: getConnection(),\n },\n )\n }\n\n workerInstance.on(\"error\", (err) => {\n console.log(`worker error: ${err.message}`)\n })\n\n workerInstance.on(\"stalled\", (jobId) => {\n console.log(`job ${jobId} stalled`)\n })\n\n workerInstance.on(\"failed\", (job, err) => {\n console.log(`job ${job?.id ?? \"unknown\"} failed`, err)\n })\n\n await workerInstance.waitUntilReady()\n\n return queue\n}\n\nexport const close = async (): Promise<void> => {\n if (!queueInstance && !workerInstance) return\n try {\n await workerInstance?.close()\n await queueInstance?.close()\n } finally {\n workerInstance = null\n queueInstance = null\n hasStarted = false\n }\n}\n\nexport const getUrl = (): string => getRedisUrl()\n\nexport type ScheduleTaskOptions = Omit<JobOptions, \"repeat\"> & {\n jobId?: string\n repeat: NonNullable<JobOptions[\"repeat\"]>\n}\n\nexport function scheduleTask<TName extends keyof WorkerTasksMap & string>(\n taskName: TName,\n payload: WorkerTasksMap[TName],\n options: ScheduleTaskOptions,\n): Promise<Job>\nexport function scheduleTask(taskName: string, payload: unknown, options: ScheduleTaskOptions): Promise<Job>\nexport async function scheduleTask(taskName: string, payload: unknown, options: ScheduleTaskOptions): Promise<Job> {\n const { jobId, repeat, ...rest } = options\n return add(taskName, payload, {\n ...rest,\n jobId: jobId ?? `schedule|${taskName}`,\n repeat,\n })\n}\n\nconst queueApi = {\n start,\n close,\n registerTask,\n getTasks,\n add,\n scheduleTask,\n getJob,\n getJobs,\n getInstance,\n getUrl,\n}\n\nexport default queueApi\n","import {\n MongoClient,\n type ChangeStream,\n type ChangeStreamDocument,\n type Document,\n type MongoClientOptions,\n} from \"mongodb\"\nimport mongoose from \"mongoose\"\n\nimport queue from \"./queue\"\n\n\nexport type QueueListenerRetryDelays = {\n minMs?: number\n maxMs?: number\n factor?: number\n}\n\nexport type QueueListenerStatus =\n | { state: \"connecting\"; attempt: number }\n | { state: \"ready\" }\n | { state: \"error\"; attempt: number; error: unknown; nextRetryInMs: number }\n | { state: \"failed\"; attempt: number; error: unknown }\n | { state: \"closed\" }\n\nexport type QueueListenerHandle = {\n ready: Promise<void>\n close: () => Promise<void>\n getStatus: () => QueueListenerStatus\n}\n\nexport type QueueListenerOptions = {\n maxRetries?: number | \"infinite\"\n fatalOnMaxRetries?: boolean\n onStateChange?: (status: QueueListenerStatus) => void\n onFatal?: (err: unknown) => void\n retryDelays?: QueueListenerRetryDelays\n mongoClientOptions?: MongoClientOptions\n}\n\nconst RETRY_MAXIMUM_DELAY_MS = 3000\nconst RETRY_MINIMUM_DELAY_MS = 50\nconst RETRY_DEFAULT_FACTOR = 2\n\nconst sleep = async (ms: number, signal?: AbortSignal): Promise<void> => new Promise((resolve) => {\n if (signal?.aborted) {\n resolve()\n return\n }\n\n let timeout: ReturnType<typeof setTimeout> | null = null\n\n const cleanup = () => {\n if (timeout) clearTimeout(timeout)\n signal?.removeEventListener(\"abort\", onAbort)\n }\n\n const onAbort = () => {\n cleanup()\n resolve()\n }\n\n timeout = setTimeout(() => {\n cleanup()\n resolve()\n }, ms)\n timeout.unref?.()\n\n signal?.addEventListener(\"abort\", onAbort)\n})\n\nconst getMongoUrl = (): string => {\n const explicit =\n process.env.MONGODB_URL\n ?? process.env.MONGO_URL\n ?? process.env.MONGODB_URI\n ?? process.env.DB_URL\n\n if (explicit && explicit.trim()) return explicit.trim()\n\n const port = process.env.DB_PORT?.trim()\n if (!port) throw new Error(\"Missing Mongo connection details (MONGODB_URL/MONGO_URL/MONGODB_URI/DB_URL/DB_PORT)\")\n\n const host = process.env.DB_HOST?.trim() || \"localhost\"\n return `mongodb://${host}:${port}`\n}\n\nconst escapeRegex = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n\ntype ModelWithCollection = {\n collection?: {\n collectionName?: string\n name?: string\n }\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null\n\nconst getDocumentId = (doc: unknown): unknown => {\n if (!isRecord(doc)) return undefined\n return doc._id\n}\n\nconst resolveModelNameFromCollection = (collName: string): string | null => {\n const models = mongoose.models\n\n for (const modelName of Object.keys(models)) {\n const model = models[modelName] as ModelWithCollection\n const collectionName =\n model.collection?.collectionName\n ?? model.collection?.name\n\n if (collectionName === collName) {\n return modelName\n }\n }\n\n return null\n}\n\nconst normalizeUpdateDescription = (updateDescription: unknown): unknown => {\n if (!isRecord(updateDescription)) return updateDescription\n if (isRecord(updateDescription.updatedFields)) {\n return {\n ...updateDescription,\n updatedFieldsKeys: Object.keys(updateDescription.updatedFields),\n }\n }\n\n return updateDescription\n}\n\nconst normalizeOpForTaskName = (op: string): string => (op === \"replace\" ? \"update\" : op)\n\nconst dispatchWorkerQueue = async ({\n dbName,\n modelName,\n op,\n doc,\n updateDescription,\n}: {\n dbName: string\n modelName: string\n op: string\n doc: unknown\n updateDescription?: unknown\n}): Promise<void> => {\n const tasks = queue.getTasks()\n const taskOp = normalizeOpForTaskName(op)\n const handlerName = `on-${taskOp}-${modelName}`\n\n if (!tasks[handlerName]) return\n\n const normalizedUpdateDescription = normalizeUpdateDescription(updateDescription)\n\n await queue.add(handlerName, { doc, updateDescription: normalizedUpdateDescription }, {\n jobId: `${dbName}|${taskOp}-${getDocumentId(doc) ?? \"unknown\"}`,\n removeOnComplete: true,\n removeOnFail: true,\n })\n}\n\nconst shouldSkipCollection = (collName: string): boolean =>\n collName.endsWith(\".files\") || collName.endsWith(\".chunks\")\n\nconst INTERNAL_IGNORED_MODEL_NAMES = new Set([\"RBRtsChange\", \"RBRtsCounter\"])\n\nconst INTERNAL_IGNORED_COLLECTION_NAMES = new Set([\"rtschanges\", \"rtscounters\"])\n\nconst normalizeRetryDelays = (input: QueueListenerRetryDelays | undefined): Required<QueueListenerRetryDelays> => {\n const minMs = Math.max(0, Math.floor(input?.minMs ?? RETRY_MINIMUM_DELAY_MS))\n const rawMaxMs = Math.max(0, Math.floor(input?.maxMs ?? RETRY_MAXIMUM_DELAY_MS))\n const maxMs = Math.max(minMs, rawMaxMs)\n const factor = Math.max(1, Number.isFinite(input?.factor) ? (input?.factor ?? RETRY_DEFAULT_FACTOR) : RETRY_DEFAULT_FACTOR)\n return { minMs, maxMs, factor }\n}\n\nconst getRetryDelayMs = (attempt: number, delays: Required<QueueListenerRetryDelays>): number =>\n Math.min(delays.maxMs, Math.round(delays.minMs * Math.pow(delays.factor, Math.max(0, attempt - 1))))\n\nconst normalizeMaxRetries = (value: number | \"infinite\"): number | \"infinite\" => {\n if (value === \"infinite\") return value\n const parsed = Math.floor(value)\n return Number.isFinite(parsed) ? Math.max(0, parsed) : 0\n}\n\nexport const registerQueueListener = async (options: QueueListenerOptions = {}): Promise<QueueListenerHandle> => {\n const maxRetries = normalizeMaxRetries(options.maxRetries ?? \"infinite\")\n const fatalOnMaxRetries = options.fatalOnMaxRetries ?? false\n const retryDelays = normalizeRetryDelays(options.retryDelays)\n\n const appName = process.env.APP_NAME?.trim()\n if (!appName) {\n throw new Error(\"Missing APP_NAME (required to configure the worker DB change listener)\")\n }\n\n const mongoUrl = getMongoUrl()\n const mongoClientOptions: MongoClientOptions = {\n family: 4,\n serverSelectionTimeoutMS: 2000,\n connectTimeoutMS: 2000,\n ...options.mongoClientOptions,\n }\n\n let stopped = false\n const abortController = new AbortController()\n let client: MongoClient | null = null\n let stream: ChangeStream<Document> | null = null\n let resumeAfter: Document | null = null\n let processing = Promise.resolve()\n\n let status: QueueListenerStatus = { state: \"connecting\", attempt: 1 }\n const setStatus = (next: QueueListenerStatus): void => {\n status = next\n try {\n options.onStateChange?.(next)\n } catch (err) {\n console.warn(\"queue listener onStateChange failed\", err)\n }\n }\n\n let readySettled = false\n let readyResolve: (() => void) | null = null\n let readyReject: ((err: unknown) => void) | null = null\n const ready = new Promise<void>((resolve, reject) => {\n readyResolve = resolve\n readyReject = reject\n })\n\n const resolveReady = (): void => {\n if (readySettled) return\n readySettled = true\n readyResolve?.()\n }\n\n const rejectReady = (err: unknown): void => {\n if (readySettled) return\n readySettled = true\n readyReject?.(err)\n }\n\n const isChangeStreamHistoryLost = (err: unknown): boolean => {\n const maybeErr = err as { code?: unknown; codeName?: unknown }\n const code = typeof maybeErr.code === \"number\" ? maybeErr.code : null\n const codeName = typeof maybeErr.codeName === \"string\" ? maybeErr.codeName : \"\"\n const message = err instanceof Error ? err.message : String(err ?? \"\")\n\n return (\n code === 286\n || codeName === \"ChangeStreamHistoryLost\"\n || message.includes(\"ChangeStreamHistoryLost\")\n || message.includes(\"resume token\")\n || message.includes(\"Resume token\")\n || message.includes(\"resume of change stream was not possible\")\n || message.includes(\"cannot resume\")\n )\n }\n\n const close = async (): Promise<void> => {\n stopped = true\n abortController.abort()\n try {\n stream?.removeAllListeners()\n await stream?.close()\n } catch {\n // ignore\n }\n stream = null\n\n try {\n await client?.close()\n } catch {\n // ignore\n }\n client = null\n\n if (!readySettled) {\n rejectReady(new Error(\"queue listener closed before ready\"))\n }\n\n setStatus({ state: \"closed\" })\n }\n\n const closeResources = async (): Promise<void> => {\n try {\n stream?.removeAllListeners()\n await stream?.close()\n } catch {\n // ignore\n }\n stream = null\n\n try {\n await client?.close()\n } catch {\n // ignore\n }\n client = null\n }\n\n const startStream = async (): Promise<{ stoppedPromise: Promise<{ reason: \"close\" | \"error\"; error?: unknown }> }> => {\n if (stopped) return { stoppedPromise: Promise.resolve({ reason: \"close\" }) }\n\n if (stream) {\n try {\n stream.removeAllListeners()\n await stream.close()\n } catch {\n // ignore\n }\n stream = null\n }\n\n if (client) {\n try {\n await client.close()\n } catch {\n // ignore\n }\n client = null\n }\n\n client = new MongoClient(mongoUrl, mongoClientOptions)\n\n await client.connect()\n\n const dbMatch = { \"ns.db\": { $regex: `^${escapeRegex(appName)}-.*-db$` } }\n\n const pipeline: Document[] = [\n { $match: dbMatch },\n {\n $match: {\n operationType: { $in: [\"insert\", \"update\", \"replace\", \"delete\"] },\n }\n },\n { $match: { \"ns.coll\": { $nin: Array.from(INTERNAL_IGNORED_COLLECTION_NAMES) } } },\n ]\n\n stream = client.watch(pipeline, {\n fullDocument: \"updateLookup\",\n ...(resumeAfter ? { resumeAfter } : {}),\n })\n\n const stoppedPromise = new Promise<{ reason: \"close\" | \"error\"; error?: unknown }>((resolve) => {\n let settled = false\n const onAbort = () => settle({ reason: \"close\" })\n\n const settle = (result: { reason: \"close\" | \"error\"; error?: unknown }) => {\n if (settled) return\n settled = true\n abortController.signal.removeEventListener(\"abort\", onAbort)\n resolve(result)\n }\n\n abortController.signal.addEventListener(\"abort\", onAbort)\n stream?.once(\"close\", () => settle({ reason: \"close\" }))\n stream?.once(\"error\", (err) => settle({ reason: \"error\", error: err }))\n })\n\n stream.on(\"change\", (change: ChangeStreamDocument<Document>) => {\n const streamRef = stream\n processing = processing.then(async () => {\n const ns = \"ns\" in change ? change.ns : undefined\n const dbName = String(ns?.db ?? \"\")\n if (!dbName) return\n\n const collName = String(ns && \"coll\" in ns ? ns.coll : \"\")\n if (!collName) return\n if (shouldSkipCollection(collName)) return\n if (INTERNAL_IGNORED_COLLECTION_NAMES.has(collName)) return\n\n const modelName = resolveModelNameFromCollection(collName)\n if (!modelName) return\n if (INTERNAL_IGNORED_MODEL_NAMES.has(modelName)) return\n\n const op = String(change.operationType ?? \"\")\n if (!op) return\n const normalizedOp = normalizeOpForTaskName(op)\n\n let doc = \"fullDocument\" in change ? change.fullDocument : undefined\n if (!doc && normalizedOp === \"delete\") {\n doc = \"documentKey\" in change ? change.documentKey : undefined\n }\n if (!doc) return\n\n const updateDescription = \"updateDescription\" in change ? change.updateDescription : undefined\n\n try {\n await dispatchWorkerQueue({\n dbName,\n modelName,\n op: normalizedOp,\n doc,\n updateDescription,\n })\n\n resumeAfter = change?._id ?? resumeAfter\n } catch (err) {\n console.warn(\"queue listener failed to dispatch change\", err)\n try {\n await streamRef?.close()\n } catch {\n // ignore\n }\n }\n }).catch((err) => {\n console.warn(\"queue listener change handler failed\", err)\n })\n })\n\n stream.on(\"error\", (err) => {\n if (stopped) return\n if (resumeAfter && isChangeStreamHistoryLost(err)) {\n resumeAfter = null\n }\n try {\n void Promise.resolve(stream?.close()).catch(() => {})\n } catch {\n // ignore\n }\n })\n\n return { stoppedPromise }\n }\n\n const run = async (): Promise<void> => {\n let retryCounter = 0\n\n while (!stopped) {\n try {\n setStatus({ state: \"connecting\", attempt: retryCounter + 1 })\n\n const { stoppedPromise } = await startStream()\n retryCounter = 0\n setStatus({ state: \"ready\" })\n resolveReady()\n\n const end = await stoppedPromise\n if (stopped) return\n\n retryCounter += 1\n if (maxRetries !== \"infinite\" && retryCounter > maxRetries) {\n const err = end.reason === \"error\" ? end.error : new Error(\"queue listener closed\")\n setStatus({ state: \"failed\", attempt: retryCounter, error: err })\n if (fatalOnMaxRetries) {\n try {\n options.onFatal?.(err)\n } catch (fatalErr) {\n console.warn(\"queue listener onFatal failed\", fatalErr)\n }\n }\n rejectReady(err)\n abortController.abort()\n await closeResources()\n return\n }\n\n const delayMs = getRetryDelayMs(retryCounter, retryDelays)\n const err = end.reason === \"error\" ? end.error : new Error(\"queue listener closed\")\n console.warn(\"queue listener not ready, retrying in\", delayMs, err)\n setStatus({ state: \"error\", attempt: retryCounter, error: err, nextRetryInMs: delayMs })\n await closeResources()\n await sleep(delayMs, abortController.signal)\n } catch (err) {\n if (stopped) return\n\n retryCounter += 1\n if (maxRetries !== \"infinite\" && retryCounter > maxRetries) {\n setStatus({ state: \"failed\", attempt: retryCounter, error: err })\n if (fatalOnMaxRetries) {\n try {\n options.onFatal?.(err)\n } catch (fatalErr) {\n console.warn(\"queue listener onFatal failed\", fatalErr)\n }\n }\n rejectReady(err)\n abortController.abort()\n await closeResources()\n return\n }\n\n const delayMs = getRetryDelayMs(retryCounter, retryDelays)\n console.warn(\"queue listener not ready, retrying in\", delayMs, err)\n setStatus({ state: \"error\", attempt: retryCounter, error: err, nextRetryInMs: delayMs })\n await closeResources()\n await sleep(delayMs, abortController.signal)\n }\n }\n }\n\n const handle: QueueListenerHandle = {\n ready,\n close,\n getStatus: () => status,\n }\n\n void run().catch(async (err) => {\n if (stopped) return\n setStatus({ state: \"failed\", attempt: 0, error: err })\n rejectReady(err)\n await closeResources()\n })\n\n return handle\n}\n","export type DbEventOp = \"insert\" | \"update\" | \"delete\"\n\nexport type DbEventTaskName<TOp extends DbEventOp = DbEventOp, TModelName extends string = string> =\n `on-${TOp}-${TModelName}`\n\nexport type DbEventTaskPayload<TDoc = unknown, TUpdateDescription = unknown> = {\n doc: TDoc\n updateDescription?: TUpdateDescription\n}\n\nexport const dbEventTaskName = <TOp extends DbEventOp, TModelName extends string>(\n op: TOp,\n modelName: TModelName,\n): DbEventTaskName<TOp, TModelName> => `on-${op}-${modelName}`\n"],"names":["DEFAULT_QUEUE_NAME","DEFAULT_CONCURRENCY","DEFAULT_RENTENTION","tasksByName","Object","create","queueInstance","workerInstance","hasStarted","getRedisUrl","redisUrl","process","env","REDIS_URL","trim","Error","getQueueName","RB_QUEUE_NAME","getConcurrency","raw","RB_QUEUE_CONCURRENCY","value","Number","isFinite","Math","floor","getConnection","url","ensureQueue","Queue","connection","defaultJobOptions","removeOnComplete","removeOnFail","registerTask","name","handler","getTasks","add","taskName","payload","options","getJob","jobId","getJobs","args","getInstance","start","queue","concurrency","console","log","on","err","message","Worker","job","data","id","waitUntilReady","close","getUrl","scheduleTask","repeat","rest","queueApi","RETRY_MAXIMUM_DELAY_MS","RETRY_MINIMUM_DELAY_MS","RETRY_DEFAULT_FACTOR","sleep","ms","signal","Promise","resolve","aborted","timeout","cleanup","removeEventListener","onAbort","setTimeout","unref","addEventListener","getMongoUrl","explicit","MONGODB_URL","MONGO_URL","MONGODB_URI","DB_URL","port","DB_PORT","host","DB_HOST","escapeRegex","replace","isRecord","getDocumentId","doc","undefined","_id","resolveModelNameFromCollection","collName","models","mongoose","modelName","keys","model","collectionName","collection","normalizeUpdateDescription","updateDescription","updatedFields","updatedFieldsKeys","normalizeOpForTaskName","op","dispatchWorkerQueue","dbName","tasks","taskOp","handlerName","normalizedUpdateDescription","shouldSkipCollection","endsWith","INTERNAL_IGNORED_MODEL_NAMES","Set","INTERNAL_IGNORED_COLLECTION_NAMES","normalizeRetryDelays","input","minMs","max","rawMaxMs","maxMs","factor","getRetryDelayMs","attempt","delays","min","round","pow","normalizeMaxRetries","parsed","registerQueueListener","maxRetries","fatalOnMaxRetries","retryDelays","appName","APP_NAME","mongoUrl","mongoClientOptions","family","serverSelectionTimeoutMS","connectTimeoutMS","stopped","abortController","AbortController","client","stream","resumeAfter","processing","status","state","setStatus","next","onStateChange","warn","readySettled","readyResolve","readyReject","ready","reject","resolveReady","rejectReady","isChangeStreamHistoryLost","maybeErr","code","codeName","String","includes","abort","removeAllListeners","closeResources","startStream","stoppedPromise","reason","MongoClient","connect","dbMatch","$regex","pipeline","$match","operationType","$in","$nin","Array","from","watch","fullDocument","settled","settle","result","once","error","change","streamRef","then","ns","db","coll","has","normalizedOp","documentKey","catch","run","retryCounter","end","onFatal","fatalErr","delayMs","nextRetryInMs","handle","getStatus","dbEventTaskName"],"mappings":";;;AAOA,MAAMA,qBAAqB;AAC3B,MAAMC,sBAAsB;AAC5B,MAAMC,qBAAqB;AAE3B,MAAMC,cAAoDC,uBAAOC,OAAO,IAAI;AAE5E,IAAIC,gBAA8B;AAClC,IAAIC,iBAAgC;AACpC,IAAIC,aAAa;AAEjB,MAAMC,cAAcA,MAAc;AAChC,QAAMC,WAAWC,QAAQC,IAAIC,WAAWC,KAAAA;AACxC,MAAI,CAACJ,UAAU;AACb,UAAM,IAAIK,MAAM,wDAAwD;AAAA,EAC1E;AACA,SAAOL;AACT;AAEA,MAAMM,eAAeA,MAAcL,QAAQC,IAAIK,eAAeH,UAAUd;AAExE,MAAMkB,iBAAiBA,MAAc;AACnC,QAAMC,MAAMR,QAAQC,IAAIQ,sBAAsBN,KAAAA;AAC9C,QAAMO,QAAQF,MAAMG,OAAOH,GAAG,IAAIlB;AAClC,MAAI,CAACqB,OAAOC,SAASF,KAAK,KAAKA,SAAS,EAAG,QAAOpB;AAClD,SAAOuB,KAAKC,MAAMJ,KAAK;AACzB;AAEA,MAAMK,gBAAgBA,OAAO;AAAA,EAAEC,KAAKlB,YAAAA;AAAc;AAElD,MAAMmB,cAAcA,MAAa;AAC/B,MAAItB,cAAe,QAAOA;AAC1BA,kBAAgB,IAAIuB,MAAMb,gBAAgB;AAAA,IACxCc,YAAYJ,cAAAA;AAAAA,IACZK,mBAAmB;AAAA,MACjBC,kBAAkB9B;AAAAA,MAClB+B,cAAc/B;AAAAA,IAAAA;AAAAA,EAChB,CACD;AAED,SAAOI;AACT;AAOO,SAAS4B,aAAaC,MAAcC,SAAqC;AAC9EjC,cAAYgC,IAAI,IAAIC;AACtB;AAEO,MAAMC,WAAWA,MAA4ClC;AAQ7D,SAASmC,IAAIC,UAAkBC,SAAkBC,SAAoC;AAC1F,SAAOb,YAAAA,EAAcU,IAAIC,UAAUC,SAASC,OAAO;AACrD;AAEO,MAAMC,SAAS,OAAOC,UAAwC,MAAMf,cAAcc,OAAOC,KAAK,KAAM;AAEpG,MAAMC,UAAU,UAAUC,SAC/BjB,cAAcgB,QAAQ,GAAGC,IAAI;AAExB,MAAMC,cAAcA,MAAalB,YAAAA;AAEjC,MAAMmB,QAAQ,YAA4B;AAC/C,MAAIvC,mBAAmBoB,YAAAA;AACvBpB,eAAa;AAEb,QAAMwC,QAAQpB,YAAAA;AACd,QAAMqB,cAAc/B,eAAAA;AAEpBgC,UAAQC,IAAI,sBAAsB;AAAA,IAAEH,OAAOA,MAAMb;AAAAA,IAAMzB,UAAUD,YAAAA;AAAAA,IAAewC;AAAAA,EAAAA,CAAa;AAE7FD,QAAMI,GAAG,SAAUC,CAAAA,QAAQ;AACzBH,YAAQC,IAAI,gBAAgBE,IAAIC,OAAO,EAAE;AAAA,EAC3C,CAAC;AAED,MAAI,CAAC/C,gBAAgB;AACnBA,qBAAiB,IAAIgD,OACnBP,MAAMb,MACN,OAAOqB,QAAQ;AACb,YAAMjB,WAAWiB,IAAIrB;AAErB,YAAMC,UAAUjC,YAAYoC,QAAQ;AACpC,UAAI,CAACH,SAAS;AACZ,cAAM,IAAIrB,MAAM,2BAA2BwB,QAAQ,GAAG;AAAA,MACxD;AAEA,aAAO,MAAMH,QAAQoB,IAAIC,MAAMD,GAAG;AAAA,IACpC,GACA;AAAA,MACEP;AAAAA,MACAnB,YAAYJ,cAAAA;AAAAA,IAAc,CAE9B;AAAA,EACF;AAEAnB,iBAAe6C,GAAG,SAAUC,CAAAA,QAAQ;AAClCH,YAAQC,IAAI,iBAAiBE,IAAIC,OAAO,EAAE;AAAA,EAC5C,CAAC;AAED/C,iBAAe6C,GAAG,WAAYT,CAAAA,UAAU;AACtCO,YAAQC,IAAI,OAAOR,KAAK,UAAU;AAAA,EACpC,CAAC;AAEDpC,iBAAe6C,GAAG,UAAU,CAACI,KAAKH,QAAQ;AACxCH,YAAQC,IAAI,OAAOK,KAAKE,MAAM,SAAS,WAAWL,GAAG;AAAA,EACvD,CAAC;AAED,QAAM9C,eAAeoD,eAAAA;AAErB,SAAOX;AACT;AAEO,MAAMY,QAAQ,YAA2B;AAC9C,MAAI,CAACtD,iBAAiB,CAACC,eAAgB;AACvC,MAAI;AACF,UAAMA,gBAAgBqD,MAAAA;AACtB,UAAMtD,eAAesD,MAAAA;AAAAA,EACvB,UAAA;AACErD,qBAAiB;AACjBD,oBAAgB;AAChBE,iBAAa;AAAA,EACf;AACF;AAEO,MAAMqD,SAASA,MAAcpD,YAAAA;AAapC,eAAsBqD,aAAavB,UAAkBC,SAAkBC,SAA4C;AACjH,QAAM;AAAA,IAAEE;AAAAA,IAAOoB;AAAAA,IAAQ,GAAGC;AAAAA,EAAAA,IAASvB;AACnC,SAAOH,IAAIC,UAAUC,SAAS;AAAA,IAC5B,GAAGwB;AAAAA,IACHrB,OAAOA,SAAS,YAAYJ,QAAQ;AAAA,IACpCwB;AAAAA,EAAAA,CACD;AACH;AAEA,MAAME,WAAW;AAAA,EACflB;AAAAA,EACAa;AAAAA,EACA1B;AAAAA,EACAG;AAAAA,EACAC;AAAAA,EACAwB;AAAAA,EACApB;AAAAA,EACAE;AAAAA,EACAE;AAAAA,EACAe;AACF;ACpIA,MAAMK,yBAAyB;AAC/B,MAAMC,yBAAyB;AAC/B,MAAMC,uBAAuB;AAE7B,MAAMC,QAAQ,OAAOC,IAAYC,WAAwC,IAAIC,QAASC,CAAAA,YAAY;AAChG,MAAIF,QAAQG,SAAS;AACnBD,YAAAA;AACA;AAAA,EACF;AAEA,MAAIE,UAAgD;AAEpD,QAAMC,UAAUA,MAAM;AACpB,QAAID,sBAAsBA,OAAO;AACjCJ,YAAQM,oBAAoB,SAASC,OAAO;AAAA,EAC9C;AAEA,QAAMA,UAAUA,MAAM;AACpBF,YAAAA;AACAH,YAAAA;AAAAA,EACF;AAEAE,YAAUI,WAAW,MAAM;AACzBH,YAAAA;AACAH,YAAAA;AAAAA,EACF,GAAGH,EAAE;AACLK,UAAQK,QAAAA;AAERT,UAAQU,iBAAiB,SAASH,OAAO;AAC3C,CAAC;AAED,MAAMI,cAAcA,MAAc;AAChC,QAAMC,WACJxE,QAAQC,IAAIwE,eACTzE,QAAQC,IAAIyE,aACZ1E,QAAQC,IAAI0E,eACZ3E,QAAQC,IAAI2E;AAEjB,MAAIJ,YAAYA,SAASrE,KAAAA,EAAQ,QAAOqE,SAASrE,KAAAA;AAEjD,QAAM0E,OAAO7E,QAAQC,IAAI6E,SAAS3E,KAAAA;AAClC,MAAI,CAAC0E,KAAM,OAAM,IAAIzE,MAAM,qFAAqF;AAEhH,QAAM2E,OAAO/E,QAAQC,IAAI+E,SAAS7E,UAAU;AAC5C,SAAO,aAAa4E,IAAI,IAAIF,IAAI;AAClC;AAEA,MAAMI,cAAcA,CAACvE,UAA0BA,MAAMwE,QAAQ,uBAAuB,MAAM;AAS1F,MAAMC,WAAWA,CAACzE,UAChB,OAAOA,UAAU,YAAYA,UAAU;AAEzC,MAAM0E,gBAAgBA,CAACC,QAA0B;AAC/C,MAAI,CAACF,SAASE,GAAG,EAAG,QAAOC;AAC3B,SAAOD,IAAIE;AACb;AAEA,MAAMC,iCAAiCA,CAACC,aAAoC;AAC1E,QAAMC,SAASC,SAASD;AAExB,aAAWE,aAAanG,OAAOoG,KAAKH,MAAM,GAAG;AAC3C,UAAMI,QAAQJ,OAAOE,SAAS;AAC9B,UAAMG,iBACJD,MAAME,YAAYD,kBACfD,MAAME,YAAYxE;AAEvB,QAAIuE,mBAAmBN,UAAU;AAC/B,aAAOG;AAAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAMK,6BAA6BA,CAACC,sBAAwC;AAC1E,MAAI,CAACf,SAASe,iBAAiB,EAAG,QAAOA;AACzC,MAAIf,SAASe,kBAAkBC,aAAa,GAAG;AAC7C,WAAO;AAAA,MACL,GAAGD;AAAAA,MACHE,mBAAmB3G,OAAOoG,KAAKK,kBAAkBC,aAAa;AAAA,IAAA;AAAA,EAElE;AAEA,SAAOD;AACT;AAEA,MAAMG,yBAAyBA,CAACC,OAAwBA,OAAO,YAAY,WAAWA;AAEtF,MAAMC,sBAAsB,OAAO;AAAA,EACjCC;AAAAA,EACAZ;AAAAA,EACAU;AAAAA,EACAjB;AAAAA,EACAa;AAOF,MAAqB;AACnB,QAAMO,QAAQpE,SAAMX,SAAAA;AACpB,QAAMgF,SAASL,uBAAuBC,EAAE;AACxC,QAAMK,cAAc,MAAMD,MAAM,IAAId,SAAS;AAE7C,MAAI,CAACa,MAAME,WAAW,EAAG;AAEzB,QAAMC,8BAA8BX,2BAA2BC,iBAAiB;AAEhF,QAAM7D,SAAMV,IAAIgF,aAAa;AAAA,IAAEtB;AAAAA,IAAKa,mBAAmBU;AAAAA,EAAAA,GAA+B;AAAA,IACpF5E,OAAO,GAAGwE,MAAM,IAAIE,MAAM,IAAItB,cAAcC,GAAG,KAAK,SAAS;AAAA,IAC7DhE,kBAAkB;AAAA,IAClBC,cAAc;AAAA,EAAA,CACf;AACH;AAEA,MAAMuF,uBAAuBA,CAACpB,aAC5BA,SAASqB,SAAS,QAAQ,KAAKrB,SAASqB,SAAS,SAAS;AAE5D,MAAMC,+BAA+B,oBAAIC,IAAI,CAAC,eAAe,cAAc,CAAC;AAE5E,MAAMC,oCAAoC,oBAAID,IAAI,CAAC,cAAc,aAAa,CAAC;AAE/E,MAAME,uBAAuBA,CAACC,UAAoF;AAChH,QAAMC,QAAQvG,KAAKwG,IAAI,GAAGxG,KAAKC,MAAMqG,OAAOC,SAAS5D,sBAAsB,CAAC;AAC5E,QAAM8D,WAAWzG,KAAKwG,IAAI,GAAGxG,KAAKC,MAAMqG,OAAOI,SAAShE,sBAAsB,CAAC;AAC/E,QAAMgE,QAAQ1G,KAAKwG,IAAID,OAAOE,QAAQ;AACtC,QAAME,SAAS3G,KAAKwG,IAAI,GAAG1G,OAAOC,SAASuG,OAAOK,MAAM,IAAKL,OAAOK,UAAU/D,uBAAwBA,oBAAoB;AAC1H,SAAO;AAAA,IAAE2D;AAAAA,IAAOG;AAAAA,IAAOC;AAAAA,EAAAA;AACzB;AAEA,MAAMC,kBAAkBA,CAACC,SAAiBC,WACxC9G,KAAK+G,IAAID,OAAOJ,OAAO1G,KAAKgH,MAAMF,OAAOP,QAAQvG,KAAKiH,IAAIH,OAAOH,QAAQ3G,KAAKwG,IAAI,GAAGK,UAAU,CAAC,CAAC,CAAC,CAAC;AAErG,MAAMK,sBAAsBA,CAACrH,UAAoD;AAC/E,MAAIA,UAAU,WAAY,QAAOA;AACjC,QAAMsH,SAASnH,KAAKC,MAAMJ,KAAK;AAC/B,SAAOC,OAAOC,SAASoH,MAAM,IAAInH,KAAKwG,IAAI,GAAGW,MAAM,IAAI;AACzD;AAEO,MAAMC,wBAAwB,OAAOnG,UAAgC,OAAqC;AAC/G,QAAMoG,aAAaH,oBAAoBjG,QAAQoG,cAAc,UAAU;AACvE,QAAMC,oBAAoBrG,QAAQqG,qBAAqB;AACvD,QAAMC,cAAclB,qBAAqBpF,QAAQsG,WAAW;AAE5D,QAAMC,UAAUrI,QAAQC,IAAIqI,UAAUnI,KAAAA;AACtC,MAAI,CAACkI,SAAS;AACZ,UAAM,IAAIjI,MAAM,wEAAwE;AAAA,EAC1F;AAEA,QAAMmI,WAAWhE,YAAAA;AACjB,QAAMiE,qBAAyC;AAAA,IAC7CC,QAAQ;AAAA,IACRC,0BAA0B;AAAA,IAC1BC,kBAAkB;AAAA,IAClB,GAAG7G,QAAQ0G;AAAAA,EAAAA;AAGb,MAAII,UAAU;AACd,QAAMC,kBAAkB,IAAIC,gBAAAA;AAC5B,MAAIC,SAA6B;AACjC,MAAIC,SAAwC;AAC5C,MAAIC,cAA+B;AACnC,MAAIC,aAAarF,QAAQC,QAAAA;AAEzB,MAAIqF,SAA8B;AAAA,IAAEC,OAAO;AAAA,IAAc1B,SAAS;AAAA,EAAA;AAClE,QAAM2B,YAAYA,CAACC,SAAoC;AACrDH,aAASG;AACT,QAAI;AACFxH,cAAQyH,gBAAgBD,IAAI;AAAA,IAC9B,SAAS5G,KAAK;AACZH,cAAQiH,KAAK,uCAAuC9G,GAAG;AAAA,IACzD;AAAA,EACF;AAEA,MAAI+G,eAAe;AACnB,MAAIC,eAAoC;AACxC,MAAIC,cAA+C;AACnD,QAAMC,QAAQ,IAAI/F,QAAc,CAACC,SAAS+F,WAAW;AACnDH,mBAAe5F;AACf6F,kBAAcE;AAAAA,EAChB,CAAC;AAED,QAAMC,eAAeA,MAAY;AAC/B,QAAIL,aAAc;AAClBA,mBAAe;AACfC,mBAAAA;AAAAA,EACF;AAEA,QAAMK,cAAcA,CAACrH,QAAuB;AAC1C,QAAI+G,aAAc;AAClBA,mBAAe;AACfE,kBAAcjH,GAAG;AAAA,EACnB;AAEA,QAAMsH,4BAA4BA,CAACtH,QAA0B;AAC3D,UAAMuH,WAAWvH;AACjB,UAAMwH,OAAO,OAAOD,SAASC,SAAS,WAAWD,SAASC,OAAO;AACjE,UAAMC,WAAW,OAAOF,SAASE,aAAa,WAAWF,SAASE,WAAW;AAC7E,UAAMxH,UAAUD,eAAetC,QAAQsC,IAAIC,UAAUyH,OAAO1H,OAAO,EAAE;AAErE,WACEwH,SAAS,OACNC,aAAa,6BACbxH,QAAQ0H,SAAS,yBAAyB,KAC1C1H,QAAQ0H,SAAS,cAAc,KAC/B1H,QAAQ0H,SAAS,cAAc,KAC/B1H,QAAQ0H,SAAS,0CAA0C,KAC3D1H,QAAQ0H,SAAS,eAAe;AAAA,EAEvC;AAEA,QAAMpH,SAAQ,YAA2B;AACvC2F,cAAU;AACVC,oBAAgByB,MAAAA;AAChB,QAAI;AACFtB,cAAQuB,mBAAAA;AACR,YAAMvB,QAAQ/F,MAAAA;AAAAA,IAChB,QAAQ;AAAA,IACN;AAEF+F,aAAS;AAET,QAAI;AACF,YAAMD,QAAQ9F,MAAAA;AAAAA,IAChB,QAAQ;AAAA,IACN;AAEF8F,aAAS;AAET,QAAI,CAACU,cAAc;AACjBM,kBAAY,IAAI3J,MAAM,oCAAoC,CAAC;AAAA,IAC7D;AAEAiJ,cAAU;AAAA,MAAED,OAAO;AAAA,IAAA,CAAU;AAAA,EAC/B;AAEA,QAAMoB,iBAAiB,YAA2B;AAChD,QAAI;AACFxB,cAAQuB,mBAAAA;AACR,YAAMvB,QAAQ/F,MAAAA;AAAAA,IAChB,QAAQ;AAAA,IACN;AAEF+F,aAAS;AAET,QAAI;AACF,YAAMD,QAAQ9F,MAAAA;AAAAA,IAChB,QAAQ;AAAA,IACN;AAEF8F,aAAS;AAAA,EACX;AAEA,QAAM0B,cAAc,YAAkG;AACpH,QAAI7B,QAAS,QAAO;AAAA,MAAE8B,gBAAgB7G,QAAQC,QAAQ;AAAA,QAAE6G,QAAQ;AAAA,MAAA,CAAS;AAAA,IAAA;AAEzE,QAAI3B,QAAQ;AACV,UAAI;AACFA,eAAOuB,mBAAAA;AACP,cAAMvB,OAAO/F,MAAAA;AAAAA,MACf,QAAQ;AAAA,MACN;AAEF+F,eAAS;AAAA,IACX;AAEA,QAAID,QAAQ;AACV,UAAI;AACF,cAAMA,OAAO9F,MAAAA;AAAAA,MACf,QAAQ;AAAA,MACN;AAEF8F,eAAS;AAAA,IACX;AAEAA,aAAS,IAAI6B,YAAYrC,UAAUC,kBAAkB;AAErD,UAAMO,OAAO8B,QAAAA;AAEb,UAAMC,UAAU;AAAA,MAAE,SAAS;AAAA,QAAEC,QAAQ,IAAI9F,YAAYoD,OAAO,CAAC;AAAA,MAAA;AAAA,IAAU;AAEvE,UAAM2C,WAAuB,CAC3B;AAAA,MAAEC,QAAQH;AAAAA,IAAAA,GACV;AAAA,MACEG,QAAQ;AAAA,QACNC,eAAe;AAAA,UAAEC,KAAK,CAAC,UAAU,UAAU,WAAW,QAAQ;AAAA,QAAA;AAAA,MAAE;AAAA,IAClE,GAEF;AAAA,MAAEF,QAAQ;AAAA,QAAE,WAAW;AAAA,UAAEG,MAAMC,MAAMC,KAAKrE,iCAAiC;AAAA,QAAA;AAAA,MAAE;AAAA,IAAE,CAAG;AAGpF+B,aAASD,OAAOwC,MAAMP,UAAU;AAAA,MAC9BQ,cAAc;AAAA,MACd,GAAIvC,cAAc;AAAA,QAAEA;AAAAA,MAAAA,IAAgB,CAAA;AAAA,IAAC,CACtC;AAED,UAAMyB,iBAAiB,IAAI7G,QAAyDC,CAAAA,YAAY;AAC9F,UAAI2H,UAAU;AACd,YAAMtH,UAAUA,MAAMuH,OAAO;AAAA,QAAEf,QAAQ;AAAA,MAAA,CAAS;AAEhD,YAAMe,SAASA,CAACC,WAA2D;AACzE,YAAIF,QAAS;AACbA,kBAAU;AACV5C,wBAAgBjF,OAAOM,oBAAoB,SAASC,OAAO;AAC3DL,gBAAQ6H,MAAM;AAAA,MAChB;AAEA9C,sBAAgBjF,OAAOU,iBAAiB,SAASH,OAAO;AACxD6E,cAAQ4C,KAAK,SAAS,MAAMF,OAAO;AAAA,QAAEf,QAAQ;AAAA,MAAA,CAAS,CAAC;AACvD3B,cAAQ4C,KAAK,SAAUlJ,CAAAA,QAAQgJ,OAAO;AAAA,QAAEf,QAAQ;AAAA,QAASkB,OAAOnJ;AAAAA,MAAAA,CAAK,CAAC;AAAA,IACxE,CAAC;AAEDsG,WAAOvG,GAAG,UAAU,CAACqJ,WAA2C;AAC9D,YAAMC,YAAY/C;AAClBE,mBAAaA,WAAW8C,KAAK,YAAY;AACvC,cAAMC,KAAK,QAAQH,SAASA,OAAOG,KAAK3G;AACxC,cAAMkB,SAAS4D,OAAO6B,IAAIC,MAAM,EAAE;AAClC,YAAI,CAAC1F,OAAQ;AAEb,cAAMf,WAAW2E,OAAO6B,MAAM,UAAUA,KAAKA,GAAGE,OAAO,EAAE;AACzD,YAAI,CAAC1G,SAAU;AACf,YAAIoB,qBAAqBpB,QAAQ,EAAG;AACpC,YAAIwB,kCAAkCmF,IAAI3G,QAAQ,EAAG;AAErD,cAAMG,YAAYJ,+BAA+BC,QAAQ;AACzD,YAAI,CAACG,UAAW;AAChB,YAAImB,6BAA6BqF,IAAIxG,SAAS,EAAG;AAEjD,cAAMU,KAAK8D,OAAO0B,OAAOZ,iBAAiB,EAAE;AAC5C,YAAI,CAAC5E,GAAI;AACT,cAAM+F,eAAehG,uBAAuBC,EAAE;AAE9C,YAAIjB,MAAM,kBAAkByG,SAASA,OAAON,eAAelG;AAC3D,YAAI,CAACD,OAAOgH,iBAAiB,UAAU;AACrChH,gBAAM,iBAAiByG,SAASA,OAAOQ,cAAchH;AAAAA,QACvD;AACA,YAAI,CAACD,IAAK;AAEV,cAAMa,oBAAoB,uBAAuB4F,SAASA,OAAO5F,oBAAoBZ;AAErF,YAAI;AACF,gBAAMiB,oBAAoB;AAAA,YACxBC;AAAAA,YACAZ;AAAAA,YACAU,IAAI+F;AAAAA,YACJhH;AAAAA,YACAa;AAAAA,UAAAA,CACD;AAED+C,wBAAc6C,QAAQvG,OAAO0D;AAAAA,QAC/B,SAASvG,KAAK;AACZH,kBAAQiH,KAAK,4CAA4C9G,GAAG;AAC5D,cAAI;AACF,kBAAMqJ,WAAW9I,MAAAA;AAAAA,UACnB,QAAQ;AAAA,UACN;AAAA,QAEJ;AAAA,MACF,CAAC,EAAEsJ,MAAO7J,CAAAA,QAAQ;AAChBH,gBAAQiH,KAAK,wCAAwC9G,GAAG;AAAA,MAC1D,CAAC;AAAA,IACH,CAAC;AAEDsG,WAAOvG,GAAG,SAAUC,CAAAA,QAAQ;AAC1B,UAAIkG,QAAS;AACb,UAAIK,eAAee,0BAA0BtH,GAAG,GAAG;AACjDuG,sBAAc;AAAA,MAChB;AACA,UAAI;AACF,aAAKpF,QAAQC,QAAQkF,QAAQ/F,OAAO,EAAEsJ,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACtD,QAAQ;AAAA,MACN;AAAA,IAEJ,CAAC;AAED,WAAO;AAAA,MAAE7B;AAAAA,IAAAA;AAAAA,EACX;AAEA,QAAM8B,MAAM,YAA2B;AACrC,QAAIC,eAAe;AAEnB,WAAO,CAAC7D,SAAS;AACf,UAAI;AACFS,kBAAU;AAAA,UAAED,OAAO;AAAA,UAAc1B,SAAS+E,eAAe;AAAA,QAAA,CAAG;AAE5D,cAAM;AAAA,UAAE/B;AAAAA,QAAAA,IAAmB,MAAMD,YAAAA;AACjCgC,uBAAe;AACfpD,kBAAU;AAAA,UAAED,OAAO;AAAA,QAAA,CAAS;AAC5BU,qBAAAA;AAEA,cAAM4C,MAAM,MAAMhC;AAClB,YAAI9B,QAAS;AAEb6D,wBAAgB;AAChB,YAAIvE,eAAe,cAAcuE,eAAevE,YAAY;AAC1D,gBAAMxF,OAAMgK,IAAI/B,WAAW,UAAU+B,IAAIb,QAAQ,IAAIzL,MAAM,uBAAuB;AAClFiJ,oBAAU;AAAA,YAAED,OAAO;AAAA,YAAU1B,SAAS+E;AAAAA,YAAcZ,OAAOnJ;AAAAA,UAAAA,CAAK;AAChE,cAAIyF,mBAAmB;AACrB,gBAAI;AACFrG,sBAAQ6K,UAAUjK,IAAG;AAAA,YACvB,SAASkK,UAAU;AACjBrK,sBAAQiH,KAAK,iCAAiCoD,QAAQ;AAAA,YACxD;AAAA,UACF;AACA7C,sBAAYrH,IAAG;AACfmG,0BAAgByB,MAAAA;AAChB,gBAAME,eAAAA;AACN;AAAA,QACF;AAEA,cAAMqC,UAAUpF,gBAAgBgF,cAAcrE,WAAW;AACzD,cAAM1F,MAAMgK,IAAI/B,WAAW,UAAU+B,IAAIb,QAAQ,IAAIzL,MAAM,uBAAuB;AAClFmC,gBAAQiH,KAAK,yCAAyCqD,SAASnK,GAAG;AAClE2G,kBAAU;AAAA,UAAED,OAAO;AAAA,UAAS1B,SAAS+E;AAAAA,UAAcZ,OAAOnJ;AAAAA,UAAKoK,eAAeD;AAAAA,QAAAA,CAAS;AACvF,cAAMrC,eAAAA;AACN,cAAM9G,MAAMmJ,SAAShE,gBAAgBjF,MAAM;AAAA,MAC7C,SAASlB,KAAK;AACZ,YAAIkG,QAAS;AAEb6D,wBAAgB;AAChB,YAAIvE,eAAe,cAAcuE,eAAevE,YAAY;AAC1DmB,oBAAU;AAAA,YAAED,OAAO;AAAA,YAAU1B,SAAS+E;AAAAA,YAAcZ,OAAOnJ;AAAAA,UAAAA,CAAK;AAChE,cAAIyF,mBAAmB;AACrB,gBAAI;AACFrG,sBAAQ6K,UAAUjK,GAAG;AAAA,YACvB,SAASkK,UAAU;AACjBrK,sBAAQiH,KAAK,iCAAiCoD,QAAQ;AAAA,YACxD;AAAA,UACF;AACA7C,sBAAYrH,GAAG;AACfmG,0BAAgByB,MAAAA;AAChB,gBAAME,eAAAA;AACN;AAAA,QACF;AAEA,cAAMqC,UAAUpF,gBAAgBgF,cAAcrE,WAAW;AACzD7F,gBAAQiH,KAAK,yCAAyCqD,SAASnK,GAAG;AAClE2G,kBAAU;AAAA,UAAED,OAAO;AAAA,UAAS1B,SAAS+E;AAAAA,UAAcZ,OAAOnJ;AAAAA,UAAKoK,eAAeD;AAAAA,QAAAA,CAAS;AACvF,cAAMrC,eAAAA;AACN,cAAM9G,MAAMmJ,SAAShE,gBAAgBjF,MAAM;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,QAAMmJ,SAA8B;AAAA,IAClCnD;AAAAA,IACA3G,OAAAA;AAAAA,IACA+J,WAAWA,MAAM7D;AAAAA,EAAAA;AAGnB,OAAKqD,IAAAA,EAAMD,MAAM,OAAO7J,QAAQ;AAC9B,QAAIkG,QAAS;AACbS,cAAU;AAAA,MAAED,OAAO;AAAA,MAAU1B,SAAS;AAAA,MAAGmE,OAAOnJ;AAAAA,IAAAA,CAAK;AACrDqH,gBAAYrH,GAAG;AACf,UAAM8H,eAAAA;AAAAA,EACR,CAAC;AAED,SAAOuC;AACT;AChfO,MAAME,kBAAkB,CAC7B3G,IACAV,cACqC,MAAMU,EAAE,IAAIV,SAAS;"}
|