@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/app.d.ts +14 -3
- package/dist/app.js +39 -15
- package/dist/compression.d.ts +2 -0
- package/dist/compression.js +4 -52
- package/dist/conn-info.d.ts +3 -2
- package/dist/conn-info.js +11 -9
- package/dist/fetch-guard.d.ts +10 -1
- package/dist/fetch-guard.js +24 -3
- package/dist/fetch-resilience.d.ts +4 -0
- package/dist/fetch-resilience.js +10 -7
- package/dist/http-signatures.d.ts +12 -3
- package/dist/http-signatures.js +30 -11
- package/dist/idempotency.d.ts +16 -1
- package/dist/idempotency.js +28 -5
- package/dist/internal-body.d.ts +12 -0
- package/dist/internal-body.js +45 -0
- package/dist/internal-replay.d.ts +14 -0
- package/dist/internal-replay.js +21 -0
- package/dist/jwk.d.ts +6 -2
- package/dist/jwk.js +4 -0
- package/dist/jwt.d.ts +11 -1
- package/dist/jwt.js +21 -2
- package/dist/logger.d.ts +3 -1
- package/dist/logger.js +8 -0
- package/dist/mtls.d.ts +11 -3
- package/dist/mtls.js +59 -8
- package/dist/response-cache.d.ts +4 -0
- package/dist/response-cache.js +10 -2
- package/dist/router.d.ts +2 -1
- package/dist/router.js +2 -1
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/dist/scheduler.d.ts +2 -0
- package/dist/scheduler.js +31 -14
- package/dist/waf.js +19 -1
- package/dist/webhook-delivery.d.ts +2 -0
- package/dist/webhook-delivery.js +3 -0
- package/package.json +4 -4
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
|
-
|
|
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
|
-
|
|
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
|
|
213
|
-
*
|
|
214
|
-
*
|
|
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 =
|
|
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
|
|
425
|
+
if (allowFingerprints !== undefined && !matchesFingerprint(cert, allowFingerprints)) {
|
|
375
426
|
throw new ForbiddenError(message);
|
|
376
427
|
}
|
|
377
428
|
if (allowSANs && !matchesSAN(cert.subjectAltNames, allowSANs)) {
|
package/dist/response-cache.d.ts
CHANGED
|
@@ -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
|
package/dist/response-cache.js
CHANGED
|
@@ -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 =
|
|
645
|
-
if (buf
|
|
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
|
-
* -
|
|
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
|
-
* -
|
|
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/sbom.cdx.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:6545c557-5cea-599b-8286-d910b13cb482",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-
|
|
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.
|
|
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.
|
|
22
|
+
"bom-ref": "pkg:npm/@daloyjs/core@1.3.3",
|
|
23
23
|
"name": "@daloyjs/core",
|
|
24
|
-
"version": "1.3.
|
|
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.
|
|
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.
|
|
49
|
+
"tagId": "swidtag--daloyjs-core-1.3.3",
|
|
50
50
|
"name": "@daloyjs/core",
|
|
51
|
-
"version": "1.3.
|
|
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.
|
|
60
|
+
"ref": "pkg:npm/@daloyjs/core@1.3.3",
|
|
61
61
|
"dependsOn": []
|
|
62
62
|
}
|
|
63
63
|
]
|
package/dist/sbom.spdx.json
CHANGED
|
@@ -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.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.3.
|
|
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-
|
|
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.
|
|
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.
|
|
30
|
+
"referenceLocator": "pkg:npm/@daloyjs/core@1.3.3"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
191
|
-
if (
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
package/dist/webhook-delivery.js
CHANGED
|
@@ -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.
|
|
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
|
|
270
|
-
"coverage:branches": "tsc -p tsconfig.coverage.json && node --test --experimental-test-coverage --test-coverage-include
|
|
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@
|
|
322
|
+
"packageManager": "pnpm@12.3.0",
|
|
323
323
|
"dependencies": {}
|
|
324
324
|
}
|