batchwork 1.4.0 → 1.5.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.
Files changed (61) hide show
  1. package/dist/body.d.ts +2 -2
  2. package/dist/body.d.ts.map +1 -1
  3. package/dist/chunk-6jgvqdpc.js +52 -0
  4. package/dist/chunk-6jgvqdpc.js.map +11 -0
  5. package/dist/{chunk-vy4w8mpb.js → chunk-7y95mhvm.js} +119 -151
  6. package/dist/chunk-7y95mhvm.js.map +27 -0
  7. package/dist/{chunk-ka9d4t6v.js → chunk-k7n7k3et.js} +66 -152
  8. package/dist/chunk-k7n7k3et.js.map +13 -0
  9. package/dist/{chunk-2ea62n95.js → chunk-zss8h417.js} +213 -200
  10. package/dist/chunk-zss8h417.js.map +12 -0
  11. package/dist/guards.d.ts +5 -0
  12. package/dist/guards.d.ts.map +1 -0
  13. package/dist/http.d.ts +3 -2
  14. package/dist/http.d.ts.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +27 -28
  18. package/dist/index.js.map +1 -1
  19. package/dist/jsonl.d.ts +3 -2
  20. package/dist/jsonl.d.ts.map +1 -1
  21. package/dist/limits.d.ts +0 -1
  22. package/dist/limits.d.ts.map +1 -1
  23. package/dist/model.d.ts +48 -1
  24. package/dist/model.d.ts.map +1 -1
  25. package/dist/next/index.d.ts +1 -1
  26. package/dist/next/index.d.ts.map +1 -1
  27. package/dist/next/index.js +14 -20
  28. package/dist/next/index.js.map +3 -3
  29. package/dist/postgres/batch-store.d.ts.map +1 -1
  30. package/dist/postgres/index.js +22 -15
  31. package/dist/postgres/index.js.map +4 -4
  32. package/dist/postgres/types.d.ts +1 -1
  33. package/dist/postgres/types.d.ts.map +1 -1
  34. package/dist/providers/anthropic.d.ts.map +1 -1
  35. package/dist/providers/azure.d.ts.map +1 -1
  36. package/dist/providers/google.d.ts.map +1 -1
  37. package/dist/providers/mistral.d.ts.map +1 -1
  38. package/dist/providers/openai-compatible.d.ts +2 -2
  39. package/dist/providers/openai-compatible.d.ts.map +1 -1
  40. package/dist/providers/shared.d.ts +8 -8
  41. package/dist/providers/shared.d.ts.map +1 -1
  42. package/dist/providers/xai.d.ts.map +1 -1
  43. package/dist/redis/index.js +40 -6
  44. package/dist/redis/index.js.map +4 -4
  45. package/dist/redis/types.d.ts +18 -5
  46. package/dist/redis/types.d.ts.map +1 -1
  47. package/dist/server/index.js +13 -14
  48. package/dist/server/index.js.map +1 -1
  49. package/dist/server/poller.d.ts +6 -4
  50. package/dist/server/poller.d.ts.map +1 -1
  51. package/dist/server/signing.d.ts.map +1 -1
  52. package/dist/types.d.ts +16 -2
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/util.d.ts +18 -7
  55. package/dist/util.d.ts.map +1 -1
  56. package/package.json +20 -17
  57. package/dist/chunk-2ea62n95.js.map +0 -12
  58. package/dist/chunk-ka9d4t6v.js.map +0 -13
  59. package/dist/chunk-v0bahtg2.js +0 -7
  60. package/dist/chunk-v0bahtg2.js.map +0 -9
  61. package/dist/chunk-vy4w8mpb.js.map +0 -28
@@ -1,8 +1,13 @@
1
1
  import {
2
- BatchworkError,
3
- getAdapter,
4
- isTerminalStatus
5
- } from "./chunk-vy4w8mpb.js";
2
+ BatchworkError2,
3
+ isTerminalStatus2,
4
+ getAdapter
5
+ } from "./chunk-7y95mhvm.js";
6
+ import {
7
+ parseJson,
8
+ asRecord,
9
+ asString
10
+ } from "./chunk-6jgvqdpc.js";
6
11
 
