@daloyjs/core 1.3.1 → 1.3.3

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/mtls.js CHANGED
@@ -68,6 +68,8 @@ export function getClientCertificate(request) {
68
68
  * Normalize a Node `getPeerCertificate(true)` result into a
69
69
  * {@link ClientCertificate}. Returns `undefined` for the empty object Node
70
70
  * returns when the peer presented no certificate.
71
+ * Quoted SAN values are decoded without treating their embedded commas as
72
+ * identity separators. Malformed SAN lists yield no identities for allowlists.
71
73
  *
72
74
  * @param raw - The structured peer-certificate object from the TLS socket.
73
75
  * @param verified - Whether the socket reported `authorized === true` (the
@@ -148,13 +150,58 @@ function parseCertDate(value) {
148
150
  function parseNodeSubjectAltName(san) {
149
151
  if (typeof san !== "string" || san.length === 0)
150
152
  return [];
151
- // Node renders SANs as `DNS:a, IP Address:1.2.3.4, URI:spiffe://...`.
152
153
  const out = [];
153
- for (const piece of san.split(",")) {
154
+ const quoted = san.includes('"');
155
+ const pieces = quoted ? [] : san.split(",");
156
+ if (quoted) {
157
+ let start = 0;
158
+ let inQuotes = false;
159
+ let escaped = false;
160
+ for (let index = 0; index < san.length; index++) {
161
+ const char = san[index];
162
+ if (escaped) {
163
+ escaped = false;
164
+ }
165
+ else if (inQuotes && char === "\\") {
166
+ escaped = true;
167
+ }
168
+ else if (char === '"') {
169
+ inQuotes = !inQuotes;
170
+ }
171
+ else if (char === "," && !inQuotes) {
172
+ pieces.push(san.slice(start, index));
173
+ start = index + 1;
174
+ }
175
+ }
176
+ if (inQuotes || escaped)
177
+ return [];
178
+ pieces.push(san.slice(start));
179
+ }
180
+ for (const piece of pieces) {
154
181
  const trimmed = piece.trim();
155
182
  if (trimmed.length === 0)
156
183
  continue;
157
- out.push(trimmed.replace(/^IP Address:/i, "IP:"));
184
+ const colon = trimmed.indexOf(":");
185
+ if (colon < 1)
186
+ return [];
187
+ if (!quoted) {
188
+ out.push(trimmed.replace(/^IP Address:/i, "IP:"));
189
+ continue;
190
+ }
191
+ const type = trimmed.slice(0, colon);
192
+ let value = trimmed.slice(colon + 1);
193
+ if (value.startsWith('"')) {
194
+ try {
195
+ value = JSON.parse(value);
196
+ }
197
+ catch {
198
+ return [];
199
+ }
200
+ }
201
+ else if (quoted && value.includes('"')) {
202
+ return [];
203
+ }
204
+ out.push(`${type.toLowerCase() === "ip address" ? "IP" : type}:${value}`);
158
205
  }
159
206
  return out;
160
207
  }
@@ -209,9 +256,10 @@ function cnFromDN(dn) {
209
256
  * Parse an Envoy `X-Forwarded-Client-Cert` (XFCC) header value into a
210
257
  * {@link ClientCertificate}. XFCC is a comma-separated list of proxy elements,
211
258
  * each a `;`-delimited set of `Key=Value` pairs (`Hash`, `Subject`, `URI`,
212
- * `DNS`, `Cert`, …). The **first** element is the client closest to the origin
213
- * and is the one returned. Because Envoy only emits XFCC for connections it
214
- * mutually authenticated, the result is marked `verified: true`.
259
+ * `DNS`, `Cert`, …). The **first** element is returned. The `verified: true`
260
+ * result is a trusted-proxy assertion, not cryptographic verification by this
261
+ * parser. The terminator must verify client certificates, strip incoming XFCC,
262
+ * and replace it with its own value. Append-only forwarding is insufficient.
215
263
  *
216
264
  * @param headerValue Raw XFCC header value; `null`/`undefined` are tolerated.
217
265
  * @returns The certificate parsed from the first XFCC element, or `undefined`
@@ -297,6 +345,9 @@ const MISSING_CERT_BODY = JSON.stringify({
297
345
  * parsed from a trusted-proxy header, enforces verification + optional
298
346
  * allow-lists + validity window + a custom hook, and stamps the accepted
299
347
  * certificate on `ctx.state` for downstream handlers.
348
+ * Header mode requires an origin reachable only through a trusted terminator
349
+ * that strips and replaces identity headers after certificate verification.
350
+ * Fingerprint allowlists do not authenticate client-supplied header values.
300
351
  *
301
352
  * Rejection semantics:
302
353
  * - **No certificate presented** → `401` `application/problem+json` with
@@ -330,7 +381,7 @@ export function clientCertAuth(opts = {}) {
330
381
  const message = opts.message ?? "Client certificate not permitted";
331
382
  const stateKey = opts.stateKey ?? "clientCertificate";
332
383
  const now = opts.now ?? Date.now;
333
- const allowFingerprints = (opts.allowFingerprints ?? []).map((f) => normalizeFingerprint(f) ?? "");
384
+ const allowFingerprints = opts.allowFingerprints?.map((f) => normalizeFingerprint(f) ?? "");
334
385
  const allowSubjectCNs = opts.allowSubjectCNs;
335
386
  const allowIssuerCNs = opts.allowIssuerCNs;
336
387
  const allowSANs = opts.allowSANs;
@@ -371,7 +422,7 @@ export function clientCertAuth(opts = {}) {
371
422
  if (allowIssuerCNs && !matchesAllowedCN(cert.issuerCN, allowIssuerCNs)) {
372
423
  throw new ForbiddenError(message);
373
424
  }
374
- if (allowFingerprints.length > 0 && !matchesFingerprint(cert, allowFingerprints)) {
425
+ if (allowFingerprints !== undefined && !matchesFingerprint(cert, allowFingerprints)) {
375
426
  throw new ForbiddenError(message);
376
427
  }
377
428
  if (allowSANs && !matchesSAN(cert.subjectAltNames, allowSANs)) {
@@ -355,6 +355,10 @@ export declare class MemoryResponseCacheStore implements ResponseCacheStore {
355
355
  * `no-store` / `private` / `no-cache`, carrying `Set-Cookie` or `Vary: *`,
356
356
  * failing {@link ResponseCacheOptions.cacheableStatus}, or larger than
357
357
  * {@link ResponseCacheOptions.maxBodyBytes} are never cached.
358
+ * The byte cap is enforced while reading the response clone; exceeding it
359
+ * stops capture without waiting for EOF or consuming the client's branch.
360
+ * Replays require every scope aggregated from the route's requireScopes hooks;
361
+ * callers without those scopes continue to the normal authorization chain.
358
362
  *
359
363
  * A response that declares `Vary` is stored as a **variant**: the request's
360
364
  * values for those fields are recorded alongside it, and the entry is replayed
@@ -65,6 +65,8 @@
65
65
  * @since 0.37.0
66
66
  */
67
67
  import { markSchemaValidatedResponse } from "./internal-response.js";
68
+ import { readResponseBodyUpTo } from "./internal-body.js";
69
+ import { hasReplayScopes } from "./internal-replay.js";
68
70
  /** Internal `ctx.state` key carrying the pending cache key between hooks. */
69
71
  const PENDING_STATE_KEY = "__responseCachePending";
70
72
  /**
@@ -420,6 +422,10 @@ function isPromiseLike(value) {
420
422
  * `no-store` / `private` / `no-cache`, carrying `Set-Cookie` or `Vary: *`,
421
423
  * failing {@link ResponseCacheOptions.cacheableStatus}, or larger than
422
424
  * {@link ResponseCacheOptions.maxBodyBytes} are never cached.
425
+ * The byte cap is enforced while reading the response clone; exceeding it
426
+ * stops capture without waiting for EOF or consuming the client's branch.
427
+ * Replays require every scope aggregated from the route's requireScopes hooks;
428
+ * callers without those scopes continue to the normal authorization chain.
423
429
  *
424
430
  * A response that declares `Vary` is stored as a **variant**: the request's
425
431
  * values for those fields are recorded alongside it, and the entry is replayed
@@ -522,6 +528,8 @@ export function responseCache(opts = {}) {
522
528
  }
523
529
  const hooks = {
524
530
  async beforeHandle(ctx) {
531
+ if (!hasReplayScopes(ctx))
532
+ return undefined;
525
533
  const method = ctx.request.method.toUpperCase();
526
534
  if (!methods.has(method))
527
535
  return undefined;
@@ -641,8 +649,8 @@ export function responseCache(opts = {}) {
641
649
  res.headers.set(statusHeaderName, "MISS");
642
650
  return undefined;
643
651
  }
644
- const buf = new Uint8Array(await res.clone().arrayBuffer());
645
- if (buf.byteLength > maxBodyBytes) {
652
+ const buf = await readResponseBodyUpTo(res.clone(), maxBodyBytes);
653
+ if (buf === null) {
646
654
  if (statusHeaderName)
647
655
  res.headers.set(statusHeaderName, "MISS");
648
656
  return undefined;
package/dist/router.d.ts CHANGED
@@ -7,7 +7,8 @@
7
7
  * - Path normalization and splitting avoid regular expressions.
8
8
  *
9
9
  * Safety:
10
- * - Path traversal (`..`) and empty segments are rejected at lookup time.
10
+ * - Raw `/../`, trailing `/..`, and empty segments are rejected at lookup time.
11
+ * - Decoded parameters are untrusted data, not sanitized filesystem paths.
11
12
  * - Duplicate routes and duplicate operationIds throw at registration.
12
13
  * - Wildcard segments must be terminal.
13
14
  */
package/dist/router.js CHANGED
@@ -7,7 +7,8 @@
7
7
  * - Path normalization and splitting avoid regular expressions.
8
8
  *
9
9
  * Safety:
10
- * - Path traversal (`..`) and empty segments are rejected at lookup time.
10
+ * - Raw `/../`, trailing `/..`, and empty segments are rejected at lookup time.
11
+ * - Decoded parameters are untrusted data, not sanitized filesystem paths.
11
12
  * - Duplicate routes and duplicate operationIds throw at registration.
12
13
  * - Wildcard segments must be terminal.
13
14
  */
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:3be788cc-3073-595a-871d-593791c06327",
4
+ "serialNumber": "urn:uuid:6545c557-5cea-599b-8286-d910b13cb482",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-08-29T14:05:07.830Z",
7
+ "timestamp": "2026-09-10T10:47:58.279Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.3.1"
12
+ "version": "1.3.3"
13
13
  }
14
14
  ],
15
15
  "authors": [
@@ -19,11 +19,11 @@
19
19
  ],
20
20
  "component": {
21
21
  "type": "library",
22
- "bom-ref": "pkg:npm/@daloyjs/core@1.3.1",
22
+ "bom-ref": "pkg:npm/@daloyjs/core@1.3.3",
23
23
  "name": "@daloyjs/core",
24
- "version": "1.3.1",
24
+ "version": "1.3.3",
25
25
  "description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
26
- "purl": "pkg:npm/@daloyjs/core@1.3.1",
26
+ "purl": "pkg:npm/@daloyjs/core@1.3.3",
27
27
  "licenses": [
28
28
  {
29
29
  "license": {
@@ -46,9 +46,9 @@
46
46
  }
47
47
  ],
48
48
  "swid": {
49
- "tagId": "swidtag--daloyjs-core-1.3.1",
49
+ "tagId": "swidtag--daloyjs-core-1.3.3",
50
50
  "name": "@daloyjs/core",
51
- "version": "1.3.1",
51
+ "version": "1.3.3",
52
52
  "tagVersion": 0,
53
53
  "patch": false
54
54
  }
@@ -57,7 +57,7 @@
57
57
  "components": [],
58
58
  "dependencies": [
59
59
  {
60
- "ref": "pkg:npm/@daloyjs/core@1.3.1",
60
+ "ref": "pkg:npm/@daloyjs/core@1.3.3",
61
61
  "dependsOn": []
62
62
  }
63
63
  ]
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@daloyjs/core-1.3.1",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.3.1-3be788cc-3073-595a-871d-593791c06327",
5
+ "name": "@daloyjs/core-1.3.3",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.3.3-6545c557-5cea-599b-8286-d910b13cb482",
7
7
  "creationInfo": {
8
- "created": "2026-08-29T14:05:07.830Z",
8
+ "created": "2026-09-10T10:47:58.279Z",
9
9
  "creators": [
10
10
  "Tool: daloy-generate-sbom",
11
11
  "Organization: DaloyJS"
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "SPDXID": "SPDXRef-Package--daloyjs-core",
18
18
  "name": "@daloyjs/core",
19
- "versionInfo": "1.3.1",
19
+ "versionInfo": "1.3.3",
20
20
  "downloadLocation": "https://github.com/daloyjs/daloy",
21
21
  "filesAnalyzed": false,
22
22
  "licenseConcluded": "MIT",
@@ -27,7 +27,7 @@
27
27
  {
28
28
  "referenceCategory": "PACKAGE-MANAGER",
29
29
  "referenceType": "purl",
30
- "referenceLocator": "pkg:npm/@daloyjs/core@1.3.1"
30
+ "referenceLocator": "pkg:npm/@daloyjs/core@1.3.3"
31
31
  }
32
32
  ]
33
33
  }
@@ -228,6 +228,7 @@ export interface CronFields {
228
228
  * Supported syntax per field: `*`, lists (`1,2,3`), ranges (`1-5`), steps
229
229
  * (`*\/5`, `1-10/2`), and case-insensitive month (`JAN`–`DEC`) / day
230
230
  * (`SUN`–`SAT`) names. Day-of-week accepts both `0` and `7` for Sunday.
231
+ * Numeric weekday ranges are expanded before Sunday aliases are normalized.
231
232
  *
232
233
  * @param expression - A cron expression or alias.
233
234
  * @returns The compiled field sets.
@@ -245,6 +246,7 @@ export declare function parseCron(expression: string): CronFields;
245
246
  * @returns The next matching `Date`.
246
247
  * @throws {@link CronParseError} if no match occurs within five years
247
248
  * (an unsatisfiable expression).
249
+ * @throws {RangeError} If `after` is invalid or the timezone is unsupported.
248
250
  * @since 0.37.0
249
251
  */
250
252
  export declare function nextCronRun(expression: string | CronFields, after?: Date, timeZone?: string): Date;
package/dist/scheduler.js CHANGED
@@ -145,6 +145,7 @@ function parseField(field, min, max, fieldName, names) {
145
145
  * Supported syntax per field: `*`, lists (`1,2,3`), ranges (`1-5`), steps
146
146
  * (`*\/5`, `1-10/2`), and case-insensitive month (`JAN`–`DEC`) / day
147
147
  * (`SUN`–`SAT`) names. Day-of-week accepts both `0` and `7` for Sunday.
148
+ * Numeric weekday ranges are expanded before Sunday aliases are normalized.
148
149
  *
149
150
  * @param expression - A cron expression or alias.
150
151
  * @returns The compiled field sets.
@@ -167,7 +168,9 @@ export function parseCron(expression) {
167
168
  const dayOfMonth = parseField(dom, 1, 31, "day-of-month");
168
169
  const month = parseField(mon, 1, 12, "month", MONTH_NAMES);
169
170
  // Day-of-week allows 7 as an alias for Sunday; normalize 7 -> 0.
170
- const dowRaw = parseField(dow.replace(/7/g, "0"), 0, 6, "day-of-week", DAY_NAMES);
171
+ const dowRaw = parseField(dow, 0, 7, "day-of-week", DAY_NAMES);
172
+ if (dowRaw.delete(7))
173
+ dowRaw.add(0);
171
174
  return {
172
175
  minute,
173
176
  hour,
@@ -187,8 +190,8 @@ const WEEKDAY_INDEX = {
187
190
  Fri: 5,
188
191
  Sat: 6,
189
192
  };
190
- function wallClockOf(date, timeZone) {
191
- if (timeZone === undefined || timeZone === "UTC") {
193
+ function wallClockOf(date, formatter) {
194
+ if (formatter === undefined) {
192
195
  return {
193
196
  minute: date.getUTCMinutes(),
194
197
  hour: date.getUTCHours(),
@@ -197,16 +200,7 @@ function wallClockOf(date, timeZone) {
197
200
  dayOfWeek: date.getUTCDay(),
198
201
  };
199
202
  }
200
- const parts = new Intl.DateTimeFormat("en-US", {
201
- timeZone,
202
- hour12: false,
203
- year: "numeric",
204
- month: "numeric",
205
- day: "numeric",
206
- hour: "numeric",
207
- minute: "numeric",
208
- weekday: "short",
209
- }).formatToParts(date);
203
+ const parts = formatter.formatToParts(date);
210
204
  const get = (type) => parts.find((p) => p.type === type)?.value ?? "0";
211
205
  let hour = Number(get("hour"));
212
206
  if (hour === 24)
@@ -253,15 +247,38 @@ const MAX_LOOKAHEAD_MINUTES = 5 * 366 * 24 * 60;
253
247
  * @returns The next matching `Date`.
254
248
  * @throws {@link CronParseError} if no match occurs within five years
255
249
  * (an unsatisfiable expression).
250
+ * @throws {RangeError} If `after` is invalid or the timezone is unsupported.
256
251
  * @since 0.37.0
257
252
  */
258
253
  export function nextCronRun(expression, after = new Date(), timeZone) {
259
254
  const fields = typeof expression === "string" ? parseCron(expression) : expression;
255
+ if (!Number.isFinite(after.getTime()))
256
+ throw new RangeError("Invalid cron search date.");
257
+ const formatter = timeZone === undefined || timeZone === "UTC"
258
+ ? undefined
259
+ : new Intl.DateTimeFormat("en-US", {
260
+ timeZone,
261
+ hour12: false,
262
+ year: "numeric",
263
+ month: "numeric",
264
+ day: "numeric",
265
+ hour: "numeric",
266
+ minute: "numeric",
267
+ weekday: "short",
268
+ });
269
+ if (fields.domRestricted && !fields.dowRestricted) {
270
+ const possible = [...fields.month].some(month => {
271
+ const maxDay = new Date(Date.UTC(2000, month, 0)).getUTCDate();
272
+ return [...fields.dayOfMonth].some(day => day <= maxDay);
273
+ });
274
+ if (!possible)
275
+ throw new CronParseError("Cron expression has no valid calendar day.");
276
+ }
260
277
  // Advance to the start of the next whole minute.
261
278
  const start = Math.floor(after.getTime() / 60_000) * 60_000 + 60_000;
262
279
  for (let i = 0; i < MAX_LOOKAHEAD_MINUTES; i++) {
263
280
  const candidate = new Date(start + i * 60_000);
264
- if (matches(fields, wallClockOf(candidate, timeZone)))
281
+ if (matches(fields, wallClockOf(candidate, formatter)))
265
282
  return candidate;
266
283
  }
267
284
  throw new CronParseError(`Cron expression matches no time within five years (unsatisfiable).`);
package/dist/waf.js CHANGED
@@ -268,7 +268,7 @@ function inspectionVariants(value, maxValueLength) {
268
268
  if (v.includes("+"))
269
269
  push(v.replace(/\+/g, " "));
270
270
  if (v.includes("/*"))
271
- push(v.replace(/\/\*[\s\S]*?\*\//g, " "));
271
+ push(stripBlockComments(v));
272
272
  // Control characters (notably NUL) are not `\s`, so `1'%00OR%001=1` split
273
273
  // `OR` from `1=1` and walked past the whitespace-anchored signatures. Scan
274
274
  // a control-char→space form; benign traffic carries almost no C0 bytes, so
@@ -285,6 +285,24 @@ function inspectionVariants(value, maxValueLength) {
285
285
  }
286
286
  return out;
287
287
  }
288
+ function stripBlockComments(value) {
289
+ let cursor = 0;
290
+ const parts = [];
291
+ for (;;) {
292
+ const start = value.indexOf("/*", cursor);
293
+ if (start < 0)
294
+ break;
295
+ const end = value.indexOf("*/", start + 2);
296
+ if (end < 0)
297
+ break;
298
+ parts.push(value.slice(cursor, start), " ");
299
+ cursor = end + 2;
300
+ }
301
+ if (cursor === 0)
302
+ return value;
303
+ parts.push(value.slice(cursor));
304
+ return parts.join("");
305
+ }
288
306
  /**
289
307
  * Scan every inspection variant of `value` for the active rule set.
290
308
  *
@@ -252,6 +252,8 @@ export interface WebhookSenderOptions {
252
252
  * dead-letters a single {@link WebhookEvent}, resolving to a
253
253
  * {@link WebhookDeliveryResult} (it does not throw on ordinary delivery
254
254
  * failure).
255
+ * Intermediate response bodies are cancelled before retry backoff without
256
+ * waiting for producer cancellation; the final response remains caller-owned.
255
257
  *
256
258
  * @example
257
259
  * ```ts
@@ -133,6 +133,8 @@ function randomId() {
133
133
  * dead-letters a single {@link WebhookEvent}, resolving to a
134
134
  * {@link WebhookDeliveryResult} (it does not throw on ordinary delivery
135
135
  * failure).
136
+ * Intermediate response bodies are cancelled before retry backoff without
137
+ * waiting for producer cancellation; the final response remains caller-owned.
136
138
  *
137
139
  * @example
138
140
  * ```ts
@@ -277,6 +279,7 @@ export function createWebhookSender(options) {
277
279
  });
278
280
  if (!retryable)
279
281
  break;
282
+ void response.body?.cancel().catch(() => undefined);
280
283
  await sleep(delayMs);
281
284
  continue;
282
285
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daloyjs/core",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops \u2014 distributed via pnpm.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -266,8 +266,8 @@
266
266
  "red-team:live": "node --import tsx red-team-live/run.ts",
267
267
  "red-team:live:mcp": "node --import tsx red-team-live/mcp-attacks.ts",
268
268
  "red-team:live:wave2": "node --import tsx red-team-live/skill-wave2-attacks.ts",
269
- "coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-lines=90 --test-coverage-functions=90 tests/**/*.test.ts",
270
- "coverage:branches": "tsc -p tsconfig.coverage.json && node --test --experimental-test-coverage --test-coverage-include='dist-coverage/src/**' --test-coverage-branches=92 dist-coverage/tests/**/*.test.js",
269
+ "coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-include=\"src/**\" --test-coverage-lines=90 --test-coverage-functions=90 tests/**/*.test.ts",
270
+ "coverage:branches": "tsc -p tsconfig.coverage.json && node --test --experimental-test-coverage --test-coverage-include=\"dist-coverage/src/**\" --test-coverage-branches=92 dist-coverage/tests/**/*.test.js",
271
271
  "typecheck": "tsc --noEmit && tsc -p tsconfig.typetest.json && tsc -p tests/tsconfig.json --noEmit && tsc -p red-team-live/tsconfig.json --noEmit",
272
272
  "typecheck:tests": "tsc -p tests/tsconfig.json --noEmit",
273
273
  "format": "prettier --write .",
@@ -319,6 +319,6 @@
319
319
  "bin",
320
320
  "README.md"
321
321
  ],
322
- "packageManager": "pnpm@11.1.3",
322
+ "packageManager": "pnpm@12.3.0",
323
323
  "dependencies": {}
324
324
  }