castrum 0.9.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 (102) hide show
  1. package/README.md +153 -630
  2. package/castrum.linux-x64-gnu.node +0 -0
  3. package/dist/index.d.ts +1374 -1772
  4. package/dist/index.js +5570 -4169
  5. package/docs/API.md +337 -0
  6. package/docs/ARCHITECTURE.md +106 -14
  7. package/docs/BENCHMARKS.md +83 -47
  8. package/docs/CASE_STUDY.md +240 -0
  9. package/docs/ENVIRONMENT.md +8 -6
  10. package/docs/FFI_BUN_GUIDE.md +327 -0
  11. package/docs/GETTING_STARTED.md +15 -32
  12. package/docs/INGRESS-ROUTER.md +81 -0
  13. package/docs/INGRESS.md +5 -4
  14. package/docs/NATIVE-ROUTE.md +53 -0
  15. package/docs/REPO_MAP.md +46 -26
  16. package/docs/adr/0003-bun-builtins-delegation.md +6 -5
  17. package/docs/bun-builtins-decision-matrix.md +25 -18
  18. package/index.ts +9 -16
  19. package/package.json +36 -24
  20. package/src/ingress/body.ts +167 -27
  21. package/src/ingress/handlers.ts +91 -15
  22. package/src/ingress/health.ts +1 -0
  23. package/src/ingress/index.ts +39 -27
  24. package/src/ingress/packing/gather-raw-headers.ts +85 -21
  25. package/src/ingress/packing/header-packing.ts +5 -5
  26. package/src/ingress/packing/input-packer.ts +17 -3
  27. package/src/ingress/packing/select-headers.ts +12 -6
  28. package/src/ingress/router.ts +237 -0
  29. package/src/ingress/routes/common.ts +42 -1
  30. package/src/ingress/routes/fallback.ts +2 -2
  31. package/src/ingress/routes/head.ts +3 -13
  32. package/src/ingress/routes/index.ts +5 -4
  33. package/src/ingress/routes/json-write.ts +7 -31
  34. package/src/ingress/routes/options.ts +2 -12
  35. package/src/ingress/routes/read.ts +3 -13
  36. package/src/ingress/routes/responder.ts +159 -0
  37. package/src/ingress/server-node.ts +6 -6
  38. package/src/ingress/server.ts +45 -13
  39. package/src/ingress/types.ts +35 -1
  40. package/src/integration/pipeline.ts +3 -0
  41. package/src/integration/streaming.ts +2 -3
  42. package/src/integration/websocket.ts +4 -3
  43. package/src/loader/batch.ts +4 -0
  44. package/src/loader/cost.ts +1 -0
  45. package/src/loader/index.ts +43 -228
  46. package/src/loader/state.ts +84 -0
  47. package/src/loader/types.ts +145 -0
  48. package/src/native/ffi/constants.ts +58 -0
  49. package/src/native/ffi/selftest.ts +729 -0
  50. package/src/native/ffi/types.ts +474 -0
  51. package/src/native/ffi.ts +1063 -829
  52. package/src/native/loader.ts +52 -6
  53. package/src/native/types/instances.ts +274 -0
  54. package/src/native/types.ts +41 -238
  55. package/src/runtime/builtins.ts +131 -0
  56. package/src/runtime/codec.ts +147 -0
  57. package/src/runtime/detect.ts +36 -0
  58. package/src/runtime/index.ts +28 -0
  59. package/src/runtime/native.ts +35 -0
  60. package/src/runtime/server.ts +31 -0
  61. package/src/runtime/transport.ts +64 -0
  62. package/src/runtime/types.ts +173 -0
  63. package/src/runtime/uuid.ts +45 -0
  64. package/src/runtime/websocket.ts +30 -0
  65. package/src/rust-ffi/addon.ts +1 -1
  66. package/src/rust-ffi/batch/build.ts +155 -0
  67. package/src/rust-ffi/batch/index.ts +8 -0
  68. package/src/rust-ffi/batch/types.ts +92 -0
  69. package/src/rust-ffi/client.ts +5 -6
  70. package/src/rust-ffi/context.ts +39 -10
  71. package/src/rust-ffi/index.ts +28 -17
  72. package/src/rust-ffi/into.ts +21 -0
  73. package/src/rust-ffi/options.ts +1 -1
  74. package/src/rust-ffi/packed.ts +2 -2
  75. package/src/rust-ffi/proven.ts +16 -30
  76. package/src/rust-ffi/scalar/crypto.ts +188 -107
  77. package/src/rust-ffi/scalar/factories.ts +253 -27
  78. package/src/rust-ffi/scalar/hashing.ts +43 -41
  79. package/src/rust-ffi/scalar/http.ts +208 -77
  80. package/src/rust-ffi/scalar/index.ts +3 -3
  81. package/src/rust-ffi/scalar/interface.ts +88 -213
  82. package/src/rust-ffi/scalar/json-packed.ts +175 -0
  83. package/src/rust-ffi/scalar/json.ts +28 -12
  84. package/src/rust-ffi/scalar/payload.ts +159 -42
  85. package/src/rust-ffi/text.ts +31 -5
  86. package/src/selection.ts +27 -31
  87. package/src/shared/adaptive.ts +1 -2
  88. package/src/shared/buffer-pool.ts +72 -17
  89. package/src/shared/bytes.ts +75 -6
  90. package/src/shared/codec.ts +78 -0
  91. package/src/shared/env.ts +4 -4
  92. package/src/shared/log.ts +1 -0
  93. package/src/shared/packed/index.ts +10 -0
  94. package/src/shared/packed/parsers.ts +31 -0
  95. package/src/shared/packed/schema.ts +23 -0
  96. package/src/shared/{packed.ts → packed/wire.ts} +31 -39
  97. package/src/shared/proven.ts +189 -496
  98. package/src/shared/runtime.ts +20 -25
  99. package/src/shared/uuid.ts +12 -28
  100. package/src/data/json-rows.ts +0 -33
  101. package/src/rust-ffi/batch.ts +0 -431
  102. package/src/shared/bench-classify.ts +0 -209
