@nifrajs/core 5.4.0 → 5.5.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/internal/route-lanes.d.ts.map +1 -1
  5. package/dist/internal/route-lanes.js +2 -1
  6. package/dist/internal/route-lanes.js.map +1 -1
  7. package/dist/internal/route-program.d.ts +5 -0
  8. package/dist/internal/route-program.d.ts.map +1 -1
  9. package/dist/internal/route-program.js +36 -15
  10. package/dist/internal/route-program.js.map +1 -1
  11. package/dist/router/router.d.ts +3 -2
  12. package/dist/router/router.d.ts.map +1 -1
  13. package/dist/router/router.js +22 -6
  14. package/dist/router/router.js.map +1 -1
  15. package/dist/security/baseline.d.ts.map +1 -1
  16. package/dist/security/baseline.js +4 -3
  17. package/dist/security/baseline.js.map +1 -1
  18. package/dist/server/body-lane.d.ts.map +1 -1
  19. package/dist/server/body-lane.js +4 -3
  20. package/dist/server/body-lane.js.map +1 -1
  21. package/dist/server/body.d.ts +2 -0
  22. package/dist/server/body.d.ts.map +1 -1
  23. package/dist/server/body.js +5 -0
  24. package/dist/server/body.js.map +1 -1
  25. package/dist/server/cookies.d.ts.map +1 -1
  26. package/dist/server/cookies.js +9 -2
  27. package/dist/server/cookies.js.map +1 -1
  28. package/dist/server/core-copies.js +1 -1
  29. package/dist/server/idempotency-lane.d.ts +11 -12
  30. package/dist/server/idempotency-lane.d.ts.map +1 -1
  31. package/dist/server/idempotency-lane.js +301 -151
  32. package/dist/server/idempotency-lane.js.map +1 -1
  33. package/dist/server/methods.d.ts +3 -2
  34. package/dist/server/methods.d.ts.map +1 -1
  35. package/dist/server/methods.js.map +1 -1
  36. package/dist/server/multipart.d.ts.map +1 -1
  37. package/dist/server/multipart.js +42 -13
  38. package/dist/server/multipart.js.map +1 -1
  39. package/dist/server/node-outcome.d.ts.map +1 -1
  40. package/dist/server/node-outcome.js +2 -2
  41. package/dist/server/node-outcome.js.map +1 -1
  42. package/dist/server/query.d.ts +1 -1
  43. package/dist/server/query.d.ts.map +1 -1
  44. package/dist/server/query.js +13 -5
  45. package/dist/server/query.js.map +1 -1
  46. package/dist/server/request-context.d.ts.map +1 -1
  47. package/dist/server/request-context.js +3 -3
  48. package/dist/server/request-context.js.map +1 -1
  49. package/dist/server/respond.d.ts +17 -0
  50. package/dist/server/respond.d.ts.map +1 -1
  51. package/dist/server/respond.js +81 -3
  52. package/dist/server/respond.js.map +1 -1
  53. package/dist/server/runtime-core.d.ts +15 -0
  54. package/dist/server/runtime-core.d.ts.map +1 -1
  55. package/dist/server/runtime-core.js +41 -1
  56. package/dist/server/runtime-core.js.map +1 -1
  57. package/dist/server/server.d.ts +14 -5
  58. package/dist/server/server.d.ts.map +1 -1
  59. package/dist/server/server.js +179 -59
  60. package/dist/server/server.js.map +1 -1
  61. package/dist/server/transport-lane.d.ts +2 -2
  62. package/dist/server/transport-lane.d.ts.map +1 -1
  63. package/dist/server/transport-lane.js +20 -20
  64. package/dist/server/transport-lane.js.map +1 -1
  65. package/dist/static-file.d.ts +29 -6
  66. package/dist/static-file.d.ts.map +1 -1
  67. package/dist/static-file.js +43 -12
  68. package/dist/static-file.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/index.ts +1 -1
  71. package/src/internal/route-lanes.ts +18 -16
  72. package/src/internal/route-program.ts +76 -12
  73. package/src/router/router.ts +26 -5
  74. package/src/security/baseline.ts +4 -3
  75. package/src/server/body-lane.ts +4 -4
  76. package/src/server/body.ts +6 -0
  77. package/src/server/cookies.ts +7 -2
  78. package/src/server/core-copies.ts +1 -1
  79. package/src/server/idempotency-lane.ts +343 -209
  80. package/src/server/methods.ts +3 -2
  81. package/src/server/multipart.ts +62 -14
  82. package/src/server/node-outcome.ts +2 -1
  83. package/src/server/query.ts +12 -5
  84. package/src/server/request-context.ts +6 -3
  85. package/src/server/respond.ts +108 -6
  86. package/src/server/runtime-core.ts +54 -1
  87. package/src/server/server.ts +249 -99
  88. package/src/server/transport-lane.ts +28 -22
  89. package/src/static-file.ts +72 -16
