@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,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqliteJobStore = void 0;
4
+ exports.isSqliteUniqueViolation = isSqliteUniqueViolation;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const drizzle_orm_1 = require("drizzle-orm");
7
+ const enqueue_input_1 = require("../../enqueue-input");
8
+ const schema_1 = require("./schema");
9
+ /**
10
+ * better-sqlite3 surfaces a unique-constraint violation as a `SqliteError` with
11
+ * `code === 'SQLITE_CONSTRAINT_UNIQUE'`. Match on the code (stable across driver
12
+ * versions), not the message. Drizzle may wrap driver errors in a
13
+ * `DrizzleQueryError`, so the code may instead sit on `error.cause` — check both.
14
+ */
15
+ function isSqliteUniqueViolation(error) {
16
+ const code = 'SQLITE_CONSTRAINT_UNIQUE';
17
+ return hasCode(error, code) || hasCode(error?.cause, code);
18
+ }
19
+ function hasCode(error, code) {
20
+ return (typeof error === 'object' &&
21
+ error !== null &&
22
+ 'code' in error &&
23
+ error.code === code);
24
+ }
25
+ /**
26
+ * SQLite (better-sqlite3) job store. Every method runs **synchronously** —
27
+ * `enqueue` returns the row directly so it composes inside a synchronous
28
+ * `@Transactional` body, and the rest wrap their synchronous result in a
29
+ * resolved Promise for the engine to await from outside the transaction.
30
+ *
31
+ * A `(name, unique_key)` unique violation on `enqueue` is the active-dedup
32
+ * no-op: the store returns the EXISTING active row instead of inserting.
33
+ */
34
+ class SqliteJobStore {
35
+ enqueue(db, input) {
36
+ const availableAt = (0, enqueue_input_1.resolveAvailableAt)(input).toISOString();
37
+ try {
38
+ return db
39
+ .insert(schema_1.jobs)
40
+ .values({
41
+ id: (0, node_crypto_1.randomUUID)(),
42
+ name: input.name,
43
+ // The one place the structural input payload widens to the stored shape.
44
+ payload: input.payload,
45
+ status: 'pending',
46
+ maxAttempts: input.maxAttempts ?? 10,
47
+ uniqueKey: input.uniqueKey ?? null,
48
+ priority: input.priority ?? 0,
49
+ availableAt,
50
+ createdAt: new Date().toISOString(),
51
+ })
52
+ .returning()
53
+ .get();
54
+ }
55
+ catch (error) {
56
+ const existing = this.findActiveDuplicate(db, input, error);
57
+ if (!existing)
58
+ throw error;
59
+ return existing;
60
+ }
61
+ }
62
+ /**
63
+ * On a unique violation with a `uniqueKey`, the ACTIVE row that owns the
64
+ * `(name, uniqueKey)` pair — terminal rows cannot own it (their key is
65
+ * cleared to NULL). Any other error, or a vanished row (the owner completed
66
+ * between the insert and this read), returns undefined so the caller
67
+ * rethrows the original error.
68
+ */
69
+ findActiveDuplicate(db, input, error) {
70
+ if (!isSqliteUniqueViolation(error) || input.uniqueKey == null) {
71
+ return undefined;
72
+ }
73
+ return db
74
+ .select()
75
+ .from(schema_1.jobs)
76
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.jobs.name, input.name), (0, drizzle_orm_1.eq)(schema_1.jobs.uniqueKey, input.uniqueKey)))
77
+ .get();
78
+ }
79
+ claimBatch(db, cfg) {
80
+ const now = new Date();
81
+ const nowIso = now.toISOString();
82
+ const stuckCutoff = new Date(now.getTime() - cfg.stuckTimeoutMs).toISOString();
83
+ const rows = db.transaction((tx) => {
84
+ const candidates = tx
85
+ .select({ id: schema_1.jobs.id })
86
+ .from(schema_1.jobs)
87
+ .where((0, drizzle_orm_1.or)((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.jobs.status, 'pending'), (0, drizzle_orm_1.lte)(schema_1.jobs.availableAt, nowIso)), (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.jobs.status, 'processing'), (0, drizzle_orm_1.lte)(schema_1.jobs.claimedAt, stuckCutoff))))
88
+ .orderBy((0, drizzle_orm_1.desc)(schema_1.jobs.priority), (0, drizzle_orm_1.asc)(schema_1.jobs.availableAt))
89
+ .limit(cfg.batchSize)
90
+ .all();
91
+ if (candidates.length === 0)
92
+ return [];
93
+ const ids = candidates.map((c) => c.id);
94
+ tx.update(schema_1.jobs)
95
+ .set({ status: 'processing', claimedAt: nowIso, claimedBy: cfg.workerInstanceId })
96
+ .where((0, drizzle_orm_1.inArray)(schema_1.jobs.id, ids))
97
+ .run();
98
+ return tx
99
+ .select()
100
+ .from(schema_1.jobs)
101
+ .where((0, drizzle_orm_1.inArray)(schema_1.jobs.id, ids))
102
+ .orderBy((0, drizzle_orm_1.desc)(schema_1.jobs.priority), (0, drizzle_orm_1.asc)(schema_1.jobs.availableAt))
103
+ .all();
104
+ });
105
+ return Promise.resolve(rows);
106
+ }
107
+ markCompleted(db, id) {
108
+ db
109
+ .update(schema_1.jobs)
110
+ .set({
111
+ status: 'completed',
112
+ processedAt: new Date().toISOString(),
113
+ lastError: null,
114
+ // Terminal → release the active-dedup key.
115
+ uniqueKey: null,
116
+ })
117
+ .where((0, drizzle_orm_1.eq)(schema_1.jobs.id, id))
118
+ .run();
119
+ return Promise.resolve();
120
+ }
121
+ retry(db, id, delayMs, lastError) {
122
+ const nextAvailable = new Date(Date.now() + delayMs).toISOString();
123
+ db
124
+ .update(schema_1.jobs)
125
+ .set({
126
+ status: 'pending',
127
+ attempts: (0, drizzle_orm_1.sql) `${schema_1.jobs.attempts} + 1`,
128
+ availableAt: nextAvailable,
129
+ claimedAt: null,
130
+ claimedBy: null,
131
+ lastError: lastError ?? null,
132
+ // Still active → the uniqueKey stays claimed.
133
+ })
134
+ .where((0, drizzle_orm_1.eq)(schema_1.jobs.id, id))
135
+ .run();
136
+ return Promise.resolve();
137
+ }
138
+ markFailed(db, id, reason) {
139
+ db
140
+ .update(schema_1.jobs)
141
+ .set({
142
+ status: 'failed',
143
+ attempts: (0, drizzle_orm_1.sql) `${schema_1.jobs.attempts} + 1`,
144
+ lastError: reason,
145
+ processedAt: new Date().toISOString(),
146
+ // Terminal → release the active-dedup key.
147
+ uniqueKey: null,
148
+ })
149
+ .where((0, drizzle_orm_1.eq)(schema_1.jobs.id, id))
150
+ .run();
151
+ return Promise.resolve();
152
+ }
153
+ }
154
+ exports.SqliteJobStore = SqliteJobStore;
155
+ //# sourceMappingURL=job-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job-store.js","sourceRoot":"","sources":["../../../dialects/sqlite/job-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,279 @@
1
+ /**
2
+ * SQLite `jobs` table. Add it to your Drizzle schema and generate a migration
3
+ * with drizzle-kit.
4
+ *
5
+ * The **full** unique index on `(name, unique_key)` is the active-dedup
6
+ * primitive: SQL treats `NULL` as distinct in unique indexes, so jobs without
7
+ * a `uniqueKey` never collide, and — because terminal transitions clear
8
+ * `unique_key` to `NULL` — uniqueness only constrains ACTIVE jobs. The same
9
+ * full-index shape works on every dialect (no partial indexes needed). The
10
+ * `(status, available_at)` index serves the claimer's batch query.
11
+ */
12
+ export declare const jobs: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
13
+ name: "jobs";
14
+ schema: undefined;
15
+ columns: {
16
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
17
+ name: "id";
18
+ tableName: "jobs";
19
+ dataType: "string";
20
+ columnType: "SQLiteText";
21
+ data: string;
22
+ driverParam: string;
23
+ notNull: true;
24
+ hasDefault: false;
25
+ isPrimaryKey: true;
26
+ isAutoincrement: false;
27
+ hasRuntimeDefault: false;
28
+ enumValues: [string, ...string[]];
29
+ baseColumn: never;
30
+ identity: undefined;
31
+ generated: undefined;
32
+ }, {}, {
33
+ length: number | undefined;
34
+ }>;
35
+ name: import("drizzle-orm/sqlite-core").SQLiteColumn<{
36
+ name: "name";
37
+ tableName: "jobs";
38
+ dataType: "string";
39
+ columnType: "SQLiteText";
40
+ data: string;
41
+ driverParam: string;
42
+ notNull: true;
43
+ hasDefault: false;
44
+ isPrimaryKey: false;
45
+ isAutoincrement: false;
46
+ hasRuntimeDefault: false;
47
+ enumValues: [string, ...string[]];
48
+ baseColumn: never;
49
+ identity: undefined;
50
+ generated: undefined;
51
+ }, {}, {
52
+ length: number | undefined;
53
+ }>;
54
+ payload: import("drizzle-orm/sqlite-core").SQLiteColumn<{
55
+ name: "payload";
56
+ tableName: "jobs";
57
+ dataType: "json";
58
+ columnType: "SQLiteTextJson";
59
+ data: Record<string, unknown>;
60
+ driverParam: string;
61
+ notNull: true;
62
+ hasDefault: false;
63
+ isPrimaryKey: false;
64
+ isAutoincrement: false;
65
+ hasRuntimeDefault: false;
66
+ enumValues: undefined;
67
+ baseColumn: never;
68
+ identity: undefined;
69
+ generated: undefined;
70
+ }, {}, {
71
+ $type: Record<string, unknown>;
72
+ }>;
73
+ status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
74
+ name: "status";
75
+ tableName: "jobs";
76
+ dataType: "string";
77
+ columnType: "SQLiteText";
78
+ data: "pending" | "processing" | "completed" | "failed";
79
+ driverParam: string;
80
+ notNull: true;
81
+ hasDefault: false;
82
+ isPrimaryKey: false;
83
+ isAutoincrement: false;
84
+ hasRuntimeDefault: false;
85
+ enumValues: [string, ...string[]];
86
+ baseColumn: never;
87
+ identity: undefined;
88
+ generated: undefined;
89
+ }, {}, {
90
+ length: number | undefined;
91
+ $type: "pending" | "processing" | "completed" | "failed";
92
+ }>;
93
+ attempts: import("drizzle-orm/sqlite-core").SQLiteColumn<{
94
+ name: "attempts";
95
+ tableName: "jobs";
96
+ dataType: "number";
97
+ columnType: "SQLiteInteger";
98
+ data: number;
99
+ driverParam: number;
100
+ notNull: true;
101
+ hasDefault: true;
102
+ isPrimaryKey: false;
103
+ isAutoincrement: false;
104
+ hasRuntimeDefault: false;
105
+ enumValues: undefined;
106
+ baseColumn: never;
107
+ identity: undefined;
108
+ generated: undefined;
109
+ }, {}, {}>;
110
+ maxAttempts: import("drizzle-orm/sqlite-core").SQLiteColumn<{
111
+ name: "max_attempts";
112
+ tableName: "jobs";
113
+ dataType: "number";
114
+ columnType: "SQLiteInteger";
115
+ data: number;
116
+ driverParam: number;
117
+ notNull: true;
118
+ hasDefault: true;
119
+ isPrimaryKey: false;
120
+ isAutoincrement: false;
121
+ hasRuntimeDefault: false;
122
+ enumValues: undefined;
123
+ baseColumn: never;
124
+ identity: undefined;
125
+ generated: undefined;
126
+ }, {}, {}>;
127
+ uniqueKey: import("drizzle-orm/sqlite-core").SQLiteColumn<{
128
+ name: "unique_key";
129
+ tableName: "jobs";
130
+ dataType: "string";
131
+ columnType: "SQLiteText";
132
+ data: string;
133
+ driverParam: string;
134
+ notNull: false;
135
+ hasDefault: false;
136
+ isPrimaryKey: false;
137
+ isAutoincrement: false;
138
+ hasRuntimeDefault: false;
139
+ enumValues: [string, ...string[]];
140
+ baseColumn: never;
141
+ identity: undefined;
142
+ generated: undefined;
143
+ }, {}, {
144
+ length: number | undefined;
145
+ }>;
146
+ priority: import("drizzle-orm/sqlite-core").SQLiteColumn<{
147
+ name: "priority";
148
+ tableName: "jobs";
149
+ dataType: "number";
150
+ columnType: "SQLiteInteger";
151
+ data: number;
152
+ driverParam: number;
153
+ notNull: true;
154
+ hasDefault: true;
155
+ isPrimaryKey: false;
156
+ isAutoincrement: false;
157
+ hasRuntimeDefault: false;
158
+ enumValues: undefined;
159
+ baseColumn: never;
160
+ identity: undefined;
161
+ generated: undefined;
162
+ }, {}, {}>;
163
+ availableAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
164
+ name: "available_at";
165
+ tableName: "jobs";
166
+ dataType: "string";
167
+ columnType: "SQLiteText";
168
+ data: string;
169
+ driverParam: string;
170
+ notNull: true;
171
+ hasDefault: false;
172
+ isPrimaryKey: false;
173
+ isAutoincrement: false;
174
+ hasRuntimeDefault: false;
175
+ enumValues: [string, ...string[]];
176
+ baseColumn: never;
177
+ identity: undefined;
178
+ generated: undefined;
179
+ }, {}, {
180
+ length: number | undefined;
181
+ }>;
182
+ claimedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
183
+ name: "claimed_at";
184
+ tableName: "jobs";
185
+ dataType: "string";
186
+ columnType: "SQLiteText";
187
+ data: string;
188
+ driverParam: string;
189
+ notNull: false;
190
+ hasDefault: false;
191
+ isPrimaryKey: false;
192
+ isAutoincrement: false;
193
+ hasRuntimeDefault: false;
194
+ enumValues: [string, ...string[]];
195
+ baseColumn: never;
196
+ identity: undefined;
197
+ generated: undefined;
198
+ }, {}, {
199
+ length: number | undefined;
200
+ }>;
201
+ claimedBy: import("drizzle-orm/sqlite-core").SQLiteColumn<{
202
+ name: "claimed_by";
203
+ tableName: "jobs";
204
+ dataType: "string";
205
+ columnType: "SQLiteText";
206
+ data: string;
207
+ driverParam: string;
208
+ notNull: false;
209
+ hasDefault: false;
210
+ isPrimaryKey: false;
211
+ isAutoincrement: false;
212
+ hasRuntimeDefault: false;
213
+ enumValues: [string, ...string[]];
214
+ baseColumn: never;
215
+ identity: undefined;
216
+ generated: undefined;
217
+ }, {}, {
218
+ length: number | undefined;
219
+ }>;
220
+ processedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
221
+ name: "processed_at";
222
+ tableName: "jobs";
223
+ dataType: "string";
224
+ columnType: "SQLiteText";
225
+ data: string;
226
+ driverParam: string;
227
+ notNull: false;
228
+ hasDefault: false;
229
+ isPrimaryKey: false;
230
+ isAutoincrement: false;
231
+ hasRuntimeDefault: false;
232
+ enumValues: [string, ...string[]];
233
+ baseColumn: never;
234
+ identity: undefined;
235
+ generated: undefined;
236
+ }, {}, {
237
+ length: number | undefined;
238
+ }>;
239
+ lastError: import("drizzle-orm/sqlite-core").SQLiteColumn<{
240
+ name: "last_error";
241
+ tableName: "jobs";
242
+ dataType: "string";
243
+ columnType: "SQLiteText";
244
+ data: string;
245
+ driverParam: string;
246
+ notNull: false;
247
+ hasDefault: false;
248
+ isPrimaryKey: false;
249
+ isAutoincrement: false;
250
+ hasRuntimeDefault: false;
251
+ enumValues: [string, ...string[]];
252
+ baseColumn: never;
253
+ identity: undefined;
254
+ generated: undefined;
255
+ }, {}, {
256
+ length: number | undefined;
257
+ }>;
258
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
259
+ name: "created_at";
260
+ tableName: "jobs";
261
+ dataType: "string";
262
+ columnType: "SQLiteText";
263
+ data: string;
264
+ driverParam: string;
265
+ notNull: true;
266
+ hasDefault: false;
267
+ isPrimaryKey: false;
268
+ isAutoincrement: false;
269
+ hasRuntimeDefault: false;
270
+ enumValues: [string, ...string[]];
271
+ baseColumn: never;
272
+ identity: undefined;
273
+ generated: undefined;
274
+ }, {}, {
275
+ length: number | undefined;
276
+ }>;
277
+ };
278
+ dialect: "sqlite";
279
+ }>;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jobs = void 0;
4
+ const sqlite_core_1 = require("drizzle-orm/sqlite-core");
5
+ /**
6
+ * SQLite `jobs` table. Add it to your Drizzle schema and generate a migration
7
+ * with drizzle-kit.
8
+ *
9
+ * The **full** unique index on `(name, unique_key)` is the active-dedup
10
+ * primitive: SQL treats `NULL` as distinct in unique indexes, so jobs without
11
+ * a `uniqueKey` never collide, and — because terminal transitions clear
12
+ * `unique_key` to `NULL` — uniqueness only constrains ACTIVE jobs. The same
13
+ * full-index shape works on every dialect (no partial indexes needed). The
14
+ * `(status, available_at)` index serves the claimer's batch query.
15
+ */
16
+ exports.jobs = (0, sqlite_core_1.sqliteTable)('jobs', {
17
+ id: (0, sqlite_core_1.text)('id').primaryKey(),
18
+ name: (0, sqlite_core_1.text)('name').notNull(),
19
+ payload: (0, sqlite_core_1.text)('payload', { mode: 'json' })
20
+ .$type()
21
+ .notNull(),
22
+ status: (0, sqlite_core_1.text)('status').$type().notNull(),
23
+ attempts: (0, sqlite_core_1.integer)('attempts').notNull().default(0),
24
+ maxAttempts: (0, sqlite_core_1.integer)('max_attempts').notNull().default(10),
25
+ uniqueKey: (0, sqlite_core_1.text)('unique_key'),
26
+ priority: (0, sqlite_core_1.integer)('priority').notNull().default(0),
27
+ availableAt: (0, sqlite_core_1.text)('available_at').notNull(),
28
+ claimedAt: (0, sqlite_core_1.text)('claimed_at'),
29
+ claimedBy: (0, sqlite_core_1.text)('claimed_by'),
30
+ processedAt: (0, sqlite_core_1.text)('processed_at'),
31
+ lastError: (0, sqlite_core_1.text)('last_error'),
32
+ createdAt: (0, sqlite_core_1.text)('created_at').notNull(),
33
+ }, (table) => [
34
+ (0, sqlite_core_1.uniqueIndex)('jobs_name_unique_key_unique').on(table.name, table.uniqueKey),
35
+ (0, sqlite_core_1.index)('jobs_status_available_idx').on(table.status, table.availableAt),
36
+ ]);
37
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../dialects/sqlite/schema.ts"],"names":[],"mappings":";;;AAAA,yDAMiC;AAGjC;;;;;;;;;;GAUG;AACU,QAAA,IAAI,GAAG,IAAA,yBAAW,EAC7B,MAAM,EACN;IACE,EAAE,EAAE,IAAA,kBAAI,EAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,IAAI,EAAE,IAAA,kBAAI,EAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,OAAO,EAAE,IAAA,kBAAI,EAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SACvC,KAAK,EAA2B;SAChC,OAAO,EAAE;IACZ,MAAM,EAAE,IAAA,kBAAI,EAAC,QAAQ,CAAC,CAAC,KAAK,EAAa,CAAC,OAAO,EAAE;IACnD,QAAQ,EAAE,IAAA,qBAAO,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,WAAW,EAAE,IAAA,qBAAO,EAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC1D,SAAS,EAAE,IAAA,kBAAI,EAAC,YAAY,CAAC;IAC7B,QAAQ,EAAE,IAAA,qBAAO,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,WAAW,EAAE,IAAA,kBAAI,EAAC,cAAc,CAAC,CAAC,OAAO,EAAE;IAC3C,SAAS,EAAE,IAAA,kBAAI,EAAC,YAAY,CAAC;IAC7B,SAAS,EAAE,IAAA,kBAAI,EAAC,YAAY,CAAC;IAC7B,WAAW,EAAE,IAAA,kBAAI,EAAC,cAAc,CAAC;IACjC,SAAS,EAAE,IAAA,kBAAI,EAAC,YAAY,CAAC;IAC7B,SAAS,EAAE,IAAA,kBAAI,EAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CACxC,EACD,CAAC,KAAK,EAAE,EAAE,CAAC;IACT,IAAA,yBAAW,EAAC,6BAA6B,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1E,IAAA,mBAAK,EAAC,2BAA2B,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC;CACvE,CACF,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Resolves when an enqueued job becomes due.
3
+ *
4
+ * - `runAt` and `delayMs` are **mutually exclusive** — both set throws, so a
5
+ * silent precedence rule can never mask a caller bug.
6
+ * - `runAt` wins as the absolute time; `delayMs` counts from now; neither
7
+ * means "due immediately".
8
+ */
9
+ export declare function resolveAvailableAt(input: {
10
+ runAt?: Date;
11
+ delayMs?: number;
12
+ }): Date;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // Shared enqueue-input resolution, used by every dialect store so the
3
+ // runAt/delayMs contract is identical no matter which store executes it.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.resolveAvailableAt = resolveAvailableAt;
6
+ /**
7
+ * Resolves when an enqueued job becomes due.
8
+ *
9
+ * - `runAt` and `delayMs` are **mutually exclusive** — both set throws, so a
10
+ * silent precedence rule can never mask a caller bug.
11
+ * - `runAt` wins as the absolute time; `delayMs` counts from now; neither
12
+ * means "due immediately".
13
+ */
14
+ function resolveAvailableAt(input) {
15
+ if (input.runAt !== undefined && input.delayMs !== undefined) {
16
+ throw new Error('EnqueueJobInput: runAt and delayMs are mutually exclusive — set at most one.');
17
+ }
18
+ if (input.runAt !== undefined) {
19
+ return input.runAt;
20
+ }
21
+ if (input.delayMs !== undefined) {
22
+ return new Date(Date.now() + input.delayMs);
23
+ }
24
+ return new Date();
25
+ }
26
+ //# sourceMappingURL=enqueue-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enqueue-input.js","sourceRoot":"","sources":["../enqueue-input.ts"],"names":[],"mappings":";AAAA,sEAAsE;AACtE,yEAAyE;;AAUzE,gDAaC;AArBD;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAAC,KAAyC;IAC1E,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,IAAI,EAAE,CAAC;AACpB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Signals a transient failure: the job could not complete now but a later
3
+ * attempt may succeed. `delayMs`, when set, overrides the claimer's jittered
4
+ * exponential backoff for the next attempt (e.g. a handler-supplied
5
+ * retry-after).
6
+ */
7
+ export declare class RetryableError extends Error {
8
+ readonly delayMs?: number | undefined;
9
+ constructor(message: string, delayMs?: number | undefined);
10
+ }
11
+ /**
12
+ * Signals a non-recoverable failure: retrying can never succeed (e.g. no
13
+ * handler registered for the job name, or a malformed payload). The claimer
14
+ * marks the job failed immediately rather than burning retry attempts.
15
+ */
16
+ export declare class PermanentError extends Error {
17
+ constructor(message: string);
18
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ // The retry vocabulary handlers use to steer the claimer. It is deliberately
3
+ // dependency-free (no Nest, no Drizzle) so handlers, stores, and tests can all
4
+ // throw these without importing anything heavier than this module.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PermanentError = exports.RetryableError = void 0;
7
+ /**
8
+ * Signals a transient failure: the job could not complete now but a later
9
+ * attempt may succeed. `delayMs`, when set, overrides the claimer's jittered
10
+ * exponential backoff for the next attempt (e.g. a handler-supplied
11
+ * retry-after).
12
+ */
13
+ class RetryableError extends Error {
14
+ constructor(message, delayMs) {
15
+ super(message);
16
+ this.delayMs = delayMs;
17
+ this.name = 'RetryableError';
18
+ }
19
+ }
20
+ exports.RetryableError = RetryableError;
21
+ /**
22
+ * Signals a non-recoverable failure: retrying can never succeed (e.g. no
23
+ * handler registered for the job name, or a malformed payload). The claimer
24
+ * marks the job failed immediately rather than burning retry attempts.
25
+ */
26
+ class PermanentError extends Error {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = 'PermanentError';
30
+ }
31
+ }
32
+ exports.PermanentError = PermanentError;
33
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../errors.ts"],"names":[],"mappings":";AAAA,6EAA6E;AAC7E,+EAA+E;AAC/E,mEAAmE;;;AAEnE;;;;;GAKG;AACH,MAAa,cAAe,SAAQ,KAAK;IACvC,YACE,OAAe,EACN,OAAgB;QAEzB,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,YAAO,GAAP,OAAO,CAAS;QAGzB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AARD,wCAQC;AAED;;;;GAIG;AACH,MAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AALD,wCAKC"}
@@ -0,0 +1,10 @@
1
+ export * from './errors';
2
+ export * from './interfaces';
3
+ export * from './tokens';
4
+ export * from './enqueue-input';
5
+ export * from './job-handler.decorator';
6
+ export * from './jobs-handler.explorer';
7
+ export * from './jobs.service';
8
+ export * from './jobs-claimer.service';
9
+ export * from './jobs-worker';
10
+ export * from './jobs.module';
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // Public entrypoint for @nest-native/jobs (core engine).
18
+ // Dialect-specific stores + table definitions ship from their own modules
19
+ // (./sqlite, ./postgres, ./mysql); this barrel is the dialect-agnostic engine.
20
+ __exportStar(require("./errors"), exports);
21
+ __exportStar(require("./interfaces"), exports);
22
+ __exportStar(require("./tokens"), exports);
23
+ __exportStar(require("./enqueue-input"), exports);
24
+ __exportStar(require("./job-handler.decorator"), exports);
25
+ __exportStar(require("./jobs-handler.explorer"), exports);
26
+ __exportStar(require("./jobs.service"), exports);
27
+ __exportStar(require("./jobs-claimer.service"), exports);
28
+ __exportStar(require("./jobs-worker"), exports);
29
+ __exportStar(require("./jobs.module"), exports);
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yDAAyD;AACzD,0EAA0E;AAC1E,+EAA+E;AAC/E,2CAAyB;AACzB,+CAA6B;AAC7B,2CAAyB;AACzB,kDAAgC;AAChC,0DAAwC;AACxC,0DAAwC;AACxC,iDAA+B;AAC/B,yDAAuC;AACvC,gDAA8B;AAC9B,gDAA8B"}