castrum 0.7.0 → 0.9.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 (176) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -59
  3. package/castrum.linux-x64-gnu.node +0 -0
  4. package/dist/index.d.ts +3161 -0
  5. package/dist/index.js +7306 -0
  6. package/docs/ARCHITECTURE.md +415 -0
  7. package/docs/BENCHMARKS.md +307 -0
  8. package/docs/ENVIRONMENT.md +79 -0
  9. package/docs/GETTING_STARTED.md +209 -0
  10. package/docs/INGRESS.md +745 -0
  11. package/docs/REPO_MAP.md +259 -0
  12. package/docs/adr/0001-two-wire-formats.md +39 -0
  13. package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
  14. package/docs/adr/0003-bun-builtins-delegation.md +42 -0
  15. package/docs/bun-builtins-decision-matrix.md +108 -0
  16. package/index.ts +51 -8
  17. package/package.json +73 -20
  18. package/src/data/json-rows.ts +13 -13
  19. package/src/ingress/body.ts +149 -0
  20. package/src/ingress/constants.ts +220 -48
  21. package/src/ingress/context.ts +136 -0
  22. package/src/ingress/decode/baked-result.ts +121 -0
  23. package/src/ingress/decode/fast-result.ts +160 -0
  24. package/src/ingress/decode/packed-sections.ts +56 -0
  25. package/src/ingress/decode/result-base.ts +172 -0
  26. package/src/ingress/errors.ts +72 -0
  27. package/src/ingress/fast.ts +152 -1021
  28. package/src/ingress/handlers.ts +563 -1001
  29. package/src/ingress/headers/baked-templates.ts +102 -0
  30. package/src/ingress/headers/cors.ts +44 -0
  31. package/src/ingress/headers/fast-templates.ts +238 -0
  32. package/src/ingress/headers/hsts.ts +83 -0
  33. package/src/ingress/health.ts +67 -0
  34. package/src/ingress/index.ts +130 -405
  35. package/src/ingress/metrics.ts +132 -0
  36. package/src/ingress/options.ts +197 -0
  37. package/src/ingress/packing/gather-raw-headers.ts +75 -0
  38. package/src/ingress/packing/header-packing.ts +75 -0
  39. package/src/ingress/packing/input-packer.ts +150 -0
  40. package/src/ingress/packing/scratch.ts +50 -0
  41. package/src/ingress/packing/select-headers.ts +96 -0
  42. package/src/ingress/response/error-bodies.ts +75 -0
  43. package/src/ingress/response/terminal.ts +59 -0
  44. package/src/ingress/routes/common.ts +38 -0
  45. package/src/ingress/routes/delete.ts +14 -0
  46. package/src/ingress/routes/echo.ts +107 -0
  47. package/src/ingress/routes/fallback.ts +23 -0
  48. package/src/ingress/routes/head.ts +37 -0
  49. package/src/ingress/routes/index.ts +10 -0
  50. package/src/ingress/routes/json-write.ts +153 -0
  51. package/src/ingress/routes/options.ts +36 -0
  52. package/src/ingress/routes/read.ts +45 -0
  53. package/src/ingress/server-node.ts +376 -0
  54. package/src/ingress/server.ts +443 -0
  55. package/src/ingress/shared.ts +132 -0
  56. package/src/ingress/status.ts +97 -0
  57. package/src/ingress/types.ts +157 -0
  58. package/src/integration/batch.ts +58 -0
  59. package/src/integration/index.ts +29 -0
  60. package/src/integration/pipeline.ts +217 -0
  61. package/src/integration/streaming.ts +79 -0
  62. package/src/integration/websocket.ts +92 -0
  63. package/src/loader/batch.ts +133 -0
  64. package/src/loader/cost.ts +104 -0
  65. package/src/loader/index.ts +930 -0
  66. package/src/loader/ops.ts +657 -0
  67. package/src/native/ffi.ts +1541 -0
  68. package/src/native/index.ts +8 -207
  69. package/src/native/loader.ts +194 -0
  70. package/src/native/types.ts +598 -0
  71. package/src/rust-ffi/addon.ts +10 -0
  72. package/src/rust-ffi/batch.ts +431 -0
  73. package/src/rust-ffi/client.ts +88 -0
  74. package/src/rust-ffi/context.ts +164 -0
  75. package/src/rust-ffi/index.ts +40 -541
  76. package/src/rust-ffi/options.ts +113 -0
  77. package/src/rust-ffi/packed.ts +253 -0
  78. package/src/rust-ffi/proven.ts +42 -0
  79. package/src/rust-ffi/scalar/crypto.ts +265 -0
  80. package/src/rust-ffi/scalar/factories.ts +233 -0
  81. package/src/rust-ffi/scalar/hashing.ts +72 -0
  82. package/src/rust-ffi/scalar/http.ts +206 -0
  83. package/src/rust-ffi/scalar/index.ts +31 -0
  84. package/src/rust-ffi/scalar/interface.ts +450 -0
  85. package/src/rust-ffi/scalar/json.ts +45 -0
  86. package/src/rust-ffi/scalar/payload.ts +124 -0
  87. package/src/rust-ffi/text.ts +95 -0
  88. package/src/selection.json +194 -0
  89. package/src/selection.ts +77 -0
  90. package/src/shared/adaptive.ts +68 -0
  91. package/src/shared/bench-classify.ts +209 -0
  92. package/src/shared/buffer-pool.ts +209 -0
  93. package/src/shared/bytes.ts +35 -5
  94. package/src/shared/env.ts +20 -0
  95. package/src/shared/log.ts +74 -0
  96. package/src/shared/metrics.ts +247 -0
  97. package/src/shared/packed.ts +350 -154
  98. package/src/shared/proven.ts +596 -0
  99. package/src/shared/request-id.ts +67 -0
  100. package/src/shared/response.ts +104 -0
  101. package/src/shared/runtime.ts +34 -0
  102. package/src/shared/trace.ts +81 -0
  103. package/src/shared/uuid.ts +31 -0
  104. package/Cargo.toml +0 -73
  105. package/bench.ts +0 -3
  106. package/rust/batch.rs +0 -195
  107. package/rust/cookie_parser.rs +0 -70
  108. package/rust/cors.rs +0 -263
  109. package/rust/hashing.rs +0 -164
  110. package/rust/headers.rs +0 -234
  111. package/rust/hmac_sha256.rs +0 -113
  112. package/rust/http_parser.rs +0 -132
  113. package/rust/ingress.rs +0 -616
  114. package/rust/ingress_constants.rs +0 -89
  115. package/rust/ip_trust.rs +0 -185
  116. package/rust/json_ops.rs +0 -66
  117. package/rust/json_patch_ops.rs +0 -25
  118. package/rust/json_schema.rs +0 -151
  119. package/rust/json_ser.rs +0 -429
  120. package/rust/lib.rs +0 -48
  121. package/rust/method.rs +0 -141
  122. package/rust/mime_lookup.rs +0 -76
  123. package/rust/output.rs +0 -238
  124. package/rust/proxy.rs +0 -90
  125. package/rust/query_parser.rs +0 -141
  126. package/rust/random_token.rs +0 -52
  127. package/rust/rate_limit.rs +0 -203
  128. package/rust/terminal.rs +0 -95
  129. package/rust/unit_tests.rs +0 -843
  130. package/rust/url_codec.rs +0 -133
  131. package/rust/util.rs +0 -537
  132. package/rust/validation.rs +0 -107
  133. package/rust/websocket.rs +0 -23
  134. package/src/baseline/index.ts +0 -12
  135. package/src/baseline/tasks/cookie.ts +0 -22
  136. package/src/baseline/tasks/hashing.ts +0 -18
  137. package/src/baseline/tasks/hmac.ts +0 -38
  138. package/src/baseline/tasks/http.ts +0 -62
  139. package/src/baseline/tasks/json-patch.ts +0 -12
  140. package/src/baseline/tasks/json.ts +0 -50
  141. package/src/baseline/tasks/mime.ts +0 -5
  142. package/src/baseline/tasks/query.ts +0 -14
  143. package/src/baseline/tasks/token.ts +0 -15
  144. package/src/baseline/tasks/url.ts +0 -11
  145. package/src/baseline/tasks/validation.ts +0 -19
  146. package/src/baseline/tasks/websocket.ts +0 -14
  147. package/src/bench/assert.ts +0 -70
  148. package/src/bench/checks.ts +0 -227
  149. package/src/bench/checksum.ts +0 -27
  150. package/src/bench/comparisons.ts +0 -70
  151. package/src/bench/concurrent-worker.ts +0 -135
  152. package/src/bench/fixtures.ts +0 -233
  153. package/src/bench/index.ts +0 -2
  154. package/src/bench/measure.ts +0 -177
  155. package/src/bench/now.ts +0 -3
  156. package/src/bench/report.ts +0 -79
  157. package/src/bench/run.ts +0 -57
  158. package/src/bench/tasks/complex.ts +0 -232
  159. package/src/bench/tasks/concurrent.ts +0 -213
  160. package/src/bench/tasks/cookie.ts +0 -44
  161. package/src/bench/tasks/hashing.ts +0 -33
  162. package/src/bench/tasks/hmac.ts +0 -36
  163. package/src/bench/tasks/http.ts +0 -50
  164. package/src/bench/tasks/index.ts +0 -52
  165. package/src/bench/tasks/json-patch.ts +0 -21
  166. package/src/bench/tasks/json.ts +0 -33
  167. package/src/bench/tasks/mime.ts +0 -21
  168. package/src/bench/tasks/query.ts +0 -44
  169. package/src/bench/tasks/stress.ts +0 -142
  170. package/src/bench/tasks/token.ts +0 -20
  171. package/src/bench/tasks/url.ts +0 -34
  172. package/src/bench/tasks/validation.ts +0 -57
  173. package/src/bench/tasks/websocket.ts +0 -21
  174. package/src/bench/types.ts +0 -60
  175. package/src/ingress/packed-input.ts +0 -59
  176. package/src/shared/json.ts +0 -18