@@ -4,7 +4,7 @@
4
4
  // `lazyAddon()` trigger loading exactly once on first use. This keeps
5
5
  // `import castrum` as cheap as possible on Bun.
6
6
 
7
- import { existsSync, statSync } from 'node:fs'
7
+ import { existsSync, readFileSync, statSync } from 'node:fs'
8
8
  import { fileURLToPath } from 'node:url'
9
9
  import { join, dirname } from 'node:path'
10
10
  import { createRequire } from 'node:module'
@@ -13,6 +13,40 @@ import type { NativeAddon } from './types'
13
13
 
14
14
  const require = createRequire(import.meta.url)
15
15
 
16
+ /**
17
+ * x86-64-v3 feature flags (the exact set `scripts/build-v3.sh` compiles with).
18
+ * The v3 variant is only preferred when the host CPU exposes ALL of them —
19
+ * checking only `avx2` would let a partial-SSE4.2 machine dlopen a binary it
20
+ * cannot run (SIGILL is not catchable from JS, so we must be conservative).
21
+ */
22
+ const X8664_V3_FLAGS = ['avx2', 'bmi2', 'fma', 'sse4_2']
23
+
24
+ /**
25
+ * Detect x86-64-v3 support from `cpuinfo` text (Linux `/proc/cpuinfo` flags).
26
+ *
27
+ * Pure and unit-testable: the caller supplies the cpuinfo text (the real path
28
+ * reads `/proc/cpuinfo` on linux/x64). Returns false for any non-linux/x64
29
+ * platform and for any missing flag — a v3 binary dlopened on an unsupported
30
+ * CPU would SIGILL the whole Bun/Node process (not catchable from JS), so the
31
+ * default must be "no".
32
+ *
33
+ * @param cpuinfo the `flags:` line content, or `undefined` to read
34
+ * `/proc/cpuinfo` (only meaningful on linux/x64)
35
+ * @returns true when all x86-64-v3 feature flags are present
36
+ */
37
+ export function supportsX8664V3(cpuinfo?: string): boolean {
38
+ const text =
39
+ cpuinfo ??
40
+ (() => {
41
+ try {
42
+ return readFileSync('/proc/cpuinfo', 'utf8')
43
+ } catch {
44
+ return ''
45
+ }
46
+ })()
47
+ return X8664_V3_FLAGS.every((flag) => new RegExp(`\\b${flag}\\b`).test(text))
48
+ }
49
+
16
50
  /**
17
51
  * Resolve the addon path given EXPLICIT inputs (pure — unit-testable).
18
52
  *
@@ -21,11 +55,18 @@ const require = createRequire(import.meta.url)
21
55
  * can exercise env-override / multi-root / missing-addon behavior without
22
56
  * mutating the process-global cache.
23
57
  *
58
+ * Dual-binary CPU-detect: on linux/x64 when the host supports x86-64-v3, the
59
+ * `castrum.linux-x64-v3-gnu.node` variant is preferred (SIMD in crc32fast /
60
+ * sonic-rs / memchr / xxh3 / simdutf8 / mimalloc) and the first-existing
61
+ * candidate wins — so an absent v3 file falls through to the baseline names.
62
+ *
24
63
  * @param envOverride an explicit `CASTRUM_NATIVE_LIBRARY_PATH`-style path, or
25
64
  * `undefined` for none (the real call passes the env-var value)
26
65
  * @param baseDir the directory of the current module (`dist/` or `src/native`)
27
66
  * @param platform `process.platform`
28
67
  * @param arch `process.arch`
68
+ * @param cpuinfo optional `/proc/cpuinfo` text (tests inject it; the real call
69
+ * omits it and reads the file lazily on linux/x64 only)
29
70
  * @returns the first existing candidate path (throws if none exists)
30
71
  */
