@daloyjs/core 1.0.0-beta.2 → 1.0.0-beta.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/README.md CHANGED
@@ -507,7 +507,7 @@ The core only ever sees `Request → Response`. Adapters live at the edge.
507
507
 
508
508
  ## Status
509
509
 
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.
510
+ DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.3`). 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.
511
511
 
512
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.
513
513
 
@@ -296,7 +296,7 @@ function toWebRequest(req, trustProxy, bufferedBody) {
296
296
  : undefined;
297
297
  const proto = forwardedProto ??
298
298
  (req.socket.encrypted ? "https" : "http");
299
- const url = `${proto}://${host}${req.url ?? "/"}`;
299
+ const url = `${proto}://${host}${normalizeRequestTarget(req.url)}`;
300
300
  // Build headers from `rawHeaders` (a flat [k0,v0,k1,v1,...] array) instead
301
301
  // of the parsed `req.headers` object. This matches @hono/node-server's
302
302
  // `newHeadersFromIncoming`: one `new Headers([[k,v],...])` constructor
@@ -348,6 +348,23 @@ function firstHeader(v) {
348
348
  const comma = raw.indexOf(",");
349
349
  return (comma === -1 ? raw : raw.slice(0, comma)).trim() || undefined;
350
350
  }
351
+ function normalizeRequestTarget(target) {
352
+ const raw = target && target.length > 0 ? target : "/";
353
+ if (raw.charCodeAt(0) === 47 /* / */)
354
+ return raw;
355
+ if (raw.startsWith("http://") || raw.startsWith("https://")) {
356
+ try {
357
+ const url = new URL(raw);
358
+ return `${url.pathname}${url.search}`;
359
+ }
360
+ catch {
361
+ return "/";
362
+ }
363
+ }
364
+ if (raw.charCodeAt(0) === 63 /* ? */)
365
+ return `/${raw}`;
366
+ return `/${raw}`;
367
+ }
351
368
  function sendWebResponse(res, out) {
352
369
  out.statusCode = res.status;
353
370
  res.headers.forEach((v, k) => out.setHeader(k, v));
package/dist/app.js CHANGED
@@ -3265,7 +3265,7 @@ function serializeResult(result, def, validateResponses) {
3265
3265
  const treatAsJson = !explicitCt || explicitCt.includes("application/json");
3266
3266
  if (!explicitCt)
3267
3267
  headers.set("content-type", "application/json");
3268
- // RFC 8594 deprecation lifecycle headers. A route with an explicit
3268
+ // Deprecation lifecycle headers. A route with an explicit RFC 8594
3269
3269
  // `sunset` date is implicitly deprecated. Never overwrite a value the
3270
3270
  // handler set deliberately.
3271
3271
  if (def.deprecated === true || def.sunset !== undefined) {
package/dist/docs.js CHANGED
@@ -218,6 +218,7 @@ export function htmlResponse(html, opts = {}) {
218
218
  connectOrigins: opts.connectOrigins,
219
219
  scriptNonce: opts.scriptNonce,
220
220
  allowInlineStyles: opts.allowInlineStyles,
221
+ allowBlobWorkers: opts.allowBlobWorkers,
221
222
  }),
222
223
  "x-content-type-options": "nosniff",
223
224
  "referrer-policy": "no-referrer",
@@ -15,8 +15,8 @@
15
15
  * - {@link buildLinkHeader} / {@link buildPageLinks} — assemble a Web-standard
16
16
  * `Link` header, with CRLF / angle-bracket header-injection guards baked in.
17
17
  * - {@link paginationQuery} — a Standard Schema validator for the `cursor` +
18
- * `limit` query parameters that both validates at runtime (clamping `limit`
19
- * to a safe range) **and** advertises itself to the OpenAPI generator via a
18
+ * `limit` query parameters that both validates runtime page-size bounds
19
+ * **and** advertises itself to the OpenAPI generator via a
20
20
  * `toJSONSchema()` method, so `request: { query: paginationQuery() }` wires
21
21
  * the parameters into the contract with no extra code.
22
22
  *
@@ -157,12 +157,12 @@ export interface PaginationQueryOptions {
157
157
  defaultLimit?: number;
158
158
  /** Minimum accepted page size. Default: `1`. */
159
159
  minLimit?: number;
160
- /** Maximum accepted page size (also caps over-large requests). Default: `100`. */
160
+ /** Maximum accepted page size. Over-large requests are rejected. Default: `100`. */
161
161
  maxLimit?: number;
162
162
  }
163
163
  /** Validated output of {@link paginationQuery}. */
164
164
  export interface PaginationParams {
165
- /** The resolved page size, clamped to `[minLimit, maxLimit]`. */
165
+ /** The resolved page size after validation against `[minLimit, maxLimit]`. */
166
166
  limit: number;
167
167
  /** The opaque cursor, if the client supplied one. */
168
168
  cursor?: string;
@@ -180,11 +180,12 @@ export interface PaginationQuerySchema extends StandardSchemaV1<Record<string, u
180
180
  * Build a Standard Schema validator for cursor-pagination query parameters.
181
181
  *
182
182
  * Use it as a route's `request.query`. At runtime it parses and validates
183
- * `limit` (coerced from its string query value to an integer and clamped to
184
- * `[minLimit, maxLimit]`, defaulting to `defaultLimit` when absent) and passes
185
- * `cursor` through as an optional opaque string. Because it also exposes
186
- * `toJSONSchema()`, the same call wires both parameters into the generated
187
- * OpenAPI document and typed client — no duplicate parameter declarations.
183
+ * `limit` (coerced from its string query value to an integer, rejected when
184
+ * outside `[minLimit, maxLimit]`, and defaulting to `defaultLimit` when absent)
185
+ * and passes `cursor` through as an optional opaque string. Because it also
186
+ * exposes `toJSONSchema()`, the same call wires both parameters into the
187
+ * generated OpenAPI document and typed client — no duplicate parameter
188
+ * declarations.
188
189
  *
189
190
  * @example
190
191
  * ```ts
@@ -15,8 +15,8 @@
15
15
  * - {@link buildLinkHeader} / {@link buildPageLinks} — assemble a Web-standard
16
16
  * `Link` header, with CRLF / angle-bracket header-injection guards baked in.
17
17
  * - {@link paginationQuery} — a Standard Schema validator for the `cursor` +
18
- * `limit` query parameters that both validates at runtime (clamping `limit`
19
- * to a safe range) **and** advertises itself to the OpenAPI generator via a
18
+ * `limit` query parameters that both validates runtime page-size bounds
19
+ * **and** advertises itself to the OpenAPI generator via a
20
20
  * `toJSONSchema()` method, so `request: { query: paginationQuery() }` wires
21
21
  * the parameters into the contract with no extra code.
22
22
  *
@@ -190,11 +190,12 @@ export function buildPageLinks(opts) {
190
190
  * Build a Standard Schema validator for cursor-pagination query parameters.
191
191
  *
192
192
  * Use it as a route's `request.query`. At runtime it parses and validates
193
- * `limit` (coerced from its string query value to an integer and clamped to
194
- * `[minLimit, maxLimit]`, defaulting to `defaultLimit` when absent) and passes
195
- * `cursor` through as an optional opaque string. Because it also exposes
196
- * `toJSONSchema()`, the same call wires both parameters into the generated
197
- * OpenAPI document and typed client — no duplicate parameter declarations.
193
+ * `limit` (coerced from its string query value to an integer, rejected when
194
+ * outside `[minLimit, maxLimit]`, and defaulting to `defaultLimit` when absent)
195
+ * and passes `cursor` through as an optional opaque string. Because it also
196
+ * exposes `toJSONSchema()`, the same call wires both parameters into the
197
+ * generated OpenAPI document and typed client — no duplicate parameter
198
+ * declarations.
198
199
  *
199
200
  * @example
200
201
  * ```ts
@@ -14,8 +14,11 @@
14
14
  * - Same-origin paths must start with `/` and must not start with `//`
15
15
  * or `/\` (which browsers interpret as protocol-relative URLs that
16
16
  * escape your origin).
17
- * - Backslashes, control characters, and `CR`/`LF` are rejected to
18
- * stop response-splitting and homograph tricks.
17
+ * - Backslashes, encoded backslashes, control characters, and `CR`/`LF`
18
+ * are rejected to stop response-splitting and homograph tricks.
19
+ * Percent-encoded protocol-relative path prefixes such as `/%2f%2f`
20
+ * are also refused so downstream decoders cannot turn a same-origin
21
+ * `Location` into an origin-escaping redirect.
19
22
  * - Absolute URLs are only allowed when their `origin` exactly matches
20
23
  * one of the entries in `allowedOrigins`.
21
24
  * - `javascript:`, `data:`, `vbscript:`, and `file:` schemes are always
@@ -14,8 +14,11 @@
14
14
  * - Same-origin paths must start with `/` and must not start with `//`
15
15
  * or `/\` (which browsers interpret as protocol-relative URLs that
16
16
  * escape your origin).
17
- * - Backslashes, control characters, and `CR`/`LF` are rejected to
18
- * stop response-splitting and homograph tricks.
17
+ * - Backslashes, encoded backslashes, control characters, and `CR`/`LF`
18
+ * are rejected to stop response-splitting and homograph tricks.
19
+ * Percent-encoded protocol-relative path prefixes such as `/%2f%2f`
20
+ * are also refused so downstream decoders cannot turn a same-origin
21
+ * `Location` into an origin-escaping redirect.
19
22
  * - Absolute URLs are only allowed when their `origin` exactly matches
20
23
  * one of the entries in `allowedOrigins`.
21
24
  * - `javascript:`, `data:`, `vbscript:`, and `file:` schemes are always
@@ -79,6 +82,11 @@ function classify(target, allowedPaths, allowedOrigins) {
79
82
  // `/\evil.com` is interpreted by some browsers as protocol-relative too.
80
83
  if (target.startsWith("/\\"))
81
84
  return { ok: false, reason: "backslash-path" };
85
+ if (hasEncodedBackslash(target))
86
+ return { ok: false, reason: "backslash-path" };
87
+ if (hasEncodedProtocolRelativePrefix(target)) {
88
+ return { ok: false, reason: "protocol-relative" };
89
+ }
82
90
  if (target.startsWith("/")) {
83
91
  // Same-origin path. Backslashes anywhere in the path can confuse
84
92
  // user agents and proxies — refuse them outright.
@@ -113,6 +121,26 @@ function classify(target, allowedPaths, allowedOrigins) {
113
121
  }
114
122
  return { ok: true, location: parsed.toString() };
115
123
  }
124
+ function hasEncodedBackslash(value) {
125
+ return /%5c/i.test(value);
126
+ }
127
+ function hasEncodedProtocolRelativePrefix(value) {
128
+ let raw = value;
129
+ for (let i = 0; i < 3; i++) {
130
+ if (/^\/%2f/i.test(raw))
131
+ return true;
132
+ try {
133
+ const decoded = decodeURIComponent(raw);
134
+ if (decoded === raw)
135
+ return false;
136
+ raw = decoded;
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ return raw.startsWith("//");
143
+ }
116
144
  /**
117
145
  * Build a redirect `Response` after validating the target against an
118
146
  * explicit allowlist. Throws {@link OpenRedirectBlockedError} when the
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:086b2383-b351-57fb-bd98-0fbf04f2342b",
4
+ "serialNumber": "urn:uuid:6e9958fe-26f1-5084-94ea-5ff9bfb68412",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-06-22T21:07:28.471Z",
7
+ "timestamp": "2026-06-24T05:34:04.875Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.0.0-beta.2"
12
+ "version": "1.0.0-beta.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.0.0-beta.2",
22
+ "bom-ref": "pkg:npm/@daloyjs/core@1.0.0-beta.3",
23
23
  "name": "@daloyjs/core",
24
- "version": "1.0.0-beta.2",
24
+ "version": "1.0.0-beta.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.0.0-beta.2",
26
+ "purl": "pkg:npm/@daloyjs/core@1.0.0-beta.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.0.0-beta.2",
49
+ "tagId": "swidtag--daloyjs-core-1.0.0-beta.3",
50
50
  "name": "@daloyjs/core",
51
- "version": "1.0.0-beta.2",
51
+ "version": "1.0.0-beta.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.0.0-beta.2",
60
+ "ref": "pkg:npm/@daloyjs/core@1.0.0-beta.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.0.0-beta.2",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.2-086b2383-b351-57fb-bd98-0fbf04f2342b",
5
+ "name": "@daloyjs/core-1.0.0-beta.3",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.3-6e9958fe-26f1-5084-94ea-5ff9bfb68412",
7
7
  "creationInfo": {
8
- "created": "2026-06-22T21:07:28.471Z",
8
+ "created": "2026-06-24T05:34:04.875Z",
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.2",
19
+ "versionInfo": "1.0.0-beta.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.0.0-beta.2"
30
+ "referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-beta.3"
31
31
  }
32
32
  ]
33
33
  }
@@ -28,9 +28,15 @@
28
28
  *
29
29
  * For NDJSON, each yielded value is JSON-encoded and terminated with `\n`.
30
30
  */
31
- /** A single SSE event. `data` may be a string or any JSON-serializable value. */
31
+ /**
32
+ * A single SSE event or control frame.
33
+ *
34
+ * `data` may be a string or any JSON-serializable value. It is optional so
35
+ * callers can emit valid comment-only or retry-only SSE frames for keep-alive
36
+ * and reconnection control without sending an event payload.
37
+ */
32
38
  export interface SSEMessage {
33
- data: unknown;
39
+ data?: unknown;
34
40
  event?: string;
35
41
  id?: string;
36
42
  /** Reconnection delay in milliseconds. */
package/dist/types.d.ts CHANGED
@@ -327,8 +327,8 @@ export interface RouteDefinition<P extends PathString = PathString, M extends Ht
327
327
  *
328
328
  * - implicitly treats the route as {@link RouteDefinition.deprecated}
329
329
  * (the OpenAPI operation is emitted with `deprecated: true`);
330
- * - emits a `Deprecation: true` response header (RFC 8594 / the
331
- * `Deprecation` HTTP header field) on every response from the route; and
330
+ * - emits a `Deprecation: true` response header on every response from the
331
+ * route; and
332
332
  * - emits a `Sunset: <IMF-fixdate>` response header normalized to an HTTP
333
333
  * date so clients and gateways can schedule migration.
334
334
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daloyjs/core",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.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 — distributed via pnpm.",
5
5
  "type": "module",
6
6
  "publishConfig": {