castrum 0.9.0 → 0.9.2
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.
- package/README.md +153 -630
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +1653 -1817
- package/dist/index.js +6172 -4294
- package/docs/API.md +337 -0
- package/docs/ARCHITECTURE.md +111 -17
- package/docs/BENCHMARKS.md +117 -49
- package/docs/CASE_STUDY.md +243 -0
- package/docs/ENVIRONMENT.md +24 -7
- package/docs/FFI_BUN_GUIDE.md +335 -0
- package/docs/GETTING_STARTED.md +18 -34
- package/docs/HOW_TO_ADD_AN_OP.md +180 -0
- package/docs/INGRESS-ROUTER.md +82 -0
- package/docs/INGRESS.md +11 -7
- package/docs/NATIVE-ROUTE.md +77 -0
- package/docs/REPO_MAP.md +103 -53
- package/docs/adr/0001-two-wire-formats.md +1 -1
- package/docs/adr/0003-bun-builtins-delegation.md +6 -5
- package/docs/ai/LOCAL_DEV.md +81 -0
- package/docs/ai/TREE.md +1070 -0
- package/docs/bun-builtins-decision-matrix.md +25 -18
- package/index.ts +9 -16
- package/package.json +41 -28
- package/src/ingress/body.ts +167 -27
- package/src/ingress/decode/result-base.ts +10 -2
- package/src/ingress/handlers.ts +116 -285
- package/src/ingress/headers/security.ts +41 -0
- package/src/ingress/health.ts +1 -0
- package/src/ingress/index.ts +45 -169
- package/src/ingress/native-route.ts +182 -0
- package/src/ingress/packing/gather-raw-headers.ts +110 -22
- package/src/ingress/packing/header-packing.ts +5 -5
- package/src/ingress/packing/input-packer.ts +17 -3
- package/src/ingress/packing/route-wire.ts +197 -0
- package/src/ingress/packing/select-headers.ts +20 -7
- package/src/ingress/path-matcher.ts +115 -0
- package/src/ingress/response/baked-response.ts +302 -0
- package/src/ingress/router.ts +277 -0
- package/src/ingress/routes/common.ts +50 -1
- package/src/ingress/routes/fallback.ts +2 -2
- package/src/ingress/routes/head.ts +3 -13
- package/src/ingress/routes/index.ts +5 -4
- package/src/ingress/routes/json-write.ts +26 -47
- package/src/ingress/routes/native.ts +130 -0
- package/src/ingress/routes/options.ts +2 -12
- package/src/ingress/routes/read.ts +3 -13
- package/src/ingress/routes/responder.ts +159 -0
- package/src/ingress/server-node.ts +7 -7
- package/src/ingress/server.ts +83 -112
- package/src/ingress/sync.ts +151 -0
- package/src/ingress/types.ts +44 -1
- package/src/integration/pipeline.ts +3 -0
- package/src/integration/streaming.ts +2 -3
- package/src/integration/websocket.ts +4 -3
- package/src/loader/batch.ts +4 -0
- package/src/loader/cost.ts +1 -0
- package/src/loader/index.ts +43 -228
- package/src/loader/state.ts +84 -0
- package/src/loader/types.ts +145 -0
- package/src/native/ffi/build/codecs.ts +736 -0
- package/src/native/ffi/build/compress.ts +248 -0
- package/src/native/ffi/build/instances.ts +598 -0
- package/src/native/ffi/build/parse.ts +539 -0
- package/src/native/ffi/build/util.ts +140 -0
- package/src/native/ffi/build.ts +94 -0
- package/src/native/ffi/constants.ts +58 -0
- package/src/native/ffi/selftest.ts +22 -0
- package/src/native/ffi/types.ts +499 -0
- package/src/native/ffi.ts +307 -1357
- package/src/native/loader.ts +52 -6
- package/src/native/types/instances.ts +279 -0
- package/src/native/types.ts +41 -238
- package/src/runtime/builtins.ts +131 -0
- package/src/runtime/codec.ts +147 -0
- package/src/runtime/detect.ts +36 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/native.ts +35 -0
- package/src/runtime/server.ts +31 -0
- package/src/runtime/transport.ts +64 -0
- package/src/runtime/types.ts +173 -0
- package/src/runtime/uuid.ts +45 -0
- package/src/runtime/websocket.ts +30 -0
- package/src/rust-ffi/addon.ts +1 -1
- package/src/rust-ffi/batch/build.ts +155 -0
- package/src/rust-ffi/batch/index.ts +8 -0
- package/src/rust-ffi/batch/types.ts +92 -0
- package/src/rust-ffi/client.ts +5 -6
- package/src/rust-ffi/context.ts +39 -10
- package/src/rust-ffi/index.ts +28 -17
- package/src/rust-ffi/into.ts +21 -0
- package/src/rust-ffi/options.ts +1 -1
- package/src/rust-ffi/packed.ts +40 -2
- package/src/rust-ffi/proven.ts +16 -30
- package/src/rust-ffi/scalar/crypto.ts +188 -107
- package/src/rust-ffi/scalar/factories.ts +257 -27
- package/src/rust-ffi/scalar/hashing.ts +43 -41
- package/src/rust-ffi/scalar/http.ts +208 -77
- package/src/rust-ffi/scalar/index.ts +3 -3
- package/src/rust-ffi/scalar/interface.ts +88 -213
- package/src/rust-ffi/scalar/json-packed.ts +175 -0
- package/src/rust-ffi/scalar/json.ts +28 -12
- package/src/rust-ffi/scalar/payload.ts +159 -42
- package/src/rust-ffi/text.ts +31 -5
- package/src/selection.ts +27 -31
- package/src/shared/adaptive.ts +1 -2
- package/src/shared/buffer-pool.ts +72 -17
- package/src/shared/bytes.ts +75 -6
- package/src/shared/codec.ts +78 -0
- package/src/shared/env.ts +4 -4
- package/src/shared/log.ts +1 -0
- package/src/shared/packed/index.ts +10 -0
- package/src/shared/packed/parsers.ts +31 -0
- package/src/shared/packed/schema.ts +23 -0
- package/src/shared/{packed.ts → packed/wire.ts} +31 -39
- package/src/shared/proven.ts +189 -496
- package/src/shared/runtime.ts +20 -25
- package/src/shared/uuid.ts +12 -28
- package/src/data/json-rows.ts +0 -33
- package/src/rust-ffi/batch.ts +0 -431
- package/src/shared/bench-classify.ts +0 -209
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# `createIngressRouter` — per-route compiled ingress
|
|
2
|
+
|
|
3
|
+
`createIngressRouter` (`src/ingress/router.ts`) compiles a **route table into
|
|
4
|
+
dedicated native ingress instances** — one `IngressInner` per route, pruned to
|
|
5
|
+
exactly that route's stages. It is the "one super solution" over the existing
|
|
6
|
+
ingress core for servers with heterogeneous routes.
|
|
7
|
+
|
|
8
|
+
## Why per-route compilation
|
|
9
|
+
|
|
10
|
+
The plain `createIngressHandler` (see [`INGRESS.md`](./INGRESS.md)) serves every
|
|
11
|
+
path from ONE global `IngressInner` configured with a **superset** of options. A
|
|
12
|
+
router compiles each route's `options` into its OWN handler:
|
|
13
|
+
|
|
14
|
+
- The native pipeline prunes to that route's stages (`parseCookies`,
|
|
15
|
+
`parseQuery`, `schema`, `cors`, `rateLimit`, `limits`, `requireJsonBody`…).
|
|
16
|
+
- `buildHeaderPlan` produces a **per-route header plan** — routes that need no
|
|
17
|
+
headers gather ZERO headers.
|
|
18
|
+
- All routes share the SAME wire format (the existing packed
|
|
19
|
+
`handle_request_packed`), the shared process-wide rate-limiter budgets, and
|
|
20
|
+
optional `warmOnCreate` pre-warming.
|
|
21
|
+
|
|
22
|
+
The per-route native-compile idea is ALSO exposed standalone as the native route
|
|
23
|
+
stack (`rust/ingress/native_route.rs` + the `castrum_route_*` / napi `Route`
|
|
24
|
+
surface consumed by `@ignex/native`) — see [`NATIVE-ROUTE.md`](./NATIVE-ROUTE.md).
|
|
25
|
+
|
|
26
|
+
## API
|
|
27
|
+
|
|
28
|
+
### `RouterRouteSpec`
|
|
29
|
+
|
|
30
|
+
Per-route spec: `options` (per-route `IngressHandlerOptions`), plus flags that
|
|
31
|
+
wire route factories over that route's compiled handler:
|
|
32
|
+
|
|
33
|
+
| Flag | Routes wired |
|
|
34
|
+
|------|--------------|
|
|
35
|
+
| `read` | GET + HEAD read handler |
|
|
36
|
+
| `write` | POST/PUT/PATCH JSON-write handler |
|
|
37
|
+
| `echo` | POST echo handler |
|
|
38
|
+
| `cookies` | GET read (cookies-style) handler |
|
|
39
|
+
| `delete` | DELETE read-style handler |
|
|
40
|
+
| `responder` | JS responder route (`methods`, `readBody`, `terminalStyle`) |
|
|
41
|
+
| `native` | LEAN native-stack responder route: the route-wire v3 per-route stack (`createNativeRoute`) runs ONLY the plan's stages (parseQuery/parseCookies/requireJsonBody/validateBody) in ONE native call — no CORS/rate-limit/security/IP/metadata envelope. The responder builds the 2xx from the decoded snapshot. Measured ~580ns cheaper per request than the full-pipeline responder on a parseQuery+parseCookies route, and **+34% RPS at the HTTP level** on the bench server's `/api/native` vs `/api/users` (server-bound config). |
|
|
42
|
+
| `raw` | Raw `Request → Response` handler served OUTSIDE the pipeline (health/metrics probes) |
|
|
43
|
+
| `maxBodyBytes` / `bodyTimeoutMs` | Per-route write/echo overrides |
|
|
44
|
+
|
|
45
|
+
### `createIngressRouter(options)` → `IngressRouter`
|
|
46
|
+
|
|
47
|
+
- `routes: Record<string, RouterRouteSpec>` — the route table (required).
|
|
48
|
+
- `runtime?: BakedIngressRuntime` — shared hooks applied to every compiled route.
|
|
49
|
+
- `getIp`, `copyBody`, `terminalStyle` — shared defaults.
|
|
50
|
+
- `warmOnCreate?: boolean` — pre-warm every compiled route at construction.
|
|
51
|
+
|
|
52
|
+
`IngressRouter` exposes:
|
|
53
|
+
|
|
54
|
+
- `routeHandlers` — per-path compiled handlers.
|
|
55
|
+
- `routes` — path → method → handler map (compatible with `Bun.serve({ routes })`
|
|
56
|
+
and `createIngressServerNode`).
|
|
57
|
+
- `match(pathname)` — path matcher (`:param` / `*` dynamic routes).
|
|
58
|
+
- `fetch(req, srv?)` — fetch-style dispatcher.
|
|
59
|
+
- `prewarm()` — JIT-warm all compiled routes.
|
|
60
|
+
|
|
61
|
+
## Example
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const router = createIngressRouter({
|
|
65
|
+
warmOnCreate: true,
|
|
66
|
+
runtime: ingressMetrics?.runtime,
|
|
67
|
+
routes: {
|
|
68
|
+
'/health': { read: true, options: { parseCookies: false, parseQuery: false } },
|
|
69
|
+
'/api/users': {
|
|
70
|
+
read: true,
|
|
71
|
+
write: true,
|
|
72
|
+
options: { parseCookies: true, parseQuery: true, schema: USER_SCHEMA_BYTES },
|
|
73
|
+
},
|
|
74
|
+
'/metrics': { raw: metricsHandler(ingressMetrics) },
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
const server = Bun.serve({ routes: router.routes }) // or router.fetch
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The benchmark server `bench/http/servers/router-server.ts` uses this API (same route
|
|
81
|
+
surface as `ingress-server.ts` but per-route compiled). Cost breakdown:
|
|
82
|
+
`bun bench/cost/router-cost.ts` (`bun run bench:router`).
|
package/docs/INGRESS.md
CHANGED
|
@@ -83,7 +83,7 @@ provides ready-made route handlers, response builders, and a `Bun.serve`
|
|
|
83
83
|
builder — no need to hand-build responses, header templates, or error bodies.
|
|
84
84
|
|
|
85
85
|
All of these are exported from the package root and are **re-exported from the
|
|
86
|
-
benchmark server** (`bench/servers/ingress-server.ts`), which itself is built
|
|
86
|
+
benchmark server** (`bench/http/servers/ingress-server.ts`), which itself is built
|
|
87
87
|
entirely from them:
|
|
88
88
|
|
|
89
89
|
```ts
|
|
@@ -106,7 +106,7 @@ createIngressServer({
|
|
|
106
106
|
});
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
> **Wire format** (a contract — `bench/load.ts` depends on it):
|
|
109
|
+
> **Wire format** (a contract — `bench/http/load.ts` depends on it):
|
|
110
110
|
> - Success (with `emitMetadataJson: true` — the bench enables it): the body is
|
|
111
111
|
> Rust-generated `{"ok":true,"requestId":"...",...}`. With the DEFAULT
|
|
112
112
|
> `emitMetadataJson: false` the success body is **empty** (lean reads) — serve
|
|
@@ -138,6 +138,9 @@ native options as `createIngressFast` (`trustProxy`, `https`,
|
|
|
138
138
|
| `onResponse` | `(req, result, status, requestId) => void` | — | Hook invoked after a `Response` is produced (metrics/logging) |
|
|
139
139
|
| `onError` | `(req, requestId, error) => void` | — | Hook invoked when the native pipeline fails (silent 500s are otherwise invisible) |
|
|
140
140
|
| `structuredLog` | `boolean` | `false` | Emit one JSON line per request/error (gated by `CASTRUM_LOG_LEVEL`) |
|
|
141
|
+
| `logger` | `StructuredLogger` | — | Custom structured logger (custom stream/extra fields); implies `structuredLog: true` |
|
|
142
|
+
| `maxInFlight` | `number` | `0` (unlimited) | Hard cap on concurrently borrowed zero-copy output buffers (`BufferPool.maxInFlight`); exceeding it fails requests with 500 instead of allocating unbounded temporaries |
|
|
143
|
+
| `zeroCopyTimeoutMs` | `number` | `0` (disabled) | Abandonment guard (ms) for zero-copy responses: unread/cancelled bodies release their pooled buffer and close the stream |
|
|
141
144
|
|
|
142
145
|
Returns an `OptimizedIngressHandler`:
|
|
143
146
|
|
|
@@ -219,8 +222,9 @@ interface BakedServer {
|
|
|
219
222
|
|
|
220
223
|
### `createIngressServerNode(options)` — Node.js adapter
|
|
221
224
|
|
|
222
|
-
Bun remains the primary server target
|
|
223
|
-
|
|
225
|
+
Bun remains the primary server target. The public `createIngressServer` is
|
|
226
|
+
runtime-adaptive (Bun.serve on Bun, `node:http` on Node); for pinned Node
|
|
227
|
+
consumers `createIngressServerNode` serves the SAME pre-baked route
|
|
224
228
|
handlers over `node:http` (sharing `buildRouteHandlers`). It returns a
|
|
225
229
|
`NodeIngressServer` (`BakedServer` plus an async `ready: Promise<number>` that
|
|
226
230
|
resolves to the bound port — use it with `port: 0`).
|
|
@@ -287,7 +291,7 @@ rendering).
|
|
|
287
291
|
> (`createIngressServer`) and Node (`createIngressServerNode`). Raw handlers
|
|
288
292
|
> are served for GET only and skip the ingress pipeline / OPTIONS preflight.
|
|
289
293
|
> See `examples/basic-server.ts` and the bench ingress server
|
|
290
|
-
> (`bench/servers/ingress-server.ts`), which wire `/metrics` + `/healthz`
|
|
294
|
+
> (`bench/http/servers/ingress-server.ts`), which wire `/metrics` + `/healthz`
|
|
291
295
|
> `/readyz` `/livez` end-to-end.
|
|
292
296
|
|
|
293
297
|
**Tracing** — W3C trace-context helpers: `parseTraceParent(header)`,
|
|
@@ -518,6 +522,7 @@ interface SyncIngressHandler {
|
|
|
518
522
|
|
|
519
523
|
| Code | HTTP Status | Description |
|
|
520
524
|
|------|-------------|-------------|
|
|
525
|
+
| `ERR_CODE_REQUEST_TIMEOUT` (-1) | 408 | Body-read deadline exceeded on a write/echo route (`bodyTimeoutMs`, default 30 s) |
|
|
521
526
|
| `ERR_CODE_NONE` (0) | 200 / varies | No error |
|
|
522
527
|
| `ERR_CODE_CORS_PREFLIGHT` (1) | 403 / 204 | CORS preflight rejected (or allowed → 204) |
|
|
523
528
|
| `ERR_CODE_RATE_LIMITED` (2) | 429 | Rate limit exceeded |
|
|
@@ -741,5 +746,4 @@ async function* tick() {
|
|
|
741
746
|
}
|
|
742
747
|
}
|
|
743
748
|
return sseResponse(tick());
|
|
744
|
-
```
|
|
745
|
-
}
|
|
749
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Native route stack — the `@ignex/native` wire (route-wire v3)
|
|
2
|
+
|
|
3
|
+
`rust/ingress/native_route.rs` exposes the **per-route native stack**: a route
|
|
4
|
+
descriptor compiles once into a pre-baked `NativeRoute`, then each request runs
|
|
5
|
+
ONE native call. It is the LIVE external wire consumed by `@ignex/native`'s
|
|
6
|
+
`createNativeRoute` — a `castrum` consumer that compiles routes natively.
|
|
7
|
+
|
|
8
|
+
> **History**: the old `rust/route.rs` was dead external-project wire (a THIRD
|
|
9
|
+
> wire format, never bound in `src/native/ffi.ts`) and was removed in v0.9.0.
|
|
10
|
+
> `native_route.rs` implements the now-live ignex contract and supersedes it.
|
|
11
|
+
|
|
12
|
+
## Route-wire v3
|
|
13
|
+
|
|
14
|
+
- Magic `ROUT` (0x524f5554), version 3.
|
|
15
|
+
- Stage tags: `parseQuery = 0 … requireJsonBody = 5`.
|
|
16
|
+
- Result layout: `[flags u32][errorCode u32]` + optional query/cookie pair
|
|
17
|
+
sections.
|
|
18
|
+
|
|
19
|
+
Descriptor/stage/part tags + result layout must match `route-wire.ts` EXACTLY —
|
|
20
|
+
`ROUTE_DESC_VERSION` bumps on any wire change (a mismatched compiler/addon must
|
|
21
|
+
be a hard reject, never a silent misparse).
|
|
22
|
+
|
|
23
|
+
## Parse semantics (LENIENT)
|
|
24
|
+
|
|
25
|
+
Byte-parity with ignex's JS `queryPairs` / `cookiePairs`: malformed `%ZZ` /
|
|
26
|
+
invalid-UTF-8 `%FF` pass through raw, `+` → space, `%2B` → `+`, cookies trim +
|
|
27
|
+
DQUOTE-unwrap the VALUE but not the name, no cookie URL-decoding. Do NOT reuse
|
|
28
|
+
the strict scalar `query_parser` for this wire.
|
|
29
|
+
|
|
30
|
+
## Validation
|
|
31
|
+
|
|
32
|
+
The stack validates the BODY only (via `IngressSchema`); a non-body schema in
|
|
33
|
+
the descriptor is an unsupported feature → fail compile so the caller falls
|
|
34
|
+
back to JS. `requireJsonBody` → 400; `validateBody` schema fail → 422;
|
|
35
|
+
first-failure-wins in stage order.
|
|
36
|
+
|
|
37
|
+
## Surfaces
|
|
38
|
+
|
|
39
|
+
- `castrum_route_compile` / `castrum_route_run` / `castrum_route_destroy`
|
|
40
|
+
(C-ABI, needed-size convention, panic-guarded, immutable `&self` run).
|
|
41
|
+
- napi `Route` class (Node / fallback path).
|
|
42
|
+
- **Public castrum surface (`createNativeRoute`, `nativeRouteHandler`)**:
|
|
43
|
+
the stack is now a first-class castrum API, not just the ignex wire.
|
|
44
|
+
- `createNativeRoute(plan)` (`src/ingress/native-route.ts`) compiles a
|
|
45
|
+
route-wire v3 descriptor once and runs each frame in ONE native call —
|
|
46
|
+
`run(query, cookie, body)` / `runFrame(frame)` return the decoded verdict
|
|
47
|
+
(flags + errorCode + query/cookie pairs); `destroy()` frees the handle.
|
|
48
|
+
The pure wire helpers (`encodeRouteDescriptor`, `packRouteFrame`,
|
|
49
|
+
`decodeRouteResult`, layout constants) live in
|
|
50
|
+
`src/ingress/packing/route-wire.ts`.
|
|
51
|
+
- `nativeRouteHandler(plan, responder, opts)` (`src/ingress/routes/native.ts`)
|
|
52
|
+
wraps a compiled route as a `RouteHandler`: extracts the query substring +
|
|
53
|
+
Cookie header, runs the tiny frame, rejects 400/422 on verdict failure,
|
|
54
|
+
and hands the decoded snapshot to the responder for the 2xx.
|
|
55
|
+
- `createIngressRouter`'s `native` route spec wires it into a server (see
|
|
56
|
+
`INGRESS-ROUTER.md`). Measured: ~580ns cheaper per request than the
|
|
57
|
+
full-pipeline responder on a parseQuery+parseCookies route, and **+34%
|
|
58
|
+
RPS at the HTTP level** on the bench server's `/api/native` vs `/api/users`
|
|
59
|
+
(server-bound config, 2000 connections).
|
|
60
|
+
- Trade-off (deliberate): the native stack does NOT do CORS, rate limiting,
|
|
61
|
+
security headers, IP trust, or the castrum metadata envelope — routes that
|
|
62
|
+
need those must use the full pipeline. The lean path is for routes where
|
|
63
|
+
the framework owns the response body and only needs parse + verdict.
|
|
64
|
+
|
|
65
|
+
## Tests / parity
|
|
66
|
+
|
|
67
|
+
- `rust/ingress/native_route.rs` unit tests + `rust/ffi/` C-ABI tests.
|
|
68
|
+
- `test/unit/ingress/native-route.test.ts` (wire round-trip + lenient parity).
|
|
69
|
+
- `test/unit/ingress/native-route-public.test.ts` (public surface + router
|
|
70
|
+
`native` kind).
|
|
71
|
+
- External parity: ignex's `route-wire.test.ts` /
|
|
72
|
+
`packages/native/test/route.test.ts` (in the `@ignex/native` repo).
|
|
73
|
+
|
|
74
|
+
> **Naming note (flux → ignex)**: the external consumer was historically called
|
|
75
|
+
> `@flux/native` (castrum v0.8.0, guarded by `test/compat/flux-contract.test.ts`).
|
|
76
|
+
> The current live wire is `@ignex/native`. Both names appear in changelogs;
|
|
77
|
+
> `@ignex/native` is the active contract.
|
package/docs/REPO_MAP.md
CHANGED
|
@@ -14,14 +14,14 @@ agent editing this repo, [`AGENTS.md`](../AGENTS.md) is the agent-facing map.
|
|
|
14
14
|
|
|
15
15
|
- A **Rust cdylib** (`castrum.<platform>-<arch>.node`, built with napi-rs,
|
|
16
16
|
ALSO exporting `extern "C"` symbols for Bun's `bun:ffi` — see
|
|
17
|
-
`rust/ffi
|
|
17
|
+
`rust/ffi/`) provides the performance-critical primitives (parsers,
|
|
18
18
|
crypto, hashing, JSON, compression, and the HTTP **ingress pipeline**).
|
|
19
19
|
Under Bun, `bun:ffi` is the PRIMARY transport; NAPI is the fallback
|
|
20
20
|
(Node, `CASTRUM_FFI_MODE=napi`, or a failed ffi self-test).
|
|
21
21
|
- **TypeScript** (`src/`) provides the ergonomic public API: the flat `rust.*`
|
|
22
|
-
FFI client
|
|
22
|
+
FFI client and the ingress layer.
|
|
23
23
|
- It ships benchmarks: a **CPU benchmark** (`bench.ts` → `src/bench`) and an
|
|
24
|
-
**HTTP benchmark** (`bench/run-bench.ts` + `bench/servers/*`).
|
|
24
|
+
**HTTP benchmark** (`bench/http/run-bench.ts` + `bench/http/servers/*`).
|
|
25
25
|
|
|
26
26
|
Bun is the primary runtime; Node.js ≥20.3 is supported via a compiled ESM entry
|
|
27
27
|
(`dist/`).
|
|
@@ -37,16 +37,16 @@ Bun is the primary runtime; Node.js ≥20.3 is supported via a compiled ESM entr
|
|
|
37
37
|
| Build Rust addon (LOCAL max perf) | `bun run build:perf` | x86-64-v3 + AVX2 — **never for publish** |
|
|
38
38
|
| Build Rust addon (debug) | `bun run build:debug` | |
|
|
39
39
|
| Build compiled JS entry (Node) | `bun run build:js` | bundle + types → `dist/` (gitignored) |
|
|
40
|
-
| TS unit tests | `bun test` | ~
|
|
40
|
+
| TS unit tests | `bun test` | ~760 tests, `test/unit/**` |
|
|
41
41
|
| Rust unit tests | `bun run test:rust` | `cargo test`, ~440 tests |
|
|
42
42
|
| Node smoke tests | `bun run test:node` | explicit `.mjs` paths (Node 24 rejects dir arg) |
|
|
43
43
|
| Installed-tarball e2e | `bun run verify:install` | pack → install → import from `node_modules` |
|
|
44
44
|
| Typecheck | `bun run typecheck` | `bunx tsc --noEmit` (only `index.ts`, `bench.ts`, `src/`) |
|
|
45
45
|
| CPU benchmark + correctness | `bun run check` | == `bun bench.ts`; writes `bench/results/cpu/` |
|
|
46
|
-
| Proven-surface audit | `bun run check:proven[:fail]` | `:fail` gates CI on regressions |
|
|
47
|
-
| Performance JSDoc | `bun run check:annotate` | rewrites perf JSDoc from CPU report; `--dry-run` previews |
|
|
48
46
|
| HTTP bench (all servers) | `bun run bench:http` | |
|
|
49
47
|
| HTTP smoke (fast sanity) | `bun run bench:http:smoke` | **the wire-format guard** (CI-gated) |
|
|
48
|
+
| FFI transport benches | `bun run bench:ffi` / `bench:ffi:load` / `bench:ffi:public` / `bench:ffi:workers` / `bench:margin` | `bench/ffi/` |
|
|
49
|
+
| Ingress cost benches | `bun run bench:ingress-cost` / `bench:ingress-cost:post` / `bench:router` | `bench/cost/` |
|
|
50
50
|
| Startup bench | `bun run bench:startup` | import + first-call timing |
|
|
51
51
|
| JS lint (Biome) | `bun run lint` / `lint:fix` | `bunx biome check [--write] .` |
|
|
52
52
|
| JS format (Biome) | `bun run format` | `bunx biome format --write .` |
|
|
@@ -62,7 +62,7 @@ Bun is the primary runtime; Node.js ≥20.3 is supported via a compiled ESM entr
|
|
|
62
62
|
### Source (what you edit)
|
|
63
63
|
|
|
64
64
|
```
|
|
65
|
-
index.ts Package entry (Bun). Re-exports src/rust-ffi,
|
|
65
|
+
index.ts Package entry (Bun). Re-exports src/rust-ffi, shared helpers, src/ingress.
|
|
66
66
|
bench.ts CPU benchmark entry point → src/bench/run.ts.
|
|
67
67
|
|
|
68
68
|
examples/ Runnable sample app (basic-server.ts + README) — see also README Quick Start.
|
|
@@ -70,43 +70,75 @@ examples/ Runnable sample app (basic-server.ts + README) — see
|
|
|
70
70
|
|
|
71
71
|
src/
|
|
72
72
|
native/ The native transport layer: bun:ffi (primary on Bun) + NAPI fallback.
|
|
73
|
-
types.ts NativeAddon interface
|
|
73
|
+
types.ts NativeAddon module-surface interface (mirrors index.d.ts).
|
|
74
|
+
types/instances.ts Per-class INSTANCE types (Ingress, SchemaValidator, JwtSigner, ...) + result types.
|
|
74
75
|
loader.ts Addon path resolution (multi-root) + getAddon()/lazyAddon().
|
|
75
|
-
ffi.ts bun:ffi C-ABI
|
|
76
|
-
CASTRUM_FFI_MODE gating
|
|
76
|
+
ffi.ts bun:ffi C-ABI transport core (Bun-only): mode resolution, buffer/buffer_length
|
|
77
|
+
probe, bind() orchestration (self-test + CASTRUM_FFI_MODE gating + napi fallback).
|
|
78
|
+
ffi/ Decomposed bind surface: types.ts (BunFFI interface) + constants.ts +
|
|
79
|
+
selftest.ts (thin aggregator) + build.ts (orchestrator) +
|
|
80
|
+
build/{util,codecs,compress,parse,instances}.ts — per-domain method builders
|
|
81
|
+
EACH with its self-test* function, so a new castrum_* symbol is bound AND
|
|
82
|
+
self-tested in one domain file.
|
|
77
83
|
index.ts Barrel.
|
|
84
|
+
runtime/ THE RUNTIME ADAPTER seam (Bun/Node) — selected once at load.
|
|
85
|
+
detect.ts Cached runtime detection — the ONLY place `typeof Bun` is checked.
|
|
86
|
+
types.ts RuntimeAdapter contract (codec/uuid/env/builtins/transport/server/websocket).
|
|
87
|
+
codec.ts Runtime-native UTF-8 codec (Bun transfer vs TextEncoder/Decoder).
|
|
88
|
+
uuid.ts uuidv7 (Bun.randomUUIDv7 vs crypto.randomUUID).
|
|
89
|
+
builtins.ts BUN_WINS delegation registry (single source; `has(op)`).
|
|
90
|
+
transport.ts bun:ffi-first `resolve(op)` + ffi/napi surfaces (lazy bind).
|
|
91
|
+
native.ts FFI-facing seam (codec/uuid/env/builtins/transport) — imported by rust-ffi.
|
|
92
|
+
server.ts createIngressServer: Bun.serve on Bun, node:http on Node.
|
|
93
|
+
websocket.ts 101 upgrade on Bun; clear Bun-only error on Node.
|
|
94
|
+
index.ts FULL adapter `runtime` (native + server + websocket) — for the public API.
|
|
78
95
|
rust-ffi/ The flat `rust.*` FFI client (TS wrappers over the addon).
|
|
79
96
|
client.ts createRust() factory + the default `rust` instance + configure().
|
|
80
|
-
scalar/ / text.ts /
|
|
81
|
-
|
|
97
|
+
scalar/ / text.ts / packed.ts namespaces (buildScalar, buildText, ...).
|
|
98
|
+
batch/ Array-of-bytes batch namespace: types.ts (RustBatch interface) + build.ts (impl) + index.ts.
|
|
99
|
+
context.ts Per-instance state (caches, rayon-pool bookkeeping, runtime seam).
|
|
82
100
|
options.ts RustOptions + rayon-thread resolution + coercion helpers.
|
|
83
101
|
addon.ts The single shared lazy addon proxy.
|
|
84
|
-
proven.ts `proven` client — the FULL rust.* surface, annotated (same object as `rust`).
|
|
85
102
|
index.ts Barrel (exports rust, createRust, types).
|
|
86
103
|
shared/ Cross-cutting helpers.
|
|
87
|
-
runtime.ts
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
runtime.ts Facade over src/runtime/detect.ts (isBun/isNode — cached).
|
|
105
|
+
codec.ts Facade over src/runtime/codec.ts.
|
|
106
|
+
uuid.ts Facade over src/runtime/uuid.ts.
|
|
107
|
+
bytes.ts encoder/decoder (Uint8Array|string union normalization) + toPlainBuffer.
|
|
108
|
+
packed/ Packed wire-format helpers (split from the old packed.ts monolith).
|
|
109
|
+
wire.ts PURE byte encode/decode (u32/bitset/i64/byte-results/multipart, pairs, pack-scratch) — no addon.
|
|
110
|
+
schema.ts SchemaValidator alias + schemaValidateBatch/Count.
|
|
111
|
+
parsers.ts lazy-addon string parsers (parseQueryString/parseCookieHeader/parseFormBody).
|
|
112
|
+
index.ts Barrel (existing `../shared/packed` imports unchanged).
|
|
90
113
|
request-id.ts Zero-alloc request-id generator (both ingress paths).
|
|
91
114
|
buffer-pool.ts Generic reusable byte-buffer pool (pooled ingress output).
|
|
92
115
|
response.ts pooledBodyResponse (releases the pool on body consume).
|
|
93
116
|
log.ts createStructuredLogger (CASTRUM_LOG_LEVEL-gated JSON lines).
|
|
94
|
-
|
|
117
|
+
metrics.ts Zero-dep metrics registry (counters/gauges/histograms + Prometheus render).
|
|
118
|
+
trace.ts W3C traceparent/span-id helpers.
|
|
119
|
+
env.ts Centralized env-var resolution (CASTRUM_* + NAPI_RS_NATIVE_LIBRARY_PATH).
|
|
120
|
+
proven.ts Baked benchmark-proven op selection (PROVEN_SELECTION — pure data).
|
|
121
|
+
loader/ Higher-order-function data loader (HFC) over the curated op set.
|
|
122
|
+
index.ts Dispatch core + factory (scalar/bulk dispatch, coalescing, LRU cache).
|
|
123
|
+
ops.ts Op registry (LOADER_OPS) + type tower; state.ts / cost.ts / batch.ts / types.ts.
|
|
95
124
|
ingress/ The HTTP ingress pipeline (two paths — see §4.2).
|
|
96
125
|
fast.ts PATH 1: createIngressFast (packed-input via handleRequestPacked).
|
|
97
126
|
handlers.ts PATH 2: createIngressHandler (JS-packs the frame via IngressInputPacker
|
|
98
|
-
+ gatherRawHeadersPacked → handleRequestPacked)
|
|
99
|
-
|
|
127
|
+
+ gatherRawHeadersPacked → handleRequestPacked); response builders
|
|
128
|
+
extracted to response/baked-response.ts.
|
|
129
|
+
index.ts Pure public barrel (all re-exports).
|
|
130
|
+
sync.ts createIngressSync / createIngress convenience factories (path 1 wrapper).
|
|
100
131
|
shared.ts JS constants + buildHeaderPlan + assertSyncCallback (shared by both paths).
|
|
101
132
|
constants.ts Binary-layout constants read from Rust at runtime (SINGLE SOURCE).
|
|
102
133
|
status.ts / errors.ts / options.ts / types.ts / body.ts / context.ts
|
|
103
134
|
packing/ header-packing.ts, input-packer.ts, gather-raw-headers.ts
|
|
104
|
-
headers/ cors.ts, hsts.ts, fast-templates.ts, baked-templates.ts
|
|
135
|
+
headers/ cors.ts, hsts.ts, security.ts (baked security merge), fast-templates.ts, baked-templates.ts
|
|
105
136
|
decode/ fast-result.ts, baked-result.ts (TWO decoders — see §4.2)
|
|
106
|
-
response/ terminal.ts, error-bodies.ts
|
|
107
|
-
routes/ read/head/json-write/echo/fallback factories + common.ts
|
|
137
|
+
response/ terminal.ts, error-bodies.ts, baked-response.ts (pre-baked response builders)
|
|
138
|
+
routes/ read/head/json-write/echo/fallback factories + common.ts + responder.ts
|
|
108
139
|
server.ts createIngressServer (Bun.serve) + buildRouteHandlers + gracefulShutdown.
|
|
109
140
|
server-node.ts createIngressServerNode (node:http adapter, same route handlers).
|
|
141
|
+
path-matcher.ts buildPathMatcher + PathMatch + safeDecode (shared by server-node + router).
|
|
110
142
|
integration/ Framework integration (wraps path 2 — createIngressHandler).
|
|
111
143
|
pipeline.ts createPipeline (framework-agnostic request stage for Hono/Elysia/Bun.serve).
|
|
112
144
|
websocket.ts createWebSocketUpgrade (RFC 6455 101 handshake + subprotocol).
|
|
@@ -119,20 +151,33 @@ src/
|
|
|
119
151
|
```
|
|
120
152
|
rust/ ONE cdylib crate (Cargo [lib] → lib.rs).
|
|
121
153
|
lib.rs Declaration hub + module map + crate docs.
|
|
122
|
-
|
|
154
|
+
selection.rs Rust-side op selection metadata (mirrors src/selection.json).
|
|
155
|
+
util/ Shared infrastructure: bytes, packed, batch/ (api.rs + core.rs + tests.rs),
|
|
156
|
+
threadpool, validation.
|
|
123
157
|
http/ HTTP wire formats & parsing: headers, method, http/cookie/query parsers,
|
|
124
|
-
form, media_type, url_codec, url_join, etag, accept,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
158
|
+
form, media_type, url_codec, url_join, http_date, etag, accept,
|
|
159
|
+
mime_lookup, multipart.
|
|
160
|
+
crypto/ Auth & hashing: hmac_sha256, cookie_sign, csrf, jwt/ (token.rs + api.rs +
|
|
161
|
+
tests.rs), aead, argon2, base64, ed25519, hashing (fnv/crc32/xxh3),
|
|
162
|
+
random_token.
|
|
163
|
+
json/ JSON & schema: json_ops, json_ser, json/patch/ (pointer.rs + ops.rs +
|
|
164
|
+
engine.rs + api.rs + tests.rs), json_schema (napi),
|
|
128
165
|
fast_schema/ (zero-DOM engine — pure, no napi).
|
|
129
166
|
payload/ Output & streaming: compress, sse, ws_frames, websocket, template.
|
|
130
|
-
ingress/ THE ingress pipeline:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
167
|
+
ingress/ THE ingress pipeline: api.rs (Ingress/IngressInner napi class) + mod.rs
|
|
168
|
+
(declaration hub), pipeline.rs (core 8-stage), native_route.rs (per-route
|
|
169
|
+
native stack), options/time/packed, cors, proxy, ip_trust, rate_limit,
|
|
170
|
+
terminal, output.rs (single numeric layout source),
|
|
171
|
+
ingress_constants.rs (napi projection).
|
|
172
|
+
ffi/ #[no_mangle] extern "C" exports (85 castrum_* symbols — 77 direct + 4
|
|
173
|
+
validator_c_abi! + 4 compress_to_out!; parity guarded by
|
|
174
|
+
test/unit/native/ffi-symbol-parity.test.ts) for Bun's bun:ffi
|
|
134
175
|
primary transport, incl. castrum_ingress_layout (the layout blob).
|
|
135
|
-
|
|
176
|
+
mod.rs (module map) + util.rs (panic_guard / HMAC cache / cstring helpers)
|
|
177
|
+
+ per-domain wrappers (hashing/validators/crypto/jwt/http/payload/json/
|
|
178
|
+
rate_limit/ingress/route/probe) + tests.rs.
|
|
179
|
+
panic_safety.rs / test_support.rs Cross-module fuzz + shared #[cfg(test)] helpers.
|
|
180
|
+
proptest_suite.rs Property-based adversarial-parser tests (dev-dep proptest).
|
|
136
181
|
```
|
|
137
182
|
|
|
138
183
|
### Generated / ignored (do NOT edit)
|
|
@@ -142,7 +187,7 @@ rust/ ONE cdylib crate (Cargo [lib] → lib.rs).
|
|
|
142
187
|
| `dist/` | Compiled ESM entry + types for Node | Built by `build:js`; gitignored |
|
|
143
188
|
| `index.js` / `index.d.ts` | NAPI-RS generated loader/declarations | Regenerated by `napi build`; gitignored |
|
|
144
189
|
| `castrum.<platform>-<arch>.node` | The native addon binary | Built locally; gitignored |
|
|
145
|
-
| `bench/results/` | Bench reports (cpu/, http/) | Generated; gitignored |
|
|
190
|
+
| `bench/results/` | Bench reports (cpu/, per-server http/, ffi-margin/, router/, startup/, autocannon/) | Generated; gitignored |
|
|
146
191
|
| `target/` | Cargo build output | gitignored |
|
|
147
192
|
| `node_modules/` | Deps | gitignored |
|
|
148
193
|
| `artifacts/` | CI publish staging dir | gitignored |
|
|
@@ -154,7 +199,7 @@ rust/ ONE cdylib crate (Cargo [lib] → lib.rs).
|
|
|
154
199
|
### 4.1 Constants never drift (single numeric source)
|
|
155
200
|
`rust/ingress/output.rs` defines every ingress layout constant once → projected
|
|
156
201
|
to JS two ways: `rust/ingress/ingress_constants.rs` via `#[napi] const` (NAPI)
|
|
157
|
-
AND `rust/ffi
|
|
202
|
+
AND `rust/ffi/` `IngressLayout` via `castrum_ingress_layout` (C ABI, primary
|
|
158
203
|
on Bun) → `src/ingress/constants.ts` reads them at runtime (ffi blob on Bun,
|
|
159
204
|
napi on Node). Drift is pinned by the Rust unit test
|
|
160
205
|
`ingress_layout_c_abi_matches_output_source` and the bun:ffi bind-time
|
|
@@ -173,7 +218,7 @@ self-test. **Never hardcode a layout value** — if you change the layout, chang
|
|
|
173
218
|
`{"ok":true,...,"requestId":...}` (success) / `{"ok":false,"error":{code,
|
|
174
219
|
message}}` (errors) with `ratelimit-*` headers.
|
|
175
220
|
|
|
176
|
-
These wire formats are a **benchmark contract** (`bench/load.ts` validates
|
|
221
|
+
These wire formats are a **benchmark contract** (`bench/http/load.ts` validates
|
|
177
222
|
them) — they are intentionally NOT unified. The two decoders
|
|
178
223
|
(`decode/fast-result.ts` vs `decode/baked-result.ts`) and the two header-template
|
|
179
224
|
builders (`headers/fast-templates.ts` vs `headers/baked-templates.ts`) exist for
|
|
@@ -185,22 +230,22 @@ this reason. **Shared between both paths**: `generateRequestId`
|
|
|
185
230
|
- `src/native/loader.ts` resolves the `.node` from multiple roots so it works
|
|
186
231
|
from both the source layout (`src/…`) and the bundled layout (`dist/…`).
|
|
187
232
|
Honors `CASTRUM_NATIVE_LIBRARY_PATH` / `NAPI_RS_NATIVE_LIBRARY_PATH`.
|
|
188
|
-
- Runtime detection lives **only** in `src/
|
|
189
|
-
|
|
190
|
-
`
|
|
233
|
+
- Runtime detection lives **only** in `src/runtime/detect.ts` (resolved once at
|
|
234
|
+
load; `src/shared/runtime.ts` is a thin facade). The public
|
|
235
|
+
`createIngressServer` (via the adapter's `server`) picks `Bun.serve` on Bun
|
|
236
|
+
and the `node:http` backend on Node; `createIngressServerNode` stays for
|
|
237
|
+
pinned Node users (same route handlers via `buildRouteHandlers`).
|
|
191
238
|
- ⚠️ `src/ingress/constants.ts` is the **one module that dlopens the addon at
|
|
192
239
|
import time** (its constants are needed as soon as ingress is imported).
|
|
193
240
|
Everything else is lazy.
|
|
194
241
|
|
|
195
|
-
### 4.4 The
|
|
196
|
-
`src/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
`src/
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
and marked `@deprecated` when slower. `bun run check:proven:fail` (CI) audits
|
|
203
|
-
the registry against the CPU benchmark report on a **release** build.
|
|
242
|
+
### 4.4 The runtime adapter
|
|
243
|
+
`src/runtime/` is the single seam for every Bun-vs-Node and transport decision
|
|
244
|
+
(detection, codec, uuid, env, the BUN_WINS built-in delegation, the bun:ffi-vs-
|
|
245
|
+
napi transport, the ingress server backend, websocket) — selected ONCE at
|
|
246
|
+
module load. The FFI layer imports the seam (`src/runtime/native.ts`); the
|
|
247
|
+
public API imports the full `runtime` (`src/runtime/index.ts`). No
|
|
248
|
+
`isBun()` / `getBunFFI()` branches in builders.
|
|
204
249
|
|
|
205
250
|
### 4.5 Pooled output buffers (path 2)
|
|
206
251
|
`createIngressHandler` owns a `BufferPool` (`src/shared/buffer-pool.ts`) sized
|
|
@@ -217,16 +262,19 @@ consumed.
|
|
|
217
262
|
|
|
218
263
|
## 5. Testing & benchmarks
|
|
219
264
|
|
|
220
|
-
- **TS**: `bun test` → `test/unit/**` (`
|
|
265
|
+
- **TS**: `bun test` → `test/unit/**` (`native/`, `rust-ffi/`, `contract/`,
|
|
266
|
+
`ingress/`, `shared/`, `integration/`) + `test/compat/` + `test/property/`.
|
|
267
|
+
See `test/README.md` for the intended split.
|
|
221
268
|
- **Rust**: `cargo test` → per-module `#[cfg(test)] mod tests` + cross-module
|
|
222
|
-
`rust/
|
|
269
|
+
`rust/panic_safety.rs` (fuzz) + `rust/proptest_suite.rs`.
|
|
223
270
|
- **Node**: `test/integration/node-smoke.test.mjs` + `node-enterprise.test.mjs`
|
|
224
271
|
via `node --test` (run with `bun run test:node`).
|
|
225
272
|
- **CPU bench**: `bun run check` (correctness checks + comparisons). Sub-µs ops
|
|
226
273
|
are batched (`CASTRUM_BENCH_BATCH_SIZE`). Report → `bench/results/cpu/`.
|
|
227
|
-
- **HTTP bench**: `bench/run-bench.ts` + `bench/load.ts` (load generator that
|
|
228
|
-
validates response SHAPE) + `bench/servers/{bun,elysia,ingress}-server.ts`.
|
|
229
|
-
`bench:http:smoke` is the fast CI wire-format gate.
|
|
274
|
+
- **HTTP bench**: `bench/http/run-bench.ts` + `bench/http/load.ts` (load generator that
|
|
275
|
+
validates response SHAPE) + `bench/http/servers/{bun,elysia,ingress,router}-server.ts`.
|
|
276
|
+
`bench:http:smoke` is the fast CI wire-format gate. FFI-transport benches live in
|
|
277
|
+
`bench/ffi/`; per-request cost benches in `bench/cost/` (see `bench/README.md`).
|
|
230
278
|
|
|
231
279
|
---
|
|
232
280
|
|
|
@@ -251,9 +299,11 @@ consumed.
|
|
|
251
299
|
| Doc | Purpose |
|
|
252
300
|
|-----|---------|
|
|
253
301
|
| [`GETTING_STARTED.md`](./GETTING_STARTED.md) | Beginner tutorial (intern-friendly) |
|
|
302
|
+
| [`API.md`](./API.md) | The complete public API reference |
|
|
303
|
+
| [`CASE_STUDY.md`](./CASE_STUDY.md) | Data-driven case study: how castrum was built and measured |
|
|
254
304
|
| [`ARCHITECTURE.md`](./ARCHITECTURE.md) | Deep-dive: modules, data flow, output layout, memory, concurrency |
|
|
255
305
|
| [`INGRESS.md`](./INGRESS.md) | The pre-baked ingress API + route factories + servers |
|
|
256
306
|
| [`BENCHMARKS.md`](./BENCHMARKS.md) | Benchmark scenarios, report format, how to run |
|
|
257
307
|
| [`ENVIRONMENT.md`](./ENVIRONMENT.md) | All `CASTRUM_*` env vars (+ legacy aliases) |
|
|
258
308
|
| [`AGENTS.md`](../AGENTS.md) | AI-agent editing guidance (commands, constraints, gotchas) |
|
|
259
|
-
| `README.md` | Public-facing
|
|
309
|
+
| `README.md` | Public-facing overview + quick start |
|
|
@@ -20,7 +20,7 @@ They also use separate result decoders (`FastIngressResult` vs
|
|
|
20
20
|
|
|
21
21
|
## Decision
|
|
22
22
|
|
|
23
|
-
**Do not unify them.** The HTTP benchmark load generator (`bench/load.ts`)
|
|
23
|
+
**Do not unify them.** The HTTP benchmark load generator (`bench/http/load.ts`)
|
|
24
24
|
requires `ok === true` + `requestId: string` on success and `error.code` /
|
|
25
25
|
`error.message` on errors — i.e. path 2's format. The two formats are a
|
|
26
26
|
**benchmark contract**: the benchmark server uses path 2, and changing its
|
|
@@ -17,7 +17,7 @@ Every overlapping op is **benchmarked against its Bun built-in** before it is
|
|
|
17
17
|
built, kept, or delegated. Results live in
|
|
18
18
|
`docs/bun-builtins-decision-matrix.md`. Rules:
|
|
19
19
|
|
|
20
|
-
1. **Delegate to Bun** at the
|
|
20
|
+
1. **Delegate to Bun** at the selection/delegation layer where Bun wins
|
|
21
21
|
clearly: non-crypto hashing (`Bun.hash.crc32` ~3x, `xxHash3` ~4x),
|
|
22
22
|
gzip (`Bun.gzipSync`/`gunzipSync`), `Bun.randomUUIDv7` (~2x), HMAC
|
|
23
23
|
(`Bun.CryptoHasher` ~1.2x).
|
|
@@ -31,10 +31,11 @@ built, kept, or delegated. Results live in
|
|
|
31
31
|
|
|
32
32
|
- New `rust.*` primitives ship with a Bun-built-in baseline + decision-matrix
|
|
33
33
|
entry (`xxh3`, `passwordHashBcrypt`/`verifyBcrypt`, `pbkdf2Sha256`).
|
|
34
|
-
-
|
|
35
|
-
(e.g. `xxh3` is
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
- The Bun/JS baselines are recorded honestly in the decision matrix
|
|
35
|
+
(e.g. `xxh3` is measured `Bun.hash.xxHash3` ~4x faster — the public
|
|
36
|
+
`rust.xxh3` delegates to it under Bun via the runtime adapter's `builtins`).
|
|
37
|
+
- Diagnostic benchmark tasks use `diag:` names so they are never mistaken for
|
|
38
|
+
shipped-op `native:`/`rust:` comparisons.
|
|
38
39
|
|
|
39
40
|
## See also
|
|
40
41
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Local Development with the Core Projects — `bun link`
|
|
2
|
+
|
|
3
|
+
> **Scope**: maintainers and AI agents working across the IgnEX core stack.
|
|
4
|
+
> Application developers consume published versions from npm and do **not**
|
|
5
|
+
> need this file.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
The IgnEX core packages live side-by-side, one directory back from this repo,
|
|
10
|
+
in `/home/adeel/poc/`. When a change in this repo must be tested against the
|
|
11
|
+
local source of a core project (or a core change must be tested against this
|
|
12
|
+
repo), use `bun link` so the consumer resolves the local directory instead of
|
|
13
|
+
the npm registry. This is the supported Bun ≥ 1.4 (Rust-based runtime)
|
|
14
|
+
local-development mechanism
|
|
15
|
+
([`bun link` docs](https://bun.com/docs/cli/link),
|
|
16
|
+
[bun.com/blog/bun-v1.4](https://bun.com/blog/bun-v1.4)).
|
|
17
|
+
|
|
18
|
+
This workflow is **only for maintainers and AI agents working with the core
|
|
19
|
+
projects**. CI and release pipelines always resolve from the registry; local
|
|
20
|
+
links are a development-only convenience.
|
|
21
|
+
|
|
22
|
+
## Core packages (one directory back)
|
|
23
|
+
|
|
24
|
+
| Repo (`/home/adeel/poc/`) | Package(s) | `bun link` name |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `ignus` — ignex monorepo, workspaces `packages/*` | `@ignex/core`, `@ignex/cli`, `@ignex/compiler`, `@ignex/native`, `@ignex/shared`, `@ignex/mcp`, `@ignex/app`, `create-ignex` | run `bun link` inside each package dir |
|
|
27
|
+
| `bun-rust-runtime-bench` | `castrum` (Rust addon `castrum.<platform>-<arch>.node`) | `castrum` |
|
|
28
|
+
| `ignex-mongodb` | `@ignex/ninox` | `@ignex/ninox` |
|
|
29
|
+
| `ignex-nova` | `@ignex/nova` | `@ignex/nova` |
|
|
30
|
+
|
|
31
|
+
Known cross-repo edges (verify with `grep` in `package.json` before assuming):
|
|
32
|
+
|
|
33
|
+
- `@ignex/native` (in `ignus/packages/native`) depends on `castrum` — link
|
|
34
|
+
`castrum` there when changing the addon wire (`createNativeRoute`).
|
|
35
|
+
- `@ignex/core` (in `ignus/packages/core`) optionally depends on
|
|
36
|
+
`@ignex/nova`; `@ignex/app` depends on `@ignex/core`, `@ignex/cli`, and
|
|
37
|
+
`@ignex/ninox`.
|
|
38
|
+
|
|
39
|
+
## How to link
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 1. Register the core package (once per machine, from the core repo):
|
|
43
|
+
cd /home/adeel/poc/ignex-mongodb
|
|
44
|
+
bun link # → Success! Registered "@ignex/ninox"
|
|
45
|
+
|
|
46
|
+
# 2. Link it into the consumer project:
|
|
47
|
+
cd /home/adeel/poc/ignex-app
|
|
48
|
+
bun link @ignex/ninox # symlinks node_modules/@ignex/ninox → ../ignex-mongodb
|
|
49
|
+
bun link @ignex/ninox --save # also writes "link:@ignex/ninox" into package.json deps
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `bun link` (no args) in a package directory registers that package globally
|
|
53
|
+
for this user.
|
|
54
|
+
- `bun link <name>` in a consumer creates a symlink in the consumer's
|
|
55
|
+
`node_modules` pointing at the registered directory. `--save` additionally
|
|
56
|
+
records `"<name>": "link:<name>"` in `package.json` dependencies.
|
|
57
|
+
- Unregister a package: `bun unlink` from the core repo dir.
|
|
58
|
+
- Return to registry versions: remove the `link:` entry (or `bun unlink
|
|
59
|
+
<name>` in the consumer) and `bun install`.
|
|
60
|
+
|
|
61
|
+
## Rust-core caveats (castrum & @ignex/nova)
|
|
62
|
+
|
|
63
|
+
Both `castrum` and `@ignex/nova` ship a Rust cdylib. After changing Rust
|
|
64
|
+
source, rebuild the addon **before** linking or using:
|
|
65
|
+
|
|
66
|
+
- `castrum`: `bun run build` (release `napi build`) or `bun run build:debug`.
|
|
67
|
+
Under Bun the addon is also called through `bun:ffi` from the same cdylib.
|
|
68
|
+
- `@ignex/nova`: `bun run build:rust`
|
|
69
|
+
(`cargo build --release --manifest-path rust/Cargo.toml`), or rely on
|
|
70
|
+
`prepack`/`prebuild` to stage `prebuilds/<platform>-<arch>/`.
|
|
71
|
+
|
|
72
|
+
A stale `.node`/`.so` silently serves old behavior — rebuild, then re-test.
|
|
73
|
+
`@ignex/nova` additionally fails its bind-time self-test on schema/wire-version
|
|
74
|
+
mismatch (`IGNEX_FFI_PATH` can point at a specific build); `castrum` falls back
|
|
75
|
+
to the napi transport when the `bun:ffi` self-test fails.
|
|
76
|
+
|
|
77
|
+
## Never publish from a linked tree
|
|
78
|
+
|
|
79
|
+
Publishing a consumer whose dependencies are `link:` entries ships symlinks,
|
|
80
|
+
not packages. Releases always run against registry versions (`prepublishOnly` /
|
|
81
|
+
CI re-verify with a clean install). Keep `bun link` strictly local.
|