31
72
  export function resolveAddonPathFrom(
@@ -33,6 +74,7 @@ export function resolveAddonPathFrom(
33
74
  baseDir: string,
34
75
  platform: string,
35
76
  arch: string,
77
+ cpuinfo?: string,
36
78
  ): string {
37
79
  // napi-rs artifact naming: castrum.<platform>-<arch>[-<libc>].node
38
80
  // e.g. linux-x64-gnu, linux-x64-musl, win32-x64-msvc, darwin-arm64.
@@ -44,6 +86,13 @@ export function resolveAddonPathFrom(
44
86
  )
45
87
  names.push('castrum.node')
46
88
 
89
+ // On a v3-capable linux/x64 host, prefer the SIMD variant first. The v3 file
90
+ // only ships in glibc (gnu) packages, so on musl the candidate is simply
91
+ // absent and resolution falls through to the baseline musl artifact.
92
+ if (platform === 'linux' && arch === 'x64' && supportsX8664V3(cpuinfo)) {
93
+ names.unshift('castrum.linux-x64-v3-gnu.node')
94
+ }
95
+
47
96
  const candidates: string[] = []
48
97
 
49
98
  // Explicit override (napi-rs convention + castrum/legacy aliases). May be a
@@ -122,10 +171,7 @@ export function resolveAddonPathFrom(
122
171
 
123
172
  function resolveAddonPath(): string {
124
173
  return resolveAddonPathFrom(
125
- resolveEnvVar('CASTRUM_NATIVE_LIBRARY_PATH', [
126
- 'NAPI_RS_NATIVE_LIBRARY_PATH',
127
- 'RUST_BENCH_NATIVE_LIBRARY_PATH',
128
- ]),
174
+ resolveEnvVar('CASTRUM_NATIVE_LIBRARY_PATH', ['NAPI_RS_NATIVE_LIBRARY_PATH']),
129
175
  dirname(fileURLToPath(import.meta.url)),
130
176
  process.platform,
131
177
  process.arch,
@@ -165,7 +211,7 @@ export function getAddon(): NativeAddon {
165
211
  )
166
212
  }
167
213
 
168
- if (resolveEnvVar('CASTRUM_DEBUG', ['RUST_BENCH_DEBUG']) !== undefined) {
214
+ if (resolveEnvVar('CASTRUM_DEBUG') !== undefined) {
169
215
  console.log('Native addon loaded from:', addonPath)
170
216
  console.log('Exported keys:', Object.keys(loaded).sort())
171
217
  }
@@ -0,0 +1,274 @@
1
+ // src/native/types/instances.ts — Native addon class-instance types.
2
+ //
3
+ // The per-class instance interfaces + their result/option payload types,
4
+ // separated from `NativeAddon` (the module surface in ../types.ts) so the
5
+ // class-shaped types live in one place. `../types.ts` re-exports everything
6
+ // here, so `import { IngressInstance } from '../native/types'` keeps working.
7
+
8
+ /** The native Ingress class instance (hot-path pipeline). */
9
+ export interface IngressInstance {
10
+ handleRequestPacked(input: Uint8Array, body: Uint8Array | null, output: Uint8Array): number
11
+
12
+ handleRequestFullSync(
13
+ methodKind: number,
14
+ url: string,
15
+ ip: string,
16
+ requestId: string,
17
+ headers: Array<[string, string]>,
18
+ body: Uint8Array | null,
19
+ outputBufferSize?: number,
20
+ ): Uint8Array
21
+
22
+ /**
23
+ * Reusable-output variant of `handleRequestFullSync`: packs the request in
24
+ * Rust and writes the packed decision into `output`, returning the number of
25
+ * bytes written. Pool the output buffer across requests to avoid per-request
26
+ * allocation (see `src/shared/buffer-pool.ts`).
27
+ */
28
+ handleRequestFullSyncInto(
29
+ methodKind: number,
30
+ url: string,
31
+ ip: string,
32
+ requestId: string,
33
+ headers: Array<[string, string]>,
34
+ body: Uint8Array | null,
35
+ output: Uint8Array,
36
+ ): number
37
+
38
+ /**
39
+ * Opaque handle to the inner pipeline state for the `bun:ffi` fast path.
40
+ * Internal only — the returned pointer is valid only while THIS instance is
41
+ * alive and must never be dereferenced from JS. Present only on addons built
42
+ * with the `castrum_ingress_handle_packed` C-ABI export; guard with
43
+ * `typeof handler.ingressInnerPtr === 'function'`.
44
+ */
45
+ ingressInnerPtr?(): bigint
46
+ }
47
+
48
+ /**
49
+ * The native `Route` class instance (per-route native stack — the
50
+ * `@ignex/native` route-wire v3 contract). Constructed from a compiled route
51
+ * descriptor; `run` processes one packed request frame and returns the packed
52
+ * verdict result bytes written (`0` = error / too-small, `> output.length` =
53
+ * the exact required size — the growExact convention).
54
+ */
55
+ export interface RouteInstance {
56
+ run(frame: Uint8Array, output: Uint8Array): number
57
+ destroy?(): void
58
+ }
59
+
60
+ /** A single JSON Schema validation error (fast path + DOM fallback). */
61
+ export interface SchemaError {
62
+ /** RFC 6901 JSON pointer to the failing instance value ("" = root). */
63
+ instancePath: string
64
+ /** JSON pointer into the schema at the failing keyword. */
65
+ schemaPath: string
66
+ /** The failing keyword (e.g. "type", "pattern", "required"). */
67
+ keyword: string
68
+ /** Human-readable failure message. */
69
+ message: string
70
+ }
71
+
72
+ /** The native `SchemaValidator` class instance. */
73
+ export interface SchemaValidatorInstance {
74
+ /** Validate a single JSON document. */
75
+ validate(input: Uint8Array): boolean
76
+ /** Opaque handle (bun:ffi fast path) — `castrum_schema_validator_validate`. */
77
+ innerPtr?(): bigint
78
+ /** Validate a single JSON document, returning detailed errors (empty = valid). */
79
+ validateDetailed(input: Uint8Array): SchemaError[]
80
+ /** Validate a single JSON document, returning only the first error (null = valid). */
81
+ validateFirstError(input: Uint8Array): SchemaError | null
82
+ validateBatchPackedCount(packed: Uint8Array): number
83
+ validateBatchPackedBitset(packed: Uint8Array): Uint8Array
84
+ /**
85
+ * One-pass validate + extract: validate `input` against the schema and
86
+ * capture scalar values / array lengths at `paths` during the same native
87
+ * walk (no `JSON.parse`, no DOM). For "derive" routes (response built from a
88
+ * handful of body fields) this replaces `JSON.parse` + Ajv on the happy path
89
+ * and rejects invalid bodies with zero DOM/GC.
90
+ *
91
+ * `paths` are RFC 6901 JSON pointers of OBJECT KEYS; a trailing `/-`
92
+ * captures the ARRAY LENGTH at that path (e.g. `"/totalCents"`,
93
+ * `"/lineItems/-"`). Array-index steps are not supported.
94
+ */
95
+ derive(input: Uint8Array, paths: string[]): JsonDeriveResult
96
+ }
97
+
98
+ /** A single derived value captured during one-pass validation. */
99
+ export interface JsonDeriveValue {
100
+ /** `"int" | "number" | "string" | "bool" | "null"`. */
101
+ kind: string
102
+ int: number | null
103
+ number: number | null
104
+ text: string | null
105
+ boolean: boolean | null
106
+ }
107
+
108
+ /** Result of a one-pass `validate + derive`. */
109
+ export interface JsonDeriveResult {
110
+ /** `true` when the document is schema-valid; `false` → caller rejects. */
111
+ ok: boolean
112
+ /** One entry per requested path (`null` = path absent from the document). */
113
+ values: Array<JsonDeriveValue | null>
114
+ }
115
+
116
+ /** The native `HmacSigner` class instance. */
117
+ export interface HmacSignerInstance {
118
+ sign(data: Uint8Array): Uint8Array
119
+ verify(data: Uint8Array, sig: Uint8Array): boolean
120
+ }
121
+
122
+ /** The native `FormParser` class instance (reusable output buffer). */
123
+ export interface FormParserInstance {
124
+ /** Parse an x-www-form-urlencoded body into packed pairs (reuses the buffer). */
125
+ parse(input: Uint8Array): Uint8Array
126
+ /** Zero-alloc parse into a caller-provided output buffer. */
127
+ parseInto(input: Uint8Array, output: Uint8Array): number
128
+ }
129
+
130
+ /** A parsed Content-Type / media type. */
131
+ export interface MediaTypeResult {
132
+ /** Lowercased `type/subtype`. */
133
+ mediaType: string
134
+ charset: string | null
135
+ boundary: string | null
136
+ params: Record<string, string>
137
+ }
138
+
139
+ /** The native `MediaTypeParser` class instance. */
140
+ export interface MediaTypeParserInstance {
141
+ parse(input: Uint8Array): MediaTypeResult
142
+ }
143
+
144
+ /** The native `ConditionalRequest` class instance (per-resource 304 checks). */
145
+ export interface ConditionalRequestInstance {
146
+ isNotModified(ifNoneMatch: Uint8Array | null, ifModifiedSince: Uint8Array | null): boolean
147
+ /** Opaque handle to the precompiled state (present when the bun:ffi fast path
148
+ * is active) — `castrum_conditional_is_not_modified` in rust/ffi.rs. */
149
+ innerPtr?(): bigint
150
+ }
151
+
152
+ /** One parsed Accept-Encoding preference. */
153
+ export interface EncodingPrefResult {
154
+ encoding: string
155
+ q: number
156
+ order: number
157
+ }
158
+
159
+ /**
160
+ * The native `AcceptNegotiator` class instance (supported encodings compiled
161
+ * once). Best supported encoding for a header, or `null` for identity.
162
+ */
163
+ export interface AcceptNegotiatorInstance {
164
+ /** Best supported encoding for `header`, or null for identity. */
165
+ negotiate(header: Uint8Array): string | null
166
+ /** Opaque handle (bun:ffi fast path) — `castrum_accept_negotiator_negotiate`. */
167
+ innerPtr?(): bigint
168
+ }
169
+
170
+ /** The native `Base64Codec` class instance (config compiled once). */
171
+ export interface Base64CodecInstance {
172
+ encode(input: Uint8Array): Uint8Array
173
+ decode(input: Uint8Array): Uint8Array
174
+ }
175
+
176
+ /** Result of a native rate-limit check for one key at a point in time. */
177
+ export interface RateCheckResult {
178
+ allowed: boolean
179
+ remaining: number
180
+ resetMs: number
181
+ }
182
+
183
+ /** The native `RateLimiter` class instance (sharded fixed-window state). */
184
+ export interface RateLimiterInstance {
185
+ check(key: string, nowMs: number): RateCheckResult
186
+ checkKey(key: number, nowMs: number): RateCheckResult
187
+ /** Opaque handle (bun:ffi fast path) — `castrum_rate_limiter_check*`. */
188
+ innerPtr?(): bigint
189
+ }
190
+
191
+ /** The native `CookieSigner` class instance (HMAC key compiled once). */
192
+ export interface CookieSignerInstance {
193
+ sign(value: Uint8Array): Uint8Array
194
+ /** Returns the signed value without its signature, or null on invalid. */
195
+ verify(signed: Uint8Array): Uint8Array | null
196
+ }
197
+
198
+ /** The native `CsrfProtector` class instance (HMAC key compiled once). */
199
+ export interface CsrfProtectorInstance {
200
+ create(): Uint8Array
201
+ verify(token: Uint8Array): boolean
202
+ }
203
+
204
+ /** The native `UrlBuilder` class instance (base parsed once). */
205
+ export interface UrlBuilderInstance {
206
+ resolve(reference: Uint8Array): Uint8Array
207
+ /** Opaque handle to the precompiled base (present when the bun:ffi fast path
208
+ * is active) — `castrum_url_builder_resolve` in rust/ffi.rs. */
209
+ innerPtr?(): bigint
210
+ }
211
+
212
+ /** Options for `passwordHash` (argon2id). */
213
+ export interface PasswordHashOptions {
214
+ mCost?: number
215
+ tCost?: number
216
+ pCost?: number
217
+ outLen?: number
218
+ }
219
+
220
+ /** A parsed multipart/form-data part. */
221
+ export interface MultipartPart {
222
+ name: string
223
+ filename: string | null
224
+ contentType: string | null
225
+ data: Uint8Array
226
+ }
227
+
228
+ /** A decoded RFC 6455 websocket frame. */
229
+ export interface WsFrame {
230
+ fin: boolean
231
+ opcode: number
232
+ payload: Uint8Array
233
+ }
234
+
235
+ /** The native `TemplateRenderer` class instance. */
236
+ export interface TemplateRendererInstance {
237
+ render(context: unknown): Uint8Array
238
+ /** Render from pre-serialized JSON context bytes (no napi Value marshal). */
239
+ renderBytes(contextJson: Uint8Array): Uint8Array
240
+ /** Parallel batch render: packed contexts in → packed rendered out. */
241
+ renderBatchPacked(data: Uint8Array): Uint8Array
242
+ /** Opaque handle (bun:ffi fast path) — `castrum_template_render`. */
243
+ innerPtr?(): bigint
244
+ }
245
+
246
+ /** The native `MediaTypeMatcher` class instance (expected precompiled once). */
247
+ export interface MediaTypeMatcherInstance {
248
+ /** Wildcard match against the precompiled expected type. */
249
+ matches(actual: Uint8Array): boolean
250
+ /** Opaque handle (bun:ffi fast path) — `castrum_media_type_matcher_matches`. */
251
+ innerPtr?(): bigint
252
+ }
253
+
254
+ /** The native `JwtSigner` class instance (HS256 key + ttl compiled once). */
255
+ export interface JwtSignerInstance {
256
+ sign(claims: unknown, nowSeconds: number): Uint8Array
257
+ /** Sign from pre-serialized claim JSON bytes (no napi Value marshal). */
258
+ signBytes(claimsJson: Uint8Array, nowSeconds: number): Uint8Array
259
+ verify(token: Uint8Array, nowSeconds: number): unknown
260
+ /** Opaque handle (bun:ffi fast path) — `castrum_jwt_signer_*`. */
261
+ innerPtr?(): bigint
262
+ }
263
+
264
+ /** The native `AeadCipher` class instance (algorithm + key compiled once). */
265
+ export interface AeadCipherInstance {
266
+ encrypt(nonce: Uint8Array, plaintext: Uint8Array): Uint8Array
267
+ decrypt(nonce: Uint8Array, ciphertext: Uint8Array): Uint8Array | null
268
+ }
269
+
270
+ /** The native `Argon2Hasher` class instance (params compiled once). */
271
+ export interface Argon2HasherInstance {
272
+ hash(password: Uint8Array, salt: Uint8Array): Uint8Array
273
+ verify(password: Uint8Array, phc: Uint8Array): boolean
274
+ }
@@ -4,244 +4,38 @@
4
4
  // (index.d.ts) and types every class + function + layout constant exported by
5
5
  // the Rust cdylib. Keeping it separate from the loader (./loader.ts) means
6
6
  // type-only consumers never pull in the file-system loading logic.
7
-
8
- /** The native Ingress class instance (hot-path pipeline). */
9
- export interface IngressInstance {
10
- handleRequestPacked(input: Uint8Array, body: Uint8Array | null, output: Uint8Array): number
11
-
12
- handleRequestFullSync(
13
- methodKind: number,
14
- url: string,
15
- ip: string,
16
- requestId: string,
17
- headers: Array<[string, string]>,
18
- body: Uint8Array | null,
19
- outputBufferSize?: number,
20
- ): Uint8Array
21
-
22
- /**
23
- * Reusable-output variant of `handleRequestFullSync`: packs the request in
24
- * Rust and writes the packed decision into `output`, returning the number of
25
- * bytes written. Pool the output buffer across requests to avoid per-request
26
- * allocation (see `src/shared/buffer-pool.ts`).
27
- */
28
- handleRequestFullSyncInto(
29
- methodKind: number,
30
- url: string,
31
- ip: string,
32
- requestId: string,
33
- headers: Array<[string, string]>,
34
- body: Uint8Array | null,
35
- output: Uint8Array,
36
- ): number
37
-
38
- /**
39
- * Opaque handle to the inner pipeline state for the `bun:ffi` fast path.
40
- * Internal only — the returned pointer is valid only while THIS instance is
41
- * alive and must never be dereferenced from JS. Present only on addons built
42
- * with the `castrum_ingress_handle_packed` C-ABI export; guard with
43
- * `typeof handler.ingressInnerPtr === 'function'`.
44
- */
45
- ingressInnerPtr?(): bigint
46
- }
47
-
48
- /** A single JSON Schema validation error (fast path + DOM fallback). */
49
- export interface SchemaError {
50
- /** RFC 6901 JSON pointer to the failing instance value ("" = root). */
51
- instancePath: string
52
- /** JSON pointer into the schema at the failing keyword. */
53
- schemaPath: string
54
- /** The failing keyword (e.g. "type", "pattern", "required"). */
55
- keyword: string
56
- /** Human-readable failure message. */
57
- message: string
58
- }
59
-
60
- /** The native `SchemaValidator` class instance. */
61
- export interface SchemaValidatorInstance {
62
- /** Validate a single JSON document. */
63
- validate(input: Uint8Array): boolean
64
- /** Validate a single JSON document, returning detailed errors (empty = valid). */
65
- validateDetailed(input: Uint8Array): SchemaError[]
66
- /** Validate a single JSON document, returning only the first error (null = valid). */
67
- validateFirstError(input: Uint8Array): SchemaError | null
68
- validateBatchPackedCount(packed: Uint8Array): number
69
- validateBatchPackedBitset(packed: Uint8Array): Uint8Array
70
- validateBatchStreaming(batchBytes: Uint8Array): number
71
- /**
72
- * One-pass validate + extract: validate `input` against the schema and
73
- * capture scalar values / array lengths at `paths` during the same native
74
- * walk (no `JSON.parse`, no DOM). For "derive" routes (response built from a
75
- * handful of body fields) this replaces `JSON.parse` + Ajv on the happy path
76
- * and rejects invalid bodies with zero DOM/GC.
77
- *
78
- * `paths` are RFC 6901 JSON pointers of OBJECT KEYS; a trailing `/-`
79
- * captures the ARRAY LENGTH at that path (e.g. `"/totalCents"`,
80
- * `"/lineItems/-"`). Array-index steps are not supported.
81
- */
82
- derive(input: Uint8Array, paths: string[]): JsonDeriveResult
83
- }
84
-
85
- /** A single derived value captured during one-pass validation. */
86
- export interface JsonDeriveValue {
87
- /** `"int" | "number" | "string" | "bool" | "null"`. */
88
- kind: string
89
- int: number | null
90
- number: number | null
91
- text: string | null
92
- boolean: boolean | null
93
- }
94
-
95
- /** Result of a one-pass `validate + derive`. */
96
- export interface JsonDeriveResult {
97
- /** `true` when the document is schema-valid; `false` → caller rejects. */
98
- ok: boolean
99
- /** One entry per requested path (`null` = path absent from the document). */
100
- values: Array<JsonDeriveValue | null>
101
- }
102
-
103
- /** The native `HmacSigner` class instance. */
104
- export interface HmacSignerInstance {
105
- sign(data: Uint8Array): Uint8Array
106
- verify(data: Uint8Array, sig: Uint8Array): boolean
107
- }
108
-
109
- /** The native `FormParser` class instance (reusable output buffer). */
110
- export interface FormParserInstance {
111
- /** Parse an x-www-form-urlencoded body into packed pairs (reuses the buffer). */
112
- parse(input: Uint8Array): Uint8Array
113
- /** Zero-alloc parse into a caller-provided output buffer. */
114
- parseInto(input: Uint8Array, output: Uint8Array): number
115
- }
116
-
117
- /** A parsed Content-Type / media type. */
118
- export interface MediaTypeResult {
119
- /** Lowercased `type/subtype`. */
120
- mediaType: string
121
- charset: string | null
122
- boundary: string | null
123
- params: Record<string, string>
124
- }
125
-
126
- /** The native `MediaTypeParser` class instance. */
127
- export interface MediaTypeParserInstance {
128
- parse(input: Uint8Array): MediaTypeResult
129
- /** Wildcard match: any/any, type/any, or exact type/subtype. */
130
- matches(actual: Uint8Array, expected: Uint8Array): boolean
131
- }
132
-
133
- /** The native `ConditionalRequest` class instance (per-resource 304 checks). */
134
- export interface ConditionalRequestInstance {
135
- isNotModified(ifNoneMatch: Uint8Array | null, ifModifiedSince: Uint8Array | null): boolean
136
- }
137
-
138
- /** One parsed Accept-Encoding preference. */
139
- export interface EncodingPrefResult {
140
- encoding: string
141
- q: number
142
- order: number
143
- }
144
-
145
- /** The native `AcceptNegotiator` class instance. */
146
- export interface AcceptNegotiatorInstance {
147
- /** Best supported encoding for `header`, or null for identity. */
148
- negotiate(header: Uint8Array): string | null
149
- }
150
-
151
- /** The native `Base64Codec` class instance (config compiled once). */
152
- export interface Base64CodecInstance {
153
- encode(input: Uint8Array): Uint8Array
154
- decode(input: Uint8Array): Uint8Array
155
- }
156
-
157
- /** Result of a native rate-limit check for one key at a point in time. */
158
- export interface RateCheckResult {
159
- allowed: boolean
160
- remaining: number
161
- resetMs: number
162
- }
163
-
164
- /** The native `RateLimiter` class instance (sharded fixed-window state). */
165
- export interface RateLimiterInstance {
166
- check(key: string, nowMs: number): RateCheckResult
167
- checkKey(key: number, nowMs: number): RateCheckResult
168
- }
169
-
170
- /** The native `CookieSigner` class instance (HMAC key compiled once). */
171
- export interface CookieSignerInstance {
172
- sign(value: Uint8Array): Uint8Array
173
- /** Returns the signed value without its signature, or null on invalid. */
174
- verify(signed: Uint8Array): Uint8Array | null
175
- }
176
-
177
- /** The native `CsrfProtector` class instance (HMAC key compiled once). */
178
- export interface CsrfProtectorInstance {
179
- create(): Uint8Array
180
- verify(token: Uint8Array): boolean
181
- }
182
-
183
- /** The native `UrlBuilder` class instance (base parsed once). */
184
- export interface UrlBuilderInstance {
185
- resolve(reference: Uint8Array): Uint8Array
186
- }
187
-
188
- /** Options for `passwordHash` (argon2id). */
189
- export interface PasswordHashOptions {
190
- mCost?: number
191
- tCost?: number
192
- pCost?: number
193
- outLen?: number
194
- }
195
-
196
- /** A parsed multipart/form-data part. */
197
- export interface MultipartPart {
198
- name: string
199
- filename: string | null
200
- contentType: string | null
201
- data: Uint8Array
202
- }
203
-
204
- /** A decoded RFC 6455 websocket frame. */
205
- export interface WsFrame {
206
- fin: boolean
207
- opcode: number
208
- payload: Uint8Array
209
- }
210
-
211
- /** The native `TemplateRenderer` class instance. */
212
- export interface TemplateRendererInstance {
213
- render(context: unknown): Uint8Array
214
- /** Render from pre-serialized JSON context bytes (no napi Value marshal). */
215
- renderBytes(contextJson: Uint8Array): Uint8Array
216
- /** Parallel batch render: packed contexts in → packed rendered out. */
217
- renderBatchPacked(data: Uint8Array): Uint8Array
218
- }
219
-
220
- /** The native `MediaTypeMatcher` class instance (expected precompiled once). */
221
- export interface MediaTypeMatcherInstance {
222
- /** Wildcard match against the precompiled expected type. */
223
- matches(actual: Uint8Array): boolean
224
- }
225
-
226
- /** The native `JwtSigner` class instance (HS256 key + ttl compiled once). */
227
- export interface JwtSignerInstance {
228
- sign(claims: unknown, nowSeconds: number): Uint8Array
229
- /** Sign from pre-serialized claim JSON bytes (no napi Value marshal). */
230
- signBytes(claimsJson: Uint8Array, nowSeconds: number): Uint8Array
231
- verify(token: Uint8Array, nowSeconds: number): unknown
232
- }
233
-
234
- /** The native `AeadCipher` class instance (algorithm + key compiled once). */
235
- export interface AeadCipherInstance {
236
- encrypt(nonce: Uint8Array, plaintext: Uint8Array): Uint8Array
237
- decrypt(nonce: Uint8Array, ciphertext: Uint8Array): Uint8Array | null
238
- }
239
-
240
- /** The native `Argon2Hasher` class instance (params compiled once). */
241
- export interface Argon2HasherInstance {
242
- hash(password: Uint8Array, salt: Uint8Array): Uint8Array
243
- verify(password: Uint8Array, phc: Uint8Array): boolean
244
- }
7
+ //
8
+ // The per-class INSTANCE types live in ./types/instances.ts. They are imported
9
+ // here (so `NativeAddon` can reference them) and re-exported so existing
10
+ // `import { X } from '../native/types'` call sites keep working.
11
+
12
+ import type {
13
+ AcceptNegotiatorInstance,
14
+ AeadCipherInstance,
15
+ Argon2HasherInstance,
16
+ Base64CodecInstance,
17
+ ConditionalRequestInstance,
18
+ CookieSignerInstance,
19
+ CsrfProtectorInstance,
20
+ EncodingPrefResult,
21
+ FormParserInstance,
22
+ HmacSignerInstance,
23
+ IngressInstance,
24
+ JwtSignerInstance,
25
+ MediaTypeMatcherInstance,
26
+ MediaTypeParserInstance,
27
+ MediaTypeResult,
28
+ MultipartPart,
29
+ PasswordHashOptions,
30
+ RateLimiterInstance,
31
+ RouteInstance,
32
+ SchemaValidatorInstance,
33
+ TemplateRendererInstance,
34
+ UrlBuilderInstance,
35
+ WsFrame,
36
+ } from './types/instances'
37
+
38
+ export * from './types/instances'
245
39
 
246
40
  /**
247
41
  * Full shape of the native addon module: classes, scalar/batch functions and
@@ -251,6 +45,15 @@ export interface Argon2HasherInstance {
251
45
  export interface NativeAddon {
252
46
  HmacSigner: new (key: Uint8Array) => HmacSignerInstance
253
47
  Ingress: new (options: Record<string, unknown>) => IngressInstance
48
+ /**
49
+ * Per-route native stack (the `@ignex/native` route-wire v3 contract).
50
+ * Compile a route descriptor once at construction; `run` processes one
51
+ * packed request frame (parseQuery/parseCookies/requireJsonBody/
52
+ * validateBody) into the packed verdict result.
53
+ */
54
+ Route: new (
55
+ descriptor: Uint8Array,
56
+ ) => RouteInstance
254
57
 
255
58
  /**
256
59
  * Benchmark-driven native-vs-JS decision (single source of truth: