@hoox-sh/hoox-shared 1.2.1 → 1.4.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 (67) hide show
  1. package/dist/agent-config-fields.js +2 -0
  2. package/dist/d1/index.js +4 -2
  3. package/dist/d1/repository.js +4 -2
  4. package/dist/errors.d.ts.map +1 -1
  5. package/dist/errors.js +4 -2
  6. package/dist/health.js +4 -2
  7. package/dist/index.d.ts +5 -5
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +432 -139
  10. package/dist/kill-switch.js +2 -0
  11. package/dist/kvKeys.d.ts +3 -0
  12. package/dist/kvKeys.d.ts.map +1 -1
  13. package/dist/kvKeys.js +3 -0
  14. package/dist/kvUtils.d.ts +34 -1
  15. package/dist/kvUtils.d.ts.map +1 -1
  16. package/dist/kvUtils.js +56 -3
  17. package/dist/middleware/auth.d.ts +6 -3
  18. package/dist/middleware/auth.d.ts.map +1 -1
  19. package/dist/middleware/auth.js +19 -5
  20. package/dist/middleware/index.d.ts +3 -2
  21. package/dist/middleware/index.d.ts.map +1 -1
  22. package/dist/middleware/index.js +88 -8
  23. package/dist/middleware/logger.js +4 -2
  24. package/dist/middleware/validate.d.ts +16 -0
  25. package/dist/middleware/validate.d.ts.map +1 -1
  26. package/dist/middleware/validate.js +37 -1
  27. package/dist/middleware/wait-until.d.ts +32 -0
  28. package/dist/middleware/wait-until.d.ts.map +1 -0
  29. package/dist/middleware/wait-until.js +46 -0
  30. package/dist/path-utils.d.ts +35 -4
  31. package/dist/path-utils.d.ts.map +1 -1
  32. package/dist/path-utils.js +72 -5
  33. package/dist/queue-handler.d.ts +6 -0
  34. package/dist/queue-handler.d.ts.map +1 -1
  35. package/dist/queue-handler.js +51 -20
  36. package/dist/router.d.ts.map +1 -1
  37. package/dist/router.js +12 -2
  38. package/dist/schemas/index.d.ts +2 -1
  39. package/dist/schemas/index.d.ts.map +1 -1
  40. package/dist/schemas/index.js +58 -13
  41. package/dist/schemas/registry.d.ts +13 -0
  42. package/dist/schemas/registry.d.ts.map +1 -1
  43. package/dist/schemas/registry.js +55 -12
  44. package/dist/schemas/types.d.ts +5 -0
  45. package/dist/schemas/types.d.ts.map +1 -1
  46. package/dist/schemas/validators.d.ts.map +1 -1
  47. package/dist/schemas/validators.js +3 -1
  48. package/dist/service-bindings.js +18 -5
  49. package/dist/session.js +69 -5
  50. package/dist/stores/config-store.d.ts +2 -0
  51. package/dist/stores/config-store.d.ts.map +1 -1
  52. package/dist/stores/config-store.js +267 -16
  53. package/dist/stores/service-store.d.ts +12 -0
  54. package/dist/stores/service-store.d.ts.map +1 -1
  55. package/dist/stores/service-store.js +101 -40
  56. package/dist/stores/ui-store.d.ts +14 -0
  57. package/dist/stores/ui-store.d.ts.map +1 -1
  58. package/dist/stores/ui-store.js +8 -1
  59. package/dist/types.d.ts +22 -4
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/types.js +8 -4
  62. package/dist/wizard/engine.d.ts.map +1 -1
  63. package/dist/wizard/engine.js +18 -35
  64. package/dist/wizard/index.js +18 -35
  65. package/dist/wizard/presets.d.ts.map +1 -1
  66. package/dist/wizard/presets.js +6 -32
  67. package/package.json +1 -1
@@ -26,6 +26,7 @@ var KV_BOT_ENABLED = "bot:enabled";
26
26
  var KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id";
27
27
  var KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution";
28
28
  var KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error";
29
+ var KV_TELEGRAM_ALLOWED_CHAT_IDS = "telegram:allowed_chat_ids";
29
30
  var KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled";
30
31
  var KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips";
31
32
  var KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode";
