brustjs 0.1.34-alpha → 0.1.36-alpha
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 +18 -13
- package/example/pokedex/components/AddToTeamButton.tsx +1 -1
- package/example/pokedex/components/Breadcrumb.tsx +1 -1
- package/example/pokedex/components/DexFilter.tsx +1 -1
- package/example/pokedex/components/HeroSearch.tsx +0 -1
- package/example/pokedex/components/NavLink.tsx +0 -1
- package/example/pokedex/components/ThemeToggle.tsx +0 -1
- package/example/pokedex/lib/pokeapi.ts +45 -15
- package/package.json +7 -7
- package/runtime/cli/native-routes-emit.ts +17 -1
- package/runtime/index.d.ts +46 -5
- package/runtime/index.js +53 -52
- package/runtime/index.ts +51 -11
- package/runtime/islands/island.tsx +26 -11
- package/runtime/native/build.ts +15 -9
- package/runtime/render/stream.ts +36 -18
- package/runtime/routes.ts +167 -82
package/README.md
CHANGED
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
React on the server, Rust everywhere else. One Bun host process; the HTTP
|
|
17
|
-
|
|
18
|
-
Renders cross into Bun Worker threads via
|
|
19
|
-
|
|
16
|
+
React on the server, Rust everywhere else. One Bun host process; the HTTP server
|
|
17
|
+
(hyper 1.x, HTTP/1.1 + HTTP/2) and worker pool are pure Rust, loaded as a `.node`
|
|
18
|
+
native module (napi-rs). Renders cross into Bun Worker threads via
|
|
19
|
+
`ThreadsafeFunction` (request as inline JSON) and return over a per-worker
|
|
20
|
+
`SharedArrayBuffer`. One worker can hold several renders in-flight
|
|
21
|
+
(`renderSlots`), overlapping I/O-bound (Suspense) renders. Multi-thread tokio
|
|
22
|
+
runtime — runs the same everywhere (no io_uring / seccomp caveat).
|
|
20
23
|
|
|
21
24
|
> Published on npm as [`brustjs`](https://www.npmjs.com/package/brustjs) (the
|
|
22
25
|
> `brust` name is taken). Alpha — see **Status**.
|
|
@@ -133,7 +136,8 @@ bun test tests/integration.test.ts # integration (real server)
|
|
|
133
136
|
```
|
|
134
137
|
|
|
135
138
|
```
|
|
136
|
-
crates/brust/
|
|
139
|
+
crates/brust-core/ Rust core (pure, zero napi): hyper server, worker pool, routing, cache
|
|
140
|
+
crates/brust/ Thin napi cdylib over brust-core (the .node)
|
|
137
141
|
crates/jsx-rust-compiler/ JSX → jinja compiler for native: true routes
|
|
138
142
|
runtime/ Bun-side: routing, render, actions, store, native directives, CLI
|
|
139
143
|
example/ pokedex native-first demo
|
|
@@ -142,14 +146,15 @@ bench/ · docs/ · architecture.md
|
|
|
142
146
|
|
|
143
147
|
## Status
|
|
144
148
|
|
|
145
|
-
Alpha, solo-developed. Linux is tier-1 (
|
|
146
|
-
binaries)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
Alpha, solo-developed. Linux is tier-1 (glibc + musl, 6 prebuilt platform
|
|
150
|
+
binaries); the multi-thread tokio server runs under default container seccomp —
|
|
151
|
+
no `io_uring` exception needed. The server speaks **HTTP/1.1 + HTTP/2** with
|
|
152
|
+
optional in-process **TLS**, and a worker can hold several renders in-flight
|
|
153
|
+
(**`renderSlots`**) to overlap Suspense / loader-bound requests. Known partials:
|
|
154
|
+
`brustjs dev` reload is a full worker-respawn (not state-preserving HMR) — TS,
|
|
155
|
+
islands, and `.module.css` all reload that way. Tailwind is opt-in — the scaffold
|
|
156
|
+
adds it as a project dependency; `@import "tailwindcss"` resolves from your own
|
|
157
|
+
`node_modules`. Roadmap and limitations in [`architecture.md`](./architecture.md).
|
|
153
158
|
|
|
154
159
|
MIT.
|
|
155
160
|
|
|
@@ -78,7 +78,7 @@ export const behavior = ({ props }: { props: AddTeamProps }) => {
|
|
|
78
78
|
// emitted by the compiler as x-props="{{ (data) | json_attr }}" (XSS-safe).
|
|
79
79
|
export default function AddToTeamButton({ data }: { data: AddTeamProps }) {
|
|
80
80
|
return (
|
|
81
|
-
<div x-
|
|
81
|
+
<div x-props={data} className="relative">
|
|
82
82
|
<Plus
|
|
83
83
|
size={16}
|
|
84
84
|
className="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-white"
|
|
@@ -42,7 +42,7 @@ export const behavior = () => {
|
|
|
42
42
|
// bind and on every SPA navigation.
|
|
43
43
|
export default function Breadcrumb({ crumb }: { crumb: string }) {
|
|
44
44
|
return (
|
|
45
|
-
<b x-
|
|
45
|
+
<b x-text="label" className="text-slate-600 dark:text-slate-300">
|
|
46
46
|
{crumb}
|
|
47
47
|
</b>
|
|
48
48
|
)
|
|
@@ -52,7 +52,7 @@ export const behavior = ({ props }: { el: HTMLElement; props: unknown }) => {
|
|
|
52
52
|
// behavior's props — one prop, no separate pre-stringified `dexProps`.
|
|
53
53
|
export default function DexFilter({ items }: { items: Card[] }) {
|
|
54
54
|
return (
|
|
55
|
-
<section x-
|
|
55
|
+
<section x-props={items}>
|
|
56
56
|
<div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
57
57
|
<div className="relative w-full sm:max-w-xs">
|
|
58
58
|
<Search
|
|
@@ -29,7 +29,6 @@ export const behavior = () => {
|
|
|
29
29
|
export default function HeroSearch() {
|
|
30
30
|
return (
|
|
31
31
|
<form
|
|
32
|
-
x-data="heroSearch"
|
|
33
32
|
x-on-submit="go"
|
|
34
33
|
className="mx-auto mt-8 flex w-full max-w-md items-center gap-2 rounded-2xl bg-white/95 p-2 shadow-lg ring-1 ring-black/5 dark:bg-slate-900/90 dark:ring-white/10"
|
|
35
34
|
>
|
|
@@ -31,7 +31,6 @@ export const behavior = ({ el }: { el: HTMLElement }) => {
|
|
|
31
31
|
export default function NavLink({ href, label }: { href: string; label: string }) {
|
|
32
32
|
return (
|
|
33
33
|
<a
|
|
34
|
-
x-data="navLink"
|
|
35
34
|
x-bind-class="cls"
|
|
36
35
|
x-bind-aria-current="current"
|
|
37
36
|
className="inline-flex items-center rounded-lg px-3 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white"
|
|
@@ -45,7 +45,6 @@ export default function ThemeToggle() {
|
|
|
45
45
|
return (
|
|
46
46
|
<button
|
|
47
47
|
type="button"
|
|
48
|
-
x-data="themeToggle"
|
|
49
48
|
x-on-click="toggle"
|
|
50
49
|
aria-label="Toggle theme"
|
|
51
50
|
className="inline-flex items-center gap-1.5 rounded-lg border border-slate-200 px-3 py-1.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-100 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800"
|
|
@@ -8,6 +8,40 @@ import { cachedFetch } from 'brustjs'
|
|
|
8
8
|
|
|
9
9
|
export const API = 'https://pokeapi.co/api/v2'
|
|
10
10
|
|
|
11
|
+
// Process-lifetime memo of PokeAPI JSON. `cachedFetch` dedupes only WITHIN one
|
|
12
|
+
// request (it's AsyncLocalStorage-scoped), so on its own EVERY request re-hits
|
|
13
|
+
// pokeapi.co over the network (~700 ms) — at 120 conns `/pokedex` collapses to
|
|
14
|
+
// ~170 rps, bound entirely by that round-trip. PokeAPI is IMMUTABLE reference
|
|
15
|
+
// data, so the parsed JSON is cached for the process lifetime here: the first
|
|
16
|
+
// request warms it, every later request renders from memory. Only successful
|
|
17
|
+
// (2xx) responses are cached; a non-ok response or a network error resolves
|
|
18
|
+
// `null` and is NOT cached, so a transient 429/500 or a typo'd name retries.
|
|
19
|
+
// (Per-request personalization — theme cookie, team — lives in `chrome()`, not
|
|
20
|
+
// here, so it is never cached.)
|
|
21
|
+
const jsonCache = new Map<string, Promise<unknown>>()
|
|
22
|
+
|
|
23
|
+
async function getJson<T>(url: string): Promise<T | null> {
|
|
24
|
+
const hit = jsonCache.get(url) as Promise<T | null> | undefined
|
|
25
|
+
if (hit) return hit
|
|
26
|
+
const p = (async (): Promise<T | null> => {
|
|
27
|
+
const res = await cachedFetch(url)
|
|
28
|
+
if (!res.ok) return null
|
|
29
|
+
return (await res.json()) as T
|
|
30
|
+
})()
|
|
31
|
+
jsonCache.set(url, p)
|
|
32
|
+
// Evict anything that didn't yield cacheable data (null / rejected) so the
|
|
33
|
+
// next request can retry rather than memoizing a transient failure forever.
|
|
34
|
+
p.then(
|
|
35
|
+
(v) => {
|
|
36
|
+
if (v === null && jsonCache.get(url) === p) jsonCache.delete(url)
|
|
37
|
+
},
|
|
38
|
+
() => {
|
|
39
|
+
if (jsonCache.get(url) === p) jsonCache.delete(url)
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
return p
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
export const idFromUrl = (url: string): number => Number((url.match(/\/pokemon\/(\d+)\//) || [])[1])
|
|
12
46
|
|
|
13
47
|
/** Official-artwork PNG from the PokeAPI sprites CDN — derived from id so the
|
|
@@ -81,12 +115,11 @@ export interface RawEvolutionStage {
|
|
|
81
115
|
}
|
|
82
116
|
|
|
83
117
|
export async function fetchList(offset: number, limit: number) {
|
|
84
|
-
const
|
|
85
|
-
if (!res.ok) throw new Error(`PokeAPI list ${res.status}`)
|
|
86
|
-
const page = (await res.json()) as {
|
|
118
|
+
const page = await getJson<{
|
|
87
119
|
count: number
|
|
88
120
|
results: { name: string; url: string }[]
|
|
89
|
-
}
|
|
121
|
+
}>(`${API}/pokemon?limit=${limit}&offset=${offset}`)
|
|
122
|
+
if (!page) throw new Error('PokeAPI list fetch failed')
|
|
90
123
|
return {
|
|
91
124
|
results: page.results.map((r) => ({ id: idFromUrl(r.url), name: r.name })),
|
|
92
125
|
total: page.count,
|
|
@@ -94,9 +127,8 @@ export async function fetchList(offset: number, limit: number) {
|
|
|
94
127
|
}
|
|
95
128
|
|
|
96
129
|
export async function fetchPokemon(name: string): Promise<RawPokemon | null> {
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
99
|
-
const p = (await res.json()) as any
|
|
130
|
+
const p = await getJson<any>(`${API}/pokemon/${name}`)
|
|
131
|
+
if (!p) return null
|
|
100
132
|
return {
|
|
101
133
|
id: p.id,
|
|
102
134
|
name: p.name,
|
|
@@ -110,8 +142,8 @@ export async function fetchPokemon(name: string): Promise<RawPokemon | null> {
|
|
|
110
142
|
}
|
|
111
143
|
|
|
112
144
|
export async function fetchSpecies(id: number): Promise<RawSpecies> {
|
|
113
|
-
const
|
|
114
|
-
|
|
145
|
+
const s = await getJson<any>(`${API}/pokemon-species/${id}`)
|
|
146
|
+
if (!s) return { flavorText: '', genus: '', evolutionUrl: '' }
|
|
115
147
|
const flavor = s.flavor_text_entries?.find((e: any) => e.language.name === 'en')?.flavor_text as
|
|
116
148
|
| string
|
|
117
149
|
| undefined
|
|
@@ -126,9 +158,8 @@ export async function fetchSpecies(id: number): Promise<RawSpecies> {
|
|
|
126
158
|
* flattened to the first branch — noted as an open question in the design. */
|
|
127
159
|
export async function fetchEvolution(url: string): Promise<RawEvolutionStage[]> {
|
|
128
160
|
if (!url) return []
|
|
129
|
-
const
|
|
130
|
-
if (!
|
|
131
|
-
const data = (await res.json()) as any
|
|
161
|
+
const data = await getJson<any>(url)
|
|
162
|
+
if (!data) return []
|
|
132
163
|
const stages: RawEvolutionStage[] = []
|
|
133
164
|
let node = data.chain
|
|
134
165
|
while (node) {
|
|
@@ -164,9 +195,8 @@ export const ALL_TYPES = [
|
|
|
164
195
|
|
|
165
196
|
/** Fetch one type's damage relations → a map of defendingType → multiplier. */
|
|
166
197
|
export async function fetchTypeRelations(type: string): Promise<Record<string, number>> {
|
|
167
|
-
const
|
|
168
|
-
if (!
|
|
169
|
-
const d = (await res.json()) as any
|
|
198
|
+
const d = await getJson<any>(`${API}/type/${type}`)
|
|
199
|
+
if (!d) return {}
|
|
170
200
|
const rel = d.damage_relations
|
|
171
201
|
const out: Record<string, number> = {}
|
|
172
202
|
for (const t of rel.double_damage_to || []) out[t.name] = 2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brustjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36-alpha",
|
|
4
4
|
"description": "Bun + Rust SSR framework — React on the server, Rust everywhere else (napi cdylib + per-worker SharedArrayBuffer).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"typescript": "^6.0.3"
|
|
41
41
|
},
|
|
42
42
|
"optionalDependencies": {
|
|
43
|
-
"brustjs-darwin-x64": "0.1.
|
|
44
|
-
"brustjs-darwin-arm64": "0.1.
|
|
45
|
-
"brustjs-linux-x64-gnu": "0.1.
|
|
46
|
-
"brustjs-linux-arm64-gnu": "0.1.
|
|
47
|
-
"brustjs-linux-x64-musl": "0.1.
|
|
48
|
-
"brustjs-linux-arm64-musl": "0.1.
|
|
43
|
+
"brustjs-darwin-x64": "0.1.36-alpha",
|
|
44
|
+
"brustjs-darwin-arm64": "0.1.36-alpha",
|
|
45
|
+
"brustjs-linux-x64-gnu": "0.1.36-alpha",
|
|
46
|
+
"brustjs-linux-arm64-gnu": "0.1.36-alpha",
|
|
47
|
+
"brustjs-linux-x64-musl": "0.1.36-alpha",
|
|
48
|
+
"brustjs-linux-arm64-musl": "0.1.36-alpha"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "^19.2.6",
|
|
@@ -483,6 +483,7 @@ export async function emitNativeTemplates(opts: NativeRouteEmitOpts): Promise<vo
|
|
|
483
483
|
path: string,
|
|
484
484
|
componentSources?: Record<string, string>,
|
|
485
485
|
lucideIcons?: Record<string, string>,
|
|
486
|
+
directiveNames?: Record<string, string>,
|
|
486
487
|
) => { template: string; islandsJson: string; warnings?: string[] })
|
|
487
488
|
| null = null
|
|
488
489
|
if (nativeRoutes.length > 0) {
|
|
@@ -573,9 +574,24 @@ export async function emitNativeTemplates(opts: NativeRouteEmitOpts): Promise<vo
|
|
|
573
574
|
const lucideIcons: Record<string, string> = {}
|
|
574
575
|
for (const p of lucidePaths) Object.assign(lucideIcons, await extractLucideIcons(p))
|
|
575
576
|
|
|
577
|
+
// Build directive name map: for each ident in `sources` whose source text
|
|
578
|
+
// contains `export const behavior`, resolve its absolute path from
|
|
579
|
+
// `mergedImports` and derive the canonical directive name. Uses a dynamic
|
|
580
|
+
// import to avoid a circular dependency (native/build.ts → scanImports here).
|
|
581
|
+
const { directiveName } = await import('../native/build.ts')
|
|
582
|
+
const BEHAVIOR_RE = /export\s+const\s+behavior\b/
|
|
583
|
+
const directiveNames: Record<string, string> = {}
|
|
584
|
+
for (const [ident, src] of Object.entries(sources)) {
|
|
585
|
+
if (!BEHAVIOR_RE.test(src)) continue
|
|
586
|
+
const ref = mergedImports.get(ident)
|
|
587
|
+
if (ref && !ref.bare && typeof ref.spec === 'string') {
|
|
588
|
+
directiveNames[ident] = directiveName(ref.spec, process.cwd())
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
576
592
|
let compiled: { template: string; islandsJson: string; warnings?: string[] }
|
|
577
593
|
try {
|
|
578
|
-
compiled = compileJsx!(routeSource, routeSourcePath, sources, lucideIcons)
|
|
594
|
+
compiled = compileJsx!(routeSource, routeSourcePath, sources, lucideIcons, directiveNames)
|
|
579
595
|
} catch (e) {
|
|
580
596
|
throw new Error(
|
|
581
597
|
`native route "${name}" failed to compile (${routeSourcePath}):\n${String(e)}`,
|
package/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
/* auto-generated by NAPI-RS */
|
|
2
2
|
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* Graceful drain: stop accepting new connections, tell every in-flight one to
|
|
5
|
+
* finish its current request then close, and resolve once they've all drained
|
|
6
|
+
* (or `timeout_ms` elapses). The TS SIGINT handler awaits this before
|
|
7
|
+
* `process.exit` so an in-flight render/stream isn't cut off mid-response (the
|
|
8
|
+
* teardown that produced spurious `split_meta` errors under load). Idempotent-ish:
|
|
9
|
+
* the accept loop only drains once; a second call just re-resolves.
|
|
10
|
+
*/
|
|
11
|
+
export declare function beginDrain(timeoutMs: number): Promise<NapiResult<undefined>>
|
|
12
|
+
|
|
3
13
|
export declare function beginServe(opts: ServeOptions): NapiResult<undefined>
|
|
4
14
|
|
|
5
15
|
export interface CachedIslandJs {
|
|
@@ -7,7 +17,7 @@ export interface CachedIslandJs {
|
|
|
7
17
|
props: string
|
|
8
18
|
}
|
|
9
19
|
|
|
10
|
-
export declare function compileJsx(source: string, path: string, componentSources?: Record<string, string> | undefined | null, lucideIcons?: Record<string, string> | undefined | null): NapiCompiledJsx
|
|
20
|
+
export declare function compileJsx(source: string, path: string, componentSources?: Record<string, string> | undefined | null, lucideIcons?: Record<string, string> | undefined | null, directiveNames?: Record<string, string> | undefined | null): NapiCompiledJsx
|
|
11
21
|
|
|
12
22
|
export declare function configureCache(maxEntries: number): NapiResult<undefined>
|
|
13
23
|
|
|
@@ -120,7 +130,7 @@ export declare function napiRegisterWsPaths(paths: Array<string>): NapiResult<un
|
|
|
120
130
|
* - Ack receiver dropped (handle_conn torn down mid-stream) → NAPI Err
|
|
121
131
|
* (NOT hang — worker's sink propagates via cb(err) to renderer Promise).
|
|
122
132
|
*/
|
|
123
|
-
export declare function napiRenderChunk(workerId: number, len: number): Promise<NapiResult<undefined>>
|
|
133
|
+
export declare function napiRenderChunk(workerId: number, slot: number, len: number): Promise<NapiResult<undefined>>
|
|
124
134
|
|
|
125
135
|
/**
|
|
126
136
|
* Buffering-path finalizer: equivalent to `napi_render_chunk(_, len)` followed
|
|
@@ -134,7 +144,7 @@ export declare function napiRenderChunk(workerId: number, len: number): Promise<
|
|
|
134
144
|
*
|
|
135
145
|
* Same error semantics as `napi_render_chunk`.
|
|
136
146
|
*/
|
|
137
|
-
export declare function napiRenderChunkFinal(workerId: number, len: number): Promise<NapiResult<undefined>>
|
|
147
|
+
export declare function napiRenderChunkFinal(workerId: number, slot: number, len: number): Promise<NapiResult<undefined>>
|
|
138
148
|
|
|
139
149
|
/**
|
|
140
150
|
* Sub-project J — render via minijinja using SAB-side-channeled loader data.
|
|
@@ -165,7 +175,7 @@ export declare function napiRenderChunkFinal(workerId: number, len: number): Pro
|
|
|
165
175
|
* - `jinja::render` failure → writes a framed 500 into the SAB and returns its
|
|
166
176
|
* length (the protocol error is converted to an HTTP 500 on the wire).
|
|
167
177
|
*/
|
|
168
|
-
export declare function napiRenderJinja(workerId: number, dataLen: number, templateName: string, status?: number | undefined | null): NapiResult<number>
|
|
178
|
+
export declare function napiRenderJinja(workerId: number, slot: number, dataLen: number, templateName: string, status?: number | undefined | null): NapiResult<number>
|
|
169
179
|
|
|
170
180
|
/**
|
|
171
181
|
* Drop the connection's sender, which signals the per-conn task to exit
|
|
@@ -231,7 +241,7 @@ export declare function napiWsSignalOpen(connId: bigint, status: number, body: B
|
|
|
231
241
|
*/
|
|
232
242
|
export declare function registerActions(endpoints: Array<EndpointReg>): NapiResult<number>
|
|
233
243
|
|
|
234
|
-
export declare function registerRenderer(buf: Uint8Array, f: (
|
|
244
|
+
export declare function registerRenderer(buf: Uint8Array, slots: number, f: (arg0: string, arg1: number) => Promise<number>): NapiResult<number>
|
|
235
245
|
|
|
236
246
|
export declare function registerRoutes(configs: Array<string>): NapiResult<number>
|
|
237
247
|
|
|
@@ -259,6 +269,21 @@ export interface ServeOptions {
|
|
|
259
269
|
tuning?: ServeTuning
|
|
260
270
|
/** Optional action prefix override. Defaults to `/_brust/action`. */
|
|
261
271
|
actionPrefix?: string
|
|
272
|
+
/**
|
|
273
|
+
* Optional in-process TLS: PEM certificate (chain) path. When BOTH this and
|
|
274
|
+
* `tls_key_path` are present, the server terminates TLS itself (ALPN
|
|
275
|
+
* h2+http/1.1). Omit either to serve plaintext (unchanged default).
|
|
276
|
+
*/
|
|
277
|
+
tlsCertPath?: string
|
|
278
|
+
/** Optional in-process TLS: PEM private-key path. See `tls_cert_path`. */
|
|
279
|
+
tlsKeyPath?: string
|
|
280
|
+
/**
|
|
281
|
+
* Optional minimum TLS version: `"1.2"` (default, = rustls TLS 1.2 + 1.3
|
|
282
|
+
* safe defaults) or `"1.3"` (TLS 1.3 only, for hardened/compliance
|
|
283
|
+
* deployments). Case-insensitive, trimmed. Only meaningful when TLS is
|
|
284
|
+
* configured (cert + key present). An unrecognized value is rejected.
|
|
285
|
+
*/
|
|
286
|
+
tlsMinVersion?: string
|
|
262
287
|
}
|
|
263
288
|
|
|
264
289
|
/**
|
|
@@ -287,6 +312,22 @@ export interface ServeTuning {
|
|
|
287
312
|
* Default 10000.
|
|
288
313
|
*/
|
|
289
314
|
claimTimeoutMs?: number
|
|
315
|
+
/**
|
|
316
|
+
* tokio I/O runtime worker-thread count for the hyper server. Runs inside
|
|
317
|
+
* Bun (which has its own threads + render workers), so this is NOT
|
|
318
|
+
* one-per-core. Default `min(available_parallelism, 4)` (fallback 2).
|
|
319
|
+
*/
|
|
320
|
+
workerThreads?: number
|
|
321
|
+
/**
|
|
322
|
+
* Number of render slots per Bun worker (concurrent in-flight renders per
|
|
323
|
+
* isolate). Default 1 (single in-flight render per worker — byte-identical
|
|
324
|
+
* to the pre-multi-slot behaviour). The COUNT reaches `register_renderer`
|
|
325
|
+
* via the `BRUST_RENDER_SLOTS` worker env var set in `runtime/index.ts`; it
|
|
326
|
+
* is NOT consumed by the Rust `Tuning` struct here (the pool learns it from
|
|
327
|
+
* `dispatch.slot_count()`). Carried on `ServeTuning` only so the JS layer
|
|
328
|
+
* has one tunable surface.
|
|
329
|
+
*/
|
|
330
|
+
renderSlots?: number
|
|
290
331
|
}
|
|
291
332
|
|
|
292
333
|
export declare function untilReady(timeoutMs: number): Promise<NapiResult<undefined>>
|