castrum 0.9.1 → 0.9.3

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 (49) hide show
  1. package/README.md +1 -1
  2. package/castrum.linux-x64-gnu.node +0 -0
  3. package/dist/index.d.ts +239 -5
  4. package/dist/index.js +3852 -3375
  5. package/docs/ARCHITECTURE.md +11 -9
  6. package/docs/BENCHMARKS.md +70 -38
  7. package/docs/CASE_STUDY.md +10 -7
  8. package/docs/ENVIRONMENT.md +16 -1
  9. package/docs/FFI_BUN_GUIDE.md +18 -10
  10. package/docs/GETTING_STARTED.md +5 -4
  11. package/docs/HOW_TO_ADD_AN_OP.md +180 -0
  12. package/docs/INGRESS-ROUTER.md +3 -2
  13. package/docs/INGRESS.md +6 -3
  14. package/docs/NATIVE-ROUTE.md +28 -4
  15. package/docs/REPO_MAP.md +60 -30
  16. package/docs/adr/0001-two-wire-formats.md +1 -1
  17. package/docs/ai/LOCAL_DEV.md +81 -0
  18. package/docs/ai/TREE.md +1070 -0
  19. package/package.json +24 -23
  20. package/src/ingress/decode/result-base.ts +10 -2
  21. package/src/ingress/handlers.ts +45 -290
  22. package/src/ingress/headers/security.ts +41 -0
  23. package/src/ingress/index.ts +12 -148
  24. package/src/ingress/native-route.ts +182 -0
  25. package/src/ingress/packing/gather-raw-headers.ts +26 -2
  26. package/src/ingress/packing/route-wire.ts +197 -0
  27. package/src/ingress/packing/select-headers.ts +8 -1
  28. package/src/ingress/path-matcher.ts +115 -0
  29. package/src/ingress/response/baked-response.ts +302 -0
  30. package/src/ingress/router.ts +42 -2
  31. package/src/ingress/routes/common.ts +9 -1
  32. package/src/ingress/routes/json-write.ts +21 -18
  33. package/src/ingress/routes/native.ts +130 -0
  34. package/src/ingress/server-node.ts +1 -1
  35. package/src/ingress/server.ts +38 -99
  36. package/src/ingress/sync.ts +151 -0
  37. package/src/ingress/types.ts +9 -0
  38. package/src/native/ffi/build/codecs.ts +736 -0
  39. package/src/native/ffi/build/compress.ts +248 -0
  40. package/src/native/ffi/build/instances.ts +598 -0
  41. package/src/native/ffi/build/parse.ts +539 -0
  42. package/src/native/ffi/build/util.ts +140 -0
  43. package/src/native/ffi/build.ts +94 -0
  44. package/src/native/ffi/selftest.ts +11 -718
  45. package/src/native/ffi/types.ts +25 -0
  46. package/src/native/ffi.ts +36 -1320
  47. package/src/native/types/instances.ts +5 -0
  48. package/src/rust-ffi/packed.ts +38 -0
  49. package/src/rust-ffi/scalar/factories.ts +4 -0
package/README.md CHANGED
@@ -168,7 +168,7 @@ isEmail([a, b, c]); // one packed batch call
168
168
  ## Testing
169
169
 
