@hoox-sh/hoox-shared 1.3.0 → 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.
- package/dist/agent-config-fields.js +2 -0
- package/dist/d1/index.js +4 -2
- package/dist/d1/repository.js +4 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -2
- package/dist/health.js +4 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +209 -80
- package/dist/kill-switch.js +2 -0
- package/dist/kvKeys.d.ts +3 -0
- package/dist/kvKeys.d.ts.map +1 -1
- package/dist/kvKeys.js +3 -0
- package/dist/kvUtils.d.ts +34 -1
- package/dist/kvUtils.d.ts.map +1 -1
- package/dist/kvUtils.js +56 -3
- package/dist/middleware/auth.d.ts +6 -3
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +19 -5
- package/dist/middleware/index.d.ts +3 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +88 -8
- package/dist/middleware/logger.js +4 -2
- package/dist/middleware/validate.d.ts +16 -0
- package/dist/middleware/validate.d.ts.map +1 -1
- package/dist/middleware/validate.js +37 -1
- package/dist/middleware/wait-until.d.ts +32 -0
- package/dist/middleware/wait-until.d.ts.map +1 -0
- package/dist/middleware/wait-until.js +46 -0
- package/dist/queue-handler.d.ts +6 -0
- package/dist/queue-handler.d.ts.map +1 -1
- package/dist/queue-handler.js +51 -20
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +12 -2
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +58 -13
- package/dist/schemas/registry.d.ts +13 -0
- package/dist/schemas/registry.d.ts.map +1 -1
- package/dist/schemas/registry.js +55 -12
- package/dist/schemas/types.d.ts +5 -0
- package/dist/schemas/types.d.ts.map +1 -1
- package/dist/schemas/validators.d.ts.map +1 -1
- package/dist/schemas/validators.js +3 -1
- package/dist/service-bindings.js +18 -5
- package/dist/stores/service-store.js +1 -1
- package/dist/types.d.ts +22 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -4
- package/dist/wizard/engine.d.ts.map +1 -1
- package/dist/wizard/engine.js +18 -35
- package/dist/wizard/index.js +18 -35
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +6 -32
- package/package.json +1 -1
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
|
package/dist/kvUtils.d.ts.map
CHANGED
|
@@ -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
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
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"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -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 =
|
|
27
|
-
const bv =
|
|
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 || !
|
|
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
|
-
|
|
104
|
+
matched = true;
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
|
-
return
|
|
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,
|
|
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"}
|
package/dist/middleware/index.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
|
|
25
|
-
|
|
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 =
|
|
153
|
-
const bv =
|
|
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 || !
|
|
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
|
-
|
|
232
|
+
matched = true;
|
|
218
233
|
}
|
|
219
234
|
}
|
|
220
|
-
return
|
|
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
|
|
25
|
-
|
|
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
|
-
|
|
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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
|
|
18
|
+
// src/middleware/wait-until.ts
|
|
19
|
+
function safeWaitUntil(ctx, promise, onError) {
|
|
20
|
+
ctx.waitUntil(promise.catch((err) => {
|
|
21
|
+
onError?.(err);
|
|
22
|
+
console.error(JSON.stringify({
|
|
23
|
+
message: "waitUntil background task failed",
|
|
24
|
+
error: err instanceof Error ? err.message : String(err)
|
|
25
|
+
}));
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
function waitUntilAll(ctx, promises, onError) {
|
|
29
|
+
if (promises.length === 0)
|
|
30
|
+
return;
|
|
31
|
+
safeWaitUntil(ctx, Promise.allSettled(promises).then((results) => {
|
|
32
|
+
for (const result of results) {
|
|
33
|
+
if (result.status === "rejected") {
|
|
34
|
+
onError?.(result.reason);
|
|
35
|
+
console.error(JSON.stringify({
|
|
36
|
+
message: "waitUntilAll background task failed",
|
|
37
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}), onError);
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
waitUntilAll,
|
|
45
|
+
safeWaitUntil
|
|
46
|
+
};
|
package/dist/queue-handler.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface QueueHandlerOptions<T> {
|
|
|
19
19
|
info(msg: string, data?: unknown): void;
|
|
20
20
|
error(msg: string, data?: unknown): void;
|
|
21
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Max number of messages to process concurrently within a batch.
|
|
24
|
+
* Defaults to 1 (serial) for back-compat. Values < 1 are treated as 1.
|
|
25
|
+
*/
|
|
26
|
+
concurrency?: number;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* Creates a reusable queue handler with retry + exponential backoff logic.
|
|
@@ -28,6 +33,7 @@ export interface QueueHandlerOptions<T> {
|
|
|
28
33
|
* const handler = createQueueHandler({
|
|
29
34
|
* maxRetries: 5,
|
|
30
35
|
* backoffDelays: [0, 30, 60, 300, 900],
|
|
36
|
+
* concurrency: 4, // optional; default 1 (serial)
|
|
31
37
|
* onMessage: async (msg) => {
|
|
32
38
|
* await executeTask(msg);
|
|
33
39
|
* },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-handler.d.ts","sourceRoot":"","sources":["../src/queue-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"queue-handler.d.ts","sourceRoot":"","sources":["../src/queue-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,2BAA2B,CAAC;AAEvE,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,+CAA+C;IAC/C,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7E,sDAAsD;IACtD,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,CAAC,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,KACjB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,iEAAiE;IACjE,KAAK,CAAC,EAAE,CACN,OAAO,EAAE,CAAC,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,KACV,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,oCAAoC;IACpC,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;QACxC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;KAC1C,CAAC;IACF;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAkCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,IAqDrD,OAAO,YAAY,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAGrD"}
|