@@ -0,0 +1,133 @@
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
+ export function createTickCoalescer(
98
+ flush: (op: string, requests: LoadRequest[]) => void,
99
+ ): TickCoalescer {
100
+ let buffer = new Map<string, LoadRequest[]>()
101
+ let scheduled = false
102
+ let pending = 0
103
+
104
+ function schedule() {
105
+ if (scheduled) return
106
+ scheduled = true
107
+ queueMicrotask(() => {
108
+ scheduled = false
109
+ const snapshot = buffer
110
+ buffer = new Map()
111
+ pending = 0
112
+ for (const [op, requests] of snapshot) {
113
+ if (requests.length > 0) flush(op, requests)
114
+ }
115
+ })
116
+ }
117
+
118
+ return {
119
+ enqueue(op, request) {
120
+ let list = buffer.get(op)
121
+ if (!list) {
122
+ list = []
123
+ buffer.set(op, list)
124
+ }
125
+ list.push(request)
126
+ pending++
127
+ schedule()
128
+ },
129
+ get pending() {
130
+ return pending
131
+ },
132
+ }
133
+ }
@@ -0,0 +1,104 @@
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
+ export interface CostModelOptions {
54
+ /** Allow the model to adjust `batchMin` from measurement. Default true. */
55
+ adaptive?: boolean
56
+ /** Initial batch threshold in [2, 8]. Default 2 (batch wins for n >= 2). */
57
+ batchMin?: number
58
+ }
59
+
60
+ /** Build a per-op cost model. */
61
+ export function createCostModel(options: CostModelOptions = {}): LoaderCostModel {
62
+ const adaptive = options.adaptive !== false
63
+ let batchMin = clampBatchMin(options.batchMin ?? MIN_BATCH_MIN)
64
+ let scalarPerItemNs = 0
65
+ let batchPerItemNs = 0
66
+ let samples = 0
67
+
68
+ function ewma(prev: number, next: number): number {
69
+ return prev === 0 ? next : prev * (1 - EWMA_ALPHA) + next * EWMA_ALPHA
70
+ }
71
+
72
+ return {
73
+ decide(n) {
74
+ if (n <= 1) return false
75
+ return n >= batchMin
76
+ },
77
+ recordScalar(n, elapsedNs) {
78
+ samples++
79
+ const perItem = n > 0 ? elapsedNs / n : elapsedNs
80
+ scalarPerItemNs = ewma(scalarPerItemNs, perItem)
81
+ if (adaptive && n >= 2 && batchPerItemNs > 0 && perItem < batchPerItemNs * 0.8) {
82
+ batchMin = clampBatchMin(batchMin + 1)
83
+ }
84
+ },
85
+ recordBatch(n, elapsedNs) {
86
+ samples++
87
+ const perItem = n > 0 ? elapsedNs / n : elapsedNs
88
+ batchPerItemNs = ewma(batchPerItemNs, perItem)
89
+ if (adaptive && scalarPerItemNs > 0) {
90
+ if (perItem > scalarPerItemNs * 1.2) {
91
+ batchMin = clampBatchMin(batchMin + 1)
92
+ } else if (perItem < scalarPerItemNs * 0.8) {
93
+ batchMin = clampBatchMin(batchMin - 1)
94
+ }
95
+ }
96
+ },
97
+ get batchMin() {
98
+ return batchMin
99
+ },
100
+ get stats() {
101
+ return { batchMin, scalarPerItemNs, batchPerItemNs, samples }
102
+ },
103
+ }
104
+ }