brustjs 0.1.0-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.
Files changed (63) hide show
  1. package/README.md +110 -0
  2. package/package.json +92 -0
  3. package/runtime/actions.ts +65 -0
  4. package/runtime/bun.lock +236 -0
  5. package/runtime/cli/actions-prebuilt-plugin.ts +97 -0
  6. package/runtime/cli/build.ts +252 -0
  7. package/runtime/cli/dev.ts +92 -0
  8. package/runtime/cli/index.ts +30 -0
  9. package/runtime/cli/native-routes-emit.ts +171 -0
  10. package/runtime/cli/native-shim-plugin.ts +85 -0
  11. package/runtime/cli/new.ts +208 -0
  12. package/runtime/cli/templates/minimal/README.md.tmpl +16 -0
  13. package/runtime/cli/templates/minimal/_gitignore +4 -0
  14. package/runtime/cli/templates/minimal/app.css +6 -0
  15. package/runtime/cli/templates/minimal/components/Counter.tsx +13 -0
  16. package/runtime/cli/templates/minimal/components/Layout.tsx +16 -0
  17. package/runtime/cli/templates/minimal/index.ts +4 -0
  18. package/runtime/cli/templates/minimal/package.json.tmpl +21 -0
  19. package/runtime/cli/templates/minimal/pages/Home.tsx.tmpl +16 -0
  20. package/runtime/cli/templates/minimal/routes.tsx +6 -0
  21. package/runtime/cli/templates/minimal/tsconfig.json +20 -0
  22. package/runtime/client/index.ts +121 -0
  23. package/runtime/config.ts +148 -0
  24. package/runtime/css/build.ts +54 -0
  25. package/runtime/css/component-build.ts +78 -0
  26. package/runtime/css/component-loader.ts +27 -0
  27. package/runtime/css/manifest.ts +51 -0
  28. package/runtime/css/process-modules.ts +56 -0
  29. package/runtime/css/route-deps.ts +33 -0
  30. package/runtime/css/scan-imports.ts +79 -0
  31. package/runtime/css.ts +39 -0
  32. package/runtime/dev/client.ts +49 -0
  33. package/runtime/dev/coordinator.ts +127 -0
  34. package/runtime/dev/inject.ts +17 -0
  35. package/runtime/dev/tui.ts +109 -0
  36. package/runtime/dev/watcher.ts +109 -0
  37. package/runtime/dev/worker-registry.ts +96 -0
  38. package/runtime/dev/ws-channel.ts +99 -0
  39. package/runtime/index.d.ts +199 -0
  40. package/runtime/index.js +604 -0
  41. package/runtime/index.ts +618 -0
  42. package/runtime/islands/__fixtures__/NoDefault.tsx +3 -0
  43. package/runtime/islands/__fixtures__/StubIsland.tsx +7 -0
  44. package/runtime/islands/__fixtures__/ThrowingIsland.tsx +9 -0
  45. package/runtime/islands/_entries/react-dom.ts +7 -0
  46. package/runtime/islands/_entries/react.ts +11 -0
  47. package/runtime/islands/bootstrap.ts +241 -0
  48. package/runtime/islands/build.ts +141 -0
  49. package/runtime/islands/importmap.ts +17 -0
  50. package/runtime/islands/island.tsx +58 -0
  51. package/runtime/islands/native-render.ts +153 -0
  52. package/runtime/mcp/extractor.ts +160 -0
  53. package/runtime/mcp/manifest.ts +50 -0
  54. package/runtime/mcp/schema.ts +124 -0
  55. package/runtime/mcp/server.ts +250 -0
  56. package/runtime/render/inject-css-link.ts +59 -0
  57. package/runtime/render/inject-dev-client.ts +49 -0
  58. package/runtime/render/stream.ts +304 -0
  59. package/runtime/routes.ts +1406 -0
  60. package/runtime/scan-actions.ts +172 -0
  61. package/runtime/sse/handler.ts +85 -0
  62. package/runtime/tsconfig.json +14 -0
  63. package/runtime/ws/handler.ts +151 -0
