batchwork 0.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/batch.d.ts +27 -0
  4. package/dist/batch.d.ts.map +1 -0
  5. package/dist/body.d.ts +19 -0
  6. package/dist/body.d.ts.map +1 -0
  7. package/dist/chunk-e6qn48qa.js +301 -0
  8. package/dist/chunk-e6qn48qa.js.map +13 -0
  9. package/dist/chunk-g481f961.js +1212 -0
  10. package/dist/chunk-g481f961.js.map +26 -0
  11. package/dist/chunk-m4n610nm.js +338 -0
  12. package/dist/chunk-m4n610nm.js.map +12 -0
  13. package/dist/chunk-v0bahtg2.js +7 -0
  14. package/dist/chunk-v0bahtg2.js.map +9 -0
  15. package/dist/errors.d.ts +16 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/http.d.ts +5 -0
  18. package/dist/http.d.ts.map +1 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +30 -0
  22. package/dist/index.js.map +9 -0
  23. package/dist/job.d.ts +31 -0
  24. package/dist/job.d.ts.map +1 -0
  25. package/dist/jsonl.d.ts +7 -0
  26. package/dist/jsonl.d.ts.map +1 -0
  27. package/dist/limits.d.ts +12 -0
  28. package/dist/limits.d.ts.map +1 -0
  29. package/dist/model.d.ts +32 -0
  30. package/dist/model.d.ts.map +1 -0
  31. package/dist/next/index.d.ts +61 -0
  32. package/dist/next/index.d.ts.map +1 -0
  33. package/dist/next/index.js +64 -0
  34. package/dist/next/index.js.map +10 -0
  35. package/dist/postgres/batch-store.d.ts +19 -0
  36. package/dist/postgres/batch-store.d.ts.map +1 -0
  37. package/dist/postgres/index.d.ts +6 -0
  38. package/dist/postgres/index.d.ts.map +1 -0
  39. package/dist/postgres/index.js +101 -0
  40. package/dist/postgres/index.js.map +12 -0
  41. package/dist/postgres/migrate.d.ts +15 -0
  42. package/dist/postgres/migrate.d.ts.map +1 -0
  43. package/dist/postgres/types.d.ts +26 -0
  44. package/dist/postgres/types.d.ts.map +1 -0
  45. package/dist/providers/adapter.d.ts +25 -0
  46. package/dist/providers/adapter.d.ts.map +1 -0
  47. package/dist/providers/anthropic.d.ts +3 -0
  48. package/dist/providers/anthropic.d.ts.map +1 -0
  49. package/dist/providers/google.d.ts +8 -0
  50. package/dist/providers/google.d.ts.map +1 -0
  51. package/dist/providers/groq.d.ts +8 -0
  52. package/dist/providers/groq.d.ts.map +1 -0
  53. package/dist/providers/ids.d.ts +3 -0
  54. package/dist/providers/ids.d.ts.map +1 -0
  55. package/dist/providers/index.d.ts +5 -0
  56. package/dist/providers/index.d.ts.map +1 -0
  57. package/dist/providers/mistral.d.ts +8 -0
  58. package/dist/providers/mistral.d.ts.map +1 -0
  59. package/dist/providers/openai-compatible.d.ts +41 -0
  60. package/dist/providers/openai-compatible.d.ts.map +1 -0
  61. package/dist/providers/openai.d.ts +7 -0
  62. package/dist/providers/openai.d.ts.map +1 -0
  63. package/dist/providers/shared.d.ts +23 -0
  64. package/dist/providers/shared.d.ts.map +1 -0
  65. package/dist/providers/together.d.ts +8 -0
  66. package/dist/providers/together.d.ts.map +1 -0
  67. package/dist/providers/xai.d.ts +8 -0
  68. package/dist/providers/xai.d.ts.map +1 -0
  69. package/dist/redis/batch-store.d.ts +15 -0
  70. package/dist/redis/batch-store.d.ts.map +1 -0
  71. package/dist/redis/index.d.ts +4 -0
  72. package/dist/redis/index.d.ts.map +1 -0
  73. package/dist/redis/index.js +55 -0
  74. package/dist/redis/index.js.map +11 -0
  75. package/dist/redis/types.d.ts +19 -0
  76. package/dist/redis/types.d.ts.map +1 -0
  77. package/dist/server/events.d.ts +5 -0
  78. package/dist/server/events.d.ts.map +1 -0
  79. package/dist/server/index.d.ts +7 -0
  80. package/dist/server/index.d.ts.map +1 -0
  81. package/dist/server/index.js +19 -0
  82. package/dist/server/index.js.map +9 -0
  83. package/dist/server/poller.d.ts +63 -0
  84. package/dist/server/poller.d.ts.map +1 -0
  85. package/dist/server/signing.d.ts +27 -0
  86. package/dist/server/signing.d.ts.map +1 -0
  87. package/dist/server/store.d.ts +4 -0
  88. package/dist/server/store.d.ts.map +1 -0
  89. package/dist/server/types.d.ts +37 -0
  90. package/dist/server/types.d.ts.map +1 -0
  91. package/dist/types.d.ts +125 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/util.d.ts +10 -0
  94. package/dist/util.d.ts.map +1 -0
  95. package/package.json +119 -7
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hayden Bleasel
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,57 @@
1
+ # Batchwork
2
+
3
+ A unified **batch API** for AI providers. Submit thousands of LLM requests at roughly half the cost with a single call — `batchwork` handles JSONL, file uploads, inline submission, polling, and result parsing across every major provider.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/batchwork.svg)](https://www.npmjs.com/package/batchwork)
6
+ [![npm downloads](https://img.shields.io/npm/dm/batchwork.svg)](https://www.npmjs.com/package/batchwork)
7
+ [![license](https://img.shields.io/npm/l/batchwork.svg)](#license)
8
+ [![Socket Badge](https://socket.dev/api/badge/npm/package/batchwork)](https://socket.dev/npm/package/batchwork)
9
+
10
+ 📖 **Full documentation: [batchwork.dev](https://batchwork.dev)**
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install batchwork
16
+ # plus the provider package(s) you use:
17
+ npm install @ai-sdk/openai @ai-sdk/anthropic
18
+ ```
19
+
20
+ `batchwork` depends only on `ai`. The `@ai-sdk/*` provider packages are **optional peer dependencies** — install only the ones you batch with. Requires Node.js 20 or newer.
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import { batch } from "batchwork";
26
+ import { openai } from "@ai-sdk/openai";
27
+
28
+ const job = await batch({
29
+ model: openai.chat("gpt-5.5"),
30
+ requests: [
31
+ { customId: "a", prompt: "Summarize: …" },
32
+ { customId: "b", messages: [{ role: "user", content: "Translate: …" }] },
33
+ ],
34
+ });
35
+
36
+ const results = await job.wait().then(() => job.collect());
37
+ for (const r of results) {
38
+ console.log(r.customId, r.status, r.text);
39
+ }
40
+ ```
41
+
42
+ Author requests in the same `generateText` shape you already use, pass the AI SDK models you already use, and get back one normalized result type correlated by `customId`.
43
+
44
+ ## Features
45
+
46
+ - **One API, many providers** — OpenAI, Anthropic, Google Gemini, Groq, Mistral, Together AI, and xAI.
47
+ - **AI SDK native** — author requests in the familiar `generateText` shape.
48
+ - **~50% cheaper** — every request runs against the provider's batch window.
49
+ - **Normalized results** — unified status, text, usage, and error types regardless of provider.
50
+ - **Server-ready** — optional layers for managed polling, unified webhooks, and Next.js route handlers.
51
+ - **Durable stores** — drop-in Postgres (`batchwork/postgres`) and Upstash Redis (`batchwork/redis`) adapters for the poller, or bring your own.
52
+
53
+ Guides for models, the job handle, rehydration, the server layer, and Next.js handlers all live at **[batchwork.dev](https://batchwork.dev)**.
54
+
55
+ ## License
56
+
57
+ [MIT](https://opensource.org/licenses/MIT) © [Hayden Bleasel](https://github.com/haydenbleasel)
@@ -0,0 +1,27 @@
1
+ import { BatchJob } from "./job";
2
+ import type { BatchOptions, BatchRef, BatchResult } from "./types";
3
+ /**
4
+ * Submit a batch of requests to the model's provider and return a handle.
5
+ *
6
+ * Resolves immediately once the batch is accepted — it does not wait for
7
+ * processing. Use the returned {@link BatchJob} to poll, wait, or stream
8
+ * results.
9
+ *
10
+ * @example
11
+ * const job = await batch({
12
+ * model: openai("gpt-5.5"),
13
+ * requests: [{ customId: "a", prompt: "Say hi" }],
14
+ * });
15
+ * const results = await job.wait().then(() => job.collect());
16
+ */
17
+ export declare const batch: (options: BatchOptions) => Promise<BatchJob>;
18
+ /**
19
+ * Rehydrate a {@link BatchJob} for an existing batch id (e.g. one persisted
20
+ * after submission). Identify the provider with `provider` or `model`.
21
+ */
22
+ export declare const getBatch: (ref: BatchRef) => Promise<BatchJob>;
23
+ /** Stream the results of an existing batch by id, without a handle. */
24
+ export declare const getBatchResults: (ref: BatchRef) => AsyncGenerator<BatchResult>;
25
+ /** Request cancellation of an existing batch by id. */
26
+ export declare const cancelBatch: (ref: BatchRef) => Promise<void>;
27
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAIjC,OAAO,KAAK,EACV,YAAY,EAEZ,QAAQ,EACR,WAAW,EAEZ,MAAM,SAAS,CAAC;AAoBjB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,KAAK,YAAmB,YAAY,KAAG,OAAO,CAAC,QAAQ,CA2BnE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,QAAe,QAAQ,KAAG,OAAO,CAAC,QAAQ,CAK9D,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,eAAe,QAAS,QAAQ,KAAG,cAAc,CAAC,WAAW,CAGzE,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,WAAW,QAAe,QAAQ,KAAG,OAAO,CAAC,IAAI,CAG7D,CAAC"}
package/dist/body.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { ResolvedBatchLimits } from "./limits";
2
+ import type { ResolvedModel } from "./model";
3
+ import type { BatchDefaults, BatchLimits, BatchRequest, ProviderCredentials } from "./types";
4
+ /** A provider request body derived from a single batch item. */
5
+ export interface BuiltRequest {
6
+ /** The serialized provider request body (becomes the batch line). */
7
+ body: Record<string, unknown>;
8
+ customId: string;
9
+ /** API endpoint path the model targets, e.g. `/v1/chat/completions`. */
10
+ endpoint: string;
11
+ }
12
+ /**
13
+ * Derive provider request bodies for every batch item by running each through
14
+ * the AI SDK with a capturing `fetch`. This reuses the AI SDK's full message,
15
+ * tool, and multimodal conversion, so the body matches what `generateText`
16
+ * would send — minus the network call.
17
+ */
18
+ export declare const buildRequestBodies: (resolved: ResolvedModel, requests: readonly BatchRequest[], defaults: BatchDefaults | undefined, credentials: ProviderCredentials, rawLimits?: BatchLimits | ResolvedBatchLimits) => Promise<BuiltRequest[]>;
19
+ //# sourceMappingURL=body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAEpD,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAIjB,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuID;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,aACnB,aAAa,YACb,SAAS,YAAY,EAAE,YACvB,aAAa,GAAG,SAAS,eACtB,mBAAmB,cACpB,WAAW,GAAG,mBAAmB,KAC5C,OAAO,CAAC,YAAY,EAAE,CAwCxB,CAAC"}
@@ -0,0 +1,301 @@
1
+ import {
2
+ BatchworkError,
3
+ getAdapter,
4
+ isTerminalStatus
5
+ } from "./chunk-g481f961.js";
6
+
7
+ // src/server/events.ts
8
+ var EVENT_BY_STATUS = {
9
+ cancelled: "batch.cancelled",
10
+ completed: "batch.completed",
11
+ expired: "batch.expired",
12
+ failed: "batch.failed"
13
+ };
14
+ var toEvent = (provider, snapshot) => ({
15
+ completedAt: snapshot.completedAt?.toISOString(),
16
+ createdAt: snapshot.createdAt?.toISOString(),
17
+ id: snapshot.id,
18
+ provider,
19
+ requestCounts: snapshot.requestCounts,
20
+ type: EVENT_BY_STATUS[snapshot.status] ?? "batch.completed"
21
+ });
22
+
23
+ // src/server/signing.ts
24
+ var SIGNATURE_VERSION = "v1";
25
+ var TOLERANCE_SECONDS = 300;
26
+ var SECRET_PREFIX = "whsec_";
27
+ var encoder = new TextEncoder;
28
+ var replayCache = new Map;
29
+ var defaultReplayStore = {
30
+ get: (id) => replayCache.get(id),
31
+ set: (id, expiresAt) => {
32
+ replayCache.set(id, expiresAt);
33
+ }
34
+ };
35
+ var pruneReplayCache = (now) => {
36
+ for (const [id, expiresAt] of replayCache) {
37
+ if (expiresAt <= now) {
38
+ replayCache.delete(id);
39
+ }
40
+ }
41
+ };
42
+ var base64ToBytes = (value) => Uint8Array.from(atob(value), (char) => char.codePointAt(0) ?? 0);
43
+ var bytesToBase64 = (bytes) => {
44
+ let binary = "";
45
+ for (const byte of bytes) {
46
+ binary += String.fromCodePoint(byte);
47
+ }
48
+ return btoa(binary);
49
+ };
50
+ var importKey = (secret) => {
51
+ const raw = secret.startsWith(SECRET_PREFIX) ? base64ToBytes(secret.slice(SECRET_PREFIX.length)) : encoder.encode(secret);
52
+ return crypto.subtle.importKey("raw", raw, { hash: "SHA-256", name: "HMAC" }, false, ["sign", "verify"]);
53
+ };
54
+ var signContent = async (secret, content) => {
55
+ const key = await importKey(secret);
56
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(content));
57
+ return bytesToBase64(new Uint8Array(signature));
58
+ };
59
+ var verifyContent = async (secret, content, signature) => {
60
+ const key = await importKey(secret);
61
+ return crypto.subtle.verify("HMAC", key, base64ToBytes(signature), encoder.encode(content));
62
+ };
63
+ var rememberWebhookId = async (id, timestamp, now, store) => {
64
+ if (store === defaultReplayStore) {
65
+ pruneReplayCache(now);
66
+ }
67
+ const expiresAt = await store.get(id);
68
+ if (expiresAt && expiresAt > now) {
69
+ throw new BatchworkError("batchwork: webhook replay detected.");
70
+ }
71
+ await store.set(id, timestamp + TOLERANCE_SECONDS);
72
+ };
73
+ var signWebhook = async (secret, id, body, timestampSeconds) => {
74
+ const timestamp = Math.floor(timestampSeconds).toString();
75
+ const signature = await signContent(secret, `${id}.${timestamp}.${body}`);
76
+ return {
77
+ "webhook-id": id,
78
+ "webhook-signature": `${SIGNATURE_VERSION},${signature}`,
79
+ "webhook-timestamp": timestamp
80
+ };
81
+ };
82
+ var verifyWebhook = async (request, secret, options) => {
83
+ const id = request.headers.get("webhook-id");
84
+ const timestamp = request.headers.get("webhook-timestamp");
85
+ const signatureHeader = request.headers.get("webhook-signature");
86
+ if (!(id && timestamp && signatureHeader)) {
87
+ throw new BatchworkError("batchwork: missing webhook signature headers.");
88
+ }
89
+ const seconds = Number(timestamp);
90
+ const now = Date.now() / 1000;
91
+ if (!Number.isFinite(seconds) || Math.abs(now - seconds) > TOLERANCE_SECONDS) {
92
+ throw new BatchworkError("batchwork: webhook timestamp outside tolerance.");
93
+ }
94
+ const body = await request.text();
95
+ const content = `${id}.${timestamp}.${body}`;
96
+ const signatures = signatureHeader.split(" ").map((part) => {
97
+ const comma = part.indexOf(",");
98
+ return comma === -1 ? part : part.slice(comma + 1);
99
+ });
100
+ let valid = false;
101
+ for (const signature of signatures) {
102
+ if (await verifyContent(secret, content, signature)) {
103
+ valid = true;
104
+ break;
105
+ }
106
+ }
107
+ if (!valid) {
108
+ throw new BatchworkError("batchwork: webhook signature verification failed.");
109
+ }
110
+ await rememberWebhookId(id, seconds, now, options?.replayStore ?? defaultReplayStore);
111
+ return { body, id, timestamp: seconds };
112
+ };
113
+ var verifyBatchWebhook = async (request, secret, options) => {
114
+ const { body } = await verifyWebhook(request, secret, options);
115
+ return JSON.parse(body);
116
+ };
117
+
118
+ // src/server/poller.ts
119
+ var parseIpv4 = (host) => {
120
+ if (!/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host)) {
121
+ return;
122
+ }
123
+ const parts = host.split(".").map(Number);
124
+ const valid = parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255);
125
+ return valid ? parts : undefined;
126
+ };
127
+ var isPrivateIpv4 = (parts) => {
128
+ const [a = 0, b = 0] = parts;
129
+ return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224;
130
+ };
131
+ var isPrivateIpv6 = (host) => {
132
+ const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase();
133
+ return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:");
134
+ };
135
+ var isRedirectStatus = (status) => status >= 300 && status < 400;
136
+ var assertSafeWebhookUrl = (url) => {
137
+ if (url.protocol !== "https:") {
138
+ throw new BatchworkError("batchwork: webhookUrl must use https.");
139
+ }
140
+ if (url.username || url.password) {
141
+ throw new BatchworkError("batchwork: webhookUrl must not include credentials.");
142
+ }
143
+ const host = url.hostname.toLowerCase();
144
+ const ipv4 = parseIpv4(host);
145
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || ipv4 && isPrivateIpv4(ipv4) || isPrivateIpv6(host)) {
146
+ throw new BatchworkError("batchwork: webhookUrl must not target localhost or private networks.");
147
+ }
148
+ };
149
+ var validateWebhookUrl = async (rawUrl, validator) => {
150
+ let url;
151
+ try {
152
+ url = new URL(rawUrl);
153
+ } catch (error) {
154
+ throw new BatchworkError("batchwork: webhookUrl must be a valid URL.", {
155
+ cause: error
156
+ });
157
+ }
158
+ await validator(url);
159
+ return url.toString();
160
+ };
161
+ var createWebhookSink = (validator) => async (record, snapshot) => {
162
+ if (!record.webhookUrl) {
163
+ throw new BatchworkError("batchwork: tracked batch has no webhookUrl to deliver to.");
164
+ }
165
+ const webhookUrl = await validateWebhookUrl(record.webhookUrl, validator);
166
+ const body = JSON.stringify(toEvent(record.provider, snapshot));
167
+ const headers = {
168
+ "content-type": "application/json"
169
+ };
170
+ if (record.webhookSecret) {
171
+ Object.assign(headers, await signWebhook(record.webhookSecret, record.id, body, Date.now() / 1000));
172
+ }
173
+ const response = await fetch(webhookUrl, {
174
+ body,
175
+ headers,
176
+ method: "POST",
177
+ redirect: "manual"
178
+ });
179
+ if (isRedirectStatus(response.status)) {
180
+ throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`);
181
+ }
182
+ if (!response.ok) {
183
+ throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`);
184
+ }
185
+ };
186
+ var createBatchPoller = (options) => {
187
+ const resolveCredentials = (provider) => {
188
+ if (typeof options.credentials === "function") {
189
+ return options.credentials(provider);
190
+ }
191
+ return options.credentials ?? {};
192
+ };
193
+ const webhookUrlValidator = options.validateWebhookUrl ?? assertSafeWebhookUrl;
194
+ const sink = options.onComplete ?? createWebhookSink(webhookUrlValidator);
195
+ const deliver = async (record, snapshot) => {
196
+ await sink(record, snapshot);
197
+ await options.store.set({
198
+ ...record,
199
+ deliveredAt: new Date().toISOString(),
200
+ status: snapshot.status
201
+ });
202
+ };
203
+ const track = async (target, opts) => {
204
+ const webhookUrl = opts.webhookUrl && !options.onComplete ? await validateWebhookUrl(opts.webhookUrl, webhookUrlValidator) : opts.webhookUrl;
205
+ const record = {
206
+ createdAt: new Date().toISOString(),
207
+ id: target.id,
208
+ provider: target.provider,
209
+ status: target.status ?? "in_progress",
210
+ webhookSecret: opts.secret,
211
+ webhookUrl
212
+ };
213
+ await options.store.set(record);
214
+ return record;
215
+ };
216
+ const process = async (record, delivered) => {
217
+ const adapter = getAdapter(record.provider);
218
+ const snapshot = await adapter.retrieve(record.id, resolveCredentials(record.provider));
219
+ if (isTerminalStatus(snapshot.status)) {
220
+ await deliver(record, snapshot);
221
+ delivered.push(record.id);
222
+ } else if (snapshot.status !== record.status) {
223
+ await options.store.set({ ...record, status: snapshot.status });
224
+ }
225
+ };
226
+ const tick = async () => {
227
+ const pending = await options.store.list({ delivered: false });
228
+ const delivered = [];
229
+ const failed = [];
230
+ for (const record of pending) {
231
+ try {
232
+ await process(record, delivered);
233
+ } catch (error) {
234
+ if (!options.onError) {
235
+ throw error;
236
+ }
237
+ options.onError(record, error);
238
+ failed.push({
239
+ error: error instanceof Error ? error.message : String(error),
240
+ id: record.id
241
+ });
242
+ }
243
+ }
244
+ return failed.length > 0 ? { checked: pending.length, delivered, failed } : { checked: pending.length, delivered };
245
+ };
246
+ const openaiWebhookHandler = (config) => async (request) => {
247
+ let verified;
248
+ try {
249
+ verified = await verifyWebhook(request, config.signingSecret);
250
+ } catch {
251
+ return new Response("invalid signature", { status: 400 });
252
+ }
253
+ const payload = JSON.parse(verified.body);
254
+ if (!payload.type?.startsWith("batch.")) {
255
+ return new Response("ignored", { status: 202 });
256
+ }
257
+ const id = payload.data?.id;
258
+ if (!id) {
259
+ return new Response("missing batch id", { status: 400 });
260
+ }
261
+ const record = await options.store.get(id);
262
+ if (!record || record.deliveredAt) {
263
+ return new Response("ok", { status: 200 });
264
+ }
265
+ const snapshot = await getAdapter("openai").retrieve(id, resolveCredentials("openai"));
266
+ if (isTerminalStatus(snapshot.status)) {
267
+ await deliver(record, snapshot);
268
+ }
269
+ return new Response("ok", { status: 200 });
270
+ };
271
+ return { deliver, openaiWebhookHandler, tick, track };
272
+ };
273
+
274
+ // src/server/store.ts
275
+ var createMemoryStore = () => {
276
+ const records = new Map;
277
+ return {
278
+ delete: (id) => {
279
+ records.delete(id);
280
+ return Promise.resolve();
281
+ },
282
+ get: (id) => Promise.resolve(records.get(id) ?? null),
283
+ list: (filter) => {
284
+ const all = [...records.values()];
285
+ if (filter?.delivered === undefined) {
286
+ return Promise.resolve(all);
287
+ }
288
+ const { delivered } = filter;
289
+ return Promise.resolve(all.filter((record) => record.deliveredAt !== undefined === delivered));
290
+ },
291
+ set: (record) => {
292
+ records.set(record.id, record);
293
+ return Promise.resolve();
294
+ }
295
+ };
296
+ };
297
+
298
+ export { toEvent, signWebhook, verifyWebhook, verifyBatchWebhook, createBatchPoller, createMemoryStore };
299
+
300
+ //# debugId=14EA1AD74C5676A164756E2164756E21
301
+ //# sourceMappingURL=chunk-e6qn48qa.js.map
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/server/events.ts", "../src/server/signing.ts", "../src/server/poller.ts", "../src/server/store.ts"],
4
+ "sourcesContent": [
5
+ "import type { BatchProvider, BatchSnapshot, BatchStatus } from \"../types\";\nimport type { BatchWebhookEvent, BatchWebhookEventType } from \"./types\";\n\nconst EVENT_BY_STATUS: Partial<Record<BatchStatus, BatchWebhookEventType>> = {\n cancelled: \"batch.cancelled\",\n completed: \"batch.completed\",\n expired: \"batch.expired\",\n failed: \"batch.failed\",\n};\n\n/** Map a terminal snapshot to the unified webhook event batchwork delivers. */\nexport const toEvent = (\n provider: BatchProvider,\n snapshot: BatchSnapshot\n): BatchWebhookEvent => ({\n completedAt: snapshot.completedAt?.toISOString(),\n createdAt: snapshot.createdAt?.toISOString(),\n id: snapshot.id,\n provider,\n requestCounts: snapshot.requestCounts,\n type: EVENT_BY_STATUS[snapshot.status] ?? \"batch.completed\",\n});\n",
6
+ "import { BatchworkError } from \"../errors\";\nimport type { BatchWebhookEvent } from \"./types\";\n\n// Standard Webhooks-style HMAC-SHA256 signing, compatible with OpenAI's webhook\n// signatures (so the same verifier handles inbound OpenAI events and batchwork's\n// own outbound deliveries). Uses Web Crypto so it runs on edge runtimes.\n\nconst SIGNATURE_VERSION = \"v1\";\nconst TOLERANCE_SECONDS = 300;\nconst SECRET_PREFIX = \"whsec_\";\nconst encoder = new TextEncoder();\n\nexport interface WebhookReplayStore {\n get: (id: string) => number | Promise<number | undefined> | undefined;\n set: (id: string, expiresAt: number) => Promise<void> | void;\n}\n\nexport interface VerifyWebhookOptions {\n replayStore?: WebhookReplayStore;\n}\n\nconst replayCache = new Map<string, number>();\n\nconst defaultReplayStore: WebhookReplayStore = {\n get: (id) => replayCache.get(id),\n set: (id, expiresAt) => {\n replayCache.set(id, expiresAt);\n },\n};\n\nconst pruneReplayCache = (now: number): void => {\n for (const [id, expiresAt] of replayCache) {\n if (expiresAt <= now) {\n replayCache.delete(id);\n }\n }\n};\n\nconst base64ToBytes = (value: string): Uint8Array<ArrayBuffer> =>\n Uint8Array.from(atob(value), (char) => char.codePointAt(0) ?? 0);\n\nconst bytesToBase64 = (bytes: Uint8Array): string => {\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCodePoint(byte);\n }\n return btoa(binary);\n};\n\nconst importKey = (secret: string): Promise<CryptoKey> => {\n const raw = secret.startsWith(SECRET_PREFIX)\n ? base64ToBytes(secret.slice(SECRET_PREFIX.length))\n : encoder.encode(secret);\n return crypto.subtle.importKey(\n \"raw\",\n raw,\n { hash: \"SHA-256\", name: \"HMAC\" },\n false,\n [\"sign\", \"verify\"]\n );\n};\n\nconst signContent = async (\n secret: string,\n content: string\n): Promise<string> => {\n const key = await importKey(secret);\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n encoder.encode(content)\n );\n return bytesToBase64(new Uint8Array(signature));\n};\n\nconst verifyContent = async (\n secret: string,\n content: string,\n signature: string\n): Promise<boolean> => {\n const key = await importKey(secret);\n return crypto.subtle.verify(\n \"HMAC\",\n key,\n base64ToBytes(signature),\n encoder.encode(content)\n );\n};\n\nconst rememberWebhookId = async (\n id: string,\n timestamp: number,\n now: number,\n store: WebhookReplayStore\n): Promise<void> => {\n if (store === defaultReplayStore) {\n pruneReplayCache(now);\n }\n const expiresAt = await store.get(id);\n if (expiresAt && expiresAt > now) {\n throw new BatchworkError(\"batchwork: webhook replay detected.\");\n }\n await store.set(id, timestamp + TOLERANCE_SECONDS);\n};\n\n/** Build Standard Webhooks signature headers for an outbound delivery. */\nexport const signWebhook = async (\n secret: string,\n id: string,\n body: string,\n timestampSeconds: number\n): Promise<Record<string, string>> => {\n const timestamp = Math.floor(timestampSeconds).toString();\n const signature = await signContent(secret, `${id}.${timestamp}.${body}`);\n return {\n \"webhook-id\": id,\n \"webhook-signature\": `${SIGNATURE_VERSION},${signature}`,\n \"webhook-timestamp\": timestamp,\n };\n};\n\nexport interface VerifiedWebhook {\n id: string;\n timestamp: number;\n body: string;\n}\n\n/**\n * Verify a Standard Webhooks-signed request and return its raw body. Throws if\n * headers are missing, the timestamp is outside tolerance, or no signature\n * matches. Consumes the request body.\n */\nexport const verifyWebhook = async (\n request: Request,\n secret: string,\n options?: VerifyWebhookOptions\n): Promise<VerifiedWebhook> => {\n const id = request.headers.get(\"webhook-id\");\n const timestamp = request.headers.get(\"webhook-timestamp\");\n const signatureHeader = request.headers.get(\"webhook-signature\");\n if (!(id && timestamp && signatureHeader)) {\n throw new BatchworkError(\"batchwork: missing webhook signature headers.\");\n }\n\n const seconds = Number(timestamp);\n const now = Date.now() / 1000;\n if (\n !Number.isFinite(seconds) ||\n Math.abs(now - seconds) > TOLERANCE_SECONDS\n ) {\n throw new BatchworkError(\"batchwork: webhook timestamp outside tolerance.\");\n }\n\n const body = await request.text();\n const content = `${id}.${timestamp}.${body}`;\n const signatures = signatureHeader.split(\" \").map((part) => {\n const comma = part.indexOf(\",\");\n return comma === -1 ? part : part.slice(comma + 1);\n });\n\n let valid = false;\n for (const signature of signatures) {\n // oxlint-disable-next-line no-await-in-loop -- usually a single signature.\n if (await verifyContent(secret, content, signature)) {\n valid = true;\n break;\n }\n }\n if (!valid) {\n throw new BatchworkError(\n \"batchwork: webhook signature verification failed.\"\n );\n }\n\n await rememberWebhookId(\n id,\n seconds,\n now,\n options?.replayStore ?? defaultReplayStore\n );\n\n return { body, id, timestamp: seconds };\n};\n\n/**\n * Verify and parse a batchwork webhook on your receiving endpoint. Returns the\n * unified {@link BatchWebhookEvent}.\n */\nexport const verifyBatchWebhook = async (\n request: Request,\n secret: string,\n options?: VerifyWebhookOptions\n): Promise<BatchWebhookEvent> => {\n const { body } = await verifyWebhook(request, secret, options);\n return JSON.parse(body) as BatchWebhookEvent;\n};\n",
7
+ "import { BatchworkError } from \"../errors\";\nimport { isTerminalStatus } from \"../job\";\nimport { getAdapter } from \"../providers\";\nimport type {\n BatchProvider,\n BatchSnapshot,\n BatchStatus,\n ProviderCredentials,\n} from \"../types\";\nimport { toEvent } from \"./events\";\nimport { signWebhook, verifyWebhook } from \"./signing\";\nimport type { BatchStore, TrackedBatch } from \"./types\";\n\n/** Credentials for polling: a fixed config, or one resolved per provider. */\nexport type CredentialResolver =\n | ProviderCredentials\n | ((provider: BatchProvider) => ProviderCredentials);\n\n/**\n * Handles a batch reaching a terminal status. Replaces the default signed\n * webhook delivery — e.g. to invoke a callback instead (see `batchwork/next`).\n */\nexport type CompletionSink = (\n record: TrackedBatch,\n snapshot: BatchSnapshot\n) => Promise<void>;\n\nexport type WebhookUrlValidator = (url: URL) => void | Promise<void>;\n\nexport interface BatchPollerOptions {\n store: BatchStore;\n /** Falls back to provider env vars (e.g. `OPENAI_API_KEY`) when omitted. */\n credentials?: CredentialResolver;\n /** Replaces signed-webhook delivery when a batch finishes. */\n onComplete?: CompletionSink;\n /** Override the default webhook URL policy with an application allowlist. */\n validateWebhookUrl?: WebhookUrlValidator;\n /**\n * Called when processing a single batch throws during `tick`. When provided,\n * the tick reports the error and continues to the next batch; when omitted,\n * the error propagates out of `tick`.\n */\n onError?: (record: TrackedBatch, error: unknown) => void;\n}\n\nexport interface TrackTarget {\n id: string;\n provider: BatchProvider;\n status?: BatchStatus;\n}\n\nexport interface TrackOptions {\n /** Where to POST the completion webhook. Omit for callback-based delivery. */\n webhookUrl?: string;\n /** Signs the outbound webhook (Standard Webhooks HMAC) when provided. */\n secret?: string;\n}\n\nexport interface TickResult {\n checked: number;\n delivered: string[];\n /** Batches whose processing threw this tick (only when `onError` is set). */\n failed?: { id: string; error: string }[];\n}\n\nexport interface OpenAIWebhookOptions {\n /** The OpenAI webhook signing secret (`whsec_…`). */\n signingSecret: string;\n}\n\nexport interface BatchPoller {\n track: (target: TrackTarget, options: TrackOptions) => Promise<TrackedBatch>;\n tick: () => Promise<TickResult>;\n deliver: (record: TrackedBatch, snapshot: BatchSnapshot) => Promise<void>;\n openaiWebhookHandler: (\n options: OpenAIWebhookOptions\n ) => (request: Request) => Promise<Response>;\n}\n\nconst parseIpv4 = (host: string): number[] | undefined => {\n if (!/^\\d{1,3}(?:\\.\\d{1,3}){3}$/u.test(host)) {\n return;\n }\n const parts = host.split(\".\").map(Number);\n const valid = parts.every(\n (part) => Number.isInteger(part) && part >= 0 && part <= 255\n );\n return valid ? parts : undefined;\n};\n\nconst isPrivateIpv4 = (parts: number[]): boolean => {\n const [a = 0, b = 0] = parts;\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 198 && (b === 18 || b === 19)) ||\n a >= 224\n );\n};\n\nconst isPrivateIpv6 = (host: string): boolean => {\n const normalized = host.replace(/^\\[/u, \"\").replace(/\\]$/u, \"\").toLowerCase();\n return (\n normalized === \"::\" ||\n normalized === \"::1\" ||\n normalized.startsWith(\"fc\") ||\n normalized.startsWith(\"fd\") ||\n normalized.startsWith(\"fe80:\")\n );\n};\n\nconst isRedirectStatus = (status: number): boolean =>\n status >= 300 && status < 400;\n\nconst assertSafeWebhookUrl: WebhookUrlValidator = (url) => {\n if (url.protocol !== \"https:\") {\n throw new BatchworkError(\"batchwork: webhookUrl must use https.\");\n }\n if (url.username || url.password) {\n throw new BatchworkError(\n \"batchwork: webhookUrl must not include credentials.\"\n );\n }\n\n const host = url.hostname.toLowerCase();\n const ipv4 = parseIpv4(host);\n if (\n host === \"localhost\" ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n (ipv4 && isPrivateIpv4(ipv4)) ||\n isPrivateIpv6(host)\n ) {\n throw new BatchworkError(\n \"batchwork: webhookUrl must not target localhost or private networks.\"\n );\n }\n};\n\nconst validateWebhookUrl = async (\n rawUrl: string,\n validator: WebhookUrlValidator\n): Promise<string> => {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch (error) {\n throw new BatchworkError(\"batchwork: webhookUrl must be a valid URL.\", {\n cause: error,\n });\n }\n await validator(url);\n return url.toString();\n};\n\n/** The default completion sink: POST a signed webhook to the tracked URL. */\nconst createWebhookSink =\n (validator: WebhookUrlValidator): CompletionSink =>\n async (record, snapshot) => {\n if (!record.webhookUrl) {\n throw new BatchworkError(\n \"batchwork: tracked batch has no webhookUrl to deliver to.\"\n );\n }\n const webhookUrl = await validateWebhookUrl(record.webhookUrl, validator);\n const body = JSON.stringify(toEvent(record.provider, snapshot));\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n };\n if (record.webhookSecret) {\n Object.assign(\n headers,\n await signWebhook(\n record.webhookSecret,\n record.id,\n body,\n Date.now() / 1000\n )\n );\n }\n const response = await fetch(webhookUrl, {\n body,\n headers,\n method: \"POST\",\n redirect: \"manual\",\n });\n if (isRedirectStatus(response.status)) {\n throw new BatchworkError(\n `batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`\n );\n }\n if (!response.ok) {\n throw new BatchworkError(\n `batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`\n );\n }\n };\n\n/**\n * Create a managed poller: register submitted batches with `track`, then run\n * `tick` on a schedule (cron) to poll open batches and deliver one unified,\n * signed webhook per batch when it finishes. For OpenAI, mount\n * `openaiWebhookHandler` to skip polling and react to native webhooks instead.\n */\nexport const createBatchPoller = (options: BatchPollerOptions): BatchPoller => {\n const resolveCredentials = (provider: BatchProvider): ProviderCredentials => {\n if (typeof options.credentials === \"function\") {\n return options.credentials(provider);\n }\n return options.credentials ?? {};\n };\n\n const webhookUrlValidator =\n options.validateWebhookUrl ?? assertSafeWebhookUrl;\n const sink = options.onComplete ?? createWebhookSink(webhookUrlValidator);\n\n const deliver = async (\n record: TrackedBatch,\n snapshot: BatchSnapshot\n ): Promise<void> => {\n // Run the side effect before marking delivered: if it throws, the record\n // stays pending and is retried on the next tick (at-least-once delivery).\n await sink(record, snapshot);\n await options.store.set({\n ...record,\n deliveredAt: new Date().toISOString(),\n status: snapshot.status,\n });\n };\n\n const track = async (\n target: TrackTarget,\n opts: TrackOptions\n ): Promise<TrackedBatch> => {\n const webhookUrl =\n opts.webhookUrl && !options.onComplete\n ? await validateWebhookUrl(opts.webhookUrl, webhookUrlValidator)\n : opts.webhookUrl;\n const record: TrackedBatch = {\n createdAt: new Date().toISOString(),\n id: target.id,\n provider: target.provider,\n status: target.status ?? \"in_progress\",\n webhookSecret: opts.secret,\n webhookUrl,\n };\n await options.store.set(record);\n return record;\n };\n\n const process = async (\n record: TrackedBatch,\n delivered: string[]\n ): Promise<void> => {\n const adapter = getAdapter(record.provider);\n const snapshot = await adapter.retrieve(\n record.id,\n resolveCredentials(record.provider)\n );\n if (isTerminalStatus(snapshot.status)) {\n await deliver(record, snapshot);\n delivered.push(record.id);\n } else if (snapshot.status !== record.status) {\n await options.store.set({ ...record, status: snapshot.status });\n }\n };\n\n const tick = async (): Promise<TickResult> => {\n const pending = await options.store.list({ delivered: false });\n const delivered: string[] = [];\n const failed: { id: string; error: string }[] = [];\n for (const record of pending) {\n // oxlint-disable-next-line no-await-in-loop -- batches are polled serially\n // to avoid hammering provider rate limits; deliver before the next.\n try {\n // oxlint-disable-next-line no-await-in-loop -- see above.\n await process(record, delivered);\n } catch (error) {\n // Without an `onError` handler, preserve the propagate-the-throw\n // behavior; with one, report and continue so a single failing batch\n // can't starve the rest of the queue. Either way the record stays\n // pending (it was never marked delivered) and retries next tick.\n if (!options.onError) {\n throw error;\n }\n options.onError(record, error);\n failed.push({\n error: error instanceof Error ? error.message : String(error),\n id: record.id,\n });\n }\n }\n return failed.length > 0\n ? { checked: pending.length, delivered, failed }\n : { checked: pending.length, delivered };\n };\n\n const openaiWebhookHandler =\n (config: OpenAIWebhookOptions) =>\n async (request: Request): Promise<Response> => {\n let verified: { body: string };\n try {\n verified = await verifyWebhook(request, config.signingSecret);\n } catch {\n return new Response(\"invalid signature\", { status: 400 });\n }\n\n const payload = JSON.parse(verified.body) as {\n type?: string;\n data?: { id?: string };\n };\n if (!payload.type?.startsWith(\"batch.\")) {\n return new Response(\"ignored\", { status: 202 });\n }\n const id = payload.data?.id;\n if (!id) {\n return new Response(\"missing batch id\", { status: 400 });\n }\n const record = await options.store.get(id);\n if (!record || record.deliveredAt) {\n return new Response(\"ok\", { status: 200 });\n }\n const snapshot = await getAdapter(\"openai\").retrieve(\n id,\n resolveCredentials(\"openai\")\n );\n if (isTerminalStatus(snapshot.status)) {\n await deliver(record, snapshot);\n }\n return new Response(\"ok\", { status: 200 });\n };\n\n return { deliver, openaiWebhookHandler, tick, track };\n};\n",
8
+ "import type { BatchStore, TrackedBatch } from \"./types\";\n\n/** An in-memory `BatchStore`. Suitable for development and single-process use. */\nexport const createMemoryStore = (): BatchStore => {\n const records = new Map<string, TrackedBatch>();\n\n return {\n delete: (id) => {\n records.delete(id);\n return Promise.resolve();\n },\n get: (id) => Promise.resolve(records.get(id) ?? null),\n list: (filter) => {\n const all = [...records.values()];\n if (filter?.delivered === undefined) {\n return Promise.resolve(all);\n }\n const { delivered } = filter;\n return Promise.resolve(\n all.filter((record) => (record.deliveredAt !== undefined) === delivered)\n );\n },\n set: (record) => {\n records.set(record.id, record);\n return Promise.resolve();\n },\n };\n};\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "14EA1AD74C5676A164756E2164756E21",
12
+ "names": []
13
+ }