170
170
  ```bash
171
- bun test # TypeScript unit tests (~540)
171
+ bun test # TypeScript unit tests (~760)
172
172
  bun run test:rust # Rust unit tests (cargo test)
173
173
  bun run typecheck # TypeScript typecheck
174
174
  bun run test:node # Node.js integration tests (run `bun run build:js` first)
Binary file
package/dist/index.d.ts CHANGED
@@ -96,6 +96,11 @@ export interface EncodingPrefResult {
96
96
  export interface AcceptNegotiatorInstance {
97
97
  /** Best supported encoding for `header`, or null for identity. */
98
98
  negotiate(header: Uint8Array): string | null;
99
+ /** Best supported encoding for `header` with SERVER-preference tie-breaking
100
+ * (RFC 7231 server semantics — the supported list's order breaks q-value
101
+ * ties), or null for identity. On the napi fallback this is the projected
102
+ * `negotiate_server_preference` method. */
103
+ negotiateServerPreference(header: Uint8Array): string | null;
99
104
  /** Opaque handle (bun:ffi fast path) — `castrum_accept_negotiator_negotiate`. */
100
105
  innerPtr?(): bigint;
101
106
  }
@@ -573,6 +578,14 @@ export interface OptimizedIngressHandler {
573
578
  string,
574
579
  string
575
580
  ][];
581
+ /**
582
+ * Zero-DOM JSON-validity check bound to this handler's native transport
583
+ * (bun:ffi primary, napi fallback). Provided so the pure route factories
584
+ * (`routes/*`) can validate request bodies via dependency injection without
585
+ * importing the native layer. Always present on handlers from
586
+ * `createIngressHandler`; optional so hand-rolled mocks need not provide it.
587
+ */
588
+ jsonValid?: (bytes: Uint8Array) => boolean;
576
589
  /**
577
590
  * Build a zero-copy `Response` whose body is the request's pooled output
578
591
  * slice. The pooled buffer is returned to its pool once the body has been
@@ -626,6 +639,14 @@ export interface BakedHandlerOptions {
626
639
  bodyTimeoutMs?: number;
627
640
  /** Fallback handler used for write error paths. Defaults to the write ingress. */
628
641
  fallback?: OptimizedIngressHandler;
642
+ /**
643
+ * Zero-DOM JSON-validity check for the write-route fallback validation
644
+ * (`jsonWriteHandler` when neither `requireJsonBody` nor a `schema` is
645
+ * configured). Injected so the pure route factories never import the native
646
+ * layer (dependency injection across the purity boundary). Defaults to the
647
+ * `ingress.jsonValid` capability that `createIngressHandler` provides.
648
+ */
649
+ jsonValid?: (bytes: Uint8Array) => boolean;
629
650
  /**
630
651
  * When true, route the JSON-validity fallback check through the higher-order
631
652
  * loader (`loader("jsonValid")`) instead of the direct native call —
@@ -635,6 +656,142 @@ export interface BakedHandlerOptions {
635
656
  */
636
657
  enableLoader?: boolean;
637
658
  }