7
12
  // src/server/events.ts
8
13
  var EVENT_BY_STATUS = {
@@ -21,10 +26,11 @@ var toEvent = (provider, snapshot) => ({
21
26
  });
22
27
 
23
28
  // src/server/signing.ts
24
- var SIGNATURE_VERSION = "v1";
29
+ import { Webhook, WebhookVerificationError } from "standardwebhooks";
25
30
  var TOLERANCE_SECONDS = 300;
26
31
  var SECRET_PREFIX = "whsec_";
27
32
  var encoder = new TextEncoder;
33
+ var createWebhook = (secret) => secret.startsWith(SECRET_PREFIX) ? new Webhook(secret) : new Webhook(encoder.encode(secret), { format: "raw" });
28
34
  var replayCache = new Map;
29
35
  var replayLocks = new WeakMap;
30
36
  var pruneReplayCache = (now) => {
@@ -73,33 +79,12 @@ var withReplayLock = async (store, id, task) => {
73
79
  }
74
80
  }
75
81
  };
76
- var base64ToBytes = (value) => Uint8Array.from(atob(value), (char) => char.codePointAt(0) ?? 0);
77
- var bytesToBase64 = (bytes) => {
78
- let binary = "";
79
- for (const byte of bytes) {
80
- binary += String.fromCodePoint(byte);
81
- }
82
- return btoa(binary);
83
- };
84
- var importKey = (secret) => {
85
- const raw = secret.startsWith(SECRET_PREFIX) ? base64ToBytes(secret.slice(SECRET_PREFIX.length)) : encoder.encode(secret);
86
- return crypto.subtle.importKey("raw", raw, { hash: "SHA-256", name: "HMAC" }, false, ["sign", "verify"]);
87
- };
88
- var signContent = async (secret, content) => {
89
- const key = await importKey(secret);
90
- const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(content));
91
- return bytesToBase64(new Uint8Array(signature));
92
- };
93
- var verifyContent = async (secret, content, signature) => {
94
- const key = await importKey(secret);
95
- return crypto.subtle.verify("HMAC", key, base64ToBytes(signature), encoder.encode(content));
96
- };
97
82
  var rememberWebhookId = async (id, timestamp, now, store) => {
98
83
  const expiresAt = timestamp + TOLERANCE_SECONDS;
99
84
  if (store.claim) {
100
85
  const claimed = await store.claim(id, expiresAt, now);
101
86
  if (!claimed) {
102
- throw new BatchworkError("batchwork: webhook replay detected.");
87
+ throw new BatchworkError2("batchwork: webhook replay detected.");
103
88
  }
104
89
  return;
105
90
  }
@@ -109,145 +94,81 @@ var rememberWebhookId = async (id, timestamp, now, store) => {
109
94
  }
110
95
  const existing = await store.get(id);
111
96
  if (existing && existing > now) {
112
- throw new BatchworkError("batchwork: webhook replay detected.");
97
+ throw new BatchworkError2("batchwork: webhook replay detected.");
113
98
  }
114
99
  await store.set(id, expiresAt);
115
100
  });
116
101
  };