@@ -56,6 +57,7 @@ var KVKeys = {
56
57
  KV_BOT_DEFAULT_CHAT_ID,
57
58
  KV_BOT_NOTIFY_ON_EXECUTION,
58
59
  KV_BOT_NOTIFY_ON_ERROR,
60
+ KV_TELEGRAM_ALLOWED_CHAT_IDS,
59
61
  KV_WEBHOOK_IP_CHECK_ENABLED,
60
62
  KV_WEBHOOK_ALLOWED_IPS,
61
63
  KV_WEBHOOK_QUEUE_MODE,
package/dist/kvKeys.d.ts CHANGED
@@ -25,6 +25,8 @@ export declare const KV_BOT_ENABLED = "bot:enabled";
25
25
  export declare const KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id";
26
26
  export declare const KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution";
27
27
  export declare const KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error";
28
+ /** JSON array of string|number — gateway notify chatId allowlist (fail-closed). */
29
+ export declare const KV_TELEGRAM_ALLOWED_CHAT_IDS = "telegram:allowed_chat_ids";
28
30
  export declare const KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled";
29
31
  export declare const KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips";
30
32
  export declare const KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode";
@@ -55,6 +57,7 @@ export declare const KVKeys: {
55
57
  KV_BOT_DEFAULT_CHAT_ID: string;
56
58
  KV_BOT_NOTIFY_ON_EXECUTION: string;
57
59
  KV_BOT_NOTIFY_ON_ERROR: string;
60
+ KV_TELEGRAM_ALLOWED_CHAT_IDS: string;
58
61
  KV_WEBHOOK_IP_CHECK_ENABLED: string;
59
62
  KV_WEBHOOK_ALLOWED_IPS: string;
60
63
  KV_WEBHOOK_QUEUE_MODE: string;
@@ -1 +1 @@
1
- {"version":3,"file":"kvKeys.d.ts","sourceRoot":"","sources":["../src/kvKeys.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;GAYG;AAGH,eAAO,MAAM,yBAAyB,2BAA2B,CAAC;AAClE,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AACpE,eAAO,MAAM,mCAAmC,qCACZ,CAAC;AACrC,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAC5E,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AACxD,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAGhD,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAC5C,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAC5D,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AACpE,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAG5D,eAAO,MAAM,2BAA2B,yCACA,CAAC;AACzC,eAAO,MAAM,sBAAsB,oCAAoC,CAAC;AACxE,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAG1D,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAClD,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,uBAAuB,yBAAyB,CAAC;AAC9D,eAAO,MAAM,4BAA4B,8BAA8B,CAAC;AAGxE,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAC9C,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAC5D,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAGtD,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAG5E,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAC9C,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AAGpE,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AACpD,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAKpD,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BlB,CAAC"}
1
+ {"version":3,"file":"kvKeys.d.ts","sourceRoot":"","sources":["../src/kvKeys.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;GAYG;AAGH,eAAO,MAAM,yBAAyB,2BAA2B,CAAC;AAClE,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AACpE,eAAO,MAAM,mCAAmC,qCACZ,CAAC;AACrC,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAC5E,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AACxD,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAGhD,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAC5C,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAC5D,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AACpE,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAC5D,mFAAmF;AACnF,eAAO,MAAM,4BAA4B,8BAA8B,CAAC;AAGxE,eAAO,MAAM,2BAA2B,yCACA,CAAC;AACzC,eAAO,MAAM,sBAAsB,oCAAoC,CAAC;AACxE,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAG1D,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAClD,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,uBAAuB,yBAAyB,CAAC;AAC9D,eAAO,MAAM,4BAA4B,8BAA8B,CAAC;AAGxE,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAC9C,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAC5D,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAGtD,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAG5E,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAC9C,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AAGpE,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AACpD,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAKpD,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlB,CAAC"}
package/dist/kvKeys.js CHANGED
@@ -26,6 +26,7 @@ var KV_BOT_ENABLED = "bot:enabled";
26
26
  var KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id";
27
27
  var KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution";
28
28
  var KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error";
29
+ var KV_TELEGRAM_ALLOWED_CHAT_IDS = "telegram:allowed_chat_ids";
29
30
  var KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled";
30
31
  var KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips";
31
32
  var KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode";
@@ -56,6 +57,7 @@ var KVKeys = {
56
57
  KV_BOT_DEFAULT_CHAT_ID,
57
58
  KV_BOT_NOTIFY_ON_EXECUTION,
58
59
  KV_BOT_NOTIFY_ON_ERROR,
60
+ KV_TELEGRAM_ALLOWED_CHAT_IDS,
59
61
  KV_WEBHOOK_IP_CHECK_ENABLED,
60
62
  KV_WEBHOOK_ALLOWED_IPS,
61
63
  KV_WEBHOOK_QUEUE_MODE,
@@ -89,6 +91,7 @@ export {
89
91
  KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT,
90
92
  KV_TRADE_KILL_SWITCH,
91
93
  KV_TRADE_DEFAULT_LEVERAGE,
94
+ KV_TELEGRAM_ALLOWED_CHAT_IDS,
92
95
  KV_HOUSEKEEPING_LAST_CHECK,
93
96
  KV_HEALTH_CHECK,
94
97
  KV_GLOBAL_KILL_SWITCH,
package/dist/kvUtils.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /**
6
6
  * Utility functions for KV operations
7
- * Shared across workers that need KV timestamp logging
7
+ * Shared across workers that need KV timestamp logging and parallel I/O
8
8
  */
9
9
  import type { KVNamespace } from "@cloudflare/workers-types";
10
10
  /**
@@ -21,6 +21,39 @@ export interface EnvWithKV {
21
21
  * @returns Promise that resolves when the operation completes
22
22
  */
23
23
  export declare function logKvTimestamp(env: EnvWithKV, prefix?: string): Promise<void>;
24
+ /**
25
+ * Max keys per Workers KV bulk read (platform limit).
26
+ * @see https://developers.cloudflare.com/kv/api/read-key-value-pairs/
27
+ */
28
+ export declare const KV_BULK_GET_MAX_KEYS = 100;
29
+ /**
30
+ * Parallel / bulk KV gets — prefer over sequential `await kv.get` loops.
31
+ * Order of returned values matches `keys`.
32
+ *
33
+ * Uses native `kv.get(string[])` bulk reads (up to 100 keys per call) when
34
+ * available. Bulk reads count as a single operation against the Workers
35
+ * subrequest limit and avoid simultaneous-connection pressure.
36
+ * Falls back to `Promise.all` of individual gets for mocks / older runtimes.
37
+ */
38
+ export declare function kvGetMany(kv: KVNamespace, keys: readonly string[]): Promise<Array<string | null>>;
39
+ /**
40
+ * Parallel KV gets as a record keyed by the original key names.
41
+ */
42
+ export declare function kvGetManyAsRecord(kv: KVNamespace, keys: readonly string[]): Promise<Record<string, string | null>>;
43
+ export interface KvPutEntry {
44
+ key: string;
45
+ value: string;
46
+ options?: {
47
+ expirationTtl?: number;
48
+ expiration?: number;
49
+ metadata?: Record<string, unknown>;
50
+ };
51
+ }
52
+ /**
53
+ * Parallel KV puts — prefer over sequential `await kv.put` loops
54
+ * when writes are independent (no read-modify-write races).
55
+ */
56
+ export declare function kvPutMany(kv: KVNamespace, entries: readonly KvPutEntry[]): Promise<void>;
24
57
  /**
25
58
  * Helper function to convert Headers to a plain object
26
59
  * @param headers - The Headers object to convert
@@ -1 +1 @@
1
- {"version":3,"file":"kvUtils.d.ts","sourceRoot":"","sources":["../src/kvUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,WAAW,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,SAAS,EACd,MAAM,GAAE,MAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAClC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBxB"}
1
+ {"version":3,"file":"kvUtils.d.ts","sourceRoot":"","sources":["../src/kvUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,WAAW,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,SAAS,EACd,MAAM,GAAE,MAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAyBxC;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,WAAW,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CA0B/B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,WAAW,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAOxC;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE;QACR,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;CACH;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,WAAW,EACf,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAClC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBxB"}
package/dist/kvUtils.js CHANGED
@@ -21,8 +21,10 @@ function toError(err, fallback = "Unknown error") {
21
21
  return err.message;
22
22
  if (typeof err === "string")
23
23
  return err;
24
- if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
25
- return err.message;
24
+ if (err && typeof err === "object" && "message" in err) {
25
+ const message = err.message;
26
+ if (typeof message === "string")
27
+ return message;
26
28
  }
27
29
  if (err === null || err === undefined)
28
30
  return fallback;
@@ -151,6 +153,53 @@ async function logKvTimestamp(env, prefix = "timestamp") {
151
153
  console.error(`Failed to log timestamp to KV: ${toError(error, "Unknown error")}`);
152
154
  }
153
155
  }
156
+ var KV_BULK_GET_MAX_KEYS = 100;
157
+ async function tryBulkGetChunk(kv, chunk) {
158
+ try {
159
+ const result = await kv.get([...chunk]);
160
+ if (!(result instanceof Map)) {
161
+ return null;
162
+ }
163
+ return chunk.map((key) => result.get(key) ?? null);
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ async function kvGetMany(kv, keys) {
169
+ if (keys.length === 0)
170
+ return [];
171
+ const out = new Array(keys.length);
172
+ let offset = 0;
173
+ while (offset < keys.length) {
174
+ const chunk = keys.slice(offset, offset + KV_BULK_GET_MAX_KEYS);
175
+ const bulk = await tryBulkGetChunk(kv, chunk);
176
+ if (bulk === null) {
177
+ const rest = await Promise.all(keys.slice(offset).map((key) => kv.get(key)));
178
+ for (let i = 0;i < rest.length; i++) {
179
+ out[offset + i] = rest[i] ?? null;
180
+ }
181
+ return out;
182
+ }
183
+ for (let i = 0;i < bulk.length; i++) {
184
+ out[offset + i] = bulk[i] ?? null;
185
+ }
186
+ offset += chunk.length;
187
+ }
188
+ return out;
189
+ }
190
+ async function kvGetManyAsRecord(kv, keys) {
191
+ const values = await kvGetMany(kv, keys);
192
+ const out = {};
193
+ for (let i = 0;i < keys.length; i++) {
194
+ out[keys[i]] = values[i] ?? null;
195
+ }
196
+ return out;
197
+ }
198
+ async function kvPutMany(kv, entries) {
199
+ if (entries.length === 0)
200
+ return;
201
+ await Promise.all(entries.map((entry) => kv.put(entry.key, entry.value, entry.options)));
202
+ }
154
203
  function headersToObject(headers) {
155
204
  const result = {};
156
205
  if (!headers)
@@ -166,5 +215,9 @@ function headersToObject(headers) {
166
215
  }
167
216
  export {
168
217
  logKvTimestamp,
169
- headersToObject
218
+ kvPutMany,
219
+ kvGetManyAsRecord,
220
+ kvGetMany,
221
+ headersToObject,
222
+ KV_BULK_GET_MAX_KEYS
170
223
  };
@@ -11,14 +11,17 @@ import type { MiddlewareHandler } from "../types/router";
11
11
  /**
12
12
  * Constant-time string comparison to prevent timing attacks.
13
13
  *
14
- * Length is NOT short-circuited: both strings are hashed to fixed-size
15
- * digests via a simple XOR-fold into equal-length buffers so that
16
- * unequal lengths do not leak via early return (length oracle).
14
+ * Length is NOT short-circuited: both strings are padded to equal length
15
+ * and XOR-folded so unequal lengths do not leak via early return
16
+ * (length oracle). Prefer {@link timingSafeEqualAsync} when Web Crypto
17
+ * is available — it hashes both sides to fixed-size digests first
18
+ * (Cloudflare Workers best practice).
17
19
  *
18
20
  * Note: true crypto.subtle.timingSafeEqual needs equal-length ArrayBuffers;
19
21
  * we pad both encodings to the same max length before XOR comparison.
20
22
  */
21
23
  export declare function timingSafeEqual(a: string, b: string): boolean;
24
+ export declare function timingSafeEqualAsync(a: string, b: string): Promise<boolean>;
22
25
  /**
23
26
  * Environment bindings accepted for operator Bearer auth.
24
27
  * Prefer OPERATOR_API_KEY; INTERNAL_API_KEY remains a legacy alias.
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAe7D;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,GAAG,GAAG,GACzB,MAAM,GAAG,SAAS,CAUpB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,GAAG,eAAe,GACzB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAE1B;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,GAAG,GAAG,GACzB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqB1B;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAUjF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAS7D,6FAA6F;AAC7F,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,mBAAmB,GAC5B,MAAM,EAAE,CASV;AA2BD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,mBAA4C,GACpD,QAAQ,GAAG,IAAI,CAcjB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,mBAA4C,GACpD,iBAAiB,CAAC,eAAe,CAAC,CAoBpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,mBAA4C,GACpD;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAezC"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAe7D;AAmBD,wBAAsB,oBAAoB,CACxC,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,OAAO,CAAC,OAAO,CAAC,CAelB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,GAAG,GAAG,GACzB,MAAM,GAAG,SAAS,CAUpB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,GAAG,eAAe,GACzB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAE1B;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,GAAG,GAAG,GACzB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAyB1B;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,iBAAiB,CAAC,eAAe,CAAC,CAUjF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAS7D,6FAA6F;AAC7F,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,mBAAmB,GAC5B,MAAM,EAAE,CASV;AAiCD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,mBAA4C,GACpD,QAAQ,GAAG,IAAI,CAcjB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,mBAA4C,GACpD,iBAAiB,CAAC,eAAe,CAAC,CAoBpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,mBAA4C,GACpD;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAezC"}
@@ -23,12 +23,24 @@ function timingSafeEqual(a, b) {
23
23
  const len = Math.max(aBuf.length, bBuf.length);
24
24
  let result = aBuf.length === bBuf.length ? 0 : 1;
25
25
  for (let i = 0;i < len; i++) {
26
- const av = i < aBuf.length ? aBuf[i] : 0;
27
- const bv = i < bBuf.length ? bBuf[i] : 0;
26
+ const av = aBuf[i] ?? 0;
27
+ const bv = bBuf[i] ?? 0;
28
28
  result |= av ^ bv;
29
29
  }
30
30
  return result === 0;
31
31
  }
32
+ async function timingSafeEqualAsync(a, b) {
33
+ const subtle = globalThis.crypto?.subtle;
34
+ if (!subtle || typeof subtle.digest !== "function" || typeof subtle.timingSafeEqual !== "function") {
35
+ return timingSafeEqual(a, b);
36
+ }
37
+ const encoder = new TextEncoder;
38
+ const [providedHash, expectedHash] = await Promise.all([
39
+ subtle.digest("SHA-256", encoder.encode(a)),
40
+ subtle.digest("SHA-256", encoder.encode(b))
41
+ ]);
42
+ return subtle.timingSafeEqual(providedHash, expectedHash);
43
+ }
32
44
  function resolveOperatorApiKey(env) {
33
45
  const preferred = env.OPERATOR_API_KEY;
34
46
  if (typeof preferred === "string" && preferred.length > 0) {
@@ -53,7 +65,7 @@ async function requireOperatorAuth(request, env) {
53
65
  }
54
66
  const authHeader = request.headers.get("Authorization");
55
67
  const expectedHeader = `Bearer ${apiKey}`;
56
- if (!authHeader || !timingSafeEqual(authHeader, expectedHeader)) {
68
+ if (!authHeader || !await timingSafeEqualAsync(authHeader, expectedHeader)) {
57
69
  return new Response(JSON.stringify({ error: "Unauthorized" }), {
58
70
  status: 401,
59
71
  headers: { "Content-Type": "application/json" }
@@ -86,12 +98,13 @@ function collectInternalAuthKeys(env, keyNames) {
86
98
  return keys;
87
99
  }
88
100
  function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
101
+ let matched = false;
89
102
  for (const expected of expectedKeys) {
90
103
  if (timingSafeEqual(providedKey, expected)) {
91
- return true;
104
+ matched = true;
92
105
  }
93
106
  }
94
- return false;
107
+ return matched;
95
108
  }
96
109
  function internalAuthNotConfiguredResponse(keyNames) {
97
110
  const label = normalizeKeyNames(keyNames).join(" | ");
@@ -139,6 +152,7 @@ function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
139
152
  return { authorized: true };
140
153
  }
141
154
  export {
155
+ timingSafeEqualAsync,
142
156
  timingSafeEqual,
143
157
  resolveOperatorApiKey,
144
158
  requireOperatorAuth,
@@ -6,9 +6,10 @@
6
6
  * Barrel exports for shared middleware
7
7
  */
8
8
  export { createLogger, withRequestLog, type Logger, type LogContext, } from "./logger";
9
- export { requireAuth, requireOperatorAuth, createOperatorAuthMiddleware, resolveOperatorApiKey, requireInternalAuth, createInternalAuthMiddleware, checkInternalAuth, timingSafeEqual, collectInternalAuthKeys, type InternalAuthEnv, type InternalAuthKeyName, type OperatorAuthEnv, } from "./auth";
9
+ export { requireAuth, requireOperatorAuth, createOperatorAuthMiddleware, resolveOperatorApiKey, requireInternalAuth, createInternalAuthMiddleware, checkInternalAuth, timingSafeEqual, timingSafeEqualAsync, collectInternalAuthKeys, type InternalAuthEnv, type InternalAuthKeyName, type OperatorAuthEnv, } from "./auth";
10
10
  export { createRateLimiter, type RateLimiter, type RateLimitConfig, } from "./rate-limit";
11
- export { validateJson, validateJsonLegacy, requireField, optionalField, } from "./validate";
11
+ export { validateJson, validateJsonLegacy, parseJsonBody, requireField, optionalField, DEFAULT_MAX_JSON_BODY_BYTES, type ParseJsonBodyOptions, } from "./validate";
12
+ export { safeWaitUntil, waitUntilAll, type WaitUntilHost } from "./wait-until";
12
13
  export { corsHeaders, resolveCorsOptions, type CorsEnv, publicCorsHeaders, internalCorsHeaders, handleCorsPreflightRequest, type CorsOptions, } from "./cors";
13
14
  export { secureHeaders, wrapWithSecurityHeaders, type SecurityHeadersOptions, SECURITY_HEADERS_DEFAULTS, } from "./security-headers";
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,KAAK,MAAM,EACX,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC5B,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,KAAK,OAAO,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,0BAA0B,EAC1B,KAAK,WAAW,GACjB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,KAAK,sBAAsB,EAC3B,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,KAAK,MAAM,EACX,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC5B,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,2BAA2B,EAC3B,KAAK,oBAAoB,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,KAAK,OAAO,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,0BAA0B,EAC1B,KAAK,WAAW,GACjB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,KAAK,sBAAsB,EAC3B,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC"}
@@ -21,8 +21,10 @@ function toError(err, fallback = "Unknown error") {
21
21
  return err.message;
22
22
  if (typeof err === "string")
23
23
  return err;
24
- if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
25
- return err.message;
24
+ if (err && typeof err === "object" && "message" in err) {
25
+ const message = err.message;
26
+ if (typeof message === "string")
27
+ return message;
26
28
  }
27
29
  if (err === null || err === undefined)
28
30
  return fallback;
@@ -149,12 +151,24 @@ function timingSafeEqual(a, b) {
149
151
  const len = Math.max(aBuf.length, bBuf.length);
150
152
  let result = aBuf.length === bBuf.length ? 0 : 1;
151
153
  for (let i = 0;i < len; i++) {
152
- const av = i < aBuf.length ? aBuf[i] : 0;
153
- const bv = i < bBuf.length ? bBuf[i] : 0;
154
+ const av = aBuf[i] ?? 0;
155
+ const bv = bBuf[i] ?? 0;
154
156
  result |= av ^ bv;
155
157
  }
156
158
  return result === 0;
157
159
  }
160
+ async function timingSafeEqualAsync(a, b) {
161
+ const subtle = globalThis.crypto?.subtle;
162
+ if (!subtle || typeof subtle.digest !== "function" || typeof subtle.timingSafeEqual !== "function") {
163
+ return timingSafeEqual(a, b);
164
+ }
165
+ const encoder = new TextEncoder;
166
+ const [providedHash, expectedHash] = await Promise.all([
167
+ subtle.digest("SHA-256", encoder.encode(a)),
168
+ subtle.digest("SHA-256", encoder.encode(b))
169
+ ]);
170
+ return subtle.timingSafeEqual(providedHash, expectedHash);
171
+ }
158
172
  function resolveOperatorApiKey(env) {
159
173
  const preferred = env.OPERATOR_API_KEY;
160
174
  if (typeof preferred === "string" && preferred.length > 0) {
@@ -179,7 +193,7 @@ async function requireOperatorAuth(request, env) {
179
193
  }
180
194
  const authHeader = request.headers.get("Authorization");
181
195
  const expectedHeader = `Bearer ${apiKey}`;
182
- if (!authHeader || !timingSafeEqual(authHeader, expectedHeader)) {
196
+ if (!authHeader || !await timingSafeEqualAsync(authHeader, expectedHeader)) {
183
197
  return new Response(JSON.stringify({ error: "Unauthorized" }), {
184
198
  status: 401,
185
199
  headers: { "Content-Type": "application/json" }
@@ -212,12 +226,13 @@ function collectInternalAuthKeys(env, keyNames) {
212
226
  return keys;
213
227
  }
214
228
  function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
229
+ let matched = false;
215
230
  for (const expected of expectedKeys) {
216
231
  if (timingSafeEqual(providedKey, expected)) {
217
- return true;
232
+ matched = true;
218
233
  }
219
234
  }
220
- return false;
235
+ return matched;
221
236
  }
222
237
  function internalAuthNotConfiguredResponse(keyNames) {
223
238
  const label = normalizeKeyNames(keyNames).join(" | ");
@@ -538,6 +553,7 @@ function createRateLimiter(kv, config) {
538
553
  }
539
554
 
540
555
  // src/middleware/validate.ts
556
+ var DEFAULT_MAX_JSON_BODY_BYTES = 1048576;
541
557
  function validateJson(schema, data) {
542
558
  const result = schema.safeParse(data);
543
559
  if (!result.success) {
@@ -548,6 +564,39 @@ function validateJson(schema, data) {
548
564
  }
549
565
  return { ok: true, value: result.data };
550
566
  }
567
+ async function parseJsonBody(request, schema, options = {}) {
568
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_JSON_BODY_BYTES;
569
+ const contentLength = request.headers.get("content-length");
570
+ if (contentLength !== null) {
571
+ const declared = Number(contentLength);
572
+ if (Number.isFinite(declared) && declared > maxBytes) {
573
+ return {
574
+ ok: false,
575
+ error: `Request body too large (max ${maxBytes} bytes)`
576
+ };
577
+ }
578
+ }
579
+ let text;
580
+ try {
581
+ text = await request.text();
582
+ } catch {
583
+ return { ok: false, error: "Failed to read request body" };
584
+ }
585
+ const byteLength = new TextEncoder().encode(text).byteLength;
586
+ if (byteLength > maxBytes) {
587
+ return {
588
+ ok: false,
589
+ error: `Request body too large (max ${maxBytes} bytes)`
590
+ };
591
+ }
592
+ let data;
593
+ try {
594
+ data = text.length === 0 ? null : JSON.parse(text);
595
+ } catch {
596
+ return { ok: false, error: "Invalid JSON in request body" };
597
+ }
598
+ return validateJson(schema, data);
599
+ }
551
600
  async function validateJsonLegacy(request) {
552
601
  try {
553
602
  const body = await request.json();
@@ -571,6 +620,32 @@ function optionalField(body, field, defaultValue) {
571
620
  return body[field];
572
621
  }
573
622
 
623
+ // src/middleware/wait-until.ts
624
+ function safeWaitUntil(ctx, promise, onError) {
625
+ ctx.waitUntil(promise.catch((err) => {
626
+ onError?.(err);
627
+ console.error(JSON.stringify({
628
+ message: "waitUntil background task failed",
629
+ error: err instanceof Error ? err.message : String(err)
630
+ }));
631
+ }));
632
+ }
633
+ function waitUntilAll(ctx, promises, onError) {
634
+ if (promises.length === 0)
635
+ return;
636
+ safeWaitUntil(ctx, Promise.allSettled(promises).then((results) => {
637
+ for (const result of results) {
638
+ if (result.status === "rejected") {
639
+ onError?.(result.reason);
640
+ console.error(JSON.stringify({
641
+ message: "waitUntilAll background task failed",
642
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
643
+ }));
644
+ }
645
+ }
646
+ }), onError);
647
+ }
648
+
574
649
  // src/middleware/security-headers.ts
575
650
  var SECURITY_HEADERS_DEFAULTS = {
576
651
  xContentTypeOptions: "nosniff",
@@ -610,10 +685,13 @@ function wrapWithSecurityHeaders(response, options) {
610
685
  export {
611
686
  wrapWithSecurityHeaders,
612
687
  withRequestLog,
688
+ waitUntilAll,
613
689
  validateJsonLegacy,
614
690
  validateJson,
691
+ timingSafeEqualAsync,
615
692
  timingSafeEqual,
616
693
  secureHeaders,
694
+ safeWaitUntil,
617
695
  resolveOperatorApiKey,
618
696
  resolveCorsOptions,
619
697
  requireOperatorAuth,
@@ -621,6 +699,7 @@ export {
621
699
  requireField,
622
700
  requireAuth,
623
701
  publicCorsHeaders,
702
+ parseJsonBody,
624
703
  optionalField,
625
704
  internalCorsHeaders,
626
705
  handleCorsPreflightRequest,
@@ -631,5 +710,6 @@ export {
631
710
  corsHeaders,
632
711
  collectInternalAuthKeys,
633
712
  checkInternalAuth,
634
- SECURITY_HEADERS_DEFAULTS
713
+ SECURITY_HEADERS_DEFAULTS,
714
+ DEFAULT_MAX_JSON_BODY_BYTES
635
715
  };
@@ -21,8 +21,10 @@ function toError(err, fallback = "Unknown error") {
21
21
  return err.message;
22
22
  if (typeof err === "string")
23
23
  return err;
24
- if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
25
- return err.message;
24
+ if (err && typeof err === "object" && "message" in err) {
25
+ const message = err.message;
26
+ if (typeof message === "string")
27
+ return message;
26
28
  }
27
29
  if (err === null || err === undefined)
28
30
  return fallback;
@@ -10,11 +10,27 @@
10
10
  */
11
11
  import { z } from "zod";
12
12
  import type { Result } from "../types";
13
+ /** Default body size cap for JSON request parsing (1 MiB). */
14
+ export declare const DEFAULT_MAX_JSON_BODY_BYTES = 1048576;
15
+ export interface ParseJsonBodyOptions {
16
+ /** Reject bodies larger than this many bytes (Content-Length or measured). */
17
+ maxBytes?: number;
18
+ }
13
19
  /**
14
20
  * Validate unknown data against a Zod schema, returning a Result type.
15
21
  * Provides structured error messages from Zod issue paths.
16
22
  */
17
23
  export declare function validateJson<T extends z.ZodTypeAny>(schema: T, data: unknown): Result<z.infer<T>>;
24
+ /**
25
+ * Parse a Request body as JSON and validate against a Zod schema.
26
+ * Enforces an optional max body size to stay within Workers memory limits.
27
+ *
28
+ * @example
29
+ * const parsed = await parseJsonBody(request, WebhookPayloadSchema);
30
+ * if (!parsed.ok) return Errors.badRequest(parsed.error);
31
+ * const payload = parsed.value;
32
+ */
33
+ export declare function parseJsonBody<T extends z.ZodTypeAny>(request: Request, schema: T, options?: ParseJsonBodyOptions): Promise<Result<z.infer<T>>>;
18
34
  /**
19
35
  * Legacy signature: parse request body as JSON object.
20
36
  * Kept for backward compatibility with existing workers.
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/middleware/validate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACjD,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAWpB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAU1C;AAED,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,CAAC,CAAC,CAKX;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,CAAC,GACd,CAAC,CAGH"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/middleware/validate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,8DAA8D;AAC9D,eAAO,MAAM,2BAA2B,UAAY,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACjD,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAWpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACxD,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAsC7B;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAU1C;AAED,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,CAAC,CAAC,CAKX;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,CAAC,GACd,CAAC,CAGH"}
@@ -16,6 +16,7 @@ var __export = (target, all) => {
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
18
  // src/middleware/validate.ts
19
+ var DEFAULT_MAX_JSON_BODY_BYTES = 1048576;
19
20
  function validateJson(schema, data) {
20
21
  const result = schema.safeParse(data);
21
22
  if (!result.success) {
@@ -26,6 +27,39 @@ function validateJson(schema, data) {
26
27
  }
27
28
  return { ok: true, value: result.data };
28
29
  }
30
+ async function parseJsonBody(request, schema, options = {}) {
31
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_JSON_BODY_BYTES;
32
+ const contentLength = request.headers.get("content-length");
33
+ if (contentLength !== null) {
34
+ const declared = Number(contentLength);
35
+ if (Number.isFinite(declared) && declared > maxBytes) {
36
+ return {
37
+ ok: false,
38
+ error: `Request body too large (max ${maxBytes} bytes)`
39
+ };
40
+ }
41
+ }
42
+ let text;
43
+ try {
44
+ text = await request.text();
45
+ } catch {
46
+ return { ok: false, error: "Failed to read request body" };
47
+ }
48
+ const byteLength = new TextEncoder().encode(text).byteLength;
49
+ if (byteLength > maxBytes) {
50
+ return {
51
+ ok: false,
52
+ error: `Request body too large (max ${maxBytes} bytes)`
53
+ };
54
+ }
55
+ let data;
56
+ try {
57
+ data = text.length === 0 ? null : JSON.parse(text);
58
+ } catch {
59
+ return { ok: false, error: "Invalid JSON in request body" };
60
+ }
61
+ return validateJson(schema, data);
62
+ }
29
63
  async function validateJsonLegacy(request) {
30
64
  try {
31
65
  const body = await request.json();
@@ -52,5 +86,7 @@ export {
52
86
  validateJsonLegacy,
53
87
  validateJson,
54
88
  requireField,
55
- optionalField
89
+ parseJsonBody,
90
+ optionalField,
91
+ DEFAULT_MAX_JSON_BODY_BYTES
56
92
  };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Production helpers around ExecutionContext / DurableObjectState.waitUntil.
7
+ *
8
+ * Floating / rejected background promises are a common Workers footgun:
9
+ * the isolate may exit before work finishes, and rejections can go unhandled.
10
+ * Always pass promises through these helpers (or await them).
11
+ *
12
+ * Never destructure `waitUntil` off `ctx` — it loses `this` and throws
13
+ * "Illegal invocation" at runtime.
14
+ *
15
+ * Accepts any object with waitUntil so the same helper works for both
16
+ * fetch ExecutionContext and DurableObjectState (DO lifecycle).
17
+ */
18
+ /** Minimal surface shared by ExecutionContext and DurableObjectState. */
19
+ export type WaitUntilHost = {
20
+ waitUntil(promise: Promise<unknown>): void;
21
+ };
22
+ /**
23
+ * Schedule background work that must not affect the HTTP response.
24
+ * Captures rejections so they never become unhandled promise rejections.
25
+ */
26
+ export declare function safeWaitUntil(ctx: WaitUntilHost, promise: Promise<unknown>, onError?: (err: unknown) => void): void;
27
+ /**
28
+ * Fan-out multiple independent background tasks via a single waitUntil.
29
+ * Uses allSettled so one failure does not cancel siblings.
30
+ */
31
+ export declare function waitUntilAll(ctx: WaitUntilHost, promises: ReadonlyArray<Promise<unknown>>, onError?: (err: unknown) => void): void;
32
+ //# sourceMappingURL=wait-until.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wait-until.d.ts","sourceRoot":"","sources":["../../src/middleware/wait-until.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;GAYG;AAEH,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EACzB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAC/B,IAAI,CAYN;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EACzC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAC/B,IAAI,CAsBN"}