659
+ /** Route descriptor magic (`"ROUT"` LE). Must match `ROUTE_DESC_MAGIC` in Rust. */
660
+ export declare const ROUTE_DESC_MAGIC = 1380930900;
661
+ /** Wire version — bump on ANY descriptor/frame/result layout change. */
662
+ export declare const ROUTE_DESC_VERSION = 3;
663
+ /** Descriptor stage tags (the ordered pipeline a route instance runs). */
664
+ export declare const ROUTE_STAGE: {
665
+ readonly parseQuery: 0;
666
+ readonly parseCookies: 1;
667
+ readonly validateQuery: 2;
668
+ readonly validateCookies: 3;
669
+ readonly validateBody: 4;
670
+ readonly requireJsonBody: 5;
671
+ };
672
+ /** A descriptor stage tag (`ROUTE_STAGE` values, must match Rust). */
673
+ export type RouteStageTag = (typeof ROUTE_STAGE)[keyof typeof ROUTE_STAGE];
674
+ /** Descriptor part tags (`RoutePartKind`): the schema-bearing request parts. */
675
+ export declare const ROUTE_PART: {
676
+ readonly body: 3;
677
+ };
678
+ /** Result flag bits (`ROUTE_RESULT_FLAG_*` in Rust). */
679
+ export declare const ROUTE_FLAG: {
680
+ readonly OK: number;
681
+ readonly BODY_VALID_JSON: number;
682
+ readonly QUERY_VALID: number;
683
+ readonly COOKIE_VALID: number;
684
+ readonly BODY_VALID: number;
685
+ };
686
+ /** Frame flag: the body section is present (bit 0 of the frame flags word). */
687
+ export declare const ROUTE_FRAME_FLAG_HAS_BODY: number;
688
+ /** Size limits a route descriptor carries. */
689
+ export interface RouteWireLimits {
690
+ maxBodyBytes: number;
691
+ maxQueryBytes: number;
692
+ maxCookieBytes: number;
693
+ maxPairs: number;
694
+ }
695
+ /** A schema part carried by the descriptor (part tag + draft-07 JSON bytes). */
696
+ export interface RouteWireSchema {
697
+ part: number;
698
+ bytes: Uint8Array;
699
+ }
700
+ /**
701
+ * Encode a route plan into the descriptor wire.
702
+ *
703
+ * Layout: `[magic u32][version u32][maxBody u32][maxQuery u32][maxCookie u32]
704
+ * [maxPairs u32][stageCount u32][stages u8…][schemaCount u32]
705
+ * { [part u8][len u32][schema] }…`
706
+ */
707
+ export declare function encodeRouteDescriptor(pipeline: readonly RouteStageTag[], schemas: readonly RouteWireSchema[], limits: RouteWireLimits): Uint8Array;
708
+ /**
709
+ * Encode a request frame: `[flags u32][qLen][query][cLen][cookie]([bLen][body])`.
710
+ * The `flags` word currently only carries `ROUTE_FRAME_FLAG_HAS_BODY`.
711
+ */
712
+ export declare function packRouteFrame(query: string, cookie: string, body: Uint8Array | null): Uint8Array;
713
+ /** A decoded `[name, value]` pair from a result section. */
714
+ export type RouteWirePair = [
715
+ string,
716
+ string
717
+ ];
718
+ /** The decoded route result: the verdict header + optional pair sections. */
719
+ export interface RouteWireResult {
720
+ /** `ROUTE_FLAG_*` bits from the result header. */
721
+ flags: number;
722
+ /** `0` = ok; `400` = not-JSON under requireJsonBody; `422` = schema fail. */
723
+ errorCode: number;
724
+ /** Decoded query pairs (present iff the plan compiled `parseQuery`). */
725
+ query: RouteWirePair[];
726
+ /** Decoded cookie pairs (present iff the plan compiled `parseCookies`). */
727
+ cookie: RouteWirePair[];
728
+ }
729
+ /**
730
+ * Decode the result wire: `[flags u32][errorCode u32]` + a query pair section
731
+ * iff `query` and a cookie pair section iff `cookie` (the caller knows its own
732
+ * plan). Sections are `[count u32] { [nameLen u32][name][valueLen u32][value] }`.
733
+ */
734
+ export declare function decodeRouteResult(buf: Uint8Array, opts: {
735
+ query: boolean;
736
+ cookie: boolean;
737
+ }): RouteWireResult;
738
+ /** Plan for a compiled native route (the stage/limit/schema surface). */
739
+ export interface NativeRoutePlan {
740
+ /** Parse + lenient-decode the query string into pairs (stage 0). */
741
+ parseQuery?: boolean;
742
+ /** Parse + lenient-decode the Cookie header into pairs (stage 1). */
743
+ parseCookies?: boolean;
744
+ /** Reject a missing / non-JSON body with 400 (stage 5, first-failure-wins). */
745
+ requireJsonBody?: boolean;
746
+ /** Validate the body against `schema` with 422 on failure (stage 4). */
747
+ validateBody?: boolean;
748
+ /** Draft-07 JSON schema bytes for the body (`validateBody`). */
749
+ schema?: Uint8Array;
750
+ /** Max body bytes before `requireJsonBody`/`validateBody` fail (default 2 MiB). */
751
+ maxBodyBytes?: number;
752
+ /** Max query bytes before the parse VALID bit clears (default 8192). */
753
+ maxQueryBytes?: number;
754
+ /** Max cookie bytes before the parse VALID bit clears (default 8192). */
755
+ maxCookieBytes?: number;
756
+ /** Max pairs per section (0 = unlimited; default 0). */
757
+ maxPairs?: number;
758
+ }
759
+ /** A compiled per-route native stack (route-wire v3). */
760
+ export interface NativeRoute {
761
+ /** Whether the plan compiled `parseQuery` (the result carries a query section). */
762
+ readonly parseQuery: boolean;
763
+ /** Whether the plan compiled `parseCookies` (the result carries a cookie section). */
764
+ readonly parseCookies: boolean;
765
+ /**
766
+ * Run one request frame through the compiled stack and return the decoded
767
+ * verdict. The frame is `[flags u32][qLen][query][cLen][cookie]([bLen][body])`
768
+ * — build it with {@link packRouteFrame}. Reuses one growable output buffer
769
+ * (the needed-size convention: `0` = real error → throws; `> out.length` =
770
+ * exact required size → retry once).
771
+ */
772
+ runFrame(frame: Uint8Array): RouteWireResult;
773
+ /** Convenience: pack a `(query, cookie, body)` frame then {@link runFrame}. */
774
+ run(query: string, cookie: string, body: Uint8Array | null): RouteWireResult;
775
+ /** Free the native handle (idempotent; safe to call once at shutdown). */
776
+ destroy(): void;
777
+ }
778
+ /**
779
+ * Compile a per-route native stack from a plan. Returns a compiled route whose
780
+ * `run`/`runFrame` perform the parse + verdict in ONE native call.
781
+ *
782
+ * @example
783
+ * ```ts
784
+ * const route = createNativeRoute({ parseQuery: true, parseCookies: true })
785
+ * const result = route.run('page=1&limit=20', 'session=abc', null)
786
+ * // result.flags & ROUTE_FLAG.OK → parse succeeded; result.query = [['page','1'],…]
787
+ * ```
788
+ */
789
+ export declare function createNativeRoute(plan?: NativeRoutePlan): NativeRoute;
790
+ /** A matched dynamic route: the handler map + the extracted path params. */
791
+ export interface PathMatch {
792
+ methods: Record<string, unknown>;
793
+ params: Record<string, string>;
794
+ }
638
795
  /** A raw web-standard route handler (`Request` → `Response`), e.g. the
639
796
  * health/metrics probes (`livenessHandler`, `readinessHandler`,
640
797
  * `healthHandler`, `metricsHandler`). Accepted as `BakedRoute.read` and
@@ -677,6 +834,22 @@ export interface BakedRoute {
677
834
  /** Read the body for native validation (default false — framework owns it). */