@@ -0,0 +1,109 @@
1
+ import { watch, type FSWatcher } from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ export type ChangeKind = 'ts' | 'css' | 'component-css' | 'html' | 'islands'
5
+
6
+ const IGNORE_DIR_SEGMENTS = new Set(['node_modules', '.git', '.brust', 'dist'])
7
+ const TS_RE = /\.(tsx?|jsx?)$/
8
+ const TEST_RE = /\.test\.(tsx?|jsx?)$/
9
+
10
+ /** Classify a changed path. Returns null when the path should be ignored.
11
+ * `root` is used to compute the relative path for ignore-segment matching. */
12
+ export function classifyPath(absPath: string, root: string): ChangeKind | null {
13
+ const rel = path.relative(root, absPath)
14
+ const segs = rel.split(path.sep)
15
+ for (const s of segs) {
16
+ if (IGNORE_DIR_SEGMENTS.has(s)) return null
17
+ }
18
+ if (TEST_RE.test(absPath)) return null
19
+
20
+ const base = path.basename(absPath)
21
+ if (base === 'app.css') return 'css'
22
+ // skip generated module type declarations
23
+ if (absPath.endsWith('.module.css.d.ts')) return null
24
+ // any other .css (including .module.css) is component CSS
25
+ if (absPath.endsWith('.css')) return 'component-css'
26
+ if (absPath.endsWith('.html')) return 'html'
27
+ if (TS_RE.test(absPath)) return 'ts'
28
+ return null
29
+ }
30
+
31
+ interface Coalesce {
32
+ add(path: string): void
33
+ flush(): void
34
+ }
35
+
36
+ /** Internal — exposed for unit tests. */
37
+ export function _testCoalesce(debounceMs: number, flush: (paths: string[]) => void): Coalesce {
38
+ let pending = new Set<string>()
39
+ let timer: ReturnType<typeof setTimeout> | null = null
40
+ return {
41
+ add(p) {
42
+ pending.add(p)
43
+ if (timer) clearTimeout(timer)
44
+ timer = setTimeout(() => {
45
+ const out = Array.from(pending)
46
+ pending = new Set()
47
+ timer = null
48
+ flush(out)
49
+ }, debounceMs)
50
+ },
51
+ flush() {
52
+ if (timer) {
53
+ clearTimeout(timer)
54
+ timer = null
55
+ }
56
+ if (pending.size > 0) {
57
+ const out = Array.from(pending)
58
+ pending = new Set()
59
+ flush(out)
60
+ }
61
+ },
62
+ }
63
+ }
64
+
65
+ export interface CreateWatcherOptions {
66
+ root: string
67
+ debounceMs?: number
68
+ onChange: (ev: { paths: string[]; kind: ChangeKind }) => void
69
+ }
70
+
71
+ export interface Watcher {
72
+ close(): void
73
+ }
74
+
75
+ /** Watch `root` recursively. Emits one `onChange` call per debounce window
76
+ * with paths classified by the dominant kind. Mixed-kind windows pick
77
+ * by priority: islands > ts > html > css (islands trigger a full restart
78
+ * that subsumes the others). */
79
+ export function createWatcher(opts: CreateWatcherOptions): Watcher {
80
+ const debounceMs = opts.debounceMs ?? 50
81
+ const kindPriority: ChangeKind[] = ['islands', 'ts', 'html', 'css', 'component-css']
82
+
83
+ const coalesce = _testCoalesce(debounceMs, (paths) => {
84
+ const kinds = new Set<ChangeKind>()
85
+ const keep: string[] = []
86
+ for (const p of paths) {
87
+ const k = classifyPath(p, opts.root)
88
+ if (k === null) continue
89
+ kinds.add(k)
90
+ keep.push(p)
91
+ }
92
+ if (keep.length === 0) return
93
+ const dominant = kindPriority.find((k) => kinds.has(k))!
94
+ opts.onChange({ paths: keep, kind: dominant })
95
+ })
96
+
97
+ const fsWatcher: FSWatcher = watch(opts.root, { recursive: true }, (_event, filename) => {
98
+ if (!filename) return
99
+ const abs = path.resolve(opts.root, filename)
100
+ coalesce.add(abs)
101
+ })
102
+
103
+ return {
104
+ close() {
105
+ fsWatcher.close()
106
+ coalesce.flush()
107
+ },
108
+ }
109
+ }
@@ -0,0 +1,96 @@
1
+ const TERMINATE_TIMEOUT_MS = 2000
2
+
3
+ interface RegistryState {
4
+ workers: Worker[]
5
+ entry: string | null
6
+ count: number
7
+ baseEnv: Record<string, string> | null
8
+ }
9
+
10
+ const state: RegistryState = {
11
+ workers: [],
12
+ entry: null,
13
+ count: 0,
14
+ baseEnv: null,
15
+ }
16
+
17
+ /** Called once by brust.serve() in dev mode AFTER it spawns the initial
18
+ * pool. Hands the references to the registry so the coordinator can
19
+ * churn them later. */
20
+ export function registerInitialPool(
21
+ workers: Worker[],
22
+ entry: string,
23
+ count: number,
24
+ baseEnv: Record<string, string>,
25
+ ): void {
26
+ state.workers = workers.slice()
27
+ state.entry = entry
28
+ state.count = count
29
+ state.baseEnv = baseEnv
30
+ }
31
+
32
+ /** Terminate every Worker with a 2s per-worker grace. If termination
33
+ * doesn't return in time, abandon the reference and continue. */
34
+ export async function terminateAll(): Promise<void> {
35
+ const olds = state.workers
36
+ state.workers = []
37
+ await Promise.all(
38
+ olds.map(async (w) => {
39
+ try {
40
+ await Promise.race([
41
+ w.terminate(),
42
+ new Promise<void>((resolve) => setTimeout(resolve, TERMINATE_TIMEOUT_MS)),
43
+ ])
44
+ } catch {
45
+ // Already-terminated rejections swallowed.
46
+ }
47
+ }),
48
+ )
49
+ }
50
+
51
+ /** Spawn `count` fresh Workers using the entry + env captured at
52
+ * registerInitialPool time. Each worker gets BRUST_WORKER_ID=i.
53
+ * Resolves only after every fresh worker reports `brust-worker-ready`
54
+ * via postMessage — so the caller (coordinator) doesn't broadcast
55
+ * `reload` against workers whose message listeners aren't installed
56
+ * yet. Falls back after a 5s grace if a worker never signals. */
57
+ export async function spawnAll(): Promise<void> {
58
+ if (state.entry === null || state.baseEnv === null) {
59
+ throw new Error('worker-registry: spawnAll called before registerInitialPool')
60
+ }
61
+ const fresh: Worker[] = []
62
+ const readies: Promise<void>[] = []
63
+ for (let i = 0; i < state.count; i++) {
64
+ const w = new Worker(state.entry, {
65
+ env: { ...state.baseEnv, BRUST_WORKER_ID: String(i) },
66
+ })
67
+ fresh.push(w)
68
+ readies.push(
69
+ new Promise<void>((resolve) => {
70
+ const onMsg = (e: MessageEvent) => {
71
+ const d: any = (e as any).data
72
+ if (d && d.type === 'brust-worker-ready') {
73
+ ;(w as any).removeEventListener?.('message', onMsg)
74
+ resolve()
75
+ }
76
+ }
77
+ ;(w as any).addEventListener?.('message', onMsg)
78
+ // Grace: don't hang forever if a worker crashes before reporting.
79
+ setTimeout(resolve, 5000)
80
+ }),
81
+ )
82
+ }
83
+ state.workers = fresh
84
+ await Promise.all(readies)
85
+ }
86
+
87
+ /** Test helper. */
88
+ export function _workersForTests(): Worker[] {
89
+ return [...state.workers]
90
+ }
91
+ export function _resetForTests(): void {
92
+ state.workers = []
93
+ state.entry = null
94
+ state.count = 0
95
+ state.baseEnv = null
96
+ }
@@ -0,0 +1,99 @@
1
+ import type { Route, WsHandlers, WsSocket } from '../routes.ts'
2
+
3
+ /** Server-to-client protocol. Client never sends after open. */
4
+ export type DevMessage =
5
+ | { type: 'building' }
6
+ | { type: 'reload' }
7
+ | { type: 'css-update'; href: string }
8
+ | { type: 'error'; message: string; stack?: string }
9
+ | { type: 'ok' }
10
+
11
+ // Worker-side state. WS connections to /_brust/dev are dispatched by Rust
12
+ // to a worker isolate, so this Set lives in that worker's module copy.
13
+ // Each worker has its own independent set; only the worker holding a
14
+ // given connection has it in `clients`.
15
+ const clients: Set<WsSocket> = new Set()
16
+
17
+ export function _clientCountForTests(): number {
18
+ return clients.size
19
+ }
20
+ export function _resetForTests(): void {
21
+ clients.clear()
22
+ }
23
+
24
+ /** Build the synthetic /_brust/dev WS route. brust.run() prepends this
25
+ * to opts.routes in dev mode (both main + worker route arrays). */
26
+ export function createDevWsRoute(): Route {
27
+ return {
28
+ path: '/_brust/dev',
29
+ websocket: () => Promise.resolve(devHandlers),
30
+ }
31
+ }
32
+
33
+ const devHandlers: WsHandlers = {
34
+ open(socket) {
35
+ clients.add(socket)
36
+ },
37
+ close(socket) {
38
+ clients.delete(socket)
39
+ },
40
+ message() {
41
+ /* ignore — server→client only */
42
+ },
43
+ }
44
+
45
+ /** Worker-side: forward JSON to every client connected to this worker. */
46
+ async function broadcastLocal(json: string): Promise<void> {
47
+ const sends: Promise<unknown>[] = []
48
+ for (const s of clients) {
49
+ sends.push(
50
+ s.send(json).catch(() => {
51
+ clients.delete(s)
52
+ }),
53
+ )
54
+ }
55
+ await Promise.all(sends)
56
+ }
57
+
58
+ let workerListenerInstalled = false
59
+
60
+ /** Worker-side: install a message listener that receives `dev-broadcast`
61
+ * envelopes from the main process and forwards them to this worker's
62
+ * local clients. Idempotent. After install, posts `brust-worker-ready`
63
+ * back to the parent so spawnAll() knows the worker is ready to relay
64
+ * broadcasts (avoids a race where `reload` is dispatched before the
65
+ * fresh worker's listener is wired). */
66
+ export function installWorkerBroadcastListener(): void {
67
+ if (workerListenerInstalled) return
68
+ workerListenerInstalled = true
69
+ ;(globalThis as any).addEventListener('message', (e: MessageEvent) => {
70
+ const data: any = (e as any).data
71
+ if (data && data.type === 'dev-broadcast' && typeof data.json === 'string') {
72
+ void broadcastLocal(data.json)
73
+ }
74
+ })
75
+ // Signal readiness to parent (main thread). In a Worker context,
76
+ // postMessage targets the parent.
77
+ try {
78
+ ;(globalThis as any).postMessage({ type: 'brust-worker-ready' })
79
+ } catch {
80
+ /* not in a worker (unit test); harmless */
81
+ }
82
+ }
83
+
84
+ /** Main-side: relay the message to every worker via postMessage. Each
85
+ * worker's installed message listener forwards to its local clients.
86
+ * Per-worker postMessage failures are swallowed (worker may be already
87
+ * terminating). */
88
+ export async function broadcast(msg: DevMessage): Promise<void> {
89
+ const json = JSON.stringify(msg)
90
+ const { _workersForTests } = await import('./worker-registry.ts')
91
+ const workers = _workersForTests()
92
+ for (const w of workers) {
93
+ try {
94
+ w.postMessage({ type: 'dev-broadcast', json })
95
+ } catch {
96
+ /* worker already terminated; drop */
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,199 @@
1
+ /* auto-generated by NAPI-RS */
2
+ /* eslint-disable */
3
+ export declare function beginServe(opts: ServeOptions): NapiResult<undefined>
4
+
5
+ export declare function configureCache(maxEntries: number): NapiResult<undefined>
6
+
7
+ export declare function configureCssDir(path: string): NapiResult<undefined>
8
+
9
+ export declare function configureIslandsDir(path: string): NapiResult<undefined>
10
+
11
+ export declare function isWorker(): boolean
12
+
13
+ /**
14
+ * Sub-project J — boot-time listing of registered minijinja templates.
15
+ * JS dispatcher uses this to validate every `native: true` route's
16
+ * `Component.name` is present (warns on mismatch per Reviewer Fix 1).
17
+ */
18
+ export declare function napiListNativeTemplates(): Array<string>
19
+
20
+ /**
21
+ * Sub-project J — boot-time loader for `.brust/jinja/*.jinja` templates.
22
+ * Returns the list of template names registered (each `<Name>.jinja` file
23
+ * stem becomes the lookup key). Lenient on missing/non-directory `dir`.
24
+ */
25
+ export declare function napiLoadJinjaTemplates(dir: string): Array<string>
26
+
27
+ /**
28
+ * Register literal SSE paths. Rust's accept loop will match incoming GET
29
+ * requests against this set; matched requests enter the SSE dispatch branch
30
+ * instead of the normal render path. Call once at boot before begin_serve.
31
+ * Only exact-match (literal) paths are supported — parameterized routes
32
+ * (e.g. `/sse/{room}`) require a matchit-rs follow-up.
33
+ */
34
+ export declare function napiRegisterSsePaths(paths: Array<string>): NapiResult<undefined>
35
+
36
+ /**
37
+ * Boot-time registry of literal WS paths. Mirror of napi_register_sse_paths.
38
+ * Call once before begin_serve; exact-match only (parameterized routes are a
39
+ * follow-up).
40
+ */
41
+ export declare function napiRegisterWsPaths(paths: Array<string>): NapiResult<undefined>
42
+
43
+ /**
44
+ * Worker-driven render chunk delivery. Worker calls this once per chunk
45
+ * it wants to emit; final call uses `len = 0` to close the channel.
46
+ *
47
+ * Contract (spec §5.2):
48
+ * - `len > 0`: read SAB[0..len], send Bytes through render_slot.chunk_tx,
49
+ * await ack. Resolves after Rust writes the chunk to the socket.
50
+ * - `len == 0`: send Final, await ack. Closes the response.
51
+ * - Bounds violation (len > buf_len) → NAPI Err.
52
+ * - Slot empty (no in-flight render for this worker) → NAPI Err.
53
+ * - Ack receiver dropped (handle_conn torn down mid-stream) → NAPI Err
54
+ * (NOT hang — worker's sink propagates via cb(err) to renderer Promise).
55
+ */
56
+ export declare function napiRenderChunk(workerId: number, len: number): Promise<NapiResult<undefined>>
57
+
58
+ /**
59
+ * Buffering-path finalizer: equivalent to `napi_render_chunk(_, len)` followed
60
+ * by `napi_render_chunk(_, 0)` but in a single tsfn crossing. Cuts JS-side
61
+ * per-request overhead by one full Promise+await cycle.
62
+ *
63
+ * Streaming-path callers MUST NOT use this — they send N body chunks then a
64
+ * separate `Final`. Calling this with `streaming=true` meta is logged at WARN
65
+ * on the Rust side and falls back to emitting chunked headers + framed body
66
+ * + chunked terminator (byte-equivalent to Bytes-then-Final in chunked mode).
67
+ *
68
+ * Same error semantics as `napi_render_chunk`.
69
+ */
70
+ export declare function napiRenderChunkFinal(workerId: number, len: number): Promise<NapiResult<undefined>>
71
+
72
+ /**
73
+ * Sub-project J — render via minijinja using SAB-side-channeled loader data.
74
+ *
75
+ * SAB convention (spec §6 last paragraph): per-napi-call. `napi_render_jinja`
76
+ * treats SAB[0..data_len] as INBOUND raw JSON written by the JS worker. It
77
+ * renders the template Rust-side, assembles a `[meta_len: u16 BE][meta JSON]
78
+ * [body]` payload, writes it back into the SAB, and returns its byte length —
79
+ * the FAST LANE. The JS native branch returns this length up to the tsfn, and
80
+ * the dispatch loop's fast-lane arm reads the framed bytes directly from the
81
+ * SAB and ships them via `build_single_response_bytes`. No chunk channel, no
82
+ * per-chunk ack round-trip.
83
+ *
84
+ * The render reads the inbound JSON into an owned `Vec` first, so overwriting
85
+ * the SAB with the assembled response afterward is safe (no aliasing).
86
+ *
87
+ * SYNCHRONOUS napi fn (not `async`): the body is pure CPU work (jinja render +
88
+ * SAB memcpy) with no `.await`, so it's a direct FFI call from the worker
89
+ * thread — no Promise, no microtask round-trip. This is what lets native
90
+ * routes reach the same crossing floor as actions: the only Rust↔JS hops left
91
+ * are the tsfn call and the render-Promise resolution.
92
+ *
93
+ * Errors:
94
+ * - worker id not registered → NAPI Err
95
+ * - `data_len > buf_len` → NAPI Err (caller should have written ≤ buf_len)
96
+ * - assembled response > buf_len → writes a small framed 500 that fits and
97
+ * returns ITS length, so the client gets a response instead of hanging.
98
+ * - `jinja::render` failure → writes a framed 500 into the SAB and returns its
99
+ * length (the protocol error is converted to an HTTP 500 on the wire).
100
+ */
101
+ export declare function napiRenderJinja(workerId: number, dataLen: number, templateName: string): NapiResult<number>
102
+
103
+ /**
104
+ * Drop the connection's sender, which signals the per-conn task to exit
105
+ * and close the TCP socket. Idempotent — a missing conn is a no-op.
106
+ */
107
+ export declare function napiSseClose(connId: bigint): NapiResult<undefined>
108
+
109
+ /**
110
+ * JS provides a callback that fires once when Rust detects client disconnect.
111
+ * Stored as a thread-safe wrapper on the SseConn.
112
+ */
113
+ export declare function napiSseRegisterAbort(connId: bigint, cb: () => void): NapiResult<undefined>
114
+
115
+ /**
116
+ * JS reports the middleware open verdict. Single-shot — a second call is
117
+ * dropped (open_tx is taken on first use).
118
+ */
119
+ export declare function napiSseSignalOpen(connId: bigint, status: number, body: Buffer, contentType: string): NapiResult<undefined>
120
+
121
+ /**
122
+ * Enqueue one SSE frame for the given connection. Returns a Promise that
123
+ * resolves when the Rust-side per-conn task has finished the TCP write —
124
+ * cooperative backpressure for the JS reader loop.
125
+ */
126
+ export declare function napiSseWrite(connId: bigint, bytes: Buffer): Promise<NapiResult<undefined>>
127
+
128
+ /**
129
+ * Initiate close. code defaults applied client-side (default 1000);
130
+ * reason capped at 123 bytes (RFC 6455) at the JS layer.
131
+ * Idempotent — missing conn is a silent no-op.
132
+ */
133
+ export declare function napiWsClose(connId: bigint, code: number, reason: string): Promise<NapiResult<undefined>>
134
+
135
+ /**
136
+ * JS registers per-conn callbacks. Each Function arg is converted to a tsfn
137
+ * then wrapped in a Box<dyn Fn> closure, matching the field shape Task 3
138
+ * chose to keep cargo test --lib happy without napi linker symbols.
139
+ *
140
+ * Uses struct-payload form (WsMessageArg / WsCloseArg) because napi-rs
141
+ * does not accept tuple type parameters for Function<(A, B), R>.
142
+ * Task 5 + Task 9 must use matching TS object shapes.
143
+ *
144
+ * Single-shot registration; a second call replaces both handlers.
145
+ */
146
+ export declare function napiWsRegisterHandlers(connId: bigint, onMessage: (arg: WsMessageArg) => void, onClose: (arg: WsCloseArg) => void): NapiResult<undefined>
147
+
148
+ /**
149
+ * Send one frame. is_binary=false → Text frame (UTF-8 validated before
150
+ * enqueue), true → Binary frame. Returns Promise<()> resolving after the
151
+ * TCP write completes — cooperative backpressure, mirrors napi_sse_write.
152
+ */
153
+ export declare function napiWsSend(connId: bigint, data: Buffer, isBinary: boolean): Promise<NapiResult<undefined>>
154
+
155
+ /**
156
+ * JS reports the middleware verdict + chosen subprotocol. Single-shot;
157
+ * second call is a no-op (the Option is taken).
158
+ */
159
+ export declare function napiWsSignalOpen(connId: bigint, status: number, body: Buffer, contentType: string, subprotocol: string): NapiResult<undefined>
160
+
161
+ /**
162
+ * Register the set of action ids that Rust will accept on
163
+ * /_brust/action/<id>. Called once at boot from the main thread.
164
+ * Validates charset and rejects duplicates. Replaces any previous set
165
+ * (no incremental registration in MVP — register once at boot).
166
+ */
167
+ export declare function registerActions(ids: Array<string>): NapiResult<number>
168
+
169
+ export declare function registerRenderer(buf: Uint8Array, f: (arg: number | string) => Promise<number>): NapiResult<number>
170
+
171
+ export declare function registerRoutes(configs: Array<string>): NapiResult<number>
172
+
173
+ export interface ServeOptions {
174
+ port: number
175
+ workers: number
176
+ entry: string
177
+ }
178
+
179
+ export declare function untilReady(timeoutMs: number): Promise<NapiResult<undefined>>
180
+
181
+ export declare function untilShutdown(): Promise<NapiResult<undefined>>
182
+
183
+ export declare function workerId(): number | null
184
+
185
+ /** Argument struct for the JS on_close callback. */
186
+ export interface WsCloseArg {
187
+ code: number
188
+ reason: string
189
+ }
190
+
191
+ /**
192
+ * Argument struct for the JS on_message callback. napi-rs does not accept
193
+ * tuple type parameters for Function<(A, B), R> so we use an object payload
194
+ * instead. Task 5 + Task 9 must construct a matching TS type.
195
+ */
196
+ export interface WsMessageArg {
197
+ data: Buffer
198
+ isBinary: boolean
199
+ }