@pyreon/runtime-server 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { VNode } from "@pyreon/core";
2
+
3
+ //#region src/index.d.ts
4
+ /**
5
+ * Wire up per-request store isolation.
6
+ * Call once at server startup, passing a `setStoreRegistryProvider` function.
7
+ *
8
+ * @example
9
+ * import { configureStoreIsolation } from "@pyreon/runtime-server"
10
+ * configureStoreIsolation(setStoreRegistryProvider)
11
+ */
12
+ declare function configureStoreIsolation(setStoreRegistryProvider: (fn: () => Map<string, unknown>) => void): void;
13
+ /** Render a VNode tree to an HTML string. Supports async component functions. */
14
+ declare function renderToString(root: VNode | null): Promise<string>;
15
+ /**
16
+ * Run an async function with a fresh, isolated context stack and store registry.
17
+ * Useful when you need to call Pyreon APIs (e.g. useHead, prefetchLoaderData)
18
+ * outside of renderToString but still want per-request isolation.
19
+ */
20
+ declare function runWithRequestContext<T>(fn: () => Promise<T>): Promise<T>;
21
+ /**
22
+ * Render a VNode tree to a Web-standard ReadableStream of HTML chunks.
23
+ *
24
+ * True progressive streaming: HTML is flushed to the client as soon as each
25
+ * node is ready. Synchronous subtrees are enqueued immediately; async component
26
+ * boundaries are awaited in-order and their output is enqueued as it resolves.
27
+ *
28
+ * Suspense boundaries are streamed out-of-order: the fallback is emitted
29
+ * immediately, and the resolved children are sent as a `<template>` + inline
30
+ * swap script once ready — without blocking the rest of the page.
31
+ *
32
+ * Each renderToStream call gets its own isolated ALS context stack.
33
+ */
34
+ declare function renderToStream(root: VNode | null): ReadableStream<string>;
35
+ //#endregion
36
+ export { configureStoreIsolation, renderToStream, renderToString, runWithRequestContext };
37
+ //# sourceMappingURL=index2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;;;;;;AAiFA;;iBA3BgB,uBAAA,CACd,wBAAA,GAA2B,EAAA,QAAU,GAAA;;iBAejB,cAAA,CAAe,IAAA,EAAM,KAAA,UAAe,OAAA;;;;;;iBAW1C,qBAAA,GAAA,CAAyB,EAAA,QAAU,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;;;;;;;AAiBxE;;;;;;;iBAAgB,cAAA,CAAe,IAAA,EAAM,KAAA,UAAe,cAAA"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@pyreon/runtime-server",
3
+ "version": "0.1.0",
4
+ "description": "SSR/SSG renderer for Pyreon — streaming HTML + static generation",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/pyreon/pyreon.git",
9
+ "directory": "packages/runtime-server"
10
+ },
11
+ "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/runtime-server#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/pyreon/pyreon/issues"
14
+ },
15
+ "files": [
16
+ "lib",
17
+ "src",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "sideEffects": false,
22
+ "type": "module",
23
+ "main": "./lib/index.js",
24
+ "module": "./lib/index.js",
25
+ "types": "./lib/types/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "bun": "./src/index.ts",
29
+ "import": "./lib/index.js",
30
+ "types": "./lib/types/index.d.ts"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build": "vl_rolldown_build",
35
+ "dev": "vl_rolldown_build-watch",
36
+ "test": "vitest run",
37
+ "typecheck": "tsc --noEmit",
38
+ "prepublishOnly": "bun run build"
39
+ },
40
+ "dependencies": {
41
+ "@pyreon/core": "workspace:*",
42
+ "@pyreon/reactivity": "workspace:*"
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,460 @@
1
+ /**
2
+ * @pyreon/runtime-server — SSR/SSG renderer for Pyreon.
3
+ *
4
+ * Walks a VNode tree and produces HTML strings.
5
+ * Signal accessors (reactive getters `() => value`) are called synchronously
6
+ * to snapshot their current value — no effects are set up on the server.
7
+ *
8
+ * Async components (`async function Component()`) are fully supported:
9
+ * renderToString will await them before continuing the tree walk.
10
+ *
11
+ * API:
12
+ * renderToString(vnode) → Promise<string>
13
+ * renderToStream(vnode) → ReadableStream<string>
14
+ */
15
+
16
+ import { AsyncLocalStorage } from "node:async_hooks"
17
+ import type { ComponentFn, ForProps, VNode, VNodeChild } from "@pyreon/core"
18
+ import { ForSymbol, Fragment, runWithHooks, Suspense, setContextStackProvider } from "@pyreon/core"
19
+
20
+ // ─── Streaming Suspense context ───────────────────────────────────────────────
21
+ // Tracks in-flight async Suspense boundary resolutions within a single stream.
22
+
23
+ interface StreamCtx {
24
+ pending: Promise<void>[]
25
+ nextId: () => number
26
+ mainEnqueue: (s: string) => void
27
+ }
28
+
29
+ const _streamCtxAls = new AsyncLocalStorage<StreamCtx>()
30
+
31
+ // ─── Concurrent SSR context isolation ────────────────────────────────────────
32
+ // Each renderToString call runs in its own ALS store (a fresh empty stack[]).
33
+ // Concurrent requests never share context frames.
34
+
35
+ const _contextAls = new AsyncLocalStorage<Map<symbol, unknown>[]>()
36
+ const _fallbackStack: Map<symbol, unknown>[] = []
37
+
38
+ setContextStackProvider(() => _contextAls.getStore() ?? _fallbackStack)
39
+
40
+ // ─── Store isolation (optional) ───────────────────────────────────────────────
41
+ // A second ALS isolates store registries between concurrent requests.
42
+ // Activated only when the user calls configureStoreIsolation().
43
+
44
+ const _storeAls = new AsyncLocalStorage<Map<string, unknown>>()
45
+ let _storeIsolationActive = false
46
+
47
+ /**
48
+ * Wire up per-request store isolation.
49
+ * Call once at server startup, passing a `setStoreRegistryProvider` function.
50
+ *
51
+ * @example
52
+ * import { configureStoreIsolation } from "@pyreon/runtime-server"
53
+ * configureStoreIsolation(setStoreRegistryProvider)
54
+ */
55
+ export function configureStoreIsolation(
56
+ setStoreRegistryProvider: (fn: () => Map<string, unknown>) => void,
57
+ ): void {
58
+ setStoreRegistryProvider(() => _storeAls.getStore() ?? new Map())
59
+ _storeIsolationActive = true
60
+ }
61
+
62
+ /** Wrap a function call in a fresh store registry (no-op if isolation not configured). */
63
+ function withStoreContext<T>(fn: () => T): T {
64
+ if (!_storeIsolationActive) return fn()
65
+ return _storeAls.run(new Map(), fn)
66
+ }
67
+
68
+ // ─── Public API ───────────────────────────────────────────────────────────────
69
+
70
+ /** Render a VNode tree to an HTML string. Supports async component functions. */
71
+ export async function renderToString(root: VNode | null): Promise<string> {
72
+ if (root === null) return ""
73
+ // Each call gets a fresh isolated context stack and (optionally) store registry
74
+ return withStoreContext(() => _contextAls.run([], () => renderNode(root)))
75
+ }
76
+
77
+ /**
78
+ * Run an async function with a fresh, isolated context stack and store registry.
79
+ * Useful when you need to call Pyreon APIs (e.g. useHead, prefetchLoaderData)
80
+ * outside of renderToString but still want per-request isolation.
81
+ */
82
+ export function runWithRequestContext<T>(fn: () => Promise<T>): Promise<T> {
83
+ return withStoreContext(() => _contextAls.run([], fn))
84
+ }
85
+
86
+ /**
87
+ * Render a VNode tree to a Web-standard ReadableStream of HTML chunks.
88
+ *
89
+ * True progressive streaming: HTML is flushed to the client as soon as each
90
+ * node is ready. Synchronous subtrees are enqueued immediately; async component
91
+ * boundaries are awaited in-order and their output is enqueued as it resolves.
92
+ *
93
+ * Suspense boundaries are streamed out-of-order: the fallback is emitted
94
+ * immediately, and the resolved children are sent as a `<template>` + inline
95
+ * swap script once ready — without blocking the rest of the page.
96
+ *
97
+ * Each renderToStream call gets its own isolated ALS context stack.
98
+ */
99
+ export function renderToStream(root: VNode | null): ReadableStream<string> {
100
+ return new ReadableStream<string>({
101
+ start(controller) {
102
+ const enqueue = (chunk: string) => controller.enqueue(chunk)
103
+ let bid = 0
104
+ const ctx: StreamCtx = {
105
+ pending: [],
106
+ nextId: () => bid++,
107
+ mainEnqueue: enqueue,
108
+ }
109
+ return withStoreContext(() =>
110
+ _contextAls.run([], () =>
111
+ _streamCtxAls
112
+ .run(ctx, async () => {
113
+ await streamNode(root, enqueue)
114
+ // Drain all pending Suspense resolutions (may spawn nested ones)
115
+ while (ctx.pending.length > 0) {
116
+ await Promise.all(ctx.pending.splice(0))
117
+ }
118
+ controller.close()
119
+ })
120
+ .catch((err) => controller.error(err)),
121
+ ),
122
+ )
123
+ },
124
+ })
125
+ }
126
+
127
+ // ─── Streaming renderer ───────────────────────────────────────────────────────
128
+
129
+ async function streamVNode(vnode: VNode, enqueue: (s: string) => void): Promise<void> {
130
+ if (vnode.type === Fragment) {
131
+ for (const child of vnode.children) await streamNode(child, enqueue)
132
+ return
133
+ }
134
+
135
+ if (vnode.type === (ForSymbol as unknown as string)) {
136
+ const { each, children } = vnode.props as unknown as ForProps<unknown>
137
+ enqueue("<!--pyreon-for-->")
138
+ for (const item of each()) await streamNode(children(item) as VNodeChild, enqueue)
139
+ enqueue("<!--/pyreon-for-->")
140
+ return
141
+ }
142
+
143
+ if (typeof vnode.type === "function") {
144
+ await streamComponentNode(vnode, enqueue)
145
+ return
146
+ }
147
+
148
+ await streamElementNode(vnode, enqueue)
149
+ }
150
+
151
+ async function streamComponentNode(vnode: VNode, enqueue: (s: string) => void): Promise<void> {
152
+ if (vnode.type === Suspense) {
153
+ await streamSuspenseBoundary(vnode, enqueue)
154
+ return
155
+ }
156
+ const { vnode: output } = runWithHooks(vnode.type as ComponentFn, mergeChildrenIntoProps(vnode))
157
+ const resolved = output instanceof Promise ? await output : output
158
+ if (resolved !== null) await streamNode(resolved, enqueue)
159
+ }
160
+
161
+ async function streamElementNode(vnode: VNode, enqueue: (s: string) => void): Promise<void> {
162
+ const tag = vnode.type as string
163
+ let open = `<${tag}`
164
+ for (const [key, value] of Object.entries(vnode.props)) {
165
+ const attr = renderProp(key, value)
166
+ if (attr) open += ` ${attr}`
167
+ }
168
+ if (isVoidElement(tag)) {
169
+ enqueue(`${open} />`)
170
+ return
171
+ }
172
+ enqueue(`${open}>`)
173
+ for (const child of vnode.children) await streamNode(child, enqueue)
174
+ enqueue(`</${tag}>`)
175
+ }
176
+
177
+ async function streamNode(
178
+ node: VNodeChild | null | (() => VNodeChild),
179
+ enqueue: (s: string) => void,
180
+ ): Promise<void> {
181
+ if (typeof node === "function") {
182
+ return streamNode((node as () => VNodeChild)(), enqueue)
183
+ }
184
+ if (node == null || node === false) return
185
+ if (typeof node === "string") {
186
+ enqueue(escapeHtml(node))
187
+ return
188
+ }
189
+ if (typeof node === "number" || typeof node === "boolean") {
190
+ enqueue(String(node))
191
+ return
192
+ }
193
+ if (Array.isArray(node)) {
194
+ for (const child of node) await streamNode(child, enqueue)
195
+ return
196
+ }
197
+
198
+ await streamVNode(node as VNode, enqueue)
199
+ }
200
+
201
+ // Inline swap helper emitted once per stream, before the first <template>
202
+ const SUSPENSE_SWAP_FN =
203
+ "<script>function __NS(s,t){var e=document.getElementById(s),l=document.getElementById(t);" +
204
+ "if(e&&l){e.replaceWith(l.content.cloneNode(!0));l.remove()}}</script>"
205
+
206
+ /**
207
+ * Stream a Suspense boundary: emit fallback immediately, then resolve children
208
+ * asynchronously and emit them as a `<template>` + client-side swap.
209
+ *
210
+ * The actual children HTML is buffered until fully resolved, then emitted to the
211
+ * main stream enqueue so it always arrives after the fallback placeholder.
212
+ */
213
+ async function streamSuspenseBoundary(vnode: VNode, enqueue: (s: string) => void): Promise<void> {
214
+ const ctx = _streamCtxAls.getStore()
215
+ const { fallback, children } = vnode.props as { fallback: VNodeChild; children?: VNodeChild }
216
+
217
+ // No streaming context (e.g. called from renderToString) — render children inline
218
+ if (!ctx) {
219
+ const { vnode: output } = runWithHooks(Suspense as ComponentFn, vnode.props)
220
+ if (output !== null) await streamNode(output, enqueue)
221
+ return
222
+ }
223
+
224
+ const id = ctx.nextId()
225
+ const { mainEnqueue } = ctx
226
+
227
+ // Emit the swap helper function once (before first use)
228
+ if (id === 0) mainEnqueue(SUSPENSE_SWAP_FN)
229
+
230
+ // Stream the fallback synchronously (no await on children)
231
+ mainEnqueue(`<div id="pyreon-s-${id}">`)
232
+ await streamNode(fallback ?? null, enqueue)
233
+ mainEnqueue("</div>")
234
+
235
+ // Capture the context store for the async resolution so it inherits context
236
+ const ctxStore = _contextAls.getStore() ?? []
237
+
238
+ // Queue async resolution — runs in parallel, emits to main stream when done
239
+ // Errors are caught per-boundary so one failing Suspense doesn't abort the stream.
240
+ ctx.pending.push(
241
+ _contextAls.run(ctxStore, async () => {
242
+ try {
243
+ const buf: string[] = []
244
+ await streamNode(children ?? null, (s) => buf.push(s))
245
+ mainEnqueue(`<template id="pyreon-t-${id}">${buf.join("")}</template>`)
246
+ mainEnqueue(`<script>__NS("pyreon-s-${id}","pyreon-t-${id}")</script>`)
247
+ } catch (_err) {
248
+ // Fallback stays visible — no swap script emitted
249
+ }
250
+ }),
251
+ )
252
+ }
253
+
254
+ // ─── Core renderer ───────────────────────────────────────────────────────────
255
+
256
+ async function renderNode(node: VNodeChild | (() => VNodeChild)): Promise<string> {
257
+ // Reactive accessor — call it synchronously (snapshot)
258
+ if (typeof node === "function") {
259
+ return renderNode((node as () => VNodeChild)())
260
+ }
261
+
262
+ if (node == null || node === false) return ""
263
+
264
+ if (typeof node === "string") return escapeHtml(node)
265
+ if (typeof node === "number" || typeof node === "boolean") return String(node)
266
+
267
+ if (Array.isArray(node)) {
268
+ const parts = await Promise.all(node.map((n) => renderNode(n)))
269
+ return parts.join("")
270
+ }
271
+
272
+ const vnode = node as VNode
273
+
274
+ if (vnode.type === Fragment) {
275
+ return renderChildren(vnode.children)
276
+ }
277
+
278
+ if (vnode.type === (ForSymbol as unknown as string)) {
279
+ const { each, children } = vnode.props as unknown as ForProps<unknown>
280
+ const parts = await Promise.all(each().map((item) => renderNode(children(item) as VNodeChild)))
281
+ // Hydration markers so the client can claim existing For-rendered children
282
+ return `<!--pyreon-for-->${parts.join("")}<!--/pyreon-for-->`
283
+ }
284
+
285
+ if (typeof vnode.type === "function") {
286
+ return renderComponent(vnode as VNode & { type: ComponentFn })
287
+ }
288
+
289
+ return renderElement(vnode)
290
+ }
291
+
292
+ async function renderChildren(children: VNodeChild[]): Promise<string> {
293
+ const parts = await Promise.all(children.map((c) => renderNode(c)))
294
+ return parts.join("")
295
+ }
296
+
297
+ async function renderComponent(vnode: VNode & { type: ComponentFn }): Promise<string> {
298
+ const { vnode: output } = runWithHooks(vnode.type, mergeChildrenIntoProps(vnode))
299
+
300
+ // Async component function (async function Component()) — await the promise
301
+ if (output instanceof Promise) {
302
+ const resolved = await output
303
+ if (resolved === null) return ""
304
+ return renderNode(resolved)
305
+ }
306
+
307
+ if (output === null) return ""
308
+ return renderNode(output)
309
+ }
310
+
311
+ async function renderElement(vnode: VNode): Promise<string> {
312
+ const tag = vnode.type as string
313
+ let html = `<${tag}`
314
+
315
+ for (const [key, value] of Object.entries(vnode.props)) {
316
+ const attr = renderProp(key, value)
317
+ if (attr) html += ` ${attr}`
318
+ }
319
+
320
+ if (isVoidElement(tag)) {
321
+ html += " />"
322
+ return html
323
+ }
324
+
325
+ html += ">"
326
+
327
+ for (const child of vnode.children) {
328
+ html += await renderNode(child)
329
+ }
330
+
331
+ html += `</${tag}>`
332
+ return html
333
+ }
334
+
335
+ const SSR_URL_ATTRS = new Set(["href", "src", "action", "formaction", "poster", "cite", "data"])
336
+ const SSR_UNSAFE_URL_RE = /^\s*(?:javascript|data):/i
337
+
338
+ function renderPropSkipped(key: string): boolean {
339
+ if (key === "key" || key === "ref" || key === "n-show") return true
340
+ if (key.startsWith("n-")) return true
341
+ if (/^on[A-Z]/.test(key)) return true
342
+ return false
343
+ }
344
+
345
+ function renderPropValue(key: string, value: unknown): string | null {
346
+ if (value === null || value === undefined || value === false) return null
347
+ if (value === true) return escapeHtml(toAttrName(key))
348
+
349
+ if (key === "class") {
350
+ const cls = normalizeClass(value)
351
+ return cls ? `class="${escapeHtml(cls)}"` : null
352
+ }
353
+
354
+ if (key === "style") {
355
+ const style = normalizeStyle(value)
356
+ return style ? `style="${escapeHtml(style)}"` : null
357
+ }
358
+
359
+ return `${escapeHtml(toAttrName(key))}="${escapeHtml(String(value))}"`
360
+ }
361
+
362
+ function renderProp(key: string, value: unknown): string | null {
363
+ if (renderPropSkipped(key)) return null
364
+
365
+ if (typeof value === "function") {
366
+ return renderProp(key, (value as () => unknown)())
367
+ }
368
+
369
+ if (SSR_URL_ATTRS.has(key) && typeof value === "string" && SSR_UNSAFE_URL_RE.test(value)) {
370
+ return null
371
+ }
372
+
373
+ return renderPropValue(key, value)
374
+ }
375
+
376
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
377
+
378
+ const VOID_ELEMENTS = new Set([
379
+ "area",
380
+ "base",
381
+ "br",
382
+ "col",
383
+ "embed",
384
+ "hr",
385
+ "img",
386
+ "input",
387
+ "link",
388
+ "meta",
389
+ "param",
390
+ "source",
391
+ "track",
392
+ "wbr",
393
+ ])
394
+
395
+ function isVoidElement(tag: string): boolean {
396
+ return VOID_ELEMENTS.has(tag.toLowerCase())
397
+ }
398
+
399
+ /** camelCase prop → kebab-case HTML attribute (e.g. className → class, htmlFor → for) */
400
+ function toAttrName(key: string): string {
401
+ if (key === "className") return "class"
402
+ if (key === "htmlFor") return "for"
403
+ return key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
404
+ }
405
+
406
+ function normalizeClass(value: unknown): string {
407
+ if (typeof value === "string") return value
408
+ if (Array.isArray(value)) return value.filter(Boolean).join(" ")
409
+ if (typeof value === "object" && value !== null) {
410
+ return Object.entries(value as Record<string, unknown>)
411
+ .filter(([, v]) => v)
412
+ .map(([k]) => k)
413
+ .join(" ")
414
+ }
415
+ return ""
416
+ }
417
+
418
+ function normalizeStyle(value: unknown): string {
419
+ if (typeof value === "string") return value
420
+ if (typeof value === "object" && value !== null) {
421
+ return Object.entries(value as Record<string, unknown>)
422
+ .map(([k, v]) => `${toKebab(k)}: ${v}`)
423
+ .join("; ")
424
+ }
425
+ return ""
426
+ }
427
+
428
+ function toKebab(str: string): string {
429
+ return str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
430
+ }
431
+
432
+ const ESCAPE_MAP: Record<string, string> = {
433
+ "&": "&amp;",
434
+ "<": "&lt;",
435
+ ">": "&gt;",
436
+ '"': "&quot;",
437
+ "'": "&#39;",
438
+ }
439
+
440
+ function escapeHtml(str: string): string {
441
+ return str.replace(/[&<>"']/g, (c) => ESCAPE_MAP[c] ?? c)
442
+ }
443
+
444
+ /**
445
+ * Merge vnode.children into props.children for component rendering.
446
+ * Matches the behavior of mount.ts and hydrate.ts so components can
447
+ * access children passed via h(Comp, props, child1, child2).
448
+ */
449
+ function mergeChildrenIntoProps(vnode: VNode): Record<string, unknown> {
450
+ if (
451
+ vnode.children.length > 0 &&
452
+ (vnode.props as Record<string, unknown>).children === undefined
453
+ ) {
454
+ return {
455
+ ...vnode.props,
456
+ children: vnode.children.length === 1 ? vnode.children[0] : vnode.children,
457
+ }
458
+ }
459
+ return vnode.props as Record<string, unknown>
460
+ }