117
- var signWebhook = async (secret, id, body, timestampSeconds) => {
118
- const timestamp = Math.floor(timestampSeconds).toString();
119
- const signature = await signContent(secret, `${id}.${timestamp}.${body}`);
120
- return {
102
+ var signWebhook2 = (secret, id, body, timestampSeconds) => {
103
+ const timestamp = Math.floor(timestampSeconds);
104
+ const signature = createWebhook(secret).sign(id, new Date(timestamp * 1000), body);
105
+ return Promise.resolve({
121
106
  "webhook-id": id,
122
- "webhook-signature": `${SIGNATURE_VERSION},${signature}`,
123
- "webhook-timestamp": timestamp
124
- };
107
+ "webhook-signature": signature,
108
+ "webhook-timestamp": timestamp.toString()
109
+ });
125
110
  };
126
- var verifyWebhook = async (request, secret, options) => {
111
+ var verifyWebhook2 = async (request, secret, options) => {
127
112
  const id = request.headers.get("webhook-id");
128
113
  const timestamp = request.headers.get("webhook-timestamp");
129
114
  const signatureHeader = request.headers.get("webhook-signature");
130
115
  if (!(id && timestamp && signatureHeader)) {
131
- throw new BatchworkError("batchwork: missing webhook signature headers.");
116
+ throw new BatchworkError2("batchwork: missing webhook signature headers.");
132
117
  }
133
118
  const seconds = Number(timestamp);
134
119
  const now = Date.now() / 1000;
135
120
  if (!Number.isFinite(seconds) || Math.abs(now - seconds) > TOLERANCE_SECONDS) {
136
- throw new BatchworkError("batchwork: webhook timestamp outside tolerance.");
121
+ throw new BatchworkError2("batchwork: webhook timestamp outside tolerance.");
137
122
  }
138
123
  const body = await request.text();
139
- const content = `${id}.${timestamp}.${body}`;
140
- const signatures = signatureHeader.split(" ").map((part) => {
141
- const comma = part.indexOf(",");
142
- return comma === -1 ? part : part.slice(comma + 1);
143
- });
144
- let valid = false;
145
- for (const signature of signatures) {
146
- if (await verifyContent(secret, content, signature)) {
147
- valid = true;
148
- break;
124
+ try {
125
+ createWebhook(secret).verify(body, {
126
+ "webhook-id": id,
127
+ "webhook-signature": signatureHeader,
128
+ "webhook-timestamp": timestamp
129
+ });
130
+ } catch (error) {
131
+ if (error instanceof WebhookVerificationError) {
132
+ throw new BatchworkError2(error.message.toLowerCase().includes("timestamp") ? "batchwork: webhook timestamp outside tolerance." : "batchwork: webhook signature verification failed.", { cause: error });
149
133
  }
150
134
  }
151
- if (!valid) {
152
- throw new BatchworkError("batchwork: webhook signature verification failed.");
153
- }
154
135
  await rememberWebhookId(id, seconds, now, options?.replayStore ?? defaultReplayStore);
155
136
  return { body, id, timestamp: seconds };
156
137
  };
157
- var verifyBatchWebhook = async (request, secret, options) => {
158
- const { body } = await verifyWebhook(request, secret, options);
138
+ var verifyBatchWebhook2 = async (request, secret, options) => {
139
+ const { body } = await verifyWebhook2(request, secret, options);
159
140
  return JSON.parse(body);
160
141
  };
161
142
 
162
143
  // src/server/poller.ts
163
- var parseIpv4 = (host) => {
164
- if (!/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(host)) {
165
- return;
166
- }
167
- const parts = host.split(".").map(Number);
168
- const valid = parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255);
169
- return valid ? parts : undefined;
170
- };
171
- var isPrivateIpv4 = (parts) => {
172
- const [a = 0, b = 0] = parts;
173
- if (a === 0 || a === 10 || a === 127 || a >= 224) {
174
- return true;
175
- }
176
- switch (a) {
177
- case 100: {
178
- return b >= 64 && b <= 127;
179
- }
180
- case 169: {
181
- return b === 254;
182
- }
183
- case 172: {
184
- return b >= 16 && b <= 31;
185
- }
186
- case 192: {
187
- return b === 168;
188
- }
189
- case 198: {
190
- return b === 18 || b === 19;
191
- }
192
- default: {
193
- return false;
194
- }
195
- }
196
- };
197
- var isHextet = (value) => Number.isInteger(value) && value >= 0 && value <= 65535;
198
- var parseIpv4MappedIpv6 = (host) => {
199
- const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase();
200
- if (!normalized.startsWith("::ffff:")) {
201
- return;
202
- }
203
- const suffix = normalized.slice("::ffff:".length);
204
- const dotted = parseIpv4(suffix);
205
- if (dotted) {
206
- return dotted;
207
- }
208
- const [high, low, extra] = suffix.split(":");
209
- if (!(high && low) || extra !== undefined) {
210
- return;
211
- }
212
- const highBits = Number.parseInt(high, 16);
213
- const lowBits = Number.parseInt(low, 16);
214
- if (!(isHextet(highBits) && isHextet(lowBits))) {
215
- return;
216
- }
217
- return [
218
- Math.floor(highBits / 256),
219
- highBits % 256,
220
- Math.floor(lowBits / 256),
221
- lowBits % 256
222
- ];
223
- };
224
- var isPrivateIpv6 = (host) => {
225
- const normalized = host.replace(/^\[/u, "").replace(/\]$/u, "").toLowerCase();
226
- if (!normalized.includes(":")) {
227
- return false;
228
- }
229
- if (normalized === "::" || normalized === "::1") {
230
- return true;
144
+ import ipaddr from "ipaddr.js";
145
+ var isCredentialFunction = (credentials) => typeof credentials === "function";
146
+ var resolveProviderCredentials = (credentials, provider) => {
147
+ if (credentials === undefined) {
148
+ return {};
231
149
  }
232
- return normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:");
150
+ return isCredentialFunction(credentials) ? credentials(provider) : credentials;
233
151
  };
234
152
  var isPrivateHost = (host) => {
235
153
  if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
236
154
  return true;
237
155
  }
238
- const ipv4 = parseIpv4(host) ?? parseIpv4MappedIpv6(host);
239
- return (ipv4 ? isPrivateIpv4(ipv4) : false) || isPrivateIpv6(host);
156
+ const literal = host.replace(/^\[/u, "").replace(/\]$/u, "");
157
+ if (!ipaddr.isValid(literal)) {
158
+ return false;
159
+ }
160
+ return ipaddr.process(literal).range() !== "unicast";
240
161
  };
241
162
  var isRedirectStatus = (status) => status >= 300 && status < 400;
242
163
  var assertSafeWebhookUrl = (url) => {
243
164
  if (url.protocol !== "https:") {
244
- throw new BatchworkError("batchwork: webhookUrl must use https.");
165
+ throw new BatchworkError2("batchwork: webhookUrl must use https.");
245
166
  }
246
167
  if (url.username || url.password) {
247
- throw new BatchworkError("batchwork: webhookUrl must not include credentials.");
168
+ throw new BatchworkError2("batchwork: webhookUrl must not include credentials.");
248
169
  }
249
170
  if (isPrivateHost(url.hostname.toLowerCase())) {
250
- throw new BatchworkError("batchwork: webhookUrl must not target localhost or private networks.");
171
+ throw new BatchworkError2("batchwork: webhookUrl must not target localhost or private networks.");
251
172
  }
252
173
  };
253
174
  var validateWebhookUrl = async (rawUrl, validator) => {
@@ -255,7 +176,7 @@ var validateWebhookUrl = async (rawUrl, validator) => {
255
176
  try {
256
177
  url = new URL(rawUrl);
257
178
  } catch (error) {
258
- throw new BatchworkError("batchwork: webhookUrl must be a valid URL.", {
179
+ throw new BatchworkError2("batchwork: webhookUrl must be a valid URL.", {
259
180
  cause: error
260
181
  });
261
182
  }
@@ -264,15 +185,13 @@ var validateWebhookUrl = async (rawUrl, validator) => {
264
185
  };
265
186
  var createWebhookSink = (validator) => async (record, snapshot) => {
266
187
  if (!record.webhookUrl) {
267
- throw new BatchworkError("batchwork: tracked batch has no webhookUrl to deliver to.");
188
+ throw new BatchworkError2("batchwork: tracked batch has no webhookUrl to deliver to.");
268
189
  }
269
190
  const webhookUrl = await validateWebhookUrl(record.webhookUrl, validator);
270
191
  const body = JSON.stringify(toEvent(record.provider, snapshot));
271
- const headers = {
272
- "content-type": "application/json"
273
- };
192
+ const headers = { "content-type": "application/json" };
274
193
  if (record.webhookSecret) {
275
- Object.assign(headers, await signWebhook(record.webhookSecret, record.id, body, Date.now() / 1000));
194
+ Object.assign(headers, await signWebhook2(record.webhookSecret, record.id, body, Date.now() / 1000));
276
195
  }
277
196
  const response = await fetch(webhookUrl, {
278
197
  body,
@@ -281,19 +200,14 @@ var createWebhookSink = (validator) => async (record, snapshot) => {
281
200
  redirect: "manual"
282
201
  });
283
202
  if (isRedirectStatus(response.status)) {
284
- throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`);
203
+ throw new BatchworkError2(`batchwork: webhook delivery to ${webhookUrl} redirected (${response.status}).`);
285
204
  }
286
205
  if (!response.ok) {
287
- throw new BatchworkError(`batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`);
206
+ throw new BatchworkError2(`batchwork: webhook delivery to ${webhookUrl} failed (${response.status}).`);
288
207
  }
289
208
  };
290
- var createBatchPoller = (options) => {
291
- const resolveCredentials = (provider) => {
292
- if (typeof options.credentials === "function") {
293
- return options.credentials(provider);
294
- }
295
- return options.credentials ?? {};
296
- };
209
+ var createBatchPoller2 = (options) => {
210
+ const resolveCredentials = (provider) => resolveProviderCredentials(options.credentials, provider);
297
211
  const webhookUrlValidator = options.validateWebhookUrl ?? assertSafeWebhookUrl;
298
212
  const sink = options.onComplete ?? createWebhookSink(webhookUrlValidator);
299
213
  const deliver = async (record, snapshot) => {
@@ -320,7 +234,7 @@ var createBatchPoller = (options) => {
320
234
  const process = async (record, delivered) => {
321
235
  const adapter = getAdapter(record.provider);
322
236
  const snapshot = await adapter.retrieve(record.id, resolveCredentials(record.provider));
323
- if (isTerminalStatus(snapshot.status)) {
237
+ if (isTerminalStatus2(snapshot.status)) {
324
238
  await deliver(record, snapshot);
325
239
  delivered.push(record.id);
326
240
  } else if (snapshot.status !== record.status) {
@@ -350,15 +264,15 @@ var createBatchPoller = (options) => {
350
264
  const openaiWebhookHandler = (config) => async (request) => {
351
265
  let verified;
352
266
  try {
353
- verified = await verifyWebhook(request, config.signingSecret);
267
+ verified = await verifyWebhook2(request, config.signingSecret);
354
268
  } catch {
355
269
  return new Response("invalid signature", { status: 400 });
356
270
  }
357
- const payload = JSON.parse(verified.body);
358
- if (!payload.type?.startsWith("batch.")) {
271
+ const payload = asRecord(parseJson(verified.body));
272
+ if (!asString(payload.type)?.startsWith("batch.")) {
359
273
  return new Response("ignored", { status: 202 });
360
274
  }
361
- const id = payload.data?.id;
275
+ const id = asString(asRecord(payload.data).id);
362
276
  if (!id) {
363
277
  return new Response("missing batch id", { status: 400 });
364
278
  }
@@ -367,7 +281,7 @@ var createBatchPoller = (options) => {
367
281
  return new Response("ok", { status: 200 });
368
282
  }
369
283
  const snapshot = await getAdapter("openai").retrieve(id, resolveCredentials("openai"));
370
- if (isTerminalStatus(snapshot.status)) {
284
+ if (isTerminalStatus2(snapshot.status)) {
371
285
  await deliver(record, snapshot);
372
286
  }
373
287
  return new Response("ok", { status: 200 });
@@ -376,7 +290,7 @@ var createBatchPoller = (options) => {
376
290
  };
377
291
 
378
292
  // src/server/store.ts
379
- var createMemoryStore = () => {
293
+ var createMemoryStore2 = () => {
380
294
  const records = new Map;
381
295
  return {
382
296
  delete: (id) => {
@@ -399,7 +313,7 @@ var createMemoryStore = () => {
399
313
  };
400
314
  };
401
315
 
402
- export { toEvent, signWebhook, verifyWebhook, verifyBatchWebhook, createBatchPoller, createMemoryStore };
316
+ export { toEvent, signWebhook2, verifyWebhook2, verifyBatchWebhook2, resolveProviderCredentials, createBatchPoller2, createMemoryStore2 };
403
317
 
404
- //# debugId=F1B4BA11C9A6AE3364756E2164756E21
405
- //# sourceMappingURL=chunk-ka9d4t6v.js.map
318
+ //# debugId=A205BBC0AF616CBE64756E2164756E21
319
+ //# sourceMappingURL=chunk-k7n7k3et.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 { Webhook, WebhookVerificationError } from \"standardwebhooks\";\n\nimport { BatchworkError } from \"../errors\";\nimport type { BatchWebhookEvent } from \"./types\";\n\n// Standard Webhooks HMAC-SHA256 signing via the reference `standardwebhooks`\n// implementation, compatible with OpenAI's webhook signatures (so the same\n// verifier handles inbound OpenAI events and batchwork's own outbound\n// deliveries). The library is pure JS, so it runs on edge runtimes.\n\nconst TOLERANCE_SECONDS = 300;\nconst SECRET_PREFIX = \"whsec_\";\nconst encoder = new TextEncoder();\n\n/**\n * A `whsec_` secret is base64-encoded key material (the library decodes it);\n * anything else is used as raw UTF-8 bytes, matching OpenAI's conventions.\n */\nconst createWebhook = (secret: string): Webhook =>\n secret.startsWith(SECRET_PREFIX)\n ? new Webhook(secret)\n : new Webhook(encoder.encode(secret), { format: \"raw\" });\n\nexport interface WebhookReplayStore {\n claim?: (\n id: string,\n expiresAt: number,\n now: number\n ) => boolean | Promise<boolean>;\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>();\nconst replayLocks = new WeakMap<\n WebhookReplayStore,\n Map<string, Promise<void>>\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 defaultReplayStore: WebhookReplayStore = {\n claim: (id, expiresAt, now) => {\n pruneReplayCache(now);\n const existing = replayCache.get(id);\n if (existing && existing > now) {\n return false;\n }\n replayCache.set(id, expiresAt);\n return true;\n },\n get: (id) => replayCache.get(id),\n set: (id, expiresAt) => {\n replayCache.set(id, expiresAt);\n },\n};\n\nconst withReplayLock = async <Result>(\n store: WebhookReplayStore,\n id: string,\n task: () => Promise<Result>\n): Promise<Result> => {\n let locks = replayLocks.get(store);\n if (!locks) {\n locks = new Map();\n replayLocks.set(store, locks);\n }\n\n const previous = locks.get(id);\n let release!: () => void;\n // oxlint-disable-next-line promise/avoid-new -- a per-id lock needs a deferred release signal.\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n locks.set(id, current);\n\n if (previous) {\n await previous;\n }\n try {\n return await task();\n } finally {\n release();\n if (locks.get(id) === current) {\n locks.delete(id);\n }\n }\n};\n\nconst rememberWebhookId = async (\n id: string,\n timestamp: number,\n now: number,\n store: WebhookReplayStore\n): Promise<void> => {\n const expiresAt = timestamp + TOLERANCE_SECONDS;\n if (store.claim) {\n const claimed = await store.claim(id, expiresAt, now);\n if (!claimed) {\n throw new BatchworkError(\"batchwork: webhook replay detected.\");\n }\n return;\n }\n\n await withReplayLock(store, id, async () => {\n if (store === defaultReplayStore) {\n pruneReplayCache(now);\n }\n const existing = await store.get(id);\n if (existing && existing > now) {\n throw new BatchworkError(\"batchwork: webhook replay detected.\");\n }\n await store.set(id, expiresAt);\n });\n};\n\n/** Build Standard Webhooks signature headers for an outbound delivery. */\nexport const signWebhook = (\n secret: string,\n id: string,\n body: string,\n timestampSeconds: number\n): Promise<Record<string, string>> => {\n const timestamp = Math.floor(timestampSeconds);\n // `sign` returns the already version-prefixed signature (`v1,…`).\n const signature = createWebhook(secret).sign(\n id,\n new Date(timestamp * 1000),\n body\n );\n return Promise.resolve({\n \"webhook-id\": id,\n \"webhook-signature\": signature,\n \"webhook-timestamp\": timestamp.toString(),\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 try {\n createWebhook(secret).verify(body, {\n \"webhook-id\": id,\n \"webhook-signature\": signatureHeader,\n \"webhook-timestamp\": timestamp,\n });\n } catch (error) {\n if (error instanceof WebhookVerificationError) {\n // The library re-checks the timestamp with the same tolerance; map its\n // failures onto the tolerance error above for a stable error surface.\n throw new BatchworkError(\n error.message.toLowerCase().includes(\"timestamp\")\n ? \"batchwork: webhook timestamp outside tolerance.\"\n : \"batchwork: webhook signature verification failed.\",\n { cause: error }\n );\n }\n // Any other error means the signature verified but the library's trailing\n // `JSON.parse` of the payload failed. `verifyWebhook` returns the raw body\n // and does not require JSON, so a non-JSON body is not an error here.\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 // SAFETY: the signature just verified against the shared secret proves the\n // body was produced by batchwork's own delivery, which serializes `toEvent`.\n return JSON.parse(body) as BatchWebhookEvent;\n};\n",
7
+ "import ipaddr from \"ipaddr.js\";\n\nimport { BatchworkError } from \"../errors\";\nimport { isTerminalStatus } from \"../job\";\nimport { getAdapter } from \"../providers\";\nimport type {\n BatchProvider,\n BatchSnapshot,\n BatchStatus,\n HttpHeaders,\n ProviderCredentials,\n} from \"../types\";\nimport { asRecord, asString, parseJson } from \"../util\";\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\nconst isCredentialFunction = (\n credentials: CredentialResolver\n): credentials is (provider: BatchProvider) => ProviderCredentials =>\n typeof credentials === \"function\";\n\n/** Resolve the credentials to poll a provider with; env vars when omitted. */\nexport const resolveProviderCredentials = (\n credentials: CredentialResolver | undefined,\n provider: BatchProvider\n): ProviderCredentials => {\n if (credentials === undefined) {\n return {};\n }\n return isCredentialFunction(credentials)\n ? credentials(provider)\n : credentials;\n};\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 with the thrown value when processing a single batch fails during\n * `tick`. When provided, the tick reports the error and continues to the\n * next batch; when omitted, the error propagates out of `tick`.\n */\n onError?: (record: TrackedBatch, cause: 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\n/** Whether a hostname points at localhost or a private network. */\nconst isPrivateHost = (host: string): boolean => {\n if (\n host === \"localhost\" ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\")\n ) {\n return true;\n }\n // A URL hostname wraps IPv6 literals in brackets; ipaddr.js expects them bare.\n const literal = host.replace(/^\\[/u, \"\").replace(/\\]$/u, \"\");\n if (!ipaddr.isValid(literal)) {\n // A DNS name; only literal addresses are classified here.\n return false;\n }\n // `process` unwraps IPv4-mapped IPv6 (`::ffff:127.0.0.1`) to IPv4 first.\n // Anything but plain public unicast (loopback, private, link-local, CGNAT,\n // multicast, reserved, teredo/6to4 with embedded addresses, …) is refused.\n return ipaddr.process(literal).range() !== \"unicast\";\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 if (isPrivateHost(url.hostname.toLowerCase())) {\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: HttpHeaders = { \"content-type\": \"application/json\" };\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 resolveProviderCredentials(options.credentials, provider);\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, react-doctor/async-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 = asRecord(parseJson(verified.body));\n if (!asString(payload.type)?.startsWith(\"batch.\")) {\n return new Response(\"ignored\", { status: 202 });\n }\n const id = asString(asRecord(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": "A205BBC0AF616CBE64756E2164756E21",
12
+ "names": []
13
+ }