@chainfuse/helpers 2.4.2 → 3.0.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/net.mjs CHANGED
@@ -1,112 +1,255 @@
1
+ /**
2
+ * Enum representing HTTP request methods.
3
+ *
4
+ * Each member of the enum corresponds to a standard HTTP method, which can be used to specify the desired action to be performed on a given resource.
5
+ */
6
+ export var Methods;
7
+ (function (Methods) {
8
+ Methods["GET"] = "GET";
9
+ Methods["HEAD"] = "HEAD";
10
+ Methods["POST"] = "POST";
11
+ Methods["PUT"] = "PUT";
12
+ Methods["DELETE"] = "DELETE";
13
+ Methods["CONNECT"] = "CONNECT";
14
+ Methods["OPTIONS"] = "OPTIONS";
15
+ Methods["TRACE"] = "TRACE";
16
+ Methods["PATCH"] = "PATCH";
17
+ })(Methods || (Methods = {}));
1
18
  export class NetHelpers {
19
+ static cfApiLogging() {
20
+ return import('zod').then(({ z }) => z
21
+ .union([
22
+ z.object({
23
+ level: z.literal(0),
24
+ }),
25
+ z.object({
26
+ level: z.coerce.number().int().min(1).max(3),
27
+ color: z.boolean().default(true),
28
+ custom: z
29
+ .function()
30
+ .args()
31
+ .returns(z.union([z.void(), z.promise(z.void())]))
32
+ .optional(),
33
+ }),
34
+ ])
35
+ .default({
36
+ level: 0,
37
+ }));
38
+ }
2
39
  /**
3
- * Removes the `body` property from a RequestInit object to reduce verbosity when logging.
40
+ * Creates an instance of the Cloudflare API client with enhanced logging capabilities.
41
+ *
42
+ * @param apiKey - The API token used to authenticate with the Cloudflare API.
43
+ * @param logging - The logging configuration object, parsed using the `cfApiLogging` parser.
44
+ *
45
+ * @returns A promise that resolves to an instance of the Cloudflare API client.
4
46
  *
5
- * @param {RequestInit} [init={}] - The RequestInit object from which to remove the 'body' property. If not provided, an empty object will be used.
47
+ * The logging configuration supports the following properties:
48
+ * - `level`: The logging level. If set to `1`, it is internally treated as `2` to include headers for the `cf-ray` ID, but reset back to 1 for the returned logs.
49
+ * - `color`: Optional. If `true`, enables colored output using the `chalk` library.
50
+ * - `custom`: Optional. A custom logging function that receives detailed request and response information.
6
51
  *
7
- * @returns {RequestInit} The updated RequestInit object without the 'body' property.
52
+ * The function also enhances logging by:
53
+ * - Extracting and displaying the `cf-ray` ID from response headers.
54
+ * - Formatting and coloring log output for better readability.
55
+ * - Stripping redundant parts of URLs and wrapping unique IDs in brackets with color coding.
8
56
  */
9
- static initBodyTrimmer(init = {}) {
10
- if ('cf' in init)
11
- delete init.cf;
12
- delete init.body;
13
- return init;
14
- }
15
- static stripSensitiveHeaders(originalHeaders = new Headers()) {
16
- const mutableHeaders = new Headers(originalHeaders);
17
- mutableHeaders.delete('Set-Cookie');
18
- mutableHeaders.delete('Authorization');
19
- return mutableHeaders;
20
- }
21
- static cfApi(apiKey, logger = false) {
22
- return import('cloudflare').then(({ Cloudflare }) => new Cloudflare({
57
+ static cfApi(apiKey, logging) {
58
+ return Promise.all([
59
+ //
60
+ import('cloudflare'),
61
+ NetHelpers.cfApiLogging().then((parser) => parser.parseAsync(logging)),
62
+ ]).then(([{ Cloudflare }, logging]) => new Cloudflare({
23
63
  apiToken: apiKey,
24
64
  fetch: async (info, init) => {
25
- if (typeof logger === 'boolean' && logger) {
26
- logger = async (date, id, methodOrStatus, url, headers, ...rest) => {
27
- const customUrl = new URL(url);
28
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
29
- const loggingItems = ['CF Rest', date, id, methodOrStatus, `${customUrl.pathname}${customUrl.search}${customUrl.hash}`, ...rest];
30
- const customHeaders = new Headers(headers);
31
- await import('chalk')
32
- .then(({ Chalk }) => {
33
- const chalk = new Chalk({ level: 2 });
34
- // Replace with color
35
- loggingItems.splice(0, 1, chalk.rgb(245, 130, 30)('CF Rest'));
36
- // Add in with color
37
- if (customHeaders.has('cf-ray'))
38
- loggingItems.splice(3, 0, chalk.rgb(245, 130, 30)(customHeaders.get('cf-ray')));
39
- })
40
- .catch(() => {
41
- // Add in ray id
42
- if (customHeaders.has('cf-ray'))
43
- loggingItems.splice(3, 0, customHeaders.get('cf-ray'));
44
- });
45
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
46
- console.debug(...loggingItems);
47
- };
48
- }
49
- return this.loggingFetch(info, init, undefined, logger);
65
+ const loggingFetchInit = {
66
+ ...init,
67
+ logging: {
68
+ // Fake level 1 as 2 to get headers for ray-id
69
+ level: logging.level === 1 ? 2 : logging.level,
70
+ ...('color' in logging && { color: logging.color }),
71
+ ...(logging.level > 0 && {
72
+ custom: async (...args) => {
73
+ const [, id, , url, headers] = args;
74
+ const customUrl = new URL(url);
75
+ const customHeaders = new Headers(headers);
76
+ if (customHeaders.has('cf-ray')) {
77
+ args.splice(3, 0, customHeaders.get('cf-ray'));
78
+ }
79
+ if ('color' in logging && logging.color) {
80
+ await import('chalk')
81
+ .then(({ Chalk }) => {
82
+ const chalk = new Chalk({ level: 2 });
83
+ if (customHeaders.has('cf-ray')) {
84
+ args.splice(3, 1, chalk.rgb(255, 102, 51)(customHeaders.get('cf-ray')));
85
+ }
86
+ })
87
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
88
+ .catch(() => { });
89
+ }
90
+ if ('custom' in logging && logging.custom) {
91
+ // We faked level 1 as 2 to get headers for ray-id
92
+ if (logging.level === 1) {
93
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
94
+ return logging.custom(...args.slice(0, -1));
95
+ }
96
+ else {
97
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
98
+ return logging.custom(...args);
99
+ }
100
+ }
101
+ else {
102
+ await Promise.all([import('strip-ansi'), import('chalk'), import("./index.mjs")]).then(([{ default: stripAnsi }, { Chalk }, { Helpers }]) => {
103
+ const chalk = new Chalk({ level: 2 });
104
+ // We faked level 1 as 2 to get headers for ray-id
105
+ if (logging.level === 1) {
106
+ console.info('CF Rest',
107
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
108
+ ...args
109
+ .slice(0, -1)
110
+ // Convert date to ISO string
111
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
112
+ .map((value) => (value instanceof Date && !isNaN(value.getTime()) ? value.toISOString() : value))
113
+ // Wrap id in brackets
114
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
115
+ .map((value) => (value === id ? chalk.rgb(...Helpers.uniqueIdColor(stripAnsi(id)))(`[${stripAnsi(id)}]`) : value))
116
+ // Strip out redundant parts of url
117
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
118
+ .map((value) => (value === url ? `${customUrl.pathname}${customUrl.search}${customUrl.hash}` : value)));
119
+ }
120
+ else {
121
+ console.info('CF Rest',
122
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
123
+ ...args
124
+ // Convert date to ISO string
125
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
126
+ .map((value) => (value instanceof Date && !isNaN(value.getTime()) ? value.toISOString() : value))
127
+ // Wrap id in brackets
128
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
129
+ .map((value) => (value === id ? chalk.rgb(...Helpers.uniqueIdColor(stripAnsi(id)))(`[${stripAnsi(id)}]`) : value))
130
+ // Strip out redundant parts of url
131
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
132
+ .map((value) => (value === url ? `${customUrl.pathname}${customUrl.search}${customUrl.hash}` : value)));
133
+ }
134
+ });
135
+ }
136
+ },
137
+ }),
138
+ },
139
+ };
140
+ return this.loggingFetch(info, loggingFetchInit);
50
141
  },
51
142
  }));
52
143
  }
53
- static isRequestLike(obj) {
54
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
55
- return typeof obj?.url === 'string';
144
+ static loggingFetchInit() {
145
+ return Promise.all([import('zod'), this.loggingFetchInitLogging()]).then(([{ z }, logging]) => z.object({
146
+ logging,
147
+ }));
56
148
  }
57
- static loggingFetch(info, init, body = false, logger = false) {
58
- return import("./crypto.mjs")
149
+ static loggingFetchInitLogging() {
150
+ return import('zod').then(({ z }) => z
151
+ .union([
152
+ z.object({
153
+ level: z.literal(0),
154
+ }),
155
+ z.object({
156
+ level: z.coerce.number().int().min(1).max(3),
157
+ color: z.boolean().default(true),
158
+ custom: z
159
+ .function()
160
+ // .args()
161
+ .returns(z.union([z.void(), z.promise(z.void())]))
162
+ .optional(),
163
+ }),
164
+ ])
165
+ .default({
166
+ level: 0,
167
+ }));
168
+ }
169
+ /**
170
+ * A utility function that wraps the native `fetch` API with enhanced capabilities.
171
+ * This function allows for customizable logging of request and response details, including headers, body, and status, with support for colorized output and custom logging handlers.
172
+ *
173
+ * @template RI - The type of the `RequestInit` object, defaulting to `RequestInit`. Intended for cloudflare's `RequestInit` variation.
174
+ *
175
+ * @param info - The input to the `fetch` function, which can be a `Request` object or a URL string.
176
+ * @param init - An optional configuration object extending `RequestInit` with additional options.
177
+ *
178
+ * @returns A promise that resolves to the `Response` object returned by the `fetch` call.
179
+ *
180
+ * ### Logging Levels:
181
+ * - `level >= 1`: Logs basic request details (timestamp, unique ID, method, and URL).
182
+ * - `level >= 2`: Logs request headers (with sensitive headers stripped).
183
+ * - `level >= 3`: Logs request body (if available) and response body (if available).
184
+ *
185
+ * ### Logging Options:
186
+ * - `logging.level`: The verbosity level of logging (1, 2, or 3).
187
+ * - `logging.color`: A boolean indicating whether to use colorized output.
188
+ * - `logging.custom`: An optional custom logging function to handle the log output.
189
+ *
190
+ * ### Features:
191
+ * - Automatically generates a unique ID for each request.
192
+ * - Strips sensitive headers from logs to ensure security.
193
+ * - Supports JSON and stream-based request/response bodies.
194
+ * - Allows for colorized output using the `chalk` library.
195
+ * - Provides hooks for custom logging implementations.
196
+ */
197
+ static loggingFetch(info, init) {
198
+ return NetHelpers.loggingFetchInit()
199
+ .then((parser) => parser.passthrough().parseAsync(init))
200
+ .then((parsed) => parsed)
201
+ .then((init) => import("./crypto.mjs")
59
202
  .then(({ CryptoHelpers }) => CryptoHelpers.base62secret(8))
60
203
  .then(async (id) => {
61
- const loggingItems = [new Date().toISOString(), id, init?.method ?? 'GET', this.isRequestLike(info) ? info.url : info.toString(), Object.fromEntries(this.stripSensitiveHeaders(new Headers(init?.headers)).entries())];
62
- if (body && init?.body) {
63
- if (new Headers(init.headers).get('Content-Type')?.toLowerCase().startsWith('application/json')) {
64
- loggingItems.push(JSON.parse(init?.body));
204
+ if (init.logging.level) {
205
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
+ const loggingItems = [new Date(), id, init?.method ?? Methods.GET, this.isRequestLike(info) ? info.url : info.toString()];
207
+ if (init.logging.level >= 2) {
208
+ loggingItems.push(Object.fromEntries(this.stripSensitiveHeaders(new Headers(init?.headers)).entries()));
65
209
  }
66
- else {
67
- loggingItems.push(init.body);
210
+ if (init.logging.level >= 3 && init?.body) {
211
+ if (init.body instanceof ReadableStream) {
212
+ loggingItems.push(Array.from(new Uint8Array(await new Response(init.body).arrayBuffer())));
213
+ }
214
+ else if (new Headers(init.headers).get('Content-Type')?.toLowerCase().startsWith('application/json')) {
215
+ loggingItems.push(JSON.parse(init.body));
216
+ }
217
+ else {
218
+ loggingItems.push(init.body);
219
+ }
68
220
  }
69
- }
70
- await Promise.all([import('chalk'), import("./index.mjs")])
71
- .then(([{ Chalk }, { Helpers }]) => {
72
- const chalk = new Chalk({ level: 2 });
73
- loggingItems.splice(1, 1, chalk.rgb(...Helpers.uniqueIdColor(id))(`[${id}]`));
74
- const initMethod = init?.method ?? 'GET';
75
- /**
76
- * @link https://github.com/swagger-api/swagger-ui/blob/master/src/style/_variables.scss#L48-L53
77
- */
78
- switch (initMethod) {
79
- case 'POST':
80
- loggingItems.splice(2, 1, chalk.hex('#49cc90')(initMethod));
81
- break;
82
- case 'GET':
83
- loggingItems.splice(2, 1, chalk.hex('#61affe')(initMethod));
84
- break;
85
- case 'PUT':
86
- loggingItems.splice(2, 1, chalk.hex('#fca130')(initMethod));
87
- break;
88
- case 'DELETE':
89
- loggingItems.splice(2, 1, chalk.hex('#f93e3e')(initMethod));
90
- break;
91
- case 'HEAD':
92
- loggingItems.splice(2, 1, chalk.hex('#9012fe')(initMethod));
93
- break;
94
- case 'PATCH':
95
- loggingItems.splice(2, 1, chalk.hex('#50e3c2')(initMethod));
96
- break;
221
+ if ('color' in init.logging && init.logging.color) {
222
+ await Promise.all([import('chalk'), import("./index.mjs")])
223
+ .then(([{ Chalk }, { Helpers }]) => {
224
+ const chalk = new Chalk({ level: 2 });
225
+ loggingItems.splice(1, 1, chalk.rgb(...Helpers.uniqueIdColor(id))(id));
226
+ })
227
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
228
+ .catch(() => { });
229
+ await this.methodColors(loggingItems[2])
230
+ .then((color) => {
231
+ if (color) {
232
+ loggingItems.splice(2, 1, color(loggingItems[2]));
233
+ }
234
+ })
235
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
236
+ .catch(() => { });
97
237
  }
98
- })
99
- // eslint-disable-next-line @typescript-eslint/no-empty-function
100
- .catch(() => { });
101
- if (typeof logger === 'boolean') {
102
- if (logger) {
238
+ if ('custom' in init.logging && init.logging.custom) {
103
239
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
104
- console.debug(...loggingItems);
240
+ await init.logging.custom(...loggingItems);
241
+ }
242
+ else {
243
+ console.info(
244
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
245
+ ...loggingItems
246
+ // Convert date to ISO string
247
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
248
+ .map((value) => (value instanceof Date && !isNaN(value.getTime()) ? value.toISOString() : value))
249
+ // Wrap id in brackets
250
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
251
+ .map((value) => (typeof value === 'string' && value.includes(id) ? value.replace(id, `[${id}]`) : value)));
105
252
  }
106
- }
107
- else {
108
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
109
- await logger(...loggingItems);
110
253
  }
111
254
  return id;
112
255
  })
@@ -114,37 +257,114 @@ export class NetHelpers {
114
257
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
115
258
  new Promise((resolve, reject) => fetch(info, init)
116
259
  .then(async (response) => {
117
- const loggingItems = [new Date().toISOString(), id, response.status, response.url, Object.fromEntries(this.stripSensitiveHeaders(response.headers).entries())];
118
- if (body) {
119
- const loggingClone = response.clone();
120
- if (response.headers.get('Content-Type')?.toLowerCase().startsWith('application/json')) {
121
- loggingItems.push(await loggingClone.json());
260
+ if (init.logging.level) {
261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
+ const loggingItems = [new Date(), id, response.status, response.url];
263
+ if (init.logging.level >= 2) {
264
+ loggingItems.push(Object.fromEntries(this.stripSensitiveHeaders(response.headers).entries()));
122
265
  }
123
- else {
124
- loggingItems.push(await loggingClone.text());
266
+ if (init.logging.level >= 3 && init?.body) {
267
+ const loggingClone = response.clone();
268
+ if (response.headers.get('Content-Type')?.toLowerCase().startsWith('application/json')) {
269
+ loggingItems.push(await loggingClone.json());
270
+ }
271
+ else {
272
+ loggingItems.push(await loggingClone.text());
273
+ }
274
+ /**
275
+ * @todo @demosjarco detect if the body is a stream and convert it to an array
276
+ */
125
277
  }
126
- }
127
- await Promise.all([import('chalk'), import("./index.mjs")])
128
- .then(([{ Chalk }, { Helpers }]) => {
129
- const chalk = new Chalk({ level: 2 });
130
- loggingItems.splice(1, 1, chalk.rgb(...Helpers.uniqueIdColor(id))(`[${id}]`));
131
- loggingItems.splice(2, 1, response.ok ? chalk.green(response.status) : chalk.red(response.status));
132
- })
133
- // eslint-disable-next-line @typescript-eslint/no-empty-function
134
- .catch(() => { });
135
- if (typeof logger === 'boolean') {
136
- if (logger) {
278
+ if ('color' in init.logging && init.logging.color) {
279
+ await Promise.all([import('chalk'), import("./index.mjs")])
280
+ .then(([{ Chalk }, { Helpers }]) => {
281
+ const chalk = new Chalk({ level: 2 });
282
+ loggingItems.splice(1, 1, chalk.rgb(...Helpers.uniqueIdColor(id))(id));
283
+ loggingItems.splice(2, 1, response.ok ? chalk.green(response.status) : chalk.red(response.status));
284
+ })
285
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
286
+ .catch(() => { });
287
+ }
288
+ if ('custom' in init.logging && init.logging.custom) {
137
289
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
138
- console.debug(...loggingItems);
290
+ await init.logging.custom(...loggingItems);
291
+ }
292
+ else {
293
+ console.info(
294
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
295
+ ...loggingItems
296
+ // Convert date to ISO string
297
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
298
+ .map((value) => (value instanceof Date && !isNaN(value.getTime()) ? value.toISOString() : value))
299
+ // Wrap id in brackets
300
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
301
+ .map((value) => (typeof value === 'string' && value.includes(id) ? value.replace(id, `[${id}]`) : value)));
139
302
  }
140
- }
141
- else {
142
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
143
- await logger(...loggingItems);
144
303
  }
145
304
  resolve(response);
146
305
  })
147
- .catch(reject)));
306
+ .catch(reject))));
307
+ }
308
+ /**
309
+ * Removes sensitive headers from the provided `Headers` object. Specifically, it deletes the `Set-Cookie` and `Authorization` headers.
310
+ *
311
+ * @param originalHeaders - The original `Headers` object to sanitize. Defaults to an empty `Headers` object if not provided.
312
+ * @returns A new `Headers` object with the sensitive headers removed.
313
+ */
314
+ static stripSensitiveHeaders(originalHeaders = new Headers()) {
315
+ const mutableHeaders = new Headers(originalHeaders);
316
+ mutableHeaders.delete('Set-Cookie');
317
+ mutableHeaders.delete('Authorization');
318
+ return mutableHeaders;
319
+ }
320
+ /**
321
+ * Determines if the given object is a `Request`-like object.
322
+ *
323
+ * This function checks if the provided object has a `url` property of type `string`,
324
+ * which is a characteristic of the `Request` object used in the Fetch API.
325
+ *
326
+ * @param obj - The object to check, typically the first parameter of the `fetch` function.
327
+ * @returns A boolean indicating whether the object is a `Request` instance.
328
+ */
329
+ static isRequestLike(obj) {
330
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
331
+ return typeof obj?.url === 'string';
332
+ }
333
+ /**
334
+ * Returns a promise that resolves to a `chalk` instance with a color corresponding to the provided HTTP method.
335
+ *
336
+ * The colors are based on the Swagger UI method colors:
337
+ * @link https://github.com/swagger-api/swagger-ui/blob/master/src/style/_variables.scss#L48-L55
338
+ *
339
+ * @param method - The HTTP method for which to retrieve the color.
340
+ * @returns A promise that resolves to a `chalk` instance with the color corresponding to the provided HTTP method.
341
+ * @throws An error if the provided method is unsupported.
342
+ */
343
+ static methodColors(method) {
344
+ return import('chalk').then(({ Chalk }) => {
345
+ const chalk = new Chalk({ level: 2 });
346
+ /**
347
+ * @link https://github.com/swagger-api/swagger-ui/blob/master/src/style/_variables.scss#L48-L55
348
+ */
349
+ switch (method) {
350
+ case Methods.GET:
351
+ return chalk.hex('#61affe');
352
+ case Methods.HEAD:
353
+ return chalk.hex('#9012fe');
354
+ case Methods.POST:
355
+ return chalk.hex('#49cc90');
356
+ case Methods.PUT:
357
+ return chalk.hex('#fca130');
358
+ case Methods.DELETE:
359
+ return chalk.hex('#f93e3e');
360
+ case Methods.OPTIONS:
361
+ return chalk.hex('#0d5aa7');
362
+ case Methods.PATCH:
363
+ return chalk.hex('#50e3c2');
364
+ default:
365
+ throw new Error(`Unsupported method: ${method}`);
366
+ }
367
+ });
148
368
  }
149
369
  /**
150
370
  * Parses the Server-Timing header and returns an object with the metrics.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainfuse/helpers",
3
- "version": "2.4.2",
3
+ "version": "3.0.0",
4
4
  "description": "",
5
5
  "author": "ChainFuse",
6
6
  "homepage": "https://github.com/ChainFuse/packages/tree/main/packages/helpers#readme",
@@ -51,12 +51,14 @@
51
51
  "@discordjs/rest": "^2.4.3",
52
52
  "chalk": "^5.4.1",
53
53
  "cloudflare": "^4.2.0",
54
- "uuid": "^11.1.0"
54
+ "strip-ansi": "^7.1.0",
55
+ "uuid": "^11.1.0",
56
+ "zod": "^3.24.3"
55
57
  },
56
58
  "devDependencies": {
57
- "@chainfuse/types": "^2.10.0",
58
- "@cloudflare/workers-types": "^4.20250410.0",
59
- "@types/node": "^22.14.0"
59
+ "@chainfuse/types": "^2.10.1",
60
+ "@cloudflare/workers-types": "^4.20250417.0",
61
+ "@types/node": "^22.14.1"
60
62
  },
61
- "gitHead": "f6da46cf1b8862c4607b7eb485f48125056907f9"
63
+ "gitHead": "ed937523841735fb5d944dfe1a5bc8d396a601c5"
62
64
  }