@brust/core 0.0.0-stage → 2.0.0-alpha.10
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 +151 -2
- package/bin/brust +4 -0
- package/native/index.d.ts +226 -0
- package/native/index.js +796 -0
- package/package.json +102 -4
- package/src/actions/client.ts +71 -0
- package/src/actions/elysia.ts +15 -0
- package/src/actions/load.ts +23 -0
- package/src/actions/prefix.ts +45 -0
- package/src/actions-bridge.ts +173 -0
- package/src/browser.ts +31 -0
- package/src/build/actions-only.ts +86 -0
- package/src/build/build-eval.ts +319 -0
- package/src/build/build-icons.ts +69 -0
- package/src/build/bundle.ts +630 -0
- package/src/build/cf-assets.ts +83 -0
- package/src/build/cf-core.ts +90 -0
- package/src/build/cf-entry.ts +76 -0
- package/src/build/cf-wrangler.ts +169 -0
- package/src/build/cloudflare.ts +317 -0
- package/src/build/compile.ts +215 -0
- package/src/build/config.ts +332 -0
- package/src/build/css.ts +275 -0
- package/src/build/errors.ts +11 -0
- package/src/build/features.ts +110 -0
- package/src/build/index.ts +402 -0
- package/src/build/manifest.ts +371 -0
- package/src/build/pkg-src.ts +383 -0
- package/src/build/scan.ts +83 -0
- package/src/build/server-only.ts +39 -0
- package/src/build/ssg-render.ts +65 -0
- package/src/build/ssg.ts +146 -0
- package/src/build/static-worker.ts +91 -0
- package/src/build/static.ts +258 -0
- package/src/build/tailwind.ts +117 -0
- package/src/build/tsconfig-paths.ts +67 -0
- package/src/build-time.ts +10 -0
- package/src/cache-adapter/bridge.ts +91 -0
- package/src/cache-adapter/conformance.ts +226 -0
- package/src/cache-adapter/index.ts +10 -0
- package/src/cache-adapter/load.ts +144 -0
- package/src/cache-adapter/memory.ts +229 -0
- package/src/cache-adapter/runtime.ts +124 -0
- package/src/cache-adapter/types.ts +96 -0
- package/src/cache-adapter/workers.ts +118 -0
- package/src/cache-core.ts +15 -0
- package/src/cache.ts +32 -0
- package/src/cf.ts +19 -0
- package/src/cli.ts +144 -0
- package/src/config.ts +302 -0
- package/src/css-modules.d.ts +8 -0
- package/src/dev/bun.ts +365 -0
- package/src/dev/client.ts +92 -0
- package/src/dev/devtools-data.ts +23 -0
- package/src/dev/devtools.ts +356 -0
- package/src/dev/diag.ts +30 -0
- package/src/dev/proxy.ts +236 -0
- package/src/dev/tui.ts +144 -0
- package/src/dev/watch.ts +81 -0
- package/src/dev.ts +93 -0
- package/src/handlers.ts +282 -0
- package/src/index.ts +20 -0
- package/src/native.ts +132 -0
- package/src/routes.ts +276 -0
- package/src/run.ts +194 -0
- package/src/server-handlers.ts +32 -0
- package/src/server.ts +5 -0
- package/src/store/core-adapter.ts +32 -0
- package/src/store/index.ts +4 -0
- package/src/store/react.ts +1 -0
- package/src/store/server-context.ts +55 -0
- package/src/worker.ts +229 -0
package/README.md
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @brust/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Rust serves the pages; N Bun workers answer only `loader` and `jobs` calls. One napi addon
|
|
4
|
+
(`crates/brust-napi`, loader generated into `native/` by `napi build`) binds `brust-server` and
|
|
5
|
+
`brust-compiler`. Workspace-private (0.0.0) until m2e publishes it; peers `react`/`react-dom` 19.
|
|
6
|
+
|
|
7
|
+
## Routes (`routes.tsx`)
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { defineRoutes, notFound } from '@brust/core/routes'
|
|
11
|
+
export const routes = defineRoutes([
|
|
12
|
+
{ Component: AppLayout, children: [ // layout renders <Outlet />
|
|
13
|
+
{ path: '/', Component: HomePage }, // static: never calls Bun
|
|
14
|
+
{ path: '/items/{id}', Component: ItemPage,
|
|
15
|
+
loader: async ({ params }) => (params.id === 'x' ? notFound({}) : { item: await load(params.id) }),
|
|
16
|
+
cache: { ttl_seconds: 60, tags: ['items'] } }, // a HIT never calls Bun
|
|
17
|
+
] },
|
|
18
|
+
])
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Route fields are exactly `path`, `Component`, `loader`, `cache`, `ssg`, `children` (`ssg` only matters
|
|
22
|
+
to `--target static`, see [docs/static.md](../../docs/static.md)); anything else fails the
|
|
23
|
+
build with `<field> is not supported in M2 (M3)`. Loaders run root → leaf, results merge flat
|
|
24
|
+
(later keys win); return `notFound(data)` / `redirect(location, status)` or throw
|
|
25
|
+
`httpError(status, body)` for a verdict. `cache(Comp, opts)` and `cache.invalidate(…)` ship too.
|
|
26
|
+
|
|
27
|
+
## `brust build [entry=routes.tsx] [--out-dir dist]`
|
|
28
|
+
|
|
29
|
+
`--target static [--strict]` also renders every page to `dist/static/` for Cloudflare Pages or Workers
|
|
30
|
+
Static Assets: see [docs/static.md](../../docs/static.md).
|
|
31
|
+
|
|
32
|
+
Compiles every route component through lowering, then writes (always from scratch):
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
dist/manifest.json the only build → server contract (spec S6)
|
|
36
|
+
dist/jinja/<id>.jinja templates dist/jobs.js + jobs/*.server.ts worker jobs
|
|
37
|
+
dist/client/*-<hex>.js runtime, native chunks, react-<id> island chunks (served immutable)
|
|
38
|
+
dist/client/*-<hex>.css one minified stylesheet per imported .css / .module.css (see docs/css.md)
|
|
39
|
+
dist/public/ copied from ./public dist/index.js `bun dist/index.js` = start
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Every route component's `children[]` also gets one static entry per native descendant (at any depth) that has a client chunk, so its `<script>` is linked once per page.
|
|
43
|
+
|
|
44
|
+
A diagnostic (`nested-instance`, `outlet-in-react`, `outlet-outside-layout`, unsupported route
|
|
45
|
+
field, unresolvable Component) prints `error <rule> <message>` and exits 1. The route tree is
|
|
46
|
+
validated whether or not the entry used `defineRoutes` (`route-config`, `duplicate-route` for two
|
|
47
|
+
patterns the router cannot hold together, e.g. `/a` and `/a/`).
|
|
48
|
+
|
|
49
|
+
Server-only code never reaches a browser bundle (`error server-only-in-client <importer> imports
|
|
50
|
+
<spec>`, at any import depth, react islands included): `node:*` / `bun:*` / bare Node builtins
|
|
51
|
+
(unless an npm package of that name is installed), `@brust/core/server|native`, any
|
|
52
|
+
`*.server.*` file, and the paths/prefixes listed in `brust.toml`:
|
|
53
|
+
|
|
54
|
+
```toml
|
|
55
|
+
[build]
|
|
56
|
+
server_only = ["lib/server", "@acme/db"] # import prefixes, or paths relative to the app root
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The `server-only` marker package is honoured the same way: a browser build that reaches `import 'server-only'` (any depth, islands included) fails with `server-only-in-client` whether or not the package throws, and on the server side (route scan, jobs, actions, the Worker) it resolves to an empty module, so a loader may import a module guarded by it.
|
|
60
|
+
|
|
61
|
+
Loaders and precompute jobs may use all of it (they run in Bun). In the browser `@brust/core`
|
|
62
|
+
resolves to a side-effect-free entry (`cache`, route helpers, verdicts; `cache.invalidate` throws).
|
|
63
|
+
|
|
64
|
+
The out dir is rebuilt in a sibling temp dir and swapped in only on success (a failed build leaves
|
|
65
|
+
the previous one untouched); an out dir that is the filesystem root, the home dir, the app root or
|
|
66
|
+
an ancestor of it, or that holds the entry / a route Component / `public/`, is refused
|
|
67
|
+
(`out-dir-unsafe`). Relative imports of
|
|
68
|
+
generated files resolve against the component's source dir, bare imports against the app root, so
|
|
69
|
+
`--out-dir` may point anywhere; at run time `dist/` must still resolve `react` and `@brust/core`
|
|
70
|
+
(keep it inside the app, or next to a `node_modules` that has them).
|
|
71
|
+
|
|
72
|
+
## `brust dev [entry=routes.tsx] [--port N] [--workers N]`
|
|
73
|
+
|
|
74
|
+
The Bun-target dev server. A long-lived parent owns your port and proxies to the newest build; every
|
|
75
|
+
successful build is booted as a new child (`brust start` on a loopback port, `--workers 1` unless you pass
|
|
76
|
+
`--workers`), the proxy switches over, browsers are told to `reload`, and the old child is stopped. A failed
|
|
77
|
+
build **or a failed boot** keeps the old child serving and shows the error as an overlay above the stale page
|
|
78
|
+
(a page opened while builds fail shows it too); the next good build clears it and reloads.
|
|
79
|
+
|
|
80
|
+
- Source changes do a full reload (debounced ~150 ms; an edit made during a rebuild is not dropped).
|
|
81
|
+
- A change to `public/*.css` skips the build: the file is copied into the running dist and the `<link>` is
|
|
82
|
+
swapped (`?v=`), the page keeps its state. Other `public/` files reload.
|
|
83
|
+
- Every `text/html` response, including error pages, gets `/_brust/dev/client.js` (a WebSocket at
|
|
84
|
+
`/_brust/dev/ws`); responses are `cache-control: no-store`. `Host` and `Origin` reach the child unchanged,
|
|
85
|
+
so the actions cross-site guard behaves as in production.
|
|
86
|
+
- Dev builds use React in development mode (`NODE_ENV=development`); `brust build` / `brust start` are unchanged.
|
|
87
|
+
- The terminal shows request lines (`method path status ms cache`) and a status block of at most 6 lines at the
|
|
88
|
+
bottom (state, URL, generation, build time, last request, warnings, hotkeys `r` reload, `o` open, `w` warnings,
|
|
89
|
+
`c` clear, `q` quit). It never uses the alternate screen. Not a TTY, `CI` or `NO_COLOR`: plain lines only.
|
|
90
|
+
- **Trace for dev tools (L14).** The dev proxy generates a token per session and gives it to each child (`BRUST_DEV_TOKEN`,
|
|
91
|
+
napi `devToken`) and to every upstream request (`x-brust-dev-token`). Only then the core answers `x-brust-dev: base64(json)`
|
|
92
|
+
(route, pattern, chain, params, cache outcome, loader and render ms, jobs with kind/key/tags/hit, bun calls, the components the
|
|
93
|
+
page is made of). It is added after the L1 store, so it is never cached; a core without a token (production) never makes it.
|
|
94
|
+
The proxy strips the header, puts the trace in the page as `<script type=application/json id=__brust_trace>` and keeps the
|
|
95
|
+
last 50 (actions included) at `/_brust/dev/traces`. `brust build --dev` (used by `brust dev`) also writes `dev.json`
|
|
96
|
+
(component id → source file, tier, the react reason, diagnostics with rule/line/col/remediation; served at `/_brust/dev/meta`);
|
|
97
|
+
a production build writes none. In dev only, every chain level is wrapped in `<!--brust:<id>-->…<!--/brust-->` (never inside a
|
|
98
|
+
`<brust-island>`), and the runtime exposes its live instances on `globalThis.__brustDev.instances` (`__BRUST_DEV__` is a
|
|
99
|
+
build define that is `false`, and removed by the minifier, in every other build).
|
|
100
|
+
- **`<brust-devtools>` (L15).** The dev proxy serves `/_brust/dev/devtools.js` (bundled from `src/dev/devtools.ts` on first request) and
|
|
101
|
+
injects it into every HTML response, so `brust start` and production builds contain none of it. A corner indicator shows the build state
|
|
102
|
+
and the page's tier counts (`ready · 2s 1n 1r`); `Alt+Shift+B` toggles the panel (real buttons and tabs, arrow keys, `Esc` closes and
|
|
103
|
+
restores focus, contrast ≥ 4.5:1 in light and dark). Panels, each from the trace, `dev.json`, the DOM or `__brustDev` only: **Page**
|
|
104
|
+
(route, params, cache, timings), **Components** (tree with tier badges; a react component shows its reason and diagnostic location;
|
|
105
|
+
hover highlights it), **Jobs**, **Store** (seed from `data-brust-store`, live values of mounted native stores), **Islands**
|
|
106
|
+
(hydrated or not), **Build** (diagnostics with `file:line:col`, the current build error), **Requests** (the last 50). The terminal
|
|
107
|
+
status block shows the same tier counts for the last page.
|
|
108
|
+
- Not hot module replacement: component state does not survive an edit. `brust dev --target cloudflare` keeps
|
|
109
|
+
its wrangler loop (now on the same watch loop).
|
|
110
|
+
|
|
111
|
+
## `brust start [--port N] [--workers N] [--dist-dir dist] [--entry routes.tsx]`
|
|
112
|
+
|
|
113
|
+
Prints `[brust] listening on <addr>` then `[brust] ready (N workers)`. `--port 0` picks a free port.
|
|
114
|
+
SIGINT/SIGTERM drains gracefully (`BRUST_DRAIN_TIMEOUT_MS`); a second signal exits at once.
|
|
115
|
+
Config precedence: env (`BRUST_ADDR`, `BRUST_PORT`, `BRUST_WORKERS`, `BRUST_RENDER_SLOTS`,
|
|
116
|
+
`BRUST_DRAIN_TIMEOUT_MS`, `BRUST_BOOT_TIMEOUT_MS`, `BRUST_CALL_TIMEOUT_MS`, `BRUST_SLOT_BYTES`,
|
|
117
|
+
`BRUST_MAX_LOADER_RESPONSE_BYTES`, `BRUST_DIST_DIR`, `BRUST_APP_ENTRY`) > flags
|
|
118
|
+
(`--port`, `--workers`, `--dist-dir`, `--entry`) and `run({ config })` > `brust.toml` (`[server] address/port/max_loader_response_bytes`,
|
|
119
|
+
`[workers] count/slot_bytes`, `[cors]`) > defaults (`localhost:1337`, one worker per CPU, `min(cores, 16)` slots per worker (each slot is a `slotBytes` shared response buffer, 256 KiB by default, so 10 slots = 2.5 MiB per worker), 10 s drain, 30 s
|
|
120
|
+
worker boot timeout, 30 s call timeout, 8 MiB loader response limit).
|
|
121
|
+
`slotBytes` (`[workers] slot_bytes`, env `BRUST_SLOT_BYTES`, `run({ config: { slotBytes } })`) is 64 KiB..16 MiB.
|
|
122
|
+
A loader, jobs or planned reply that does not fit its slot is not an error: the worker hands it to the server
|
|
123
|
+
over the heap, up to `maxLoaderResponseBytes` (`[server] max_loader_response_bytes`, env
|
|
124
|
+
`BRUST_MAX_LOADER_RESPONSE_BYTES`, `run({ config: { maxLoaderResponseBytes } })`; 256 KiB..64 MiB, default 8 MiB).
|
|
125
|
+
A bigger reply answers `{"error":"response too large: <n> > <limit> (maxLoaderResponseBytes)"}` (a 500). This
|
|
126
|
+
limit applies to loader and jobs replies; `[actions]` has its own `max_response_bytes`.
|
|
127
|
+
`[cors]` can also be given as `run({ config: { cors: { origins, methods, headers, exposeHeaders, credentials,
|
|
128
|
+
maxAgeSeconds } } })` (camelCase, an unknown key is rejected). It replaces a `brust.toml` `[cors]` as a whole,
|
|
129
|
+
never merging with it; `cors: null` turns the toml table off.
|
|
130
|
+
A loader or job call that has not settled within `BRUST_CALL_TIMEOUT_MS` answers **504**; its worker
|
|
131
|
+
slot stays claimed until the call settles (the late result is discarded), so a stuck call costs one
|
|
132
|
+
slot, never a frozen worker. Waiting for a free slot is the separate 503 "all workers busy".
|
|
133
|
+
`GET /_brust/cache/stats` reports L1/job cache counters plus `loader_calls` / `job_calls` /
|
|
134
|
+
`timed_out_calls`.
|
|
135
|
+
|
|
136
|
+
## Worker wire (contract 7, `crates/brust-server/src/protocol.rs`)
|
|
137
|
+
|
|
138
|
+
Requests arrive as inline JSON; responses go into the worker's SharedArrayBuffer slot (never
|
|
139
|
+
past `floor(len / slots)` bytes). A `loader`/`jobs` reply that does not fit crosses by heap: the worker
|
|
140
|
+
puts the bytes with `putFetchReply` and writes the 5-byte marker `[0x00][u32 LE n]` into the slot (JSON
|
|
141
|
+
never starts with 0x00); the server reads the `n` bytes it was handed. A loader context too large for the
|
|
142
|
+
slot is planned from the bytes (`planJobsBuf`). Over `maxLoaderResponseBytes` the reply becomes
|
|
143
|
+
`{"error":…}`. A handler never rejects.
|
|
144
|
+
|
|
145
|
+
`loader` `{routeId, params, path, req}` → `{ok:true, data}` | `{verdict, …}` | `{error}`;
|
|
146
|
+
`jobs` `{jobs:[{id, componentId, kind, inputs, target?, row?}]}` → `{results:[{id, value}|{id, error}]}`
|
|
147
|
+
(an `ssr` job renders `jobs[target]` with the manifest job's `literals` merged over `inputs`).
|
|
148
|
+
|
|
149
|
+
**M2 limits:** the route fields above only; per-row child instances one level deep (a nested
|
|
150
|
+
instance is a build error); a react child's props must be props paths or literals; no worker respawn.
|
|
151
|
+
**Gates:** `bun run build:debug && bun run typecheck && bun test` (CI runs each server-starting
|
|
152
|
+
file alone; e2e: `bun test --timeout 120000 test/e2e.test.ts`).
|
package/bin/brust
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/* auto-generated by NAPI-RS */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Which binding artifact the generated loader actually loaded: `'native'` for
|
|
6
|
+
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
|
7
|
+
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
|
8
|
+
* can point the loader at a WASI artifact this package does not build itself.
|
|
9
|
+
*/
|
|
10
|
+
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Graceful drain of the current server: stop accepting, wait for in-flight
|
|
14
|
+
* connections up to `timeout_ms`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function beginDrain(timeoutMs: number): Promise<void>
|
|
17
|
+
|
|
18
|
+
/** Drop both tier-1 caches (a broadcast gap, R10); `stats.l2.resets`++. The L1 entries removed. */
|
|
19
|
+
export declare function cacheClear(): number
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Static output (gaps round R9.4): the request reads of one `cache.prefix` / `cache.bypass`
|
|
23
|
+
* expression (`header(x)`, `cookie(x)`, `query(x)`, `request(host)`), parsed by the server's own
|
|
24
|
+
* grammar. Errors when the expression does not parse. Needs no running server.
|
|
25
|
+
*/
|
|
26
|
+
export declare function cacheExprReads(src: string): Array<string>
|
|
27
|
+
|
|
28
|
+
/** `cache.invalidate({ key?, tags?, path?, method? })` (spec §5). */
|
|
29
|
+
export declare function cacheInvalidate(args: NapiInvalidateArgs): NapiInvalidateResult
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Bump the `stats.l2` counter `name` (the JS side's counts, R9). `false` = unknown name, no
|
|
33
|
+
* adapter, or no server. Never throws.
|
|
34
|
+
*/
|
|
35
|
+
export declare function cacheL2Count(name: string): boolean
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A broadcast invalidation another process published (worker 0's subscriber, cache-adapter R10):
|
|
39
|
+
* tier 1 + the floor at `seq`. Never re-published (L3). Same args/result as `cache_invalidate`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function cacheReplay(args: NapiInvalidateArgs, seq?: number | undefined | null): NapiInvalidateResult
|
|
42
|
+
|
|
43
|
+
/** `/_brust/cache/stats` JSON (snake_case keys, as the server serialises it). */
|
|
44
|
+
export declare function cacheStats(): string
|
|
45
|
+
|
|
46
|
+
export interface CompiledComponent {
|
|
47
|
+
id: string
|
|
48
|
+
source: string
|
|
49
|
+
/** `ComponentIR` as JSON (snake_case keys, as the compiler serialises it). */
|
|
50
|
+
ir: string
|
|
51
|
+
jinja: string
|
|
52
|
+
serverTs?: string
|
|
53
|
+
/** Build-time evaluation module (L2): one thunk per `ir.build_eval` slot. */
|
|
54
|
+
evalTs?: string
|
|
55
|
+
clientJs?: string
|
|
56
|
+
diagnostics: Array<NapiDiagnostic>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface CompiledTree {
|
|
60
|
+
components: Array<CompiledComponent>
|
|
61
|
+
error?: NapiDiagnostic
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Compiles `path` (relative to `root`) and every child it reaches through lowering. `css_modules` (L20): the class
|
|
66
|
+
* map of each `*.module.css`, keyed by its path relative to `root`; `styles.x` folds to the literal class.
|
|
67
|
+
* `packages` (F153c): the JSON resolution map of the package sources the build wrote (`pkg-src.ts`).
|
|
68
|
+
*/
|
|
69
|
+
export declare function compileTree(path: string, root: string, runtimeImport: string, serverOnly: Array<string>, slotValues?: string | undefined | null, cssModules?: Record<string, Record<string, string>> | undefined | null, iconSvgs?: string | undefined | null, routeRoots?: Array<string> | undefined | null, pathAliases?: string | undefined | null, packages?: string | undefined | null): CompiledTree
|
|
70
|
+
|
|
71
|
+
/** The bound address as `host:port` (port 0 resolves here). */
|
|
72
|
+
export declare function localAddr(): string
|
|
73
|
+
|
|
74
|
+
/** L16: what the page tells the browser client. `credentials`: `"same-origin"` (default), `"include"` or `"omit"`. */
|
|
75
|
+
export interface NapiActionsClient {
|
|
76
|
+
prefix?: string
|
|
77
|
+
baseUrl?: string
|
|
78
|
+
credentials?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** M3-F A3 consumer `{ prefix?, maxBodyBytes?, maxResponseBytes? }` (defaults `/_brust/action`, 1 MiB, 8 MiB). Present = actions on. */
|
|
82
|
+
export interface NapiActionsOptions {
|
|
83
|
+
prefix?: string
|
|
84
|
+
maxBodyBytes?: number
|
|
85
|
+
/** F82: cap on an action response body (default 8388608). */
|
|
86
|
+
maxResponseBytes?: number
|
|
87
|
+
/** `"reject"` (default) or `"allow"` (F87): cross-site mutating action requests. */
|
|
88
|
+
crossSite?: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Global CORS (0.1.x `NapiCorsOptions`, brust/src/lib.rs:79-98); napi camelCases the keys:
|
|
93
|
+
* `origins, methods, headers, exposeHeaders, credentials, maxAgeSeconds`. Validated by `start` (`cors: …`).
|
|
94
|
+
*/
|
|
95
|
+
export interface NapiCorsOptions {
|
|
96
|
+
origins: Array<string>
|
|
97
|
+
methods?: Array<string>
|
|
98
|
+
headers?: Array<string>
|
|
99
|
+
exposeHeaders?: Array<string>
|
|
100
|
+
credentials?: boolean
|
|
101
|
+
maxAgeSeconds?: number
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface NapiDiagnostic {
|
|
105
|
+
class: string
|
|
106
|
+
rule: string
|
|
107
|
+
message: string
|
|
108
|
+
line: number
|
|
109
|
+
col: number
|
|
110
|
+
remediation: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface NapiInvalidateArgs {
|
|
114
|
+
key?: string
|
|
115
|
+
tags?: Array<string>
|
|
116
|
+
path?: string
|
|
117
|
+
method?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface NapiInvalidateResult {
|
|
121
|
+
l1Removed: number
|
|
122
|
+
jobRemoved: number
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The cache adapter (`[cache]`, cache-adapter lane A); built by `brust start` from `manifest.cache`. */
|
|
126
|
+
export interface NapiL2Options {
|
|
127
|
+
adapter: string
|
|
128
|
+
/** The `store.get` budget; absent = 50. */
|
|
129
|
+
getTimeoutMs?: number
|
|
130
|
+
/** A bigger rendered body is not written; absent = 1 048 576. */
|
|
131
|
+
maxBodyBytes?: number
|
|
132
|
+
namespace: string
|
|
133
|
+
buildId: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* M3-P P6: plan the calling worker's in-flight loader call for `slot` from the `len` bytes it
|
|
138
|
+
* just wrote there. Returns the misses' `JobsRequest` JSON, `""` when every job hit, or `"!"`
|
|
139
|
+
* (declined: answer with the loader response already in the slot). Sync, on the worker's JS
|
|
140
|
+
* thread; never throws (a napi Error return is unreliable under Bun).
|
|
141
|
+
*/
|
|
142
|
+
export declare function planJobs(slot: number, len: number): string
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* L10: [`plan_jobs`] for a loader response too large for the slot: the worker hands the bytes
|
|
146
|
+
* over in `buf` instead of writing them to the slot. Same answers (`""`, the misses' JSON, or
|
|
147
|
+
* `"!"`); a declined plan leaves the response for the worker to hand over with `putFetchReply`.
|
|
148
|
+
*/
|
|
149
|
+
export declare function planJobsBuf(slot: number, buf: Uint8Array): string
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* F82: hand Rust the response body of the calling worker's in-flight `fetch` call for `slot`
|
|
153
|
+
* (too large for the SAB slot). `false` = declined (over `max_response_bytes`, not in a `fetch`
|
|
154
|
+
* call, already put, nothing bound). Sync, on the worker's JS thread; never throws.
|
|
155
|
+
*/
|
|
156
|
+
export declare function putFetchReply(slot: number, body: Uint8Array): boolean
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Register the calling worker with the current server. `buf` is a view of
|
|
160
|
+
* the worker's SharedArrayBuffer, which must outlive the process's use of
|
|
161
|
+
* this worker (`brust start` allocates it on the main thread and keeps it for
|
|
162
|
+
* the process lifetime: `dispatch.rs` BufPtr); it is split into
|
|
163
|
+
* `slots` disjoint sub-regions of `floor(buf.byteLength / slots)` bytes.
|
|
164
|
+
* `f(kind, requestJson, slot)` writes the response JSON at offset
|
|
165
|
+
* `slot * sub` and resolves with its byte length (> 0, ≤ sub). Returns the
|
|
166
|
+
* worker id.
|
|
167
|
+
*/
|
|
168
|
+
export declare function registerWorker(buf: Uint8Array, slots: number, f: (arg0: string, arg1: string, arg2: number) => Promise<number>): number
|
|
169
|
+
|
|
170
|
+
export interface StartOptions {
|
|
171
|
+
host: string
|
|
172
|
+
port: number
|
|
173
|
+
distDir: string
|
|
174
|
+
/** Workers to wait for before accepting; `0` = serve at once. */
|
|
175
|
+
workers: number
|
|
176
|
+
claimTimeoutMs?: number
|
|
177
|
+
/**
|
|
178
|
+
* A claimed worker call that has not settled after this long answers
|
|
179
|
+
* 504 (the slot stays claimed until JS settles). Absent = 30 000.
|
|
180
|
+
*/
|
|
181
|
+
callTimeoutMs?: number
|
|
182
|
+
/**
|
|
183
|
+
* L10: the biggest loader/jobs reply that may cross the heap when it does not fit the slot
|
|
184
|
+
* (256 KiB..=64 MiB; boot error otherwise). Absent = 8 MiB.
|
|
185
|
+
*/
|
|
186
|
+
maxLoaderResponseBytes?: number
|
|
187
|
+
/**
|
|
188
|
+
* tokio threads of the HTTP runtime (every page renders there); absent =
|
|
189
|
+
* one per core (`Tuning::default`).
|
|
190
|
+
*/
|
|
191
|
+
workerThreads?: number
|
|
192
|
+
l1Capacity?: number
|
|
193
|
+
jobCacheCapacity?: number
|
|
194
|
+
/** `X-Powered-By` value; absent = header not stamped. */
|
|
195
|
+
generator?: string
|
|
196
|
+
/** Global CORS policy; absent = CORS off. */
|
|
197
|
+
cors?: NapiCorsOptions
|
|
198
|
+
/** The actions subtree (M3-F); absent = no actions (byte-identical server). */
|
|
199
|
+
actions?: NapiActionsOptions
|
|
200
|
+
/** L16: base URL / credentials / prefix injected for the browser `client()`; absent = from `actions` alone. */
|
|
201
|
+
actionsClient?: NapiActionsClient
|
|
202
|
+
/** The cache adapter; absent = none (byte-identical server). */
|
|
203
|
+
l2?: NapiL2Options
|
|
204
|
+
/** `brust dev` only (L14): a request carrying this token in `x-brust-dev-token` is traced. Absent in production. */
|
|
205
|
+
devToken?: string
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Boot the server (manifest, routes, templates, bind). Throws on any boot
|
|
210
|
+
* error (`manifest: …`, `routes: …`, `render: …`, `cors: …`, `actions: …`,
|
|
211
|
+
* `bind failed on …`).
|
|
212
|
+
*/
|
|
213
|
+
export declare function startServer(opts: StartOptions): void
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* F82: the heap request body of the calling worker's in-flight `fetch` call for `slot`, once.
|
|
217
|
+
* `None` when there is none, nothing is bound, or the binding is ambiguous. Sync, on the
|
|
218
|
+
* worker's JS thread; never throws.
|
|
219
|
+
*/
|
|
220
|
+
export declare function takeFetchBody(slot: number): Uint8Array | null
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Resolves once `workers` workers registered with the current server and the server has printed its
|
|
224
|
+
* `[brust] listening on` line (so a caller's `ready` always follows it, F128); rejects after `timeout_ms` (the caller decides the exit policy).
|
|
225
|
+
*/
|
|
226
|
+
export declare function untilReady(timeoutMs: number): Promise<void>
|