678
835
  readBody?: boolean;
679
836
  };
837
+ /**
838
+ * A LEAN native-stack responder route: the route-wire v3 per-route stack
839
+ * (`createNativeRoute`) runs ONLY the plan's stages in ONE native call —
840
+ * no CORS/rate-limit/security/IP/metadata envelope. Wired for `methods`
841
+ * (default GET) by `nativeRouteHandler` (src/ingress/routes/native.ts).
842
+ */
843
+ native?: {
844
+ /** The route-wire v3 plan (parse/validate stages + limits). */
845
+ plan: NativeRoutePlan;
846
+ /** The JS 2xx builder. */
847
+ handler: NativeResponder;
848
+ /** HTTP methods to wire (default `['GET']`). */
849
+ methods?: ReadonlyArray<string>;
850
+ /** Read the body for `requireJsonBody`/`validateBody` (default false). */
851
+ readBody?: boolean;
852
+ };
680
853
  /** Overrides `maxBodyBytes` for this route's write/echo handlers. */
681
854
  maxBodyBytes?: number;
682
855
  /**
@@ -770,11 +943,6 @@ export interface GracefulShutdownOptions {
770
943
  * `process.exit`) to tear down without hanging.
771
944
  */
772
945
  export declare function gracefulShutdown(handles: ReadonlyArray<ServerHandle>, options?: GracefulShutdownOptions): () => void;
