@queuebase/core 1.0.0 → 1.2.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.cjs +225 -0
- package/dist/index.d.cts +222 -0
- package/dist/index.d.ts +222 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -11
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +2 -4
- package/dist/router.d.ts.map +1 -1
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
WEBHOOK_HEADERS: () => WEBHOOK_HEADERS,
|
|
24
|
+
calculateBackoff: () => calculateBackoff,
|
|
25
|
+
createJobRouter: () => createJobRouter,
|
|
26
|
+
generateJobId: () => generateJobId,
|
|
27
|
+
generatePublicId: () => generatePublicId,
|
|
28
|
+
job: () => job,
|
|
29
|
+
parseDelay: () => parseDelay,
|
|
30
|
+
processJobCallback: () => processJobCallback,
|
|
31
|
+
signPayload: () => signPayload,
|
|
32
|
+
verifySignature: () => verifySignature
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/job.ts
|
|
37
|
+
function job(definition) {
|
|
38
|
+
return definition;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/router.ts
|
|
42
|
+
function createJobRouter(jobs) {
|
|
43
|
+
return jobs;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
|
|
47
|
+
var import_node_crypto = require("crypto");
|
|
48
|
+
|
|
49
|
+
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/url-alphabet/index.js
|
|
50
|
+
var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
51
|
+
|
|
52
|
+
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
|
|
53
|
+
var POOL_SIZE_MULTIPLIER = 128;
|
|
54
|
+
var pool;
|
|
55
|
+
var poolOffset;
|
|
56
|
+
function fillPool(bytes) {
|
|
57
|
+
if (!pool || pool.length < bytes) {
|
|
58
|
+
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
59
|
+
import_node_crypto.webcrypto.getRandomValues(pool);
|
|
60
|
+
poolOffset = 0;
|
|
61
|
+
} else if (poolOffset + bytes > pool.length) {
|
|
62
|
+
import_node_crypto.webcrypto.getRandomValues(pool);
|
|
63
|
+
poolOffset = 0;
|
|
64
|
+
}
|
|
65
|
+
poolOffset += bytes;
|
|
66
|
+
}
|
|
67
|
+
function nanoid(size = 21) {
|
|
68
|
+
fillPool(size |= 0);
|
|
69
|
+
let id = "";
|
|
70
|
+
for (let i = poolOffset - size; i < poolOffset; i++) {
|
|
71
|
+
id += urlAlphabet[pool[i] & 63];
|
|
72
|
+
}
|
|
73
|
+
return id;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/utils.ts
|
|
77
|
+
function parseDelay(delay) {
|
|
78
|
+
if (typeof delay === "number") {
|
|
79
|
+
return delay;
|
|
80
|
+
}
|
|
81
|
+
const match = delay.match(/^(\d+)(s|m|h|d)$/);
|
|
82
|
+
if (!match) {
|
|
83
|
+
const num2 = Number.parseInt(delay, 10);
|
|
84
|
+
if (Number.isNaN(num2)) {
|
|
85
|
+
throw new Error(`Invalid delay format: ${delay}`);
|
|
86
|
+
}
|
|
87
|
+
return num2;
|
|
88
|
+
}
|
|
89
|
+
const [, value, unit] = match;
|
|
90
|
+
const num = Number.parseInt(value, 10);
|
|
91
|
+
switch (unit) {
|
|
92
|
+
case "s":
|
|
93
|
+
return num * 1e3;
|
|
94
|
+
case "m":
|
|
95
|
+
return num * 60 * 1e3;
|
|
96
|
+
case "h":
|
|
97
|
+
return num * 60 * 60 * 1e3;
|
|
98
|
+
case "d":
|
|
99
|
+
return num * 24 * 60 * 60 * 1e3;
|
|
100
|
+
default:
|
|
101
|
+
throw new Error(`Invalid delay unit: ${unit}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function calculateBackoff(attempt, strategy, baseDelay) {
|
|
105
|
+
switch (strategy) {
|
|
106
|
+
case "linear":
|
|
107
|
+
return baseDelay * attempt;
|
|
108
|
+
case "exponential":
|
|
109
|
+
return baseDelay * 2 ** (attempt - 1);
|
|
110
|
+
default:
|
|
111
|
+
return baseDelay;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function generateJobId() {
|
|
115
|
+
const timestamp = Date.now().toString(36);
|
|
116
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
117
|
+
return `job_${timestamp}_${random}`;
|
|
118
|
+
}
|
|
119
|
+
function generatePublicId(prefix = "job") {
|
|
120
|
+
return `${prefix}_${nanoid(21)}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/webhook.ts
|
|
124
|
+
var import_node_crypto2 = require("crypto");
|
|
125
|
+
var WEBHOOK_HEADERS = {
|
|
126
|
+
SIGNATURE: "X-Queuebase-Signature"
|
|
127
|
+
};
|
|
128
|
+
var DEFAULT_TOLERANCE_SECONDS = 5 * 60;
|
|
129
|
+
function signPayload(payload, secret) {
|
|
130
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
131
|
+
const signedContent = `${timestamp}.${payload}`;
|
|
132
|
+
const hmac = (0, import_node_crypto2.createHmac)("sha256", secret).update(signedContent).digest("hex");
|
|
133
|
+
return {
|
|
134
|
+
signature: `t=${timestamp},v1=${hmac}`,
|
|
135
|
+
timestamp
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function verifySignature(payload, signature, secret, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS) {
|
|
139
|
+
const parts = signature.split(",");
|
|
140
|
+
const tPart = parts.find((p) => p.startsWith("t="));
|
|
141
|
+
const v1Part = parts.find((p) => p.startsWith("v1="));
|
|
142
|
+
if (!tPart || !v1Part) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const timestamp = Number.parseInt(tPart.slice(2), 10);
|
|
146
|
+
const receivedHmac = v1Part.slice(3);
|
|
147
|
+
if (Number.isNaN(timestamp)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
151
|
+
if (Math.abs(now - timestamp) > toleranceSeconds) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const signedContent = `${timestamp}.${payload}`;
|
|
155
|
+
const expectedHmac = (0, import_node_crypto2.createHmac)("sha256", secret).update(signedContent).digest("hex");
|
|
156
|
+
const expected = Buffer.from(expectedHmac, "hex");
|
|
157
|
+
const received = Buffer.from(receivedHmac, "hex");
|
|
158
|
+
if (expected.length !== received.length) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return (0, import_node_crypto2.timingSafeEqual)(expected, received);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/handler.ts
|
|
165
|
+
async function processJobCallback(router, request, options) {
|
|
166
|
+
const webhookSecret = options?.webhookSecret ?? process.env.QUEUEBASE_WEBHOOK_SECRET;
|
|
167
|
+
if (webhookSecret) {
|
|
168
|
+
if (!request.signatureHeader) {
|
|
169
|
+
return {
|
|
170
|
+
status: 401,
|
|
171
|
+
body: { success: false, error: "Missing webhook signature" }
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (!verifySignature(request.body, request.signatureHeader, webhookSecret)) {
|
|
175
|
+
return {
|
|
176
|
+
status: 401,
|
|
177
|
+
body: { success: false, error: "Invalid webhook signature" }
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(request.body);
|
|
184
|
+
} catch {
|
|
185
|
+
return { status: 400, body: { success: false, error: "Invalid JSON body" } };
|
|
186
|
+
}
|
|
187
|
+
const { jobId, name, payload, attempt, maxAttempts } = parsed;
|
|
188
|
+
const jobDef = router[name];
|
|
189
|
+
if (!jobDef) {
|
|
190
|
+
return { status: 404, body: { success: false, error: `Unknown job: ${name}` } };
|
|
191
|
+
}
|
|
192
|
+
const parseResult = jobDef.input.safeParse(payload);
|
|
193
|
+
if (!parseResult.success) {
|
|
194
|
+
return {
|
|
195
|
+
status: 400,
|
|
196
|
+
body: { success: false, error: `Invalid payload: ${parseResult.error.message}` }
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const context = {
|
|
200
|
+
input: parseResult.data,
|
|
201
|
+
jobId,
|
|
202
|
+
attempt,
|
|
203
|
+
maxAttempts
|
|
204
|
+
};
|
|
205
|
+
try {
|
|
206
|
+
const output = await jobDef.handler(context);
|
|
207
|
+
return { status: 200, body: { success: true, output } };
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
210
|
+
return { status: 500, body: { success: false, error: message } };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
214
|
+
0 && (module.exports = {
|
|
215
|
+
WEBHOOK_HEADERS,
|
|
216
|
+
calculateBackoff,
|
|
217
|
+
createJobRouter,
|
|
218
|
+
generateJobId,
|
|
219
|
+
generatePublicId,
|
|
220
|
+
job,
|
|
221
|
+
parseDelay,
|
|
222
|
+
processJobCallback,
|
|
223
|
+
signPayload,
|
|
224
|
+
verifySignature
|
|
225
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Status of a job in the queue
|
|
5
|
+
*/
|
|
6
|
+
type JobStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
7
|
+
/**
|
|
8
|
+
* Backoff strategy for retries
|
|
9
|
+
*/
|
|
10
|
+
type BackoffStrategy = 'linear' | 'exponential';
|
|
11
|
+
/**
|
|
12
|
+
* Options for enqueueing a job
|
|
13
|
+
*/
|
|
14
|
+
interface EnqueueOptions {
|
|
15
|
+
/** Delay before running the job (e.g., '5m', '1h', or milliseconds) */
|
|
16
|
+
delay?: string | number;
|
|
17
|
+
/** Number of retry attempts on failure */
|
|
18
|
+
retries?: number;
|
|
19
|
+
/** Backoff strategy for retries */
|
|
20
|
+
backoff?: BackoffStrategy;
|
|
21
|
+
/** Initial delay between retries in ms (default: 1000) */
|
|
22
|
+
backoffDelay?: number;
|
|
23
|
+
/** Maximum concurrent jobs of this type (per worker) */
|
|
24
|
+
concurrency?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A job definition with typed input/output
|
|
28
|
+
*/
|
|
29
|
+
interface JobDefinition<TInput = unknown, TOutput = unknown> {
|
|
30
|
+
/** Zod schema for validating job input */
|
|
31
|
+
input: z.ZodType<TInput>;
|
|
32
|
+
/** The handler function that executes the job */
|
|
33
|
+
handler: (ctx: JobContext<TInput>) => Promise<TOutput>;
|
|
34
|
+
/** Default options for this job */
|
|
35
|
+
defaults?: EnqueueOptions;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Context passed to job handlers
|
|
39
|
+
*/
|
|
40
|
+
interface JobContext<TInput = unknown> {
|
|
41
|
+
/** The validated input data */
|
|
42
|
+
input: TInput;
|
|
43
|
+
/** Unique ID for this job execution */
|
|
44
|
+
jobId: string;
|
|
45
|
+
/** Current attempt number (1-indexed) */
|
|
46
|
+
attempt: number;
|
|
47
|
+
/** Maximum attempts allowed */
|
|
48
|
+
maxAttempts: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Internal representation of a queued job
|
|
52
|
+
*/
|
|
53
|
+
interface QueuedJob {
|
|
54
|
+
id: number;
|
|
55
|
+
publicId: string;
|
|
56
|
+
name: string;
|
|
57
|
+
payload: unknown;
|
|
58
|
+
status: JobStatus;
|
|
59
|
+
attempt: number;
|
|
60
|
+
maxAttempts: number;
|
|
61
|
+
runAt: Date;
|
|
62
|
+
createdAt: Date;
|
|
63
|
+
startedAt: Date | null;
|
|
64
|
+
completedAt: Date | null;
|
|
65
|
+
result: unknown | null;
|
|
66
|
+
error: string | null;
|
|
67
|
+
backoffStrategy: BackoffStrategy;
|
|
68
|
+
backoffDelay: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Job status as returned by the API
|
|
72
|
+
*/
|
|
73
|
+
interface JobStatusResponse {
|
|
74
|
+
publicId: string;
|
|
75
|
+
name: string;
|
|
76
|
+
status: JobStatus;
|
|
77
|
+
attempt: number;
|
|
78
|
+
maxAttempts: number;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
startedAt: string | null;
|
|
81
|
+
completedAt: string | null;
|
|
82
|
+
result: unknown;
|
|
83
|
+
error: string | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returned from enqueue — includes jobId and a polling handle
|
|
87
|
+
*/
|
|
88
|
+
interface EnqueueResult {
|
|
89
|
+
jobId: string;
|
|
90
|
+
getStatus: () => Promise<JobStatusResponse>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Result of a job execution
|
|
94
|
+
*/
|
|
95
|
+
interface JobResult<TOutput = unknown> {
|
|
96
|
+
success: boolean;
|
|
97
|
+
output?: TOutput;
|
|
98
|
+
error?: string;
|
|
99
|
+
duration: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Configuration for queuebase
|
|
103
|
+
*/
|
|
104
|
+
interface QueuebaseConfig {
|
|
105
|
+
/** Directory containing job definitions */
|
|
106
|
+
jobsDir: string;
|
|
107
|
+
/** URL where the queuebase API can callback to execute jobs */
|
|
108
|
+
callbackUrl?: string;
|
|
109
|
+
/** API key for production (from queuebase.io) */
|
|
110
|
+
apiKey?: string;
|
|
111
|
+
/** Override the API endpoint (auto-detected by NODE_ENV) */
|
|
112
|
+
apiUrl?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A job definition - the basic building block
|
|
117
|
+
*/
|
|
118
|
+
interface AnyJobDefinition {
|
|
119
|
+
input: z.ZodTypeAny;
|
|
120
|
+
handler: (ctx: JobContext<unknown>) => Promise<unknown>;
|
|
121
|
+
defaults?: EnqueueOptions;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A collection of job definitions
|
|
125
|
+
*/
|
|
126
|
+
type JobRouter = Record<string, AnyJobDefinition>;
|
|
127
|
+
/**
|
|
128
|
+
* Creates a typed job router from job definitions
|
|
129
|
+
*/
|
|
130
|
+
declare function createJobRouter<T extends Record<string, AnyJobDefinition>>(jobs: T): T;
|
|
131
|
+
/**
|
|
132
|
+
* Callable job that can be enqueued
|
|
133
|
+
*/
|
|
134
|
+
interface CallableJob<TInput, TOutput> {
|
|
135
|
+
/** Enqueue this job for execution */
|
|
136
|
+
enqueue: (input: TInput, options?: EnqueueOptions) => Promise<EnqueueResult>;
|
|
137
|
+
/** The underlying job definition */
|
|
138
|
+
_def: AnyJobDefinition;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extract input type from a job definition
|
|
142
|
+
*/
|
|
143
|
+
type ExtractJobInput<T extends AnyJobDefinition> = T['input'] extends z.ZodType<infer I> ? I : never;
|
|
144
|
+
/**
|
|
145
|
+
* Extract output type from a job definition (from handler return type)
|
|
146
|
+
*/
|
|
147
|
+
type ExtractJobOutput<T extends AnyJobDefinition> = T['handler'] extends (ctx: JobContext<unknown>) => Promise<infer O> ? O : never;
|
|
148
|
+
/**
|
|
149
|
+
* Transform a job router into callable jobs
|
|
150
|
+
*/
|
|
151
|
+
type CallableJobRouter<T extends Record<string, AnyJobDefinition>> = {
|
|
152
|
+
[K in keyof T]: CallableJob<ExtractJobInput<T[K]>, ExtractJobOutput<T[K]>>;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Creates a job definition with typed input and handler
|
|
157
|
+
*/
|
|
158
|
+
declare function job<TInput, TOutput>(definition: {
|
|
159
|
+
input: z.ZodType<TInput>;
|
|
160
|
+
handler: (ctx: JobContext<TInput>) => Promise<TOutput>;
|
|
161
|
+
defaults?: EnqueueOptions;
|
|
162
|
+
}): AnyJobDefinition;
|
|
163
|
+
/**
|
|
164
|
+
* Infer the input type from a job definition's Zod schema
|
|
165
|
+
*/
|
|
166
|
+
type InferJobInput<T extends AnyJobDefinition> = T['input'] extends z.ZodType<infer I> ? I : never;
|
|
167
|
+
/**
|
|
168
|
+
* Infer the output type from a job definition's handler
|
|
169
|
+
*/
|
|
170
|
+
type InferJobOutput<T extends AnyJobDefinition> = T['handler'] extends (ctx: JobContext<unknown>) => Promise<infer O> ? O : never;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Parse a delay string into milliseconds
|
|
174
|
+
* Supports: '5s', '5m', '5h', '5d' or raw milliseconds
|
|
175
|
+
*/
|
|
176
|
+
declare function parseDelay(delay: string | number): number;
|
|
177
|
+
/**
|
|
178
|
+
* Calculate the next retry delay based on backoff strategy
|
|
179
|
+
*/
|
|
180
|
+
declare function calculateBackoff(attempt: number, strategy: 'linear' | 'exponential', baseDelay: number): number;
|
|
181
|
+
/**
|
|
182
|
+
* Generate a unique job ID
|
|
183
|
+
* @deprecated Use `generatePublicId()` instead
|
|
184
|
+
*/
|
|
185
|
+
declare function generateJobId(): string;
|
|
186
|
+
/**
|
|
187
|
+
* Generate a Stripe-style public ID for external use
|
|
188
|
+
* @param prefix - Prefix for the ID (default: 'job')
|
|
189
|
+
* @returns A string like `job_V1StGXR8kZ5jx...`
|
|
190
|
+
*/
|
|
191
|
+
declare function generatePublicId(prefix?: string): string;
|
|
192
|
+
|
|
193
|
+
declare const WEBHOOK_HEADERS: {
|
|
194
|
+
readonly SIGNATURE: "X-Queuebase-Signature";
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Sign a webhook payload using HMAC-SHA256.
|
|
198
|
+
* Returns a signature string in the format `t=<timestamp>,v1=<hmac>`.
|
|
199
|
+
*/
|
|
200
|
+
declare function signPayload(payload: string, secret: string): {
|
|
201
|
+
signature: string;
|
|
202
|
+
timestamp: number;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Verify a webhook signature against a payload.
|
|
206
|
+
* Uses timing-safe comparison and enforces a timestamp tolerance for replay protection.
|
|
207
|
+
*/
|
|
208
|
+
declare function verifySignature(payload: string, signature: string, secret: string, toleranceSeconds?: number): boolean;
|
|
209
|
+
|
|
210
|
+
interface HandlerRequest {
|
|
211
|
+
body: string;
|
|
212
|
+
signatureHeader: string | null;
|
|
213
|
+
}
|
|
214
|
+
interface HandlerResponse {
|
|
215
|
+
status: number;
|
|
216
|
+
body: unknown;
|
|
217
|
+
}
|
|
218
|
+
declare function processJobCallback(router: Record<string, AnyJobDefinition>, request: HandlerRequest, options?: {
|
|
219
|
+
webhookSecret?: string;
|
|
220
|
+
}): Promise<HandlerResponse>;
|
|
221
|
+
|
|
222
|
+
export { type AnyJobDefinition, type BackoffStrategy, type CallableJob, type CallableJobRouter, type EnqueueOptions, type EnqueueResult, type HandlerRequest, type HandlerResponse, type InferJobInput, type InferJobOutput, type JobContext, type JobDefinition, type JobResult, type JobRouter, type JobStatus, type JobStatusResponse, type QueuebaseConfig, type QueuedJob, WEBHOOK_HEADERS, calculateBackoff, createJobRouter, generateJobId, generatePublicId, job, parseDelay, processJobCallback, signPayload, verifySignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,222 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Status of a job in the queue
|
|
5
|
+
*/
|
|
6
|
+
type JobStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
7
|
+
/**
|
|
8
|
+
* Backoff strategy for retries
|
|
9
|
+
*/
|
|
10
|
+
type BackoffStrategy = 'linear' | 'exponential';
|
|
11
|
+
/**
|
|
12
|
+
* Options for enqueueing a job
|
|
13
|
+
*/
|
|
14
|
+
interface EnqueueOptions {
|
|
15
|
+
/** Delay before running the job (e.g., '5m', '1h', or milliseconds) */
|
|
16
|
+
delay?: string | number;
|
|
17
|
+
/** Number of retry attempts on failure */
|
|
18
|
+
retries?: number;
|
|
19
|
+
/** Backoff strategy for retries */
|
|
20
|
+
backoff?: BackoffStrategy;
|
|
21
|
+
/** Initial delay between retries in ms (default: 1000) */
|
|
22
|
+
backoffDelay?: number;
|
|
23
|
+
/** Maximum concurrent jobs of this type (per worker) */
|
|
24
|
+
concurrency?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A job definition with typed input/output
|
|
28
|
+
*/
|
|
29
|
+
interface JobDefinition<TInput = unknown, TOutput = unknown> {
|
|
30
|
+
/** Zod schema for validating job input */
|
|
31
|
+
input: z.ZodType<TInput>;
|
|
32
|
+
/** The handler function that executes the job */
|
|
33
|
+
handler: (ctx: JobContext<TInput>) => Promise<TOutput>;
|
|
34
|
+
/** Default options for this job */
|
|
35
|
+
defaults?: EnqueueOptions;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Context passed to job handlers
|
|
39
|
+
*/
|
|
40
|
+
interface JobContext<TInput = unknown> {
|
|
41
|
+
/** The validated input data */
|
|
42
|
+
input: TInput;
|
|
43
|
+
/** Unique ID for this job execution */
|
|
44
|
+
jobId: string;
|
|
45
|
+
/** Current attempt number (1-indexed) */
|
|
46
|
+
attempt: number;
|
|
47
|
+
/** Maximum attempts allowed */
|
|
48
|
+
maxAttempts: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Internal representation of a queued job
|
|
52
|
+
*/
|
|
53
|
+
interface QueuedJob {
|
|
54
|
+
id: number;
|
|
55
|
+
publicId: string;
|
|
56
|
+
name: string;
|
|
57
|
+
payload: unknown;
|
|
58
|
+
status: JobStatus;
|
|
59
|
+
attempt: number;
|
|
60
|
+
maxAttempts: number;
|
|
61
|
+
runAt: Date;
|
|
62
|
+
createdAt: Date;
|
|
63
|
+
startedAt: Date | null;
|
|
64
|
+
completedAt: Date | null;
|
|
65
|
+
result: unknown | null;
|
|
66
|
+
error: string | null;
|
|
67
|
+
backoffStrategy: BackoffStrategy;
|
|
68
|
+
backoffDelay: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Job status as returned by the API
|
|
72
|
+
*/
|
|
73
|
+
interface JobStatusResponse {
|
|
74
|
+
publicId: string;
|
|
75
|
+
name: string;
|
|
76
|
+
status: JobStatus;
|
|
77
|
+
attempt: number;
|
|
78
|
+
maxAttempts: number;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
startedAt: string | null;
|
|
81
|
+
completedAt: string | null;
|
|
82
|
+
result: unknown;
|
|
83
|
+
error: string | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returned from enqueue — includes jobId and a polling handle
|
|
87
|
+
*/
|
|
88
|
+
interface EnqueueResult {
|
|
89
|
+
jobId: string;
|
|
90
|
+
getStatus: () => Promise<JobStatusResponse>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Result of a job execution
|
|
94
|
+
*/
|
|
95
|
+
interface JobResult<TOutput = unknown> {
|
|
96
|
+
success: boolean;
|
|
97
|
+
output?: TOutput;
|
|
98
|
+
error?: string;
|
|
99
|
+
duration: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Configuration for queuebase
|
|
103
|
+
*/
|
|
104
|
+
interface QueuebaseConfig {
|
|
105
|
+
/** Directory containing job definitions */
|
|
106
|
+
jobsDir: string;
|
|
107
|
+
/** URL where the queuebase API can callback to execute jobs */
|
|
108
|
+
callbackUrl?: string;
|
|
109
|
+
/** API key for production (from queuebase.io) */
|
|
110
|
+
apiKey?: string;
|
|
111
|
+
/** Override the API endpoint (auto-detected by NODE_ENV) */
|
|
112
|
+
apiUrl?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A job definition - the basic building block
|
|
117
|
+
*/
|
|
118
|
+
interface AnyJobDefinition {
|
|
119
|
+
input: z.ZodTypeAny;
|
|
120
|
+
handler: (ctx: JobContext<unknown>) => Promise<unknown>;
|
|
121
|
+
defaults?: EnqueueOptions;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A collection of job definitions
|
|
125
|
+
*/
|
|
126
|
+
type JobRouter = Record<string, AnyJobDefinition>;
|
|
127
|
+
/**
|
|
128
|
+
* Creates a typed job router from job definitions
|
|
129
|
+
*/
|
|
130
|
+
declare function createJobRouter<T extends Record<string, AnyJobDefinition>>(jobs: T): T;
|
|
131
|
+
/**
|
|
132
|
+
* Callable job that can be enqueued
|
|
133
|
+
*/
|
|
134
|
+
interface CallableJob<TInput, TOutput> {
|
|
135
|
+
/** Enqueue this job for execution */
|
|
136
|
+
enqueue: (input: TInput, options?: EnqueueOptions) => Promise<EnqueueResult>;
|
|
137
|
+
/** The underlying job definition */
|
|
138
|
+
_def: AnyJobDefinition;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extract input type from a job definition
|
|
142
|
+
*/
|
|
143
|
+
type ExtractJobInput<T extends AnyJobDefinition> = T['input'] extends z.ZodType<infer I> ? I : never;
|
|
144
|
+
/**
|
|
145
|
+
* Extract output type from a job definition (from handler return type)
|
|
146
|
+
*/
|
|
147
|
+
type ExtractJobOutput<T extends AnyJobDefinition> = T['handler'] extends (ctx: JobContext<unknown>) => Promise<infer O> ? O : never;
|
|
148
|
+
/**
|
|
149
|
+
* Transform a job router into callable jobs
|
|
150
|
+
*/
|
|
151
|
+
type CallableJobRouter<T extends Record<string, AnyJobDefinition>> = {
|
|
152
|
+
[K in keyof T]: CallableJob<ExtractJobInput<T[K]>, ExtractJobOutput<T[K]>>;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Creates a job definition with typed input and handler
|
|
157
|
+
*/
|
|
158
|
+
declare function job<TInput, TOutput>(definition: {
|
|
159
|
+
input: z.ZodType<TInput>;
|
|
160
|
+
handler: (ctx: JobContext<TInput>) => Promise<TOutput>;
|
|
161
|
+
defaults?: EnqueueOptions;
|
|
162
|
+
}): AnyJobDefinition;
|
|
163
|
+
/**
|
|
164
|
+
* Infer the input type from a job definition's Zod schema
|
|
165
|
+
*/
|
|
166
|
+
type InferJobInput<T extends AnyJobDefinition> = T['input'] extends z.ZodType<infer I> ? I : never;
|
|
167
|
+
/**
|
|
168
|
+
* Infer the output type from a job definition's handler
|
|
169
|
+
*/
|
|
170
|
+
type InferJobOutput<T extends AnyJobDefinition> = T['handler'] extends (ctx: JobContext<unknown>) => Promise<infer O> ? O : never;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Parse a delay string into milliseconds
|
|
174
|
+
* Supports: '5s', '5m', '5h', '5d' or raw milliseconds
|
|
175
|
+
*/
|
|
176
|
+
declare function parseDelay(delay: string | number): number;
|
|
177
|
+
/**
|
|
178
|
+
* Calculate the next retry delay based on backoff strategy
|
|
179
|
+
*/
|
|
180
|
+
declare function calculateBackoff(attempt: number, strategy: 'linear' | 'exponential', baseDelay: number): number;
|
|
181
|
+
/**
|
|
182
|
+
* Generate a unique job ID
|
|
183
|
+
* @deprecated Use `generatePublicId()` instead
|
|
184
|
+
*/
|
|
185
|
+
declare function generateJobId(): string;
|
|
186
|
+
/**
|
|
187
|
+
* Generate a Stripe-style public ID for external use
|
|
188
|
+
* @param prefix - Prefix for the ID (default: 'job')
|
|
189
|
+
* @returns A string like `job_V1StGXR8kZ5jx...`
|
|
190
|
+
*/
|
|
191
|
+
declare function generatePublicId(prefix?: string): string;
|
|
192
|
+
|
|
193
|
+
declare const WEBHOOK_HEADERS: {
|
|
194
|
+
readonly SIGNATURE: "X-Queuebase-Signature";
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Sign a webhook payload using HMAC-SHA256.
|
|
198
|
+
* Returns a signature string in the format `t=<timestamp>,v1=<hmac>`.
|
|
199
|
+
*/
|
|
200
|
+
declare function signPayload(payload: string, secret: string): {
|
|
201
|
+
signature: string;
|
|
202
|
+
timestamp: number;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Verify a webhook signature against a payload.
|
|
206
|
+
* Uses timing-safe comparison and enforces a timestamp tolerance for replay protection.
|
|
207
|
+
*/
|
|
208
|
+
declare function verifySignature(payload: string, signature: string, secret: string, toleranceSeconds?: number): boolean;
|
|
209
|
+
|
|
210
|
+
interface HandlerRequest {
|
|
211
|
+
body: string;
|
|
212
|
+
signatureHeader: string | null;
|
|
213
|
+
}
|
|
214
|
+
interface HandlerResponse {
|
|
215
|
+
status: number;
|
|
216
|
+
body: unknown;
|
|
217
|
+
}
|
|
218
|
+
declare function processJobCallback(router: Record<string, AnyJobDefinition>, request: HandlerRequest, options?: {
|
|
219
|
+
webhookSecret?: string;
|
|
220
|
+
}): Promise<HandlerResponse>;
|
|
221
|
+
|
|
222
|
+
export { type AnyJobDefinition, type BackoffStrategy, type CallableJob, type CallableJobRouter, type EnqueueOptions, type EnqueueResult, type HandlerRequest, type HandlerResponse, type InferJobInput, type InferJobOutput, type JobContext, type JobDefinition, type JobResult, type JobRouter, type JobStatus, type JobStatusResponse, type QueuebaseConfig, type QueuedJob, WEBHOOK_HEADERS, calculateBackoff, createJobRouter, generateJobId, generatePublicId, job, parseDelay, processJobCallback, signPayload, verifySignature };
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,eAAe,EACf,cAAc,EACd,aAAa,EACb,UAAU,EACV,SAAS,EACT,SAAS,EACT,eAAe,GAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,GAAG,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAGxE,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG3F,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG7E,OAAO,EACL,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,eAAe,EACf,cAAc,EACd,aAAa,EACb,UAAU,EACV,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,GAAG,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAGxE,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG3F,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG7E,OAAO,EACL,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,189 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
// src/job.ts
|
|
2
|
+
function job(definition) {
|
|
3
|
+
return definition;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/router.ts
|
|
7
|
+
function createJobRouter(jobs) {
|
|
8
|
+
return jobs;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
|
|
12
|
+
import { webcrypto as crypto } from "crypto";
|
|
13
|
+
|
|
14
|
+
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/url-alphabet/index.js
|
|
15
|
+
var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
16
|
+
|
|
17
|
+
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
|
|
18
|
+
var POOL_SIZE_MULTIPLIER = 128;
|
|
19
|
+
var pool;
|
|
20
|
+
var poolOffset;
|
|
21
|
+
function fillPool(bytes) {
|
|
22
|
+
if (!pool || pool.length < bytes) {
|
|
23
|
+
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
24
|
+
crypto.getRandomValues(pool);
|
|
25
|
+
poolOffset = 0;
|
|
26
|
+
} else if (poolOffset + bytes > pool.length) {
|
|
27
|
+
crypto.getRandomValues(pool);
|
|
28
|
+
poolOffset = 0;
|
|
29
|
+
}
|
|
30
|
+
poolOffset += bytes;
|
|
31
|
+
}
|
|
32
|
+
function nanoid(size = 21) {
|
|
33
|
+
fillPool(size |= 0);
|
|
34
|
+
let id = "";
|
|
35
|
+
for (let i = poolOffset - size; i < poolOffset; i++) {
|
|
36
|
+
id += urlAlphabet[pool[i] & 63];
|
|
37
|
+
}
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/utils.ts
|
|
42
|
+
function parseDelay(delay) {
|
|
43
|
+
if (typeof delay === "number") {
|
|
44
|
+
return delay;
|
|
45
|
+
}
|
|
46
|
+
const match = delay.match(/^(\d+)(s|m|h|d)$/);
|
|
47
|
+
if (!match) {
|
|
48
|
+
const num2 = Number.parseInt(delay, 10);
|
|
49
|
+
if (Number.isNaN(num2)) {
|
|
50
|
+
throw new Error(`Invalid delay format: ${delay}`);
|
|
51
|
+
}
|
|
52
|
+
return num2;
|
|
53
|
+
}
|
|
54
|
+
const [, value, unit] = match;
|
|
55
|
+
const num = Number.parseInt(value, 10);
|
|
56
|
+
switch (unit) {
|
|
57
|
+
case "s":
|
|
58
|
+
return num * 1e3;
|
|
59
|
+
case "m":
|
|
60
|
+
return num * 60 * 1e3;
|
|
61
|
+
case "h":
|
|
62
|
+
return num * 60 * 60 * 1e3;
|
|
63
|
+
case "d":
|
|
64
|
+
return num * 24 * 60 * 60 * 1e3;
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Invalid delay unit: ${unit}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function calculateBackoff(attempt, strategy, baseDelay) {
|
|
70
|
+
switch (strategy) {
|
|
71
|
+
case "linear":
|
|
72
|
+
return baseDelay * attempt;
|
|
73
|
+
case "exponential":
|
|
74
|
+
return baseDelay * 2 ** (attempt - 1);
|
|
75
|
+
default:
|
|
76
|
+
return baseDelay;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function generateJobId() {
|
|
80
|
+
const timestamp = Date.now().toString(36);
|
|
81
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
82
|
+
return `job_${timestamp}_${random}`;
|
|
83
|
+
}
|
|
84
|
+
function generatePublicId(prefix = "job") {
|
|
85
|
+
return `${prefix}_${nanoid(21)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/webhook.ts
|
|
89
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
90
|
+
var WEBHOOK_HEADERS = {
|
|
91
|
+
SIGNATURE: "X-Queuebase-Signature"
|
|
92
|
+
};
|
|
93
|
+
var DEFAULT_TOLERANCE_SECONDS = 5 * 60;
|
|
94
|
+
function signPayload(payload, secret) {
|
|
95
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
96
|
+
const signedContent = `${timestamp}.${payload}`;
|
|
97
|
+
const hmac = createHmac("sha256", secret).update(signedContent).digest("hex");
|
|
98
|
+
return {
|
|
99
|
+
signature: `t=${timestamp},v1=${hmac}`,
|
|
100
|
+
timestamp
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function verifySignature(payload, signature, secret, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS) {
|
|
104
|
+
const parts = signature.split(",");
|
|
105
|
+
const tPart = parts.find((p) => p.startsWith("t="));
|
|
106
|
+
const v1Part = parts.find((p) => p.startsWith("v1="));
|
|
107
|
+
if (!tPart || !v1Part) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const timestamp = Number.parseInt(tPart.slice(2), 10);
|
|
111
|
+
const receivedHmac = v1Part.slice(3);
|
|
112
|
+
if (Number.isNaN(timestamp)) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
116
|
+
if (Math.abs(now - timestamp) > toleranceSeconds) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const signedContent = `${timestamp}.${payload}`;
|
|
120
|
+
const expectedHmac = createHmac("sha256", secret).update(signedContent).digest("hex");
|
|
121
|
+
const expected = Buffer.from(expectedHmac, "hex");
|
|
122
|
+
const received = Buffer.from(receivedHmac, "hex");
|
|
123
|
+
if (expected.length !== received.length) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return timingSafeEqual(expected, received);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/handler.ts
|
|
130
|
+
async function processJobCallback(router, request, options) {
|
|
131
|
+
const webhookSecret = options?.webhookSecret ?? process.env.QUEUEBASE_WEBHOOK_SECRET;
|
|
132
|
+
if (webhookSecret) {
|
|
133
|
+
if (!request.signatureHeader) {
|
|
134
|
+
return {
|
|
135
|
+
status: 401,
|
|
136
|
+
body: { success: false, error: "Missing webhook signature" }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (!verifySignature(request.body, request.signatureHeader, webhookSecret)) {
|
|
140
|
+
return {
|
|
141
|
+
status: 401,
|
|
142
|
+
body: { success: false, error: "Invalid webhook signature" }
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = JSON.parse(request.body);
|
|
149
|
+
} catch {
|
|
150
|
+
return { status: 400, body: { success: false, error: "Invalid JSON body" } };
|
|
151
|
+
}
|
|
152
|
+
const { jobId, name, payload, attempt, maxAttempts } = parsed;
|
|
153
|
+
const jobDef = router[name];
|
|
154
|
+
if (!jobDef) {
|
|
155
|
+
return { status: 404, body: { success: false, error: `Unknown job: ${name}` } };
|
|
156
|
+
}
|
|
157
|
+
const parseResult = jobDef.input.safeParse(payload);
|
|
158
|
+
if (!parseResult.success) {
|
|
159
|
+
return {
|
|
160
|
+
status: 400,
|
|
161
|
+
body: { success: false, error: `Invalid payload: ${parseResult.error.message}` }
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const context = {
|
|
165
|
+
input: parseResult.data,
|
|
166
|
+
jobId,
|
|
167
|
+
attempt,
|
|
168
|
+
maxAttempts
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
const output = await jobDef.handler(context);
|
|
172
|
+
return { status: 200, body: { success: true, output } };
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
175
|
+
return { status: 500, body: { success: false, error: message } };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
WEBHOOK_HEADERS,
|
|
180
|
+
calculateBackoff,
|
|
181
|
+
createJobRouter,
|
|
182
|
+
generateJobId,
|
|
183
|
+
generatePublicId,
|
|
184
|
+
job,
|
|
185
|
+
parseDelay,
|
|
186
|
+
processJobCallback,
|
|
187
|
+
signPayload,
|
|
188
|
+
verifySignature
|
|
189
|
+
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,eAAe;AACf,OAAO,EAAE,GAAG,EAA2C,MAAM,UAAU,CAAC;AAExE,SAAS;AACT,OAAO,EACL,eAAe,GAKhB,MAAM,aAAa,CAAC;AAErB,YAAY;AACZ,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3F,kBAAkB;AAClB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE7E,UAAU;AACV,OAAO,EACL,kBAAkB,GAGnB,MAAM,cAAc,CAAC"}
|
package/dist/router.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
-
import type { EnqueueOptions, JobContext } from './types.js';
|
|
2
|
+
import type { EnqueueOptions, EnqueueResult, JobContext } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* A job definition - the basic building block
|
|
5
5
|
*/
|
|
@@ -21,9 +21,7 @@ export declare function createJobRouter<T extends Record<string, AnyJobDefinitio
|
|
|
21
21
|
*/
|
|
22
22
|
export interface CallableJob<TInput, TOutput> {
|
|
23
23
|
/** Enqueue this job for execution */
|
|
24
|
-
enqueue: (input: TInput, options?: EnqueueOptions) => Promise<
|
|
25
|
-
jobId: string;
|
|
26
|
-
}>;
|
|
24
|
+
enqueue: (input: TInput, options?: EnqueueOptions) => Promise<EnqueueResult>;
|
|
27
25
|
/** The underlying job definition */
|
|
28
26
|
_def: AnyJobDefinition;
|
|
29
27
|
}
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC;IACpB,OAAO,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAEzD;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAEtF;AAED;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,MAAM,EAAE,OAAO;IAC1C,qCAAqC;IACrC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7E,oCAAoC;IACpC,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED;;GAEG;AACH,KAAK,eAAe,CAAC,CAAC,SAAS,gBAAgB,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GACpF,CAAC,GACD,KAAK,CAAC;AAEV;;GAEG;AACH,KAAK,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,IAAI,CAAC,CAAC,SAAS,CAAC,SAAS,CACvE,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,KACrB,OAAO,CAAC,MAAM,CAAC,CAAC,GACjB,CAAC,GACD,KAAK,CAAC;AAEV;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,IAAI;KACzE,CAAC,IAAI,MAAM,CAAC,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3E,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -66,6 +66,28 @@ export interface QueuedJob {
|
|
|
66
66
|
backoffStrategy: BackoffStrategy;
|
|
67
67
|
backoffDelay: number;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Job status as returned by the API
|
|
71
|
+
*/
|
|
72
|
+
export interface JobStatusResponse {
|
|
73
|
+
publicId: string;
|
|
74
|
+
name: string;
|
|
75
|
+
status: JobStatus;
|
|
76
|
+
attempt: number;
|
|
77
|
+
maxAttempts: number;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
startedAt: string | null;
|
|
80
|
+
completedAt: string | null;
|
|
81
|
+
result: unknown;
|
|
82
|
+
error: string | null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returned from enqueue — includes jobId and a polling handle
|
|
86
|
+
*/
|
|
87
|
+
export interface EnqueueResult {
|
|
88
|
+
jobId: string;
|
|
89
|
+
getStatus: () => Promise<JobStatusResponse>;
|
|
90
|
+
}
|
|
69
91
|
/**
|
|
70
92
|
* Result of a job execution
|
|
71
93
|
*/
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAErF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO;IAChE,0CAA0C;IAC1C,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,iDAAiD;IACjD,OAAO,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,mCAAmC;IACnC,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,MAAM,GAAG,OAAO;IAC1C,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS,CAAC,OAAO,GAAG,OAAO;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@queuebase/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
-
"import": "./dist/index.js"
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"require": "./dist/index.cjs"
|
|
9
10
|
}
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
@@ -20,12 +21,13 @@
|
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@types/node": "^22.19.9",
|
|
24
|
+
"tsup": "^8.4.0",
|
|
23
25
|
"typescript": "^5.7.2",
|
|
24
26
|
"vitest": "^2.1.8",
|
|
25
27
|
"@queuebase/tsconfig": "0.0.0"
|
|
26
28
|
},
|
|
27
29
|
"scripts": {
|
|
28
|
-
"build": "
|
|
30
|
+
"build": "tsup",
|
|
29
31
|
"dev": "tsc -b --watch",
|
|
30
32
|
"clean": "rm -rf dist .turbo *.tsbuildinfo",
|
|
31
33
|
"typecheck": "tsc --noEmit",
|