@daloyjs/core 1.0.0-beta.7 → 1.0.0-rc.0
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 +1 -1
- package/dist/adapters/node.js +216 -11
- package/dist/app.d.ts +11 -0
- package/dist/app.js +347 -55
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -509,7 +509,7 @@ The core only ever sees `Request → Response`. Adapters live at the edge.
|
|
|
509
509
|
|
|
510
510
|
## Status
|
|
511
511
|
|
|
512
|
-
DaloyJS is now
|
|
512
|
+
DaloyJS is now at its **first `1.0.0` release candidate** (`1.0.0-rc.0`). The public API is frozen for the 1.0 line: only bug fixes and documentation land between RC and GA, and from `1.0.0` onward breaking changes follow SemVer with deprecations getting at least one minor cycle. The framework is already in use for production trials.
|
|
513
513
|
|
|
514
514
|
**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.
|
|
515
515
|
|
package/dist/adapters/node.js
CHANGED
|
@@ -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 } from "../app.js";
|
|
7
|
+
import { DALOY_RAW_BODY, DALOY_RAW_STREAM, DALOY_REQUEST_RAW_BODY, DALOY_LIGHT_RESPONSE_OK, } from "../app.js";
|
|
8
8
|
import { setClientCertificate, normalizePeerCertificate, } from "../mtls.js";
|
|
9
9
|
import { FrameSink, encodeFrame, encodeClosePayload, encodeSendPayload, validateUpgrade, validateSelectedSubprotocol, checkWebSocketOrigin, WS_OPCODE, WS_CLOSE_CODE, WS_READY_STATE, WS_MAX_CONTROL_PAYLOAD, WebSocketProtocolError, WebSocketPayloadTooLargeError, } from "../websocket.js";
|
|
10
10
|
/**
|
|
@@ -291,6 +291,209 @@ function writeAdapterError(res, e) {
|
|
|
291
291
|
res.destroy(e);
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Lazily-materializing stand-in for an incoming WHATWG `Request`.
|
|
296
|
+
*
|
|
297
|
+
* Constructing a real (undici) `Request` costs ~4µs for GET and far more for
|
|
298
|
+
* POST-with-body (the constructor wraps the body bytes in a WHATWG
|
|
299
|
+
* `ReadableStream` that DaloyJS then bypasses anyway via the
|
|
300
|
+
* `DALOY_REQUEST_RAW_BODY` fast path). The dispatch hot path only ever reads
|
|
301
|
+
* `url`, `method`, `headers`, `signal`, and the raw-body symbol — so this
|
|
302
|
+
* shell carries those directly and defers the real `Request` until one of the
|
|
303
|
+
* less common surfaces (`json()`, `clone()`, `body`, `formData()`, …) is
|
|
304
|
+
* actually touched.
|
|
305
|
+
*
|
|
306
|
+
* Fidelity notes:
|
|
307
|
+
* - `instanceof Request` holds (prototype chain is re-rooted onto
|
|
308
|
+
* `Request.prototype`), and every WHATWG method/getter is overridden here,
|
|
309
|
+
* so nothing hits undici's brand-checked prototype accessors.
|
|
310
|
+
* - `signal` is an inert per-instance `AbortSignal`. This matches the real
|
|
311
|
+
* adapter behaviour today: the Node adapter never wires socket aborts into
|
|
312
|
+
* the request signal, so the signal never fires in either implementation.
|
|
313
|
+
* - Passing this object directly to `fetch()` is not supported (undici
|
|
314
|
+
* brand-checks its input) — forward with `request.clone()` instead, which
|
|
315
|
+
* returns a real `Request`. This mirrors @hono/node-server's shim.
|
|
316
|
+
*
|
|
317
|
+
* Security parity: the `Headers` instance is built eagerly from `rawHeaders`
|
|
318
|
+
* exactly as before, so the duplicate-singleton / reserved-header /
|
|
319
|
+
* header-count guards in `App.dispatch` see the identical view they saw with
|
|
320
|
+
* a real `Request`.
|
|
321
|
+
*/
|
|
322
|
+
/** Shared decoder for LightRequest's direct body reads. */
|
|
323
|
+
const LIGHT_TEXT_DECODER = new TextDecoder();
|
|
324
|
+
class LightRequest {
|
|
325
|
+
#url;
|
|
326
|
+
#method;
|
|
327
|
+
#headers;
|
|
328
|
+
#bodyBytes;
|
|
329
|
+
#real;
|
|
330
|
+
#signal;
|
|
331
|
+
constructor(url, method, headers, bodyBytes) {
|
|
332
|
+
this.#url = url;
|
|
333
|
+
this.#method = method;
|
|
334
|
+
this.#headers = headers;
|
|
335
|
+
this.#bodyBytes = bodyBytes;
|
|
336
|
+
}
|
|
337
|
+
/** Build (once) and return the real undici `Request` for rare surfaces. */
|
|
338
|
+
#materialize() {
|
|
339
|
+
return (this.#real ??=
|
|
340
|
+
this.#bodyBytes !== undefined
|
|
341
|
+
? new Request(this.#url, {
|
|
342
|
+
method: this.#method,
|
|
343
|
+
headers: this.#headers,
|
|
344
|
+
body: this.#bodyBytes,
|
|
345
|
+
})
|
|
346
|
+
: new Request(this.#url, { method: this.#method, headers: this.#headers }));
|
|
347
|
+
}
|
|
348
|
+
get url() {
|
|
349
|
+
return this.#url;
|
|
350
|
+
}
|
|
351
|
+
get method() {
|
|
352
|
+
return this.#method;
|
|
353
|
+
}
|
|
354
|
+
get headers() {
|
|
355
|
+
return this.#headers;
|
|
356
|
+
}
|
|
357
|
+
get signal() {
|
|
358
|
+
// Inert, lazily created: the Node adapter has never wired socket
|
|
359
|
+
// teardown into the request signal, so a never-firing signal is
|
|
360
|
+
// behaviourally identical to the one a real `Request` would carry.
|
|
361
|
+
return (this.#signal ??= new AbortController().signal);
|
|
362
|
+
}
|
|
363
|
+
get body() {
|
|
364
|
+
return this.#materialize().body;
|
|
365
|
+
}
|
|
366
|
+
get bodyUsed() {
|
|
367
|
+
if (this.#real !== undefined)
|
|
368
|
+
return this.#real.bodyUsed;
|
|
369
|
+
return this.#directlyConsumed;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Set when a body method served the pre-buffered bytes directly (no real
|
|
373
|
+
* `Request` ever existed). Subsequent body reads reject with a `TypeError`
|
|
374
|
+
* exactly like a consumed WHATWG body would.
|
|
375
|
+
*/
|
|
376
|
+
#directlyConsumed = false;
|
|
377
|
+
/**
|
|
378
|
+
* Serve a body read straight from the pre-buffered bytes when possible.
|
|
379
|
+
* Returns `undefined` when the caller must delegate to the materialized
|
|
380
|
+
* real `Request` (no buffered bytes, or a real `Request` already owns the
|
|
381
|
+
* body state). Enforces single-read semantics via {@link #directlyConsumed}.
|
|
382
|
+
*/
|
|
383
|
+
#consumeBytes() {
|
|
384
|
+
if (this.#real !== undefined || this.#bodyBytes === undefined)
|
|
385
|
+
return undefined;
|
|
386
|
+
if (this.#directlyConsumed) {
|
|
387
|
+
throw new TypeError("Body is unusable: Body has already been read");
|
|
388
|
+
}
|
|
389
|
+
this.#directlyConsumed = true;
|
|
390
|
+
return this.#bodyBytes;
|
|
391
|
+
}
|
|
392
|
+
// Spec-constant getters: these are exactly the values undici assigns to a
|
|
393
|
+
// server-side `new Request(url, { method, headers, body })`, hardcoded so
|
|
394
|
+
// reading them does not force materialization.
|
|
395
|
+
get cache() {
|
|
396
|
+
return "default";
|
|
397
|
+
}
|
|
398
|
+
get credentials() {
|
|
399
|
+
return "same-origin";
|
|
400
|
+
}
|
|
401
|
+
get destination() {
|
|
402
|
+
return "";
|
|
403
|
+
}
|
|
404
|
+
get integrity() {
|
|
405
|
+
return "";
|
|
406
|
+
}
|
|
407
|
+
get keepalive() {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
get mode() {
|
|
411
|
+
return "cors";
|
|
412
|
+
}
|
|
413
|
+
get redirect() {
|
|
414
|
+
return "follow";
|
|
415
|
+
}
|
|
416
|
+
get referrer() {
|
|
417
|
+
return "about:client";
|
|
418
|
+
}
|
|
419
|
+
get referrerPolicy() {
|
|
420
|
+
return "";
|
|
421
|
+
}
|
|
422
|
+
arrayBuffer() {
|
|
423
|
+
try {
|
|
424
|
+
const bytes = this.#consumeBytes();
|
|
425
|
+
if (bytes === undefined)
|
|
426
|
+
return this.#materialize().arrayBuffer();
|
|
427
|
+
// Copy: the underlying buffer is also the framework's raw-body cache.
|
|
428
|
+
return Promise.resolve(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
return Promise.reject(e);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
blob() {
|
|
435
|
+
return this.#materialize().blob();
|
|
436
|
+
}
|
|
437
|
+
bytes() {
|
|
438
|
+
try {
|
|
439
|
+
const bytes = this.#consumeBytes();
|
|
440
|
+
if (bytes === undefined)
|
|
441
|
+
return this.#materialize().bytes();
|
|
442
|
+
return Promise.resolve(new Uint8Array(bytes));
|
|
443
|
+
}
|
|
444
|
+
catch (e) {
|
|
445
|
+
return Promise.reject(e);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
formData() {
|
|
449
|
+
return this.#materialize().formData();
|
|
450
|
+
}
|
|
451
|
+
json() {
|
|
452
|
+
try {
|
|
453
|
+
const bytes = this.#consumeBytes();
|
|
454
|
+
if (bytes === undefined)
|
|
455
|
+
return this.#materialize().json();
|
|
456
|
+
// JSON.parse (not the framework's safeJsonParse): request.json() is the
|
|
457
|
+
// raw WHATWG surface, and its error semantics (SyntaxError rejection)
|
|
458
|
+
// must match a real Request exactly. Framework-parsed bodies go through
|
|
459
|
+
// readBody()/safeJsonParse and never hit this method.
|
|
460
|
+
return Promise.resolve(JSON.parse(LIGHT_TEXT_DECODER.decode(bytes)));
|
|
461
|
+
}
|
|
462
|
+
catch (e) {
|
|
463
|
+
return Promise.reject(e);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
text() {
|
|
467
|
+
try {
|
|
468
|
+
const bytes = this.#consumeBytes();
|
|
469
|
+
if (bytes === undefined)
|
|
470
|
+
return this.#materialize().text();
|
|
471
|
+
return Promise.resolve(LIGHT_TEXT_DECODER.decode(bytes));
|
|
472
|
+
}
|
|
473
|
+
catch (e) {
|
|
474
|
+
return Promise.reject(e);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Returns a real, fully-branded `Request` clone — safe to pass to `fetch()`.
|
|
479
|
+
* Throws a `TypeError` if the body has already been read, per spec.
|
|
480
|
+
*/
|
|
481
|
+
clone() {
|
|
482
|
+
if (this.#directlyConsumed) {
|
|
483
|
+
throw new TypeError("Request body is already used");
|
|
484
|
+
}
|
|
485
|
+
return this.#materialize().clone();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// `instanceof Request` must hold for handler code and the framework's own
|
|
489
|
+
// checks. Every own getter/method above shadows the brand-checked undici
|
|
490
|
+
// accessors, so the re-rooted chain is only consulted for identity.
|
|
491
|
+
Object.setPrototypeOf(LightRequest.prototype, Request.prototype);
|
|
492
|
+
// This adapter consumes responses via status/headers/DALOY_RAW_BODY only
|
|
493
|
+
// (see sendWebResponse), so serializeResult may skip the undici Response
|
|
494
|
+
// construction for requests dispatched through this shim. Set once on the
|
|
495
|
+
// prototype: zero per-request cost.
|
|
496
|
+
LightRequest.prototype[DALOY_LIGHT_RESPONSE_OK] = true;
|
|
294
497
|
function toWebRequest(req, trustProxy, bufferedBody) {
|
|
295
498
|
const reqHeaders = req.headers;
|
|
296
499
|
const forwardedHost = trustProxy
|
|
@@ -323,18 +526,20 @@ function toWebRequest(req, trustProxy, bufferedBody) {
|
|
|
323
526
|
const headers = new Headers(headerPairs);
|
|
324
527
|
const method = req.method ?? "GET";
|
|
325
528
|
if (method === "GET" || method === "HEAD") {
|
|
326
|
-
|
|
529
|
+
// LightRequest: skips the ~4µs undici Request constructor on the GET
|
|
530
|
+
// hot path. Headers are still built eagerly above, so every header
|
|
531
|
+
// guard sees the same view as before.
|
|
532
|
+
return new LightRequest(url, method, headers, undefined);
|
|
327
533
|
}
|
|
328
534
|
if (bufferedBody !== undefined) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
// here; readBodyLimited re-checks against the caller's limit.
|
|
535
|
+
// LightRequest with the pre-buffered bytes: skips the (much more
|
|
536
|
+
// expensive) body-wrapping undici Request constructor. The bytes are
|
|
537
|
+
// stashed via DALOY_REQUEST_RAW_BODY so readBodyLimited (and any other
|
|
538
|
+
// internal body reader) skips the WHATWG ReadableStream reader loop.
|
|
539
|
+
// The adapter has already enforced BUFFERED_BODY_MAX_BYTES +
|
|
540
|
+
// Content-Length here; readBodyLimited re-checks against the caller's
|
|
541
|
+
// limit.
|
|
542
|
+
const req2 = new LightRequest(url, method, headers, bufferedBody);
|
|
338
543
|
req2[DALOY_REQUEST_RAW_BODY] = bufferedBody;
|
|
339
544
|
return req2;
|
|
340
545
|
}
|
package/dist/app.d.ts
CHANGED
|
@@ -756,6 +756,17 @@ export declare const DALOY_REQUEST_RAW_BODY: unique symbol;
|
|
|
756
756
|
* opt in; userland code should not depend on it.
|
|
757
757
|
*/
|
|
758
758
|
export declare const DALOY_RAW_STREAM: unique symbol;
|
|
759
|
+
/**
|
|
760
|
+
* Internal Symbol an adapter sets (once, on its request shim's prototype) to
|
|
761
|
+
* declare: "the object that ultimately consumes this request's `Response`
|
|
762
|
+
* only reads `status` / `headers` / {@link DALOY_RAW_BODY} — it never needs a
|
|
763
|
+
* branded WHATWG `Response`". When present on the incoming request,
|
|
764
|
+
* {@link serializeResult} may return a {@link LightResponse} and skip the
|
|
765
|
+
* ~2µs undici `Response` construction per request. Requests without the
|
|
766
|
+
* marker (Bun / Deno / Workers adapters, tests, direct `app.fetch()` callers)
|
|
767
|
+
* always get a real `Response`, so the public contract is unchanged.
|
|
768
|
+
*/
|
|
769
|
+
export declare const DALOY_LIGHT_RESPONSE_OK: unique symbol;
|
|
759
770
|
/**
|
|
760
771
|
* Contract-first HTTP application.
|
|
761
772
|
*
|
package/dist/app.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Router } from "./router.js";
|
|
2
2
|
import { WebSocketRegistry, normalizeWebSocketOptions, } from "./websocket.js";
|
|
3
3
|
import { BadRequestError, ForbiddenError, HttpError, InternalError, MethodNotAllowedError, NotFoundError, PayloadTooLargeError, RequestTimeoutError, TooManyRequestsError, UnsupportedMediaTypeError, ValidationError, } from "./errors.js";
|
|
4
|
-
import { validate } from "./schema.js";
|
|
5
4
|
import { readBodyLimited, safeJsonParse, randomId, assertNoDuplicateSingletonHeaders, assertNoReservedInternalHeaders, assertHeaderCountWithinLimit, DEFAULT_MAX_HEADER_COUNT, assertStrongSecret, timingSafeEqual, isForbiddenObjectKey, } from "./security.js";
|
|
6
5
|
import { createLogger, noopLogger } from "./logger.js";
|
|
7
6
|
import { generateOpenAPI, openapiToYAML, } from "./openapi.js";
|
|
@@ -194,6 +193,99 @@ export const DALOY_REQUEST_RAW_BODY = Symbol.for("daloyjs.request.rawBody");
|
|
|
194
193
|
* opt in; userland code should not depend on it.
|
|
195
194
|
*/
|
|
196
195
|
export const DALOY_RAW_STREAM = Symbol.for("daloyjs.response.rawStream");
|
|
196
|
+
/**
|
|
197
|
+
* Internal Symbol an adapter sets (once, on its request shim's prototype) to
|
|
198
|
+
* declare: "the object that ultimately consumes this request's `Response`
|
|
199
|
+
* only reads `status` / `headers` / {@link DALOY_RAW_BODY} — it never needs a
|
|
200
|
+
* branded WHATWG `Response`". When present on the incoming request,
|
|
201
|
+
* {@link serializeResult} may return a {@link LightResponse} and skip the
|
|
202
|
+
* ~2µs undici `Response` construction per request. Requests without the
|
|
203
|
+
* marker (Bun / Deno / Workers adapters, tests, direct `app.fetch()` callers)
|
|
204
|
+
* always get a real `Response`, so the public contract is unchanged.
|
|
205
|
+
*/
|
|
206
|
+
export const DALOY_LIGHT_RESPONSE_OK = Symbol.for("daloyjs.response.lightOk");
|
|
207
|
+
/**
|
|
208
|
+
* Minimal `Response` stand-in returned on the Node-adapter hot path (gated by
|
|
209
|
+
* {@link DALOY_LIGHT_RESPONSE_OK}). Carries `status` + a real `Headers`
|
|
210
|
+
* instance + the raw body bytes via {@link DALOY_RAW_BODY}; every other
|
|
211
|
+
* WHATWG surface (body streams, `json()`, `clone()`, …) delegates to a
|
|
212
|
+
* lazily-materialized real `Response`, so hook code that inspects the
|
|
213
|
+
* response body still behaves exactly as before — it just pays the
|
|
214
|
+
* construction cost only when it actually does so. `instanceof Response`
|
|
215
|
+
* holds via prototype re-rooting; all overridden accessors below shadow
|
|
216
|
+
* undici's brand-checked ones.
|
|
217
|
+
*/
|
|
218
|
+
class LightResponse {
|
|
219
|
+
#status;
|
|
220
|
+
#headers;
|
|
221
|
+
#rawBody;
|
|
222
|
+
#real;
|
|
223
|
+
constructor(status, headers, rawBody) {
|
|
224
|
+
this.#status = status;
|
|
225
|
+
this.#headers = headers;
|
|
226
|
+
this.#rawBody = rawBody;
|
|
227
|
+
this[DALOY_RAW_BODY] = rawBody;
|
|
228
|
+
}
|
|
229
|
+
/** Build (once) and return an equivalent real `Response` for rare surfaces. */
|
|
230
|
+
#materialize() {
|
|
231
|
+
return (this.#real ??= new Response(this.#rawBody, {
|
|
232
|
+
status: this.#status,
|
|
233
|
+
headers: this.#headers,
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
get status() {
|
|
237
|
+
return this.#status;
|
|
238
|
+
}
|
|
239
|
+
get headers() {
|
|
240
|
+
return this.#headers;
|
|
241
|
+
}
|
|
242
|
+
get ok() {
|
|
243
|
+
return this.#status >= 200 && this.#status <= 299;
|
|
244
|
+
}
|
|
245
|
+
// Spec constants for a synthesized (non-network) Response.
|
|
246
|
+
get statusText() {
|
|
247
|
+
return "";
|
|
248
|
+
}
|
|
249
|
+
get type() {
|
|
250
|
+
return "default";
|
|
251
|
+
}
|
|
252
|
+
get url() {
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
get redirected() {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
get body() {
|
|
259
|
+
return this.#materialize().body;
|
|
260
|
+
}
|
|
261
|
+
get bodyUsed() {
|
|
262
|
+
return this.#real !== undefined ? this.#real.bodyUsed : false;
|
|
263
|
+
}
|
|
264
|
+
arrayBuffer() {
|
|
265
|
+
return this.#materialize().arrayBuffer();
|
|
266
|
+
}
|
|
267
|
+
blob() {
|
|
268
|
+
return this.#materialize().blob();
|
|
269
|
+
}
|
|
270
|
+
bytes() {
|
|
271
|
+
return this.#materialize().bytes();
|
|
272
|
+
}
|
|
273
|
+
formData() {
|
|
274
|
+
return this.#materialize().formData();
|
|
275
|
+
}
|
|
276
|
+
json() {
|
|
277
|
+
return this.#materialize().json();
|
|
278
|
+
}
|
|
279
|
+
text() {
|
|
280
|
+
return this.#materialize().text();
|
|
281
|
+
}
|
|
282
|
+
clone() {
|
|
283
|
+
return this.#materialize().clone();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// `instanceof Response` must hold for hook code and adapter checks. Every
|
|
287
|
+
// own getter/method above shadows undici's brand-checked accessors.
|
|
288
|
+
Object.setPrototypeOf(LightResponse.prototype, Response.prototype);
|
|
197
289
|
/**
|
|
198
290
|
* The DaloyJS application: a contract-first router plus a web-standard
|
|
199
291
|
* `fetch(Request): Promise<Response>` handler that runs unchanged on Node,
|
|
@@ -614,6 +706,26 @@ export class App {
|
|
|
614
706
|
const origin = request.headers.get("origin");
|
|
615
707
|
if (!origin || origin === "null")
|
|
616
708
|
return;
|
|
709
|
+
// Fast path: when both the Origin header and the request URL are in the
|
|
710
|
+
// trivially-normalized shape (lowercase ASCII scheme://host[:port] with
|
|
711
|
+
// no userinfo / percent-escapes / IPv6 brackets), their origins can be
|
|
712
|
+
// compared as plain strings without two `new URL()` constructions per
|
|
713
|
+
// request. Anything unusual returns `undefined` and falls back to the
|
|
714
|
+
// exact WHATWG comparison below — the guard's accept/reject semantics
|
|
715
|
+
// are identical on both paths.
|
|
716
|
+
const fastHeaderOrigin = getOriginFast(origin);
|
|
717
|
+
if (fastHeaderOrigin !== undefined) {
|
|
718
|
+
const fastReqOrigin = typeof requestUrl === "string" ? getOriginFast(requestUrl) : requestUrl.origin;
|
|
719
|
+
if (fastReqOrigin !== undefined) {
|
|
720
|
+
if (fastHeaderOrigin === fastReqOrigin)
|
|
721
|
+
return;
|
|
722
|
+
if (corsOriginAllows.some((allows) => allows(origin)))
|
|
723
|
+
return;
|
|
724
|
+
throw new ForbiddenError(`Cross-origin ${method} from "${fastHeaderOrigin}" rejected: no registered cors() policy allows that origin. ` +
|
|
725
|
+
`Register cors({ origin: [...] }) via app.use(...) to allow it, or pass ` +
|
|
726
|
+
`app({ corsCrossOriginGuard: false }) / app({ secureDefaults: false }) to disable this guard.`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
617
729
|
let originUrl;
|
|
618
730
|
try {
|
|
619
731
|
originUrl = new URL(origin);
|
|
@@ -2287,7 +2399,11 @@ export class App {
|
|
|
2287
2399
|
if (isPromiseLike(routeOnRequestResult))
|
|
2288
2400
|
await routeOnRequestResult;
|
|
2289
2401
|
}
|
|
2290
|
-
|
|
2402
|
+
// buildContext is sync unless a schema validator or body read actually
|
|
2403
|
+
// suspends — branch on the promise so the fully-sync case never
|
|
2404
|
+
// schedules a microtask.
|
|
2405
|
+
const builtCtx = buildContext(request, getUrl, match.params, def, this.options);
|
|
2406
|
+
ctx = isPromiseLike(builtCtx) ? await builtCtx : builtCtx;
|
|
2291
2407
|
// Stable two-field write keeps `ctx.state`'s hidden class consistent across
|
|
2292
2408
|
// requests for the common no-decorator case. The decorations spread only
|
|
2293
2409
|
// fires when `app.decorate()` was actually called.
|
|
@@ -2358,7 +2474,11 @@ export class App {
|
|
|
2358
2474
|
}
|
|
2359
2475
|
return finalizedRaw;
|
|
2360
2476
|
}
|
|
2361
|
-
const serializeResultRes = serializeResult(result, def, this.options.validateResponses ?? true
|
|
2477
|
+
const serializeResultRes = serializeResult(result, def, this.options.validateResponses ?? true,
|
|
2478
|
+
// Adapter shims set this marker on their request prototype to declare
|
|
2479
|
+
// that the response consumer only reads status/headers/raw-body — see
|
|
2480
|
+
// DALOY_LIGHT_RESPONSE_OK. Everyone else gets a real Response.
|
|
2481
|
+
request[DALOY_LIGHT_RESPONSE_OK] === true);
|
|
2362
2482
|
let response = isPromiseLike(serializeResultRes)
|
|
2363
2483
|
? await serializeResultRes
|
|
2364
2484
|
: serializeResultRes;
|
|
@@ -2807,6 +2927,85 @@ function getPathnameFast(url) {
|
|
|
2807
2927
|
end = h;
|
|
2808
2928
|
return url.slice(pathStart, end);
|
|
2809
2929
|
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Extract the WHATWG origin (`scheme://host[:port]`) from an absolute
|
|
2932
|
+
* `http`/`https` URL or Origin-header value without constructing a `URL`.
|
|
2933
|
+
* Companion to {@link getPathnameFast}, used by the cross-origin guard on
|
|
2934
|
+
* state-changing requests.
|
|
2935
|
+
*
|
|
2936
|
+
* Returns `undefined` — signalling "fall back to `new URL(...).origin`" —
|
|
2937
|
+
* whenever the input is not in the trivially-normalized shape a `URL` parse
|
|
2938
|
+
* would return unchanged: non-`http(s)` schemes, uppercase characters
|
|
2939
|
+
* (scheme/host case-folding), userinfo (`user@host`, which `URL.origin`
|
|
2940
|
+
* strips), percent-escapes, non-ASCII hosts (IDNA/punycode), IPv6 literals
|
|
2941
|
+
* (zero-compression normalization), empty hosts, backslashes (URL treats
|
|
2942
|
+
* `\` as `/`), and explicit default ports (`:80` / `:443`, which
|
|
2943
|
+
* `URL.origin` elides). The fast path therefore never *disagrees* with the
|
|
2944
|
+
* WHATWG origin — it only answers when the answer is unambiguous.
|
|
2945
|
+
*/
|
|
2946
|
+
function getOriginFast(url) {
|
|
2947
|
+
let hostStart;
|
|
2948
|
+
if (url.startsWith("http://"))
|
|
2949
|
+
hostStart = 7;
|
|
2950
|
+
else if (url.startsWith("https://"))
|
|
2951
|
+
hostStart = 8;
|
|
2952
|
+
else
|
|
2953
|
+
return undefined;
|
|
2954
|
+
// Find the end of the authority: first "/", "?", or "#" after the scheme.
|
|
2955
|
+
let end = url.length;
|
|
2956
|
+
for (let i = hostStart; i < url.length; i++) {
|
|
2957
|
+
const c = url.charCodeAt(i);
|
|
2958
|
+
if (c === 47 /* / */ || c === 63 /* ? */ || c === 35 /* # */) {
|
|
2959
|
+
end = i;
|
|
2960
|
+
break;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
if (end === hostStart)
|
|
2964
|
+
return undefined; // empty host
|
|
2965
|
+
for (let i = hostStart; i < end; i++) {
|
|
2966
|
+
const c = url.charCodeAt(i);
|
|
2967
|
+
// Reject anything that could normalize differently under a real URL
|
|
2968
|
+
// parse: uppercase A-Z, userinfo "@", percent "%", IPv6 "[", backslash
|
|
2969
|
+
// "\", raw whitespace/controls, and all non-ASCII.
|
|
2970
|
+
if ((c >= 65 && c <= 90) /* A-Z */ ||
|
|
2971
|
+
c === 64 /* @ */ ||
|
|
2972
|
+
c === 37 /* % */ ||
|
|
2973
|
+
c === 91 /* [ */ ||
|
|
2974
|
+
c === 92 /* \ */ ||
|
|
2975
|
+
c <= 32 /* controls + space */ ||
|
|
2976
|
+
c >= 127 /* DEL + non-ASCII */) {
|
|
2977
|
+
return undefined;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
const authority = url.slice(hostStart, end);
|
|
2981
|
+
const colon = authority.indexOf(":");
|
|
2982
|
+
if (colon !== -1) {
|
|
2983
|
+
const port = authority.slice(colon + 1);
|
|
2984
|
+
// Trailing ":" alone, an empty port, or a default port all normalize to
|
|
2985
|
+
// no port under URL — fall back rather than replicate that here. A
|
|
2986
|
+
// second ":" (malformed / IPv6-ish) also falls back.
|
|
2987
|
+
if (port.length === 0 || port.indexOf(":") !== -1)
|
|
2988
|
+
return undefined;
|
|
2989
|
+
if ((hostStart === 7 && port === "80") /* http default */ ||
|
|
2990
|
+
(hostStart === 8 && port === "443") /* https default */) {
|
|
2991
|
+
return undefined;
|
|
2992
|
+
}
|
|
2993
|
+
// Port must be all digits; anything else is not trivially normalized.
|
|
2994
|
+
for (let i = 0; i < port.length; i++) {
|
|
2995
|
+
const c = port.charCodeAt(i);
|
|
2996
|
+
if (c < 48 || c > 57)
|
|
2997
|
+
return undefined;
|
|
2998
|
+
}
|
|
2999
|
+
// Leading zeros normalize away under URL ("0080" -> "80"), and ports
|
|
3000
|
+
// above 65535 make the URL constructor *throw* (the guard's malformed-
|
|
3001
|
+
// origin rejection) — both must take the exact WHATWG path.
|
|
3002
|
+
if (port.length > 1 && port.charCodeAt(0) === 48 /* 0 */)
|
|
3003
|
+
return undefined;
|
|
3004
|
+
if (port.length > 5 || (port.length === 5 && Number(port) > 65535))
|
|
3005
|
+
return undefined;
|
|
3006
|
+
}
|
|
3007
|
+
return url.slice(0, end);
|
|
3008
|
+
}
|
|
2810
3009
|
function mergeHooks(layers) {
|
|
2811
3010
|
const pick = (key) => layers.map((h) => h[key]).filter((f) => typeof f === "function");
|
|
2812
3011
|
const requiredScopes = requiredScopesFromHooks(layers);
|
|
@@ -3183,44 +3382,77 @@ function buildContext(request, getUrl, rawParams, def, opts) {
|
|
|
3183
3382
|
if (!hasSchema) {
|
|
3184
3383
|
return finishContext();
|
|
3185
3384
|
}
|
|
3186
|
-
|
|
3187
|
-
if (
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3385
|
+
const applyChecked = (r, part) => {
|
|
3386
|
+
if (r.issues)
|
|
3387
|
+
throw new ValidationError(part, toIssues(r.issues));
|
|
3388
|
+
return r.value;
|
|
3389
|
+
};
|
|
3390
|
+
const validateBodyAndFinish = (raw) => {
|
|
3391
|
+
const r = def.request.body["~standard"].validate(raw);
|
|
3392
|
+
if (isPromiseLike(r)) {
|
|
3393
|
+
return r.then((resolved) => {
|
|
3394
|
+
body = applyChecked(resolved, "body");
|
|
3395
|
+
return finishContext();
|
|
3396
|
+
});
|
|
3192
3397
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3398
|
+
body = applyChecked(r, "body");
|
|
3399
|
+
return finishContext();
|
|
3400
|
+
};
|
|
3401
|
+
const stepBody = () => {
|
|
3402
|
+
if (!def.request?.body)
|
|
3403
|
+
return finishContext();
|
|
3404
|
+
const ct = (request.headers.get("content-type") ?? "").toLowerCase();
|
|
3405
|
+
const allowed = def.accepts ??
|
|
3406
|
+
opts.allowedContentTypes ?? [
|
|
3407
|
+
"application/json",
|
|
3408
|
+
"application/x-www-form-urlencoded",
|
|
3409
|
+
"multipart/form-data",
|
|
3410
|
+
];
|
|
3411
|
+
if (!allowed.some((a) => ct.includes(a))) {
|
|
3412
|
+
throw new UnsupportedMediaTypeError(ct || "(none)", allowed);
|
|
3198
3413
|
}
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
if (
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
"
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
if (!allowed.some((a) => ct.includes(a))) {
|
|
3214
|
-
throw new UnsupportedMediaTypeError(ct || "(none)", allowed);
|
|
3215
|
-
}
|
|
3216
|
-
const raw = await readBody(request, ct, opts.bodyLimitBytes, opts.multipart);
|
|
3217
|
-
const r = await validate(def.request.body, raw);
|
|
3218
|
-
if (r.issues)
|
|
3219
|
-
throw new ValidationError("body", toIssues(r.issues));
|
|
3220
|
-
body = r.value;
|
|
3414
|
+
const raw = readBody(request, ct, opts.bodyLimitBytes, opts.multipart);
|
|
3415
|
+
if (isPromiseLike(raw))
|
|
3416
|
+
return raw.then(validateBodyAndFinish);
|
|
3417
|
+
return validateBodyAndFinish(raw);
|
|
3418
|
+
};
|
|
3419
|
+
const stepHeaders = () => {
|
|
3420
|
+
if (!hasHeadersSchema)
|
|
3421
|
+
return stepBody();
|
|
3422
|
+
const r = def.request.headers["~standard"].validate(buildHeaders());
|
|
3423
|
+
if (isPromiseLike(r)) {
|
|
3424
|
+
return r.then((resolved) => {
|
|
3425
|
+
headers = applyChecked(resolved, "headers");
|
|
3426
|
+
return stepBody();
|
|
3427
|
+
});
|
|
3221
3428
|
}
|
|
3222
|
-
|
|
3223
|
-
|
|
3429
|
+
headers = applyChecked(r, "headers");
|
|
3430
|
+
return stepBody();
|
|
3431
|
+
};
|
|
3432
|
+
const stepQuery = () => {
|
|
3433
|
+
if (!hasQuerySchema)
|
|
3434
|
+
return stepHeaders();
|
|
3435
|
+
const r = def.request.query["~standard"].validate(buildQuery());
|
|
3436
|
+
if (isPromiseLike(r)) {
|
|
3437
|
+
return r.then((resolved) => {
|
|
3438
|
+
query = applyChecked(resolved, "query");
|
|
3439
|
+
return stepHeaders();
|
|
3440
|
+
});
|
|
3441
|
+
}
|
|
3442
|
+
query = applyChecked(r, "query");
|
|
3443
|
+
return stepHeaders();
|
|
3444
|
+
};
|
|
3445
|
+
if (def.request?.params) {
|
|
3446
|
+
const r = def.request.params["~standard"].validate(rawParams);
|
|
3447
|
+
if (isPromiseLike(r)) {
|
|
3448
|
+
return r.then((resolved) => {
|
|
3449
|
+
params = applyChecked(resolved, "params");
|
|
3450
|
+
return stepQuery();
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
params = applyChecked(r, "params");
|
|
3454
|
+
}
|
|
3455
|
+
return stepQuery();
|
|
3224
3456
|
}
|
|
3225
3457
|
function headersToObject(h) {
|
|
3226
3458
|
const o = {};
|
|
@@ -3251,26 +3483,79 @@ function toIssues(issues) {
|
|
|
3251
3483
|
.join("."),
|
|
3252
3484
|
}));
|
|
3253
3485
|
}
|
|
3254
|
-
|
|
3486
|
+
/** Shared decoder for request-body text. Allocating one per request is wasted work. */
|
|
3487
|
+
const TEXT_DECODER = new TextDecoder();
|
|
3488
|
+
/**
|
|
3489
|
+
* Synchronous fast path for {@link readBodyLimited}: returns the adapter's
|
|
3490
|
+
* pre-buffered body bytes when they are available on the request via
|
|
3491
|
+
* {@link DALOY_REQUEST_RAW_BODY}, or `undefined` when the caller must fall
|
|
3492
|
+
* back to the async streaming read. Runs the exact same Content-Length
|
|
3493
|
+
* validation and size-limit checks (in the same order, throwing the same
|
|
3494
|
+
* errors) as `readBodyLimited`, so the security posture is identical — the
|
|
3495
|
+
* only difference is that a symbol-cache hit never touches the microtask
|
|
3496
|
+
* queue.
|
|
3497
|
+
*
|
|
3498
|
+
* @throws {BadRequestError} When `Content-Length` is present but invalid.
|
|
3499
|
+
* @throws {PayloadTooLargeError} When the declared or actual size exceeds `limit`.
|
|
3500
|
+
*/
|
|
3501
|
+
function readBodyBytesFast(req, limit) {
|
|
3502
|
+
const cl = req.headers.get("content-length");
|
|
3503
|
+
if (cl) {
|
|
3504
|
+
const n = Number(cl);
|
|
3505
|
+
if (!Number.isFinite(n) || n < 0)
|
|
3506
|
+
throw new BadRequestError("Invalid Content-Length");
|
|
3507
|
+
if (n > limit)
|
|
3508
|
+
throw new PayloadTooLargeError(limit);
|
|
3509
|
+
}
|
|
3510
|
+
const cached = req[DALOY_REQUEST_RAW_BODY];
|
|
3511
|
+
if (cached instanceof Uint8Array) {
|
|
3512
|
+
if (cached.byteLength > limit)
|
|
3513
|
+
throw new PayloadTooLargeError(limit);
|
|
3514
|
+
return cached;
|
|
3515
|
+
}
|
|
3516
|
+
return undefined;
|
|
3517
|
+
}
|
|
3518
|
+
function parseJsonBodyBytes(bytes) {
|
|
3519
|
+
if (bytes.byteLength === 0)
|
|
3520
|
+
return undefined;
|
|
3521
|
+
return safeJsonParse(TEXT_DECODER.decode(bytes));
|
|
3522
|
+
}
|
|
3523
|
+
function parseUrlencodedBodyBytes(bytes) {
|
|
3524
|
+
const params = new URLSearchParams(TEXT_DECODER.decode(bytes));
|
|
3525
|
+
// Same Spring4Shell-class defense as queryToObject: Object.fromEntries
|
|
3526
|
+
// would set __proto__ / constructor / prototype as own properties.
|
|
3527
|
+
const out = {};
|
|
3528
|
+
for (const [k, v] of params) {
|
|
3529
|
+
if (isForbiddenObjectKey(k))
|
|
3530
|
+
continue;
|
|
3531
|
+
out[k] = v;
|
|
3532
|
+
}
|
|
3533
|
+
return out;
|
|
3534
|
+
}
|
|
3535
|
+
/**
|
|
3536
|
+
* Read and parse a request body according to its content type. Plain
|
|
3537
|
+
* (non-`async`) on purpose: when the adapter pre-buffered the body bytes
|
|
3538
|
+
* (the common JSON POST case on Node), the parse completes synchronously and
|
|
3539
|
+
* the caller stays on the sync dispatch fast path. Falls back to the
|
|
3540
|
+
* streaming `readBodyLimited` promise otherwise. All parsing keeps the
|
|
3541
|
+
* prototype-pollution-safe semantics of the previous implementation.
|
|
3542
|
+
*/
|
|
3543
|
+
function readBody(req, ct, limit, multipart) {
|
|
3255
3544
|
if (ct.includes("application/json")) {
|
|
3256
|
-
const
|
|
3257
|
-
if (
|
|
3258
|
-
return
|
|
3259
|
-
return
|
|
3545
|
+
const fast = readBodyBytesFast(req, limit);
|
|
3546
|
+
if (fast !== undefined)
|
|
3547
|
+
return parseJsonBodyBytes(fast);
|
|
3548
|
+
return readBodyLimited(req, limit).then(parseJsonBodyBytes);
|
|
3260
3549
|
}
|
|
3261
3550
|
if (ct.includes("application/x-www-form-urlencoded")) {
|
|
3262
|
-
const
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
const out = {};
|
|
3267
|
-
for (const [k, v] of params) {
|
|
3268
|
-
if (isForbiddenObjectKey(k))
|
|
3269
|
-
continue;
|
|
3270
|
-
out[k] = v;
|
|
3271
|
-
}
|
|
3272
|
-
return out;
|
|
3551
|
+
const fast = readBodyBytesFast(req, limit);
|
|
3552
|
+
if (fast !== undefined)
|
|
3553
|
+
return parseUrlencodedBodyBytes(fast);
|
|
3554
|
+
return readBodyLimited(req, limit).then(parseUrlencodedBodyBytes);
|
|
3273
3555
|
}
|
|
3556
|
+
return readBodySlow(req, ct, limit, multipart);
|
|
3557
|
+
}
|
|
3558
|
+
async function readBodySlow(req, ct, limit, multipart) {
|
|
3274
3559
|
if (ct.includes("multipart/form-data")) {
|
|
3275
3560
|
// Fast-fail on an honestly-declared oversize body.
|
|
3276
3561
|
const cl = req.headers.get("content-length");
|
|
@@ -3362,7 +3647,7 @@ function normalizeSunset(value, method, path) {
|
|
|
3362
3647
|
}
|
|
3363
3648
|
return date.toUTCString();
|
|
3364
3649
|
}
|
|
3365
|
-
function serializeResult(result, def, validateResponses) {
|
|
3650
|
+
function serializeResult(result, def, validateResponses, lightOk = false) {
|
|
3366
3651
|
const spec = def.responses[result.status];
|
|
3367
3652
|
if (!spec) {
|
|
3368
3653
|
throw new InternalError(`Handler returned status ${result.status} which is not declared in responses for ${def.method} ${def.path}`);
|
|
@@ -3424,6 +3709,13 @@ function serializeResult(result, def, validateResponses) {
|
|
|
3424
3709
|
body = bytes;
|
|
3425
3710
|
rawBody = bytes;
|
|
3426
3711
|
}
|
|
3712
|
+
// Node-adapter hot path (opt-in via DALOY_LIGHT_RESPONSE_OK on the
|
|
3713
|
+
// incoming request): skip the ~2µs undici Response construction. Only
|
|
3714
|
+
// buffer-backed bodies qualify — streams keep the real Response so the
|
|
3715
|
+
// adapter's stream plumbing is untouched.
|
|
3716
|
+
if (lightOk && !isStream) {
|
|
3717
|
+
return new LightResponse(result.status, headers, rawBody);
|
|
3718
|
+
}
|
|
3427
3719
|
const response = new Response(body, { status: result.status, headers });
|
|
3428
3720
|
if (!isStream) {
|
|
3429
3721
|
response[DALOY_RAW_BODY] = rawBody;
|
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:e297e402-67fc-54e1-a292-02441ebb5d71",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-07-
|
|
7
|
+
"timestamp": "2026-07-03T11:51:00.910Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "1.0.0-
|
|
12
|
+
"version": "1.0.0-rc.0"
|
|
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-
|
|
22
|
+
"bom-ref": "pkg:npm/@daloyjs/core@1.0.0-rc.0",
|
|
23
23
|
"name": "@daloyjs/core",
|
|
24
|
-
"version": "1.0.0-
|
|
24
|
+
"version": "1.0.0-rc.0",
|
|
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-
|
|
26
|
+
"purl": "pkg:npm/@daloyjs/core@1.0.0-rc.0",
|
|
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-
|
|
49
|
+
"tagId": "swidtag--daloyjs-core-1.0.0-rc.0",
|
|
50
50
|
"name": "@daloyjs/core",
|
|
51
|
-
"version": "1.0.0-
|
|
51
|
+
"version": "1.0.0-rc.0",
|
|
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-
|
|
60
|
+
"ref": "pkg:npm/@daloyjs/core@1.0.0-rc.0",
|
|
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-
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-
|
|
5
|
+
"name": "@daloyjs/core-1.0.0-rc.0",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-rc.0-e297e402-67fc-54e1-a292-02441ebb5d71",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-07-
|
|
8
|
+
"created": "2026-07-03T11:51:00.910Z",
|
|
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-
|
|
19
|
+
"versionInfo": "1.0.0-rc.0",
|
|
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-
|
|
30
|
+
"referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-rc.0"
|
|
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-
|
|
3
|
+
"version": "1.0.0-rc.0",
|
|
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": {
|