@nest-native/jobs 0.1.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.
Files changed (68) hide show
  1. package/README.md +115 -0
  2. package/dist/dialects/mysql/index.d.ts +2 -0
  3. package/dist/dialects/mysql/index.js +21 -0
  4. package/dist/dialects/mysql/index.js.map +1 -0
  5. package/dist/dialects/mysql/job-store.d.ts +35 -0
  6. package/dist/dialects/mysql/job-store.js +150 -0
  7. package/dist/dialects/mysql/job-store.js.map +1 -0
  8. package/dist/dialects/mysql/schema.d.ts +255 -0
  9. package/dist/dialects/mysql/schema.js +47 -0
  10. package/dist/dialects/mysql/schema.js.map +1 -0
  11. package/dist/dialects/postgres/index.d.ts +2 -0
  12. package/dist/dialects/postgres/index.js +21 -0
  13. package/dist/dialects/postgres/index.js.map +1 -0
  14. package/dist/dialects/postgres/job-store.d.ts +32 -0
  15. package/dist/dialects/postgres/job-store.js +145 -0
  16. package/dist/dialects/postgres/job-store.js.map +1 -0
  17. package/dist/dialects/postgres/schema.d.ts +262 -0
  18. package/dist/dialects/postgres/schema.js +37 -0
  19. package/dist/dialects/postgres/schema.js.map +1 -0
  20. package/dist/dialects/sqlite/index.d.ts +2 -0
  21. package/dist/dialects/sqlite/index.js +21 -0
  22. package/dist/dialects/sqlite/index.js.map +1 -0
  23. package/dist/dialects/sqlite/job-store.d.ts +32 -0
  24. package/dist/dialects/sqlite/job-store.js +155 -0
  25. package/dist/dialects/sqlite/job-store.js.map +1 -0
  26. package/dist/dialects/sqlite/schema.d.ts +279 -0
  27. package/dist/dialects/sqlite/schema.js +37 -0
  28. package/dist/dialects/sqlite/schema.js.map +1 -0
  29. package/dist/enqueue-input.d.ts +12 -0
  30. package/dist/enqueue-input.js +26 -0
  31. package/dist/enqueue-input.js.map +1 -0
  32. package/dist/errors.d.ts +18 -0
  33. package/dist/errors.js +33 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/index.d.ts +10 -0
  36. package/dist/index.js +30 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/interfaces.d.ts +125 -0
  39. package/dist/interfaces.js +10 -0
  40. package/dist/interfaces.js.map +1 -0
  41. package/dist/job-handler.decorator.d.ts +31 -0
  42. package/dist/job-handler.decorator.js +18 -0
  43. package/dist/job-handler.decorator.js.map +1 -0
  44. package/dist/jobs-claimer.service.d.ts +37 -0
  45. package/dist/jobs-claimer.service.js +121 -0
  46. package/dist/jobs-claimer.service.js.map +1 -0
  47. package/dist/jobs-handler.explorer.d.ts +24 -0
  48. package/dist/jobs-handler.explorer.js +67 -0
  49. package/dist/jobs-handler.explorer.js.map +1 -0
  50. package/dist/jobs-worker.d.ts +21 -0
  51. package/dist/jobs-worker.js +40 -0
  52. package/dist/jobs-worker.js.map +1 -0
  53. package/dist/jobs.module.d.ts +19 -0
  54. package/dist/jobs.module.js +49 -0
  55. package/dist/jobs.module.js.map +1 -0
  56. package/dist/jobs.service.d.ts +25 -0
  57. package/dist/jobs.service.js +55 -0
  58. package/dist/jobs.service.js.map +1 -0
  59. package/dist/testing/harness.d.ts +61 -0
  60. package/dist/testing/harness.js +86 -0
  61. package/dist/testing/harness.js.map +1 -0
  62. package/dist/testing/index.d.ts +1 -0
  63. package/dist/testing/index.js +19 -0
  64. package/dist/testing/index.js.map +1 -0
  65. package/dist/tokens.d.ts +10 -0
  66. package/dist/tokens.js +15 -0
  67. package/dist/tokens.js.map +1 -0
  68. package/package.json +107 -0
