@simpleq/sdk 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SimpleQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,278 @@
1
+ # @simpleq/sdk
2
+
3
+ Official Node/TypeScript SDK for [SimpleQ](https://docs.simpleq.io): publish jobs, verify webhook signatures, and run the ack-mode callbacks. ESM + CommonJS, bundled types, zero runtime dependencies. Requires Node 22+. All durations are in **seconds**. The live API contract is machine-readable at [docs.simpleq.io/openapi.json](https://docs.simpleq.io/openapi.json).
4
+
5
+ ```bash
6
+ npm install @simpleq/sdk
7
+ ```
8
+
9
+ ## Client
10
+
11
+ An API key is **required** — pass `apiKey`, or set the `SIMPLEQ_API_KEY` environment variable and construct with no arguments. The constructor throws if neither is present.
12
+
13
+ ```ts
14
+ import { SimpleQ } from '@simpleq/sdk';
15
+
16
+ const simpleq = new SimpleQ(); // reads SIMPLEQ_API_KEY
17
+ // or explicitly:
18
+ const simpleq2 = new SimpleQ({ apiKey: 'sq_live_…' });
19
+ ```
20
+
21
+ `SimpleQ(options)`:
22
+
23
+ | Option | Required | Default | Meaning |
24
+ | --- | --- | --- | --- |
25
+ | `apiKey` | yes (here or via `SIMPLEQ_API_KEY`) | `process.env.SIMPLEQ_API_KEY` | API key (`sq_live_…`). |
26
+ | `timeout` | no | `30` | Timeout for the SDK's **own** requests to the SimpleQ API, in seconds. |
27
+ | `maxRetries` | no | `2` | How many times the SDK retries **its own** API request on a transient failure (network / 5xx / 429). |
28
+
29
+ `timeout` and `maxRetries` govern the SDK's calls to the SimpleQ API (publishing, ack/nack/defer, getJob). They do **not** control how SimpleQ retries delivery to your webhook — `maxAttempts`, backoff, rate limits, and `ackTimeout` are [per-queue settings](https://docs.simpleq.io/reference/#queues) you configure when you create or edit a queue.
30
+
31
+ ## publish
32
+
33
+ Publish a job to a queue.
34
+
35
+ ```ts
36
+ const job = await simpleq.publish('emails', {
37
+ payload: { to: 'a@b.com', template: 'welcome' },
38
+ idempotencyKey: `welcome:${userId}`,
39
+ delay: 30,
40
+ });
41
+ // job: {
42
+ // id: string;
43
+ // status: 'pending' | 'processing' | 'awaiting_ack' | 'completed' | 'failed' | 'dead';
44
+ // createdAt: string; // ISO 8601
45
+ // }
46
+ ```
47
+
48
+ `publish(queueName, params)`:
49
+
50
+ | Param | Required | Constraint | Meaning |
51
+ | --- | --- | --- | --- |
52
+ | `payload` | yes | JSON object | Delivered verbatim to the queue's webhook. |
53
+ | `idempotencyKey` | no | ≤255 chars | Cross-call dedupe: publishing again with the same key returns the existing job. |
54
+ | `delay` | no | seconds, 0–86400 | Defer first delivery. |
55
+
56
+ If you omit the `idempotencyKey`, the `publish` method generates one per call and reuses it across the SDK's automatic retries, so a retried request can never create a duplicate job. Pass your own key to dedupe across *separate* calls (e.g. one welcome email per user). A `201` (created — status `pending`) and a `200` (idempotent hit — returns the existing job, any status) both resolve as success.
57
+
58
+ ## Verifying webhooks
59
+
60
+ SimpleQ signs every delivery with an `x-simpleq-signature` header (HMAC-SHA256 of the raw body, keyed with the queue's `signingSecret`). Verify it before trusting the payload.
61
+
62
+ - **`verifyWebhook(rawBody, header, signingSecret)`** — the framework-agnostic primitive. Returns the typed envelope or throws `SignatureVerificationError`. Use it anywhere: Lambda, Cloud Functions, Fastify, Hono, Next.js route handlers, raw `http`.
63
+ - **Framework adapters** wrap that primitive for you, including the raw-body capture: **Express** via `simpleqWebhookHandler` (`@simpleq/sdk/express`), and **Nest.js** via `SimpleQModule`, `SimpleQSignatureGuard`, `@SimpleQJob()`, and `SimpleQBackpressureFilter` (`@simpleq/sdk/nest`).
64
+
65
+ > **`signingSecret` is per-queue.** Each queue has its own — store each as its own env var (e.g. `SQ_SIGNING_SECRET_EMAILS`). A worker serving multiple queues should expose **one webhook route per queue**, each wired to that queue's secret (you can't safely pick the secret from the unverified body).
66
+
67
+ ### verifyWebhook — any framework
68
+
69
+ The primitive: verify the raw body and get back the typed envelope, or a `SignatureVerificationError`.
70
+
71
+ ```ts
72
+ import { verifyWebhook } from '@simpleq/sdk';
73
+
74
+ const job = verifyWebhook(rawBody, signatureHeader, process.env.SQ_SIGNING_SECRET_EMAILS!);
75
+ // job: {
76
+ // id: string; // job ID — pass to ack/nack/defer
77
+ // queue: string; // queue name
78
+ // payload: Record<string, unknown>; // your data, verbatim
79
+ // attempt: number; // starts at 1
80
+ // maxAttempts: number;
81
+ // createdAt: string; // ISO 8601 — when the job was published
82
+ // }
83
+ ```
84
+
85
+ `rawBody` is a `string`, `Buffer`, or `Uint8Array`. `verifyWebhookSignature(rawBody, header, secret): boolean` is the non-throwing variant.
86
+
87
+ ### Express — `@simpleq/sdk/express`
88
+
89
+ `simpleqWebhookHandler` captures the raw body, verifies the signature, and maps your handler's outcome to a response (see [Response contract](#response-contract)).
90
+
91
+ ```ts
92
+ import express from 'express';
93
+ import { simpleqWebhookHandler } from '@simpleq/sdk/express';
94
+
95
+ const app = express();
96
+
97
+ app.post(
98
+ '/webhook',
99
+ simpleqWebhookHandler(process.env.SQ_SIGNING_SECRET_EMAILS!, async (job) => {
100
+ // `job` is the verified, typed envelope. Resolve → 200.
101
+ console.log(`processing job ${job.id} from queue ${job.queue}`);
102
+ }),
103
+ );
104
+ ```
105
+
106
+ ### Nest.js — `@simpleq/sdk/nest`
107
+
108
+ Create the app with `rawBody: true`, register `SimpleQModule.forRoot`, then guard the route and inject the typed job.
109
+
110
+ ```ts
111
+ // main.ts
112
+ const app = await NestFactory.create(AppModule, { rawBody: true });
113
+ ```
114
+
115
+ ```ts
116
+ // app.module.ts
117
+ import { Module } from '@nestjs/common';
118
+ import { SimpleQModule } from '@simpleq/sdk/nest';
119
+
120
+ @Module({
121
+ imports: [SimpleQModule.forRoot({ signingSecret: process.env.SQ_SIGNING_SECRET_EMAILS! })],
122
+ controllers: [WebhookController],
123
+ })
124
+ export class AppModule {}
125
+ ```
126
+
127
+ ```ts
128
+ // webhook.controller.ts
129
+ import { Controller, HttpCode, Post, UseFilters, UseGuards } from '@nestjs/common';
130
+ import type { WebhookPayload } from '@simpleq/sdk';
131
+ import { SimpleQSignatureGuard, SimpleQJob, SimpleQBackpressureFilter } from '@simpleq/sdk/nest';
132
+
133
+ @Controller('webhook')
134
+ export class WebhookController {
135
+ @Post()
136
+ @HttpCode(200)
137
+ @UseGuards(SimpleQSignatureGuard)
138
+ @UseFilters(SimpleQBackpressureFilter)
139
+ async handle(@SimpleQJob() job: WebhookPayload) {
140
+ console.log(`processing job ${job.id} from queue ${job.queue}`);
141
+ }
142
+ }
143
+ ```
144
+
145
+ ## Response contract
146
+
147
+ The adapters (`simpleqWebhookHandler`, the Nest guard) map your handler's outcome to a SimpleQ response.
148
+
149
+ | Handler | Response | SimpleQ |
150
+ | --- | --- | --- |
151
+ | resolves | `200` | **standard mode:** completes the job · **ack mode:** moves it to `awaiting_ack` |
152
+ | `throw new SimpleQBackpressure(retryAfter?, { status? })` | that status (default `503`) + `Retry-After` | holds and redelivers — no attempt burned |
153
+ | throws anything else | `500` | counts a failed attempt, retries with backoff |
154
+ | bad signature | `401` | not a job outcome |
155
+
156
+ **Backpressure** (a `429`/`503`/`529` from your downstream) tells SimpleQ to hold and redeliver the job without burning an attempt. In a **standard-mode** handler, `throw` a `SimpleQBackpressure` and the adapter turns it into the response; `SimpleQBackpressure.from(err, { fallback })` builds one from the provider error, relaying its status and `Retry-After`.
157
+
158
+ ```ts
159
+ import { SimpleQBackpressure } from '@simpleq/sdk';
160
+
161
+ simpleqWebhookHandler(process.env.SQ_SIGNING_SECRET_EMAILS!, async (job) => {
162
+ try {
163
+ await callProvider(job.payload); // your downstream call
164
+ } catch (err) {
165
+ if (err.status === 429 || err.status === 503 || err.status === 529) {
166
+ throw SimpleQBackpressure.from(err, { fallback: 30 });
167
+ }
168
+ throw err; // anything else → 500, retried with backoff
169
+ }
170
+ });
171
+ ```
172
+
173
+ In **ack mode** you've already returned `200`, so there's no response left to throw into — call `simpleq.defer(...)` out of band instead (see below).
174
+
175
+ ## Ack mode
176
+
177
+ For work longer than the 15s delivery window, use an ack-mode queue: return `200` immediately, then do the work and report the outcome with `ack` / `nack` / `defer`. Send the `200` yourself from the handler (via `res`), then run the work — the adapter won't respond again once you have.
178
+
179
+ ```ts
180
+ import express from 'express';
181
+ import { SimpleQ, retryAfterSeconds } from '@simpleq/sdk';
182
+ import { simpleqWebhookHandler } from '@simpleq/sdk/express';
183
+
184
+ const simpleq = new SimpleQ(); // reads SIMPLEQ_API_KEY
185
+ const app = express();
186
+
187
+ app.post(
188
+ '/webhook',
189
+ simpleqWebhookHandler(process.env.SQ_SIGNING_SECRET_EMAILS!, async (job, { res }) => {
190
+ res.status(200).end(); // ack mode: respond now, do the work after
191
+
192
+ try {
193
+ await callProvider(job.payload); // your downstream call
194
+ await simpleq.ack(job.id);
195
+ } catch (err) {
196
+ if (err.status === 429 || err.status === 503 || err.status === 529) {
197
+ // Backpressure: hold and redeliver, no attempt burned. Relay the provider's Retry-After.
198
+ await simpleq.defer(job.id, { retryAfter: retryAfterSeconds(err) ?? 10 });
199
+ } else if (err.status >= 400 && err.status < 500) {
200
+ await simpleq.nack(job.id, { retryable: false, reason: `downstream ${err.status}` }); // dead-letter
201
+ } else {
202
+ await simpleq.nack(job.id, { retryable: true, reason: String(err) }); // retry with backoff
203
+ }
204
+ }
205
+ }),
206
+ );
207
+ ```
208
+
209
+ The three callbacks, each resolving to `{ id: string; accepted: true }`:
210
+
211
+ | Callback | Effect |
212
+ | --- | --- |
213
+ | `simpleq.ack(id)` | Mark the job completed. |
214
+ | `simpleq.nack(id, { retryable?, reason? })` | Mark failed. `retryable: false` dead-letters immediately; default `true` retries with backoff. `reason` ≤500 chars. |
215
+ | `simpleq.defer(id, { retryAfter, reason? })` | Backpressure: held and redelivered, no attempt burned. `retryAfter` seconds (0–3600); `reason` ≤500 chars. |
216
+
217
+ ## getJob
218
+
219
+ Fetch a job's current status and attempt history.
220
+
221
+ ```ts
222
+ const job = await simpleq.getJob(jobId);
223
+ // job: {
224
+ // id: string;
225
+ // queue: string; // queue name
226
+ // status: 'pending' | 'processing' | 'awaiting_ack' | 'completed' | 'failed' | 'dead';
227
+ // attempts: number;
228
+ // maxAttempts: number;
229
+ // idempotencyKey: string | null;
230
+ // payload: Record<string, unknown>;
231
+ // scheduledFor: string; // ISO 8601
232
+ // lastError: string | null;
233
+ // history: Array<{
234
+ // attempt: number;
235
+ // status: 'success' | 'failed' | 'nacked' | 'deferred';
236
+ // error: string | null;
237
+ // webhookStatusCode: number | null;
238
+ // timestamp: string; // ISO 8601
239
+ // }>;
240
+ // createdAt: string; // ISO 8601
241
+ // completedAt: string | null; // ISO 8601
242
+ // }
243
+ ```
244
+
245
+ ## Errors
246
+
247
+ Every thrown value extends `SimpleQError`. API responses throw `ApiError` (or a subclass) carrying `.status` and `.body`.
248
+
249
+ | Class | Thrown when | Extra fields |
250
+ | --- | --- | --- |
251
+ | `AuthenticationError` | `401`/`403` — missing, invalid, or revoked API key | `.status`, `.body` |
252
+ | `ValidationError` | `400` — request validation failed | `.status`, `.body` (field-level details) |
253
+ | `NotFoundError` | `404` — queue or job not found | `.status`, `.body` |
254
+ | `RateLimitError` | `429` — rate limited | `.status`, `.body`, `.retryAfter` (seconds) |
255
+ | `ApiError` | any other non-2xx | `.status`, `.body` |
256
+ | `SimpleQConnectionError` | network failure or timeout | — |
257
+ | `SignatureVerificationError` | webhook signature mismatch | — |
258
+ | `SimpleQError` | base class — catch-all (also config errors, e.g. missing API key) | — |
259
+
260
+ ```ts
261
+ import { ApiError, NotFoundError, RateLimitError, SimpleQError } from '@simpleq/sdk';
262
+
263
+ try {
264
+ await simpleq.publish('emails', { payload: { to: 'a@b.com' } });
265
+ } catch (err) {
266
+ if (err instanceof NotFoundError) {
267
+ console.error('Queue does not exist:', err.body);
268
+ } else if (err instanceof RateLimitError) {
269
+ console.error(`Rate limited; retry after ${err.retryAfter}s`);
270
+ } else if (err instanceof ApiError) {
271
+ console.error(`SimpleQ API error ${err.status}:`, err.body);
272
+ } else if (err instanceof SimpleQError) {
273
+ console.error('Network, signature, or config error:', err.message);
274
+ } else {
275
+ throw err;
276
+ }
277
+ }
278
+ ```
@@ -0,0 +1,138 @@
1
+ import { timingSafeEqual, createHmac } from 'crypto';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __decorateClass = (decorators, target, key, kind) => {
6
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
7
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
8
+ if (decorator = decorators[i])
9
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
10
+ if (kind && result) __defProp(target, key, result);
11
+ return result;
12
+ };
13
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
14
+
15
+ // src/errors.ts
16
+ var SimpleQError = class extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = this.constructor.name;
20
+ }
21
+ };
22
+ var SimpleQConnectionError = class extends SimpleQError {
23
+ };
24
+ var SignatureVerificationError = class extends SimpleQError {
25
+ };
26
+ var SimpleQBackpressure = class _SimpleQBackpressure extends SimpleQError {
27
+ constructor(retryAfter, options) {
28
+ const detail = retryAfter != null ? ` (retry after ${retryAfter}s)` : "";
29
+ super(options?.reason ?? `SimpleQ backpressure${detail}`);
30
+ this.retryAfter = retryAfter;
31
+ this.status = options?.status ?? 503;
32
+ }
33
+ /**
34
+ * Build a backpressure signal directly from a provider error (Anthropic, OpenAI, any
35
+ * HTTP-shaped error). Reads `err.status` (429/503/529 pass through; anything else maps
36
+ * to 503) and the `Retry-After` header in seconds from `err.headers` or
37
+ * `err.response.headers` (plain object or Headers). When no header is present,
38
+ * `options.fallback` seconds is used; with neither, SimpleQ applies its own 60s hold.
39
+ */
40
+ static from(err, options) {
41
+ const e = err;
42
+ const status = e?.status === 429 || e?.status === 503 || e?.status === 529 ? e.status : 503;
43
+ const retryAfter = retryAfterSeconds(err) ?? options?.fallback;
44
+ const reason = options?.reason ?? (typeof e?.message === "string" && e.message ? e.message : void 0);
45
+ return new _SimpleQBackpressure(retryAfter, { status, reason });
46
+ }
47
+ };
48
+ function readHeader(headers, name) {
49
+ if (!headers || typeof headers !== "object") return void 0;
50
+ if (typeof headers.get === "function") {
51
+ return headers.get(name) ?? void 0;
52
+ }
53
+ const record = headers;
54
+ const value = record[name.toLowerCase()] ?? record["Retry-After"];
55
+ return typeof value === "string" ? value : void 0;
56
+ }
57
+ function retryAfterSeconds(err) {
58
+ const e = err;
59
+ const raw = readHeader(e?.headers, "retry-after") ?? readHeader(e?.response?.headers, "retry-after");
60
+ if (raw === void 0) return void 0;
61
+ const seconds = Number(raw);
62
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
63
+ }
64
+ var ApiError = class extends SimpleQError {
65
+ constructor(message, status, body) {
66
+ super(message);
67
+ this.status = status;
68
+ this.body = body;
69
+ }
70
+ };
71
+ var AuthenticationError = class extends ApiError {
72
+ };
73
+ var ValidationError = class extends ApiError {
74
+ };
75
+ var NotFoundError = class extends ApiError {
76
+ };
77
+ var RateLimitError = class extends ApiError {
78
+ constructor(message, status, body, retryAfter) {
79
+ super(message, status, body);
80
+ this.retryAfter = retryAfter;
81
+ }
82
+ };
83
+ function extractMessage(status, body) {
84
+ if (body && typeof body === "object" && "error" in body) {
85
+ const err = body.error;
86
+ if (typeof err === "string") return err;
87
+ if (err && typeof err === "object") return `Validation failed: ${JSON.stringify(err)}`;
88
+ }
89
+ return `SimpleQ API error (HTTP ${status})`;
90
+ }
91
+ function mapApiError(status, body, headers) {
92
+ const message = extractMessage(status, body);
93
+ switch (status) {
94
+ case 400:
95
+ return new ValidationError(message, status, body);
96
+ case 401:
97
+ case 403:
98
+ return new AuthenticationError(message, status, body);
99
+ case 404:
100
+ return new NotFoundError(message, status, body);
101
+ case 429: {
102
+ const raw = headers?.get("retry-after");
103
+ const retryAfter = raw != null ? Number(raw) : NaN;
104
+ return new RateLimitError(message, status, body, Number.isFinite(retryAfter) ? retryAfter : void 0);
105
+ }
106
+ default:
107
+ return new ApiError(message, status, body);
108
+ }
109
+ }
110
+
111
+ // src/webhooks.ts
112
+ var SIGNATURE_HEADER = "x-simpleq-signature";
113
+ function toBuffer(rawBody) {
114
+ if (typeof rawBody === "string") return Buffer.from(rawBody, "utf8");
115
+ if (Buffer.isBuffer(rawBody)) return rawBody;
116
+ return Buffer.from(rawBody);
117
+ }
118
+ function expectedSignature(rawBody, signingSecret) {
119
+ return "sha256=" + createHmac("sha256", signingSecret).update(toBuffer(rawBody)).digest("hex");
120
+ }
121
+ function verifyWebhookSignature(rawBody, signatureHeader, signingSecret) {
122
+ if (!signatureHeader) return false;
123
+ const expected = Buffer.from(expectedSignature(rawBody, signingSecret));
124
+ const received = Buffer.from(signatureHeader);
125
+ if (expected.length !== received.length) return false;
126
+ return timingSafeEqual(expected, received);
127
+ }
128
+ function verifyWebhook(rawBody, signatureHeader, signingSecret) {
129
+ if (!verifyWebhookSignature(rawBody, signatureHeader, signingSecret)) {
130
+ throw new SignatureVerificationError("SimpleQ webhook signature verification failed");
131
+ }
132
+ const text = typeof rawBody === "string" ? rawBody : toBuffer(rawBody).toString("utf8");
133
+ return JSON.parse(text);
134
+ }
135
+
136
+ export { ApiError, AuthenticationError, NotFoundError, RateLimitError, SIGNATURE_HEADER, SignatureVerificationError, SimpleQBackpressure, SimpleQConnectionError, SimpleQError, ValidationError, __decorateClass, __decorateParam, mapApiError, retryAfterSeconds, verifyWebhook, verifyWebhookSignature };
137
+ //# sourceMappingURL=chunk-72DDDNF6.js.map
138
+ //# sourceMappingURL=chunk-72DDDNF6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"chunk-72DDDNF6.js","sourcesContent":["// Error and signal types for @simpleq/sdk.\n\n/** Base class for everything thrown by the SDK. Catch this to handle any SimpleQ error. */\nexport class SimpleQError extends Error {\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\n/** A network failure, timeout, or aborted request — retryable. */\nexport class SimpleQConnectionError extends SimpleQError {}\n\n/** Thrown by `verifyWebhook` when a webhook signature does not verify. */\nexport class SignatureVerificationError extends SimpleQError {}\n\nexport type BackpressureStatus = 429 | 503 | 529;\n\n/**\n * A backpressure signal — not a failure. Throw this from a standard-mode webhook handler to\n * tell the adapter to respond with `429`/`503`/`529` and a `Retry-After`. SimpleQ then holds\n * the job and redelivers it without burning a delivery attempt.\n */\nexport class SimpleQBackpressure extends SimpleQError {\n /** Seconds to hold the job before redelivery. Omit to let SimpleQ pick its fallback. */\n readonly retryAfter?: number;\n /** HTTP status the adapter responds with. Defaults to `503`. */\n readonly status: BackpressureStatus;\n\n constructor(retryAfter?: number, options?: { status?: BackpressureStatus; reason?: string }) {\n const detail = retryAfter != null ? ` (retry after ${retryAfter}s)` : '';\n super(options?.reason ?? `SimpleQ backpressure${detail}`);\n this.retryAfter = retryAfter;\n this.status = options?.status ?? 503;\n }\n\n /**\n * Build a backpressure signal directly from a provider error (Anthropic, OpenAI, any\n * HTTP-shaped error). Reads `err.status` (429/503/529 pass through; anything else maps\n * to 503) and the `Retry-After` header in seconds from `err.headers` or\n * `err.response.headers` (plain object or Headers). When no header is present,\n * `options.fallback` seconds is used; with neither, SimpleQ applies its own 60s hold.\n */\n static from(err: unknown, options?: { fallback?: number; reason?: string }): SimpleQBackpressure {\n const e = err as { status?: unknown; message?: unknown } | null | undefined;\n const status: BackpressureStatus =\n e?.status === 429 || e?.status === 503 || e?.status === 529 ? e.status : 503;\n const retryAfter = retryAfterSeconds(err) ?? options?.fallback;\n const reason =\n options?.reason ?? (typeof e?.message === 'string' && e.message ? e.message : undefined);\n return new SimpleQBackpressure(retryAfter, { status, reason });\n }\n}\n\nfunction readHeader(headers: unknown, name: string): string | undefined {\n if (!headers || typeof headers !== 'object') return undefined;\n if (typeof (headers as Headers).get === 'function') {\n return (headers as Headers).get(name) ?? undefined;\n }\n const record = headers as Record<string, unknown>;\n const value = record[name.toLowerCase()] ?? record['Retry-After'];\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Read the `Retry-After` value, in **seconds**, from a provider error (Anthropic, OpenAI, any\n * HTTP-shaped error). Looks at `err.headers` and `err.response.headers` (plain object or a\n * `Headers` instance). Returns `undefined` when the header is absent or non-numeric (e.g. an\n * HTTP-date). Pair with `simpleq.defer` in ack mode: `defer(id, { retryAfter: retryAfterSeconds(err) ?? 10 })`.\n */\nexport function retryAfterSeconds(err: unknown): number | undefined {\n const e = err as { headers?: unknown; response?: { headers?: unknown } } | null | undefined;\n const raw =\n readHeader(e?.headers, 'retry-after') ?? readHeader(e?.response?.headers, 'retry-after');\n if (raw === undefined) return undefined;\n const seconds = Number(raw);\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : undefined;\n}\n\n/** Any non-2xx response from the SimpleQ API. */\nexport class ApiError extends SimpleQError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.status = status;\n this.body = body;\n }\n}\n\n/** `401`/`403` — the API key is missing, invalid, or revoked. */\nexport class AuthenticationError extends ApiError {}\n\n/** `400` — request validation failed. `body.error` carries the field-level details. */\nexport class ValidationError extends ApiError {}\n\n/** `404` — the queue or job was not found. */\nexport class NotFoundError extends ApiError {}\n\n/** `429` — rate limited. `retryAfter` is the `Retry-After` header in seconds, if present. */\nexport class RateLimitError extends ApiError {\n readonly retryAfter?: number;\n\n constructor(message: string, status: number, body: unknown, retryAfter?: number) {\n super(message, status, body);\n this.retryAfter = retryAfter;\n }\n}\n\nfunction extractMessage(status: number, body: unknown): string {\n if (body && typeof body === 'object' && 'error' in body) {\n const err = (body as { error: unknown }).error;\n if (typeof err === 'string') return err;\n if (err && typeof err === 'object') return `Validation failed: ${JSON.stringify(err)}`;\n }\n return `SimpleQ API error (HTTP ${status})`;\n}\n\n/** Map an HTTP status + parsed body to the right ApiError subclass. */\nexport function mapApiError(status: number, body: unknown, headers?: Headers): ApiError {\n const message = extractMessage(status, body);\n switch (status) {\n case 400:\n return new ValidationError(message, status, body);\n case 401:\n case 403:\n return new AuthenticationError(message, status, body);\n case 404:\n return new NotFoundError(message, status, body);\n case 429: {\n const raw = headers?.get('retry-after');\n const retryAfter = raw != null ? Number(raw) : NaN;\n return new RateLimitError(message, status, body, Number.isFinite(retryAfter) ? retryAfter : undefined);\n }\n default:\n return new ApiError(message, status, body);\n }\n}\n","// Standalone webhook verification — no API key or client required. Importable as\n// `@simpleq/sdk/webhooks` with only `node:crypto` pulled in.\nimport { createHmac, timingSafeEqual } from 'node:crypto';\nimport { SignatureVerificationError } from './errors.js';\nimport type { WebhookPayload } from './types.js';\n\n/** The header SimpleQ sends with every webhook delivery. */\nexport const SIGNATURE_HEADER = 'x-simpleq-signature';\n\nfunction toBuffer(rawBody: string | Buffer | Uint8Array): Buffer {\n if (typeof rawBody === 'string') return Buffer.from(rawBody, 'utf8');\n if (Buffer.isBuffer(rawBody)) return rawBody;\n return Buffer.from(rawBody);\n}\n\nfunction expectedSignature(rawBody: string | Buffer | Uint8Array, signingSecret: string): string {\n return 'sha256=' + createHmac('sha256', signingSecret).update(toBuffer(rawBody)).digest('hex');\n}\n\n/**\n * Verify the `x-simpleq-signature` header against the raw request body, in constant time.\n * Returns a boolean and never throws — a missing or malformed header simply returns `false`.\n *\n * Always pass the *raw* body bytes (a string or Buffer), never a re-serialized parsed object.\n */\nexport function verifyWebhookSignature(\n rawBody: string | Buffer | Uint8Array,\n signatureHeader: string | null | undefined,\n signingSecret: string,\n): boolean {\n if (!signatureHeader) return false;\n const expected = Buffer.from(expectedSignature(rawBody, signingSecret));\n const received = Buffer.from(signatureHeader);\n // timingSafeEqual throws on differing lengths — guard before the constant-time compare.\n if (expected.length !== received.length) return false;\n return timingSafeEqual(expected, received);\n}\n\n/**\n * Verify the signature and return the parsed, typed webhook envelope (the equivalent of\n * Stripe's `constructEvent`). Throws `SignatureVerificationError` if the signature does\n * not match — the body is only parsed after verification passes, so a tampered payload\n * never reaches `JSON.parse`.\n */\nexport function verifyWebhook(\n rawBody: string | Buffer | Uint8Array,\n signatureHeader: string | null | undefined,\n signingSecret: string,\n): WebhookPayload {\n if (!verifyWebhookSignature(rawBody, signatureHeader, signingSecret)) {\n throw new SignatureVerificationError('SimpleQ webhook signature verification failed');\n }\n const text = typeof rawBody === 'string' ? rawBody : toBuffer(rawBody).toString('utf8');\n return JSON.parse(text) as WebhookPayload;\n}\n"]}
@@ -0,0 +1,66 @@
1
+ /** Base class for everything thrown by the SDK. Catch this to handle any SimpleQ error. */
2
+ declare class SimpleQError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** A network failure, timeout, or aborted request — retryable. */
6
+ declare class SimpleQConnectionError extends SimpleQError {
7
+ }
8
+ /** Thrown by `verifyWebhook` when a webhook signature does not verify. */
9
+ declare class SignatureVerificationError extends SimpleQError {
10
+ }
11
+ type BackpressureStatus = 429 | 503 | 529;
12
+ /**
13
+ * A backpressure signal — not a failure. Throw this from a standard-mode webhook handler to
14
+ * tell the adapter to respond with `429`/`503`/`529` and a `Retry-After`. SimpleQ then holds
15
+ * the job and redelivers it without burning a delivery attempt.
16
+ */
17
+ declare class SimpleQBackpressure extends SimpleQError {
18
+ /** Seconds to hold the job before redelivery. Omit to let SimpleQ pick its fallback. */
19
+ readonly retryAfter?: number;
20
+ /** HTTP status the adapter responds with. Defaults to `503`. */
21
+ readonly status: BackpressureStatus;
22
+ constructor(retryAfter?: number, options?: {
23
+ status?: BackpressureStatus;
24
+ reason?: string;
25
+ });
26
+ /**
27
+ * Build a backpressure signal directly from a provider error (Anthropic, OpenAI, any
28
+ * HTTP-shaped error). Reads `err.status` (429/503/529 pass through; anything else maps
29
+ * to 503) and the `Retry-After` header in seconds from `err.headers` or
30
+ * `err.response.headers` (plain object or Headers). When no header is present,
31
+ * `options.fallback` seconds is used; with neither, SimpleQ applies its own 60s hold.
32
+ */
33
+ static from(err: unknown, options?: {
34
+ fallback?: number;
35
+ reason?: string;
36
+ }): SimpleQBackpressure;
37
+ }
38
+ /**
39
+ * Read the `Retry-After` value, in **seconds**, from a provider error (Anthropic, OpenAI, any
40
+ * HTTP-shaped error). Looks at `err.headers` and `err.response.headers` (plain object or a
41
+ * `Headers` instance). Returns `undefined` when the header is absent or non-numeric (e.g. an
42
+ * HTTP-date). Pair with `simpleq.defer` in ack mode: `defer(id, { retryAfter: retryAfterSeconds(err) ?? 10 })`.
43
+ */
44
+ declare function retryAfterSeconds(err: unknown): number | undefined;
45
+ /** Any non-2xx response from the SimpleQ API. */
46
+ declare class ApiError extends SimpleQError {
47
+ readonly status: number;
48
+ readonly body: unknown;
49
+ constructor(message: string, status: number, body: unknown);
50
+ }
51
+ /** `401`/`403` — the API key is missing, invalid, or revoked. */
52
+ declare class AuthenticationError extends ApiError {
53
+ }
54
+ /** `400` — request validation failed. `body.error` carries the field-level details. */
55
+ declare class ValidationError extends ApiError {
56
+ }
57
+ /** `404` — the queue or job was not found. */
58
+ declare class NotFoundError extends ApiError {
59
+ }
60
+ /** `429` — rate limited. `retryAfter` is the `Retry-After` header in seconds, if present. */
61
+ declare class RateLimitError extends ApiError {
62
+ readonly retryAfter?: number;
63
+ constructor(message: string, status: number, body: unknown, retryAfter?: number);
64
+ }
65
+
66
+ export { ApiError as A, type BackpressureStatus as B, NotFoundError as N, RateLimitError as R, SignatureVerificationError as S, ValidationError as V, AuthenticationError as a, SimpleQBackpressure as b, SimpleQConnectionError as c, SimpleQError as d, retryAfterSeconds as r };
@@ -0,0 +1,66 @@
1
+ /** Base class for everything thrown by the SDK. Catch this to handle any SimpleQ error. */
2
+ declare class SimpleQError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** A network failure, timeout, or aborted request — retryable. */
6
+ declare class SimpleQConnectionError extends SimpleQError {
7
+ }
8
+ /** Thrown by `verifyWebhook` when a webhook signature does not verify. */
9
+ declare class SignatureVerificationError extends SimpleQError {
10
+ }
11
+ type BackpressureStatus = 429 | 503 | 529;
12
+ /**
13
+ * A backpressure signal — not a failure. Throw this from a standard-mode webhook handler to
14
+ * tell the adapter to respond with `429`/`503`/`529` and a `Retry-After`. SimpleQ then holds
15
+ * the job and redelivers it without burning a delivery attempt.
16
+ */
17
+ declare class SimpleQBackpressure extends SimpleQError {
18
+ /** Seconds to hold the job before redelivery. Omit to let SimpleQ pick its fallback. */
19
+ readonly retryAfter?: number;
20
+ /** HTTP status the adapter responds with. Defaults to `503`. */
21
+ readonly status: BackpressureStatus;
22
+ constructor(retryAfter?: number, options?: {
23
+ status?: BackpressureStatus;
24
+ reason?: string;
25
+ });
26
+ /**
27
+ * Build a backpressure signal directly from a provider error (Anthropic, OpenAI, any
28
+ * HTTP-shaped error). Reads `err.status` (429/503/529 pass through; anything else maps
29
+ * to 503) and the `Retry-After` header in seconds from `err.headers` or
30
+ * `err.response.headers` (plain object or Headers). When no header is present,
31
+ * `options.fallback` seconds is used; with neither, SimpleQ applies its own 60s hold.
32
+ */
33
+ static from(err: unknown, options?: {
34
+ fallback?: number;
35
+ reason?: string;
36
+ }): SimpleQBackpressure;
37
+ }
38
+ /**
39
+ * Read the `Retry-After` value, in **seconds**, from a provider error (Anthropic, OpenAI, any
40
+ * HTTP-shaped error). Looks at `err.headers` and `err.response.headers` (plain object or a
41
+ * `Headers` instance). Returns `undefined` when the header is absent or non-numeric (e.g. an
42
+ * HTTP-date). Pair with `simpleq.defer` in ack mode: `defer(id, { retryAfter: retryAfterSeconds(err) ?? 10 })`.
43
+ */
44
+ declare function retryAfterSeconds(err: unknown): number | undefined;
45
+ /** Any non-2xx response from the SimpleQ API. */
46
+ declare class ApiError extends SimpleQError {
47
+ readonly status: number;
48
+ readonly body: unknown;
49
+ constructor(message: string, status: number, body: unknown);
50
+ }
51
+ /** `401`/`403` — the API key is missing, invalid, or revoked. */
52
+ declare class AuthenticationError extends ApiError {
53
+ }
54
+ /** `400` — request validation failed. `body.error` carries the field-level details. */
55
+ declare class ValidationError extends ApiError {
56
+ }
57
+ /** `404` — the queue or job was not found. */
58
+ declare class NotFoundError extends ApiError {
59
+ }
60
+ /** `429` — rate limited. `retryAfter` is the `Retry-After` header in seconds, if present. */
61
+ declare class RateLimitError extends ApiError {
62
+ readonly retryAfter?: number;
63
+ constructor(message: string, status: number, body: unknown, retryAfter?: number);
64
+ }
65
+
66
+ export { ApiError as A, type BackpressureStatus as B, NotFoundError as N, RateLimitError as R, SignatureVerificationError as S, ValidationError as V, AuthenticationError as a, SimpleQBackpressure as b, SimpleQConnectionError as c, SimpleQError as d, retryAfterSeconds as r };