castrum 0.7.0 → 0.9.1

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -368
  3. package/castrum.linux-x64-gnu.node +0 -0
  4. package/dist/index.d.ts +2763 -0
  5. package/dist/index.js +8707 -0
  6. package/docs/API.md +337 -0
  7. package/docs/ARCHITECTURE.md +507 -0
  8. package/docs/BENCHMARKS.md +343 -0
  9. package/docs/CASE_STUDY.md +240 -0
  10. package/docs/ENVIRONMENT.md +81 -0
  11. package/docs/FFI_BUN_GUIDE.md +327 -0
  12. package/docs/GETTING_STARTED.md +192 -0
  13. package/docs/INGRESS-ROUTER.md +81 -0
  14. package/docs/INGRESS.md +746 -0
  15. package/docs/NATIVE-ROUTE.md +53 -0
  16. package/docs/REPO_MAP.md +279 -0
  17. package/docs/adr/0001-two-wire-formats.md +39 -0
  18. package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
  19. package/docs/adr/0003-bun-builtins-delegation.md +43 -0
  20. package/docs/bun-builtins-decision-matrix.md +115 -0
  21. package/index.ts +44 -8
  22. package/package.json +98 -33
  23. package/src/ingress/body.ts +289 -0
  24. package/src/ingress/constants.ts +220 -48
  25. package/src/ingress/context.ts +136 -0
  26. package/src/ingress/decode/baked-result.ts +121 -0
  27. package/src/ingress/decode/fast-result.ts +160 -0
  28. package/src/ingress/decode/packed-sections.ts +56 -0
  29. package/src/ingress/decode/result-base.ts +172 -0
  30. package/src/ingress/errors.ts +72 -0
  31. package/src/ingress/fast.ts +152 -1021
  32. package/src/ingress/handlers.ts +639 -1001
  33. package/src/ingress/headers/baked-templates.ts +102 -0
  34. package/src/ingress/headers/cors.ts +44 -0
  35. package/src/ingress/headers/fast-templates.ts +238 -0
  36. package/src/ingress/headers/hsts.ts +83 -0
  37. package/src/ingress/health.ts +68 -0
  38. package/src/ingress/index.ts +142 -405
  39. package/src/ingress/metrics.ts +132 -0
  40. package/src/ingress/options.ts +197 -0
  41. package/src/ingress/packing/gather-raw-headers.ts +139 -0
  42. package/src/ingress/packing/header-packing.ts +75 -0
  43. package/src/ingress/packing/input-packer.ts +164 -0
  44. package/src/ingress/packing/scratch.ts +50 -0
  45. package/src/ingress/packing/select-headers.ts +102 -0
  46. package/src/ingress/response/error-bodies.ts +75 -0
  47. package/src/ingress/response/terminal.ts +59 -0
  48. package/src/ingress/router.ts +237 -0
  49. package/src/ingress/routes/common.ts +79 -0
  50. package/src/ingress/routes/delete.ts +14 -0
  51. package/src/ingress/routes/echo.ts +107 -0
  52. package/src/ingress/routes/fallback.ts +23 -0
  53. package/src/ingress/routes/head.ts +27 -0
  54. package/src/ingress/routes/index.ts +11 -0
  55. package/src/ingress/routes/json-write.ts +129 -0
  56. package/src/ingress/routes/options.ts +26 -0
  57. package/src/ingress/routes/read.ts +35 -0
  58. package/src/ingress/routes/responder.ts +159 -0
  59. package/src/ingress/server-node.ts +376 -0
  60. package/src/ingress/server.ts +475 -0
  61. package/src/ingress/shared.ts +132 -0
  62. package/src/ingress/status.ts +97 -0
  63. package/src/ingress/types.ts +191 -0
  64. package/src/integration/batch.ts +58 -0
  65. package/src/integration/index.ts +29 -0
  66. package/src/integration/pipeline.ts +220 -0
  67. package/src/integration/streaming.ts +78 -0
  68. package/src/integration/websocket.ts +93 -0
  69. package/src/loader/batch.ts +137 -0
  70. package/src/loader/cost.ts +105 -0
  71. package/src/loader/index.ts +745 -0
  72. package/src/loader/ops.ts +657 -0
  73. package/src/loader/state.ts +84 -0
  74. package/src/loader/types.ts +145 -0
  75. package/src/native/ffi/constants.ts +58 -0
  76. package/src/native/ffi/selftest.ts +729 -0
  77. package/src/native/ffi/types.ts +474 -0
  78. package/src/native/ffi.ts +1775 -0
  79. package/src/native/index.ts +8 -207
  80. package/src/native/loader.ts +240 -0
  81. package/src/native/types/instances.ts +274 -0
  82. package/src/native/types.ts +401 -0
  83. package/src/runtime/builtins.ts +131 -0
  84. package/src/runtime/codec.ts +147 -0
  85. package/src/runtime/detect.ts +36 -0
  86. package/src/runtime/index.ts +28 -0
  87. package/src/runtime/native.ts +35 -0
  88. package/src/runtime/server.ts +31 -0
  89. package/src/runtime/transport.ts +64 -0
  90. package/src/runtime/types.ts +173 -0
  91. package/src/runtime/uuid.ts +45 -0
  92. package/src/runtime/websocket.ts +30 -0
  93. package/src/rust-ffi/addon.ts +10 -0
  94. package/src/rust-ffi/batch/build.ts +155 -0
  95. package/src/rust-ffi/batch/index.ts +8 -0
  96. package/src/rust-ffi/batch/types.ts +92 -0
  97. package/src/rust-ffi/client.ts +87 -0
  98. package/src/rust-ffi/context.ts +193 -0
  99. package/src/rust-ffi/index.ts +51 -541
  100. package/src/rust-ffi/into.ts +21 -0
  101. package/src/rust-ffi/options.ts +113 -0
  102. package/src/rust-ffi/packed.ts +253 -0
  103. package/src/rust-ffi/proven.ts +28 -0
  104. package/src/rust-ffi/scalar/crypto.ts +346 -0
  105. package/src/rust-ffi/scalar/factories.ts +459 -0
  106. package/src/rust-ffi/scalar/hashing.ts +74 -0
  107. package/src/rust-ffi/scalar/http.ts +337 -0
  108. package/src/rust-ffi/scalar/index.ts +31 -0
  109. package/src/rust-ffi/scalar/interface.ts +325 -0
  110. package/src/rust-ffi/scalar/json-packed.ts +175 -0
  111. package/src/rust-ffi/scalar/json.ts +61 -0
  112. package/src/rust-ffi/scalar/payload.ts +241 -0
  113. package/src/rust-ffi/text.ts +121 -0
  114. package/src/selection.json +194 -0
  115. package/src/selection.ts +73 -0
  116. package/src/shared/adaptive.ts +67 -0
  117. package/src/shared/buffer-pool.ts +264 -0
  118. package/src/shared/bytes.ts +108 -9
  119. package/src/shared/codec.ts +78 -0
  120. package/src/shared/env.ts +20 -0
  121. package/src/shared/log.ts +75 -0
  122. package/src/shared/metrics.ts +247 -0
  123. package/src/shared/packed/index.ts +10 -0
  124. package/src/shared/packed/parsers.ts +31 -0
  125. package/src/shared/packed/schema.ts +23 -0
  126. package/src/shared/packed/wire.ts +504 -0
  127. package/src/shared/proven.ts +289 -0
  128. package/src/shared/request-id.ts +67 -0
  129. package/src/shared/response.ts +104 -0
  130. package/src/shared/runtime.ts +29 -0
  131. package/src/shared/trace.ts +81 -0
  132. package/src/shared/uuid.ts +15 -0
  133. package/Cargo.toml +0 -73
  134. package/bench.ts +0 -3
  135. package/rust/batch.rs +0 -195
  136. package/rust/cookie_parser.rs +0 -70
  137. package/rust/cors.rs +0 -263
  138. package/rust/hashing.rs +0 -164
  139. package/rust/headers.rs +0 -234
  140. package/rust/hmac_sha256.rs +0 -113
  141. package/rust/http_parser.rs +0 -132
  142. package/rust/ingress.rs +0 -616
  143. package/rust/ingress_constants.rs +0 -89
  144. package/rust/ip_trust.rs +0 -185
  145. package/rust/json_ops.rs +0 -66
  146. package/rust/json_patch_ops.rs +0 -25
  147. package/rust/json_schema.rs +0 -151
  148. package/rust/json_ser.rs +0 -429
  149. package/rust/lib.rs +0 -48
  150. package/rust/method.rs +0 -141
  151. package/rust/mime_lookup.rs +0 -76
  152. package/rust/output.rs +0 -238
  153. package/rust/proxy.rs +0 -90
  154. package/rust/query_parser.rs +0 -141
  155. package/rust/random_token.rs +0 -52
  156. package/rust/rate_limit.rs +0 -203
  157. package/rust/terminal.rs +0 -95
  158. package/rust/unit_tests.rs +0 -843
  159. package/rust/url_codec.rs +0 -133
  160. package/rust/util.rs +0 -537
  161. package/rust/validation.rs +0 -107
  162. package/rust/websocket.rs +0 -23
  163. package/src/baseline/index.ts +0 -12
  164. package/src/baseline/tasks/cookie.ts +0 -22
  165. package/src/baseline/tasks/hashing.ts +0 -18
  166. package/src/baseline/tasks/hmac.ts +0 -38
  167. package/src/baseline/tasks/http.ts +0 -62
  168. package/src/baseline/tasks/json-patch.ts +0 -12
  169. package/src/baseline/tasks/json.ts +0 -50
  170. package/src/baseline/tasks/mime.ts +0 -5
  171. package/src/baseline/tasks/query.ts +0 -14
  172. package/src/baseline/tasks/token.ts +0 -15
  173. package/src/baseline/tasks/url.ts +0 -11
  174. package/src/baseline/tasks/validation.ts +0 -19
  175. package/src/baseline/tasks/websocket.ts +0 -14
  176. package/src/bench/assert.ts +0 -70
  177. package/src/bench/checks.ts +0 -227
  178. package/src/bench/checksum.ts +0 -27
  179. package/src/bench/comparisons.ts +0 -70
  180. package/src/bench/concurrent-worker.ts +0 -135
  181. package/src/bench/fixtures.ts +0 -233
  182. package/src/bench/index.ts +0 -2
  183. package/src/bench/measure.ts +0 -177
  184. package/src/bench/now.ts +0 -3
  185. package/src/bench/report.ts +0 -79
  186. package/src/bench/run.ts +0 -57
  187. package/src/bench/tasks/complex.ts +0 -232
  188. package/src/bench/tasks/concurrent.ts +0 -213
  189. package/src/bench/tasks/cookie.ts +0 -44
  190. package/src/bench/tasks/hashing.ts +0 -33
  191. package/src/bench/tasks/hmac.ts +0 -36
  192. package/src/bench/tasks/http.ts +0 -50
  193. package/src/bench/tasks/index.ts +0 -52
  194. package/src/bench/tasks/json-patch.ts +0 -21
  195. package/src/bench/tasks/json.ts +0 -33
  196. package/src/bench/tasks/mime.ts +0 -21
  197. package/src/bench/tasks/query.ts +0 -44
  198. package/src/bench/tasks/stress.ts +0 -142
  199. package/src/bench/tasks/token.ts +0 -20
  200. package/src/bench/tasks/url.ts +0 -34
  201. package/src/bench/tasks/validation.ts +0 -57
  202. package/src/bench/tasks/websocket.ts +0 -21
  203. package/src/bench/types.ts +0 -60
  204. package/src/data/json-rows.ts +0 -33
  205. package/src/ingress/packed-input.ts +0 -59
  206. package/src/shared/json.ts +0 -18
  207. package/src/shared/packed.ts +0 -316
