brustjs 0.1.50-alpha → 0.1.52-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/package.json +39 -15
- package/runtime/cache-sync.ts +291 -0
- package/runtime/cache.ts +4 -0
- package/runtime/cli/dev.ts +7 -0
- package/runtime/cli/native-routes-emit.ts +147 -1
- package/runtime/config.ts +42 -0
- package/runtime/index.d.ts +63 -0
- package/runtime/index.js +57 -52
- package/runtime/index.ts +108 -9
- package/runtime/native/runtime.ts +220 -7
- package/runtime/render/fragment.ts +87 -0
- package/runtime/routes.ts +225 -48
- package/runtime/templates.ts +47 -0
- package/runtime/treaty.ts +24 -1
- package/types/action-error.d.ts +18 -0
- package/types/cache-sync.d.ts +42 -0
- package/types/cache.d.ts +20 -0
- package/types/cli/help.d.ts +28 -0
- package/types/cli/jinja-staleness.d.ts +14 -0
- package/types/cli/native-routes-emit.d.ts +217 -0
- package/types/cli/new.d.ts +30 -0
- package/types/cli/templates.d.ts +39 -0
- package/types/client/index.d.ts +14 -0
- package/types/config.d.ts +42 -0
- package/types/cookies.d.ts +25 -0
- package/types/create.d.ts +1 -0
- package/types/css/build.d.ts +11 -0
- package/types/css/component-build.d.ts +17 -0
- package/types/css/component-loader.d.ts +8 -0
- package/types/css/manifest.d.ts +21 -0
- package/types/css/process-modules.d.ts +31 -0
- package/types/css/route-deps.d.ts +20 -0
- package/types/css/scan-imports.d.ts +13 -0
- package/types/css.d.ts +16 -0
- package/types/define-actions.d.ts +133 -0
- package/types/dev/client.d.ts +8 -0
- package/types/dev/coordinator.d.ts +33 -0
- package/types/dev/inject.d.ts +6 -0
- package/types/dev/jinja-reload.d.ts +7 -0
- package/types/dev/tui.d.ts +35 -0
- package/types/dev/watcher.d.ts +34 -0
- package/types/dev/worker-registry.d.ts +17 -0
- package/types/dev/ws-channel.d.ts +39 -0
- package/types/generator.d.ts +23 -0
- package/types/index.d.ts +222 -0
- package/types/islands/brust-page.d.ts +74 -0
- package/types/islands/build.d.ts +49 -0
- package/types/islands/chunk-id.d.ts +10 -0
- package/types/islands/importmap.d.ts +2 -0
- package/types/islands/island.d.ts +65 -0
- package/types/islands/isr-jsx.d.ts +31 -0
- package/types/islands/native-render.d.ts +89 -0
- package/types/loader-cache.d.ts +18 -0
- package/types/mcp/extractor.d.ts +14 -0
- package/types/mcp/manifest.d.ts +23 -0
- package/types/mcp/schema.d.ts +19 -0
- package/types/mcp/server.d.ts +15 -0
- package/types/md/emit.d.ts +72 -0
- package/types/md/render.d.ts +80 -0
- package/types/md/routes.d.ts +119 -0
- package/types/md/scan.d.ts +34 -0
- package/types/md/slug.d.ts +1 -0
- package/types/native/build.d.ts +30 -0
- package/types/native/index.d.ts +2 -0
- package/types/native/runtime.d.ts +52 -0
- package/types/navigation/active-nav.d.ts +2 -0
- package/types/navigation/index.d.ts +5 -0
- package/types/navigation/navigate.d.ts +14 -0
- package/types/navigation/react.d.ts +15 -0
- package/types/navigation/store.d.ts +44 -0
- package/types/render/fragment.d.ts +20 -0
- package/types/render/inject-action-prefix.d.ts +9 -0
- package/types/render/inject-css-link.d.ts +8 -0
- package/types/render/inject-dev-client.d.ts +6 -0
- package/types/render/inject-generator.d.ts +7 -0
- package/types/render/inject-store.d.ts +9 -0
- package/types/render/stream.d.ts +45 -0
- package/types/request-context.d.ts +16 -0
- package/types/routes.d.ts +506 -0
- package/types/sse/handler.d.ts +22 -0
- package/types/standard-schema.d.ts +31 -0
- package/types/store/define-store.d.ts +31 -0
- package/types/store/index.d.ts +5 -0
- package/types/store/react.d.ts +2 -0
- package/types/store/serialize.d.ts +5 -0
- package/types/store/server-context.d.ts +4 -0
- package/types/store/signal.d.ts +18 -0
- package/types/templates.d.ts +18 -0
- package/types/treaty.d.ts +70 -0
- package/types/ws/handler.d.ts +26 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Browser dev client. Inlined into the SSR first chunk via a <script
|
|
2
|
+
* type="module">…</script> wrapper. Connects WS at /_brust/dev, handles
|
|
3
|
+
* reload / css-update / error / ok messages, manages a red overlay.
|
|
4
|
+
*
|
|
5
|
+
* Keep this string short — it ships in every dev-mode SSR response. */
|
|
6
|
+
export declare const DEV_CLIENT_JS: string;
|
|
7
|
+
/** Build the full <script> tag that gets spliced before </head>. */
|
|
8
|
+
export declare function buildDevClientTag(): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DevMessage } from './ws-channel.ts';
|
|
2
|
+
import type { ChangeKind } from './watcher.ts';
|
|
3
|
+
export interface CoordinatorDeps {
|
|
4
|
+
workers: {
|
|
5
|
+
terminateAll(): Promise<void>;
|
|
6
|
+
spawnAll(): Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
buildCss: () => Promise<void>;
|
|
9
|
+
buildIslands: () => Promise<void>;
|
|
10
|
+
/** Recompile native-route `.jinja` templates from source and reload them into
|
|
11
|
+
* the minijinja env, so `native: true` routes pick up .tsx edits on reload. */
|
|
12
|
+
reEmitJinja: () => Promise<void>;
|
|
13
|
+
/** Clear the Rust-side island ISR cache. Called on every render-affecting
|
|
14
|
+
* reload (`ts`/`html`/`islands`) so a `.tsx` edit is reflected immediately —
|
|
15
|
+
* a frozen island render from before the edit must never survive a hot reload.
|
|
16
|
+
* Optional: a build without the island-cache addon export omits it. */
|
|
17
|
+
clearIslandCache?: () => void;
|
|
18
|
+
buildComponentCss?: () => Promise<void>;
|
|
19
|
+
snapshotComponentCss?: () => Promise<import('../css/manifest.ts').ComponentCssManifest | null>;
|
|
20
|
+
broadcast: (msg: DevMessage) => Promise<void> | void;
|
|
21
|
+
tui: {
|
|
22
|
+
appendEvent(line: string): void;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare class Coordinator {
|
|
26
|
+
private deps;
|
|
27
|
+
private state;
|
|
28
|
+
constructor(deps: CoordinatorDeps);
|
|
29
|
+
handleChange(ev: {
|
|
30
|
+
paths: string[];
|
|
31
|
+
kind: ChangeKind;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Set the dev-client snippet (full `<script>…</script>` tag) the renderer
|
|
2
|
+
* should inject before `</head>`. Pass `null` to disable injection (the
|
|
3
|
+
* default in non-dev mode). */
|
|
4
|
+
export declare function configureDevClientSnippet(s: string | null): void;
|
|
5
|
+
/** Returns the configured snippet, or `null` when dev mode is off. */
|
|
6
|
+
export declare function getDevClientSnippet(): string | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Called once by `brust dev` after the initial native-template emit. */
|
|
2
|
+
export declare function registerJinjaReEmit(fn: () => Promise<void>): void;
|
|
3
|
+
/** Called by the dev coordinator on a ts/html/islands hot reload. No-op when no
|
|
4
|
+
* callback is registered (e.g. unit tests, or an app with no native routes). */
|
|
5
|
+
export declare function reEmitJinja(): Promise<void>;
|
|
6
|
+
/** Test helper. */
|
|
7
|
+
export declare function _resetForTests(): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
interface Status {
|
|
2
|
+
port: number;
|
|
3
|
+
workers: number;
|
|
4
|
+
watching: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface TuiOptions {
|
|
7
|
+
isTty: boolean;
|
|
8
|
+
write: (s: string) => void;
|
|
9
|
+
capacity?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Astro-style dev output: a clean one-time banner on ready, then event lines
|
|
12
|
+
* that scroll naturally below it. Deliberately NOT a full-screen TUI — it never
|
|
13
|
+
* hides the cursor or clears the screen, so the terminal is left exactly as it
|
|
14
|
+
* was found when the dev server exits (no more vanished cursor on Ctrl-C). */
|
|
15
|
+
export declare class Tui {
|
|
16
|
+
private opts;
|
|
17
|
+
private events;
|
|
18
|
+
private capacity;
|
|
19
|
+
private bannerShown;
|
|
20
|
+
constructor(opts: TuiOptions);
|
|
21
|
+
eventsForTests(): string[];
|
|
22
|
+
/** Print the ready banner. Idempotent — only the first call emits it. */
|
|
23
|
+
updateStatus(s: Status): void;
|
|
24
|
+
/** Append one event. Scrolls below the banner (TTY) or prints a plain line
|
|
25
|
+
* (non-TTY / CI). The in-memory ring buffer backs `eventsForTests`. */
|
|
26
|
+
appendEvent(line: string): void;
|
|
27
|
+
/** Called on shutdown. There is nothing to restore (we never altered the
|
|
28
|
+
* terminal mode); the reset is belt-and-braces so a half-written colored
|
|
29
|
+
* line can't bleed into the user's prompt. */
|
|
30
|
+
stop(): void;
|
|
31
|
+
/** Colorize an event line in TTY mode: a dim timestamp prefix, with ok/error
|
|
32
|
+
* markers tinted. The coordinator emits ` → ok (Nms)` and ` ✗ <msg>`. */
|
|
33
|
+
private format;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ChangeKind = 'ts' | 'css' | 'component-css' | 'html' | 'islands' | 'md';
|
|
2
|
+
/** Classify a changed path. Returns null when the path should be ignored.
|
|
3
|
+
* `root` is used to compute the relative path for ignore-segment matching.
|
|
4
|
+
* `hasMdRoutes` gates the `'md'` kind (S4): when the app has no md routes, a
|
|
5
|
+
* project `.md` edit (README.md, notes) must not trigger the full reload path
|
|
6
|
+
* (island rebuild + worker restart) — it classifies as null instead. Defaults
|
|
7
|
+
* to true for backward compatibility with callers that don't thread the flag. */
|
|
8
|
+
export declare function classifyPath(absPath: string, root: string, hasMdRoutes?: boolean): ChangeKind | null;
|
|
9
|
+
interface Coalesce {
|
|
10
|
+
add(path: string): void;
|
|
11
|
+
flush(): void;
|
|
12
|
+
}
|
|
13
|
+
/** Internal — exposed for unit tests. */
|
|
14
|
+
export declare function _testCoalesce(debounceMs: number, flush: (paths: string[]) => void): Coalesce;
|
|
15
|
+
export interface CreateWatcherOptions {
|
|
16
|
+
root: string;
|
|
17
|
+
debounceMs?: number;
|
|
18
|
+
/** Whether the app has md routes — gates `.md` classification (see
|
|
19
|
+
* classifyPath). Defaults to true (back-compat). */
|
|
20
|
+
hasMdRoutes?: boolean;
|
|
21
|
+
onChange: (ev: {
|
|
22
|
+
paths: string[];
|
|
23
|
+
kind: ChangeKind;
|
|
24
|
+
}) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface Watcher {
|
|
27
|
+
close(): void;
|
|
28
|
+
}
|
|
29
|
+
/** Watch `root` recursively. Emits one `onChange` call per debounce window
|
|
30
|
+
* with paths classified by the dominant kind. Mixed-kind windows pick
|
|
31
|
+
* by priority: islands > ts > html > css (islands trigger a full restart
|
|
32
|
+
* that subsumes the others). */
|
|
33
|
+
export declare function createWatcher(opts: CreateWatcherOptions): Watcher;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Called once by brust.serve() in dev mode AFTER it spawns the initial
|
|
2
|
+
* pool. Hands the references to the registry so the coordinator can
|
|
3
|
+
* churn them later. */
|
|
4
|
+
export declare function registerInitialPool(workers: Worker[], entry: string, count: number, baseEnv: Record<string, string>): void;
|
|
5
|
+
/** Terminate every Worker with a 2s per-worker grace. If termination
|
|
6
|
+
* doesn't return in time, abandon the reference and continue. */
|
|
7
|
+
export declare function terminateAll(): Promise<void>;
|
|
8
|
+
/** Spawn `count` fresh Workers using the entry + env captured at
|
|
9
|
+
* registerInitialPool time. Each worker gets BRUST_WORKER_ID=i.
|
|
10
|
+
* Resolves only after every fresh worker reports `brust-worker-ready`
|
|
11
|
+
* via postMessage — so the caller (coordinator) doesn't broadcast
|
|
12
|
+
* `reload` against workers whose message listeners aren't installed
|
|
13
|
+
* yet. Falls back after a 5s grace if a worker never signals. */
|
|
14
|
+
export declare function spawnAll(): Promise<void>;
|
|
15
|
+
/** Test helper. */
|
|
16
|
+
export declare function _workersForTests(): Worker[];
|
|
17
|
+
export declare function _resetForTests(): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Route } from '../routes.ts';
|
|
2
|
+
/** Server-to-client protocol. Client never sends after open. */
|
|
3
|
+
export type DevMessage = {
|
|
4
|
+
type: 'building';
|
|
5
|
+
} | {
|
|
6
|
+
type: 'reload';
|
|
7
|
+
} | {
|
|
8
|
+
type: 'css-update';
|
|
9
|
+
href: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'error';
|
|
12
|
+
message: string;
|
|
13
|
+
stack?: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'ok';
|
|
16
|
+
};
|
|
17
|
+
export declare function _clientCountForTests(): number;
|
|
18
|
+
export declare function _resetForTests(): void;
|
|
19
|
+
/** Build the synthetic /_brust/dev WS route. brust.run() prepends this
|
|
20
|
+
* to opts.routes in dev mode (both main + worker route arrays). */
|
|
21
|
+
export declare function createDevWsRoute(): Route;
|
|
22
|
+
/** Worker-side: install a message listener that receives `dev-broadcast`
|
|
23
|
+
* envelopes from the main process and forwards them to this worker's
|
|
24
|
+
* local clients. Idempotent. After install, posts `brust-worker-ready`
|
|
25
|
+
* back to the parent so spawnAll() knows the worker is ready to relay
|
|
26
|
+
* broadcasts (avoids a race where `reload` is dispatched before the
|
|
27
|
+
* fresh worker's listener is wired). */
|
|
28
|
+
export declare function installWorkerBroadcastListener(): void;
|
|
29
|
+
/** Main-side: push the message to every `/_brust/dev` client through the napi
|
|
30
|
+
* addon. The dev WS is a Rust-owned control channel (see the `/_brust/dev`
|
|
31
|
+
* branch in crates/brust/src/server.rs), so the frame is delivered straight
|
|
32
|
+
* through each connection's Rust-owned send_tx and SURVIVES a hot reload's
|
|
33
|
+
* worker restart.
|
|
34
|
+
*
|
|
35
|
+
* The previous implementation relayed main→worker→worker-local-clients, which
|
|
36
|
+
* lost the `reload` frame: the coordinator broadcasts it AFTER terminateAll(),
|
|
37
|
+
* by which point the connection's worker-side registration had died with the
|
|
38
|
+
* old worker. */
|
|
39
|
+
export declare function broadcast(msg: DevMessage): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface GeneratorStrings {
|
|
2
|
+
/** Full meta tag, e.g. `<meta name="generator" content="brust 0.1.48-alpha"/>` */
|
|
3
|
+
meta: string;
|
|
4
|
+
/** X-Powered-By value, e.g. `brust/0.1.48-alpha` */
|
|
5
|
+
header: string;
|
|
6
|
+
}
|
|
7
|
+
/** Build the resolved strings. Version comes from the brustjs package.json
|
|
8
|
+
* (readVersion never throws — "unknown" degrades to name-only, never a crash).
|
|
9
|
+
* The version is sanitized to attr/header-safe bytes; semver chars only. */
|
|
10
|
+
export declare function generatorStrings(versionOn: boolean): GeneratorStrings;
|
|
11
|
+
/** Insert the generator meta immediately after the compiler-emitted viewport
|
|
12
|
+
* meta. Anchor missing (non-document template) → no-op, never an error.
|
|
13
|
+
* CALLER CONTRACT: pass fresh compiler output — this function does not check
|
|
14
|
+
* for an existing tag, so calling it twice on the same string duplicates.
|
|
15
|
+
* Every emit path recompiles from source each run, which keeps this safe. */
|
|
16
|
+
export declare function insertGeneratorMeta(jinja: string, metaTag: string): string;
|
|
17
|
+
/** Write the decision artifact into `dir` (a jinja out dir), creating it. */
|
|
18
|
+
export declare function writeGeneratorArtifact(dir: string, strings: GeneratorStrings): void;
|
|
19
|
+
/** Read the artifact; null on missing/malformed (caller decides the fallback). */
|
|
20
|
+
export declare function readGeneratorArtifact(dir: string): GeneratorStrings | null;
|
|
21
|
+
/** Artifact if present, else version-on defaults — the spec's fallback policy
|
|
22
|
+
* (an old dist with no artifact behaves as default = version on). */
|
|
23
|
+
export declare function resolveGenerator(dir: string): GeneratorStrings;
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import * as native from './index.js';
|
|
2
|
+
/** Global CORS policy (one policy for the whole deployment — pages, actions,
|
|
3
|
+
* static assets alike; brust has no per-route CORS). Opt-in: omit to keep CORS
|
|
4
|
+
* disabled (byte-identical default — no `Access-Control-*` headers, OPTIONS
|
|
5
|
+
* still 405). Threaded into Rust, which answers preflights before the render
|
|
6
|
+
* pipeline and stamps actual responses at a single chokepoint. */
|
|
7
|
+
export interface CorsOptions {
|
|
8
|
+
/** Allowed origins, exact scheme+host+port match (no wildcard subdomains).
|
|
9
|
+
* A list CONTAINING `'*'` is treated as wildcard: every origin allowed,
|
|
10
|
+
* echoed as the literal `*`. */
|
|
11
|
+
origins: string[];
|
|
12
|
+
/** Preflight `Access-Control-Allow-Methods`. Default: GET,POST,PUT,PATCH,DELETE,OPTIONS. */
|
|
13
|
+
methods?: string[];
|
|
14
|
+
/** Preflight `Access-Control-Allow-Headers`. Default: echo the request's
|
|
15
|
+
* `Access-Control-Request-Headers`. */
|
|
16
|
+
headers?: string[];
|
|
17
|
+
/** `Access-Control-Expose-Headers` on actual responses. Default: none. */
|
|
18
|
+
exposeHeaders?: string[];
|
|
19
|
+
/** Emit `Access-Control-Allow-Credentials: true`. INVALID combined with a
|
|
20
|
+
* wildcard origin — serve() throws at boot (browsers silently reject
|
|
21
|
+
* `Allow-Credentials` with `Allow-Origin: *`; we make it loud). */
|
|
22
|
+
credentials?: boolean;
|
|
23
|
+
/** Preflight `Access-Control-Max-Age` seconds. Default 600. */
|
|
24
|
+
maxAgeSeconds?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Fail-fast CORS validation, mirroring Rust's `CorsConfig::validate` so a bad
|
|
27
|
+
* config throws in TS before the native call (clearer stack, same message
|
|
28
|
+
* shape). Exported for unit testing. */
|
|
29
|
+
export declare function validateCorsOptions(cors: CorsOptions): void;
|
|
30
|
+
export interface ServeOptions {
|
|
31
|
+
/** Host/address to bind on. A hostname (e.g. `localhost`, resolved Rust-side)
|
|
32
|
+
* or a literal IP such as `0.0.0.0` / `127.0.0.1`. */
|
|
33
|
+
host: string;
|
|
34
|
+
port: number;
|
|
35
|
+
workers: number;
|
|
36
|
+
entry: string;
|
|
37
|
+
bootTimeoutMs?: number;
|
|
38
|
+
/** Actions builder from `defineActions(...)`. When present, `serve`
|
|
39
|
+
* registers its endpoints (method/path, keyed by registration index)
|
|
40
|
+
* before the listener binds. Optional — omit if the app has no actions. */
|
|
41
|
+
actions?: import('./define-actions.ts').ActionsBuilder;
|
|
42
|
+
/** URL prefix the action router mounts under (e.g. `/_actions`). Threaded
|
|
43
|
+
* into Rust's ServeOptions.action_prefix. */
|
|
44
|
+
actionPrefix?: string;
|
|
45
|
+
/** Optional global CORS policy — see {@link CorsOptions}. Validated at boot
|
|
46
|
+
* (TS first, Rust mirrors): `origins` non-empty; `credentials` + wildcard
|
|
47
|
+
* origin throws. */
|
|
48
|
+
cors?: CorsOptions;
|
|
49
|
+
/** MCP support — pass a manifest built via brust.buildMcpManifest. brust.serve
|
|
50
|
+
* does NOT auto-wire MCP into workers; the worker branch of the entry file must
|
|
51
|
+
* call brust.loadMcpManifest() + makeMcpServer() itself and pass the McpServer
|
|
52
|
+
* to makeRenderer via `opts.mcp`. See example/pokedex/index.ts for the
|
|
53
|
+
* pattern. The field here is currently unused inside serve() and is reserved
|
|
54
|
+
* for future IPC-based propagation of the manifest. */
|
|
55
|
+
mcp?: {
|
|
56
|
+
manifest: import('./mcp/manifest.ts').McpManifest;
|
|
57
|
+
};
|
|
58
|
+
/** Performance tunables. All optional — omitting any field keeps the
|
|
59
|
+
* framework default, so the whole object is opt-in. `connWorkers` sets the
|
|
60
|
+
* Rust-side connection-handling concurrency (accept→parse→dispatch), which is
|
|
61
|
+
* INDEPENDENT of `workers` (the Bun render threads): I/O-bound paths like
|
|
62
|
+
* `/ping` scale with `connWorkers`, render-bound paths scale with `workers`.
|
|
63
|
+
* Setting `connWorkers > workers` is safe: a render dispatch that finds every
|
|
64
|
+
* worker busy QUEUES (awaits a free worker up to `claimTimeoutMs`) instead of
|
|
65
|
+
* 503-ing, so excess conn-workers just wait their turn rather than dropping
|
|
66
|
+
* requests. */
|
|
67
|
+
tuning?: {
|
|
68
|
+
/** Rust accept/dispatch concurrency. Default = `workers`. */
|
|
69
|
+
connWorkers?: number;
|
|
70
|
+
/** Max request bytes before header terminator. Default 16384 (16 KB). */
|
|
71
|
+
maxRequestBytes?: number;
|
|
72
|
+
/** Max action/RPC body size. Default 262144 (256 KB). */
|
|
73
|
+
maxActionBodyBytes?: number;
|
|
74
|
+
/** Accept-side queue depth (TCP backpressure point). Default 1024. */
|
|
75
|
+
connQueueCap?: number;
|
|
76
|
+
/** Initial per-connection read buffer capacity. Default 4096. */
|
|
77
|
+
readBufCap?: number;
|
|
78
|
+
/** Max ms a render waits for a free worker before 503 (AllBusy queues
|
|
79
|
+
* instead of failing fast). Default 10000. */
|
|
80
|
+
claimTimeoutMs?: number;
|
|
81
|
+
/** tokio I/O runtime worker-thread count for the hyper server. Runs inside
|
|
82
|
+
* Bun (which has its own threads + render workers), so this is NOT
|
|
83
|
+
* one-per-core. Default `min(availableParallelism, 4)` (fallback 2). */
|
|
84
|
+
workerThreads?: number;
|
|
85
|
+
/** Render slots per Bun worker — concurrent in-flight renders per isolate.
|
|
86
|
+
* Default `min(cores, 16)` (set `BRUST_RENDER_SLOTS` to go higher on >16-core
|
|
87
|
+
* hosts). Only speeds renders that `await` during render (e.g. Suspense with
|
|
88
|
+
* async data); CPU-bound/native/cache routes serialize on the one isolate and
|
|
89
|
+
* are unaffected. The count is propagated to each worker via the
|
|
90
|
+
* `BRUST_RENDER_SLOTS` env var and scales the per-worker SAB (one region per
|
|
91
|
+
* slot, so the cap bounds memory). slots > 1 is byte-identical to slots = 1. */
|
|
92
|
+
renderSlots?: number;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export type RenderFn = (envelopeJson: string, slot: number) => Promise<number>;
|
|
96
|
+
export declare const isWorker: boolean;
|
|
97
|
+
export declare function workerId(): number | null;
|
|
98
|
+
export declare const brust: {
|
|
99
|
+
serve(opts: ServeOptions): Promise<void>;
|
|
100
|
+
/** Install the route table in Rust. MUST be called before `serve()`.
|
|
101
|
+
* Pass an array of FlatRoutes from `defineRoutes(...)` — each is JSON-encoded
|
|
102
|
+
* with its optional cache config. Rust matches against `fullPath`. */
|
|
103
|
+
registerRoutes(routes: import("./routes.ts").FlatRoute[]): number;
|
|
104
|
+
/** Register the list of literal route paths that should be dispatched as
|
|
105
|
+
* SSE (text/event-stream) instead of going through the render pipeline.
|
|
106
|
+
* Call from the main process after defineRoutes — the worker only needs
|
|
107
|
+
* the call if it also accepts SSE traffic, but in the current model the
|
|
108
|
+
* main accept loop owns dispatch so this is main-only. MVP supports only
|
|
109
|
+
* literal paths; parameterized routes (e.g. `/sse/{room}`) are a follow-up. */
|
|
110
|
+
registerSsePaths(paths: string[]): void;
|
|
111
|
+
/** Register the list of literal route paths that should be dispatched as
|
|
112
|
+
* WebSocket upgrades. Call from the main process after defineRoutes.
|
|
113
|
+
* MVP supports only literal paths — parameterized routes (e.g.
|
|
114
|
+
* `/ws/chat/{room}`) are a follow-up. */
|
|
115
|
+
registerWsPaths(paths: string[]): void;
|
|
116
|
+
/** Set the L1 response-cache and L2 page-cache capacities (entries).
|
|
117
|
+
* Default is 1000 each. Rust reconstructs both caches at the given
|
|
118
|
+
* capacities (moka fixes capacity at construction), so call this once at
|
|
119
|
+
* boot before serving begins. */
|
|
120
|
+
configureCache(opts: {
|
|
121
|
+
maxEntries: number;
|
|
122
|
+
pageMaxEntries: number;
|
|
123
|
+
}): void;
|
|
124
|
+
/** Tell Rust where to read `/_brust/islands/<file>` from. Called once at
|
|
125
|
+
* boot after buildIslands() emits chunks. Path must be absolute. */
|
|
126
|
+
configureIslandsDir(dir: string): void;
|
|
127
|
+
/** Tell Rust where to read `/_brust/css/<file>` from. Called from the
|
|
128
|
+
* main thread when CSS is configured. Path must be absolute. */
|
|
129
|
+
configureCssDir(dir: string): void;
|
|
130
|
+
/** Tell Rust where to read root-mapped static assets (`/favicon.ico`, …) from.
|
|
131
|
+
* Path must be absolute. */
|
|
132
|
+
configurePublicDir(dir: string): void;
|
|
133
|
+
/** Extract the MCP manifest from TypeScript source using the compiler API,
|
|
134
|
+
* write it to `.brust/mcp-manifest.json`, and return it. Call once in the
|
|
135
|
+
* main process after `brust.registerRoutes(routes)`. Workers must read the
|
|
136
|
+
* persisted manifest themselves via `brust.loadMcpManifest()` in the worker
|
|
137
|
+
* branch and pass an `McpServer` (built via `makeMcpServer`) to
|
|
138
|
+
* `makeRenderer` via `opts.mcp`. See example/pokedex/index.ts. */
|
|
139
|
+
buildMcpManifest(opts: {
|
|
140
|
+
actionsFile?: string;
|
|
141
|
+
routesFile: string;
|
|
142
|
+
sourceRoots: string[];
|
|
143
|
+
routes: import("./routes.ts").FlatRoute[];
|
|
144
|
+
cwd?: string;
|
|
145
|
+
}): Promise<import("./mcp/manifest.ts").McpManifest>;
|
|
146
|
+
/** Read the MCP manifest from `.brust/mcp-manifest.json`. Returns null if
|
|
147
|
+
* the file does not exist (i.e. the main process hasn't called
|
|
148
|
+
* `brust.buildMcpManifest` yet). Throws if the file is malformed. */
|
|
149
|
+
loadMcpManifest(cwd?: string): Promise<import("./mcp/manifest.ts").McpManifest | null>;
|
|
150
|
+
registerRenderer(buf: Uint8Array, slots: number, fn: RenderFn): number;
|
|
151
|
+
/**
|
|
152
|
+
* One-call lifecycle: scans actions, builds islands (if `routes.tsx` uses
|
|
153
|
+
* `<Island>`), registers routes + SSE/WS paths, builds the MCP manifest, then
|
|
154
|
+
* branches to `serve()` (main thread) or registers a renderer (worker
|
|
155
|
+
* thread). Replaces ~70 lines of boilerplate; the lower-level helpers
|
|
156
|
+
* (`scanActions`, `registerRoutes`, `makeRenderer`, etc.) are still exported
|
|
157
|
+
* for apps that need finer control.
|
|
158
|
+
*
|
|
159
|
+
* Conventions assumed (override via the corresponding option if your layout
|
|
160
|
+
* differs):
|
|
161
|
+
* - `<scanRoot>/routes.tsx` — scanned for `<Island>` usage (islands)
|
|
162
|
+
* and referenced by the MCP manifest builder
|
|
163
|
+
*
|
|
164
|
+
* `scanRoot` defaults to the directory of `entry` so the typical caller
|
|
165
|
+
* passes only `{ routes, entry: import.meta.url }`.
|
|
166
|
+
*/
|
|
167
|
+
run(opts: {
|
|
168
|
+
routes: import("./routes.ts").FlatRoute[];
|
|
169
|
+
entry: string;
|
|
170
|
+
scanRoot?: string;
|
|
171
|
+
/** Host/address to bind on. Default `localhost`. A `brust dev --port` /
|
|
172
|
+
* BRUST_PORT / BRUST_ADDR / brust.toml all override this app-level value
|
|
173
|
+
* (precedence: env > toml > this > framework default). */
|
|
174
|
+
address?: string;
|
|
175
|
+
/** TCP port to bind on. Default 1337. Overridable by env/toml — see
|
|
176
|
+
* `address`. */
|
|
177
|
+
port?: number;
|
|
178
|
+
/** Actions builder from `defineActions(...)`. Registered with Rust and
|
|
179
|
+
* threaded to each worker's renderer. Omit if the app has no actions. */
|
|
180
|
+
actions?: import("./define-actions.ts").ActionsBuilder;
|
|
181
|
+
/** URL prefix the action router mounts under. Threaded to serve(). */
|
|
182
|
+
actionPrefix?: string;
|
|
183
|
+
/** Optional global CORS policy — see {@link CorsOptions}. Threaded to
|
|
184
|
+
* serve() like `actionPrefix`. */
|
|
185
|
+
cors?: CorsOptions;
|
|
186
|
+
/** Overrides merged into the underlying `serve()` call (main thread). */
|
|
187
|
+
serve?: Partial<Omit<ServeOptions, "entry" | "actions" | "mcp">>;
|
|
188
|
+
/** Per-worker SAB size in bytes. Default 256 KB. */
|
|
189
|
+
sabBytes?: number;
|
|
190
|
+
/** When true, prepend the dev WS route, install file watcher, set the
|
|
191
|
+
* dev-client snippet, and start the TUI. Default false.
|
|
192
|
+
* Also activated by BRUST_DEV=1 environment variable. */
|
|
193
|
+
dev?: boolean;
|
|
194
|
+
}): Promise<void>;
|
|
195
|
+
};
|
|
196
|
+
export { defineRoutes, makeRenderer, Outlet, notFound, redirect, isNativeVerdict, httpError, isHttpErrorTrigger, } from './routes.ts';
|
|
197
|
+
export type { Route, RouteCall, RouteContext, ErrorBoundaryProps, RouteCacheConfig, BrustRequest, RouteResponse, Middleware, NativeVerdict, HttpErrorOpts, HttpErrorTrigger, } from './routes.ts';
|
|
198
|
+
export { defineActions, isValidEndpointPath } from './define-actions.ts';
|
|
199
|
+
export type { EndpointDef, ActionContext, EndpointOptions, ActionsBuilder, } from './define-actions.ts';
|
|
200
|
+
export { ActionError, isActionError } from './action-error.ts';
|
|
201
|
+
export type { ActionErrorBody } from './action-error.ts';
|
|
202
|
+
export { loadConfig, BrustConfigError } from './config.ts';
|
|
203
|
+
export type { BrustConfig } from './config.ts';
|
|
204
|
+
export { Island } from './islands/island.tsx';
|
|
205
|
+
export type { IslandProps, HydrateTrigger } from './islands/island.tsx';
|
|
206
|
+
import './islands/isr-jsx.ts';
|
|
207
|
+
export type { IsrConfig } from './islands/isr-jsx.ts';
|
|
208
|
+
export { BrustPage } from './islands/brust-page.tsx';
|
|
209
|
+
export type { BrustPageProps, HeadEntry } from './islands/brust-page.tsx';
|
|
210
|
+
export { defineStore, signal, computed, effect, batch } from './store/index.ts';
|
|
211
|
+
export type { StoreHandle, Snapshot, Signal, Computed } from './store/index.ts';
|
|
212
|
+
export { dedupe, cachedFetch } from './loader-cache.ts';
|
|
213
|
+
export { renderFragment } from './render/fragment.ts';
|
|
214
|
+
export type { RenderFragmentOpts } from './render/fragment.ts';
|
|
215
|
+
export { buildIslands } from './islands/build.ts';
|
|
216
|
+
export type { IslandsBuildResult, BuildIslandsOptions } from './islands/build.ts';
|
|
217
|
+
export { cache } from './cache.ts';
|
|
218
|
+
export type { InvalidateArgs } from './cache.ts';
|
|
219
|
+
export { templates } from './templates.ts';
|
|
220
|
+
export { getRequestContext } from './request-context.ts';
|
|
221
|
+
export { cookies } from './cookies.ts';
|
|
222
|
+
export type { CookieOptions } from './cookies.ts';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** Props for the built-in `<BrustPage>` document shell.
|
|
3
|
+
*
|
|
4
|
+
* `<BrustPage>` is a NATIVE-route document component: in a `native: true` route
|
|
5
|
+
* the Rust JSX compiler intercepts the `BrustPage` tag and emits the whole
|
|
6
|
+
* `<html>/<head>/<body>` skeleton itself, auto-injecting the framework head tags
|
|
7
|
+
* (charset, viewport, the `/_brust/css/app.css` stylesheet link). The head is
|
|
8
|
+
* configured ENTIRELY through these props — you never write `<head>` markup, so
|
|
9
|
+
* brust keeps full ownership of `<head>` and can add more tags later (importmap,
|
|
10
|
+
* preloads) without colliding with hand-written head elements.
|
|
11
|
+
*
|
|
12
|
+
* On the native path each prop accepts a compile-time string literal OR a
|
|
13
|
+
* loader member-path (`title={data.title}`), interpolated into the Rust-rendered
|
|
14
|
+
* shell as `{{ path }}` (S8). Calls/arithmetic are still rejected. This React
|
|
15
|
+
* implementation mirrors the compiled output for the rare non-native use. */
|
|
16
|
+
/** One extra `<head>` element for `<BrustPage head={[…]}>`. Discriminated by
|
|
17
|
+
* `tag`. On the native path, attribute values may be a string literal or a
|
|
18
|
+
* loader member-path (HTML-escaped); `text` (inner content of style/script/
|
|
19
|
+
* noscript) must be a static string literal (raw — never user input). */
|
|
20
|
+
export type HeadEntry = {
|
|
21
|
+
tag: 'link';
|
|
22
|
+
rel: string;
|
|
23
|
+
href: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
sizes?: string;
|
|
26
|
+
as?: string;
|
|
27
|
+
media?: string;
|
|
28
|
+
crossOrigin?: string;
|
|
29
|
+
} | {
|
|
30
|
+
tag: 'meta';
|
|
31
|
+
name?: string;
|
|
32
|
+
property?: string;
|
|
33
|
+
httpEquiv?: string;
|
|
34
|
+
content: string;
|
|
35
|
+
} | {
|
|
36
|
+
tag: 'base';
|
|
37
|
+
href?: string;
|
|
38
|
+
target?: string;
|
|
39
|
+
} | {
|
|
40
|
+
tag: 'style';
|
|
41
|
+
text: string;
|
|
42
|
+
media?: string;
|
|
43
|
+
} | {
|
|
44
|
+
tag: 'script';
|
|
45
|
+
src?: string;
|
|
46
|
+
text?: string;
|
|
47
|
+
type?: string;
|
|
48
|
+
defer?: boolean;
|
|
49
|
+
async?: boolean;
|
|
50
|
+
crossOrigin?: string;
|
|
51
|
+
} | {
|
|
52
|
+
tag: 'noscript';
|
|
53
|
+
text: string;
|
|
54
|
+
};
|
|
55
|
+
export interface BrustPageProps {
|
|
56
|
+
/** `<html lang>` — defaults to `"en"`. */
|
|
57
|
+
lang?: string;
|
|
58
|
+
/** `<html class>` (e.g. `"dark"`). */
|
|
59
|
+
className?: string;
|
|
60
|
+
/** `<body class>`. */
|
|
61
|
+
bodyClassName?: string;
|
|
62
|
+
/** `<title>…</title>`. Omitted when absent. */
|
|
63
|
+
title?: string;
|
|
64
|
+
/** `<meta name="description" content="…">`. Omitted when absent. */
|
|
65
|
+
description?: string;
|
|
66
|
+
/** Extra `<head>` elements (link/meta/base/style/script/noscript). */
|
|
67
|
+
head?: HeadEntry[];
|
|
68
|
+
/** Page body — rendered inside `<body>`. */
|
|
69
|
+
children?: ReactNode;
|
|
70
|
+
/** Arbitrary `data-*` on `<html>` (e.g. `data-mode="dark"`). String literal
|
|
71
|
+
* or loader member-path on the native path. */
|
|
72
|
+
[dataAttr: `data-${string}`]: string | undefined;
|
|
73
|
+
}
|
|
74
|
+
export declare function BrustPage({ lang, className, bodyClassName, title, description, head, children, ...rest }: BrustPageProps): ReactNode;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BunPlugin } from 'bun';
|
|
2
|
+
export interface IslandsBuildResult {
|
|
3
|
+
/** Absolute path to the output directory passed to brust's Rust side. */
|
|
4
|
+
outDir: string;
|
|
5
|
+
/** Number of island chunks emitted (excludes runtime + bootstrap). */
|
|
6
|
+
islandCount: number;
|
|
7
|
+
/** id → content-addressed chunk URL (`/_brust/islands/<id>_<hash>.js`). Also
|
|
8
|
+
* written to `_islands.js` for the client bootstrap to resolve at runtime. */
|
|
9
|
+
chunks: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface BuildIslandsOptions {
|
|
12
|
+
/** Override the output directory. Default: `<cwd>/.brust/islands`. */
|
|
13
|
+
outDir?: string;
|
|
14
|
+
/** Build plugins passed straight to `Bun.build` for the per-island chunks.
|
|
15
|
+
* Needed for the component-CSS loader: global `Bun.plugin()` registrations do
|
|
16
|
+
* NOT apply to `Bun.build`, so an island that `import`s a `.module.css` must
|
|
17
|
+
* get the resolver here or Bun emits the CSS as a separate asset and collides
|
|
18
|
+
* on the output filename (X.module.css + X.tsx → both X.js). */
|
|
19
|
+
plugins?: BunPlugin[];
|
|
20
|
+
}
|
|
21
|
+
/** Scan a routes entry file for `<Island component={X} />` usage and derive the
|
|
22
|
+
* island chunk list (componentName → absolute source path). Replaces the old
|
|
23
|
+
* static config-file lookup — the chunk set is derived from source.
|
|
24
|
+
*
|
|
25
|
+
* 1. Resolve the entry's page imports via {@link scanImports}.
|
|
26
|
+
* 2. For each page, slice every `<Island … />` tag and capture its
|
|
27
|
+
* `component={Ident}`. A tag with no `component` is a hard error (F3:
|
|
28
|
+
* never silently skip an island).
|
|
29
|
+
* 3. Resolve each captured ident through that page's OWN imports.
|
|
30
|
+
* 4. Key by the content-addressed {@link islandChunkBasename} (`<Name>_<hash>`),
|
|
31
|
+
* NOT the bare name — so two DIFFERENT files exporting a same-named component
|
|
32
|
+
* produce two distinct chunks. Same name + same file dedups (same id). The
|
|
33
|
+
* marker carries this same id (native: reconcileIslandManifest rewrite;
|
|
34
|
+
* React: the Component→id registry seeded at worker boot), so there is no
|
|
35
|
+
* app-unique-name requirement.
|
|
36
|
+
*
|
|
37
|
+
* `extraIslands` (task 2.8) merges additional islands the routes-graph scan
|
|
38
|
+
* cannot see — md-route islands resolved by `emitMdTemplates` (`name →
|
|
39
|
+
* absolute source path`, the bare-name map it returns). Each is keyed by the
|
|
40
|
+
* same content-addressed id, so same name + same path dedups against the scan
|
|
41
|
+
* result; same name + different path yields a distinct id (two chunks, the
|
|
42
|
+
* shipped same-name parity). A same-id-different-path collision is a hard
|
|
43
|
+
* error — never silently rebind a chunk id.
|
|
44
|
+
*/
|
|
45
|
+
export declare function scanIslandChunks(routesEntryFile: string, extraIslands?: Map<string, string>): Map<string, string>;
|
|
46
|
+
/** Build the runtime chunks + all island chunks + bootstrap. Returns the
|
|
47
|
+
* absolute output directory; caller passes it to `brust.configureIslandsDir`. */
|
|
48
|
+
export declare function buildIslands(islands: Map<string, string>, options?: BuildIslandsOptions): Promise<IslandsBuildResult>;
|
|
49
|
+
export declare function buildOne(entrypoints: string[], outdir: string, naming: string, external: string[], plugins?: BunPlugin[]): Promise<void>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Content-addressed island id / chunk basename = `<Name>_<8hex(sha256
|
|
2
|
+
* cwd-relative source path)>`. The SINGLE name contract (mirrors the directive
|
|
3
|
+
* chunk scheme): the chunk filename, the `data-brust-island` marker (native
|
|
4
|
+
* rewrite in reconcileIslandManifest + the React island-id onLoad plugin), and
|
|
5
|
+
* the bootstrap import all derive from this — so two same-named components from
|
|
6
|
+
* different files get distinct ids and never collide.
|
|
7
|
+
*
|
|
8
|
+
* Lives in its own module (no `scanImports` dep) so both `islands/build.ts` and
|
|
9
|
+
* `cli/native-routes-emit.ts` can import it without a circular dependency. */
|
|
10
|
+
export declare function islandChunkBasename(name: string, absSourcePath: string): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import type { IsrConfig } from './isr-jsx.ts';
|
|
3
|
+
/** Triggers that activate hydration of an island marker. */
|
|
4
|
+
export type HydrateTrigger = 'load' | 'idle' | 'visible' | 'interaction';
|
|
5
|
+
export interface IslandProps<P> {
|
|
6
|
+
/** Component rendered server-side INSIDE the marker. Same component
|
|
7
|
+
* the client chunk default-exports — SSR HTML must match the post-hydrate
|
|
8
|
+
* tree to avoid React reconciliation warnings. Its `Component.name` is the
|
|
9
|
+
* island id: it names the chunk (`<name>.js`) and the `data-brust-island`
|
|
10
|
+
* marker the client bootstrap reads, so it must be a stable, named
|
|
11
|
+
* component (no anonymous default export). */
|
|
12
|
+
component: ComponentType<P>;
|
|
13
|
+
/** Props passed to the component on both server and client. Must be
|
|
14
|
+
* JSON-serializable (no functions, classes, DOM nodes, etc.). Optional — a
|
|
15
|
+
* propless island (e.g. one that reads only global/client state) may omit it;
|
|
16
|
+
* it defaults to `{}`. On native routes, omitting `props` lowers to an empty
|
|
17
|
+
* props_path the renderer fills with `{}`. */
|
|
18
|
+
props?: P;
|
|
19
|
+
/** When to hydrate. Default 'load'. */
|
|
20
|
+
hydrate?: HydrateTrigger;
|
|
21
|
+
/** Native routes only: render this island server-side (renderToString during
|
|
22
|
+
* the loader crossing) so its markup ships in the HTML, then hydrate. Ignored
|
|
23
|
+
* on the React path (the whole tree already SSRs there). Default false. */
|
|
24
|
+
ssr?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Optional ISR (incremental static regeneration) cache for an `ssr` island on
|
|
27
|
+
* a native-jinja route. When present, the island's `renderToString` runs ONCE
|
|
28
|
+
* per `key`; later requests serve the frozen markup from the Rust-side cache.
|
|
29
|
+
* Ignored unless `ssr` is set (caching a client-only island is meaningless).
|
|
30
|
+
*
|
|
31
|
+
* - `key` (required): unique string identifying this cache entry. A different
|
|
32
|
+
* key is a different cached render. Compute it in the loader and pass it
|
|
33
|
+
* through, e.g. `isr={{ key: data.cacheKey }}`.
|
|
34
|
+
* - `tags` (optional): groups for bulk invalidation —
|
|
35
|
+
* `import { cache } from 'brustjs'; cache.invalidate({ tags: ['blog'] })`
|
|
36
|
+
* evicts every entry carrying that tag. `cache.invalidate({ key })` evicts one.
|
|
37
|
+
* - `revalidate` (optional): TTL in **seconds** (integer). Omit to cache until
|
|
38
|
+
* explicitly invalidated.
|
|
39
|
+
*
|
|
40
|
+
* Example: `isr={{ key: data.cacheKey, tags: ['blog'], revalidate: 60 }}`
|
|
41
|
+
*/
|
|
42
|
+
isr?: IsrConfig;
|
|
43
|
+
}
|
|
44
|
+
/** Per-render box tracking whether any `<Island>` rendered. Created fresh per
|
|
45
|
+
* render and provided through {@link IslandUsedContext}; the renderer reads
|
|
46
|
+
* `box.used` once at the end to decide whether to prepend the importmap +
|
|
47
|
+
* bootstrap script.
|
|
48
|
+
*
|
|
49
|
+
* This is REQUEST-SCOPED (not a module-scope flag) so concurrent renders in one
|
|
50
|
+
* isolate (renderSlots>1) never cross-contaminate: React restores each render's
|
|
51
|
+
* context stack across Suspense resumption, so an interleaved peer setting its
|
|
52
|
+
* own box never flips ours. A module `let` could not give that guarantee. */
|
|
53
|
+
export interface IslandUsedBox {
|
|
54
|
+
used: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Fresh per-render box. */
|
|
57
|
+
export declare function createIslandUsedBox(): IslandUsedBox;
|
|
58
|
+
/** Seed the Component→id registry (worker boot). */
|
|
59
|
+
export declare function configureIslandIdRegistry(entries: Iterable<[unknown, string]>): void;
|
|
60
|
+
/** Carries the per-render {@link IslandUsedBox} down to every `<Island>`. The
|
|
61
|
+
* renderer wraps the tree in a Provider with a fresh box; an `<Island>` rendered
|
|
62
|
+
* with no Provider (e.g. a standalone `renderToString` outside the React render
|
|
63
|
+
* path) reads `null` and is a no-op. */
|
|
64
|
+
export declare const IslandUsedContext: import("react").Context<IslandUsedBox | null>;
|
|
65
|
+
export declare function Island<P extends object>({ component: Component, props, hydrate, }: IslandProps<P>): ReactNode;
|