@daloyjs/core 1.0.0-rc.8 → 1.0.0-rc.9

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.
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { createServer } from "node:http";
6
6
  import { Readable } from "node:stream";
7
- import { DALOY_RAW_BODY, DALOY_RAW_STREAM, DALOY_REQUEST_RAW_BODY, DALOY_LIGHT_RESPONSE_OK, DALOY_REQUEST_ABORT, } from "../app.js";
7
+ import { DALOY_RAW_BODY, DALOY_RAW_STREAM, DALOY_REQUEST_RAW_BODY, DALOY_LIGHT_RESPONSE_OK, DALOY_REQUEST_ABORT, DALOY_REQUEST_BODY_SOLICIT, } from "../app.js";
8
8
  import { BadRequestError } from "../errors.js";
9
9
  import { setClientCertificate, normalizePeerCertificate, } from "../mtls.js";
10
10
  import { setConnInfo } from "../conn-info.js";
@@ -34,15 +34,12 @@ export function serve(app, opts = {}) {
34
34
  const connectionsCheckingInterval = connectionTimeoutMs > 0
35
35
  ? Math.max(1_000, Math.min(5_000, Math.floor(connectionTimeoutMs / 2)))
36
36
  : undefined;
37
- const server = createServer({
38
- maxHeaderSize: opts.maxHeaderBytes ?? 16 * 1024,
39
- ...(connectionsCheckingInterval !== undefined ? { connectionsCheckingInterval } : {}),
40
- }, (req, res) => {
37
+ const handleRequest = (req, res, onBodyPull) => {
41
38
  // GET/HEAD: no body work, dispatch directly. Keep this first so the GET
42
39
  // hot path doesn't pay for any of the buffering bookkeeping below.
43
40
  const method = req.method;
44
41
  if (method === "GET" || method === "HEAD" || method === undefined) {
45
- dispatchToApp(app, req, res, trustProxy, undefined);
42
+ dispatchToApp(app, req, res, trustProxy, undefined, onBodyPull);
46
43
  return;
47
44
  }
48
45
  // Refuse Fetch-forbidden methods (CONNECT/TRACE/TRACK) before building a
@@ -61,10 +58,60 @@ export function serve(app, opts = {}) {
61
58
  const cl = req.headers["content-length"];
62
59
  const n = cl ? Number(cl) : NaN;
63
60
  if (Number.isFinite(n) && n >= 0 && n <= bufferedBodyMaxBytes) {
64
- bufferRequestBody(req, n).then((bytes) => dispatchToApp(app, req, res, trustProxy, bytes), (e) => writeAdapterError(res, e));
61
+ bufferRequestBody(req, n).then((bytes) => dispatchToApp(app, req, res, trustProxy, bytes, onBodyPull), (e) => writeAdapterError(res, e));
65
62
  return;
66
63
  }
67
- dispatchToApp(app, req, res, trustProxy, undefined);
64
+ dispatchToApp(app, req, res, trustProxy, undefined, onBodyPull);
65
+ };
66
+ const server = createServer({
67
+ maxHeaderSize: opts.maxHeaderBytes ?? 16 * 1024,
68
+ ...(connectionsCheckingInterval !== undefined ? { connectionsCheckingInterval } : {}),
69
+ }, handleRequest);
70
+ // `Expect: 100-continue`: hold the interim response until the framework
71
+ // actually reaches for the body.
72
+ //
73
+ // Node's default is to answer `100 Continue` for anyone who asks, which
74
+ // solicits a body the framework may be about to refuse outright. A route with
75
+ // a request-body schema and a declared `Content-Length` over `bodyLimitBytes`
76
+ // is rejected by `readBodyLimited` *before* it reads a byte, so the interim
77
+ // `100` invited megabytes that could only ever be discarded — measured as
78
+ // `100` then `413` on the wire.
79
+ //
80
+ // Deferring to the first actual read makes the framework's own read decision
81
+ // the predicate, and that is what keeps this honest. An earlier attempt
82
+ // refused at header time against `bodyLimitBytes` directly, but that limit is
83
+ // only enforced where a body is *parsed*, so a route that declares no body
84
+ // schema never applies it: the same request answered `413` with `Expect` and
85
+ // `200` without it. `Expect` is a hint about when to send the body (RFC 9110
86
+ // §10.1.1), so it must never change the outcome — only when the client
87
+ // learns it. Keying off the read keeps the two paths in agreement by
88
+ // construction rather than by test coverage.
89
+ server.on("checkContinue", (req, res) => {
90
+ const cl = req.headers["content-length"];
91
+ const n = cl ? Number(cl) : NaN;
92
+ if (Number.isFinite(n) && n >= 0 && n <= bufferedBodyMaxBytes) {
93
+ // Small declared body: `handleRequest` buffers it eagerly, before dispatch,
94
+ // so there is no later read to key off. It is also under the buffer cap,
95
+ // so soliciting it immediately costs nothing worth deferring.
96
+ res.writeContinue();
97
+ handleRequest(req, res);
98
+ return;
99
+ }
100
+ // Streaming path (no `Content-Length`, or one above the buffer cap): answer
101
+ // the interim `100` the first time the framework pulls the body stream.
102
+ //
103
+ // The socket's own `resume` event is deliberately NOT the trigger. Node also
104
+ // resumes the stream on a microtask when it drains a body nobody read, which
105
+ // races the response: measured, that fired the `100` even for a request whose
106
+ // body was never wanted, and beat the `413` to the wire. The consumer's first
107
+ // `pull` is the only signal that means "the framework wants these bytes".
108
+ let sent = false;
109
+ handleRequest(req, res, () => {
110
+ if (sent || res.headersSent || res.writableEnded)
111
+ return;
112
+ sent = true;
113
+ res.writeContinue();
114
+ });
68
115
  });
69
116
  server.requestTimeout = connectionTimeoutMs;
70
117
  server.headersTimeout = connectionTimeoutMs;
@@ -148,10 +195,10 @@ export function serve(app, opts = {}) {
148
195
  * on body size remains `App.bodyLimitBytes`.
149
196
  */
150
197
  const DEFAULT_BUFFERED_BODY_MAX_BYTES = 256 * 1024;
151
- function dispatchToApp(app, req, res, trustProxy, bufferedBody) {
198
+ function dispatchToApp(app, req, res, trustProxy, bufferedBody, onBodyPull) {
152
199
  let request;
153
200
  try {
154
- request = toWebRequest(req, trustProxy, bufferedBody);
201
+ request = toWebRequest(req, trustProxy, bufferedBody, onBodyPull);
155
202
  }
156
203
  catch (e) {
157
204
  writeAdapterError(res, e);
@@ -537,7 +584,11 @@ Object.setPrototypeOf(LightRequest.prototype, Request.prototype);
537
584
  // construction for requests dispatched through this shim. Set once on the
538
585
  // prototype: zero per-request cost.
539
586
  LightRequest.prototype[DALOY_LIGHT_RESPONSE_OK] = true;
540
- function toWebRequest(req, trustProxy, bufferedBody) {
587
+ function toWebRequest(req, trustProxy, bufferedBody, onBodyPull) {
588
+ // `onBodyPull` is attached to the finished Request below rather than wrapping
589
+ // the body stream: undici pulls a streaming body during `new Request(...)`,
590
+ // so a stream-level hook fired at construction — before the framework had
591
+ // decided anything — and re-solicited bodies it went on to refuse.
541
592
  const reqHeaders = req.headers;
542
593
  const forwardedHost = trustProxy ? firstHeader(reqHeaders["x-forwarded-host"]) : undefined;
543
594
  const host = forwardedHost ?? reqHeaders.host ?? "localhost";
@@ -588,12 +639,16 @@ function toWebRequest(req, trustProxy, bufferedBody) {
588
639
  req2[DALOY_REQUEST_RAW_BODY] = bufferedBody;
589
640
  return req2;
590
641
  }
591
- return new Request(url, {
642
+ const streamed = new Request(url, {
592
643
  method,
593
644
  headers,
594
645
  body: Readable.toWeb(req),
595
646
  duplex: "half",
596
647
  });
648
+ if (onBodyPull !== undefined) {
649
+ streamed[DALOY_REQUEST_BODY_SOLICIT] = onBodyPull;
650
+ }
651
+ return streamed;
597
652
  }
598
653
  function firstHeader(v) {
599
654
  if (v === undefined)
package/dist/app.d.ts CHANGED
@@ -793,6 +793,31 @@ export declare const DALOY_REQUEST_RAW_BODY: unique symbol;
793
793
  * adapters can opt in; not part of the userland API surface.
794
794
  */
795
795
  export declare const DALOY_REQUEST_ABORT: unique symbol;
796
+ /**
797
+ * Internal Symbol an adapter may set to a callback that the framework invokes
798
+ * immediately before it reads the request body — and only once it has decided
799
+ * the body is both wanted and within {@link AppOptions.bodyLimitBytes}.
800
+ *
801
+ * It exists for `Expect: 100-continue`. Node answers the interim `100` to
802
+ * anyone who asks, which solicits a body the framework may be about to refuse:
803
+ * a route with a body schema and a declared `Content-Length` over the limit is
804
+ * rejected before a byte is read, so the `100` invited megabytes that could
805
+ * only be discarded. The adapter defers its `writeContinue()` into this hook so
806
+ * the invitation tracks the framework's own decision.
807
+ *
808
+ * The framework's read decision has to be the trigger, because it is the only
809
+ * thing that makes the outcome independent of the `Expect` header. `Expect` is
810
+ * a hint about *when* to send the body (RFC 9110 §10.1.1); refusing at header
811
+ * time against `bodyLimitBytes` instead looks equivalent but is not, since that
812
+ * limit is only applied where a body is parsed — a route declaring no body
813
+ * schema never applies it, so the same request answered `413` with `Expect` and
814
+ * `200` without.
815
+ *
816
+ * Adapter-facing only; userland code should not depend on it.
817
+ *
818
+ * @since 1.0.0-rc.9
819
+ */
820
+ export declare const DALOY_REQUEST_BODY_SOLICIT: unique symbol;
796
821
  /**
797
822
  * Internal Symbol set by handlers/serializers to attach a raw stream
798
823
  * (Node `Readable` or Web `ReadableStream`) to a `Response`. The Node
package/dist/app.js CHANGED
@@ -136,6 +136,8 @@ const MCP_ROUTE_MARKER = Symbol.for("daloyjs.mcp.route");
136
136
  */
137
137
  const RESPONSE_CACHE_HOOK_MARKER = Symbol.for("daloyjs.response-cache.hook");
138
138
  const TENANCY_HOOK_MARKER = Symbol.for("daloyjs.tenancy.hook");
139
+ const IDEMPOTENCY_HOOK_MARKER = Symbol.for("daloyjs.idempotency.hook");
140
+ const EARLY_REJECTION_MARKER = Symbol.for("daloyjs.middleware.earlyRejectionHooks");
139
141
  /**
140
142
  * Apply a topology-aware security preset on top of caller-supplied
141
143
  * options. Returns a new options object where preset defaults fill in
@@ -222,6 +224,31 @@ export const DALOY_REQUEST_RAW_BODY = Symbol.for("daloyjs.request.rawBody");
222
224
  * adapters can opt in; not part of the userland API surface.
223
225
  */
224
226
  export const DALOY_REQUEST_ABORT = Symbol.for("daloyjs.request.abort");
227
+ /**
228
+ * Internal Symbol an adapter may set to a callback that the framework invokes
229
+ * immediately before it reads the request body — and only once it has decided
230
+ * the body is both wanted and within {@link AppOptions.bodyLimitBytes}.
231
+ *
232
+ * It exists for `Expect: 100-continue`. Node answers the interim `100` to
233
+ * anyone who asks, which solicits a body the framework may be about to refuse:
234
+ * a route with a body schema and a declared `Content-Length` over the limit is
235
+ * rejected before a byte is read, so the `100` invited megabytes that could
236
+ * only be discarded. The adapter defers its `writeContinue()` into this hook so
237
+ * the invitation tracks the framework's own decision.
238
+ *
239
+ * The framework's read decision has to be the trigger, because it is the only
240
+ * thing that makes the outcome independent of the `Expect` header. `Expect` is
241
+ * a hint about *when* to send the body (RFC 9110 §10.1.1); refusing at header
242
+ * time against `bodyLimitBytes` instead looks equivalent but is not, since that
243
+ * limit is only applied where a body is parsed — a route declaring no body
244
+ * schema never applies it, so the same request answered `413` with `Expect` and
245
+ * `200` without.
246
+ *
247
+ * Adapter-facing only; userland code should not depend on it.
248
+ *
249
+ * @since 1.0.0-rc.9
250
+ */
251
+ export const DALOY_REQUEST_BODY_SOLICIT = Symbol.for("daloyjs.request.bodySolicit");
225
252
  /**
226
253
  * Internal Symbol set by handlers/serializers to attach a raw stream
227
254
  * (Node `Readable` or Web `ReadableStream`) to a `Response`. The Node
@@ -1028,6 +1055,29 @@ export class App {
1028
1055
  this.bootGuard.error = err;
1029
1056
  throw err;
1030
1057
  }
1058
+ // Guard 3b: a stored-response layer mounted ahead of a request budget.
1059
+ // `responseCache()` and `idempotency()` both answer from `beforeHandle` and
1060
+ // end the hook chain, and `rateLimit()` / `loginThrottle()` enforce from that
1061
+ // same phase — so a limiter mounted behind either one never counts the
1062
+ // requests it serves. Measured: `rateLimit({ max: 2 })` behind a cache or a
1063
+ // replay admitted six of six. The budget silently becomes infinite for
1064
+ // exactly the traffic that repeats most, which is what the limit was written
1065
+ // for. Same shape as the responseCache-ahead-of-gates finding, and the reason
1066
+ // the five network-identity gates moved to `preBody`; `rateLimit` cannot
1067
+ // follow them there because its `keyGenerator` is caller-supplied and may
1068
+ // read `ctx.state`, so the unsafe order is refused instead.
1069
+ const replayBeforeBudget = this.routeSecurityMarkers.find((r) => r.replayBeforeBudget !== null);
1070
+ if (replayBeforeBudget !== undefined && this.bootGuard.error === undefined) {
1071
+ this.bootGuard.error = new Error(`Route ${replayBeforeBudget.method} ${replayBeforeBudget.path} runs ` +
1072
+ `${replayBeforeBudget.replayBeforeBudget} before rateLimit() / loginThrottle() in its ` +
1073
+ `effective hook chain. Both act from beforeHandle, so a cache hit or an idempotent ` +
1074
+ `replay returns a response and ends the chain before the limiter counts the request — ` +
1075
+ `the declared budget is never spent on repeat traffic and is effectively unlimited. ` +
1076
+ `Register rateLimit() first — as a global hook (new App({ hooks: rateLimit(...) })) or ` +
1077
+ `an earlier app.use(...) — so every request is counted before a stored response can ` +
1078
+ `short-circuit it. See https://daloyjs.dev/docs/security/boot-guards.`);
1079
+ throw this.bootGuard.error;
1080
+ }
1031
1081
  // Guard 4: session() + state-changing route without csrf().
1032
1082
  if (this.options.csrf === "off")
1033
1083
  return;
@@ -3185,6 +3235,12 @@ function securityMarkersFromHooks(layers) {
3185
3235
  // to tell whether the cache reads state before tenancy has written it.
3186
3236
  let cacheIndex = -1;
3187
3237
  let tenancyIndex = -1;
3238
+ // First stored-response layer of either kind, and the first request-budget
3239
+ // layer. Only the earliest of each matters: if any replay precedes any budget
3240
+ // hook, that budget is preemptable.
3241
+ let replayIndex = -1;
3242
+ let replayName = "";
3243
+ let budgetIndex = -1;
3188
3244
  for (let i = 0; i < layers.length; i++) {
3189
3245
  const record = layers[i];
3190
3246
  if (record[SESSION_HOOK_MARKER] === true)
@@ -3197,12 +3253,25 @@ function securityMarkersFromHooks(layers) {
3197
3253
  cacheIndex = i;
3198
3254
  if (tenancyIndex === -1 && record[TENANCY_HOOK_MARKER] === true)
3199
3255
  tenancyIndex = i;
3256
+ if (replayIndex === -1) {
3257
+ if (record[RESPONSE_CACHE_HOOK_MARKER] === true) {
3258
+ replayIndex = i;
3259
+ replayName = "responseCache()";
3260
+ }
3261
+ else if (record[IDEMPOTENCY_HOOK_MARKER] === true) {
3262
+ replayIndex = i;
3263
+ replayName = "idempotency()";
3264
+ }
3265
+ }
3266
+ if (budgetIndex === -1 && Array.isArray(record[EARLY_REJECTION_MARKER]))
3267
+ budgetIndex = i;
3200
3268
  }
3201
3269
  return {
3202
3270
  hasSession,
3203
3271
  hasCsrf,
3204
3272
  hasAuth,
3205
3273
  cacheBeforeTenancy: cacheIndex !== -1 && tenancyIndex !== -1 && cacheIndex < tenancyIndex,
3274
+ replayBeforeBudget: replayIndex !== -1 && budgetIndex !== -1 && replayIndex < budgetIndex ? replayName : null,
3206
3275
  };
3207
3276
  }
3208
3277
  function isStateChangingMethod(method) {
@@ -3718,6 +3787,21 @@ function validateContext(ctx, def, opts) {
3718
3787
  if (!allowed.some((a) => ct.includes(a))) {
3719
3788
  throw new UnsupportedMediaTypeError(ct || "(none)", allowed);
3720
3789
  }
3790
+ // Refuse an over-limit *declared* length here, before soliciting the body.
3791
+ // `readBodyLimited` already makes the identical check on the identical
3792
+ // boundary, so this changes no outcome — it is load-bearing purely for
3793
+ // ordering, so that an adapter deferring `Expect: 100-continue` (see
3794
+ // {@link DALOY_REQUEST_BODY_SOLICIT}) never invites bytes this request was
3795
+ // always going to be refused for. Kept below the content-type check so a
3796
+ // wrong media type still answers `415` rather than `413`, as before.
3797
+ const declared = request.headers.get("content-length");
3798
+ if (declared !== null) {
3799
+ const declaredBytes = Number(declared);
3800
+ if (Number.isFinite(declaredBytes) && declaredBytes > opts.bodyLimitBytes) {
3801
+ throw new PayloadTooLargeError(opts.bodyLimitBytes);
3802
+ }
3803
+ }
3804
+ request[DALOY_REQUEST_BODY_SOLICIT]?.();
3721
3805
  const raw = readBody(request, ct, opts.bodyLimitBytes, opts.multipart, opts.jsonMaxKeys, opts.jsonMaxDepth);
3722
3806
  if (isPromiseLike(raw))
3723
3807
  return raw.then(validateBodyAndFinish);
@@ -246,6 +246,19 @@ export declare class MemoryIdempotencyStore implements IdempotencyStore {
246
246
  /** Test helper. Number of stored records (including expired). */
247
247
  size(): number;
248
248
  }
249
+ /**
250
+ * Internal Symbol stamped on the hooks {@link idempotency} returns, so `App`'s
251
+ * boot guards can see where a stored-response replay sits in a route's effective
252
+ * hook chain.
253
+ *
254
+ * A replay is returned from `beforeHandle` and ends the hook chain, which means
255
+ * anything enforcing from the *same* phase but mounted later never runs. That is
256
+ * how a `rateLimit()` behind a replay stops counting. The marker lets the boot
257
+ * path refuse that order rather than leave the limiter silently infinite.
258
+ *
259
+ * @internal
260
+ */
261
+ export declare const IDEMPOTENCY_HOOK_MARKER: unique symbol;
249
262
  /**
250
263
  * Idempotency-key middleware. Mount it ahead of the routes that need
251
264
  * exactly-once semantics under retries (typically the payment / write
@@ -294,6 +294,19 @@ function buildReplayResponse(stored, replayHeaderName) {
294
294
  return markSchemaValidatedResponse(new Response(body, { status: stored.status, headers }));
295
295
  }
296
296
  // ---------- Middleware ----------
297
+ /**
298
+ * Internal Symbol stamped on the hooks {@link idempotency} returns, so `App`'s
299
+ * boot guards can see where a stored-response replay sits in a route's effective
300
+ * hook chain.
301
+ *
302
+ * A replay is returned from `beforeHandle` and ends the hook chain, which means
303
+ * anything enforcing from the *same* phase but mounted later never runs. That is
304
+ * how a `rateLimit()` behind a replay stops counting. The marker lets the boot
305
+ * path refuse that order rather than leave the limiter silently infinite.
306
+ *
307
+ * @internal
308
+ */
309
+ export const IDEMPOTENCY_HOOK_MARKER = Symbol.for("daloyjs.idempotency.hook");
297
310
  /**
298
311
  * Idempotency-key middleware. Mount it ahead of the routes that need
299
312
  * exactly-once semantics under retries (typically the payment / write
@@ -361,7 +374,7 @@ export function idempotency(opts = {}) {
361
374
  store = new MemoryIdempotencyStore();
362
375
  }
363
376
  const keyPrefix = opts.groupId ? `${opts.groupId}:` : "";
364
- return {
377
+ const hooks = {
365
378
  async beforeHandle(ctx) {
366
379
  const method = ctx.request.method.toUpperCase();
367
380
  if (!methods.has(method))
@@ -483,6 +496,8 @@ export function idempotency(opts = {}) {
483
496
  return undefined;
484
497
  },
485
498
  };
499
+ hooks[IDEMPOTENCY_HOOK_MARKER] = true;
500
+ return hooks;
486
501
  }
487
502
  function isPromiseLike(value) {
488
503
  return (value !== null &&
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:91539bfb-7f26-5f45-8628-f7951991c7ca",
4
+ "serialNumber": "urn:uuid:85b1efae-7e9c-5b6d-8081-af5e65efa14a",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-07-30T16:25:43.763Z",
7
+ "timestamp": "2026-08-01T11:35:20.075Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.0.0-rc.8"
12
+ "version": "1.0.0-rc.9"
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-rc.8",
22
+ "bom-ref": "pkg:npm/@daloyjs/core@1.0.0-rc.9",
23
23
  "name": "@daloyjs/core",
24
- "version": "1.0.0-rc.8",
24
+ "version": "1.0.0-rc.9",
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-rc.8",
26
+ "purl": "pkg:npm/@daloyjs/core@1.0.0-rc.9",
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-rc.8",
49
+ "tagId": "swidtag--daloyjs-core-1.0.0-rc.9",
50
50
  "name": "@daloyjs/core",
51
- "version": "1.0.0-rc.8",
51
+ "version": "1.0.0-rc.9",
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-rc.8",
60
+ "ref": "pkg:npm/@daloyjs/core@1.0.0-rc.9",
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-rc.8",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-rc.8-91539bfb-7f26-5f45-8628-f7951991c7ca",
5
+ "name": "@daloyjs/core-1.0.0-rc.9",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-rc.9-85b1efae-7e9c-5b6d-8081-af5e65efa14a",
7
7
  "creationInfo": {
8
- "created": "2026-07-30T16:25:43.763Z",
8
+ "created": "2026-08-01T11:35:20.075Z",
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-rc.8",
19
+ "versionInfo": "1.0.0-rc.9",
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-rc.8"
30
+ "referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-rc.9"
31
31
  }
32
32
  ]
33
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daloyjs/core",
3
- "version": "1.0.0-rc.8",
3
+ "version": "1.0.0-rc.9",
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": {