@zmdb/jobs 1.0.0-beta.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.
@@ -0,0 +1,93 @@
1
+ import type { ApplicationExtension } from '@zmdb/app';
2
+
3
+ import type { JobStoreResource, Worker } from './queues/index.js';
4
+ import type { Scheduler } from './schedule/index.js';
5
+
6
+ interface JobsParticipant {
7
+ start(): void;
8
+ onShutdown(options?: { readonly graceMs: number }): Promise<void>;
9
+ }
10
+
11
+ /**
12
+ * Start and stop one application's explicit background-work components.
13
+ *
14
+ * Workers start first so a scheduled enqueue always has a consumer. Shutdown
15
+ * reverses that order so no scheduler can add work after worker drain begins.
16
+ */
17
+ export function jobsExtension(options: {
18
+ readonly workers?: readonly Worker[];
19
+ readonly schedulers?: readonly Scheduler[];
20
+ readonly stores?: readonly JobStoreResource[];
21
+ }): ApplicationExtension {
22
+ const workers = Object.freeze([...(options.workers ?? [])]);
23
+ const schedulers = Object.freeze([...(options.schedulers ?? [])]);
24
+ const stores = [...new Set(options.stores ?? [])];
25
+ const enteredWorkers: Worker[] = [];
26
+ const enteredSchedulers: Scheduler[] = [];
27
+ let started = false;
28
+ let stopped: Promise<void> | undefined;
29
+
30
+ return {
31
+ name: '@zmdb/jobs',
32
+ start() {
33
+ if (started) return;
34
+ started = true;
35
+ startAll(workers, enteredWorkers);
36
+ startAll(schedulers, enteredSchedulers);
37
+ },
38
+ stop({ graceMs }) {
39
+ stopped ??= stopAll(enteredSchedulers, enteredWorkers, stores, graceMs);
40
+ return stopped;
41
+ },
42
+ };
43
+ }
44
+
45
+ function startAll<T extends JobsParticipant>(participants: readonly T[], entered: T[]): void {
46
+ for (const participant of participants) {
47
+ entered.push(participant);
48
+ participant.start();
49
+ }
50
+ }
51
+
52
+ async function stopAll(
53
+ schedulers: readonly Scheduler[],
54
+ workers: readonly Worker[],
55
+ stores: readonly JobStoreResource[],
56
+ graceMs: number,
57
+ ): Promise<void> {
58
+ if (!Number.isSafeInteger(graceMs) || graceMs < 0 || graceMs > 2_147_483_647) {
59
+ throw new RangeError('@zmdb/jobs: graceMs must be a non-negative safe integer');
60
+ }
61
+ const deadline = Date.now() + graceMs;
62
+ const errors: unknown[] = [];
63
+ const stops = [
64
+ ...schedulers
65
+ .toReversed()
66
+ .map(participant => (remaining: number) => participant.onShutdown({ graceMs: remaining })),
67
+ ...workers.toReversed().map(participant => (remaining: number) => participant.onShutdown({ graceMs: remaining })),
68
+ ...stores.toReversed().map(store => (remaining: number) => store.close({ graceMs: remaining })),
69
+ ];
70
+ for (const stop of stops) {
71
+ let timer: ReturnType<typeof setTimeout> | undefined;
72
+ try {
73
+ const remaining = Math.max(0, deadline - Date.now());
74
+ const work = Promise.resolve(stop(remaining));
75
+ await Promise.race([
76
+ work,
77
+ new Promise<never>((_resolve, reject) => {
78
+ timer = setTimeout(
79
+ () => reject(new DOMException('@zmdb/jobs: shutdown deadline exceeded', 'TimeoutError')),
80
+ remaining,
81
+ );
82
+ }),
83
+ ]);
84
+ } catch (error) {
85
+ errors.push(error);
86
+ } finally {
87
+ clearTimeout(timer);
88
+ }
89
+ }
90
+ if (errors.length === 0) return;
91
+ if (errors.length === 1) throw errors[0];
92
+ throw new AggregateError(errors, '@zmdb/jobs: background work shutdown failed');
93
+ }
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ // @zmdb/jobs — queues, workers, scheduling and app lifecycle integration.
2
+
3
+ export { jobsExtension } from './extension.js';
4
+ export { createQueue, createWorker } from './queues/index.js';
5
+ export { Cron, Interval, createScheduler, schedulesOf } from './schedule/index.js';
6
+ export type {
7
+ AnyJobHandler,
8
+ Backoff,
9
+ Clock,
10
+ DeadJob,
11
+ DeadReason,
12
+ EnqueueOptions,
13
+ JobContext,
14
+ ClaimedJob,
15
+ JobCandidate,
16
+ JobEnqueue,
17
+ JobEnqueuer,
18
+ JobEnqueueResult,
19
+ JobSettlement,
20
+ JobStoreResource,
21
+ JobStoreMigration,
22
+ JobHandler,
23
+ JobOutcome,
24
+ JobStore,
25
+ Queue,
26
+ QueueOptions,
27
+ RetryPolicy,
28
+ RunReport,
29
+ Worker,
30
+ WorkerOptions,
31
+ } from './queues/index.js';
32
+ export type {
33
+ IntervalOptions,
34
+ LeaseStore,
35
+ ScheduleDef,
36
+ Scheduler,
37
+ SchedulerOptions,
38
+ SkippedRun,
39
+ TaskDecorator,
40
+ TaskOptions,
41
+ TaskRuns,
42
+ } from './schedule/index.js';