@@ -0,0 +1,125 @@
1
+ import type { ModuleMetadata } from '@nestjs/common';
2
+ export declare const JOB_STATUSES: readonly ["pending", "processing", "completed", "failed"];
3
+ export type JobStatus = (typeof JOB_STATUSES)[number];
4
+ /**
5
+ * The dialect-agnostic shape of a job row as the engine reasons about it. All
6
+ * three dialect stores map their Drizzle rows to this shape, so the claimer
7
+ * never sees a dialect-specific type. Timestamps are ISO-8601 strings on every
8
+ * dialect (they compare lexicographically, which the claimer's due-time query
9
+ * relies on).
10
+ */
11
+ export interface JobRow {
12
+ id: string;
13
+ name: string;
14
+ payload: Record<string, unknown>;
15
+ status: JobStatus;
16
+ attempts: number;
17
+ maxAttempts: number;
18
+ /**
19
+ * The active-dedup key. Uniqueness is enforced by a FULL unique index on
20
+ * `(name, unique_key)` on every dialect, and terminal transitions
21
+ * (`completed`/`failed`) clear it to `NULL` — so "unique among ACTIVE jobs"
22
+ * holds everywhere without partial indexes. `NULL` keys never collide.
23
+ */
24
+ uniqueKey: string | null;
25
+ /** Claim priority — higher runs first among due jobs. Default 0. */
26
+ priority: number;
27
+ availableAt: string;
28
+ claimedAt: string | null;
29
+ claimedBy: string | null;
30
+ processedAt: string | null;
31
+ lastError: string | null;
32
+ createdAt: string;
33
+ }
34
+ /**
35
+ * What a caller supplies to enqueue a job.
36
+ *
37
+ * `runAt` and `delayMs` are **mutually exclusive** — supplying both throws.
38
+ * Omitting both makes the job due immediately.
39
+ *
40
+ * `TPayload` keeps the payload **structural**: a value typed as a plain
41
+ * interface (which has no index signature, so it is not assignable to
42
+ * `Record<string, unknown>`) is accepted as-is — no `as unknown as
43
+ * Record<string, unknown>` at every call site. The stored row shape stays
44
+ * `Record<string, unknown>` (see {@link JobRow}); the dialect stores perform
45
+ * that widening internally, exactly once.
46
+ */
47
+ export interface EnqueueJobInput<TPayload extends object = Record<string, unknown>> {
48
+ name: string;
49
+ payload: TPayload;
50
+ /** Absolute due time. Mutually exclusive with `delayMs`. */
51
+ runAt?: Date;
52
+ /** Relative due time in milliseconds from now. Mutually exclusive with `runAt`. */
53
+ delayMs?: number;
54
+ maxAttempts?: number;
55
+ /**
56
+ * Dedup among ACTIVE jobs with the same `name`: while a `(name, uniqueKey)`
57
+ * job is pending/processing, enqueueing the same pair is a no-op that
58
+ * returns the EXISTING row. Terminal jobs release the key.
59
+ */
60
+ uniqueKey?: string;
61
+ /** Higher runs first among due jobs (default 0). */
62
+ priority?: number;
63
+ }
64
+ /** Fully-resolved runner configuration (defaults applied). */
65
+ export interface ResolvedRunnerConfig {
66
+ workerInstanceId: string;
67
+ stuckTimeoutMs: number;
68
+ batchSize: number;
69
+ baseBackoffMs: number;
70
+ maxBackoffMs: number;
71
+ }
72
+ export type RunnerConfig = Partial<ResolvedRunnerConfig>;
73
+ /** Execution context the claimer passes to a handler alongside the payload. */
74
+ export interface JobContext {
75
+ /** The job row id — the natural idempotency key for handler side effects. */
76
+ jobId: string;
77
+ /** The 1-based attempt number of THIS execution (first run → 1). */
78
+ attempt: number;
79
+ }
80
+ /**
81
+ * The transactional persistence seam for the queue. Each implementation is
82
+ * dialect-specific and owns its Drizzle table + the sync/async query execution;
83
+ * `db` is passed per call (the tx-scoped instance for {@link enqueue}, the base
84
+ * instance for the rest) and is intentionally opaque (`unknown`) to the engine.
85
+ *
86
+ * `enqueue` returns the store's native shape — the **sqlite** store returns a
87
+ * synchronous `JobRow` (so it composes inside a synchronous `@Transactional`
88
+ * body); the **postgres** and **mysql** stores return a `Promise`. On a
89
+ * `(name, uniqueKey)` unique violation it returns the EXISTING active row
90
+ * (dedup no-op). It accepts `EnqueueJobInput<object>` so any structurally-typed
91
+ * payload flows through; the store widens the payload internally.
92
+ *
93
+ * `claimBatch` opens its own transaction, claims due `pending` jobs plus
94
+ * `processing` jobs stuck past `stuckTimeoutMs`, ordered by `priority DESC,
95
+ * available_at ASC`, and marks them `processing`.
96
+ */
97
+ export interface JobStore {
98
+ enqueue(db: unknown, input: EnqueueJobInput<object>): JobRow | Promise<JobRow>;
99
+ claimBatch(db: unknown, cfg: ResolvedRunnerConfig): Promise<JobRow[]>;
100
+ /** Terminal: sets `completed` and clears `uniqueKey` (releases the key). */
101
+ markCompleted(db: unknown, id: string): Promise<void>;
102
+ /** Re-arms the job: `pending`, attempts+1, due in `delayMs`. Keeps `uniqueKey`. */
103
+ retry(db: unknown, id: string, delayMs: number, lastError?: string): Promise<void>;
104
+ /** Terminal: sets `failed`, attempts+1, and clears `uniqueKey`. */
105
+ markFailed(db: unknown, id: string, reason: string): Promise<void>;
106
+ }
107
+ /** Options for {@link JobsModule.forRoot}. */
108
+ export interface JobsModuleOptions {
109
+ /**
110
+ * Token of the base (non-transactional) Drizzle instance — the same instance
111
+ * the `@nestjs-cls/transactional` Drizzle adapter is configured with. The
112
+ * claimer uses it to open its own claim transaction.
113
+ */
114
+ drizzleInstanceToken: symbol | string;
115
+ /** The dialect-specific job store. */
116
+ store: JobStore;
117
+ /**
118
+ * Modules that provide (and export) the `drizzleInstanceToken`. Required when
119
+ * that token is not registered by a global module — `JobsModule` imports
120
+ * these so it can resolve the Drizzle instance.
121
+ */
122
+ imports?: ModuleMetadata['imports'];
123
+ /** Register the module globally (default: true). */
124
+ isGlobal?: boolean;
125
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JOB_STATUSES = void 0;
4
+ exports.JOB_STATUSES = [
5
+ 'pending',
6
+ 'processing',
7
+ 'completed',
8
+ 'failed',
9
+ ];
10
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../interfaces.ts"],"names":[],"mappings":";;;AAEa,QAAA,YAAY,GAAG;IAC1B,SAAS;IACT,YAAY;IACZ,WAAW;IACX,QAAQ;CACA,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { type CustomDecorator } from '@nestjs/common';
2
+ import type { JobContext } from './interfaces';
3
+ /** Metadata key carrying the job name a `@JobHandler` class handles. */
4
+ export declare const JOB_HANDLER_NAME: unique symbol;
5
+ /**
6
+ * The contract a `@JobHandler(name)` class fulfils. Handlers run in the
7
+ * claimer's poll loop, OUTSIDE any business transaction, and delivery is
8
+ * at-least-once — make them idempotent or key side effects on `ctx.jobId`.
9
+ * Throw {@link RetryableError} / {@link PermanentError} to steer retries; any
10
+ * other throw retries with backoff until `maxAttempts`, then fails.
11
+ *
12
+ * The interface and the decorator below share the name deliberately (one lives
13
+ * in the type space, the other in the value space), so a handler reads:
14
+ *
15
+ * ```ts
16
+ * @JobHandler('email.welcome')
17
+ * @Injectable()
18
+ * export class WelcomeEmailHandler implements JobHandler { ... }
19
+ * ```
20
+ */
21
+ export interface JobHandler {
22
+ handle(payload: Record<string, unknown>, ctx: JobContext): void | Promise<void>;
23
+ }
24
+ /**
25
+ * Marks a provider class as the handler for jobs enqueued under `name`.
26
+ *
27
+ * Register the class as a provider in any module; {@link JobsHandlerExplorer}
28
+ * discovers it at application bootstrap. Exactly one handler per name —
29
+ * duplicates throw at startup.
30
+ */
31
+ export declare function JobHandler(name: string): CustomDecorator<symbol>;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JOB_HANDLER_NAME = void 0;
4
+ exports.JobHandler = JobHandler;
5
+ const common_1 = require("@nestjs/common");
6
+ /** Metadata key carrying the job name a `@JobHandler` class handles. */
7
+ exports.JOB_HANDLER_NAME = Symbol.for('@nest-native/jobs:job-handler-name');
8
+ /**
9
+ * Marks a provider class as the handler for jobs enqueued under `name`.
10
+ *
11
+ * Register the class as a provider in any module; {@link JobsHandlerExplorer}
12
+ * discovers it at application bootstrap. Exactly one handler per name —
13
+ * duplicates throw at startup.
14
+ */
15
+ function JobHandler(name) {
16
+ return (0, common_1.SetMetadata)(exports.JOB_HANDLER_NAME, name);
17
+ }
18
+ //# sourceMappingURL=job-handler.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job-handler.decorator.js","sourceRoot":"","sources":["../job-handler.decorator.ts"],"names":[],"mappings":";;;AAiCA,gCAEC;AAnCD,2CAAmE;AAGnE,wEAAwE;AAC3D,QAAA,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AAsBjF;;;;;;GAMG;AACH,SAAgB,UAAU,CAAC,IAAY;IACrC,OAAO,IAAA,oBAAW,EAAC,wBAAgB,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { JobStore, ResolvedRunnerConfig, RunnerConfig } from './interfaces';
2
+ import { JobsHandlerExplorer } from './jobs-handler.explorer';
3
+ export declare const DEFAULT_RUNNER_CONFIG: ResolvedRunnerConfig;
4
+ export interface TickReport {
5
+ claimed: number;
6
+ completed: number;
7
+ retried: number;
8
+ failed: number;
9
+ }
10
+ /**
11
+ * Executes committed jobs. `tick()` claims a batch of due jobs (the store opens
12
+ * its own transaction; priority first, oldest due first, reclaiming jobs stuck
13
+ * in `processing`), dispatches each to the `@JobHandler` registered for its
14
+ * name, and records the outcome:
15
+ *
16
+ * - handler returns → `markCompleted` (key released)
17
+ * - {@link PermanentError} → `markFailed` immediately (no point retrying)
18
+ * - {@link RetryableError} → `retry`, honouring its `delayMs` if given
19
+ * - any other throw → `retry` with jittered exponential backoff
20
+ * until `maxAttempts`, then `markFailed`
21
+ * - no handler registered → `PermanentError` → `markFailed`
22
+ *
23
+ * Runs in a background worker (see `runWorkerLoop`) — never inside a business
24
+ * transaction — so it freely awaits the store and the handlers.
25
+ */
26
+ export declare class JobsClaimer {
27
+ private readonly db;
28
+ private readonly store;
29
+ private readonly explorer;
30
+ private readonly logger;
31
+ constructor(db: unknown, store: JobStore, explorer: JobsHandlerExplorer);
32
+ tick(overrides?: RunnerConfig): Promise<TickReport>;
33
+ private processOne;
34
+ private onHandlerError;
35
+ private fail;
36
+ private backoff;
37
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var JobsClaimer_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.JobsClaimer = exports.DEFAULT_RUNNER_CONFIG = void 0;
17
+ const node_os_1 = require("node:os");
18
+ const common_1 = require("@nestjs/common");
19
+ const errors_1 = require("./errors");
20
+ const jobs_handler_explorer_1 = require("./jobs-handler.explorer");
21
+ const tokens_1 = require("./tokens");
22
+ exports.DEFAULT_RUNNER_CONFIG = {
23
+ workerInstanceId: `${(0, node_os_1.hostname)()}-${process.pid}`,
24
+ stuckTimeoutMs: 60_000,
25
+ batchSize: 32,
26
+ baseBackoffMs: 1_000,
27
+ maxBackoffMs: 60_000,
28
+ };
29
+ /**
30
+ * Executes committed jobs. `tick()` claims a batch of due jobs (the store opens
31
+ * its own transaction; priority first, oldest due first, reclaiming jobs stuck
32
+ * in `processing`), dispatches each to the `@JobHandler` registered for its
33
+ * name, and records the outcome:
34
+ *
35
+ * - handler returns → `markCompleted` (key released)
36
+ * - {@link PermanentError} → `markFailed` immediately (no point retrying)
37
+ * - {@link RetryableError} → `retry`, honouring its `delayMs` if given
38
+ * - any other throw → `retry` with jittered exponential backoff
39
+ * until `maxAttempts`, then `markFailed`
40
+ * - no handler registered → `PermanentError` → `markFailed`
41
+ *
42
+ * Runs in a background worker (see `runWorkerLoop`) — never inside a business
43
+ * transaction — so it freely awaits the store and the handlers.
44
+ */
45
+ let JobsClaimer = JobsClaimer_1 = class JobsClaimer {
46
+ constructor(db, store, explorer) {
47
+ this.db = db;
48
+ this.store = store;
49
+ this.explorer = explorer;
50
+ this.logger = new common_1.Logger(JobsClaimer_1.name);
51
+ }
52
+ async tick(overrides = {}) {
53
+ const cfg = { ...exports.DEFAULT_RUNNER_CONFIG, ...overrides };
54
+ const claimed = await this.store.claimBatch(this.db, cfg);
55
+ const report = {
56
+ claimed: claimed.length,
57
+ completed: 0,
58
+ retried: 0,
59
+ failed: 0,
60
+ };
61
+ for (const job of claimed) {
62
+ const outcome = await this.processOne(job, cfg);
63
+ report[outcome] += 1;
64
+ }
65
+ return report;
66
+ }
67
+ async processOne(job, cfg) {
68
+ try {
69
+ const handler = this.explorer.get(job.name);
70
+ if (!handler) {
71
+ throw new errors_1.PermanentError(`No @JobHandler registered for job "${job.name}"`);
72
+ }
73
+ await handler.handle(job.payload, {
74
+ jobId: job.id,
75
+ attempt: job.attempts + 1,
76
+ });
77
+ await this.store.markCompleted(this.db, job.id);
78
+ return 'completed';
79
+ }
80
+ catch (error) {
81
+ return this.onHandlerError(job, cfg, error);
82
+ }
83
+ }
84
+ async onHandlerError(job, cfg, error) {
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ // Permanent: retrying can never succeed — fail now instead of burning attempts.
87
+ if (error instanceof errors_1.PermanentError) {
88
+ return this.fail(job, message);
89
+ }
90
+ // Retryable: schedule another attempt, honouring a handler-supplied delay.
91
+ if (error instanceof errors_1.RetryableError) {
92
+ const delay = error.delayMs ?? this.backoff(job.attempts, cfg);
93
+ await this.store.retry(this.db, job.id, delay, message);
94
+ return 'retried';
95
+ }
96
+ // Anything else: retry with backoff until maxAttempts, then fail.
97
+ if (job.attempts + 1 >= job.maxAttempts) {
98
+ return this.fail(job, message);
99
+ }
100
+ await this.store.retry(this.db, job.id, this.backoff(job.attempts, cfg), message);
101
+ return 'retried';
102
+ }
103
+ async fail(job, reason) {
104
+ this.logger.warn(`job ${job.id} ("${job.name}") failed: ${reason}`);
105
+ await this.store.markFailed(this.db, job.id, reason);
106
+ return 'failed';
107
+ }
108
+ backoff(attempts, cfg) {
109
+ const base = cfg.baseBackoffMs * 2 ** attempts;
110
+ const capped = Math.min(base, cfg.maxBackoffMs);
111
+ return capped + Math.floor(Math.random() * cfg.baseBackoffMs);
112
+ }
113
+ };
114
+ exports.JobsClaimer = JobsClaimer;
115
+ exports.JobsClaimer = JobsClaimer = JobsClaimer_1 = __decorate([
116
+ (0, common_1.Injectable)(),
117
+ __param(0, (0, common_1.Inject)(tokens_1.JOBS_DRIZZLE)),
118
+ __param(1, (0, common_1.Inject)(tokens_1.JOBS_STORE)),
119
+ __metadata("design:paramtypes", [Object, Object, jobs_handler_explorer_1.JobsHandlerExplorer])
120
+ ], JobsClaimer);
121
+ //# sourceMappingURL=jobs-claimer.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs-claimer.service.js","sourceRoot":"","sources":["../jobs-claimer.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type OnApplicationBootstrap } from '@nestjs/common';
2
+ import { DiscoveryService, Reflector } from '@nestjs/core';
3
+ import { type JobHandler } from './job-handler.decorator';
4
+ /**
5
+ * Discovers `@JobHandler(name)` provider classes in the Nest container and
6
+ * builds the name → handler-instance registry the claimer dispatches through.
7
+ *
8
+ * Discovery happens once, at application bootstrap (so every module has
9
+ * finished instantiating its providers). Exactly one handler per job name:
10
+ * a second class claiming an already-registered name throws, failing the
11
+ * application at startup instead of silently shadowing a handler at runtime.
12
+ */
13
+ export declare class JobsHandlerExplorer implements OnApplicationBootstrap {
14
+ private readonly discovery;
15
+ private readonly reflector;
16
+ private readonly registry;
17
+ constructor(discovery: DiscoveryService, reflector: Reflector);
18
+ onApplicationBootstrap(): void;
19
+ /** The handler registered for `name`, if any. */
20
+ get(name: string): JobHandler | undefined;
21
+ /** Every registered job name (useful for diagnostics and tests). */
22
+ names(): readonly string[];
23
+ private register;
24
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.JobsHandlerExplorer = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const core_1 = require("@nestjs/core");
15
+ const job_handler_decorator_1 = require("./job-handler.decorator");
16
+ /**
17
+ * Discovers `@JobHandler(name)` provider classes in the Nest container and
18
+ * builds the name → handler-instance registry the claimer dispatches through.
19
+ *
20
+ * Discovery happens once, at application bootstrap (so every module has
21
+ * finished instantiating its providers). Exactly one handler per job name:
22
+ * a second class claiming an already-registered name throws, failing the
23
+ * application at startup instead of silently shadowing a handler at runtime.
24
+ */
25
+ let JobsHandlerExplorer = class JobsHandlerExplorer {
26
+ constructor(discovery, reflector) {
27
+ this.discovery = discovery;
28
+ this.reflector = reflector;
29
+ this.registry = new Map();
30
+ }
31
+ onApplicationBootstrap() {
32
+ for (const wrapper of this.discovery.getProviders()) {
33
+ this.register(wrapper);
34
+ }
35
+ }
36
+ /** The handler registered for `name`, if any. */
37
+ get(name) {
38
+ return this.registry.get(name);
39
+ }
40
+ /** Every registered job name (useful for diagnostics and tests). */
41
+ names() {
42
+ return [...this.registry.keys()];
43
+ }
44
+ register(wrapper) {
45
+ const { instance, metatype } = wrapper;
46
+ if (!instance || !metatype) {
47
+ return;
48
+ }
49
+ const name = this.reflector.get(job_handler_decorator_1.JOB_HANDLER_NAME, metatype);
50
+ if (name === undefined) {
51
+ return;
52
+ }
53
+ const existing = this.registry.get(name);
54
+ if (existing && existing !== instance) {
55
+ throw new Error(`Duplicate @JobHandler("${name}"): ${metatype.name} conflicts ` +
56
+ 'with an already-registered handler. Job names must map to exactly one handler.');
57
+ }
58
+ this.registry.set(name, instance);
59
+ }
60
+ };
61
+ exports.JobsHandlerExplorer = JobsHandlerExplorer;
62
+ exports.JobsHandlerExplorer = JobsHandlerExplorer = __decorate([
63
+ (0, common_1.Injectable)(),
64
+ __metadata("design:paramtypes", [core_1.DiscoveryService,
65
+ core_1.Reflector])
66
+ ], JobsHandlerExplorer);
67
+ //# sourceMappingURL=jobs-handler.explorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs-handler.explorer.js","sourceRoot":"","sources":["../jobs-handler.explorer.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAoF;AACpF,uCAA2D;AAC3D,mEAA4E;AAW5E;;;;;;;;GAQG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAG9B,YACmB,SAA2B,EAC3B,SAAoB;QADpB,cAAS,GAAT,SAAS,CAAkB;QAC3B,cAAS,GAAT,SAAS,CAAW;QAJtB,aAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;IAKvD,CAAC;IAEJ,sBAAsB;QACpB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,YAAY,EAA2B,EAAE,CAAC;YAC7E,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,oEAAoE;IACpE,KAAK;QACH,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;IAEO,QAAQ,CAAC,OAA4B;QAC3C,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;QACvC,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAC7B,wCAAgB,EAChB,QAAgB,CACjB,CAAC;QACF,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,QAAQ,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,0BAA0B,IAAI,OAAQ,QAAiB,CAAC,IAAI,aAAa;gBACvE,gFAAgF,CACnF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,QAAsB,CAAC,CAAC;IAClD,CAAC;CACF,CAAA;AA7CY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAKmB,uBAAgB;QAChB,gBAAS;GAL5B,mBAAmB,CA6C/B"}
@@ -0,0 +1,21 @@
1
+ import type { RunnerConfig } from './interfaces';
2
+ import type { JobsClaimer, TickReport } from './jobs-claimer.service';
3
+ export interface WorkerLoopOptions {
4
+ /** Delay between ticks when the last tick claimed nothing (default 2000ms). */
5
+ pollIntervalMs?: number;
6
+ /** Runner overrides applied to every tick. */
7
+ runner?: RunnerConfig;
8
+ /** Abort to stop the loop. */
9
+ signal?: AbortSignal;
10
+ /** Called after each successful tick. */
11
+ onTick?: (report: TickReport) => void;
12
+ /** Called when a tick throws — the loop reports and continues. */
13
+ onError?: (error: unknown) => void;
14
+ }
15
+ /**
16
+ * Runs `claimer.tick()` in a loop until `signal` aborts. When a tick claims a
17
+ * batch it loops immediately to drain the backlog; when it claims nothing it
18
+ * waits `pollIntervalMs`. A throwing tick is reported via `onError` and the loop
19
+ * continues after the same wait.
20
+ */
21
+ export declare function runWorkerLoop(claimer: JobsClaimer, options?: WorkerLoopOptions): Promise<void>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runWorkerLoop = runWorkerLoop;
4
+ /**
5
+ * Runs `claimer.tick()` in a loop until `signal` aborts. When a tick claims a
6
+ * batch it loops immediately to drain the backlog; when it claims nothing it
7
+ * waits `pollIntervalMs`. A throwing tick is reported via `onError` and the loop
8
+ * continues after the same wait.
9
+ */
10
+ async function runWorkerLoop(claimer, options = {}) {
11
+ const pollIntervalMs = options.pollIntervalMs ?? 2_000;
12
+ const { signal } = options;
13
+ while (!signal?.aborted) {
14
+ try {
15
+ const report = await claimer.tick(options.runner);
16
+ options.onTick?.(report);
17
+ if (report.claimed === 0) {
18
+ await sleep(pollIntervalMs, signal);
19
+ }
20
+ }
21
+ catch (error) {
22
+ options.onError?.(error);
23
+ await sleep(pollIntervalMs, signal);
24
+ }
25
+ }
26
+ }
27
+ function sleep(ms, signal) {
28
+ return new Promise((resolve) => {
29
+ if (signal?.aborted) {
30
+ resolve();
31
+ return;
32
+ }
33
+ const timer = setTimeout(resolve, ms);
34
+ signal?.addEventListener('abort', () => {
35
+ clearTimeout(timer);
36
+ resolve();
37
+ }, { once: true });
38
+ });
39
+ }
40
+ //# sourceMappingURL=jobs-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs-worker.js","sourceRoot":"","sources":["../jobs-worker.ts"],"names":[],"mappings":";;AAsBA,sCAkBC;AAxBD;;;;;GAKG;AACI,KAAK,UAAU,aAAa,CACjC,OAAoB,EACpB,UAA6B,EAAE;IAE/B,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC;IACvD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3B,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAClD,OAAO,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;YACzB,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;gBACzB,MAAM,KAAK,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YACzB,MAAM,KAAK,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC7C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACtC,MAAM,EAAE,gBAAgB,CACtB,OAAO,EACP,GAAG,EAAE;YACH,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,EAAE,CAAC;QACZ,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACf,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type DynamicModule, type InjectionToken, type ModuleMetadata, type OptionalFactoryDependency } from '@nestjs/common';
2
+ import type { JobsModuleOptions, JobStore } from './interfaces';
3
+ /**
4
+ * Async configuration. The Drizzle token is static (a DI token is known at
5
+ * module-definition time); the store is built by a factory so it can inject
6
+ * runtime providers (e.g. configuration).
7
+ */
8
+ export interface JobsModuleAsyncOptions {
9
+ isGlobal?: boolean;
10
+ /** Token of the base (non-transactional) Drizzle instance. */
11
+ drizzleInstanceToken: symbol | string;
12
+ imports?: ModuleMetadata['imports'];
13
+ inject?: (InjectionToken | OptionalFactoryDependency)[];
14
+ useStore: (...args: any[]) => JobStore | Promise<JobStore>;
15
+ }
16
+ export declare class JobsModule {
17
+ static forRoot(options: JobsModuleOptions): DynamicModule;
18
+ static forRootAsync(options: JobsModuleAsyncOptions): DynamicModule;
19
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.JobsModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const core_1 = require("@nestjs/core");
12
+ const jobs_claimer_service_1 = require("./jobs-claimer.service");
13
+ const jobs_handler_explorer_1 = require("./jobs-handler.explorer");
14
+ const jobs_service_1 = require("./jobs.service");
15
+ const tokens_1 = require("./tokens");
16
+ let JobsModule = class JobsModule {
17
+ static forRoot(options) {
18
+ return assemble(options.isGlobal ?? true, options.imports ?? [], [
19
+ { provide: tokens_1.JOBS_OPTIONS, useValue: options },
20
+ { provide: tokens_1.JOBS_STORE, useValue: options.store },
21
+ { provide: tokens_1.JOBS_DRIZZLE, useExisting: options.drizzleInstanceToken },
22
+ ]);
23
+ }
24
+ static forRootAsync(options) {
25
+ return assemble(options.isGlobal ?? true, options.imports ?? [], [
26
+ {
27
+ provide: tokens_1.JOBS_STORE,
28
+ useFactory: options.useStore,
29
+ inject: options.inject ?? [],
30
+ },
31
+ { provide: tokens_1.JOBS_DRIZZLE, useExisting: options.drizzleInstanceToken },
32
+ ]);
33
+ }
34
+ };
35
+ exports.JobsModule = JobsModule;
36
+ exports.JobsModule = JobsModule = __decorate([
37
+ (0, common_1.Module)({})
38
+ ], JobsModule);
39
+ function assemble(global, imports, base) {
40
+ return {
41
+ module: JobsModule,
42
+ global,
43
+ // DiscoveryModule powers the @JobHandler scan at bootstrap.
44
+ imports: [core_1.DiscoveryModule, ...imports],
45
+ providers: [...base, jobs_service_1.JobsService, jobs_claimer_service_1.JobsClaimer, jobs_handler_explorer_1.JobsHandlerExplorer],
46
+ exports: [jobs_service_1.JobsService, jobs_claimer_service_1.JobsClaimer, jobs_handler_explorer_1.JobsHandlerExplorer],
47
+ };
48
+ }
49
+ //# sourceMappingURL=jobs.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.module.js","sourceRoot":"","sources":["../jobs.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAOwB;AACxB,uCAA+C;AAE/C,iEAAqD;AACrD,mEAA8D;AAC9D,iDAA6C;AAC7C,qCAAkE;AAoB3D,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,MAAM,CAAC,OAAO,CAAC,OAA0B;QACvC,OAAO,QAAQ,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE;YAC/D,EAAE,OAAO,EAAE,qBAAY,EAAE,QAAQ,EAAE,OAAO,EAAE;YAC5C,EAAE,OAAO,EAAE,mBAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE;YAChD,EAAE,OAAO,EAAE,qBAAY,EAAE,WAAW,EAAE,OAAO,CAAC,oBAAoB,EAAE;SACrE,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAA+B;QACjD,OAAO,QAAQ,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE;YAC/D;gBACE,OAAO,EAAE,mBAAU;gBACnB,UAAU,EAAE,OAAO,CAAC,QAAQ;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;aAC7B;YACD,EAAE,OAAO,EAAE,qBAAY,EAAE,WAAW,EAAE,OAAO,CAAC,oBAAoB,EAAE;SACrE,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAnBY,gCAAU;qBAAV,UAAU;IADtB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,UAAU,CAmBtB;AAED,SAAS,QAAQ,CACf,MAAe,EACf,OAA+C,EAC/C,IAAgB;IAEhB,OAAO;QACL,MAAM,EAAE,UAAU;QAClB,MAAM;QACN,4DAA4D;QAC5D,OAAO,EAAE,CAAC,sBAAe,EAAE,GAAG,OAAO,CAAC;QACtC,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,0BAAW,EAAE,kCAAW,EAAE,2CAAmB,CAAC;QACnE,OAAO,EAAE,CAAC,0BAAW,EAAE,kCAAW,EAAE,2CAAmB,CAAC;KACzD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { EnqueueJobInput, JobStore } from './interfaces';
2
+ /**
3
+ * Enqueues jobs **inside the caller's business transaction** — the job row
4
+ * commits atomically with your writes. Inject it into a `@Transactional()`
5
+ * service and call `enqueue` alongside your business writes; if the
6
+ * transaction rolls back, the job was never enqueued.
7
+ *
8
+ * `enqueue` returns the store's native shape: the **sqlite** store returns a
9
+ * synchronous `JobRow` (call it without `await` inside a synchronous
10
+ * `@Transactional` body); the **postgres** and **mysql** stores return a
11
+ * `Promise` (await it). Type the service as `JobsService<typeof yourStore>` to get the
12
+ * exact shape. On a `(name, uniqueKey)` collision with an ACTIVE job it
13
+ * returns that existing row — a dedup no-op.
14
+ *
15
+ * Requires the host app to configure `@nestjs-cls/transactional` with the
16
+ * Drizzle adapter (`enableTransactionProxy: true`) — `@InjectTransaction()`
17
+ * resolves the transaction-scoped Drizzle instance from it (and falls back to
18
+ * the base instance outside any transaction).
19
+ */
20
+ export declare class JobsService<TStore extends JobStore = JobStore> {
21
+ private readonly db;
22
+ private readonly store;
23
+ constructor(db: unknown, store: TStore);
24
+ enqueue<TPayload extends object>(input: EnqueueJobInput<TPayload>): ReturnType<TStore['enqueue']>;
25
+ }