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,137 @@
1
+ // src/loader/batch.ts — Microtask coalescer + bounded LRU cache.
2
+ //
3
+ // The coalescer is the DataLoader-style engine: N `load()` calls made within
4
+ // the same event-loop tick are grouped by op and flushed together in ONE
5
+ // packed native batch call (reducing N native crossings → 1). The cache is a
6
+ // bounded LRU (default key = fnv1a64 of the input) for repeated-key hot loads.
7
+
8
+ /** A pending `load()` request inside a coalescing tick. */
9
+ export interface LoadRequest {
10
+ input: Uint8Array
11
+ resolve(value: unknown): void
12
+ reject(err: unknown): void
13
+ /** Cache key (only set when caching is enabled for this request). */
14
+ key?: string | bigint
15
+ /**
16
+ * The default cache key should be computed lazily at flush time (coalesce
17
+ * mode deferral) so all keys are computed in ONE packed native crossing.
18
+ */
19
+ needsDefaultKey?: boolean
20
+ }
21
+
22
+ /** Bounded LRU cache used by the loader (Hot Function Cache). */
23
+ export interface LoaderCache {
24
+ get(key: string | bigint): unknown
25
+ set(key: string | bigint, value: unknown): void
26
+ clear(): void
27
+ readonly size: number
28
+ readonly evictions: number
29
+ readonly hits: number
30
+ }
31
+
32
+ /** A cache that never stores anything (maxKeys <= 0). */
33
+ const NULL_CACHE: LoaderCache = {
34
+ get: () => undefined,
35
+ set: () => {},
36
+ clear: () => {},
37
+ size: 0,
38
+ evictions: 0,
39
+ hits: 0,
40
+ }
41
+
42
+ /**
43
+ * Bounded LRU cache. `maxKeys <= 0` disables caching entirely.
44
+ * LRU order is maintained by delete+re-insert on access (Map order).
45
+ */
46
+ export function createLruCache(maxKeys: number): LoaderCache {
47
+ if (maxKeys <= 0) return NULL_CACHE
48
+ const map = new Map<string | bigint, unknown>()
49
+ let evictions = 0
50
+ let hits = 0
51
+
52
+ return {
53
+ get(key) {
54
+ const value = map.get(key)
55
+ if (value === undefined) return undefined
56
+ hits++
57
+ map.delete(key)
58
+ map.set(key, value)
59
+ return value
60
+ },
61
+ set(key, value) {
62
+ if (map.has(key)) map.delete(key)
63
+ map.set(key, value)
64
+ if (map.size > maxKeys) {
65
+ const oldest = map.keys().next().value
66
+ if (oldest !== undefined) {
67
+ map.delete(oldest)
68
+ evictions++
69
+ }
70
+ }
71
+ },
72
+ clear() {
73
+ map.clear()
74
+ },
75
+ get size() {
76
+ return map.size
77
+ },
78
+ get evictions() {
79
+ return evictions
80
+ },
81
+ get hits() {
82
+ return hits
83
+ },
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Per-tick coalescer. `enqueue` schedules a single `queueMicrotask` flush that
89
+ * drains ALL pending ops; the flush callback receives one group per op.
90
+ */
91
+ export interface TickCoalescer {
92
+ enqueue(op: string, request: LoadRequest): void
93
+ /** Number of requests not yet flushed. */
94
+ readonly pending: number
95
+ }
96
+
97
+ /**
98
+ * Create a microtask-based coalescer: `enqueue` schedules one flush that
99
+ * drains ALL pending ops; each op group is delivered to `flush` once.
100
+ */
101
+ export function createTickCoalescer(
102
+ flush: (op: string, requests: LoadRequest[]) => void,
103
+ ): TickCoalescer {
104
+ let buffer = new Map<string, LoadRequest[]>()
105
+ let scheduled = false
106
+ let pending = 0
107
+
108
+ function schedule() {
109
+ if (scheduled) return
110
+ scheduled = true
111
+ queueMicrotask(() => {
112
+ scheduled = false
113
+ const snapshot = buffer
114
+ buffer = new Map()
115
+ pending = 0
116
+ for (const [op, requests] of snapshot) {
117
+ if (requests.length > 0) flush(op, requests)
118
+ }
119
+ })
120
+ }
121
+
122
+ return {
123
+ enqueue(op, request) {
124
+ let list = buffer.get(op)
125
+ if (!list) {
126
+ list = []
127
+ buffer.set(op, list)
128
+ }
129
+ list.push(request)
130
+ pending++
131
+ schedule()
132
+ },
133
+ get pending() {
134
+ return pending
135
+ },
136
+ }
137
+ }
@@ -0,0 +1,105 @@
1
+ // src/loader/cost.ts — Adaptive scalar-vs-batch cost model.
2
+ //
3
+ // Each op owns one cost model. It learns from ACTUAL dispatch timings (EWMA)
4
+ // and decides, at runtime, whether a bulk of `n` items should go through the
5
+ // packed batch path (ONE native crossing) or a scalar loop (`n` crossings).
6
+ //
7
+ // - `n === 1` always routes to the scalar path.
8
+ // - `n >= batchMin` routes to the batch path.
9
+ // - `batchMin` starts at the configured value (default 2) and adapts in
10
+ // [2, 8]: if measured batch-per-item cost stays above the scalar-per-item
11
+ // cost, `batchMin` rises so tiny batches fall back to the scalar loop; if
12
+ // batching wins, `batchMin` sinks back to 2.
13
+ //
14
+ // Timing is sampled (every `sampleEvery` dispatches) so the hot path does NOT
15
+ // pay a `hrtime` call on every invocation.
16
+
17
+ /** A per-op adaptive cost model. */
18
+ export interface LoaderCostModel {
19
+ /**
20
+ * Should a bulk of `n` items use the packed batch path?
21
+ * `false` → scalar loop (`n` crossings).
22
+ */
23
+ decide(n: number): boolean
24
+ /** Record a scalar-path dispatch of `n` items taking `elapsedNs`. */
25
+ recordScalar(n: number, elapsedNs: number): void
26
+ /** Record a batch-path dispatch of `n` items taking `elapsedNs`. */
27
+ recordBatch(n: number, elapsedNs: number): void
28
+ /** Current learned batch threshold. */
29
+ readonly batchMin: number
30
+ /** Snapshot for observability/tests. */
31
+ readonly stats: {
32
+ batchMin: number
33
+ scalarPerItemNs: number
34
+ batchPerItemNs: number
35
+ samples: number
36
+ }
37
+ }
38
+
39
+ const MIN_BATCH_MIN = 2
40
+ const MAX_BATCH_MIN = 8
41
+ const EWMA_ALPHA = 0.3
42
+
43
+ function clampBatchMin(v: number): number {
44
+ return Math.max(MIN_BATCH_MIN, Math.min(MAX_BATCH_MIN, v))
45
+ }
46
+
47
+ /** High-resolution nanosecond clock (Bun/Node). */
48
+ export const nowNs: () => number =
49
+ typeof process !== 'undefined' && typeof process.hrtime === 'function'
50
+ ? () => Number(process.hrtime.bigint())
51
+ : () => performance.now() * 1e6
52
+
53
+ /** Options for `createCostModel` (adaptive batch-threshold learning). */
54
+ export interface CostModelOptions {
55
+ /** Allow the model to adjust `batchMin` from measurement. Default true. */
56
+ adaptive?: boolean
57
+ /** Initial batch threshold in [2, 8]. Default 2 (batch wins for n >= 2). */
58
+ batchMin?: number
59
+ }
60
+
61
+ /** Build a per-op cost model. */
62
+ export function createCostModel(options: CostModelOptions = {}): LoaderCostModel {
63
+ const adaptive = options.adaptive !== false
64
+ let batchMin = clampBatchMin(options.batchMin ?? MIN_BATCH_MIN)
65
+ let scalarPerItemNs = 0
66
+ let batchPerItemNs = 0
67
+ let samples = 0
68
+
69
+ function ewma(prev: number, next: number): number {
70
+ return prev === 0 ? next : prev * (1 - EWMA_ALPHA) + next * EWMA_ALPHA
71
+ }
72
+
73
+ return {
74
+ decide(n) {
75
+ if (n <= 1) return false
76
+ return n >= batchMin
77
+ },
78
+ recordScalar(n, elapsedNs) {
79
+ samples++
80
+ const perItem = n > 0 ? elapsedNs / n : elapsedNs
81
+ scalarPerItemNs = ewma(scalarPerItemNs, perItem)
82
+ if (adaptive && n >= 2 && batchPerItemNs > 0 && perItem < batchPerItemNs * 0.8) {
83
+ batchMin = clampBatchMin(batchMin + 1)
84
+ }
85
+ },
86
+ recordBatch(n, elapsedNs) {
87
+ samples++
88
+ const perItem = n > 0 ? elapsedNs / n : elapsedNs
89
+ batchPerItemNs = ewma(batchPerItemNs, perItem)
90
+ if (adaptive && scalarPerItemNs > 0) {
91
+ if (perItem > scalarPerItemNs * 1.2) {
92
+ batchMin = clampBatchMin(batchMin + 1)
93
+ } else if (perItem < scalarPerItemNs * 0.8) {
94
+ batchMin = clampBatchMin(batchMin - 1)
95
+ }
96
+ }
97
+ },
98
+ get batchMin() {
99
+ return batchMin
100
+ },
101
+ get stats() {
102
+ return { batchMin, scalarPerItemNs, batchPerItemNs, samples }
103
+ },
104
+ }
105
+ }