@daloyjs/core 1.0.0-beta.0 → 1.0.0-beta.2
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/README.md +16 -7
- package/dist/adapters/node.js +48 -0
- package/dist/adapters/vercel.d.ts +1 -1
- package/dist/app.d.ts +45 -4
- package/dist/app.js +162 -9
- package/dist/compression.d.ts +5 -5
- package/dist/compression.js +1 -1
- package/dist/docs.d.ts +61 -2
- package/dist/docs.js +14 -3
- package/dist/idempotency.d.ts +1 -1
- package/dist/idempotency.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/metrics.d.ts +1 -1
- package/dist/metrics.js +2 -5
- package/dist/middleware.d.ts +6 -6
- package/dist/middleware.js +7 -16
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.js +1 -1
- package/dist/request-decompression.d.ts +1 -1
- package/dist/request-decompression.js +1 -1
- package/dist/response-cache.d.ts +1 -1
- package/dist/response-cache.js +2 -4
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/dist/scheduler.d.ts +1 -1
- package/dist/scheduler.js +40 -7
- package/dist/session.d.ts +1 -1
- package/dist/session.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -278,13 +278,21 @@ new App({
|
|
|
278
278
|
customCss: ":root { --scalar-color-accent: #2563eb; }",
|
|
279
279
|
hideTestRequestButton: true,
|
|
280
280
|
},
|
|
281
|
+
swagger: {
|
|
282
|
+
docExpansion: "none",
|
|
283
|
+
displayRequestDuration: true,
|
|
284
|
+
},
|
|
281
285
|
tags: ["Docs"],
|
|
282
286
|
},
|
|
283
287
|
});
|
|
284
288
|
```
|
|
285
289
|
|
|
286
|
-
Switch UIs with one word.
|
|
287
|
-
|
|
290
|
+
Switch UIs with one word. Scalar and Swagger UI include developer request
|
|
291
|
+
consoles for authenticated endpoints: Scalar selects the first configured
|
|
292
|
+
OpenAPI security scheme by default, and Swagger UI keeps values from the
|
|
293
|
+
Authorize dialog across reloads. `ui: "redoc"` renders Redoc instead, and its
|
|
294
|
+
options are forwarded to `Redoc.init` via `docs.redoc`; Redoc displays security
|
|
295
|
+
requirements but is a read-only reference UI, not a Try It console:
|
|
288
296
|
|
|
289
297
|
```ts
|
|
290
298
|
new App({
|
|
@@ -299,7 +307,8 @@ new App({
|
|
|
299
307
|
The `scalar` option is forwarded to Scalar's HTML API as JSON configuration,
|
|
300
308
|
with Daloy keeping the live `openapiPath` as the source. Use it for themes,
|
|
301
309
|
custom CSS, layout, auth defaults, and client visibility without copying the
|
|
302
|
-
HTML helper.
|
|
310
|
+
HTML helper. The `swagger` option is forwarded to `SwaggerUIBundle` with Daloy
|
|
311
|
+
owning `url` / `dom_id`. Redoc spins up a `blob:` Web Worker for search, so the
|
|
303
312
|
auto-mounted `/docs` page widens its CSP with `worker-src 'self' blob:` for
|
|
304
313
|
`ui: "redoc"` only — Scalar and Swagger UI keep the tighter default.
|
|
305
314
|
|
|
@@ -413,7 +422,7 @@ miss 4,763,878 ops/sec
|
|
|
413
422
|
|
|
414
423
|
### Cold-start tip (serverless / edge)
|
|
415
424
|
|
|
416
|
-
For deployments where every millisecond of startup matters (Lambda, Vercel
|
|
425
|
+
For deployments where every millisecond of startup matters (Lambda, Vercel, Cloudflare Workers, Fastly Compute), import `App` from the deep entry point instead of the barrel:
|
|
417
426
|
|
|
418
427
|
```ts
|
|
419
428
|
import { App } from "@daloyjs/core/app"; // ~13 ms faster cold start than "@daloyjs/core"
|
|
@@ -498,14 +507,14 @@ The core only ever sees `Request → Response`. Adapters live at the edge.
|
|
|
498
507
|
|
|
499
508
|
## Status
|
|
500
509
|
|
|
501
|
-
DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.
|
|
510
|
+
DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.2`). The public API is feature-complete and stable for the 1.0 line; from `1.0.0` onward, breaking changes follow SemVer and deprecations get at least one minor cycle. Small adjustments are still possible before the `1.0.0` GA if beta feedback surfaces something. The framework is already in use for production trials.
|
|
502
511
|
|
|
503
512
|
**Release quality bar.** Every release ships with **≥90% line + function coverage and ≥90% branch coverage**, strict TypeScript, OpenSSF Scorecard, CodeQL + Opengrep dual SAST, zizmor workflow linting, and npm provenance. Coverage was relaxed from a former 100% gate so complex security work isn't blocked chasing throwaway tests for unreachable defensive branches or tsx source-map phantoms; see [AGENTS.md](AGENTS.md) for the policy.
|
|
504
513
|
|
|
505
514
|
### Routing, validation, and docs
|
|
506
515
|
|
|
507
516
|
- Contract-first routing with Standard Schema validation (Zod 4, Valibot, ArkType, TypeBox) and OpenAPI 3.1 generated from a single source of truth.
|
|
508
|
-
- Live OpenAPI 3.1 spec served as both JSON (`GET /openapi.json`) and YAML (`GET /openapi.yaml`) when `docs: true`, with a choice of Scalar (default), Swagger UI, or Redoc via `docs.ui`, plus Scalar theming/custom CSS via `docs.scalar` and Redoc options via `docs.redoc`.
|
|
517
|
+
- Live OpenAPI 3.1 spec served as both JSON (`GET /openapi.json`) and YAML (`GET /openapi.yaml`) when `docs: true`, with a choice of Scalar (default), Swagger UI, or Redoc via `docs.ui`, plus Scalar theming/custom CSS/auth defaults via `docs.scalar`, Swagger UI options via `docs.swagger` (including persisted Authorize credentials), and Redoc options via `docs.redoc`.
|
|
509
518
|
- Zero-config OpenAPI `info` autofill from `package.json` (Node / Bun) or `deno.json` / `deno.jsonc` (Deno); explicit `openapi.info` values always win.
|
|
510
519
|
- RFC 7231 + RFC 5789 HTTP-method allowlist enforced inside `app.route()` (WebDAV, `TRACE`, `CONNECT` rejected at the framework boundary).
|
|
511
520
|
- AI-friendly route metadata via optional `meta: { examples, extensions, summary, description, tags }`; examples are validated against your schemas at build time, surfaced as OpenAPI `examples` + `x-daloy-*` extensions, and dumped as `routes.json` / `routes.yaml` via `daloy inspect --ai`.
|
|
@@ -516,7 +525,7 @@ DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.0`). The public API is featu
|
|
|
516
525
|
|
|
517
526
|
- Adapters for Node (Heroku, Railway, Render, Fly.io), Bun, Deno, Cloudflare Workers, Vercel Node / Edge / Next.js / Netlify Edge, Fastly Compute, and AWS Lambda / Netlify Functions / Lambda Function URLs.
|
|
518
527
|
- `daloy dev` watch loop delegates to the host runtime's native watcher (`node --import tsx --watch`, `bun --hot`, or `deno run --watch`) with a `--runtime` override for cross-runtime `package.json` scripts.
|
|
519
|
-
- `pnpm create daloy` scaffolder with Node, Bun, Deno, Cloudflare Worker, and Vercel
|
|
528
|
+
- `pnpm create daloy` scaffolder with Node, Bun, Deno, Cloudflare Worker, and Vercel templates, plus optional `--with-ci` GitHub Actions / Dependabot / CODEOWNERS / SECURITY.md hardening. The completion summary surfaces official install links (nodejs.org, pnpm.io, bun.sh) for any runtime or package manager your selections need but that is missing from `PATH`, and skips a doomed dependency install when the chosen package manager is absent.
|
|
520
529
|
- Container-first templates: `HEALTHCHECK` to `/readyz`, `STOPSIGNAL SIGTERM`, non-root user, `tini` as PID 1.
|
|
521
530
|
- Generated `deploy.yml` for container templates signs every pushed GHCR image with **Sigstore Cosign** (keyless OIDC) and attaches an **SPDX SBOM attestation** so consumers can `cosign verify` and `cosign verify-attestation --type spdxjson` instead of trusting the registry alone.
|
|
522
531
|
- Pretty `printStartupBanner()` / `formatStartupBanner()` helpers at `@daloyjs/core/banner`, used by every starter template (TTY + `NO_COLOR` / `FORCE_COLOR` aware, ASCII fallback for dumb terminals).
|
package/dist/adapters/node.js
CHANGED
|
@@ -37,6 +37,15 @@ export function serve(app, opts = {}) {
|
|
|
37
37
|
dispatchToApp(app, req, res, trustProxy, undefined);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
+
// Refuse Fetch-forbidden methods (CONNECT/TRACE/TRACK) before building a
|
|
41
|
+
// `Request` — `new Request` throws a `TypeError` for them, which would
|
|
42
|
+
// otherwise be caught and reported as a generic 500 instead of a clean,
|
|
43
|
+
// intentional method refusal. Placed after the GET/HEAD fast path so the
|
|
44
|
+
// hot path never pays for this check.
|
|
45
|
+
if (FETCH_FORBIDDEN_METHODS.has(method.toUpperCase())) {
|
|
46
|
+
writeMethodRefused(res);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
40
49
|
// POST/PUT/PATCH/DELETE with a small known content-length: pre-buffer
|
|
41
50
|
// bytes from the Node socket directly so the Request constructor gets a
|
|
42
51
|
// Uint8Array body instead of `Readable.toWeb(req)`. This skips the
|
|
@@ -223,6 +232,45 @@ function attachClientCertificate(req, request) {
|
|
|
223
232
|
return normalizePeerCertificate(raw, sock.authorized === true);
|
|
224
233
|
});
|
|
225
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Methods the WHATWG Fetch standard forbids on a `Request` (`CONNECT`,
|
|
237
|
+
* `TRACE`, `TRACK`). `new Request(url, { method })` throws a `TypeError` for
|
|
238
|
+
* these, so the Node adapter refuses them *before* constructing a `Request` —
|
|
239
|
+
* otherwise that `TypeError` surfaces as a generic `500` instead of a
|
|
240
|
+
* deliberate method refusal. Refusing `TRACE`/`TRACK` also closes Cross-Site
|
|
241
|
+
* Tracing, and `CONNECT` has no meaning for an origin server, so a categorical
|
|
242
|
+
* refusal is the correct secure default. (`CONNECT` is normally routed to
|
|
243
|
+
* Node's `connect` event rather than the request listener; it is included here
|
|
244
|
+
* defensively for runtimes/proxies that surface it as a normal request.)
|
|
245
|
+
*/
|
|
246
|
+
const FETCH_FORBIDDEN_METHODS = new Set([
|
|
247
|
+
"CONNECT",
|
|
248
|
+
"TRACE",
|
|
249
|
+
"TRACK",
|
|
250
|
+
]);
|
|
251
|
+
/**
|
|
252
|
+
* Refuse a Fetch-forbidden HTTP method with a spec-correct `501 Not
|
|
253
|
+
* Implemented`. `501` is more accurate than `405` here because the method is
|
|
254
|
+
* unsupported for *every* resource (not just the matched route), and unlike
|
|
255
|
+
* `405` it does not require an `Allow` header the adapter cannot compute before
|
|
256
|
+
* routing. Mirrors {@link writeAdapterError}'s RFC 9457 problem+json shape;
|
|
257
|
+
* `Connection: close` avoids reusing a socket whose (illegal) request body was
|
|
258
|
+
* never drained.
|
|
259
|
+
*
|
|
260
|
+
* @param res - The Node {@link ServerResponse} to write the refusal to.
|
|
261
|
+
*/
|
|
262
|
+
function writeMethodRefused(res) {
|
|
263
|
+
if (res.headersSent)
|
|
264
|
+
return;
|
|
265
|
+
res.statusCode = 501;
|
|
266
|
+
res.setHeader("content-type", "application/problem+json");
|
|
267
|
+
res.setHeader("connection", "close");
|
|
268
|
+
res.end(JSON.stringify({
|
|
269
|
+
type: "https://daloyjs.dev/errors/not-implemented",
|
|
270
|
+
title: "Not Implemented",
|
|
271
|
+
status: 501,
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
226
274
|
function writeAdapterError(res, e) {
|
|
227
275
|
if (!res.headersSent) {
|
|
228
276
|
res.statusCode = 500;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `toEdgeHandler` is kept as a backward-compatible alias of `toWebHandler`.
|
|
20
20
|
*/
|
|
21
21
|
import type { App } from "../app.js";
|
|
22
|
-
/** Web-standard handler shape used by Vercel
|
|
22
|
+
/** Web-standard handler shape used by Vercel Functions, Next.js route handlers, and middleware. */
|
|
23
23
|
export type WebHandler = (req: Request) => Promise<Response>;
|
|
24
24
|
/** Default export shape for Vercel's web-standard `{ fetch }` runtime. */
|
|
25
25
|
export interface FetchHandler {
|
package/dist/app.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { WebSocketRegistry, type WebSocketHandler } from "./websocket.js";
|
|
|
2
2
|
import { type Logger } from "./logger.js";
|
|
3
3
|
import type { HttpMethod, Hooks, PathString, RequestSchemas, ResponsesMap, RouteDefinition } from "./types.js";
|
|
4
4
|
import { type OpenAPIInfo, type OpenAPIOptions } from "./openapi.js";
|
|
5
|
-
import { type DocsAssetOptions, type DocsContentSecurityPolicyOptions, type RedocConfiguration, type ScalarJsonValue, type ScalarReferenceConfiguration } from "./docs.js";
|
|
5
|
+
import { type DocsAssetOptions, type DocsContentSecurityPolicyOptions, type RedocConfiguration, type ScalarJsonValue, type ScalarReferenceConfiguration, type SwaggerUiConfiguration } from "./docs.js";
|
|
6
6
|
import { type AsyncAPIServer } from "./asyncapi.js";
|
|
7
7
|
import { type SecureHeadersOptions } from "./middleware.js";
|
|
8
8
|
import { type LoadSheddingOptions } from "./load-shedding.js";
|
|
@@ -13,6 +13,8 @@ import { type BehindProxyConfig } from "./conn-info.js";
|
|
|
13
13
|
export declare function _resetCrashHandlersForTests(): void;
|
|
14
14
|
/** @internal Test-only helper to reset the latch between tests. */
|
|
15
15
|
export declare function _resetInsecureDefaultsLogForTests(): void;
|
|
16
|
+
/** @internal Test-only helper to reset the indeterminate-env warning latch. */
|
|
17
|
+
export declare function _resetIndeterminateEnvWarningForTests(): void;
|
|
16
18
|
/**
|
|
17
19
|
* Named security posture preset. Currently only one value is supported:
|
|
18
20
|
*
|
|
@@ -244,7 +246,7 @@ export interface AppOptions {
|
|
|
244
246
|
* {@link AppOptions.secureDefaults} is not `false`. Pass `false` to opt
|
|
245
247
|
* out (long-running CLI processes / test harnesses that intentionally
|
|
246
248
|
* swallow rejections may want this). No-op on runtimes without
|
|
247
|
-
* `process.on` (Cloudflare Workers / Vercel
|
|
249
|
+
* `process.on` (Cloudflare Workers / Vercel / Fastly).
|
|
248
250
|
*
|
|
249
251
|
* @since 0.18.0
|
|
250
252
|
*/
|
|
@@ -378,8 +380,21 @@ export interface DocsRouteOptions {
|
|
|
378
380
|
* renders Redoc.
|
|
379
381
|
*/
|
|
380
382
|
ui?: "scalar" | "swagger" | "redoc";
|
|
381
|
-
/**
|
|
383
|
+
/**
|
|
384
|
+
* Scalar API reference UI configuration. Ignored unless `ui` is `"scalar"`.
|
|
385
|
+
* When OpenAPI security schemes are configured and this object does not set
|
|
386
|
+
* `authentication.preferredSecurityScheme`, DaloyJS selects the first scheme
|
|
387
|
+
* so Scalar's developer console opens with an auth type ready to fill.
|
|
388
|
+
*/
|
|
382
389
|
scalar?: ScalarReferenceConfiguration;
|
|
390
|
+
/**
|
|
391
|
+
* Swagger UI configuration, forwarded to `SwaggerUIBundle`. Ignored unless
|
|
392
|
+
* `ui: "swagger"`. DaloyJS owns `url` and `dom_id`; credentials entered in
|
|
393
|
+
* the Authorize dialog persist across reloads by default.
|
|
394
|
+
*
|
|
395
|
+
* @since 0.42.0
|
|
396
|
+
*/
|
|
397
|
+
swagger?: SwaggerUiConfiguration;
|
|
383
398
|
/**
|
|
384
399
|
* Redoc UI configuration, forwarded to `Redoc.init`. Ignored unless
|
|
385
400
|
* `ui: "redoc"`.
|
|
@@ -728,7 +743,7 @@ export declare const DALOY_RAW_STREAM: unique symbol;
|
|
|
728
743
|
* - graceful shutdown and lifecycle observability
|
|
729
744
|
*
|
|
730
745
|
* `App` is **runtime-agnostic**: the same instance runs on Node, Bun, Deno,
|
|
731
|
-
* Cloudflare Workers, Vercel
|
|
746
|
+
* Cloudflare Workers, Vercel, AWS Lambda, and Fastly Compute via the
|
|
732
747
|
* dedicated adapters.
|
|
733
748
|
*
|
|
734
749
|
* @example
|
|
@@ -992,6 +1007,32 @@ export declare class App<Routes extends readonly RouteDefinition<any, any, any,
|
|
|
992
1007
|
* a misconfigured surface.
|
|
993
1008
|
*/
|
|
994
1009
|
private assertSecureHookConfig;
|
|
1010
|
+
/**
|
|
1011
|
+
* Whether the resolved runtime environment is indeterminate: no explicit
|
|
1012
|
+
* {@link AppOptions.env} / {@link AppOptions.production} was provided AND
|
|
1013
|
+
* `process.env.NODE_ENV` is unset or empty. This is the common default on
|
|
1014
|
+
* edge runtimes (Cloudflare Workers, Deno Deploy), which have no
|
|
1015
|
+
* `NODE_ENV`. Deliberately does not sniff the runtime — it only reports
|
|
1016
|
+
* whether the environment signal is absent — so it stays runtime-portable.
|
|
1017
|
+
*
|
|
1018
|
+
* @returns `true` when neither an explicit option nor `NODE_ENV` resolves the
|
|
1019
|
+
* environment; `false` otherwise (including when `NODE_ENV` is `development` /
|
|
1020
|
+
* `test`, which is a known, non-production answer).
|
|
1021
|
+
*/
|
|
1022
|
+
private isEnvIndeterminate;
|
|
1023
|
+
/**
|
|
1024
|
+
* Emit a single once-per-process warning when a production-only secure-default
|
|
1025
|
+
* refusal (wildcard CORS origin, weak session secret) would not fire purely
|
|
1026
|
+
* because the environment is indeterminate (see {@link isEnvIndeterminate}).
|
|
1027
|
+
* Routed through the logger, so `logger: false` silences it. Only warns when a
|
|
1028
|
+
* risky config is actually present, so a clean app stays quiet. Changes no
|
|
1029
|
+
* enforcement; pure observability for the documented "set `env` on edge"
|
|
1030
|
+
* requirement.
|
|
1031
|
+
*
|
|
1032
|
+
* @param record - The hook object, carrying the wildcard-CORS / session
|
|
1033
|
+
* markers used by {@link assertSecureHookConfig}.
|
|
1034
|
+
*/
|
|
1035
|
+
private warnIndeterminateEnvSecurity;
|
|
995
1036
|
private assertRouteAuthPayloadConfig;
|
|
996
1037
|
private resetBootGuardCache;
|
|
997
1038
|
/**
|
package/dist/app.js
CHANGED
|
@@ -43,6 +43,18 @@ let insecureDefaultsLoggedThisProcess = false;
|
|
|
43
43
|
export function _resetInsecureDefaultsLogForTests() {
|
|
44
44
|
insecureDefaultsLoggedThisProcess = false;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Once-per-process latch for the "production-only secure-default guard skipped
|
|
48
|
+
* because the runtime environment is indeterminate" warning (see
|
|
49
|
+
* {@link App.warnIndeterminateEnvSecurity}). Mirrors
|
|
50
|
+
* {@link insecureDefaultsLoggedThisProcess}: one heads-up per process, not one
|
|
51
|
+
* per `App` / per `.use()` call.
|
|
52
|
+
*/
|
|
53
|
+
let indeterminateEnvSecurityWarnedThisProcess = false;
|
|
54
|
+
/** @internal Test-only helper to reset the indeterminate-env warning latch. */
|
|
55
|
+
export function _resetIndeterminateEnvWarningForTests() {
|
|
56
|
+
indeterminateEnvSecurityWarnedThisProcess = false;
|
|
57
|
+
}
|
|
46
58
|
/**
|
|
47
59
|
* The exact set of fields the `"internal-service"` preset flips off when
|
|
48
60
|
* the caller has not set them explicitly. Surfaced through the boot
|
|
@@ -613,9 +625,22 @@ export class App {
|
|
|
613
625
|
assertSecureHookConfig(hooks) {
|
|
614
626
|
if (this.options.secureDefaults === false)
|
|
615
627
|
return;
|
|
616
|
-
if (!this.isProduction())
|
|
617
|
-
return;
|
|
618
628
|
const record = hooks;
|
|
629
|
+
if (!this.isProduction()) {
|
|
630
|
+
// The refusals below are production-only. When the environment is
|
|
631
|
+
// *indeterminate* (no explicit `env` / `production` option and no
|
|
632
|
+
// `NODE_ENV` — the common default on edge runtimes such as Workers /
|
|
633
|
+
// Deno Deploy / Vercel), those refusals never fire, so a risky
|
|
634
|
+
// config could ship unguarded. Surface that once as a warning when such
|
|
635
|
+
// a config is actually present. Enforcement is unchanged — this only
|
|
636
|
+
// makes the silent skip observable. The runtime itself is deliberately
|
|
637
|
+
// NOT sniffed (that would couple the core to specific platforms and
|
|
638
|
+
// break runtime portability); we key only on "is the env signal absent?".
|
|
639
|
+
if (this.isEnvIndeterminate()) {
|
|
640
|
+
this.warnIndeterminateEnvSecurity(record);
|
|
641
|
+
}
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
619
644
|
if (record[CORS_WILDCARD_ORIGIN_MARKER] === true) {
|
|
620
645
|
throw new Error('cors({ origin: "*" }) refused in production: a wildcard CORS origin exposes every state-changing route cross-origin. ' +
|
|
621
646
|
"Replace the wildcard with an explicit allowlist (string[] or predicate), or pass " +
|
|
@@ -630,6 +655,70 @@ export class App {
|
|
|
630
655
|
}
|
|
631
656
|
}
|
|
632
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Whether the resolved runtime environment is indeterminate: no explicit
|
|
660
|
+
* {@link AppOptions.env} / {@link AppOptions.production} was provided AND
|
|
661
|
+
* `process.env.NODE_ENV` is unset or empty. This is the common default on
|
|
662
|
+
* edge runtimes (Cloudflare Workers, Deno Deploy), which have no
|
|
663
|
+
* `NODE_ENV`. Deliberately does not sniff the runtime — it only reports
|
|
664
|
+
* whether the environment signal is absent — so it stays runtime-portable.
|
|
665
|
+
*
|
|
666
|
+
* @returns `true` when neither an explicit option nor `NODE_ENV` resolves the
|
|
667
|
+
* environment; `false` otherwise (including when `NODE_ENV` is `development` /
|
|
668
|
+
* `test`, which is a known, non-production answer).
|
|
669
|
+
*/
|
|
670
|
+
isEnvIndeterminate() {
|
|
671
|
+
if (this.options.env !== undefined ||
|
|
672
|
+
this.options.production !== undefined) {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
const nodeEnv = typeof process !== "undefined" && typeof process.env !== "undefined"
|
|
676
|
+
? process.env.NODE_ENV
|
|
677
|
+
: undefined;
|
|
678
|
+
return nodeEnv === undefined || nodeEnv === "";
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Emit a single once-per-process warning when a production-only secure-default
|
|
682
|
+
* refusal (wildcard CORS origin, weak session secret) would not fire purely
|
|
683
|
+
* because the environment is indeterminate (see {@link isEnvIndeterminate}).
|
|
684
|
+
* Routed through the logger, so `logger: false` silences it. Only warns when a
|
|
685
|
+
* risky config is actually present, so a clean app stays quiet. Changes no
|
|
686
|
+
* enforcement; pure observability for the documented "set `env` on edge"
|
|
687
|
+
* requirement.
|
|
688
|
+
*
|
|
689
|
+
* @param record - The hook object, carrying the wildcard-CORS / session
|
|
690
|
+
* markers used by {@link assertSecureHookConfig}.
|
|
691
|
+
*/
|
|
692
|
+
warnIndeterminateEnvSecurity(record) {
|
|
693
|
+
if (indeterminateEnvSecurityWarnedThisProcess)
|
|
694
|
+
return;
|
|
695
|
+
const risky = [];
|
|
696
|
+
if (record[CORS_WILDCARD_ORIGIN_MARKER] === true) {
|
|
697
|
+
risky.push('cors({ origin: "*" })');
|
|
698
|
+
}
|
|
699
|
+
if (record[SESSION_HOOK_MARKER] === true) {
|
|
700
|
+
const secrets = record[SESSION_SECRETS_MARKER];
|
|
701
|
+
if (Array.isArray(secrets)) {
|
|
702
|
+
const hasWeak = secrets.some((s) => {
|
|
703
|
+
try {
|
|
704
|
+
assertStrongSecret(s, "session");
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
if (hasWeak)
|
|
712
|
+
risky.push("a weak session secret");
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (risky.length === 0)
|
|
716
|
+
return;
|
|
717
|
+
indeterminateEnvSecurityWarnedThisProcess = true;
|
|
718
|
+
this.log.warn({ event: "secure_defaults.env_indeterminate", risky }, `DaloyJS: ${risky.join(" and ")} present, but the runtime environment is indeterminate ` +
|
|
719
|
+
`(no env option and no NODE_ENV). The production-only refuse-to-boot guard is therefore ` +
|
|
720
|
+
`inactive. If this is production (e.g. an edge runtime), set app({ env: "production" }).`);
|
|
721
|
+
}
|
|
633
722
|
assertRouteAuthPayloadConfig(route) {
|
|
634
723
|
const auth = route.auth;
|
|
635
724
|
if (!auth || auth.payload !== false)
|
|
@@ -845,9 +934,16 @@ export class App {
|
|
|
845
934
|
// (unless the caller explicitly overrode it). Scalar / Swagger UI keep the
|
|
846
935
|
// tighter default. The policy is deterministic from `ui` + `opts.csp`, so
|
|
847
936
|
// compute it once at mount time instead of per request.
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
937
|
+
const docsConnectOrigins = [
|
|
938
|
+
...(opts.csp?.connectOrigins ?? []),
|
|
939
|
+
...serverConnectOrigins(this.options.openapi?.servers),
|
|
940
|
+
];
|
|
941
|
+
const docsCsp = docsContentSecurityPolicy({
|
|
942
|
+
...(ui === "redoc"
|
|
943
|
+
? { ...opts.csp, allowBlobWorkers: opts.csp?.allowBlobWorkers ?? true }
|
|
944
|
+
: opts.csp),
|
|
945
|
+
...(docsConnectOrigins.length ? { connectOrigins: docsConnectOrigins } : {}),
|
|
946
|
+
});
|
|
851
947
|
this.route({
|
|
852
948
|
method: "GET",
|
|
853
949
|
path: docsPath,
|
|
@@ -863,6 +959,7 @@ export class App {
|
|
|
863
959
|
? swaggerUiHtml({
|
|
864
960
|
specUrl: openapiPath,
|
|
865
961
|
title,
|
|
962
|
+
configuration: opts.swagger,
|
|
866
963
|
assets: opts.assets,
|
|
867
964
|
})
|
|
868
965
|
: ui === "redoc"
|
|
@@ -875,7 +972,7 @@ export class App {
|
|
|
875
972
|
: scalarHtml({
|
|
876
973
|
specUrl: openapiPath,
|
|
877
974
|
title,
|
|
878
|
-
configuration: opts.scalar,
|
|
975
|
+
configuration: scalarConfigurationWithPreferredAuth(opts.scalar, this.options.openapi?.securitySchemes),
|
|
879
976
|
assets: opts.assets,
|
|
880
977
|
});
|
|
881
978
|
return {
|
|
@@ -2684,6 +2781,47 @@ function responsePipeline(fns) {
|
|
|
2684
2781
|
return current;
|
|
2685
2782
|
};
|
|
2686
2783
|
}
|
|
2784
|
+
function serverConnectOrigins(servers) {
|
|
2785
|
+
if (!servers)
|
|
2786
|
+
return [];
|
|
2787
|
+
const origins = [];
|
|
2788
|
+
for (const server of servers) {
|
|
2789
|
+
try {
|
|
2790
|
+
const url = new URL(server.url);
|
|
2791
|
+
if ((url.protocol === "http:" || url.protocol === "https:") &&
|
|
2792
|
+
!origins.includes(url.origin)) {
|
|
2793
|
+
origins.push(url.origin);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
catch {
|
|
2797
|
+
// Relative server URLs are already covered by connect-src 'self'.
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
return origins;
|
|
2801
|
+
}
|
|
2802
|
+
function scalarConfigurationWithPreferredAuth(configuration, schemes) {
|
|
2803
|
+
const preferredSecurityScheme = schemes ? Object.keys(schemes)[0] : undefined;
|
|
2804
|
+
if (!preferredSecurityScheme)
|
|
2805
|
+
return configuration;
|
|
2806
|
+
const authentication = configuration?.authentication;
|
|
2807
|
+
if (authentication &&
|
|
2808
|
+
typeof authentication === "object" &&
|
|
2809
|
+
!Array.isArray(authentication) &&
|
|
2810
|
+
"preferredSecurityScheme" in authentication) {
|
|
2811
|
+
return configuration;
|
|
2812
|
+
}
|
|
2813
|
+
return {
|
|
2814
|
+
...(configuration ?? {}),
|
|
2815
|
+
authentication: {
|
|
2816
|
+
...(authentication &&
|
|
2817
|
+
typeof authentication === "object" &&
|
|
2818
|
+
!Array.isArray(authentication)
|
|
2819
|
+
? authentication
|
|
2820
|
+
: {}),
|
|
2821
|
+
preferredSecurityScheme,
|
|
2822
|
+
},
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2687
2825
|
function finalizeResponse(res, ctx, hooks, stripFingerprint = true) {
|
|
2688
2826
|
let final = res;
|
|
2689
2827
|
const finish = (f) => {
|
|
@@ -3034,12 +3172,27 @@ async function readBody(req, ct, limit, multipart) {
|
|
|
3034
3172
|
return out;
|
|
3035
3173
|
}
|
|
3036
3174
|
if (ct.includes("multipart/form-data")) {
|
|
3037
|
-
//
|
|
3175
|
+
// Fast-fail on an honestly-declared oversize body.
|
|
3038
3176
|
const cl = req.headers.get("content-length");
|
|
3039
3177
|
if (cl && Number(cl) > limit) {
|
|
3040
3178
|
throw new PayloadTooLargeError(limit);
|
|
3041
3179
|
}
|
|
3042
|
-
|
|
3180
|
+
// Then cap the ACTUAL bytes before handing them to the platform
|
|
3181
|
+
// formData() parser. Content-Length is not a sufficient gate on its own: a
|
|
3182
|
+
// chunked upload sends none, and a lying small value slips past the check —
|
|
3183
|
+
// in both cases the platform parser would otherwise buffer the whole body
|
|
3184
|
+
// in memory on runtimes whose adapter does not cap at the socket layer
|
|
3185
|
+
// (Workers / Deno / Vercel). `readBodyLimited` streams the body and
|
|
3186
|
+
// throws `PayloadTooLargeError` the instant it exceeds `limit`; we then
|
|
3187
|
+
// re-parse the bounded bytes with the standard `formData()` parser,
|
|
3188
|
+
// preserving the multipart boundary via the original Content-Type. This is
|
|
3189
|
+
// Web-standard only (`Request` + `formData`), so it stays runtime-portable.
|
|
3190
|
+
const bytes = await readBodyLimited(req, limit);
|
|
3191
|
+
const fd = await new Request(req.url, {
|
|
3192
|
+
method: "POST",
|
|
3193
|
+
headers: { "content-type": ct },
|
|
3194
|
+
body: bytes,
|
|
3195
|
+
}).formData();
|
|
3043
3196
|
const out = {};
|
|
3044
3197
|
let fields = 0;
|
|
3045
3198
|
let files = 0;
|
|
@@ -3256,7 +3409,7 @@ const PACKAGE_JSON_CACHE = {};
|
|
|
3256
3409
|
* none is found while walking up from `process.cwd()`, falls back to
|
|
3257
3410
|
* `deno.json` / `deno.jsonc` so Deno projects get the same DX without a
|
|
3258
3411
|
* `package.json`. Returns an empty object on edge runtimes (Cloudflare
|
|
3259
|
-
* Workers
|
|
3412
|
+
* Workers) where `node:fs` is absent, on any I/O or parse
|
|
3260
3413
|
* error, and when nothing is found.
|
|
3261
3414
|
*
|
|
3262
3415
|
* The result is memoized at module scope: manifests do not change
|
package/dist/compression.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* BREACH-aware safe defaults.
|
|
4
4
|
*
|
|
5
5
|
* Built on the web-standard `CompressionStream` API so the same line works
|
|
6
|
-
* on Node, Bun, Deno, Cloudflare Workers, Vercel
|
|
6
|
+
* on Node, Bun, Deno, Cloudflare Workers, Vercel, and Fastly Compute.
|
|
7
7
|
* The middleware deliberately refuses to expose a configurable compression
|
|
8
8
|
* level — `CompressionStream` uses the runtime's documented default (gzip
|
|
9
9
|
* level 6 on Node / V8); a `level: 9` opt-in is rejected at construction
|
|
@@ -68,10 +68,10 @@ export interface CompressionOptions {
|
|
|
68
68
|
*/
|
|
69
69
|
authCookieNames?: readonly string[];
|
|
70
70
|
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
* Deliberately unsupported. Provided as a `never`-typed trap so the type
|
|
72
|
+
* system flags accidental opt-in. The actual `CompressionStream` never
|
|
73
|
+
* exposes a level knob — passing any value triggers a construction-time
|
|
74
|
+
* refusal.
|
|
75
75
|
*
|
|
76
76
|
* @internal
|
|
77
77
|
*/
|
package/dist/compression.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* BREACH-aware safe defaults.
|
|
4
4
|
*
|
|
5
5
|
* Built on the web-standard `CompressionStream` API so the same line works
|
|
6
|
-
* on Node, Bun, Deno, Cloudflare Workers, Vercel
|
|
6
|
+
* on Node, Bun, Deno, Cloudflare Workers, Vercel, and Fastly Compute.
|
|
7
7
|
* The middleware deliberately refuses to expose a configurable compression
|
|
8
8
|
* level — `CompressionStream` uses the runtime's documented default (gzip
|
|
9
9
|
* level 6 on Node / V8); a `level: 9` opt-in is rejected at construction
|
package/dist/docs.d.ts
CHANGED
|
@@ -254,7 +254,7 @@ export interface DocsAssetOptions {
|
|
|
254
254
|
*/
|
|
255
255
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
256
256
|
}
|
|
257
|
-
/** Shared options for {@link scalarHtml} and {@link
|
|
257
|
+
/** Shared options for {@link scalarHtml}, {@link swaggerUiHtml}, and {@link redocHtml}. */
|
|
258
258
|
export interface DocsOptions {
|
|
259
259
|
/** Absolute or relative URL of the OpenAPI document to render. */
|
|
260
260
|
specUrl: string;
|
|
@@ -273,6 +273,53 @@ export interface ScalarHtmlOptions extends DocsOptions {
|
|
|
273
273
|
/** Forwarded to the Scalar `<script id="api-reference">` tag. */
|
|
274
274
|
configuration?: ScalarReferenceConfiguration;
|
|
275
275
|
}
|
|
276
|
+
/** JSON-only subset of Swagger UI's `SwaggerUIBundle(...)` configuration. */
|
|
277
|
+
export interface SwaggerUiConfiguration {
|
|
278
|
+
[key: string]: ScalarJsonValue | undefined;
|
|
279
|
+
/**
|
|
280
|
+
* Preserve developer-entered API credentials across reloads. Defaults to
|
|
281
|
+
* `true` in {@link swaggerUiHtml} so the built-in docs behave like a local
|
|
282
|
+
* developer console instead of forgetting the bearer token on every refresh.
|
|
283
|
+
*/
|
|
284
|
+
persistAuthorization?: boolean;
|
|
285
|
+
/** Expand operations by default: `"list"`, `"full"`, or `"none"`. */
|
|
286
|
+
docExpansion?: "list" | "full" | "none";
|
|
287
|
+
/** Enable filtering operations by tag/path text. */
|
|
288
|
+
filter?: boolean | string;
|
|
289
|
+
/** Show operation extension fields such as `x-*`. */
|
|
290
|
+
showExtensions?: boolean;
|
|
291
|
+
/** Show common extension fields. */
|
|
292
|
+
showCommonExtensions?: boolean;
|
|
293
|
+
/** Render request duration after "Try it out" calls. */
|
|
294
|
+
displayRequestDuration?: boolean;
|
|
295
|
+
/** Sort operations by HTTP method, alphabetically, or with no sorter. */
|
|
296
|
+
operationsSorter?: "alpha" | "method";
|
|
297
|
+
/** Sort tags alphabetically or with no sorter. */
|
|
298
|
+
tagsSorter?: "alpha";
|
|
299
|
+
/**
|
|
300
|
+
* Internal URL wiring owned by DaloyJS. Use {@link DocsOptions.specUrl}
|
|
301
|
+
* instead of passing Swagger UI's `url` directly.
|
|
302
|
+
*/
|
|
303
|
+
url?: never;
|
|
304
|
+
/**
|
|
305
|
+
* Internal DOM mount point owned by DaloyJS. The generated HTML always mounts
|
|
306
|
+
* Swagger UI into `#swagger`.
|
|
307
|
+
*/
|
|
308
|
+
dom_id?: never;
|
|
309
|
+
/** Function-valued Swagger UI plugins cannot cross the server-to-HTML boundary. */
|
|
310
|
+
plugins?: never;
|
|
311
|
+
/** Function-valued presets cannot cross the server-to-HTML boundary. */
|
|
312
|
+
presets?: never;
|
|
313
|
+
/** Function-valued request interceptors cannot cross the server-to-HTML boundary. */
|
|
314
|
+
requestInterceptor?: never;
|
|
315
|
+
/** Function-valued response interceptors cannot cross the server-to-HTML boundary. */
|
|
316
|
+
responseInterceptor?: never;
|
|
317
|
+
}
|
|
318
|
+
/** Options for {@link swaggerUiHtml}; adds Swagger UI-specific configuration. */
|
|
319
|
+
export interface SwaggerUiHtmlOptions extends DocsOptions {
|
|
320
|
+
/** Forwarded into `SwaggerUIBundle(...)` after `url` and `dom_id`. */
|
|
321
|
+
configuration?: SwaggerUiConfiguration;
|
|
322
|
+
}
|
|
276
323
|
/**
|
|
277
324
|
* Options for {@link redocHtml}; adds Redoc-specific UI configuration.
|
|
278
325
|
*
|
|
@@ -300,6 +347,14 @@ export interface AsyncApiHtmlOptions extends DocsOptions {
|
|
|
300
347
|
export interface DocsContentSecurityPolicyOptions {
|
|
301
348
|
/** Extra origins to allow for `script-src` / `style-src` (defaults to jsDelivr). */
|
|
302
349
|
assetOrigins?: string[];
|
|
350
|
+
/**
|
|
351
|
+
* Extra origins to allow for `connect-src`. Add API origins here when the
|
|
352
|
+
* docs UI should send "Try it" requests somewhere other than the same origin
|
|
353
|
+
* serving the docs page.
|
|
354
|
+
*
|
|
355
|
+
* @since 0.42.0
|
|
356
|
+
*/
|
|
357
|
+
connectOrigins?: string[];
|
|
303
358
|
/** When set, allows nonce-protected inline scripts instead of `'unsafe-inline'`. */
|
|
304
359
|
scriptNonce?: string;
|
|
305
360
|
/** When `false`, omits `'unsafe-inline'` from `style-src`. Defaults to `true`. */
|
|
@@ -331,8 +386,12 @@ export declare function scalarHtml(opts: ScalarHtmlOptions): string;
|
|
|
331
386
|
/**
|
|
332
387
|
* Render a Swagger UI HTML page that loads `opts.specUrl`. Same usage as
|
|
333
388
|
* {@link scalarHtml} but emits the classic Swagger UI bundle.
|
|
389
|
+
*
|
|
390
|
+
* Developer-entered credentials are persisted by default
|
|
391
|
+
* (`persistAuthorization: true`) so routes with OpenAPI security requirements
|
|
392
|
+
* can be exercised after using Swagger UI's Authorize dialog.
|
|
334
393
|
*/
|
|
335
|
-
export declare function swaggerUiHtml(opts:
|
|
394
|
+
export declare function swaggerUiHtml(opts: SwaggerUiHtmlOptions): string;
|
|
336
395
|
/**
|
|
337
396
|
* Render a Redoc HTML page that loads `opts.specUrl`. Same usage as
|
|
338
397
|
* {@link scalarHtml} / {@link swaggerUiHtml} but emits the Redoc standalone
|
package/dist/docs.js
CHANGED
|
@@ -67,10 +67,13 @@ export function scalarHtml(opts) {
|
|
|
67
67
|
/**
|
|
68
68
|
* Render a Swagger UI HTML page that loads `opts.specUrl`. Same usage as
|
|
69
69
|
* {@link scalarHtml} but emits the classic Swagger UI bundle.
|
|
70
|
+
*
|
|
71
|
+
* Developer-entered credentials are persisted by default
|
|
72
|
+
* (`persistAuthorization: true`) so routes with OpenAPI security requirements
|
|
73
|
+
* can be exercised after using Swagger UI's Authorize dialog.
|
|
70
74
|
*/
|
|
71
75
|
export function swaggerUiHtml(opts) {
|
|
72
76
|
const title = escapeHtml(opts.title ?? "API Docs");
|
|
73
|
-
const url = escapeHtml(opts.specUrl);
|
|
74
77
|
const cssUrl = escapeHtml(opts.assets?.swaggerUiCssUrl ??
|
|
75
78
|
`${JSDELIVR_ORIGIN}/npm/swagger-ui-dist/swagger-ui.css`);
|
|
76
79
|
const bundleUrl = escapeHtml(opts.assets?.swaggerUiBundleUrl ??
|
|
@@ -78,6 +81,12 @@ export function swaggerUiHtml(opts) {
|
|
|
78
81
|
const cssSri = integrityAttr(opts.assets?.swaggerUiCssIntegrity, opts.assets?.crossOrigin);
|
|
79
82
|
const bundleSri = integrityAttr(opts.assets?.swaggerUiBundleIntegrity, opts.assets?.crossOrigin);
|
|
80
83
|
const nonce = nonceAttr(opts.scriptNonce);
|
|
84
|
+
const configuration = jsonForScript({
|
|
85
|
+
persistAuthorization: true,
|
|
86
|
+
...(opts.configuration ?? {}),
|
|
87
|
+
url: opts.specUrl,
|
|
88
|
+
dom_id: "#swagger",
|
|
89
|
+
});
|
|
81
90
|
return `<!doctype html>
|
|
82
91
|
<html><head>
|
|
83
92
|
<meta charset="utf-8" />
|
|
@@ -87,7 +96,7 @@ export function swaggerUiHtml(opts) {
|
|
|
87
96
|
</head><body>
|
|
88
97
|
<div id="swagger"></div>
|
|
89
98
|
<script src="${bundleUrl}"${bundleSri}${nonce}></script>
|
|
90
|
-
<script${nonce}>window.onload=()=>SwaggerUIBundle(
|
|
99
|
+
<script${nonce}>window.onload=()=>SwaggerUIBundle(${configuration});</script>
|
|
91
100
|
</body></html>`;
|
|
92
101
|
}
|
|
93
102
|
/**
|
|
@@ -179,12 +188,13 @@ export function docsContentSecurityPolicy(opts = {}) {
|
|
|
179
188
|
const styleSrc = ["'self'", ...assetOrigins];
|
|
180
189
|
if (opts.allowInlineStyles !== false)
|
|
181
190
|
styleSrc.push("'unsafe-inline'");
|
|
191
|
+
const connectSrc = ["'self'", ...(opts.connectOrigins ?? [])];
|
|
182
192
|
const directives = [
|
|
183
193
|
"default-src 'self'",
|
|
184
194
|
`script-src ${scriptSrc.join(" ")}`,
|
|
185
195
|
`style-src ${styleSrc.join(" ")}`,
|
|
186
196
|
"img-src 'self' data: https:",
|
|
187
|
-
|
|
197
|
+
`connect-src ${connectSrc.join(" ")}`,
|
|
188
198
|
];
|
|
189
199
|
// Redoc spawns a Web Worker from a `blob:` URL; without an explicit
|
|
190
200
|
// worker-src the browser falls back to script-src, which forbids `blob:`
|
|
@@ -205,6 +215,7 @@ export function htmlResponse(html, opts = {}) {
|
|
|
205
215
|
"content-security-policy": opts.contentSecurityPolicy ??
|
|
206
216
|
docsContentSecurityPolicy({
|
|
207
217
|
assetOrigins: opts.assetOrigins,
|
|
218
|
+
connectOrigins: opts.connectOrigins,
|
|
208
219
|
scriptNonce: opts.scriptNonce,
|
|
209
220
|
allowInlineStyles: opts.allowInlineStyles,
|
|
210
221
|
}),
|
package/dist/idempotency.d.ts
CHANGED
package/dist/idempotency.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { findRoutesMissingResponseBodySchema } from "./app.js";
|
|
|
4
4
|
export { _resetPackageJsonCacheForTests } from "./app.js";
|
|
5
5
|
export { _resetCrashHandlersForTests } from "./app.js";
|
|
6
6
|
export { _resetInsecureDefaultsLogForTests } from "./app.js";
|
|
7
|
+
export { _resetIndeterminateEnvWarningForTests } from "./app.js";
|
|
7
8
|
export type { AppOptions, AppOpenAPIOptions, DocsRouteOptions, AsyncAPIRouteOptions, HealthRouteOptions, CspReportRouteOptions, MetricsRouteOptions, IntrospectedRoute, PluginInstalledEvent, PluginExtension, ShutdownEvent, SecurityPreset, } from "./app.js";
|
|
8
9
|
export { getConnInfo, setConnInfo, assertBehindProxy, resolveClientIp, readRemoteAddress, readRemotePort, pickForwardedForByHops, } from "./conn-info.js";
|
|
9
10
|
export type { BehindProxyConfig, ConnInfo } from "./conn-info.js";
|
|
@@ -73,7 +74,7 @@ export type { RequestIdOptions, SecureHeadersOptions, CspDirectivesOptions, Cors
|
|
|
73
74
|
export type { BearerAuthOptions, BearerAuthVerifyHook } from "./middleware.js";
|
|
74
75
|
export { createLogger, noopLogger, DEFAULT_REDACT_KEYS } from "./logger.js";
|
|
75
76
|
export type { Logger, LogLevel, ConsoleLoggerOptions, LoggerRedactionOptions, } from "./logger.js";
|
|
76
|
-
export type { ScalarJsonPrimitive, ScalarJsonValue, ScalarReferenceConfiguration, ScalarTheme, RedocConfiguration, RedocHtmlOptions, AsyncApiHtmlOptions, DocsAssetOptions, } from "./docs.js";
|
|
77
|
+
export type { ScalarJsonPrimitive, ScalarJsonValue, ScalarReferenceConfiguration, ScalarTheme, RedocConfiguration, RedocHtmlOptions, SwaggerUiConfiguration, SwaggerUiHtmlOptions, AsyncApiHtmlOptions, DocsAssetOptions, } from "./docs.js";
|
|
77
78
|
export { formatStartupBanner, printStartupBanner } from "./banner.js";
|
|
78
79
|
export type { StartupBannerLink, StartupBannerOptions } from "./banner.js";
|
|
79
80
|
export { sseStream, sseResponse, ndjsonStream, ndjsonResponse, } from "./streaming.js";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { findRoutesMissingResponseBodySchema } from "./app.js";
|
|
|
4
4
|
export { _resetPackageJsonCacheForTests } from "./app.js";
|
|
5
5
|
export { _resetCrashHandlersForTests } from "./app.js";
|
|
6
6
|
export { _resetInsecureDefaultsLogForTests } from "./app.js";
|
|
7
|
+
export { _resetIndeterminateEnvWarningForTests } from "./app.js";
|
|
7
8
|
export { getConnInfo, setConnInfo, assertBehindProxy, resolveClientIp, readRemoteAddress, readRemotePort, pickForwardedForByHops, } from "./conn-info.js";
|
|
8
9
|
export { subdomains, PSL_SNAPSHOT_DATE, PSL_PUBLIC_SUFFIXES, MAX_SNAPSHOT_AGE_DAYS, } from "./subdomains.js";
|
|
9
10
|
export { defineDependency, DEPENDENCY_MARKER } from "./dependency.js";
|
package/dist/metrics.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Everything is built on Web-standard primitives (plus optional `process.*`
|
|
20
20
|
* gauges guarded for non-Node runtimes), so it runs unchanged on Node, Bun,
|
|
21
|
-
* Deno, Cloudflare Workers, and Vercel
|
|
21
|
+
* Deno, Cloudflare Workers, and Vercel.
|
|
22
22
|
*
|
|
23
23
|
* @module
|
|
24
24
|
* @since 0.37.0
|
package/dist/metrics.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Everything is built on Web-standard primitives (plus optional `process.*`
|
|
20
20
|
* gauges guarded for non-Node runtimes), so it runs unchanged on Node, Bun,
|
|
21
|
-
* Deno, Cloudflare Workers, and Vercel
|
|
21
|
+
* Deno, Cloudflare Workers, and Vercel.
|
|
22
22
|
*
|
|
23
23
|
* @module
|
|
24
24
|
* @since 0.37.0
|
|
@@ -231,10 +231,7 @@ export class Histogram extends Metric {
|
|
|
231
231
|
}
|
|
232
232
|
/** @internal */
|
|
233
233
|
render() {
|
|
234
|
-
const lines = [
|
|
235
|
-
`# HELP ${this.name} ${escapeHelp(this.help)}`,
|
|
236
|
-
`# TYPE ${this.name} histogram`,
|
|
237
|
-
];
|
|
234
|
+
const lines = [`# HELP ${this.name} ${escapeHelp(this.help)}`, `# TYPE ${this.name} histogram`];
|
|
238
235
|
for (const s of this.series.values()) {
|
|
239
236
|
for (let i = 0; i < this.bounds.length; i++) {
|
|
240
237
|
lines.push(`${this.name}_bucket${renderLabelBlock(s.labels, ["le", String(this.bounds[i])])} ${s.counts[i]}`);
|
package/dist/middleware.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export interface CspDirectivesOptions {
|
|
|
72
72
|
* Pair with {@link SecureHeadersOptions.reportingEndpoints} (or
|
|
73
73
|
* {@link App.cspReportRoute}) to register the receiver.
|
|
74
74
|
*
|
|
75
|
-
|
|
75
|
+
* @since 0.20.0
|
|
76
76
|
*/
|
|
77
77
|
reportTo?: string;
|
|
78
78
|
}
|
|
@@ -112,7 +112,7 @@ export interface SecureHeadersOptions {
|
|
|
112
112
|
* `reportTo: "csp-endpoint"` here as a shortcut) to direct CSP
|
|
113
113
|
* violation reports there.
|
|
114
114
|
*
|
|
115
|
-
|
|
115
|
+
* @since 0.20.0
|
|
116
116
|
*/
|
|
117
117
|
reportingEndpoints?: Record<string, string>;
|
|
118
118
|
/**
|
|
@@ -121,7 +121,7 @@ export interface SecureHeadersOptions {
|
|
|
121
121
|
* header so violation reports land at the matching
|
|
122
122
|
* `reportingEndpoints` URL.
|
|
123
123
|
*
|
|
124
|
-
|
|
124
|
+
* @since 0.20.0
|
|
125
125
|
*/
|
|
126
126
|
reportTo?: string;
|
|
127
127
|
}
|
|
@@ -500,9 +500,9 @@ export interface BearerAuthOptions {
|
|
|
500
500
|
realm?: string;
|
|
501
501
|
/**
|
|
502
502
|
* Optional per-request revalidation hook. Called after `validate`
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
503
|
+
* accepts the token. Returning `false` rejects the request with `403`;
|
|
504
|
+
* returning `true` or `undefined` accepts. Use for revocation lists,
|
|
505
|
+
* token-version counters, etc.
|
|
506
506
|
*
|
|
507
507
|
* @since 0.22.0
|
|
508
508
|
*/
|
package/dist/middleware.js
CHANGED
|
@@ -44,7 +44,7 @@ function generateCspNonce() {
|
|
|
44
44
|
const cryptoApi = globalThis.crypto;
|
|
45
45
|
if (!cryptoApi?.getRandomValues) {
|
|
46
46
|
throw new Error("secureHeaders(): WebCrypto is required to generate a CSP nonce. " +
|
|
47
|
-
"Run on Node 20+, Bun, Deno, Cloudflare Workers, or Vercel
|
|
47
|
+
"Run on Node 20+, Bun, Deno, Cloudflare Workers, or Vercel.");
|
|
48
48
|
}
|
|
49
49
|
const nonceBytes = new Uint8Array(16);
|
|
50
50
|
cryptoApi.getRandomValues(nonceBytes);
|
|
@@ -193,10 +193,7 @@ export function secureHeaders(opts = {}) {
|
|
|
193
193
|
}
|
|
194
194
|
cspOpt = { directives, reportTo: opts.reportTo };
|
|
195
195
|
}
|
|
196
|
-
else if (opts.reportTo &&
|
|
197
|
-
cspOpt !== false &&
|
|
198
|
-
typeof cspOpt === "object" &&
|
|
199
|
-
!cspOpt.reportTo) {
|
|
196
|
+
else if (opts.reportTo && cspOpt !== false && typeof cspOpt === "object" && !cspOpt.reportTo) {
|
|
200
197
|
cspOpt = { ...cspOpt, reportTo: opts.reportTo };
|
|
201
198
|
}
|
|
202
199
|
const cspIsDynamic = cspOpt !== false && typeof cspOpt === "object";
|
|
@@ -479,10 +476,9 @@ export function cors(opts) {
|
|
|
479
476
|
// produces broken-but-not-obviously-broken behavior in production. Fail
|
|
480
477
|
// closed at construction time instead.
|
|
481
478
|
if (opts.credentials) {
|
|
482
|
-
const includesWildcard = opts.origin === "*" ||
|
|
483
|
-
(Array.isArray(opts.origin) && opts.origin.includes("*"));
|
|
479
|
+
const includesWildcard = opts.origin === "*" || (Array.isArray(opts.origin) && opts.origin.includes("*"));
|
|
484
480
|
if (includesWildcard) {
|
|
485
|
-
throw new Error(
|
|
481
|
+
throw new Error('cors(): origin: "*" cannot be combined with credentials: true. ' +
|
|
486
482
|
"Pass an explicit origin string, an array of allowed origins, or a predicate function instead.");
|
|
487
483
|
}
|
|
488
484
|
}
|
|
@@ -558,8 +554,7 @@ export function cors(opts) {
|
|
|
558
554
|
};
|
|
559
555
|
hooks[CORS_HOOK_MARKER] = true;
|
|
560
556
|
hooks[CORS_ORIGIN_ALLOW_MARKER] = (origin) => allow(origin) !== null;
|
|
561
|
-
const hasWildcard = opts.origin === "*" ||
|
|
562
|
-
(Array.isArray(opts.origin) && opts.origin.includes("*"));
|
|
557
|
+
const hasWildcard = opts.origin === "*" || (Array.isArray(opts.origin) && opts.origin.includes("*"));
|
|
563
558
|
if (hasWildcard) {
|
|
564
559
|
hooks[CORS_WILDCARD_ORIGIN_MARKER] = true;
|
|
565
560
|
}
|
|
@@ -732,9 +727,7 @@ export function loginThrottle(opts = {}) {
|
|
|
732
727
|
groupId,
|
|
733
728
|
keyGenerator,
|
|
734
729
|
...(opts.store ? { store: opts.store } : {}),
|
|
735
|
-
...(opts.trustProxyHeaders !== undefined
|
|
736
|
-
? { trustProxyHeaders: opts.trustProxyHeaders }
|
|
737
|
-
: {}),
|
|
730
|
+
...(opts.trustProxyHeaders !== undefined ? { trustProxyHeaders: opts.trustProxyHeaders } : {}),
|
|
738
731
|
...(opts.retryAfter !== undefined ? { retryAfter: opts.retryAfter } : {}),
|
|
739
732
|
});
|
|
740
733
|
let slowdownBuckets = SHARED_LOGIN_THROTTLE_BUCKETS.get(groupId);
|
|
@@ -1229,9 +1222,7 @@ export function requireScopes(scopes) {
|
|
|
1229
1222
|
});
|
|
1230
1223
|
}
|
|
1231
1224
|
const owned = readUserScopes(user);
|
|
1232
|
-
const missing = owned === null
|
|
1233
|
-
? aggregate.slice()
|
|
1234
|
-
: aggregate.filter((s) => !owned.has(s));
|
|
1225
|
+
const missing = owned === null ? aggregate.slice() : aggregate.filter((s) => !owned.has(s));
|
|
1235
1226
|
if (missing.length > 0) {
|
|
1236
1227
|
throw new ForbiddenError(`Missing required scope(s): ${missing.join(", ")}.`);
|
|
1237
1228
|
}
|
package/dist/pagination.d.ts
CHANGED
package/dist/pagination.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* The compressed input itself is bounded by `maxCompressedBytes` before a single
|
|
26
26
|
* byte is inflated. Built on the web-standard `DecompressionStream`, so the same
|
|
27
|
-
* line works on Node, Bun, Deno, Cloudflare Workers, and Vercel
|
|
27
|
+
* line works on Node, Bun, Deno, Cloudflare Workers, and Vercel. Zero
|
|
28
28
|
* runtime dependencies.
|
|
29
29
|
*
|
|
30
30
|
* The middleware runs in the {@link "./types.js".Hooks.onRequest} phase — before
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* The compressed input itself is bounded by `maxCompressedBytes` before a single
|
|
26
26
|
* byte is inflated. Built on the web-standard `DecompressionStream`, so the same
|
|
27
|
-
* line works on Node, Bun, Deno, Cloudflare Workers, and Vercel
|
|
27
|
+
* line works on Node, Bun, Deno, Cloudflare Workers, and Vercel. Zero
|
|
28
28
|
* runtime dependencies.
|
|
29
29
|
*
|
|
30
30
|
* The middleware runs in the {@link "./types.js".Hooks.onRequest} phase — before
|
package/dist/response-cache.d.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*
|
|
35
35
|
* This module is dependency-free and uses only Web Standard
|
|
36
36
|
* `Request`/`Response` + `Headers`, so it runs unchanged on Node, Bun, Deno,
|
|
37
|
-
* Cloudflare Workers, and Vercel
|
|
37
|
+
* Cloudflare Workers, and Vercel.
|
|
38
38
|
*
|
|
39
39
|
* @module
|
|
40
40
|
* @since 0.37.0
|
package/dist/response-cache.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*
|
|
35
35
|
* This module is dependency-free and uses only Web Standard
|
|
36
36
|
* `Request`/`Response` + `Headers`, so it runs unchanged on Node, Bun, Deno,
|
|
37
|
-
* Cloudflare Workers, and Vercel
|
|
37
|
+
* Cloudflare Workers, and Vercel.
|
|
38
38
|
*
|
|
39
39
|
* @module
|
|
40
40
|
* @since 0.37.0
|
|
@@ -304,9 +304,7 @@ export function responseCache(opts = {}) {
|
|
|
304
304
|
const reqCc = parseCacheControl(ctx.request.headers.get("cache-control"));
|
|
305
305
|
if (reqCc.has("no-store"))
|
|
306
306
|
return undefined;
|
|
307
|
-
const rawKey = opts.keyGenerator
|
|
308
|
-
? opts.keyGenerator(ctx)
|
|
309
|
-
: defaultKey(ctx, varyHeaders);
|
|
307
|
+
const rawKey = opts.keyGenerator ? opts.keyGenerator(ctx) : defaultKey(ctx, varyHeaders);
|
|
310
308
|
if (rawKey === null)
|
|
311
309
|
return undefined;
|
|
312
310
|
const key = `${keyPrefix}${rawKey}`;
|
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:086b2383-b351-57fb-bd98-0fbf04f2342b",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-06-
|
|
7
|
+
"timestamp": "2026-06-22T21:07:28.471Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.2"
|
|
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.0.0-beta.
|
|
22
|
+
"bom-ref": "pkg:npm/@daloyjs/core@1.0.0-beta.2",
|
|
23
23
|
"name": "@daloyjs/core",
|
|
24
|
-
"version": "1.0.0-beta.
|
|
24
|
+
"version": "1.0.0-beta.2",
|
|
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.0.0-beta.
|
|
26
|
+
"purl": "pkg:npm/@daloyjs/core@1.0.0-beta.2",
|
|
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.0.0-beta.
|
|
49
|
+
"tagId": "swidtag--daloyjs-core-1.0.0-beta.2",
|
|
50
50
|
"name": "@daloyjs/core",
|
|
51
|
-
"version": "1.0.0-beta.
|
|
51
|
+
"version": "1.0.0-beta.2",
|
|
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.0.0-beta.
|
|
60
|
+
"ref": "pkg:npm/@daloyjs/core@1.0.0-beta.2",
|
|
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.0.0-beta.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.
|
|
5
|
+
"name": "@daloyjs/core-1.0.0-beta.2",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.2-086b2383-b351-57fb-bd98-0fbf04f2342b",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-06-
|
|
8
|
+
"created": "2026-06-22T21:07:28.471Z",
|
|
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.0.0-beta.
|
|
19
|
+
"versionInfo": "1.0.0-beta.2",
|
|
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.0.0-beta.
|
|
30
|
+
"referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-beta.2"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Everything is built on Web-standard primitives (`AbortController`,
|
|
24
24
|
* `Intl.DateTimeFormat` for timezone wall-clock math, `setTimeout`), so it
|
|
25
|
-
* runs unchanged on Node, Bun, Deno, Cloudflare Workers, and Vercel
|
|
25
|
+
* runs unchanged on Node, Bun, Deno, Cloudflare Workers, and Vercel, with
|
|
26
26
|
* zero runtime dependencies. Pair it with {@link App.cron} for an app-managed
|
|
27
27
|
* scheduler whose lifecycle is tied to graceful shutdown, or drive a
|
|
28
28
|
* {@link Scheduler} directly.
|
package/dist/scheduler.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Everything is built on Web-standard primitives (`AbortController`,
|
|
24
24
|
* `Intl.DateTimeFormat` for timezone wall-clock math, `setTimeout`), so it
|
|
25
|
-
* runs unchanged on Node, Bun, Deno, Cloudflare Workers, and Vercel
|
|
25
|
+
* runs unchanged on Node, Bun, Deno, Cloudflare Workers, and Vercel, with
|
|
26
26
|
* zero runtime dependencies. Pair it with {@link App.cron} for an app-managed
|
|
27
27
|
* scheduler whose lifecycle is tied to graceful shutdown, or drive a
|
|
28
28
|
* {@link Scheduler} directly.
|
|
@@ -63,11 +63,27 @@ const CRON_ALIASES = {
|
|
|
63
63
|
"@hourly": "0 * * * *",
|
|
64
64
|
};
|
|
65
65
|
const MONTH_NAMES = {
|
|
66
|
-
jan: 1,
|
|
67
|
-
|
|
66
|
+
jan: 1,
|
|
67
|
+
feb: 2,
|
|
68
|
+
mar: 3,
|
|
69
|
+
apr: 4,
|
|
70
|
+
may: 5,
|
|
71
|
+
jun: 6,
|
|
72
|
+
jul: 7,
|
|
73
|
+
aug: 8,
|
|
74
|
+
sep: 9,
|
|
75
|
+
oct: 10,
|
|
76
|
+
nov: 11,
|
|
77
|
+
dec: 12,
|
|
68
78
|
};
|
|
69
79
|
const DAY_NAMES = {
|
|
70
|
-
sun: 0,
|
|
80
|
+
sun: 0,
|
|
81
|
+
mon: 1,
|
|
82
|
+
tue: 2,
|
|
83
|
+
wed: 3,
|
|
84
|
+
thu: 4,
|
|
85
|
+
fri: 5,
|
|
86
|
+
sat: 6,
|
|
71
87
|
};
|
|
72
88
|
function resolveNamed(token, names) {
|
|
73
89
|
const lower = token.toLowerCase();
|
|
@@ -163,7 +179,13 @@ export function parseCron(expression) {
|
|
|
163
179
|
};
|
|
164
180
|
}
|
|
165
181
|
const WEEKDAY_INDEX = {
|
|
166
|
-
Sun: 0,
|
|
182
|
+
Sun: 0,
|
|
183
|
+
Mon: 1,
|
|
184
|
+
Tue: 2,
|
|
185
|
+
Wed: 3,
|
|
186
|
+
Thu: 4,
|
|
187
|
+
Fri: 5,
|
|
188
|
+
Sat: 6,
|
|
167
189
|
};
|
|
168
190
|
function wallClockOf(date, timeZone) {
|
|
169
191
|
if (timeZone === undefined || timeZone === "UTC") {
|
|
@@ -323,7 +345,12 @@ export class Scheduler {
|
|
|
323
345
|
skipped: 0,
|
|
324
346
|
};
|
|
325
347
|
this.#tasks.set(def.name, task);
|
|
326
|
-
this.#logger?.debug({
|
|
348
|
+
this.#logger?.debug({
|
|
349
|
+
event: "scheduler.task.defined",
|
|
350
|
+
task: def.name,
|
|
351
|
+
cron: def.cron,
|
|
352
|
+
intervalMs: def.intervalMs,
|
|
353
|
+
}, `Scheduled task "${def.name}" defined`);
|
|
327
354
|
if (this.#started && !this.#stopped)
|
|
328
355
|
this.#arm(task, def.runOnStart === true);
|
|
329
356
|
return this;
|
|
@@ -518,7 +545,13 @@ export class Scheduler {
|
|
|
518
545
|
catch (error) {
|
|
519
546
|
task.failures++;
|
|
520
547
|
task.lastError = error;
|
|
521
|
-
this.#logger?.error({
|
|
548
|
+
this.#logger?.error({
|
|
549
|
+
event: "scheduler.task.failed",
|
|
550
|
+
task: task.def.name,
|
|
551
|
+
runCount,
|
|
552
|
+
timedOut,
|
|
553
|
+
err: serializeError(error),
|
|
554
|
+
}, `Scheduled task "${task.def.name}" failed`);
|
|
522
555
|
try {
|
|
523
556
|
task.def.onError?.(error, { name: task.def.name, runCount, timedOut });
|
|
524
557
|
}
|
package/dist/session.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* backed by Redis, Cloudflare KV, Vercel KV, or any other shared store.
|
|
14
14
|
*
|
|
15
15
|
* The module is dependency-free and uses Web Crypto (`crypto.subtle`), so it
|
|
16
|
-
* works on Node, Bun, Deno, Cloudflare Workers, and Vercel
|
|
16
|
+
* works on Node, Bun, Deno, Cloudflare Workers, and Vercel.
|
|
17
17
|
*/
|
|
18
18
|
import type { BaseContext, Hooks } from "./types.js";
|
|
19
19
|
/**
|
package/dist/session.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* backed by Redis, Cloudflare KV, Vercel KV, or any other shared store.
|
|
14
14
|
*
|
|
15
15
|
* The module is dependency-free and uses Web Crypto (`crypto.subtle`), so it
|
|
16
|
-
* works on Node, Bun, Deno, Cloudflare Workers, and Vercel
|
|
16
|
+
* works on Node, Bun, Deno, Cloudflare Workers, and Vercel.
|
|
17
17
|
*/
|
|
18
18
|
import { assertCookieAttributes, readRequestCookie, serializeClearCookie, serializeCookie, } from "./cookie.js";
|
|
19
19
|
import { timingSafeEqual } from "./security.js";
|
|
@@ -385,7 +385,9 @@ const DEFAULT_ROTATE_SESSION_KEYS = [
|
|
|
385
385
|
];
|
|
386
386
|
function sessionFromContext(ctx) {
|
|
387
387
|
const value = ctx.state[STATE_KEY];
|
|
388
|
-
if (!value ||
|
|
388
|
+
if (!value ||
|
|
389
|
+
typeof value !== "object" ||
|
|
390
|
+
typeof value.regenerate !== "function") {
|
|
389
391
|
throw new Error("rotateSession(): session() must run before rotateSession().");
|
|
390
392
|
}
|
|
391
393
|
return value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daloyjs/core",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
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 — distributed via pnpm.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|