@@ -0,0 +1,289 @@
1
+ // src/shared/proven.ts — baked "proven selection" registry (PURE DATA, no addon).
2
+ //
3
+ // The proven selection answers: "for op X, which implementation is the
4
+ // benchmark-proven winner, and is that what we wire as the default?"
5
+ //
6
+ // - 'native' → the rust addon wins (bun:ffi on Bun / napi on Node)
7
+ // - 'js' → the pure-TS implementation wins
8
+ // - 'bun' → a Bun built-in wins UNDER BUN and is delegated there
9
+ // (see src/runtime/builtins.ts)
10
+ //
11
+ // This is BAKED (committed), benchmark-derived data — the winners were
12
+ // measured by `scripts/select-native.ts` (the `nativeRatio` in
13
+ // `src/selection.json`) and the Bun built-in decision matrix
14
+ // (`docs/bun-builtins-decision-matrix.md`). It is PURE DATA: no addon import,
15
+ // no dlopen, no live benchmark audit. That live audit was the friction of the
16
+ // old `check:proven`/`check:annotate` machinery (flaky on noisy hosts) — it
17
+ // is intentionally NOT re-added. Instead `test/unit/shared/proven.test.ts`
18
+ // verifies every entry is wired to the impl it claims (`opImpl`, the Bun
19
+ // built-in registry, and the addon's embedded selection.json), so drift is
20
+ // caught deterministically instead of by re-running benchmarks.
21
+ //
22
+ // `typicalRatio` is the addon-vs-pure-TS-baseline ratio (rust faster = >1) for
23
+ // native/js entries, and the Bun-built-in-vs-addon ratio for 'bun' entries
24
+ // (higher = the built-in wins by more).
25
+
26
+ /** The benchmark-proven default implementation for an op. */
27
+ export type ProvenImpl = 'native' | 'js' | 'bun'
28
+
29
+ /** How decisive the baked winner is. */
30
+ export type ProvenStatus = 'proven' | 'parity' | 'unmeasured'
31
+
32
+ /** One entry in the baked proven-selection registry. */
33
+ export interface ProvenEntry {
34
+ /** Selection op name (matches `opImpl(op)` / `src/selection.json` keys). */
35
+ name: string
36
+ /** The baked default implementation: the benchmark-proven winner. */
37
+ impl: ProvenImpl
38
+ /** `proven` = decisive win, `parity` = borderline, `unmeasured` = pinned by policy. */
39
+ status: ProvenStatus
40
+ /** Measured ratio behind the decision (see module doc for the axis). */
41
+ typicalRatio?: number
42
+ /** Why this classification. */
43
+ note?: string
44
+ }
45
+
46
+ /**
47
+ * Baked proven-winner registry. Covering the FULL selection surface
48
+ * (`src/selection.json` ops + every Bun built-in delegation) so nothing can be
49
+ * silently dropped. Classifications are based on release/perf-build results;
50
+ * `test/unit/shared/proven.test.ts` re-checks them against the LIVE wiring.
51
+ */
52
+ export const PROVEN_SELECTION: readonly ProvenEntry[] = [
53
+ // ── Bun built-in delegation — 'bun' (wins under Bun; consumer binding = 'js') ──
54
+ {
55
+ name: 'crc32',
56
+ impl: 'bun',
57
+ status: 'proven',
58
+ typicalRatio: 4,
59
+ note: 'Bun.hash.crc32 wins 2.8–8.4×; selection.json records js (0.659)',
60
+ },
61
+ {
62
+ name: 'xxh3',
63
+ impl: 'bun',
64
+ status: 'proven',
65
+ typicalRatio: 4.15,
66
+ note: 'Bun.hash.xxHash3 ~4.15×; addon kept as the Node / non-Bun path',
67
+ },
68
+ {
69
+ name: 'hmacSha256',
70
+ impl: 'bun',
71
+ status: 'parity',
72
+ typicalRatio: 1.2,
73
+ note: 'Bun.CryptoHasher mild 1.1–1.4×; the addon still beats the pure-JS baseline (1.212)',
74
+ },
75
+ {
76
+ name: 'randomToken',
77
+ impl: 'bun',
78
+ status: 'proven',
79
+ typicalRatio: 1.62,
80
+ note: 'Bun.randomUUIDv7 / crypto.getRandomValues for token-sized output',
81
+ },
82
+ {
83
+ name: 'gzipCompress',
84
+ impl: 'bun',
85
+ status: 'proven',
86
+ typicalRatio: 2.02,
87
+ note: 'Bun.gzipSync ~2×; gzipDecompress deliberately NOT delegated (64 MiB bomb cap)',
88
+ },
89
+ {
90
+ name: 'urlEncode',
91
+ impl: 'bun',
92
+ status: 'proven',
93
+ typicalRatio: 11.5,
94
+ note: 'encodeURIComponent ~11.5×, zero alloc',
95
+ },
96
+ {
97
+ name: 'urlDecode',
98
+ impl: 'bun',
99
+ status: 'proven',
100
+ typicalRatio: 6,
101
+ note: 'decodeURIComponent ~4–8×',
102
+ },
103
+ {
104
+ name: 'base64Encode',
105
+ impl: 'bun',
106
+ status: 'proven',
107
+ typicalRatio: 2,
108
+ note: 'Buffer.toString("base64") ~2×; url-safe / unpadded fall through to native',
109
+ },
110
+ {
111
+ name: 'base64UrlEncode',
112
+ impl: 'bun',
113
+ status: 'proven',
114
+ typicalRatio: 2,
115
+ note: 'Buffer.toString("base64url")',
116
+ },
117
+ {
118
+ name: 'hexEncode',
119
+ impl: 'bun',
120
+ status: 'proven',
121
+ typicalRatio: 1.35,
122
+ note: 'Buffer.toString("hex")',
123
+ },
124
+ {
125
+ name: 'httpDate',
126
+ impl: 'bun',
127
+ status: 'proven',
128
+ typicalRatio: 3.7,
129
+ note: 'Date.toUTCString() ~3.7×',
130
+ },
131
+
132
+ // ── Native winners — 'native' (addon beats the pure-TS baseline) ──
133
+ { name: 'fnv1a64', impl: 'native', status: 'proven', typicalRatio: 12.603 },
134
+ { name: 'hmacSha256Verify', impl: 'native', status: 'proven', typicalRatio: 9.028 },
135
+ { name: 'signCookie', impl: 'native', status: 'proven', typicalRatio: 3.64 },
136
+ { name: 'verifyCookie', impl: 'native', status: 'proven', typicalRatio: 1.772 },
137
+ { name: 'csrfToken', impl: 'native', status: 'proven', typicalRatio: 12.671 },
138
+ { name: 'csrfVerify', impl: 'native', status: 'proven', typicalRatio: 12.458 },
139
+ {
140
+ name: 'passwordHash',
141
+ impl: 'native',
142
+ status: 'proven',
143
+ typicalRatio: 1.657,
144
+ note: 'argon2id — rust ~1.83× vs Bun.password at equal cost; never delegate',
145
+ },
146
+ {
147
+ name: 'passwordVerify',
148
+ impl: 'native',
149
+ status: 'proven',
150
+ typicalRatio: 1.88,
151
+ note: 'argon2id verify — rust wins; no representative pure-TS baseline (pinned)',
152
+ },
153
+ { name: 'aeadEncrypt', impl: 'native', status: 'proven', typicalRatio: 1.288 },
154
+ {
155
+ name: 'aeadDecrypt',
156
+ impl: 'native',
157
+ status: 'parity',
158
+ typicalRatio: 1.108,
159
+ note: 'borderline — kept native',
160
+ },
161
+ { name: 'createAcceptNegotiator', impl: 'native', status: 'proven', typicalRatio: 2.01 },
162
+ {
163
+ name: 'gzipDecompress',
164
+ impl: 'native',
165
+ status: 'proven',
166
+ typicalRatio: 61.039,
167
+ note: 'deliberately NOT delegated — Bun.gunzipSync has no bomb cap; native 64 MiB cap kept',
168
+ },
169
+ {
170
+ name: 'brotliCompress',
171
+ impl: 'native',
172
+ status: 'proven',
173
+ typicalRatio: 9.983,
174
+ note: 'no synchronous Bun API',
175
+ },
176
+ {
177
+ name: 'brotliDecompress',
178
+ impl: 'native',
179
+ status: 'proven',
180
+ typicalRatio: 33.327,
181
+ note: 'no synchronous Bun API',
182
+ },
183
+ {
184
+ name: 'jwtSign',
185
+ impl: 'native',
186
+ status: 'unmeasured',
187
+ note: 'pinned native — constant-time canonical (not pure-perf)',
188
+ },
189
+ {
190
+ name: 'jwtVerify',
191
+ impl: 'native',
192
+ status: 'unmeasured',
193
+ note: 'pinned native — constant-time canonical',
194
+ },
195
+ {
196
+ name: 'createTemplate',
197
+ impl: 'native',
198
+ status: 'unmeasured',
199
+ note: 'pinned native — compiled-once instance',
200
+ },
201
+ {
202
+ name: 'createSchemaValidator',
203
+ impl: 'native',
204
+ status: 'unmeasured',
205
+ note: 'pinned native — compiled-once instance',
206
+ },
207
+
208
+ // ── JS winners — 'js' (pure-TS beats the addon) ──
209
+ { name: 'queryPairs', impl: 'js', status: 'proven', typicalRatio: 0.279 },
210
+ { name: 'cookiePairs', impl: 'js', status: 'proven', typicalRatio: 0.105 },
211
+ { name: 'formPairs', impl: 'js', status: 'proven', typicalRatio: 0.328 },
212
+ { name: 'etag', impl: 'js', status: 'proven', typicalRatio: 0.187 },
213
+ { name: 'parseMediaType', impl: 'js', status: 'proven', typicalRatio: 0.378 },
214
+ { name: 'parseAcceptEncoding', impl: 'js', status: 'proven', typicalRatio: 0.367 },
215
+ { name: 'createConditionalRequest', impl: 'js', status: 'proven', typicalRatio: 0.275 },
216
+ {
217
+ name: 'multipartParse',
218
+ impl: 'js',
219
+ status: 'unmeasured',
220
+ note: 'no ratio recorded — JS baseline kept',
221
+ },
222
+ {
223
+ name: 'jsonValid',
224
+ impl: 'js',
225
+ status: 'parity',
226
+ typicalRatio: 0.749,
227
+ note: 'borderline — JSON.parse wins',
228
+ },
229
+ {
230
+ name: 'jsonPatch',
231
+ impl: 'js',
232
+ status: 'proven',
233
+ typicalRatio: 0.014,
234
+ note: 'custom JSON.patch beats the addon at small sizes',
235
+ },
236
+ { name: 'sseEncode', impl: 'js', status: 'proven', typicalRatio: 0.586 },
237
+ { name: 'wsFrameEncode', impl: 'js', status: 'proven', typicalRatio: 0.152 },
238
+ {
239
+ name: 'wsFrameDecode',
240
+ impl: 'js',
241
+ status: 'parity',
242
+ typicalRatio: 0.988,
243
+ note: 'parity boundary — JS baseline kept',
244
+ },
245
+ { name: 'wsAcceptKey', impl: 'js', status: 'parity', typicalRatio: 0.939 },
246
+ { name: 'validateEmail', impl: 'js', status: 'proven', typicalRatio: 0.007 },
247
+ { name: 'validateUuid', impl: 'js', status: 'proven', typicalRatio: 0.008 },
248
+ { name: 'validateIpv4', impl: 'js', status: 'proven', typicalRatio: 0.012 },
249
+ { name: 'validateIpv6', impl: 'js', status: 'proven', typicalRatio: 0.078 },
250
+ { name: 'createRateLimiter', impl: 'js', status: 'proven', typicalRatio: 0.061 },
251
+ { name: 'renderTemplate', impl: 'js', status: 'proven', typicalRatio: 0.141 },
252
+ ] as const satisfies readonly ProvenEntry[]
253
+
254
+ const INDEX = new Map<string, ProvenEntry>(PROVEN_SELECTION.map((e) => [e.name, e]))
255
+
256
+ /**
257
+ * The baked {@link ProvenEntry} for `op`, or `undefined` for unknown ops.
258
+ * Pure data — never dlopens the addon.
259
+ */
260
+ export function provenEntry(op: string): ProvenEntry | undefined {
261
+ return INDEX.get(op)
262
+ }
263
+
264
+ /** The baked default implementation for `op` (`native` / `js` / `bun`), or `null`. */
265
+ export function provenImpl(op: string): ProvenImpl | null {
266
+ return INDEX.get(op)?.impl ?? null
267
+ }
268
+
269
+ /** The baked status for `op`, or `null` for unknown ops. */
270
+ export function provenStatus(op: string): ProvenStatus | null {
271
+ return INDEX.get(op)?.status ?? null
272
+ }
273
+
274
+ /** Whether the op's baked winner is a decisive (`proven`) win. */
275
+ export function isProven(op: string): boolean {
276
+ return INDEX.get(op)?.status === 'proven'
277
+ }
278
+
279
+ /** The full baked registry (same array as {@link PROVEN_SELECTION}). */
280
+ export function provenSurface(): readonly ProvenEntry[] {
281
+ return PROVEN_SELECTION
282
+ }
283
+
284
+ /** A one-line summary of the baked selection (counts per implementation). */
285
+ export function provenSummary(): string {
286
+ const byImpl = { native: 0, js: 0, bun: 0 }
287
+ for (const e of PROVEN_SELECTION) byImpl[e.impl] += 1
288
+ return `proven selection: ${byImpl.native} native, ${byImpl.js} js, ${byImpl.bun} bun built-in (${PROVEN_SELECTION.length} ops)`
289
+ }
@@ -0,0 +1,67 @@
1
+ // src/shared/request-id.ts — fast counter-based request ID generator.
2
+ //
3
+ // Used by both the fast and pre-baked ingress paths. Produces a 16-byte hex
4
+ // string (as bytes) from a per-process counter seeded with boot randomness and
5
+ // an optional worker ID — no crypto, no allocation per call (reuses buffers).
6
+ //
7
+ // SIDE-EFFECT NOTE (deliberate, kept for zero-alloc hot-path perf): the module
8
+ // holds mutable shared state — a counter and reusable BINARY/HEX scratch
9
+ // buffers. `generateRequestId()` returns a SINGLE shared HEX buffer that is
10
+ // overwritten on the next call. Callers must decode or copy the returned bytes
11
+ // before the next call; the alias is confined to this module so the hazard is
12
+ // local and documented here.
13
+
14
+ const HEX_LOOKUP = new Uint8Array(256 * 2)
15
+ for (let i = 0; i < 256; i++) {
16
+ HEX_LOOKUP[i * 2] = '0123456789abcdef'.charCodeAt(i >> 4)
17
+ HEX_LOOKUP[i * 2 + 1] = '0123456789abcdef'.charCodeAt(i & 0x0f)
18
+ }
19
+
20
+ const BINARY = new Uint8Array(8)
21
+ const HEX = new Uint8Array(16)
22
+
23
+ const WORKER_ID = Number(process.env.INGRESS_WORKER_ID || 0) & 0xffff
24
+ // Boot randomness: one webcrypto draw, falling back to Math.random for hosts
25
+ // (older Node) without a `crypto` global. `[0]` is `number | undefined` under
26
+ // noUncheckedIndexedAccess — the `??` supplies the fallback for that too.
27
+ const BOOT_RANDOM =
28
+ typeof crypto !== 'undefined'
29
+ ? (crypto.getRandomValues(new Uint32Array(1))[0] ?? (Math.random() * 0xffffffff) >>> 0)
30
+ : (Math.random() * 0xffffffff) >>> 0
31
+
32
+ const COUNTER = {
33
+ hi: (BOOT_RANDOM ^ (WORKER_ID << 16)) >>> 0,
34
+ lo: 0,
35
+ }
36
+
37
+ /**
38
+ * Generate the next request ID as 16 lowercase hex bytes (zero-alloc).
39
+ *
40
+ * @returns A shared buffer aliased across calls — decode/copy before the next
41
+ * `generateRequestId()` invocation.
42
+ */
43
+ export function generateRequestId(): Uint8Array {
44
+ if (COUNTER.lo === 0xffffffff) {
45
+ COUNTER.lo = 0
46
+ COUNTER.hi = (COUNTER.hi + 1) >>> 0
47
+ } else {
48
+ COUNTER.lo++
49
+ }
50
+
51
+ BINARY[0] = (COUNTER.hi >>> 24) & 0xff
52
+ BINARY[1] = (COUNTER.hi >>> 16) & 0xff
53
+ BINARY[2] = (COUNTER.hi >>> 8) & 0xff
54
+ BINARY[3] = COUNTER.hi & 0xff
55
+ BINARY[4] = (COUNTER.lo >>> 24) & 0xff
56
+ BINARY[5] = (COUNTER.lo >>> 16) & 0xff
57
+ BINARY[6] = (COUNTER.lo >>> 8) & 0xff
58
+ BINARY[7] = COUNTER.lo & 0xff
59
+
60
+ for (let i = 0; i < 8; i++) {
61
+ const b = BINARY[i] ?? 0
62
+ HEX[i * 2] = HEX_LOOKUP[b * 2] ?? 0
63
+ HEX[i * 2 + 1] = HEX_LOOKUP[b * 2 + 1] ?? 0
64
+ }
65
+
66
+ return HEX
67
+ }
@@ -0,0 +1,104 @@
1
+ // src/shared/response.ts — Response helpers for pooled (reusable) output buffers.
2
+ //
3
+ // When a response body is backed by a pooled buffer, the pool handle must not
4
+ // be released until the body has actually been consumed, otherwise a later
5
+ // request could overwrite bytes the in-flight response still references.
6
+ // `pooledBodyResponse` wraps the slice in a streaming body that returns the
7
+ // handle to its pool once the body is read (or the request is aborted).
8
+
9
+ import type { PooledBuffer } from './buffer-pool'
10
+
11
+ /**
12
+ * Build a `Response` whose body is backed by a pooled byte slice, returning the
13
+ * pool handle once the body has been fully consumed (stream closed) or the
14
+ * request has been cancelled.
15
+ *
16
+ * The pooled buffer stays **in flight** (not reusable) until the response
17
+ * stream is closed/cancelled — this is what makes zero-copy pooling safe: a
18
+ * buffer is never handed to a second request while a previous response may
19
+ * still alias it.
20
+ *
21
+ * @param handle - The pooled-buffer handle to release on consumption.
22
+ * @param bytes - The (possibly shared) byte slice to serve as the body.
23
+ * @param init - Standard `ResponseInit` (status, headers, ...).
24
+ * @param timeoutMs - Optional abandonment guard (ms, default 0 = disabled):
25
+ * if the body is neither pulled nor cancelled within this window the pooled
26
+ * buffer is released and the stream closed. Opt-in — lets zero-copy
27
+ * responses bound memory under abandoned (unread) responses.
28
+ * @returns A `Response` that releases `handle` once its body is consumed.
29
+ *
30
+ * @remarks
31
+ * **Zero-copy caveat**: the body slice is served without copying — it aliases
32
+ * the pooled buffer. Consumers that hold a response open for a long time keep
33
+ * that buffer out of circulation. This helper is only intended for callers that
34
+ * explicitly opt into zero-copy (e.g. `INGRESS_UNSAFE_ZERO_COPY`); the default
35
+ * safe path copies the slice and releases the handle eagerly.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const handle = pool.acquire(size);
40
+ * const used = handle.buffer.subarray(0, written);
41
+ * return pooledBodyResponse(handle, used, { status: 200, headers });
42
+ * ```
43
+ */
44
+ export function pooledBodyResponse(
45
+ handle: PooledBuffer,
46
+ bytes: Uint8Array,
47
+ init: ResponseInit = {},
48
+ timeoutMs = 0,
49
+ ): Response {
50
+ if (bytes.byteLength === 0) {
51
+ // Nothing to serve: release immediately.
52
+ handle.release()
53
+ return new Response(null, init)
54
+ }
55
+
56
+ let released = false
57
+ let timer: ReturnType<typeof setTimeout> | undefined
58
+ const release = (): void => {
59
+ if (released) {
60
+ return
61
+ }
62
+ released = true
63
+ if (timer !== undefined) {
64
+ clearTimeout(timer)
65
+ }
66
+ handle.release()
67
+ }
68
+
69
+ const body = new ReadableStream<Uint8Array>({
70
+ start(controller) {
71
+ // Abandonment guard (opt-in): if the body is neither pulled nor
72
+ // cancelled within `timeoutMs`, release the pooled buffer so it can be
73
+ // reused and close the stream. Without this, an abandoned Response would
74
+ // hold the pooled buffer in flight forever (bounded only by maxInFlight).
75
+ // `pull()` re-checks `released` so a late read never serves the reused
76
+ // (potentially overwritten) bytes.
77
+ if (timeoutMs > 0) {
78
+ timer = setTimeout(() => {
79
+ if (released) {
80
+ return
81
+ }
82
+ release()
83
+ controller.close()
84
+ }, timeoutMs)
85
+ }
86
+ },
87
+ pull(controller) {
88
+ if (released) {
89
+ // Released by the abandonment guard (or a racing cancel): the pooled
90
+ // bytes may already be reused — never serve them. End-of-stream.
91
+ controller.close()
92
+ return
93
+ }
94
+ controller.enqueue(bytes)
95
+ controller.close()
96
+ release()
97
+ },
98
+ cancel() {
99
+ release()
100
+ },
101
+ })
102
+
103
+ return new Response(body, init)
104
+ }
@@ -0,0 +1,29 @@
1
+ // src/shared/runtime.ts — runtime detection helpers (facade).
2
+ //
3
+ // Facade over the cached detection in `src/runtime/detect.ts` (the ONLY place
4
+ // `typeof Bun` is checked — resolved ONCE at module load by the runtime
5
+ // adapter). Kept so existing `isBun()` / `isNode()` / `runtimeName()` /
6
+ // `nodeMajorVersion()` call sites work unchanged.
7
+
8
+ import {
9
+ detectedRuntime,
10
+ isBunRuntime,
11
+ isNodeRuntime,
12
+ nodeMajorVersion as nodeMajorVersionDetected,
13
+ } from '../runtime/detect'
14
+ import type { RuntimeName as RuntimeNameType } from '../runtime/types'
15
+
16
+ /** Host runtime identifier. */
17
+ export type RuntimeName = RuntimeNameType
18
+
19
+ /** Whether the current runtime is Bun (the primary target). Cached once at load. */
20
+ export const isBun: () => boolean = isBunRuntime
21
+
22
+ /** Whether the current runtime is Node.js. */
23
+ export const isNode: () => boolean = isNodeRuntime
24
+
25
+ /** Best-effort name of the current runtime. */
26
+ export const runtimeName: () => RuntimeName = () => detectedRuntime
27
+
28
+ /** The Node.js major version when running under Node, otherwise `null`. */
29
+ export const nodeMajorVersion: () => number | null = nodeMajorVersionDetected
@@ -0,0 +1,81 @@
1
+ // src/shared/trace.ts — W3C Trace Context helpers (zero-dep).
2
+ //
3
+ // Parses the `traceparent` request header and generates span ids so ingress
4
+ // logs/hooks can correlate requests across services. This is the "tracing"
5
+ // primitive — wire `parseTraceContext(req)` in an `onRequest` hook and pass the
6
+ // traceId/spanId into your structured logger (pino/otel), or use it directly.
7
+ //
8
+ // Format: `version-traceid-spanid-flags` (e.g. `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01`).
9
+
10
+ /** A parsed W3C trace context (valid per the spec's length/hex rules). */
11
+ export interface TraceContext {
12
+ /** Version, always `"00"` for accepted headers. */
13
+ version: string
14
+ /** 32 hex chars — the trace id (shared across the whole trace). */
15
+ traceId: string
16
+ /** 16 hex chars — the current span id. */
17
+ spanId: string
18
+ /** The flags byte (bit 0 = sampled). */
19
+ flags: number
20
+ /** True when the `sampled` (bit 0) flag is set. */
21
+ sampled: boolean
22
+ }
23
+
24
+ const HEX = /^[0-9a-fA-F]+$/
25
+
26
+ /**
27
+ * Parse a `traceparent` header value into a {@link TraceContext}, or `null`
28
+ * when malformed (wrong version / length / non-hex). Invalid contexts are
29
+ * ignored per the spec — callers should generate a fresh trace then.
30
+ */
31
+ export function parseTraceParent(header: string | null | undefined): TraceContext | null {
32
+ if (!header) return null
33
+ const parts = header.trim().split('-')
34
+ if (parts.length !== 4) return null
35
+ const [version, traceId, spanId, flagsStr] = parts
36
+ // Only version 00 is currently defined.
37
+ if (version !== '00') return null
38
+ if (traceId === undefined || traceId.length !== 32 || !HEX.test(traceId)) return null
39
+ if (spanId === undefined || spanId.length !== 16 || !HEX.test(spanId)) return null
40
+ if (flagsStr === undefined || flagsStr.length !== 2 || !HEX.test(flagsStr)) return null
41
+ const flags = Number.parseInt(flagsStr, 16)
42
+ return {
43
+ version,
44
+ traceId: traceId.toLowerCase(),
45
+ spanId: spanId.toLowerCase(),
46
+ flags,
47
+ sampled: (flags & 0x01) === 0x01,
48
+ }
49
+ }
50
+
51
+ /** Random 32-hex trace id (crypto-backed where available). */
52
+ export function createTraceId(): string {
53
+ return randomHex(16)
54
+ }
55
+
56
+ /** Random 16-hex span id. */
57
+ export function createSpanId(): string {
58
+ return randomHex(8)
59
+ }
60
+
61
+ /** Generate `n` random bytes as lowercase hex. */
62
+ function randomHex(bytes: number): string {
63
+ const buf = new Uint8Array(bytes)
64
+ const c = globalThis.crypto
65
+ if (c?.getRandomValues) {
66
+ c.getRandomValues(buf)
67
+ } else {
68
+ for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256)
69
+ }
70
+ let out = ''
71
+ for (let i = 0; i < bytes; i++) {
72
+ out += (buf[i] ?? 0).toString(16).padStart(2, '0')
73
+ }
74
+ return out
75
+ }
76
+
77
+ /** Serialize a trace context back to a `traceparent` header value. */
78
+ export function serializeTraceParent(ctx: TraceContext): string {
79
+ const flags = ctx.flags.toString(16).padStart(2, '0')
80
+ return `${ctx.version}-${ctx.traceId}-${ctx.spanId}-${flags}`
81
+ }
@@ -0,0 +1,15 @@
1
+ // src/shared/uuid.ts — UUIDv7 generation (facade).
2
+ //
3
+ // UUIDv7 delegates to the runtime adapter's uuid (`src/runtime/uuid.ts`):
4
+ // `Bun.randomUUIDv7` on Bun (measured ~2x faster than the FFI-crossing rust
5
+ // path) and `crypto.randomUUID` (RFC 4122 v4) under Node.
6
+
7
+ import { runtimeUuid } from '../runtime/uuid'
8
+
9
+ /**
10
+ * Generate a UUIDv7 (time-ordered) string, preferring Bun's native built-in.
11
+ * Under Node this falls back to `crypto.randomUUID` (v4).
12
+ *
13
+ * @returns A UUID string.
14
+ */
15
+ export const uuidv7: () => string = runtimeUuid.uuidv7
package/Cargo.toml DELETED
@@ -1,73 +0,0 @@
1
- [package]
2
- name = "castrum"
3
- # Keep in sync with package.json "version" (single source of truth: the npm package).
4
- version = "0.7.0"
5
- edition = "2021"
6
-
7
- [lib]
8
- name = "castrum"
9
- crate-type = ["cdylib"]
10
- path = "rust/lib.rs"
11
-
12
- [dependencies]
13
- napi = { version = "3", features = ["napi10"] }
14
- napi-derive = "3"
15
-
16
- serde = { version = "1", features = ["derive"] }
17
- serde_json = "1"
18
- sonic-rs = "0.5"
19
-
20
- # default-features=false: drop resolve-http + tls-aws-lc-rs which pull in
21
- # reqwest -> hyper/h2/tokio/rustls (large binary, attack surface). Format
22
- # validators (email etc.) are built-in and NOT feature-gated in 0.48.
23
- jsonschema = { version = "0.48", default-features = false, features = ["resolve-file"] }
24
- json-patch = "4"
25
- aws-lc-rs = "1"
26
- base64 = "0.23"
27
- crc32fast = "1"
28
- httparse = "1"
29
- mime_guess = "2"
30
- phf = { version = "0.14", features = ["macros"] }
31
- getrandom = "0.4"
32
- memchr = "2"
33
- simdutf8 = "0.1"
34
- email_address = "0.2"
35
- fast_chemail = { version = "0.9", optional = true }
36
- rayon = "1"
37
- # mimalloc is set as the Rust #[global_allocator] (see rust/lib.rs). We DELIBERATELY
38
- # do NOT enable the "override" feature: it interposes libc malloc/free at the
39
- # process level, which is an embedding hazard when this cdylib is dlopen'd into
40
- # Bun (which itself embeds mimalloc -> symbol clashes). The Rust-level global
41
- # allocator keeps most of the allocation win without replacing the host malloc.
42
- mimalloc = { version = "0.1", features = ["local_dynamic_tls"] }
43
- core_affinity = "0.8"
44
-
45
- parking_lot = "0.12"
46
- xxhash-rust = { version = "0.8", features = ["xxh3"] }
47
- ipnet = "2"
48
- lru = "0.12"
49
-
50
- [build-dependencies]
51
- napi-build = "2"
52
-
53
- [features]
54
- default = ["fast-email"]
55
- fast-email = ["dep:fast_chemail"]
56
-
57
- [profile.release]
58
- opt-level = 3
59
- lto = "fat"
60
- codegen-units = 1
61
- strip = "symbols"
62
- debug = 0
63
- incremental = false
64
- # Safety: this is an embedded cdylib loaded into the Bun process. panic = "unwind"
65
- # (the default) lets napi's catch_unwind convert Rust panics into JS exceptions
66
- # instead of aborting the host process. overflow-checks = true catches integer
67
- # overflow in release; the cost is negligible relative to the safety gain.
68
- overflow-checks = true
69
- debug-assertions = false
70
-
71
- [profile.release.package."*"]
72
- opt-level = 3
73
- codegen-units = 1
package/bench.ts DELETED
@@ -1,3 +0,0 @@
1
- import { runBenchmark } from "./src/bench";
2
-
3
- runBenchmark();