@daloyjs/core 1.0.0-beta.3 → 1.0.0-beta.4

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
@@ -55,7 +55,7 @@ DaloyJS exists to be the framework you'd build if you took the best ideas from e
55
55
  | **Contract-first typed client, no codegen** | [ts-rest](https://ts-rest.com/) | Your route definition *is* the contract: an in-process typed client with zero codegen, plus OpenAPI 3.1 + a Hey API SDK for consumers that can't import your types. |
56
56
  | Opinionated **DI / module architecture** for large teams | [NestJS](https://docs.nestjs.com/) | Plugin encapsulation, `register()` prefixes, and `defineDependency()` typed-DI with per-request dedup — no decorators. |
57
57
  | Minimalist **async middleware cascade** | [Koa](https://koajs.com/) | Koa-style `Context` on a web-standard core, with validation, OpenAPI, errors, and security headers in-box. |
58
- | **Services + real-time** API framework | [FeathersJS](https://feathersjs.com/) | First-party `app.ws()` with CSWSH refuse-to-boot guards, plus SSE / NDJSON streaming over explicit OpenAPI routes. |
58
+ | **Services + real-time** API framework | [FeathersJS](https://feathersjs.com/) | First-party `app.ws()` with CSWSH refuse-to-boot guards, plus SSE / NDJSON streaming and raw `Response` passthrough (return a Vercel AI SDK stream straight from a handler) over explicit OpenAPI routes. |
59
59
  | Battle-tested **Node middleware compatibility** | [Express v5](https://expressjs.com/en/blog/2024-10-15-v5-release) | Regex-free trie router, schema-validated routes, RFC 9457 problem+json, and refuse-to-boot guards on every runtime. |
60
60
  | **Portable supply-chain hardening** for the apps you build | [pnpm](https://pnpm.io/motivation) defaults + a zero-runtime-dep core | Hardened `.npmrc`, source-verified lockfiles, zero runtime deps, CycloneDX + SPDX SBOM, and npm provenance attestations. |
61
61
 
@@ -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.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.
510
+ DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.4`). 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
 
package/dist/app.js CHANGED
@@ -623,6 +623,42 @@ export class App {
623
623
  * a misconfigured surface.
624
624
  */
625
625
  assertSecureHookConfig(hooks) {
626
+ // Always-on correctness guard (independent of secureDefaults / environment).
627
+ // A hook bundle must be a single Hooks object. Passing an ARRAY — or any
628
+ // object carrying none of the recognized hook keys — is a silent no-op: the
629
+ // framework reads `.beforeHandle` / `.onSend` / ... off it, finds
630
+ // `undefined`, and applies NOTHING. A route that looks guarded
631
+ // (`hooks: [ipRestriction(...), bearerAuth(...)]`) would then ship wide open.
632
+ // TypeScript already rejects an array literal here; this catches JS callers,
633
+ // spreads, and `as`-casts, where the silent runtime skip is the dangerous part.
634
+ if (hooks !== null && typeof hooks === "object") {
635
+ const HOOK_KEYS = [
636
+ "onRequest",
637
+ "beforeHandle",
638
+ "afterHandle",
639
+ "onError",
640
+ "onSend",
641
+ "onResponse",
642
+ ];
643
+ const carriesAHook = HOOK_KEYS.some((k) => typeof hooks[k] === "function");
644
+ if (!carriesAHook) {
645
+ if (Array.isArray(hooks)) {
646
+ throw new Error("Hooks must be a single Hooks object, not an array. To run multiple " +
647
+ "hook bundles (e.g. ipRestriction + bearerAuth) on one route, compose " +
648
+ "them with every(...) (all must pass) or some(...) (any may pass) from " +
649
+ "@daloyjs/core. Passing an array silently applies NO hooks, leaving the " +
650
+ "route unguarded.");
651
+ }
652
+ if (Object.keys(hooks).length > 0) {
653
+ throw new Error("Hooks object carries none of the recognized hook keys (onRequest, " +
654
+ "beforeHandle, afterHandle, onError, onSend, onResponse), so it would " +
655
+ "silently apply no hooks. To compose multiple hook bundles use " +
656
+ "every(...) / some(...) from @daloyjs/core.");
657
+ }
658
+ // An empty object `{}` carries no hook and makes no false promise of one;
659
+ // it is an explicit no-op, equivalent to omitting `hooks`, and is allowed.
660
+ }
661
+ }
626
662
  if (this.options.secureDefaults === false)
627
663
  return;
628
664
  const record = hooks;
@@ -2260,6 +2296,37 @@ export class App {
2260
2296
  if (afterReturn !== undefined)
2261
2297
  result = afterReturn;
2262
2298
  }
2299
+ // Escape hatch: a handler (or an `afterHandle` transform) may return a
2300
+ // raw web-standard `Response` — an AI SDK stream, a forwarded upstream
2301
+ // response, or any pre-built body that no response schema can describe.
2302
+ // It bypasses response-schema validation by design, but is finalized
2303
+ // through the exact same path as every other response (and as the
2304
+ // `beforeHandle` `Response` passthrough above), so no security control
2305
+ // is skipped: `ctx.set` headers (secureHeaders / CORS) are copied on, the
2306
+ // request id is added when absent, `onSend` / `onResponse` hooks run,
2307
+ // fingerprint headers are stripped, and `HEAD` yields an empty body.
2308
+ if (result instanceof Response) {
2309
+ copyContextHeaders(ctx, result);
2310
+ if (!result.headers.has("x-request-id")) {
2311
+ result.headers.set("x-request-id", requestId);
2312
+ }
2313
+ let finalizedRaw;
2314
+ if (hasFinalizeHook) {
2315
+ const fin = finalizeResponse(result, ctx, allHooks, stripFingerprint);
2316
+ finalizedRaw = isPromiseLike(fin) ? await fin : fin;
2317
+ }
2318
+ else {
2319
+ finalizedRaw = finalizeFast(result, stripFingerprint);
2320
+ }
2321
+ if (method === "HEAD") {
2322
+ return new Response(null, {
2323
+ status: finalizedRaw.status,
2324
+ statusText: finalizedRaw.statusText,
2325
+ headers: finalizedRaw.headers,
2326
+ });
2327
+ }
2328
+ return finalizedRaw;
2329
+ }
2263
2330
  const serializeResultRes = serializeResult(result, def, this.options.validateResponses ?? true);
2264
2331
  let response = isPromiseLike(serializeResultRes) ? await serializeResultRes : serializeResultRes;
2265
2332
  copyContextHeaders(ctx, response);
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:6e9958fe-26f1-5084-94ea-5ff9bfb68412",
4
+ "serialNumber": "urn:uuid:609085f8-6098-51c0-9dce-7717eb9fb590",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-06-24T05:34:04.875Z",
7
+ "timestamp": "2026-06-26T13:41:51.513Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.0.0-beta.3"
12
+ "version": "1.0.0-beta.4"
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.3",
22
+ "bom-ref": "pkg:npm/@daloyjs/core@1.0.0-beta.4",
23
23
  "name": "@daloyjs/core",
24
- "version": "1.0.0-beta.3",
24
+ "version": "1.0.0-beta.4",
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.3",
26
+ "purl": "pkg:npm/@daloyjs/core@1.0.0-beta.4",
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.3",
49
+ "tagId": "swidtag--daloyjs-core-1.0.0-beta.4",
50
50
  "name": "@daloyjs/core",
51
- "version": "1.0.0-beta.3",
51
+ "version": "1.0.0-beta.4",
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.3",
60
+ "ref": "pkg:npm/@daloyjs/core@1.0.0-beta.4",
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.3",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.3-6e9958fe-26f1-5084-94ea-5ff9bfb68412",
5
+ "name": "@daloyjs/core-1.0.0-beta.4",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.4-609085f8-6098-51c0-9dce-7717eb9fb590",
7
7
  "creationInfo": {
8
- "created": "2026-06-24T05:34:04.875Z",
8
+ "created": "2026-06-26T13:41:51.513Z",
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.3",
19
+ "versionInfo": "1.0.0-beta.4",
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.3"
30
+ "referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-beta.4"
31
31
  }
32
32
  ]
33
33
  }
package/dist/types.d.ts CHANGED
@@ -422,7 +422,32 @@ export interface RouteDefinition<P extends PathString = PathString, M extends Ht
422
422
  */
423
423
  meta?: RouteMeta;
424
424
  hooks?: Hooks;
425
- handler: (ctx: BaseContext<P, Req>) => HandlerReturn<Res> | Promise<HandlerReturn<Res>>;
425
+ /**
426
+ * The route handler. Receives the typed, validated {@link BaseContext} and
427
+ * returns either:
428
+ *
429
+ * - a structured result `{ status, body, headers? }` whose `body` is
430
+ * validated against the route's response schema and typed end-to-end into
431
+ * the OpenAPI document and generated client (the common case), or
432
+ * - a raw web-standard {@link Response} as an escape hatch for streaming,
433
+ * proxying, or pre-built bodies (for example an AI SDK
434
+ * `result.toUIMessageStreamResponse()`, or an upstream `fetch()` response
435
+ * forwarded verbatim).
436
+ *
437
+ * A returned `Response` **bypasses response-schema validation and the
438
+ * typed-client body type by design** — there is no schema that can describe
439
+ * an opaque stream. It is still finalized exactly like every other response,
440
+ * so no security control is skipped: headers set via `ctx.set` (including
441
+ * `secureHeaders()` and CORS) are copied onto it, `x-request-id` is added
442
+ * when absent, any `onSend` / `onResponse` hooks run, server-fingerprint
443
+ * headers (`server`, `x-powered-by`) are stripped, and a `HEAD` request still
444
+ * yields an empty body. This mirrors the existing `beforeHandle` `Response`
445
+ * passthrough. Prefer the structured result whenever a schema can describe
446
+ * the payload; reach for `Response` only when it genuinely cannot.
447
+ *
448
+ * @since 0.1.0
449
+ */
450
+ handler: (ctx: BaseContext<P, Req>) => HandlerReturn<Res> | Response | Promise<HandlerReturn<Res> | Response>;
426
451
  }
427
452
  /**
428
453
  * One operation inside an OpenAPI Callback Object. Mirrors a route minus
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daloyjs/core",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
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": {