@happyvertical/jobs 0.80.0 → 0.80.2

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.
@@ -1,336 +1,310 @@
1
+ import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
2
  import { createId } from "@happyvertical/utils";
2
- import { B as BaseJobStore, p as priorityToNumber } from "../chunks/base-store-DlNksWvQ.js";
3
- class CloudTasksJobStore extends BaseJobStore {
4
- config;
5
- client = null;
6
- // biome-ignore lint/style/useNamingConvention: Google Cloud Tasks module reference
7
- cloudTasksModule = null;
8
- jobStates = /* @__PURE__ */ new Map();
9
- constructor(config) {
10
- super();
11
- this.config = {
12
- queuePrefix: "",
13
- defaultRetry: {
14
- maxAttempts: 3,
15
- minBackoff: "1s",
16
- maxBackoff: "300s"
17
- },
18
- ...config
19
- };
20
- }
21
- /**
22
- * Initialize the store - dynamically imports Google Cloud Tasks
23
- */
24
- async initialize() {
25
- if (this.initialized) return;
26
- try {
27
- this.cloudTasksModule = await import("@google-cloud/tasks");
28
- } catch {
29
- throw new Error(
30
- "Google Cloud Tasks is required for CloudTasksJobStore. Install it with: npm install @google-cloud/tasks"
31
- );
32
- }
33
- this.client = new this.cloudTasksModule.CloudTasksClient();
34
- this.initialized = true;
35
- }
36
- /**
37
- * Get the full queue path
38
- */
39
- getQueuePath(queueName) {
40
- return `projects/${this.config.projectId}/locations/${this.config.location}/queues/${this.config.queuePrefix}${queueName}`;
41
- }
42
- /**
43
- * Get the full task path
44
- */
45
- getTaskPath(queueName, taskId) {
46
- return `${this.getQueuePath(queueName)}/tasks/${taskId}`;
47
- }
48
- /**
49
- * Enqueue a new job
50
- */
51
- async enqueue(options) {
52
- if (!this.initialized || !this.client) {
53
- throw new Error("CloudTasksJobStore not initialized");
54
- }
55
- const queueName = options.queue ?? "default";
56
- const now = /* @__PURE__ */ new Date();
57
- const jobId = createId();
58
- const jobData = {
59
- id: jobId,
60
- queue: queueName,
61
- payload: options.payload,
62
- priority: priorityToNumber(options.priority),
63
- maxAttempts: options.maxAttempts ?? 3,
64
- timeout: options.timeout ?? 3e5,
65
- timeoutBehavior: options.timeoutBehavior ?? "fail",
66
- retryStrategy: options.retryStrategy && "toConfig" in options.retryStrategy ? options.retryStrategy.toConfig() : options.retryStrategy ?? {
67
- type: "exponential",
68
- config: { initialDelay: 1e3, multiplier: 2 }
69
- },
70
- createdAt: now.toISOString()
71
- };
72
- const task = {
73
- name: this.getTaskPath(queueName, jobId),
74
- httpRequest: {
75
- httpMethod: "POST",
76
- url: this.config.handlerUrl,
77
- headers: {
78
- "Content-Type": "application/json"
79
- },
80
- body: Buffer.from(JSON.stringify(jobData)).toString("base64")
81
- }
82
- };
83
- if (this.config.serviceAccountEmail) {
84
- task.httpRequest.oidcToken = {
85
- serviceAccountEmail: this.config.serviceAccountEmail
86
- };
87
- }
88
- if (options.runAt && options.runAt > now) {
89
- task.scheduleTime = {
90
- seconds: Math.floor(options.runAt.getTime() / 1e3),
91
- nanos: options.runAt.getTime() % 1e3 * 1e6
92
- };
93
- }
94
- if (options.timeout) {
95
- task.dispatchDeadline = {
96
- seconds: Math.floor(options.timeout / 1e3),
97
- nanos: options.timeout % 1e3 * 1e6
98
- };
99
- }
100
- const [response] = await this.client.createTask({
101
- parent: this.getQueuePath(queueName),
102
- task
103
- });
104
- const job = {
105
- id: jobId,
106
- queue: queueName,
107
- payload: options.payload,
108
- status: "pending",
109
- priority: priorityToNumber(options.priority),
110
- attempts: 0,
111
- maxAttempts: options.maxAttempts ?? 3,
112
- runAt: options.runAt ?? now,
113
- startedAt: null,
114
- completedAt: null,
115
- timeout: options.timeout ?? 3e5,
116
- timeoutBehavior: options.timeoutBehavior ?? "fail",
117
- lastError: null,
118
- resultPointer: null,
119
- retryStrategy: jobData.retryStrategy,
120
- workerId: null,
121
- workerHeartbeat: null,
122
- createdAt: now,
123
- updatedAt: now
124
- };
125
- this.jobStates.set(jobId, {
126
- job,
127
- taskName: response.name ?? void 0
128
- });
129
- await this.emitEvent("job.created", job);
130
- return job;
131
- }
132
- /**
133
- * Dequeue jobs - NOT SUPPORTED in Cloud Tasks
134
- * Cloud Tasks is push-based; jobs are delivered to your handler URL
135
- */
136
- async dequeue(_queues, _limit, _workerId) {
137
- throw new Error(
138
- "Cloud Tasks does not support pull-based dequeue. Jobs are pushed to your handler URL. Use the HTTP handler to receive jobs."
139
- );
140
- }
141
- /**
142
- * Update a job - LIMITED SUPPORT in Cloud Tasks
143
- */
144
- async update(_id, _updates) {
145
- throw new Error(
146
- "Cloud Tasks does not support updating tasks after creation. Cancel and recreate the job instead."
147
- );
148
- }
149
- /**
150
- * Get a job by ID
151
- */
152
- async get(id) {
153
- const state = this.jobStates.get(id);
154
- if (state) {
155
- return state.job;
156
- }
157
- return null;
158
- }
159
- /**
160
- * List jobs with filtering
161
- * Note: Only returns jobs from in-memory state
162
- */
163
- async list(filter) {
164
- const jobs = [];
165
- const limit = filter.limit ?? 100;
166
- for (const state of this.jobStates.values()) {
167
- const job = state.job;
168
- if (filter.queue && job.queue !== filter.queue) continue;
169
- if (filter.status) {
170
- const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
171
- if (!statuses.includes(job.status)) continue;
172
- }
173
- if (filter.objectType && job.payload.objectType !== filter.objectType)
174
- continue;
175
- if (filter.method && job.payload.method !== filter.method) continue;
176
- if (filter.createdAfter && job.createdAt < filter.createdAfter) continue;
177
- if (filter.createdBefore && job.createdAt > filter.createdBefore)
178
- continue;
179
- jobs.push(job);
180
- if (jobs.length >= limit) break;
181
- }
182
- return jobs;
183
- }
184
- /**
185
- * Cancel a job by deleting its Cloud Task
186
- */
187
- async cancel(id) {
188
- if (!this.initialized || !this.client) {
189
- throw new Error("CloudTasksJobStore not initialized");
190
- }
191
- const state = this.jobStates.get(id);
192
- if (!state || !state.taskName) {
193
- throw new Error(`Job ${id} not found`);
194
- }
195
- try {
196
- await this.client.deleteTask({ name: state.taskName });
197
- } catch (error) {
198
- const err = error;
199
- if (err.code !== 5) {
200
- throw error;
201
- }
202
- }
203
- state.job.status = "cancelled";
204
- state.job.completedAt = /* @__PURE__ */ new Date();
205
- await this.emitEvent("job.cancelled", state.job);
206
- }
207
- /**
208
- * Mark job as started (called by your handler when it receives the job)
209
- */
210
- async markStarted(id, workerId) {
211
- const state = this.jobStates.get(id);
212
- if (!state) {
213
- throw new Error(`Job ${id} not found`);
214
- }
215
- state.job.status = "running";
216
- state.job.workerId = workerId;
217
- state.job.startedAt = /* @__PURE__ */ new Date();
218
- state.job.attempts++;
219
- await this.emitEvent("job.started", state.job);
220
- }
221
- /**
222
- * Mark job as completed (called by your handler after successful execution)
223
- */
224
- async markCompleted(id, resultPointer) {
225
- const state = this.jobStates.get(id);
226
- if (!state) {
227
- throw new Error(`Job ${id} not found`);
228
- }
229
- state.job.status = "completed";
230
- state.job.completedAt = /* @__PURE__ */ new Date();
231
- state.job.resultPointer = resultPointer ?? null;
232
- await this.emitEvent("job.completed", state.job, { resultPointer });
233
- }
234
- /**
235
- * Mark job as failed (called by your handler after failed execution)
236
- */
237
- async markFailed(id, error) {
238
- const state = this.jobStates.get(id);
239
- if (!state) {
240
- throw new Error(`Job ${id} not found`);
241
- }
242
- state.job.status = "failed";
243
- state.job.completedAt = /* @__PURE__ */ new Date();
244
- state.job.lastError = error;
245
- await this.emitEvent("job.failed", state.job, { error });
246
- }
247
- /**
248
- * Clean up old jobs from in-memory state
249
- */
250
- async cleanup(options) {
251
- let cleaned = 0;
252
- for (const [id, state] of this.jobStates) {
253
- const { job } = state;
254
- if (options.completedBefore && job.status === "completed" && job.completedAt && job.completedAt < options.completedBefore) {
255
- this.jobStates.delete(id);
256
- cleaned++;
257
- continue;
258
- }
259
- if (options.failedBefore && job.status === "failed" && job.completedAt && job.completedAt < options.failedBefore) {
260
- this.jobStates.delete(id);
261
- cleaned++;
262
- continue;
263
- }
264
- if (options.cancelledBefore && job.status === "cancelled" && job.completedAt && job.completedAt < options.cancelledBefore) {
265
- this.jobStates.delete(id);
266
- cleaned++;
267
- continue;
268
- }
269
- if (options.limit && cleaned >= options.limit) break;
270
- }
271
- return cleaned;
272
- }
273
- /**
274
- * Update worker heartbeat - stored in memory only
275
- */
276
- async heartbeat(jobId, _workerId) {
277
- const state = this.jobStates.get(jobId);
278
- if (state) {
279
- state.job.workerHeartbeat = /* @__PURE__ */ new Date();
280
- }
281
- }
282
- /**
283
- * Get queue statistics from in-memory state
284
- */
285
- async stats(queue) {
286
- const totals = {
287
- pending: 0,
288
- running: 0,
289
- completed: 0,
290
- failed: 0,
291
- cancelled: 0,
292
- avgDuration: null
293
- };
294
- for (const state of this.jobStates.values()) {
295
- if (queue && state.job.queue !== queue) continue;
296
- switch (state.job.status) {
297
- case "pending":
298
- totals.pending++;
299
- break;
300
- case "running":
301
- totals.running++;
302
- break;
303
- case "completed":
304
- totals.completed++;
305
- break;
306
- case "failed":
307
- totals.failed++;
308
- break;
309
- case "cancelled":
310
- totals.cancelled++;
311
- break;
312
- }
313
- }
314
- return totals;
315
- }
316
- /**
317
- * Close the Cloud Tasks client
318
- */
319
- async close() {
320
- if (this.client) {
321
- await this.client.close();
322
- this.client = null;
323
- }
324
- this.jobStates.clear();
325
- this.initialized = false;
326
- }
327
- }
3
+ //#region src/adapters/cloud-tasks.ts
4
+ /**
5
+ * Cloud Tasks-based job store implementation
6
+ *
7
+ * Note: Cloud Tasks operates differently from traditional job queues:
8
+ * - Jobs are HTTP tasks sent to your handler URL
9
+ * - No pull-based dequeue (Cloud Tasks pushes to your handler)
10
+ * - Updates are limited (tasks can be deleted but not modified)
11
+ */
12
+ var CloudTasksJobStore = class extends BaseJobStore {
13
+ config;
14
+ client = null;
15
+ cloudTasksModule = null;
16
+ jobStates = /* @__PURE__ */ new Map();
17
+ constructor(config) {
18
+ super();
19
+ this.config = {
20
+ queuePrefix: "",
21
+ defaultRetry: {
22
+ maxAttempts: 3,
23
+ minBackoff: "1s",
24
+ maxBackoff: "300s"
25
+ },
26
+ ...config
27
+ };
28
+ }
29
+ /**
30
+ * Initialize the store - dynamically imports Google Cloud Tasks
31
+ */
32
+ async initialize() {
33
+ if (this.initialized) return;
34
+ try {
35
+ this.cloudTasksModule = await import("@google-cloud/tasks");
36
+ } catch {
37
+ throw new Error("Google Cloud Tasks is required for CloudTasksJobStore. Install it with: npm install @google-cloud/tasks");
38
+ }
39
+ this.client = new this.cloudTasksModule.CloudTasksClient();
40
+ this.initialized = true;
41
+ }
42
+ /**
43
+ * Get the full queue path
44
+ */
45
+ getQueuePath(queueName) {
46
+ return `projects/${this.config.projectId}/locations/${this.config.location}/queues/${this.config.queuePrefix}${queueName}`;
47
+ }
48
+ /**
49
+ * Get the full task path
50
+ */
51
+ getTaskPath(queueName, taskId) {
52
+ return `${this.getQueuePath(queueName)}/tasks/${taskId}`;
53
+ }
54
+ /**
55
+ * Enqueue a new job
56
+ */
57
+ async enqueue(options) {
58
+ if (!this.initialized || !this.client) throw new Error("CloudTasksJobStore not initialized");
59
+ const queueName = options.queue ?? "default";
60
+ const now = /* @__PURE__ */ new Date();
61
+ const jobId = createId();
62
+ const jobData = {
63
+ id: jobId,
64
+ queue: queueName,
65
+ payload: options.payload,
66
+ priority: priorityToNumber(options.priority),
67
+ maxAttempts: options.maxAttempts ?? 3,
68
+ timeout: options.timeout ?? 3e5,
69
+ timeoutBehavior: options.timeoutBehavior ?? "fail",
70
+ retryStrategy: options.retryStrategy && "toConfig" in options.retryStrategy ? options.retryStrategy.toConfig() : options.retryStrategy ?? {
71
+ type: "exponential",
72
+ config: {
73
+ initialDelay: 1e3,
74
+ multiplier: 2
75
+ }
76
+ },
77
+ createdAt: now.toISOString()
78
+ };
79
+ const task = {
80
+ name: this.getTaskPath(queueName, jobId),
81
+ httpRequest: {
82
+ httpMethod: "POST",
83
+ url: this.config.handlerUrl,
84
+ headers: { "Content-Type": "application/json" },
85
+ body: Buffer.from(JSON.stringify(jobData)).toString("base64")
86
+ }
87
+ };
88
+ if (this.config.serviceAccountEmail) task.httpRequest.oidcToken = { serviceAccountEmail: this.config.serviceAccountEmail };
89
+ if (options.runAt && options.runAt > now) task.scheduleTime = {
90
+ seconds: Math.floor(options.runAt.getTime() / 1e3),
91
+ nanos: options.runAt.getTime() % 1e3 * 1e6
92
+ };
93
+ if (options.timeout) task.dispatchDeadline = {
94
+ seconds: Math.floor(options.timeout / 1e3),
95
+ nanos: options.timeout % 1e3 * 1e6
96
+ };
97
+ const [response] = await this.client.createTask({
98
+ parent: this.getQueuePath(queueName),
99
+ task
100
+ });
101
+ const job = {
102
+ id: jobId,
103
+ queue: queueName,
104
+ payload: options.payload,
105
+ status: "pending",
106
+ priority: priorityToNumber(options.priority),
107
+ attempts: 0,
108
+ maxAttempts: options.maxAttempts ?? 3,
109
+ runAt: options.runAt ?? now,
110
+ startedAt: null,
111
+ completedAt: null,
112
+ timeout: options.timeout ?? 3e5,
113
+ timeoutBehavior: options.timeoutBehavior ?? "fail",
114
+ lastError: null,
115
+ resultPointer: null,
116
+ retryStrategy: jobData.retryStrategy,
117
+ workerId: null,
118
+ workerHeartbeat: null,
119
+ createdAt: now,
120
+ updatedAt: now
121
+ };
122
+ this.jobStates.set(jobId, {
123
+ job,
124
+ taskName: response.name ?? void 0
125
+ });
126
+ await this.emitEvent("job.created", job);
127
+ return job;
128
+ }
129
+ /**
130
+ * Dequeue jobs - NOT SUPPORTED in Cloud Tasks
131
+ * Cloud Tasks is push-based; jobs are delivered to your handler URL
132
+ */
133
+ async dequeue(_queues, _limit, _workerId) {
134
+ throw new Error("Cloud Tasks does not support pull-based dequeue. Jobs are pushed to your handler URL. Use the HTTP handler to receive jobs.");
135
+ }
136
+ /**
137
+ * Update a job - LIMITED SUPPORT in Cloud Tasks
138
+ */
139
+ async update(_id, _updates) {
140
+ throw new Error("Cloud Tasks does not support updating tasks after creation. Cancel and recreate the job instead.");
141
+ }
142
+ /**
143
+ * Get a job by ID
144
+ */
145
+ async get(id) {
146
+ const state = this.jobStates.get(id);
147
+ if (state) return state.job;
148
+ return null;
149
+ }
150
+ /**
151
+ * List jobs with filtering
152
+ * Note: Only returns jobs from in-memory state
153
+ */
154
+ async list(filter) {
155
+ const jobs = [];
156
+ const limit = filter.limit ?? 100;
157
+ for (const state of this.jobStates.values()) {
158
+ const job = state.job;
159
+ if (filter.queue && job.queue !== filter.queue) continue;
160
+ if (filter.status) {
161
+ if (!(Array.isArray(filter.status) ? filter.status : [filter.status]).includes(job.status)) continue;
162
+ }
163
+ if (filter.objectType && job.payload.objectType !== filter.objectType) continue;
164
+ if (filter.method && job.payload.method !== filter.method) continue;
165
+ if (filter.createdAfter && job.createdAt < filter.createdAfter) continue;
166
+ if (filter.createdBefore && job.createdAt > filter.createdBefore) continue;
167
+ jobs.push(job);
168
+ if (jobs.length >= limit) break;
169
+ }
170
+ return jobs;
171
+ }
172
+ /**
173
+ * Cancel a job by deleting its Cloud Task
174
+ */
175
+ async cancel(id) {
176
+ if (!this.initialized || !this.client) throw new Error("CloudTasksJobStore not initialized");
177
+ const state = this.jobStates.get(id);
178
+ if (!state || !state.taskName) throw new Error(`Job ${id} not found`);
179
+ try {
180
+ await this.client.deleteTask({ name: state.taskName });
181
+ } catch (error) {
182
+ if (error.code !== 5) throw error;
183
+ }
184
+ state.job.status = "cancelled";
185
+ state.job.completedAt = /* @__PURE__ */ new Date();
186
+ await this.emitEvent("job.cancelled", state.job);
187
+ }
188
+ /**
189
+ * Mark job as started (called by your handler when it receives the job)
190
+ */
191
+ async markStarted(id, workerId) {
192
+ const state = this.jobStates.get(id);
193
+ if (!state) throw new Error(`Job ${id} not found`);
194
+ state.job.status = "running";
195
+ state.job.workerId = workerId;
196
+ state.job.startedAt = /* @__PURE__ */ new Date();
197
+ state.job.attempts++;
198
+ await this.emitEvent("job.started", state.job);
199
+ }
200
+ /**
201
+ * Mark job as completed (called by your handler after successful execution)
202
+ */
203
+ async markCompleted(id, resultPointer) {
204
+ const state = this.jobStates.get(id);
205
+ if (!state) throw new Error(`Job ${id} not found`);
206
+ state.job.status = "completed";
207
+ state.job.completedAt = /* @__PURE__ */ new Date();
208
+ state.job.resultPointer = resultPointer ?? null;
209
+ await this.emitEvent("job.completed", state.job, { resultPointer });
210
+ }
211
+ /**
212
+ * Mark job as failed (called by your handler after failed execution)
213
+ */
214
+ async markFailed(id, error) {
215
+ const state = this.jobStates.get(id);
216
+ if (!state) throw new Error(`Job ${id} not found`);
217
+ state.job.status = "failed";
218
+ state.job.completedAt = /* @__PURE__ */ new Date();
219
+ state.job.lastError = error;
220
+ await this.emitEvent("job.failed", state.job, { error });
221
+ }
222
+ /**
223
+ * Clean up old jobs from in-memory state
224
+ */
225
+ async cleanup(options) {
226
+ let cleaned = 0;
227
+ for (const [id, state] of this.jobStates) {
228
+ const { job } = state;
229
+ if (options.completedBefore && job.status === "completed" && job.completedAt && job.completedAt < options.completedBefore) {
230
+ this.jobStates.delete(id);
231
+ cleaned++;
232
+ continue;
233
+ }
234
+ if (options.failedBefore && job.status === "failed" && job.completedAt && job.completedAt < options.failedBefore) {
235
+ this.jobStates.delete(id);
236
+ cleaned++;
237
+ continue;
238
+ }
239
+ if (options.cancelledBefore && job.status === "cancelled" && job.completedAt && job.completedAt < options.cancelledBefore) {
240
+ this.jobStates.delete(id);
241
+ cleaned++;
242
+ continue;
243
+ }
244
+ if (options.limit && cleaned >= options.limit) break;
245
+ }
246
+ return cleaned;
247
+ }
248
+ /**
249
+ * Update worker heartbeat - stored in memory only
250
+ */
251
+ async heartbeat(jobId, _workerId) {
252
+ const state = this.jobStates.get(jobId);
253
+ if (state) state.job.workerHeartbeat = /* @__PURE__ */ new Date();
254
+ }
255
+ /**
256
+ * Get queue statistics from in-memory state
257
+ */
258
+ async stats(queue) {
259
+ const totals = {
260
+ pending: 0,
261
+ running: 0,
262
+ completed: 0,
263
+ failed: 0,
264
+ cancelled: 0,
265
+ avgDuration: null
266
+ };
267
+ for (const state of this.jobStates.values()) {
268
+ if (queue && state.job.queue !== queue) continue;
269
+ switch (state.job.status) {
270
+ case "pending":
271
+ totals.pending++;
272
+ break;
273
+ case "running":
274
+ totals.running++;
275
+ break;
276
+ case "completed":
277
+ totals.completed++;
278
+ break;
279
+ case "failed":
280
+ totals.failed++;
281
+ break;
282
+ case "cancelled":
283
+ totals.cancelled++;
284
+ break;
285
+ }
286
+ }
287
+ return totals;
288
+ }
289
+ /**
290
+ * Close the Cloud Tasks client
291
+ */
292
+ async close() {
293
+ if (this.client) {
294
+ await this.client.close();
295
+ this.client = null;
296
+ }
297
+ this.jobStates.clear();
298
+ this.initialized = false;
299
+ }
300
+ };
301
+ /**
302
+ * Create a Cloud Tasks job store instance
303
+ */
328
304
  function createCloudTasksJobStore(config) {
329
- return new CloudTasksJobStore(config);
305
+ return new CloudTasksJobStore(config);
330
306
  }
331
- export {
332
- CloudTasksJobStore,
333
- createCloudTasksJobStore,
334
- CloudTasksJobStore as default
335
- };
336
- //# sourceMappingURL=cloud-tasks.js.map
307
+ //#endregion
308
+ export { CloudTasksJobStore, CloudTasksJobStore as default, createCloudTasksJobStore };
309
+
310
+ //# sourceMappingURL=cloud-tasks.js.map