@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.
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ var express = require('express');
4
+ var crypto = require('crypto');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var express__default = /*#__PURE__*/_interopDefault(express);
9
+
10
+ // src/express.ts
11
+
12
+ // src/errors.ts
13
+ var SimpleQError = class extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = this.constructor.name;
17
+ }
18
+ };
19
+ var SignatureVerificationError = class extends SimpleQError {
20
+ };
21
+ var SimpleQBackpressure = class _SimpleQBackpressure extends SimpleQError {
22
+ constructor(retryAfter, options) {
23
+ const detail = retryAfter != null ? ` (retry after ${retryAfter}s)` : "";
24
+ super(options?.reason ?? `SimpleQ backpressure${detail}`);
25
+ this.retryAfter = retryAfter;
26
+ this.status = options?.status ?? 503;
27
+ }
28
+ /**
29
+ * Build a backpressure signal directly from a provider error (Anthropic, OpenAI, any
30
+ * HTTP-shaped error). Reads `err.status` (429/503/529 pass through; anything else maps
31
+ * to 503) and the `Retry-After` header in seconds from `err.headers` or
32
+ * `err.response.headers` (plain object or Headers). When no header is present,
33
+ * `options.fallback` seconds is used; with neither, SimpleQ applies its own 60s hold.
34
+ */
35
+ static from(err, options) {
36
+ const e = err;
37
+ const status = e?.status === 429 || e?.status === 503 || e?.status === 529 ? e.status : 503;
38
+ const retryAfter = retryAfterSeconds(err) ?? options?.fallback;
39
+ const reason = options?.reason ?? (typeof e?.message === "string" && e.message ? e.message : void 0);
40
+ return new _SimpleQBackpressure(retryAfter, { status, reason });
41
+ }
42
+ };
43
+ function readHeader(headers, name) {
44
+ if (!headers || typeof headers !== "object") return void 0;
45
+ if (typeof headers.get === "function") {
46
+ return headers.get(name) ?? void 0;
47
+ }
48
+ const record = headers;
49
+ const value = record[name.toLowerCase()] ?? record["Retry-After"];
50
+ return typeof value === "string" ? value : void 0;
51
+ }
52
+ function retryAfterSeconds(err) {
53
+ const e = err;
54
+ const raw = readHeader(e?.headers, "retry-after") ?? readHeader(e?.response?.headers, "retry-after");
55
+ if (raw === void 0) return void 0;
56
+ const seconds = Number(raw);
57
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
58
+ }
59
+
60
+ // src/webhooks.ts
61
+ var SIGNATURE_HEADER = "x-simpleq-signature";
62
+ function toBuffer(rawBody) {
63
+ if (typeof rawBody === "string") return Buffer.from(rawBody, "utf8");
64
+ if (Buffer.isBuffer(rawBody)) return rawBody;
65
+ return Buffer.from(rawBody);
66
+ }
67
+ function expectedSignature(rawBody, signingSecret) {
68
+ return "sha256=" + crypto.createHmac("sha256", signingSecret).update(toBuffer(rawBody)).digest("hex");
69
+ }
70
+ function verifyWebhookSignature(rawBody, signatureHeader, signingSecret) {
71
+ if (!signatureHeader) return false;
72
+ const expected = Buffer.from(expectedSignature(rawBody, signingSecret));
73
+ const received = Buffer.from(signatureHeader);
74
+ if (expected.length !== received.length) return false;
75
+ return crypto.timingSafeEqual(expected, received);
76
+ }
77
+ function verifyWebhook(rawBody, signatureHeader, signingSecret) {
78
+ if (!verifyWebhookSignature(rawBody, signatureHeader, signingSecret)) {
79
+ throw new SignatureVerificationError("SimpleQ webhook signature verification failed");
80
+ }
81
+ const text = typeof rawBody === "string" ? rawBody : toBuffer(rawBody).toString("utf8");
82
+ return JSON.parse(text);
83
+ }
84
+
85
+ // src/express.ts
86
+ function simpleqWebhookHandler(signingSecret, handler) {
87
+ const captureRawBody = express__default.default.raw({ type: "*/*" });
88
+ const handle = async (req, res) => {
89
+ let job;
90
+ try {
91
+ const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body ?? "");
92
+ job = verifyWebhook(body, req.header(SIGNATURE_HEADER), signingSecret);
93
+ } catch (err) {
94
+ if (err instanceof SignatureVerificationError) {
95
+ res.status(401).end();
96
+ return;
97
+ }
98
+ throw err;
99
+ }
100
+ try {
101
+ await handler(job, { req, res });
102
+ if (!res.headersSent) res.status(200).end();
103
+ } catch (err) {
104
+ if (res.headersSent) return;
105
+ if (err instanceof SimpleQBackpressure) {
106
+ if (err.retryAfter != null) res.set("Retry-After", String(err.retryAfter));
107
+ res.status(err.status).end();
108
+ return;
109
+ }
110
+ res.status(500).end();
111
+ }
112
+ };
113
+ return (req, res, next) => {
114
+ captureRawBody(req, res, (err) => {
115
+ if (err) {
116
+ next(err);
117
+ return;
118
+ }
119
+ void handle(req, res);
120
+ });
121
+ };
122
+ }
123
+
124
+ exports.simpleqWebhookHandler = simpleqWebhookHandler;
125
+ //# sourceMappingURL=express.cjs.map
126
+ //# sourceMappingURL=express.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/webhooks.ts","../src/express.ts"],"names":["createHmac","timingSafeEqual","express"],"mappings":";;;;;;;;;;;;AAGO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EACtC,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,KAAK,WAAA,CAAY,IAAA;AAAA,EAC/B;AACF,CAAA;AAMO,IAAM,0BAAA,GAAN,cAAyC,YAAA,CAAa;AAAC,CAAA;AASvD,IAAM,mBAAA,GAAN,MAAM,oBAAA,SAA4B,YAAA,CAAa;AAAA,EAMpD,WAAA,CAAY,YAAqB,OAAA,EAA4D;AAC3F,IAAA,MAAM,MAAA,GAAS,UAAA,IAAc,IAAA,GAAO,CAAA,cAAA,EAAiB,UAAU,CAAA,EAAA,CAAA,GAAO,EAAA;AACtE,IAAA,KAAA,CAAM,OAAA,EAAS,MAAA,IAAU,CAAA,oBAAA,EAAuB,MAAM,CAAA,CAAE,CAAA;AACxD,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,MAAA,GAAS,SAAS,MAAA,IAAU,GAAA;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,IAAA,CAAK,GAAA,EAAc,OAAA,EAAuE;AAC/F,IAAA,MAAM,CAAA,GAAI,GAAA;AACV,IAAA,MAAM,MAAA,GACJ,CAAA,EAAG,MAAA,KAAW,GAAA,IAAO,CAAA,EAAG,MAAA,KAAW,GAAA,IAAO,CAAA,EAAG,MAAA,KAAW,GAAA,GAAM,CAAA,CAAE,MAAA,GAAS,GAAA;AAC3E,IAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,GAAG,CAAA,IAAK,OAAA,EAAS,QAAA;AACtD,IAAA,MAAM,MAAA,GACJ,OAAA,EAAS,MAAA,KAAW,OAAO,CAAA,EAAG,YAAY,QAAA,IAAY,CAAA,CAAE,OAAA,GAAU,CAAA,CAAE,OAAA,GAAU,MAAA,CAAA;AAChF,IAAA,OAAO,IAAI,oBAAA,CAAoB,UAAA,EAAY,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAAA,EAC/D;AACF,CAAA;AAEA,SAAS,UAAA,CAAW,SAAkB,IAAA,EAAkC;AACtE,EAAA,IAAI,CAAC,OAAA,IAAW,OAAO,OAAA,KAAY,UAAU,OAAO,MAAA;AACpD,EAAA,IAAI,OAAQ,OAAA,CAAoB,GAAA,KAAQ,UAAA,EAAY;AAClD,IAAA,OAAQ,OAAA,CAAoB,GAAA,CAAI,IAAI,CAAA,IAAK,MAAA;AAAA,EAC3C;AACA,EAAA,MAAM,MAAA,GAAS,OAAA;AACf,EAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,CAAK,aAAa,CAAA,IAAK,OAAO,aAAa,CAAA;AAChE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,MAAA;AAC7C;AAQO,SAAS,kBAAkB,GAAA,EAAkC;AAClE,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,MAAM,GAAA,GACJ,UAAA,CAAW,CAAA,EAAG,OAAA,EAAS,aAAa,KAAK,UAAA,CAAW,CAAA,EAAG,QAAA,EAAU,OAAA,EAAS,aAAa,CAAA;AACzF,EAAA,IAAI,GAAA,KAAQ,QAAW,OAAO,MAAA;AAC9B,EAAA,MAAM,OAAA,GAAU,OAAO,GAAG,CAAA;AAC1B,EAAA,OAAO,OAAO,QAAA,CAAS,OAAO,CAAA,IAAK,OAAA,IAAW,IAAI,OAAA,GAAU,MAAA;AAC9D;;;ACtEO,IAAM,gBAAA,GAAmB,qBAAA;AAEhC,SAAS,SAAS,OAAA,EAA+C;AAC/D,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,SAAiB,MAAA,CAAO,IAAA,CAAK,SAAS,MAAM,CAAA;AACnE,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,EAAG,OAAO,OAAA;AACrC,EAAA,OAAO,MAAA,CAAO,KAAK,OAAO,CAAA;AAC5B;AAEA,SAAS,iBAAA,CAAkB,SAAuC,aAAA,EAA+B;AAC/F,EAAA,OAAO,SAAA,GAAYA,iBAAA,CAAW,QAAA,EAAU,aAAa,CAAA,CAAE,MAAA,CAAO,QAAA,CAAS,OAAO,CAAC,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AAC/F;AAQO,SAAS,sBAAA,CACd,OAAA,EACA,eAAA,EACA,aAAA,EACS;AACT,EAAA,IAAI,CAAC,iBAAiB,OAAO,KAAA;AAC7B,EAAA,MAAM,WAAW,MAAA,CAAO,IAAA,CAAK,iBAAA,CAAkB,OAAA,EAAS,aAAa,CAAC,CAAA;AACtE,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,CAAK,eAAe,CAAA;AAE5C,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,QAAA,CAAS,MAAA,EAAQ,OAAO,KAAA;AAChD,EAAA,OAAOC,sBAAA,CAAgB,UAAU,QAAQ,CAAA;AAC3C;AAQO,SAAS,aAAA,CACd,OAAA,EACA,eAAA,EACA,aAAA,EACgB;AAChB,EAAA,IAAI,CAAC,sBAAA,CAAuB,OAAA,EAAS,eAAA,EAAiB,aAAa,CAAA,EAAG;AACpE,IAAA,MAAM,IAAI,2BAA2B,+CAA+C,CAAA;AAAA,EACtF;AACA,EAAA,MAAM,IAAA,GAAO,OAAO,OAAA,KAAY,QAAA,GAAW,UAAU,QAAA,CAAS,OAAO,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA;AACtF,EAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AACxB;;;AC7BO,SAAS,qBAAA,CACd,eACA,OAAA,EACgB;AAChB,EAAA,MAAM,iBAAiBC,wBAAA,CAAQ,GAAA,CAAI,EAAE,IAAA,EAAM,OAAO,CAAA;AAElD,EAAA,MAAM,MAAA,GAAS,OAAO,GAAA,EAAc,GAAA,KAAiC;AACnE,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,IAAI,CAAA,GAAI,GAAA,CAAI,IAAA,GAAO,MAAA,CAAO,IAAA,CAAM,GAAA,CAAI,IAAA,IAAmB,EAAE,CAAA;AAC1F,MAAA,GAAA,GAAM,cAAc,IAAA,EAAM,GAAA,CAAI,MAAA,CAAO,gBAAgB,GAAG,aAAa,CAAA;AAAA,IACvE,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,eAAe,0BAAA,EAA4B;AAC7C,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI;AACpB,QAAA;AAAA,MACF;AACA,MAAA,MAAM,GAAA;AAAA,IACR;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,GAAA,EAAK,KAAK,CAAA;AAC/B,MAAA,IAAI,CAAC,GAAA,CAAI,WAAA,MAAiB,MAAA,CAAO,GAAG,EAAE,GAAA,EAAI;AAAA,IAC5C,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,IAAI,WAAA,EAAa;AACrB,MAAA,IAAI,eAAe,mBAAA,EAAqB;AACtC,QAAA,IAAI,GAAA,CAAI,cAAc,IAAA,EAAM,GAAA,CAAI,IAAI,aAAA,EAAe,MAAA,CAAO,GAAA,CAAI,UAAU,CAAC,CAAA;AACzE,QAAA,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,MAAM,CAAA,CAAE,GAAA,EAAI;AAC3B,QAAA;AAAA,MACF;AACA,MAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI;AAAA,IACtB;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,CAAC,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AACzB,IAAA,cAAA,CAAe,GAAA,EAAK,GAAA,EAAK,CAAC,GAAA,KAAkB;AAC1C,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,IAAA,CAAK,GAAG,CAAA;AACR,QAAA;AAAA,MACF;AACA,MAAA,KAAK,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,IACtB,CAAC,CAAA;AAAA,EACH,CAAA;AACF","file":"express.cjs","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","// Express adapter — `@simpleq/sdk/express`. Requires the optional `express` peer dependency.\nimport express from 'express';\nimport type { Request, RequestHandler, Response } from 'express';\nimport { verifyWebhook, SIGNATURE_HEADER } from './webhooks.js';\nimport { SignatureVerificationError, SimpleQBackpressure } from './errors.js';\nimport type { WebhookPayload } from './types.js';\n\nexport interface SimpleQWebhookContext {\n req: Request;\n res: Response;\n}\n\nexport type SimpleQWebhookHandler = (\n job: WebhookPayload,\n ctx: SimpleQWebhookContext,\n) => unknown | Promise<unknown>;\n\n/**\n * An Express handler that verifies the SimpleQ signature, parses the job, and runs `handler`.\n * Mount it directly on a `POST` route — it captures the raw request body itself.\n *\n * Outcome → response: the handler resolves → `200`; it throws `SimpleQBackpressure` → that\n * status + `Retry-After`; it throws anything else → `500`; bad signature → `401`. In ack mode,\n * resolve quickly (kick off background work) and report the outcome later via the client.\n */\nexport function simpleqWebhookHandler(\n signingSecret: string,\n handler: SimpleQWebhookHandler,\n): RequestHandler {\n const captureRawBody = express.raw({ type: '*/*' });\n\n const handle = async (req: Request, res: Response): Promise<void> => {\n let job: WebhookPayload;\n try {\n const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from((req.body as string) ?? '');\n job = verifyWebhook(body, req.header(SIGNATURE_HEADER), signingSecret);\n } catch (err) {\n if (err instanceof SignatureVerificationError) {\n res.status(401).end();\n return;\n }\n throw err;\n }\n\n try {\n await handler(job, { req, res });\n if (!res.headersSent) res.status(200).end();\n } catch (err) {\n if (res.headersSent) return;\n if (err instanceof SimpleQBackpressure) {\n if (err.retryAfter != null) res.set('Retry-After', String(err.retryAfter));\n res.status(err.status).end();\n return;\n }\n res.status(500).end();\n }\n };\n\n return (req, res, next) => {\n captureRawBody(req, res, (err?: unknown) => {\n if (err) {\n next(err);\n return;\n }\n void handle(req, res);\n });\n };\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { Request, Response, RequestHandler } from 'express';
2
+ import { W as WebhookPayload } from './types-eDJHwexZ.cjs';
3
+
4
+ interface SimpleQWebhookContext {
5
+ req: Request;
6
+ res: Response;
7
+ }
8
+ type SimpleQWebhookHandler = (job: WebhookPayload, ctx: SimpleQWebhookContext) => unknown | Promise<unknown>;
9
+ /**
10
+ * An Express handler that verifies the SimpleQ signature, parses the job, and runs `handler`.
11
+ * Mount it directly on a `POST` route — it captures the raw request body itself.
12
+ *
13
+ * Outcome → response: the handler resolves → `200`; it throws `SimpleQBackpressure` → that
14
+ * status + `Retry-After`; it throws anything else → `500`; bad signature → `401`. In ack mode,
15
+ * resolve quickly (kick off background work) and report the outcome later via the client.
16
+ */
17
+ declare function simpleqWebhookHandler(signingSecret: string, handler: SimpleQWebhookHandler): RequestHandler;
18
+
19
+ export { type SimpleQWebhookContext, type SimpleQWebhookHandler, simpleqWebhookHandler };
@@ -0,0 +1,19 @@
1
+ import { Request, Response, RequestHandler } from 'express';
2
+ import { W as WebhookPayload } from './types-eDJHwexZ.js';
3
+
4
+ interface SimpleQWebhookContext {
5
+ req: Request;
6
+ res: Response;
7
+ }
8
+ type SimpleQWebhookHandler = (job: WebhookPayload, ctx: SimpleQWebhookContext) => unknown | Promise<unknown>;
9
+ /**
10
+ * An Express handler that verifies the SimpleQ signature, parses the job, and runs `handler`.
11
+ * Mount it directly on a `POST` route — it captures the raw request body itself.
12
+ *
13
+ * Outcome → response: the handler resolves → `200`; it throws `SimpleQBackpressure` → that
14
+ * status + `Retry-After`; it throws anything else → `500`; bad signature → `401`. In ack mode,
15
+ * resolve quickly (kick off background work) and report the outcome later via the client.
16
+ */
17
+ declare function simpleqWebhookHandler(signingSecret: string, handler: SimpleQWebhookHandler): RequestHandler;
18
+
19
+ export { type SimpleQWebhookContext, type SimpleQWebhookHandler, simpleqWebhookHandler };
@@ -0,0 +1,44 @@
1
+ import { verifyWebhook, SIGNATURE_HEADER, SignatureVerificationError, SimpleQBackpressure } from './chunk-72DDDNF6.js';
2
+ import express from 'express';
3
+
4
+ function simpleqWebhookHandler(signingSecret, handler) {
5
+ const captureRawBody = express.raw({ type: "*/*" });
6
+ const handle = async (req, res) => {
7
+ let job;
8
+ try {
9
+ const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body ?? "");
10
+ job = verifyWebhook(body, req.header(SIGNATURE_HEADER), signingSecret);
11
+ } catch (err) {
12
+ if (err instanceof SignatureVerificationError) {
13
+ res.status(401).end();
14
+ return;
15
+ }
16
+ throw err;
17
+ }
18
+ try {
19
+ await handler(job, { req, res });
20
+ if (!res.headersSent) res.status(200).end();
21
+ } catch (err) {
22
+ if (res.headersSent) return;
23
+ if (err instanceof SimpleQBackpressure) {
24
+ if (err.retryAfter != null) res.set("Retry-After", String(err.retryAfter));
25
+ res.status(err.status).end();
26
+ return;
27
+ }
28
+ res.status(500).end();
29
+ }
30
+ };
31
+ return (req, res, next) => {
32
+ captureRawBody(req, res, (err) => {
33
+ if (err) {
34
+ next(err);
35
+ return;
36
+ }
37
+ void handle(req, res);
38
+ });
39
+ };
40
+ }
41
+
42
+ export { simpleqWebhookHandler };
43
+ //# sourceMappingURL=express.js.map
44
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/express.ts"],"names":[],"mappings":";;;AAyBO,SAAS,qBAAA,CACd,eACA,OAAA,EACgB;AAChB,EAAA,MAAM,iBAAiB,OAAA,CAAQ,GAAA,CAAI,EAAE,IAAA,EAAM,OAAO,CAAA;AAElD,EAAA,MAAM,MAAA,GAAS,OAAO,GAAA,EAAc,GAAA,KAAiC;AACnE,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,IAAI,CAAA,GAAI,GAAA,CAAI,IAAA,GAAO,MAAA,CAAO,IAAA,CAAM,GAAA,CAAI,IAAA,IAAmB,EAAE,CAAA;AAC1F,MAAA,GAAA,GAAM,cAAc,IAAA,EAAM,GAAA,CAAI,MAAA,CAAO,gBAAgB,GAAG,aAAa,CAAA;AAAA,IACvE,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,eAAe,0BAAA,EAA4B;AAC7C,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI;AACpB,QAAA;AAAA,MACF;AACA,MAAA,MAAM,GAAA;AAAA,IACR;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,GAAA,EAAK,KAAK,CAAA;AAC/B,MAAA,IAAI,CAAC,GAAA,CAAI,WAAA,MAAiB,MAAA,CAAO,GAAG,EAAE,GAAA,EAAI;AAAA,IAC5C,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,IAAI,WAAA,EAAa;AACrB,MAAA,IAAI,eAAe,mBAAA,EAAqB;AACtC,QAAA,IAAI,GAAA,CAAI,cAAc,IAAA,EAAM,GAAA,CAAI,IAAI,aAAA,EAAe,MAAA,CAAO,GAAA,CAAI,UAAU,CAAC,CAAA;AACzE,QAAA,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,MAAM,CAAA,CAAE,GAAA,EAAI;AAC3B,QAAA;AAAA,MACF;AACA,MAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI;AAAA,IACtB;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,CAAC,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AACzB,IAAA,cAAA,CAAe,GAAA,EAAK,GAAA,EAAK,CAAC,GAAA,KAAkB;AAC1C,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,IAAA,CAAK,GAAG,CAAA;AACR,QAAA;AAAA,MACF;AACA,MAAA,KAAK,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,IACtB,CAAC,CAAA;AAAA,EACH,CAAA;AACF","file":"express.js","sourcesContent":["// Express adapter — `@simpleq/sdk/express`. Requires the optional `express` peer dependency.\nimport express from 'express';\nimport type { Request, RequestHandler, Response } from 'express';\nimport { verifyWebhook, SIGNATURE_HEADER } from './webhooks.js';\nimport { SignatureVerificationError, SimpleQBackpressure } from './errors.js';\nimport type { WebhookPayload } from './types.js';\n\nexport interface SimpleQWebhookContext {\n req: Request;\n res: Response;\n}\n\nexport type SimpleQWebhookHandler = (\n job: WebhookPayload,\n ctx: SimpleQWebhookContext,\n) => unknown | Promise<unknown>;\n\n/**\n * An Express handler that verifies the SimpleQ signature, parses the job, and runs `handler`.\n * Mount it directly on a `POST` route — it captures the raw request body itself.\n *\n * Outcome → response: the handler resolves → `200`; it throws `SimpleQBackpressure` → that\n * status + `Retry-After`; it throws anything else → `500`; bad signature → `401`. In ack mode,\n * resolve quickly (kick off background work) and report the outcome later via the client.\n */\nexport function simpleqWebhookHandler(\n signingSecret: string,\n handler: SimpleQWebhookHandler,\n): RequestHandler {\n const captureRawBody = express.raw({ type: '*/*' });\n\n const handle = async (req: Request, res: Response): Promise<void> => {\n let job: WebhookPayload;\n try {\n const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from((req.body as string) ?? '');\n job = verifyWebhook(body, req.header(SIGNATURE_HEADER), signingSecret);\n } catch (err) {\n if (err instanceof SignatureVerificationError) {\n res.status(401).end();\n return;\n }\n throw err;\n }\n\n try {\n await handler(job, { req, res });\n if (!res.headersSent) res.status(200).end();\n } catch (err) {\n if (res.headersSent) return;\n if (err instanceof SimpleQBackpressure) {\n if (err.retryAfter != null) res.set('Retry-After', String(err.retryAfter));\n res.status(err.status).end();\n return;\n }\n res.status(500).end();\n }\n };\n\n return (req, res, next) => {\n captureRawBody(req, res, (err?: unknown) => {\n if (err) {\n next(err);\n return;\n }\n void handle(req, res);\n });\n };\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,298 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var crypto = require('crypto');
6
+
7
+ // src/client.ts
8
+
9
+ // src/errors.ts
10
+ var SimpleQError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = this.constructor.name;
14
+ }
15
+ };
16
+ var SimpleQConnectionError = class extends SimpleQError {
17
+ };
18
+ var SignatureVerificationError = class extends SimpleQError {
19
+ };
20
+ var SimpleQBackpressure = class _SimpleQBackpressure extends SimpleQError {
21
+ constructor(retryAfter, options) {
22
+ const detail = retryAfter != null ? ` (retry after ${retryAfter}s)` : "";
23
+ super(options?.reason ?? `SimpleQ backpressure${detail}`);
24
+ this.retryAfter = retryAfter;
25
+ this.status = options?.status ?? 503;
26
+ }
27
+ /**
28
+ * Build a backpressure signal directly from a provider error (Anthropic, OpenAI, any
29
+ * HTTP-shaped error). Reads `err.status` (429/503/529 pass through; anything else maps
30
+ * to 503) and the `Retry-After` header in seconds from `err.headers` or
31
+ * `err.response.headers` (plain object or Headers). When no header is present,
32
+ * `options.fallback` seconds is used; with neither, SimpleQ applies its own 60s hold.
33
+ */
34
+ static from(err, options) {
35
+ const e = err;
36
+ const status = e?.status === 429 || e?.status === 503 || e?.status === 529 ? e.status : 503;
37
+ const retryAfter = retryAfterSeconds(err) ?? options?.fallback;
38
+ const reason = options?.reason ?? (typeof e?.message === "string" && e.message ? e.message : void 0);
39
+ return new _SimpleQBackpressure(retryAfter, { status, reason });
40
+ }
41
+ };
42
+ function readHeader(headers, name) {
43
+ if (!headers || typeof headers !== "object") return void 0;
44
+ if (typeof headers.get === "function") {
45
+ return headers.get(name) ?? void 0;
46
+ }
47
+ const record = headers;
48
+ const value = record[name.toLowerCase()] ?? record["Retry-After"];
49
+ return typeof value === "string" ? value : void 0;
50
+ }
51
+ function retryAfterSeconds(err) {
52
+ const e = err;
53
+ const raw = readHeader(e?.headers, "retry-after") ?? readHeader(e?.response?.headers, "retry-after");
54
+ if (raw === void 0) return void 0;
55
+ const seconds = Number(raw);
56
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
57
+ }
58
+ var ApiError = class extends SimpleQError {
59
+ constructor(message, status, body) {
60
+ super(message);
61
+ this.status = status;
62
+ this.body = body;
63
+ }
64
+ };
65
+ var AuthenticationError = class extends ApiError {
66
+ };
67
+ var ValidationError = class extends ApiError {
68
+ };
69
+ var NotFoundError = class extends ApiError {
70
+ };
71
+ var RateLimitError = class extends ApiError {
72
+ constructor(message, status, body, retryAfter) {
73
+ super(message, status, body);
74
+ this.retryAfter = retryAfter;
75
+ }
76
+ };
77
+ function extractMessage(status, body) {
78
+ if (body && typeof body === "object" && "error" in body) {
79
+ const err = body.error;
80
+ if (typeof err === "string") return err;
81
+ if (err && typeof err === "object") return `Validation failed: ${JSON.stringify(err)}`;
82
+ }
83
+ return `SimpleQ API error (HTTP ${status})`;
84
+ }
85
+ function mapApiError(status, body, headers) {
86
+ const message = extractMessage(status, body);
87
+ switch (status) {
88
+ case 400:
89
+ return new ValidationError(message, status, body);
90
+ case 401:
91
+ case 403:
92
+ return new AuthenticationError(message, status, body);
93
+ case 404:
94
+ return new NotFoundError(message, status, body);
95
+ case 429: {
96
+ const raw = headers?.get("retry-after");
97
+ const retryAfter = raw != null ? Number(raw) : NaN;
98
+ return new RateLimitError(message, status, body, Number.isFinite(retryAfter) ? retryAfter : void 0);
99
+ }
100
+ default:
101
+ return new ApiError(message, status, body);
102
+ }
103
+ }
104
+
105
+ // src/http.ts
106
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
107
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
108
+ function backoffMs(attempt) {
109
+ const ceiling = Math.min(250 * 2 ** attempt, 8e3);
110
+ return Math.round(ceiling * (0.5 + Math.random() * 0.5));
111
+ }
112
+ var HttpClient = class {
113
+ constructor(opts) {
114
+ this.opts = opts;
115
+ }
116
+ async request(req) {
117
+ const maxAttempts = req.retry ?? true ? this.opts.maxRetries + 1 : 1;
118
+ let lastError;
119
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
120
+ try {
121
+ return await this.attempt(req);
122
+ } catch (err) {
123
+ lastError = err;
124
+ if (!this.isRetryable(err) || attempt === maxAttempts - 1) throw err;
125
+ await sleep(this.retryAfterMs(err) ?? backoffMs(attempt));
126
+ }
127
+ }
128
+ throw lastError instanceof Error ? lastError : new SimpleQError(String(lastError));
129
+ }
130
+ async attempt(req) {
131
+ const controller = new AbortController();
132
+ const timer = setTimeout(() => controller.abort(), this.opts.timeout);
133
+ let res;
134
+ try {
135
+ res = await this.opts.fetchImpl(this.opts.baseUrl + req.path, {
136
+ method: req.method,
137
+ headers: {
138
+ Authorization: `Bearer ${this.opts.apiKey}`,
139
+ "Content-Type": "application/json",
140
+ "User-Agent": this.opts.userAgent,
141
+ ...req.headers
142
+ },
143
+ body: req.body !== void 0 ? JSON.stringify(req.body) : void 0,
144
+ signal: controller.signal
145
+ });
146
+ } catch (err) {
147
+ throw new SimpleQConnectionError(
148
+ `Request to ${req.method} ${req.path} failed: ${err instanceof Error ? err.message : String(err)}`
149
+ );
150
+ } finally {
151
+ clearTimeout(timer);
152
+ }
153
+ if (!res.ok) throw mapApiError(res.status, await this.parseBody(res), res.headers);
154
+ if (res.status === 204) return void 0;
155
+ return await this.parseBody(res);
156
+ }
157
+ async parseBody(res) {
158
+ const text = await res.text();
159
+ if (!text) return void 0;
160
+ try {
161
+ return JSON.parse(text);
162
+ } catch {
163
+ return text;
164
+ }
165
+ }
166
+ isRetryable(err) {
167
+ if (err instanceof SimpleQConnectionError) return true;
168
+ if (err instanceof ApiError) return RETRYABLE_STATUS.has(err.status);
169
+ return false;
170
+ }
171
+ retryAfterMs(err) {
172
+ return err instanceof RateLimitError && typeof err.retryAfter === "number" ? err.retryAfter * 1e3 : void 0;
173
+ }
174
+ };
175
+ var SIGNATURE_HEADER = "x-simpleq-signature";
176
+ function toBuffer(rawBody) {
177
+ if (typeof rawBody === "string") return Buffer.from(rawBody, "utf8");
178
+ if (Buffer.isBuffer(rawBody)) return rawBody;
179
+ return Buffer.from(rawBody);
180
+ }
181
+ function expectedSignature(rawBody, signingSecret) {
182
+ return "sha256=" + crypto.createHmac("sha256", signingSecret).update(toBuffer(rawBody)).digest("hex");
183
+ }
184
+ function verifyWebhookSignature(rawBody, signatureHeader, signingSecret) {
185
+ if (!signatureHeader) return false;
186
+ const expected = Buffer.from(expectedSignature(rawBody, signingSecret));
187
+ const received = Buffer.from(signatureHeader);
188
+ if (expected.length !== received.length) return false;
189
+ return crypto.timingSafeEqual(expected, received);
190
+ }
191
+ function verifyWebhook(rawBody, signatureHeader, signingSecret) {
192
+ if (!verifyWebhookSignature(rawBody, signatureHeader, signingSecret)) {
193
+ throw new SignatureVerificationError("SimpleQ webhook signature verification failed");
194
+ }
195
+ const text = typeof rawBody === "string" ? rawBody : toBuffer(rawBody).toString("utf8");
196
+ return JSON.parse(text);
197
+ }
198
+
199
+ // src/client.ts
200
+ var DEFAULT_BASE_URL = "https://api.simpleq.io";
201
+ var DEFAULT_TIMEOUT_SECONDS = 30;
202
+ var DEFAULT_MAX_RETRIES = 2;
203
+ var VERSION = "0.1.0";
204
+ var SimpleQ = class {
205
+ constructor(options = {}) {
206
+ /** Webhook signature helpers. Usable without an API key (only a `signingSecret` is needed). */
207
+ this.webhooks = {
208
+ verifyWebhookSignature,
209
+ verifyWebhook
210
+ };
211
+ const apiKey = options.apiKey ?? process.env.SIMPLEQ_API_KEY;
212
+ if (!apiKey) {
213
+ throw new SimpleQError("A SimpleQ API key is required. Pass { apiKey } or set SIMPLEQ_API_KEY.");
214
+ }
215
+ const fetchImpl = options.fetch ?? globalThis.fetch;
216
+ if (typeof fetchImpl !== "function") {
217
+ throw new SimpleQError("No fetch implementation found. Use Node 18+ or pass { fetch }.");
218
+ }
219
+ this.http = new HttpClient({
220
+ apiKey,
221
+ baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
222
+ timeout: (options.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3,
223
+ maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
224
+ fetchImpl,
225
+ userAgent: `simpleq-node/${VERSION}`
226
+ });
227
+ }
228
+ /**
229
+ * Publish a job to a queue. Retries transient failures automatically; the idempotency key
230
+ * (yours, or one generated per call when you omit it) is reused across those retries, so a
231
+ * retry can never create a duplicate job. A 200 (idempotent hit) and a 201 (created) are both
232
+ * returned as success.
233
+ */
234
+ async publish(queueName, params) {
235
+ const idempotencyKey = params.idempotencyKey ?? crypto.randomUUID();
236
+ const body = { payload: params.payload };
237
+ if (idempotencyKey !== void 0) body.idempotencyKey = idempotencyKey;
238
+ if (params.delay !== void 0) body.delay = params.delay;
239
+ return this.http.request({
240
+ method: "POST",
241
+ path: `/v1/queues/${encodeURIComponent(queueName)}/jobs`,
242
+ body
243
+ });
244
+ }
245
+ /** Fetch a job's current status and attempt history. */
246
+ async getJob(jobId) {
247
+ return this.http.request({
248
+ method: "GET",
249
+ path: `/v1/jobs/${encodeURIComponent(jobId)}`
250
+ });
251
+ }
252
+ /** Ack a job (ack-mode queues): report successful completion. */
253
+ async ack(jobId) {
254
+ return this.http.request({
255
+ method: "POST",
256
+ path: `/v1/jobs/${encodeURIComponent(jobId)}/ack`,
257
+ body: {}
258
+ });
259
+ }
260
+ /** Nack a job (ack-mode queues): report failure. `retryable: false` dead-letters immediately. */
261
+ async nack(jobId, options = {}) {
262
+ return this.http.request({
263
+ method: "POST",
264
+ path: `/v1/jobs/${encodeURIComponent(jobId)}/nack`,
265
+ body: options
266
+ });
267
+ }
268
+ /** Defer a job (ack-mode queues): apply backpressure — held and redelivered, no attempt burned. */
269
+ async defer(jobId, options) {
270
+ const { retryAfter } = options ?? {};
271
+ if (typeof retryAfter !== "number" || !Number.isFinite(retryAfter) || retryAfter < 0 || retryAfter > 3600) {
272
+ throw new ValidationError("defer requires retryAfter to be a number of seconds between 0 and 3600.", 400, void 0);
273
+ }
274
+ return this.http.request({
275
+ method: "POST",
276
+ path: `/v1/jobs/${encodeURIComponent(jobId)}/defer`,
277
+ body: options
278
+ });
279
+ }
280
+ };
281
+
282
+ exports.ApiError = ApiError;
283
+ exports.AuthenticationError = AuthenticationError;
284
+ exports.NotFoundError = NotFoundError;
285
+ exports.RateLimitError = RateLimitError;
286
+ exports.SIGNATURE_HEADER = SIGNATURE_HEADER;
287
+ exports.SignatureVerificationError = SignatureVerificationError;
288
+ exports.SimpleQ = SimpleQ;
289
+ exports.SimpleQBackpressure = SimpleQBackpressure;
290
+ exports.SimpleQConnectionError = SimpleQConnectionError;
291
+ exports.SimpleQError = SimpleQError;
292
+ exports.ValidationError = ValidationError;
293
+ exports.default = SimpleQ;
294
+ exports.retryAfterSeconds = retryAfterSeconds;
295
+ exports.verifyWebhook = verifyWebhook;
296
+ exports.verifyWebhookSignature = verifyWebhookSignature;
297
+ //# sourceMappingURL=index.cjs.map
298
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/webhooks.ts","../src/client.ts"],"names":["createHmac","timingSafeEqual","randomUUID"],"mappings":"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","file":"index.cjs","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","// Internal fetch wrapper + retry engine. Not part of the public API.\nimport { ApiError, RateLimitError, SimpleQConnectionError, SimpleQError, mapApiError } from './errors.js';\n\nexport interface HttpClientOptions {\n apiKey: string;\n baseUrl: string;\n timeout: number;\n maxRetries: number;\n fetchImpl: typeof fetch;\n userAgent: string;\n}\n\nexport interface RequestOptions {\n method: string;\n path: string;\n body?: unknown;\n headers?: Record<string, string>;\n /** Retry transient failures (network / 5xx / 429). Default true. */\n retry?: boolean;\n}\n\nconst RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n// Exponential backoff (base 250ms, cap 8s) with full jitter.\nfunction backoffMs(attempt: number): number {\n const ceiling = Math.min(250 * 2 ** attempt, 8000);\n return Math.round(ceiling * (0.5 + Math.random() * 0.5));\n}\n\nexport class HttpClient {\n constructor(private readonly opts: HttpClientOptions) {}\n\n async request<T>(req: RequestOptions): Promise<T> {\n const maxAttempts = (req.retry ?? true) ? this.opts.maxRetries + 1 : 1;\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.attempt<T>(req);\n } catch (err) {\n lastError = err;\n if (!this.isRetryable(err) || attempt === maxAttempts - 1) throw err;\n await sleep(this.retryAfterMs(err) ?? backoffMs(attempt));\n }\n }\n // Unreachable in practice — the loop either returns or throws.\n throw lastError instanceof Error ? lastError : new SimpleQError(String(lastError));\n }\n\n private async attempt<T>(req: RequestOptions): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.opts.timeout);\n let res: Response;\n try {\n res = await this.opts.fetchImpl(this.opts.baseUrl + req.path, {\n method: req.method,\n headers: {\n Authorization: `Bearer ${this.opts.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': this.opts.userAgent,\n ...req.headers,\n },\n body: req.body !== undefined ? JSON.stringify(req.body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n throw new SimpleQConnectionError(\n `Request to ${req.method} ${req.path} failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n clearTimeout(timer);\n }\n\n if (!res.ok) throw mapApiError(res.status, await this.parseBody(res), res.headers);\n if (res.status === 204) return undefined as T;\n return (await this.parseBody(res)) as T;\n }\n\n private async parseBody(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n\n private isRetryable(err: unknown): boolean {\n if (err instanceof SimpleQConnectionError) return true;\n if (err instanceof ApiError) return RETRYABLE_STATUS.has(err.status);\n return false;\n }\n\n private retryAfterMs(err: unknown): number | undefined {\n return err instanceof RateLimitError && typeof err.retryAfter === 'number'\n ? err.retryAfter * 1000\n : undefined;\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","import { randomUUID } from 'node:crypto';\nimport { HttpClient } from './http.js';\nimport { SimpleQError, ValidationError } from './errors.js';\nimport { verifyWebhook, verifyWebhookSignature } from './webhooks.js';\nimport type {\n AckResponse,\n DeferOptions,\n Job,\n NackOptions,\n PublishJobResponse,\n PublishParams,\n SimpleQOptions,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.simpleq.io';\n// Customer-facing durations are seconds throughout SimpleQ; convert to ms at the fetch boundary.\nconst DEFAULT_TIMEOUT_SECONDS = 30;\nconst DEFAULT_MAX_RETRIES = 2;\n// Mirrors package.json; surfaced in the User-Agent header.\nconst VERSION = '0.1.0';\n\n/** The SimpleQ client: publish jobs, read job status, and run the ack-mode callbacks. */\nexport class SimpleQ {\n private readonly http: HttpClient;\n\n /** Webhook signature helpers. Usable without an API key (only a `signingSecret` is needed). */\n readonly webhooks = {\n verifyWebhookSignature,\n verifyWebhook,\n };\n\n constructor(options: SimpleQOptions = {}) {\n const apiKey = options.apiKey ?? process.env.SIMPLEQ_API_KEY;\n if (!apiKey) {\n throw new SimpleQError('A SimpleQ API key is required. Pass { apiKey } or set SIMPLEQ_API_KEY.');\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new SimpleQError('No fetch implementation found. Use Node 18+ or pass { fetch }.');\n }\n\n this.http = new HttpClient({\n apiKey,\n baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, ''),\n timeout: (options.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000,\n maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,\n fetchImpl,\n userAgent: `simpleq-node/${VERSION}`,\n });\n }\n\n /**\n * Publish a job to a queue. Retries transient failures automatically; the idempotency key\n * (yours, or one generated per call when you omit it) is reused across those retries, so a\n * retry can never create a duplicate job. A 200 (idempotent hit) and a 201 (created) are both\n * returned as success.\n */\n async publish(queueName: string, params: PublishParams): Promise<PublishJobResponse> {\n const idempotencyKey = params.idempotencyKey ?? randomUUID();\n\n const body: Record<string, unknown> = { payload: params.payload };\n if (idempotencyKey !== undefined) body.idempotencyKey = idempotencyKey;\n if (params.delay !== undefined) body.delay = params.delay;\n\n return this.http.request<PublishJobResponse>({\n method: 'POST',\n path: `/v1/queues/${encodeURIComponent(queueName)}/jobs`,\n body,\n });\n }\n\n /** Fetch a job's current status and attempt history. */\n async getJob(jobId: string): Promise<Job> {\n return this.http.request<Job>({\n method: 'GET',\n path: `/v1/jobs/${encodeURIComponent(jobId)}`,\n });\n }\n\n /** Ack a job (ack-mode queues): report successful completion. */\n async ack(jobId: string): Promise<AckResponse> {\n return this.http.request<AckResponse>({\n method: 'POST',\n path: `/v1/jobs/${encodeURIComponent(jobId)}/ack`,\n body: {},\n });\n }\n\n /** Nack a job (ack-mode queues): report failure. `retryable: false` dead-letters immediately. */\n async nack(jobId: string, options: NackOptions = {}): Promise<AckResponse> {\n return this.http.request<AckResponse>({\n method: 'POST',\n path: `/v1/jobs/${encodeURIComponent(jobId)}/nack`,\n body: options,\n });\n }\n\n /** Defer a job (ack-mode queues): apply backpressure — held and redelivered, no attempt burned. */\n async defer(jobId: string, options: DeferOptions): Promise<AckResponse> {\n const { retryAfter } = options ?? {};\n if (typeof retryAfter !== 'number' || !Number.isFinite(retryAfter) || retryAfter < 0 || retryAfter > 3600) {\n throw new ValidationError('defer requires retryAfter to be a number of seconds between 0 and 3600.', 400, undefined);\n }\n return this.http.request<AckResponse>({\n method: 'POST',\n path: `/v1/jobs/${encodeURIComponent(jobId)}/defer`,\n body: options,\n });\n }\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { verifyWebhookSignature, verifyWebhook } from './webhooks.cjs';
2
+ export { SIGNATURE_HEADER } from './webhooks.cjs';
3
+ import { S as SimpleQOptions, P as PublishParams, a as PublishJobResponse, J as Job, A as AckResponse, N as NackOptions, D as DeferOptions } from './types-eDJHwexZ.cjs';
4
+ export { b as JobAttempt, c as JobAttemptStatus, d as JobStatus, e as PublishJobRequest, W as WebhookPayload } from './types-eDJHwexZ.cjs';
5
+ export { A as ApiError, a as AuthenticationError, B as BackpressureStatus, N as NotFoundError, R as RateLimitError, S as SignatureVerificationError, b as SimpleQBackpressure, c as SimpleQConnectionError, d as SimpleQError, V as ValidationError, r as retryAfterSeconds } from './errors-D7trszMq.cjs';
6
+
7
+ /** The SimpleQ client: publish jobs, read job status, and run the ack-mode callbacks. */
8
+ declare class SimpleQ {
9
+ private readonly http;
10
+ /** Webhook signature helpers. Usable without an API key (only a `signingSecret` is needed). */
11
+ readonly webhooks: {
12
+ verifyWebhookSignature: typeof verifyWebhookSignature;
13
+ verifyWebhook: typeof verifyWebhook;
14
+ };
15
+ constructor(options?: SimpleQOptions);
16
+ /**
17
+ * Publish a job to a queue. Retries transient failures automatically; the idempotency key
18
+ * (yours, or one generated per call when you omit it) is reused across those retries, so a
19
+ * retry can never create a duplicate job. A 200 (idempotent hit) and a 201 (created) are both
20
+ * returned as success.
21
+ */
22
+ publish(queueName: string, params: PublishParams): Promise<PublishJobResponse>;
23
+ /** Fetch a job's current status and attempt history. */
24
+ getJob(jobId: string): Promise<Job>;
25
+ /** Ack a job (ack-mode queues): report successful completion. */
26
+ ack(jobId: string): Promise<AckResponse>;
27
+ /** Nack a job (ack-mode queues): report failure. `retryable: false` dead-letters immediately. */
28
+ nack(jobId: string, options?: NackOptions): Promise<AckResponse>;
29
+ /** Defer a job (ack-mode queues): apply backpressure — held and redelivered, no attempt burned. */
30
+ defer(jobId: string, options: DeferOptions): Promise<AckResponse>;
31
+ }
32
+
33
+ export { AckResponse, DeferOptions, Job, NackOptions, PublishJobResponse, PublishParams, SimpleQ, SimpleQOptions, SimpleQ as default, verifyWebhook, verifyWebhookSignature };