@@ -1,5 +1,5 @@
1
1
  /** Opt-in versioned transport integration. Kept outside the server kernel's bare dependency graph. */
2
- import type { TransportCodec, TransportCodecRegistry } from "../transport-codec.js";
2
+ import { type TransportCodec, type TransportCodecRegistry } from "../transport-codec.js";
3
3
  import { type ProtoPoisoning } from "./proto-guard.js";
4
4
  import type { IdentityPlugin } from "./server.js";
5
5
  interface TransportBodySource {
@@ -12,7 +12,7 @@ export type TransportDecodeResult = {
12
12
  } | {
13
13
  readonly matched: true;
14
14
  readonly value: unknown;
15
- readonly byteLength: number;
15
+ readonly bytes: Uint8Array;
16
16
  } | {
17
17
  readonly matched: true;
18
18
  readonly response: Response;
@@ -1 +1 @@
1
- {"version":3,"file":"transport-lane.d.ts","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAGnF,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEzE,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAA;IAChD,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;CACpC;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChF;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAAA;IACpD,aAAa,CACX,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAChC;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,sBAAsB,EAChC,OAAO,GAAE,sBAA2B,GACnC,cAAc,CA6GhB"}
1
+ {"version":3,"file":"transport-lane.d.ts","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEzE,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAA;IAChD,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;CACpC;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAAA;IACpD,aAAa,CACX,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAChC;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,sBAAsB,EAChC,OAAO,GAAE,sBAA2B,GACnC,cAAc,CA+GhB"}
@@ -1,3 +1,5 @@
1
+ /** Opt-in versioned transport integration. Kept outside the server kernel's bare dependency graph. */
2
+ import { encodeTransportResponse, } from "../transport-codec.js";
1
3
  import { readBoundedBytes } from "./body.js";
2
4
  import { jsonError } from "./http.js";
3
5
  import { guardDecodedValue } from "./proto-guard.js";
@@ -42,7 +44,7 @@ export function transportCodecs(registry, options = {}) {
42
44
  return {
43
45
  matched: true,
44
46
  value: guardDecodedValue(codec.decode(text), protoPoisoning, acceptRegExp),
45
- byteLength: read.bytes.byteLength,
47
+ bytes: read.bytes,
46
48
  };
47
49
  }
48
50
  catch {
@@ -73,36 +75,34 @@ export function transportCodecs(registry, options = {}) {
73
75
  return undefined;
74
76
  if ("response" in decoded)
75
77
  return decoded.response;
76
- // Re-frame as a plain JSON request whose already-decoded value rides the pre-decoded stash;
77
- // the body lane takes the stash verbatim (this lane owns the cap and poisoning policy), so
78
- // the placeholder body is never parsed and codec machinery stays out of the kernel.
79
- const headers = {};
80
- request.headers.forEach((value, name) => {
81
- headers[name] = value;
82
- });
83
- headers["content-type"] = "application/json";
84
- headers["content-length"] = "2";
85
- delete headers["transfer-encoding"];
78
+ // The decoded value rides the pre-decoded stash, which the body lane takes verbatim (this lane
79
+ // owns the cap and poisoning policy), so codec machinery stays out of the kernel. The request
80
+ // keeps the bytes and media type it was sent with: a route without a body schema, or a
81
+ // signature check, reads the payload itself, not a stand-in.
82
+ const headers = new Headers(request.headers);
83
+ headers.set("content-length", String(decoded.bytes.byteLength));
84
+ headers.delete("transfer-encoding");
86
85
  const normalized = new Request(request.url, {
87
86
  method: request.method,
88
87
  headers,
89
- body: "{}",
88
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: every reader behind readBoundedBytes yields an unshared buffer, which a Request body accepts.
89
+ body: decoded.bytes,
90
90
  signal: request.signal,
91
91
  });
92
- const stash = { value: decoded.value, byteLength: decoded.byteLength };
92
+ const stash = {
93
+ value: decoded.value,
94
+ byteLength: decoded.bytes.byteLength,
95
+ encoded: decoded.bytes,
96
+ contentType,
97
+ };
93
98
  Object.defineProperty(normalized, PRE_DECODED_BODY, { value: stash });
94
99
  return normalized;
95
100
  });
96
101
  app.afterHandle((result, context) => {
97
102
  if (result === undefined || result instanceof Response || isResponseResult(result))
98
103
  return result;
99
- const codec = runtime.responseCodec(context.req.headers.get("accept"));
100
- const headers = new Headers(context.set.headers);
101
- headers.set("content-type", codec.mediaType);
102
- return new Response(codec.encode(result), {
103
- status: context.set.status ?? 200,
104
- headers,
105
- });
104
+ // The representation depends on `Accept`, so the response says so to every shared cache.
105
+ return encodeTransportResponse(result, runtime.responseCodec(context.req.headers.get("accept")), { status: context.set.status ?? 200, headers: context.set.headers });
106
106
  });
107
107
  return app;
108
108
  };
@@ -1 +1 @@
1
- {"version":3,"file":"transport-lane.js","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transport-lane.js","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"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"}
@@ -44,6 +44,13 @@ export interface FileVersion {
44
44
  * validates them alone.
45
45
  */
46
46
  export declare function lastModifiedOf(version: FileVersion): number | undefined;
47
+ /**
48
+ * {@link lastModifiedOf} for a body streamed after its stat, where a write can still reach the bytes
49
+ * sent. Dated only once its change is older than the racy window, so such a write falls in a later
50
+ * HTTP-date second and the next `If-Modified-Since` fetches it. A weak validator: never pass it to
51
+ * {@link ifRangeAllowsPartial}, which would resume a range across two versions.
52
+ */
53
+ export declare function streamedLastModifiedOf(version: FileVersion, now?: number): number | undefined;
47
54
  /**
48
55
  * Whether the client's copy is current, so the answer is a bodyless 304. `If-None-Match` (the header,
49
56
  * or `null`) decides whenever it is sent (RFC 9110 section 13.2.2), by weak comparison with `etag`, and
@@ -69,20 +76,27 @@ export interface ContentHash {
69
76
  /** Reads into `bytes` from byte `position` of a file, resolving to the count read (0 at its end). */
70
77
  export type ReadAt = (bytes: Uint8Array, position: number) => Promise<number>;
71
78
  /**
72
- * Strong entity tags from what a file holds, the same for the same bytes on every instance and
73
- * runtime, so a replacement that keeps a file's size and timestamps still changes its tag. A tag is
74
- * remembered per real path for the version it was computed from, so serving a known file reads no
75
- * more than before.
79
+ * Entity tags from file bytes, consistent across instances and runtimes. Files beyond the content
80
+ * hash limit instead use a per-instance identity tag. Callers must serve the same immutable bytes
81
+ * they hash; static HTTP readers use bounded snapshots for that guarantee. A tag is remembered per
82
+ * real path for the version it was computed from.
76
83
  */
77
84
  export interface ContentTags {
78
85
  /** The tag remembered for this version of the file at `path`, if any. */
79
86
  cached(path: string, version: FileVersion): string | undefined;
80
87
  /** The tag of `bytes`, read whole from this version of the file at `path`. */
81
88
  ofBytes(path: string, version: FileVersion, bytes: Uint8Array): string;
89
+ /**
90
+ * The tag of `bytes`, the whole file at `path` read after a stat that gave `version`. A small
91
+ * snapshot is hashed; a larger one takes the tag remembered for `version` when `restat`, called
92
+ * after the read, still reports that version, since no write reached the file in between.
93
+ */
94
+ ofSnapshot(path: string, version: FileVersion, bytes: Uint8Array, restat: () => Promise<FileVersion>): Promise<string>;
82
95
  /**
83
96
  * The tag of this version of the file at `path`, read whole through `read`; requests for a version
84
97
  * already being read wait for that read. A file over {@link CONTENT_TAG_MAX} is not read: its tag
85
- * names its identity on this host instead, or is `undefined` where the runtime reports none.
98
+ * names its identity within this tagger instance instead, or is `undefined` without a stable identity.
99
+ * Reading a mutable inode and later streaming it does not preserve this tag's byte identity.
86
100
  */
87
101
  ofFile(path: string, version: FileVersion, read: ReadAt): Promise<string | undefined>;
88
102
  }
@@ -91,14 +105,23 @@ export interface ContentTagOptions {
91
105
  readonly limit?: number;
92
106
  /** The clock compared with a file's ctime (default `Date.now`). */
93
107
  readonly now?: () => number;
108
+ /**
109
+ * Largest snapshot {@link ContentTags.ofSnapshot} hashes on every request (default 4 KiB). A larger
110
+ * one takes the tag remembered for its version once a restat shows that version. Raise it where a
111
+ * stat costs more than hashing, as on Deno.
112
+ */
113
+ readonly rehashMax?: number;
94
114
  }
95
115
  /**
96
116
  * Largest file tagged by its content. Its first request waits for one read of the whole file, ~0.1 s
97
117
  * per 256 MiB with hardware SHA-256 and several times that on a slow disk or CPU, so a larger file -
98
118
  * a long video that is only ever fetched by ranges, say - is tagged by its device, inode, size and
99
- * times. That tag is still strong and changes with any replacement, but differs between instances.
119
+ * times plus a random instance salt. Identity tags require immutable file versions and intentionally
120
+ * differ between tagger instances so local stat identities cannot collide across hosts.
100
121
  */
101
122
  export declare const CONTENT_TAG_MAX: number;
123
+ /** Static HTTP readers tag only a whole, bounded snapshot; streamed inodes can change after hashing. */
124
+ export declare const CONTENT_SNAPSHOT_MAX: number;
102
125
  /** Tags from `createHash` - node:crypto's, which every runtime nifra serves files on provides. */
103
126
  export declare function contentTags(createHash: (algorithm: "sha256") => ContentHash, options?: ContentTagOptions): ContentTags;
104
127
  //# sourceMappingURL=static-file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"static-file.d.ts","sourceRoot":"","sources":["../src/static-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAuCzD,CAAA;AAEF,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAKjD;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAOD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAGvE;AAWD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAcT;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAQT;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAA;IAClC,MAAM,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAA;CACtC;AAED,qGAAqG;AACrG,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAE7E;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;IAC9D,8EAA8E;IAC9E,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAAA;IACtE;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACtF;AAED,MAAM,WAAW,iBAAiB;IAChC,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAoB,CAAA;AAqBhD,kGAAkG;AAClG,wBAAgB,WAAW,CACzB,UAAU,EAAE,CAAC,SAAS,EAAE,QAAQ,KAAK,WAAW,EAChD,OAAO,GAAE,iBAAsB,GAC9B,WAAW,CAsEb"}
1
+ {"version":3,"file":"static-file.d.ts","sourceRoot":"","sources":["../src/static-file.ts"],"names":[],"mappings":"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"}
@@ -92,6 +92,15 @@ export function lastModifiedOf(version) {
92
92
  const lag = Math.abs(version.ctimeMs - version.mtimeMs);
93
93
  return version.mtimeMs > 0 && lag < MODIFIED_LAG_MS ? version.mtimeMs : undefined;
94
94
  }
95
+ /**
96
+ * {@link lastModifiedOf} for a body streamed after its stat, where a write can still reach the bytes
97
+ * sent. Dated only once its change is older than the racy window, so such a write falls in a later
98
+ * HTTP-date second and the next `If-Modified-Since` fetches it. A weak validator: never pass it to
99
+ * {@link ifRangeAllowsPartial}, which would resume a range across two versions.
100
+ */
101
+ export function streamedLastModifiedOf(version, now = Date.now()) {
102
+ return now - version.ctimeMs >= RACY_MS ? lastModifiedOf(version) : undefined;
103
+ }
95
104
  /** HTTP dates carry whole seconds; comparing raw milliseconds makes every file look stale. */
96
105
  function seconds(time) {
97
106
  return Math.floor(time / 1000);
@@ -146,9 +155,15 @@ export function ifRangeAllowsPartial(ifRange, etag, lastModified) {
146
155
  * Largest file tagged by its content. Its first request waits for one read of the whole file, ~0.1 s
147
156
  * per 256 MiB with hardware SHA-256 and several times that on a slow disk or CPU, so a larger file -
148
157
  * a long video that is only ever fetched by ranges, say - is tagged by its device, inode, size and
149
- * times. That tag is still strong and changes with any replacement, but differs between instances.
158
+ * times plus a random instance salt. Identity tags require immutable file versions and intentionally
159
+ * differ between tagger instances so local stat identities cannot collide across hosts.
150
160
  */
151
161
  export const CONTENT_TAG_MAX = 256 * 1024 * 1024;
162
+ /** Static HTTP readers tag only a whole, bounded snapshot; streamed inodes can change after hashing. */
163
+ export const CONTENT_SNAPSHOT_MAX = 256 * 1024;
164
+ // Past this, a restat proves a remembered version unchanged faster than hashing it again: served over
165
+ // HTTP on Node and Bun, an 8 KiB file is 1.02-1.05x faster that way, a 4 KiB one no faster.
166
+ const SNAPSHOT_REHASH_MAX = 4 * 1024;
152
167
  const READ_CHUNK = 1024 * 1024;
153
168
  // A file changed this recently can change again within its ctime's granularity (a jiffy on ext4, a
154
169
  // second on HFS+ and as Deno reports it) without its stat showing it, so it is not tagged by version.
@@ -167,6 +182,8 @@ function quoted(hash) {
167
182
  export function contentTags(createHash, options = {}) {
168
183
  const limit = options.limit ?? 8192;
169
184
  const now = options.now ?? Date.now;
185
+ const rehashMax = options.rehashMax ?? SNAPSHOT_REHASH_MAX;
186
+ let instance;
170
187
  const known = new Map();
171
188
  const reading = new Map();
172
189
  const remember = (path, version, tag) => {
@@ -197,18 +214,29 @@ export function contentTags(createHash, options = {}) {
197
214
  remember(path, version, tag);
198
215
  return tag;
199
216
  };
217
+ const cached = (path, version) => {
218
+ const entry = known.get(path);
219
+ return entry !== undefined && sameVersion(entry, version) ? entry.tag : undefined;
220
+ };
221
+ const ofBytes = (path, version, bytes) => {
222
+ const hash = createHash("sha256");
223
+ hash.update(bytes);
224
+ const tag = quoted(hash);
225
+ if (bytes.byteLength === version.size)
226
+ remember(path, version, tag);
227
+ return tag;
228
+ };
200
229
  return {
201
- cached(path, version) {
202
- const entry = known.get(path);
203
- return entry !== undefined && sameVersion(entry, version) ? entry.tag : undefined;
204
- },
205
- ofBytes(path, version, bytes) {
206
- const hash = createHash("sha256");
207
- hash.update(bytes);
208
- const tag = quoted(hash);
209
- if (bytes.byteLength === version.size)
210
- remember(path, version, tag);
211
- return tag;
230
+ cached,
231
+ ofBytes,
232
+ async ofSnapshot(path, version, bytes, restat) {
233
+ if (bytes.byteLength > rehashMax) {
234
+ const tag = cached(path, version);
235
+ // A remembered version is settled, so any write since its stat would show in the restat.
236
+ if (tag !== undefined && cached(path, await restat()) === tag)
237
+ return tag;
238
+ }
239
+ return ofBytes(path, version, bytes);
212
240
  },
213
241
  async ofFile(path, version, read) {
214
242
  if (version.size > CONTENT_TAG_MAX) {
@@ -216,6 +244,9 @@ export function contentTags(createHash, options = {}) {
216
244
  if (!Number.isFinite(ino) || !(now() - ctimeMs >= RACY_MS))
217
245
  return undefined;
218
246
  const hash = createHash("sha256");
247
+ // Filesystem identities are local: another host may reuse every one of these stat fields.
248
+ instance ??= crypto.getRandomValues(new Uint8Array(16));
249
+ hash.update(instance);
219
250
  hash.update(new TextEncoder().encode(`${dev}:${ino}:${size}:${mtimeMs}:${ctimeMs}`));
220
251
  return quoted(hash);
221
252
  }
@@ -1 +1 @@
1
- {"version":3,"file":"static-file.js","sourceRoot":"","sources":["../src/static-file.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"static-file.js","sourceRoot":"","sources":["../src/static-file.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/core",
3
- "version": "5.4.0",
3
+ "version": "5.5.0",
4
4
  "description": "Bun-native, contract-first HTTP framework - the router, server, and route descriptor.",
5
5
  "keywords": [
6
6
  "bun",
package/src/index.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * read its own package.json at runtime. `scripts/version.ts` rewrites it on every release bump and
17
17
  * `check:publish` asserts it equals `@nifrajs/core`'s package version.
18
18
  */
19
- export const VERSION = "5.4.0" as const
19
+ export const VERSION = "5.5.0" as const
20
20
 
21
21
  export type Version = typeof VERSION
22
22
 
@@ -119,22 +119,24 @@ export function selectRouteLanes(options: {
119
119
  !hasLedger &&
120
120
  around === 0
121
121
 
122
- const lane: RouteExecutionLane = authBeforeValidation
123
- ? "lifecycle"
124
- : bare
125
- ? "bare"
126
- : bodyOnly
127
- ? "body"
128
- : !hasResponseContract &&
129
- schema?.body === undefined &&
130
- schema?.query !== undefined &&
131
- plain &&
132
- derives === 0 &&
133
- beforeHandle === 0 &&
134
- afterHandle === 0 &&
135
- onError === 0
136
- ? "query"
137
- : "lifecycle"
122
+ // The idempotency gate lives in the general program, so an idempotent route always runs it.
123
+ const lane: RouteExecutionLane =
124
+ authBeforeValidation || hasIdempotency
125
+ ? "lifecycle"
126
+ : bare
127
+ ? "bare"
128
+ : bodyOnly
129
+ ? "body"
130
+ : !hasResponseContract &&
131
+ schema?.body === undefined &&
132
+ schema?.query !== undefined &&
133
+ plain &&
134
+ derives === 0 &&
135
+ beforeHandle === 0 &&
136
+ afterHandle === 0 &&
137
+ onError === 0
138
+ ? "query"
139
+ : "lifecycle"
138
140
 
139
141
  // Lifecycle routes with no params schema are the common middleware shape: derive/before plus an
140
142
  // optional query or body schema. Select their complete validation stage at registration so the
@@ -158,6 +158,36 @@ interface RouteProgramRuntime {
158
158
  finalize: (result: unknown, set: CtxSet, ctx?: RawContext) => T,
159
159
  wrapResponse: (response: Response | ResponseResult) => T,
160
160
  ): MaybePromise<T>
161
+ /** Installed by `idempotency()`, and present whenever a route resolved idempotency. */
162
+ readonly idempotencyRuntime: {
163
+ gate<T>(
164
+ entry: RouteEntry,
165
+ ctx: RawContext,
166
+ wrapResponse: (response: Response | ResponseResult) => T,
167
+ resume: IdempotencyResume<T>,
168
+ fail: (error: unknown) => MaybePromise<T>,
169
+ ): Promise<T>
170
+ }
171
+ }
172
+
173
+ /**
174
+ * The rest of a program after the idempotency gate. The gate continues it with the request it
175
+ * buffered, and with a replay entry and program when the key already holds a response.
176
+ */
177
+ export type IdempotencyResume<T> = (entry: RouteEntry, program: RouteProgram) => ProgramResult<T>
178
+
179
+ /** Idempotency infrastructure failures use the same boundary as the resumed route program. */
180
+ function runIdempotencyGate<T>(
181
+ host: RouteProgramRuntime,
182
+ entry: RouteEntry,
183
+ ctx: RawContext,
184
+ finalize: (result: unknown, set: CtxSet, ctx?: RawContext) => T,
185
+ wrapResponse: (response: Response | ResponseResult) => T,
186
+ resume: IdempotencyResume<T>,
187
+ ): Promise<T> {
188
+ return host.idempotencyRuntime.gate(entry, ctx, wrapResponse, resume, (error) =>
189
+ host.handleProgramError(entry, error, ctx, finalize, wrapResponse),
190
+ )
161
191
  }
162
192
 
163
193
  function responseSet(ctx: RawContext): CtxSet {
@@ -278,6 +308,22 @@ function runAuthenticationStages<T>(
278
308
  validationComplete = false,
279
309
  ): ProgramResult<T> {
280
310
  if (start >= program.authStages.length) {
311
+ // Auth-first: the idempotency gate buffers the body, so it waits for every stage to pass. It
312
+ // resumes on an entry without the gate, so the program does not meet it twice.
313
+ if (entry.idempotent !== undefined && !validationComplete) {
314
+ return runIdempotencyGate(host, entry, ctx, finalize, wrapResponse, (e, p) =>
315
+ runAuthenticationStages(
316
+ host,
317
+ e,
318
+ p,
319
+ source,
320
+ ctx,
321
+ finalize,
322
+ wrapResponse,
323
+ p.authStages.length,
324
+ ),
325
+ )
326
+ }
281
327
  if (program.authFastLane) {
282
328
  const validationIndex = validationComplete ? program.validationStages.length : 0
283
329
  return runFastAuthenticatedProgram(
@@ -578,22 +624,33 @@ async function runAuthBeforeValidation<T>(
578
624
  ctx: RawContext,
579
625
  finalize: (result: unknown, set: CtxSet, ctx?: RawContext) => T,
580
626
  wrapResponse: (response: Response | ResponseResult) => T,
627
+ gated = false,
581
628
  ): Promise<T> {
582
629
  try {
583
- if (program.hasDecorations) Object.assign(ctx, program.decorations)
630
+ if (!gated) {
631
+ if (program.hasDecorations) Object.assign(ctx, program.decorations)
584
632
 
585
- for (const derive of program.derives) {
586
- const result = derive(ctx)
587
- const settled = result instanceof Promise ? await result : result
588
- if (isEarly(settled)) return finishEarly(host, entry, ctx, settled, finalize, wrapResponse)
589
- Object.assign(ctx, settled)
590
- }
633
+ for (const derive of program.derives) {
634
+ const result = derive(ctx)
635
+ const settled = result instanceof Promise ? await result : result
636
+ if (isEarly(settled)) return finishEarly(host, entry, ctx, settled, finalize, wrapResponse)
637
+ Object.assign(ctx, settled)
638
+ }
591
639
 
592
- for (const before of program.beforeHandle) {
593
- const result = before(ctx)
594
- const settled = result instanceof Promise ? await result : result
595
- if (settled !== undefined)
596
- return finishEarly(host, entry, ctx, settled, finalize, wrapResponse)
640
+ for (const before of program.beforeHandle) {
641
+ const result = before(ctx)
642
+ const settled = result instanceof Promise ? await result : result
643
+ if (settled !== undefined)
644
+ return finishEarly(host, entry, ctx, settled, finalize, wrapResponse)
645
+ }
646
+
647
+ // The policy hooks have passed, so the idempotency gate may buffer the body now. Not awaited:
648
+ // the gate reports its own failures, and one the resumed program let escape must skip this catch.
649
+ if (entry.idempotent !== undefined) {
650
+ return runIdempotencyGate(host, entry, ctx, finalize, wrapResponse, (e, p) =>
651
+ runAuthBeforeValidation(host, e, p, source, ctx, finalize, wrapResponse, true),
652
+ )
653
+ }
597
654
  }
598
655
 
599
656
  for (const stage of program.validationStages) {
@@ -634,6 +691,13 @@ export function executeRouteProgram<T, R extends Registry, Ctx>(
634
691
  wrapResponse: (response: Response | ResponseResult) => T,
635
692
  ): ProgramResult<T> {
636
693
  const host = runtime as unknown as RouteProgramRuntime
694
+ // An auth-first program meets the idempotency gate once authentication has passed, so the gate
695
+ // never buffers a body for a caller it would refuse. Every other program meets it first.
696
+ if (entry.idempotent !== undefined && !program.authBeforeValidation) {
697
+ return runIdempotencyGate(host, entry, ctx, finalize, wrapResponse, (e, p) =>
698
+ executeRouteProgram(runtime, e, p, source, ctx, finalize, wrapResponse),
699
+ )
700
+ }
637
701
  try {
638
702
  if (program.authStages.length > 0) {
639
703
  if (program.hasDecorations) Object.assign(ctx, program.decorations)
@@ -10,8 +10,18 @@ import {
10
10
  mixedSegmentShape,
11
11
  } from "./pattern.ts"
12
12
 
13
- /** The standard HTTP methods: the ones with a builder on the server and a call on the typed client. */
14
- export const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] as const
13
+ /** The standard HTTP methods: the ones the typed registry and the typed client know. Each has a server
14
+ * builder except `QUERY` (RFC 9110's safe, idempotent read with a body), which `method()` registers. */
15
+ export const METHODS = [
16
+ "GET",
17
+ "POST",
18
+ "PUT",
19
+ "PATCH",
20
+ "DELETE",
21
+ "HEAD",
22
+ "OPTIONS",
23
+ "QUERY",
24
+ ] as const
15
25
 
16
26
  export type Method = (typeof METHODS)[number]
17
27
 
@@ -37,6 +47,8 @@ export function isRegistrableMethod(method: string): boolean {
37
47
  const SLASH = 47
38
48
  export const EMPTY_PARAMS: Record<string, string> = Object.freeze({})
39
49
  const DYNAMIC_MATCH_CACHE_MAX = 2048
50
+ // The client picks every key, so their length is bounded too: 2048 paths of 8 KiB held 16 MiB.
51
+ const DYNAMIC_MATCH_CACHE_CHARS = 256 * 1024
40
52
 
41
53
  /**
42
54
  * Result of {@link Router.find}. The `found: false` cases deliberately separate
@@ -67,6 +79,8 @@ interface DynamicMatchCacheEntry<T> {
67
79
 
68
80
  interface DynamicMatchCacheState<T> {
69
81
  cache: Map<string, DynamicMatchCacheEntry<T>> | undefined
82
+ /** Total length of the cached paths. */
83
+ chars: number
70
84
  pendingPath: string | undefined
71
85
  }
72
86
 
@@ -79,6 +93,7 @@ function dynamicMatchCacheStateFor<T>(owner: object): DynamicMatchCacheState<T>
79
93
  }
80
94
  const state: DynamicMatchCacheState<T> = {
81
95
  cache: undefined,
96
+ chars: 0,
82
97
  pendingPath: undefined,
83
98
  }
84
99
  DYNAMIC_MATCH_CACHES.set(owner, state as DynamicMatchCacheState<unknown>)
@@ -476,13 +491,19 @@ export class Router<T> {
476
491
  cache = new Map<string, DynamicMatchCacheEntry<T>>()
477
492
  state.cache = cache
478
493
  }
479
- if (cache.size >= DYNAMIC_MATCH_CACHE_MAX) {
480
- const oldest = cache.keys().next().value
481
- if (oldest !== undefined) cache.delete(oldest)
494
+ for (const oldest of cache.keys()) {
495
+ if (
496
+ cache.size < DYNAMIC_MATCH_CACHE_MAX &&
497
+ state.chars + path.length <= DYNAMIC_MATCH_CACHE_CHARS
498
+ )
499
+ break
500
+ cache.delete(oldest)
501
+ state.chars -= oldest.length
482
502
  }
483
503
  cache.set(path, {
484
504
  terminal,
485
505
  values: values.slice(0, terminal.paramNames.length),
486
506
  })
507
+ state.chars += path.length
487
508
  }
488
509
  }
@@ -41,9 +41,10 @@ export interface SecurityBaselineOptions {
41
41
  readonly requireRuntimeProvenance?: boolean
42
42
  }
43
43
 
44
- /** A mutation is every method these leave out: POST, PUT, PATCH, DELETE and any custom method. */
45
- const NOT_MUTATING: readonly (Method | "WS")[] = ["GET", "HEAD", "OPTIONS", "WS"]
46
- const READING = ["GET", "HEAD"] as const
44
+ /** A mutation is every method these leave out: POST, PUT, PATCH, DELETE and any custom method. QUERY
45
+ * reads, with a body that the generic body rule still bounds. */
46
+ const NOT_MUTATING: readonly (Method | "WS")[] = ["GET", "HEAD", "OPTIONS", "QUERY", "WS"]
47
+ const READING = ["GET", "HEAD", "QUERY"] as const
47
48
  /** The classification threshold above which the baseline demands authentication. */
48
49
  const SENSITIVE = "pii" as const
49
50
 
@@ -11,8 +11,7 @@ import { isJsonMediaType, isUrlEncodedForm, readBoundedForm } from "./query.ts"
11
11
  import {
12
12
  headerOf,
13
13
  isResponseResult,
14
- PRE_DECODED_BODY,
15
- type PreDecodedBody,
14
+ preDecodedBody,
16
15
  type ResponseResult,
17
16
  TEXT_DECODER,
18
17
  } from "./runtime-core.ts"
@@ -52,7 +51,7 @@ export function readBoundedJsonSource<T>(
52
51
  onResult?: JsonResultContinuation<T>,
53
52
  onError?: JsonErrorContinuation<T>,
54
53
  ): Promise<unknown | ResponseResult | T> {
55
- const preDecoded = (req as { [PRE_DECODED_BODY]?: PreDecodedBody })[PRE_DECODED_BODY]
54
+ const preDecoded = preDecodedBody(req)
56
55
  if (preDecoded !== undefined) {
57
56
  const value =
58
57
  preDecoded.byteLength > maxBytes ? plainError(413, "payload_too_large") : preDecoded.value
@@ -237,7 +236,8 @@ export function readBodyFramed<T>(
237
236
  formOrigins?: FormOrigins,
238
237
  ): Promise<T> {
239
238
  const contentType = headerOf(source, "content-type") ?? ""
240
- if (!isJsonMediaType(contentType)) {
239
+ // A body a transport codec decoded keeps its media type; the JSON lane below takes its value.
240
+ if (!isJsonMediaType(contentType) && preDecodedBody(source) === undefined) {
241
241
  // JSON needs a preflight, so CORS judges it; a form body reaches here from any site with cookies.
242
242
  if (isFormMediaType(contentType) && !formOriginAllowed(source, formOrigins)) {
243
243
  return Promise.resolve(wrapResponse(plainError(403, "forbidden_origin")))