773
- /** A matched dynamic route: the handler map + the extracted path params. */
774
- export interface PathMatch {
775
- methods: Record<string, unknown>;
776
- params: Record<string, string>;
777
- }
778
946
  /** Options for `createWebSocketUpgrade` (supported subprotocols). */ export interface WebSocketUpgradeOptions {
779
947
  /** Subprotocols this server supports; the first client-requested match wins. */
780
948
  protocols?: ReadonlyArray<string>;
@@ -894,15 +1062,25 @@ export interface RustBatch {
894
1062
  }
895
1063
  /** Raw packed-wire FFI namespace. */
896
1064
  export interface RustPacked {
1065
+ /** Packed CRC32 batch: packed `[u8[]]` items → packed u32 results. */
897
1066
  crc32BatchPacked(input: Uint8Array): Uint8Array;
1067
+ /** Packed JSON-validity batch: packed `[u8[]]` items → packed 0/1 bitset. */
898
1068
  jsonValidBatchPacked(input: Uint8Array): Uint8Array;
1069
+ /** Packed email-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
899
1070
  validateEmailBatchPacked(input: Uint8Array): Uint8Array;
1071
+ /** Packed UUID-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
900
1072
  validateUuidBatchPacked(input: Uint8Array): Uint8Array;
1073
+ /** Packed IPv4-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
901
1074
  validateIpv4BatchPacked(input: Uint8Array): Uint8Array;
1075
+ /** Packed IPv6-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
902
1076
  validateIpv6BatchPacked(input: Uint8Array): Uint8Array;
1077
+ /** Packed JSON-sum batch: packed `[{id}]` docs → packed `[ok u8][sum i64 LE]`. */
903
1078
  jsonSumBatchPacked(input: Uint8Array): Uint8Array;
1079
+ /** Packed query-string parser batch: packed `[u8[]]` queries → packed pair lists. */
904
1080
  queryParseBatchPacked(input: Uint8Array): Uint8Array;
1081
+ /** Packed cookie-header parser batch: packed `[u8[]]` headers → packed pair lists. */
905
1082
  cookieParseBatchPacked(input: Uint8Array): Uint8Array;
1083
+ /** Packed HTTP-request parser batch: packed `[u8[]]` requests → packed request sections. */
906
1084
  httpParseRequestBatchPacked(input: Uint8Array): Uint8Array;
907
1085
  /** Batch RFC 6902 JSON Patch (two packed lists, zipped) → packed results. */
908
1086
  jsonPatchBatchPacked(docs: Uint8Array, patches: Uint8Array): Uint8Array;
@@ -910,41 +1088,69 @@ export interface RustPacked {
910
1088
  fnv1A64BatchPacked(input: Uint8Array): Uint8Array;
911
1089
  /** Packed ETag batch (10 strong / 12 weak bytes per item). */
912
1090
  etagBatchPacked(input: Uint8Array, weak?: boolean | null): Uint8Array;
1091
+ /** Packed percent-encoding batch: packed `[u8[]]` items → packed hex-percent bytes. */
913
1092
  urlEncodeBatchPacked(input: Uint8Array): Uint8Array;
1093
+ /** Packed percent-decoding batch: packed `[u8[]]` items → packed decoded bytes (string-safe). */
914
1094
  urlDecodeBatchPacked(input: Uint8Array): Uint8Array;
1095
+ /** Packed percent-decoding batch returning raw bytes (`%FF` preserved). */
915
1096
  urlDecodeBytesBatchPacked(input: Uint8Array): Uint8Array;
1097
+ /** Packed MIME-lookup batch: packed `[u8[]]` extensions → packed media-type strings. */
916
1098
  mimeFromExtensionBatchPacked(input: Uint8Array): Uint8Array;
1099
+ /** Packed WebSocket accept-key batch: packed `[u8[]]` keys → packed base64 SHA-1 results. */
917
1100
  wsAcceptKeyBatchPacked(input: Uint8Array): Uint8Array;
1101
+ /** Valid JSON count in a packed JSON batch (0..n; invalid items not counted). */
918
1102
  jsonValidBatchCountPacked(input: Uint8Array): number;
1103
+ /** Valid-email count in a packed email batch. */
919
1104
  validateEmailBatchCountPacked(input: Uint8Array): number;
1105
+ /** Valid-UUID count in a packed UUID batch. */
920
1106
  validateUuidBatchCountPacked(input: Uint8Array): number;
1107
+ /** Valid-IPv4 count in a packed IPv4 batch. */
921
1108
  validateIpv4BatchCountPacked(input: Uint8Array): number;
1109
+ /** Valid-IPv6 count in a packed IPv6 batch. */
922
1110
  validateIpv6BatchCountPacked(input: Uint8Array): number;
1111
+ /** Total `id` sum across a packed JSON batch (valid items only). */
923
1112
  jsonSumBatchTotalPacked(input: Uint8Array): number;
1113
+ /** Total packed-output length (bytes) a query batch will produce. */
924
1114
  queryParseBatchTotalLenPacked(input: Uint8Array): number;
1115
+ /** Total packed-output length (bytes) a cookie batch will produce. */
925
1116
  cookieParseBatchTotalLenPacked(input: Uint8Array): number;
1117
+ /** Total packed-output length (bytes) an HTTP-request batch will produce. */
926
1118
  httpParseRequestBatchTotalLenPacked(input: Uint8Array): number;
1119
+ /** Packed Argon2id-hash batch: packed `[u8[]]` passwords + shared salt → packed PHC strings. */
927
1120
  passwordHashBatchPacked(input: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array;
928
1121
  /** Packed password-verify batch (two packed lists, zipped) → bitset. */
929
1122
  passwordVerifyBatchPacked(passwords: Uint8Array, phcs: Uint8Array): Uint8Array;
930
1123
  /** Packed URL-resolve batch (two packed lists, zipped) → packed results. */
931
1124
  urlResolveBatchPacked(bases: Uint8Array, references: Uint8Array): Uint8Array;
1125
+ /** Packed AEAD-encrypt batch (packed inputs + shared key/nonce) → packed ciphertexts. */
932
1126
  aeadEncryptBatchPacked(input: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null): Uint8Array;
1127
+ /** Packed AEAD-decrypt batch (packed inputs + shared key/nonce) → packed plaintexts. */
933
1128
  aeadDecryptBatchPacked(input: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null): Uint8Array;
1129
+ /** Packed gzip-compress batch: packed `[u8[]]` items → packed gzip streams. */
934
1130
  gzipCompressBatchPacked(input: Uint8Array, level?: number | null): Uint8Array;
1131
+ /** Packed gzip-decompress batch (64 MiB cap per item) → packed plaintexts. */
935
1132
  gzipDecompressBatchPacked(input: Uint8Array): Uint8Array;
1133
+ /** Packed brotli-compress batch: packed `[u8[]]` items → packed brotli streams. */
936
1134
  brotliCompressBatchPacked(input: Uint8Array, quality?: number | null): Uint8Array;
1135
+ /** Packed brotli-decompress batch (64 MiB cap per item) → packed plaintexts. */
937
1136
  brotliDecompressBatchPacked(input: Uint8Array): Uint8Array;
1137
+ /** Packed HS256 JWT-sign batch (packed claims + shared secret) → packed tokens. */
938
1138
  jwtSignBatchPacked(input: Uint8Array, secret: Uint8Array, ttlSeconds: number | null, nowSeconds: number): Uint8Array;
1139
+ /** Packed HS256 JWT-verify batch (packed tokens + shared secret) → packed claims/validity. */
939
1140
  jwtVerifyBatchPacked(input: Uint8Array, secret: Uint8Array, nowSeconds: number): Uint8Array;
940
1141
  /** Batch HMAC-SHA256 sign (packed in → hex-signature byte results). */
941
1142
  hmacSha256BatchPacked(input: Uint8Array, key: Uint8Array): Uint8Array;
942
1143
  /** Batch HMAC-SHA256 verify (two packed lists: data + hex sigs, zipped) → bitset. */
943
1144
  hmacSha256VerifyBatchPacked(input: Uint8Array, sigs: Uint8Array, key: Uint8Array): Uint8Array;
1145
+ /** Packed multipart/form-data parser batch (packed bodies + shared boundary) → packed parts. */
944
1146
  multipartParseBatchPacked(input: Uint8Array, boundary: Uint8Array): Uint8Array;
1147
+ /** Packed minijinja template batch: packed contexts + shared source → packed renders. */
945
1148
  templateRenderBatchPacked(input: Uint8Array, source: string): Uint8Array;
1149
+ /** Packed SSE-frame batch: packed data + shared event/id/retry → packed frames. */
946
1150
  sseEncodeBatchPacked(input: Uint8Array, event: string | null, id: string | null, retry: number | null): Uint8Array;
1151
+ /** Packed WebSocket-frame encode batch: packed payloads + shared opcode/mask/fin → packed frames. */
947
1152
  wsFrameEncodeBatchPacked(input: Uint8Array, opcode: number, mask: boolean, fin: boolean): Uint8Array;
1153
+ /** Packed WebSocket-frame decode batch: packed frames → packed (fin, opcode, payload) results. */
948
1154
  wsFrameDecodeBatchPacked(input: Uint8Array): Uint8Array;
949
1155
  }
950
1156
  /**
@@ -1902,6 +2108,28 @@ export interface RouterRouteSpec {
1902
2108
  /** Read the body for native validation (default false — framework owns it). */
1903
2109
  readBody?: boolean;
1904
2110
  };
2111
+ /**
2112
+ * A LEAN native-stack responder route: the route-wire v3 per-route stack
2113
+ * (`createNativeRoute` over `castrum_route_*`) runs ONLY the stages in
2114
+ * `plan` (parseQuery/parseCookies/requireJsonBody/validateBody) in ONE
2115
+ * native call — no CORS/rate-limit/security/IP/metadata envelope. On a
2116
+ * verdict failure the route rejects (400 non-JSON / 422 schema); on success
2117
+ * the responder builds the 2xx from the decoded snapshot. Measured ~580ns
2118
+ * cheaper per request than the full-pipeline responder on a
2119
+ * parseQuery+parseCookies route (bench/cost/native-route-vs-router.ts).
2120
+ * Wired for `methods` (default `['GET']`). When set, `read`/`write`/etc.
2121
+ * for this route are ignored.
2122
+ */
2123
+ native?: {
2124
+ /** The route-wire v3 plan (parse/validate stages + limits). */
2125
+ plan: NativeRoutePlan;
2126
+ /** The JS 2xx builder (receives the decoded query/cookies/body snapshot). */
2127
+ handler: NativeResponder;
2128
+ /** HTTP methods to wire (default `['GET']`). */
2129
+ methods?: ReadonlyArray<string>;
2130
+ /** Read the body for `requireJsonBody`/`validateBody` (default false). */
2131
+ readBody?: boolean;
2132
+ };
1905
2133
  /** Terminal envelope style for this route's responder (default: router-level
1906
2134
  * `terminalStyle` or `'castrum'`). */
1907
2135
  terminalStyle?: TerminalStyle;
@@ -1981,6 +2209,9 @@ export declare function createIngressRouter(options: CreateIngressRouterOptions)
1981
2209
  *
1982
2210
  * The callback runs on the fast-path result and MUST be synchronous. Use
1983
2211
  * {@link createIngress} when you want automatic body reading + error contexts.
2212
+ *
2213
+ * @param options fast-path ingress options
2214
+ * @returns a sync handler (callback must be synchronous)
1984
2215
  */
1985
2216
  export declare function createIngressSync(options?: IngressOptions): SyncIngressHandler;
1986
2217
  /**
@@ -1989,6 +2220,9 @@ export declare function createIngressSync(options?: IngressOptions): SyncIngress
1989
2220
  *
1990
2221
  * Recommended default for serverless/edge-style `fetch` handlers. For maximum
1991
2222
  * throughput use `createIngressFast` / `createIngressSync` directly.
2223
+ *
2224
+ * @param options fast-path ingress options (security applied here, not forwarded)
2225
+ * @returns an async handler returning a snapshot context
1992
2226
  */
1993
2227
  export declare function createIngress(options?: IngressOptions): IngressHandler;
1994
2228
  /** Readonly snapshot of the ingress decision, safe after `run()` returns. */