@torpor/view 1.0.3 → 1.1.1

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/dist/ssr.cjs CHANGED
@@ -22,14 +22,124 @@ function formatServerText(value) {
22
22
  return require_fromWebSocket.formatText(value).replace(/[&<>]/g, (c) => ESCAPABLE[c]);
23
23
  }
24
24
  //#endregion
25
- //#region src/ssr/$serverAsync.ts
25
+ //#region src/ssr/serverAwaitState.ts
26
26
  /**
27
- * Server no-op for the client-side `$async` getter primitive. Server renders
28
- * are synchronous, so an async value never resolves during SSR — `@await`
27
+ * The sentinel thrown when the render pass hits a boundary state that the
28
+ * collect pass didn't create (or a cursor that ran past the recorded reads).
29
+ * Only this error degrades the boundary; real render errors propagate.
30
+ */
31
+ var ServerAwaitMisaligned = class extends Error {
32
+ constructor() {
33
+ super("Server await render pass did not match the collect pass");
34
+ }
35
+ };
36
+ /** The innermost active server await state, or null outside any. */
37
+ let current = null;
38
+ /**
39
+ * Runs `fn` with `state` as the current server await state. `fn` may be sync
40
+ * or async; the pointer is restored when it settles. Because the pointer is
41
+ * only read synchronously, interleaved async renders restore it correctly.
42
+ */
43
+ async function withServerAwait(state, fn) {
44
+ const previous = current;
45
+ current = state;
46
+ try {
47
+ return await fn();
48
+ } finally {
49
+ current = previous;
50
+ }
51
+ }
52
+ /** The current server await state, or null. Only read synchronously. */
53
+ function currentServerAwait() {
54
+ return current;
55
+ }
56
+ /** Creates a fresh state for one boundary (or component flush root). */
57
+ function newServerAwaitState() {
58
+ return {
59
+ phase: "collect",
60
+ promises: [],
61
+ cursor: 0,
62
+ renderReads: 0,
63
+ clientReads: 0,
64
+ children: /* @__PURE__ */ new Map(),
65
+ counters: /* @__PURE__ */ new Map(),
66
+ degraded: false,
67
+ isRoot: false,
68
+ sentinels: /* @__PURE__ */ new Map()
69
+ };
70
+ }
71
+ /**
72
+ * The occurrence key for a nested boundary call site: `"<id>:<n>"`. The
73
+ * counter lives on the enclosing state and resets each pass, so the collect
74
+ * and render passes derive identical key sequences.
75
+ */
76
+ function childKey(state, id) {
77
+ const n = state.counters.get(id) ?? 0;
78
+ state.counters.set(id, n + 1);
79
+ return `${id}:${n}`;
80
+ }
81
+ /**
82
+ * Server counterpart of the client-side `$async` getter primitive.
83
+ *
84
+ * Without options (the default), this is a no-op stub: server renders are
85
+ * synchronous, an async value never resolves during SSR, and `@await`
29
86
  * boundaries render their `with` branch and never read the getter. The stub
30
87
  * exists so server builds importing `$async` don't emit a dangling import.
88
+ * A read inside a boundary's render pass is still recorded, so the boundary
89
+ * degrades to the `with` branch + client fetch instead of shipping content
90
+ * that would suspend on the client.
91
+ *
92
+ * With `{ source: "server" }` the getter participates in the server's
93
+ * collect/render pass (ASYNC.md → "Server rendering"):
94
+ *
95
+ * - **Collect pass** — calls the thunk (starting the fetch) and records the
96
+ * promise on the enclosing boundary, so sibling reads start in one wave.
97
+ * Returns `undefined`; the speculative pass's output is discarded.
98
+ * - **Render pass** — returns the settled value from the recorded promise at
99
+ * the same position in read order, without calling the thunk again. A
100
+ * rejection is re-thrown here so `@try`/`@error` handles it like any other
101
+ * render error. Running past the recorded reads throws a sentinel that
102
+ * degrades the boundary (the render pass no longer matches the collect
103
+ * pass, so the shipped HTML can't be trusted).
104
+ *
105
+ * Outside any `@await` boundary (including in a `with` branch) a
106
+ * `source: "server"` read returns `undefined` without fetching — the same
107
+ * undefined-then-recover contract the client has for un-boundaried reads.
31
108
  */
32
- function $serverAsync(_fn) {}
109
+ function $serverAsync(fn, options) {
110
+ const state = currentServerAwait();
111
+ if (state === null || state.isRoot || options?.source !== "server") {
112
+ if (state !== null && !state.isRoot && state.phase === "render") state.clientReads++;
113
+ return;
114
+ }
115
+ if (state.phase === "collect") {
116
+ const entry = {
117
+ promise: fn(),
118
+ timeout: options.timeout ?? 5e3
119
+ };
120
+ entry.promise.then((value) => {
121
+ entry.result = {
122
+ ok: true,
123
+ value
124
+ };
125
+ }, (error) => {
126
+ entry.result = {
127
+ ok: false,
128
+ error
129
+ };
130
+ });
131
+ state.promises.push(entry);
132
+ return;
133
+ }
134
+ const index = state.cursor;
135
+ if (index >= state.promises.length) throw new ServerAwaitMisaligned();
136
+ state.cursor++;
137
+ state.renderReads++;
138
+ const result = state.promises[index].result;
139
+ if (result === void 0) throw new ServerAwaitMisaligned();
140
+ if (result.ok) return result.value;
141
+ throw result.error;
142
+ }
33
143
  //#endregion
34
144
  //#region src/ssr/$serverBatch.ts
35
145
  function $serverBatch(fn) {
@@ -96,6 +206,182 @@ function $serverUnwrap(object) {
96
206
  function $serverWatch(object) {
97
207
  return object;
98
208
  }
209
+ /**
210
+ * Renders a component's (or a degraded boundary's `with` branch's) markup
211
+ * with a fresh flush root as the enclosing await state, then substitutes any
212
+ * boundary sentinels with their final HTML.
213
+ *
214
+ * Boundaries created during the render stash their lifecycle promise under a
215
+ * sentinel comment and return the sentinel immediately, so the render itself
216
+ * never blocks on a fetch — every `source: "server"` read in the render
217
+ * starts in the same wave. Substitution awaits the stashed promises at the
218
+ * end, which is the only point an async component render actually waits.
219
+ *
220
+ * On the client this has no counterpart: `render` here is the compiled
221
+ * component body closure that the generated component function returns.
222
+ */
223
+ async function serverFlush(render) {
224
+ const state = newServerAwaitState();
225
+ state.isRoot = true;
226
+ return substituteSentinels(await withServerAwait(state, render), state);
227
+ }
228
+ /**
229
+ * Renders a degraded boundary's `with` branch inside its own flush root, so
230
+ * a `with` branch containing its own `@await` boundaries still resolves
231
+ * them, then wraps it in the boundary's hydration markers. The `with` branch
232
+ * must not start server fetches for its own reads — the flush root isn't a
233
+ * recording context.
234
+ */
235
+ async function renderWithBranch(renderWith) {
236
+ const html = await serverFlush(async () => {
237
+ if (renderWith === null) return {
238
+ body: "",
239
+ head: ""
240
+ };
241
+ return renderWith();
242
+ });
243
+ return {
244
+ body: `<![>${html.body}<!]><!>`,
245
+ head: html.head
246
+ };
247
+ }
248
+ /**
249
+ * Replaces every sentinel the state stashed with its boundary's final HTML.
250
+ * Boundary head tags are appended to the head (they hoist into the document
251
+ * head regardless of where the boundary sits in the body). Any sentinel left
252
+ * over means a boundary's state was lost — a bug, not a degradation path, so
253
+ * it throws.
254
+ */
255
+ async function substituteSentinels(result, state) {
256
+ let { body, head } = result;
257
+ for (const [sentinel, promise] of state.sentinels) {
258
+ const html = await promise;
259
+ body = body.split(sentinel).join(html.body);
260
+ head += html.head;
261
+ }
262
+ if (body.includes("<!--t-aw:") || head.includes("<!--t-aw:")) throw new Error("Unsubstituted async boundary sentinel — boundary state was lost");
263
+ return {
264
+ body,
265
+ head
266
+ };
267
+ }
268
+ //#endregion
269
+ //#region src/ssr/runServerAwait.ts
270
+ let sentinelCount = 0;
271
+ function nextSentinel() {
272
+ return `<!--t-aw:${sentinelCount++}-->`;
273
+ }
274
+ /**
275
+ * Renders an `@await` boundary on the server (compiled as `t_await_server`).
276
+ *
277
+ * The generated code calls this for every `@await`, passing the boundary's
278
+ * site id, a closure that renders the content branch, and one that renders
279
+ * the `with` branch. It always returns a `{ body, head }` pair to append:
280
+ *
281
+ * - **Inside a boundary's render pass** — this boundary was already started
282
+ * during the collect pass; its body/head is the finished HTML (awaited if
283
+ * still in flight). A missing entry means the render pass doesn't match
284
+ * the collect pass, which degrades the enclosing boundary.
285
+ * - **Inside a collect pass or a component flush** — start the full
286
+ * lifecycle (collect, settle, render) without blocking the enclosing
287
+ * render, and return a sentinel comment as the body. Sibling reads and
288
+ * nested boundaries keep rendering, so every fetch in the pass starts in
289
+ * one wave. The sentinel is substituted later: by the enclosing boundary's
290
+ * render pass (which re-invokes this helper and awaits the finished HTML),
291
+ * or by the component flush (which replaces top-level sentinels directly).
292
+ *
293
+ * The lifecycle: render content once to start the `source: "server"` fetches
294
+ * (discarding the output), wait for them with a per-getter timeout, then
295
+ * render again with the settled values. The second pass ships wrapped in
296
+ * hydration markers plus a `<!--t-await:...-->` comment carrying the values
297
+ * for the client's `$async` hydration path. Any instability — timeout, a
298
+ * render pass that reads a different set of getters, a client-fetch read —
299
+ * degrades to the `with` branch + client fetch, exactly the pre-`source`
300
+ * behavior.
301
+ *
302
+ * @param id The boundary's site id (unique per `@await` in the component)
303
+ * @param renderContent Renders the content branch (may read `$async` getters)
304
+ * @param renderWith Renders the `with` branch, or null for empty
305
+ */
306
+ function runServerAwait(id, renderContent, renderWith) {
307
+ const parent = currentServerAwait();
308
+ if (parent !== null && parent.phase === "render") {
309
+ const cached = parent.children.get(childKey(parent, id));
310
+ if (cached === void 0) throw new ServerAwaitMisaligned();
311
+ return cached;
312
+ }
313
+ const final = renderBoundary(newServerAwaitState(), renderContent, renderWith);
314
+ const sentinel = nextSentinel();
315
+ if (parent !== null) {
316
+ if (parent.isRoot) parent.sentinels.set(sentinel, final);
317
+ else parent.children.set(childKey(parent, id), final);
318
+ }
319
+ return {
320
+ body: sentinel,
321
+ head: ""
322
+ };
323
+ }
324
+ /**
325
+ * The full lifecycle for one boundary: speculative collect pass, settle, and
326
+ * resolved render pass. Runs concurrently with the enclosing render; the
327
+ * returned promise resolves to the boundary's final HTML.
328
+ */
329
+ async function renderBoundary(state, renderContent, renderWith) {
330
+ await withServerAwait(state, renderContent).catch(() => {});
331
+ if (state.promises.length === 0) return renderWithBranch(renderWith);
332
+ const timeout = Math.max(...state.promises.map((entry) => entry.timeout));
333
+ let degraded = await Promise.race([Promise.all(state.promises.map((entry) => entry.promise.then(() => true, () => true))).then(() => true, () => true), new Promise((resolve) => setTimeout(resolve, timeout, false))]) === false;
334
+ let content;
335
+ if (!degraded) {
336
+ state.phase = "render";
337
+ state.cursor = 0;
338
+ state.renderReads = 0;
339
+ state.clientReads = 0;
340
+ state.counters.clear();
341
+ try {
342
+ content = await withServerAwait(state, renderContent);
343
+ if (state.cursor !== state.promises.length || state.clientReads > 0) degraded = true;
344
+ } catch {
345
+ degraded = true;
346
+ }
347
+ }
348
+ if (degraded || content === void 0) {
349
+ state.degraded = true;
350
+ try {
351
+ return await renderWithBranch(renderWith);
352
+ } catch {
353
+ return {
354
+ body: "<![><!]><!>",
355
+ head: ""
356
+ };
357
+ }
358
+ }
359
+ const values = [];
360
+ for (let i = 0; i < state.cursor; i++) {
361
+ const result = state.promises[i].result;
362
+ if (result === void 0) {
363
+ degraded = true;
364
+ break;
365
+ }
366
+ values.push(result.ok ? result.value : void 0);
367
+ }
368
+ if (degraded) {
369
+ state.degraded = true;
370
+ return renderWithBranch(renderWith);
371
+ }
372
+ return {
373
+ body: `<![>${content.body}<!]><!>${payloadComment(values)}`,
374
+ head: content.head
375
+ };
376
+ }
377
+ /**
378
+ * The `<!--t-await:...-->` comment carrying the boundary's resolved values,
379
+ * in read order, for the client's `$async` hydration path. `-->` inside the
380
+ * JSON is escaped so the comment can't terminate early.
381
+ */
382
+ function payloadComment(values) {
383
+ return `<!--t-await:${JSON.stringify(values).replaceAll("-->", "--\\u003E")}-->`;
384
+ }
99
385
  //#endregion
100
386
  exports.$async = $serverAsync;
101
387
  exports.$batch = $serverBatch;
@@ -113,6 +399,8 @@ exports.fromElement = require_fromWebSocket.fromElement;
113
399
  exports.fromServer = require_fromWebSocket.fromServer;
114
400
  exports.fromWebSocket = require_fromWebSocket.fromWebSocket;
115
401
  exports.t_attr = formatAttributeText;
402
+ exports.t_await_server = runServerAwait;
116
403
  exports.t_class = require_fromWebSocket.buildClasses;
117
404
  exports.t_fmt = formatServerText;
405
+ exports.t_server_flush = serverFlush;
118
406
  exports.t_style = require_fromWebSocket.buildStyles;
package/dist/ssr.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as Cleanup } from "./Cleanup-D0wvGW5d.cjs";
2
- import { a as buildStyles, i as StreamSource, n as fromServer, r as fromElement, s as buildClasses, t as fromWebSocket } from "./fromWebSocket-DeB35Q4w.cjs";
2
+ import { a as StreamSource, c as buildClasses, i as fromElement, n as fromWebSocket, o as buildStyles, r as fromServer, t as AsyncOptions } from "./AsyncOptions-BtMe2PAd.cjs";
3
3
  //#region src/render/formatAttributeText.d.ts
4
4
  declare function formatAttributeText(value: any): string;
5
5
  //#endregion
@@ -15,12 +15,34 @@ declare function formatServerText(value: any): string;
15
15
  //#endregion
16
16
  //#region src/ssr/$serverAsync.d.ts
17
17
  /**
18
- * Server no-op for the client-side `$async` getter primitive. Server renders
19
- * are synchronous, so an async value never resolves during SSR — `@await`
18
+ * Server counterpart of the client-side `$async` getter primitive.
19
+ *
20
+ * Without options (the default), this is a no-op stub: server renders are
21
+ * synchronous, an async value never resolves during SSR, and `@await`
20
22
  * boundaries render their `with` branch and never read the getter. The stub
21
23
  * exists so server builds importing `$async` don't emit a dangling import.
24
+ * A read inside a boundary's render pass is still recorded, so the boundary
25
+ * degrades to the `with` branch + client fetch instead of shipping content
26
+ * that would suspend on the client.
27
+ *
28
+ * With `{ source: "server" }` the getter participates in the server's
29
+ * collect/render pass (ASYNC.md → "Server rendering"):
30
+ *
31
+ * - **Collect pass** — calls the thunk (starting the fetch) and records the
32
+ * promise on the enclosing boundary, so sibling reads start in one wave.
33
+ * Returns `undefined`; the speculative pass's output is discarded.
34
+ * - **Render pass** — returns the settled value from the recorded promise at
35
+ * the same position in read order, without calling the thunk again. A
36
+ * rejection is re-thrown here so `@try`/`@error` handles it like any other
37
+ * render error. Running past the recorded reads throws a sentinel that
38
+ * degrades the boundary (the render pass no longer matches the collect
39
+ * pass, so the shipped HTML can't be trusted).
40
+ *
41
+ * Outside any `@await` boundary (including in a `with` branch) a
42
+ * `source: "server"` read returns `undefined` without fetching — the same
43
+ * undefined-then-recover contract the client has for un-boundaried reads.
22
44
  */
23
- declare function $serverAsync<T>(_fn: () => Promise<T>): T;
45
+ declare function $serverAsync<T>(fn: () => Promise<T>, options?: AsyncOptions): T;
24
46
  //#endregion
25
47
  //#region src/ssr/$serverBatch.d.ts
26
48
  declare function $serverBatch<T>(fn: () => T): T;
@@ -78,17 +100,94 @@ declare function $serverUnwrap<T extends Record<PropertyKey, any>>(object: T): T
78
100
  //#region src/ssr/$serverWatch.d.ts
79
101
  declare function $serverWatch<T extends Record<PropertyKey, any>>(object: T): T;
80
102
  //#endregion
103
+ //#region src/ssr/runServerAwait.d.ts
104
+ /**
105
+ * Renders an `@await` boundary on the server (compiled as `t_await_server`).
106
+ *
107
+ * The generated code calls this for every `@await`, passing the boundary's
108
+ * site id, a closure that renders the content branch, and one that renders
109
+ * the `with` branch. It always returns a `{ body, head }` pair to append:
110
+ *
111
+ * - **Inside a boundary's render pass** — this boundary was already started
112
+ * during the collect pass; its body/head is the finished HTML (awaited if
113
+ * still in flight). A missing entry means the render pass doesn't match
114
+ * the collect pass, which degrades the enclosing boundary.
115
+ * - **Inside a collect pass or a component flush** — start the full
116
+ * lifecycle (collect, settle, render) without blocking the enclosing
117
+ * render, and return a sentinel comment as the body. Sibling reads and
118
+ * nested boundaries keep rendering, so every fetch in the pass starts in
119
+ * one wave. The sentinel is substituted later: by the enclosing boundary's
120
+ * render pass (which re-invokes this helper and awaits the finished HTML),
121
+ * or by the component flush (which replaces top-level sentinels directly).
122
+ *
123
+ * The lifecycle: render content once to start the `source: "server"` fetches
124
+ * (discarding the output), wait for them with a per-getter timeout, then
125
+ * render again with the settled values. The second pass ships wrapped in
126
+ * hydration markers plus a `<!--t-await:...-->` comment carrying the values
127
+ * for the client's `$async` hydration path. Any instability — timeout, a
128
+ * render pass that reads a different set of getters, a client-fetch read —
129
+ * degrades to the `with` branch + client fetch, exactly the pre-`source`
130
+ * behavior.
131
+ *
132
+ * @param id The boundary's site id (unique per `@await` in the component)
133
+ * @param renderContent Renders the content branch (may read `$async` getters)
134
+ * @param renderWith Renders the `with` branch, or null for empty
135
+ */
136
+ declare function runServerAwait(id: string, renderContent: () => Promise<{
137
+ body: string;
138
+ head: string;
139
+ }>, renderWith: (() => Promise<{
140
+ body: string;
141
+ head: string;
142
+ }>) | null): {
143
+ body: string;
144
+ head: string;
145
+ } | Promise<{
146
+ body: string;
147
+ head: string;
148
+ }>;
149
+ //#endregion
150
+ //#region src/ssr/serverSentinels.d.ts
151
+ /** The body/head pair that server components and boundaries render into. */
152
+ interface ServerHtml {
153
+ body: string;
154
+ head: string;
155
+ }
156
+ /**
157
+ * Renders a component's (or a degraded boundary's `with` branch's) markup
158
+ * with a fresh flush root as the enclosing await state, then substitutes any
159
+ * boundary sentinels with their final HTML.
160
+ *
161
+ * Boundaries created during the render stash their lifecycle promise under a
162
+ * sentinel comment and return the sentinel immediately, so the render itself
163
+ * never blocks on a fetch — every `source: "server"` read in the render
164
+ * starts in the same wave. Substitution awaits the stashed promises at the
165
+ * end, which is the only point an async component render actually waits.
166
+ *
167
+ * On the client this has no counterpart: `render` here is the compiled
168
+ * component body closure that the generated component function returns.
169
+ */
170
+ declare function serverFlush(render: () => Promise<ServerHtml>): Promise<ServerHtml>;
171
+ //#endregion
81
172
  //#region src/types/ServerSlotRender.d.ts
82
- type ServerSlotRender = ($slot?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>) => string;
173
+ /**
174
+ * Renders a slot fill to HTML. Async so the fill can contain an `@await`
175
+ * boundary with a `source: "server"` getter (ASYNC.md → "Server rendering").
176
+ */
177
+ type ServerSlotRender = ($slot?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>) => Promise<string>;
83
178
  //#endregion
84
179
  //#region src/types/ServerComponent.d.ts
85
180
  /**
86
- * A component that generates HTML
181
+ * A component that generates HTML. Server components are async so that a
182
+ * `source: "server"` `@await` boundary can fetch during the render (see
183
+ * ASYNC.md → "Server rendering"); call sites await the result, which also
184
+ * accepts the plain object a component without boundaries effectively
185
+ * resolves to.
87
186
  */
88
- type ServerComponent = ($props?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>, $slots?: Record<string, ServerSlotRender>) => {
187
+ type ServerComponent = ($props?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>, $slots?: Record<string, ServerSlotRender>) => Promise<{
89
188
  body: string;
90
189
  head: string;
91
- };
190
+ }>;
92
191
  //#endregion
93
- export { $serverAsync as $async, $serverBatch as $batch, $serverBind as $bind, $serverCache as $cache, $serverOnmount as $onmount, $serverPeek as $peek, $serverPending as $pending, $serverRefresh as $refresh, $serverRun as $run, $serverStream as $stream, $serverUnwrap as $unwrap, $serverWatch as $watch, type ServerComponent, type ServerSlotRender, type StreamSource, fromElement, fromServer, fromWebSocket, formatAttributeText as t_attr, buildClasses as t_class, formatServerText as t_fmt, buildStyles as t_style };
192
+ export { $serverAsync as $async, $serverBatch as $batch, $serverBind as $bind, $serverCache as $cache, $serverOnmount as $onmount, $serverPeek as $peek, $serverPending as $pending, $serverRefresh as $refresh, $serverRun as $run, $serverStream as $stream, $serverUnwrap as $unwrap, $serverWatch as $watch, type AsyncOptions, type ServerComponent, type ServerSlotRender, type StreamSource, fromElement, fromServer, fromWebSocket, formatAttributeText as t_attr, runServerAwait as t_await_server, buildClasses as t_class, formatServerText as t_fmt, serverFlush as t_server_flush, buildStyles as t_style };
94
193
  //# sourceMappingURL=ssr.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.d.cts","names":[],"sources":["../src/render/formatAttributeText.ts","../src/ssr/formatText.ts","../src/ssr/$serverAsync.ts","../src/ssr/$serverBatch.ts","../src/ssr/$serverBind.ts","../src/ssr/$serverCache.ts","../src/ssr/$serverOnmount.ts","../src/ssr/$serverPeek.ts","../src/ssr/$serverPending.ts","../src/ssr/$serverRefresh.ts","../src/ssr/$serverRun.ts","../src/ssr/$serverStream.ts","../src/ssr/$serverUnwrap.ts","../src/ssr/$serverWatch.ts","../src/types/ServerSlotRender.ts","../src/types/ServerComponent.ts"],"mappings":";;;iBAAwB,oBAAoB;;;;;;;;;;iBCepB,iBAAiB;;;;;;;;;iBCTjB,aAAa,GAAG,WAAW,QAAQ,KAAK;;;iBCNxC,aAAa,GAAG,UAAU,IAAI;;;;;;;;;iBCM9B,YACvB,QAAQ,OAAO,mBACf,QAAQ,OAAO,kCACZ;;;iBCToB,aAAa,GAAG,UAAU,IAAI;;;iBCE9B,eAAe,WAAW;;;iBCF1B,YAAY,GAAG,UAAU,IAAI;;;;;;;;iBCK7B,eAAe;;;;;;;;iBCAf,eAAe;;;iBCHf,WAAW,WAAW;;;;;;;;iBCKtB,cAAc,GACrC,SAAS,aAAa,IACtB,WAAW,OAAO,YAClB;EAAa;;;;iBCVU,cAAc,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;iBCA9D,aAAa,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;KCAhF,oBACJ,QAAQ,OAAO,mBACf,WAAW,OAAO;;;;;;KCGd,mBACJ,SAAS,OAAO,mBAChB,WAAW,OAAO,mBAClB,SAAS,eAAe;EAClB;EAAc"}
1
+ {"version":3,"file":"ssr.d.cts","names":[],"sources":["../src/render/formatAttributeText.ts","../src/ssr/formatText.ts","../src/ssr/$serverAsync.ts","../src/ssr/$serverBatch.ts","../src/ssr/$serverBind.ts","../src/ssr/$serverCache.ts","../src/ssr/$serverOnmount.ts","../src/ssr/$serverPeek.ts","../src/ssr/$serverPending.ts","../src/ssr/$serverRefresh.ts","../src/ssr/$serverRun.ts","../src/ssr/$serverStream.ts","../src/ssr/$serverUnwrap.ts","../src/ssr/$serverWatch.ts","../src/ssr/runServerAwait.ts","../src/ssr/serverSentinels.ts","../src/types/ServerSlotRender.ts","../src/types/ServerComponent.ts"],"mappings":";;;iBAAwB,oBAAoB;;;;;;;;;;iBCepB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC2BjB,aAAa,GAAG,UAAU,QAAQ,IAAI,UAAU,eAAe;;;iBC1C/D,aAAa,GAAG,UAAU,IAAI;;;;;;;;;iBCM9B,YACvB,QAAQ,OAAO,mBACf,QAAQ,OAAO,kCACZ;;;iBCToB,aAAa,GAAG,UAAU,IAAI;;;iBCE9B,eAAe,WAAW;;;iBCF1B,YAAY,GAAG,UAAU,IAAI;;;;;;;;iBCK7B,eAAe;;;;;;;;iBCAf,eAAe;;;iBCHf,WAAW,WAAW;;;;;;;;iBCKtB,cAAc,GACrC,SAAS,aAAa,IACtB,WAAW,OAAO,YAClB;EAAa;;;;iBCVU,cAAc,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;iBCA9D,aAAa,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCgD7D,eACvB,YACA,qBAAqB;EAAU;EAAc;IAC7C,mBAAmB;EAAU;EAAc;;EACvC;EAAc;IAAiB;EAAU;EAAc;;;;;UC7C3C;EAChB;EACA;;;;;;;;;;;;;;;;iBAiBqB,YAAY,cAAc,QAAQ,cAAc,QAAQ;;;;;;;KCtBzE,oBACJ,QAAQ,OAAO,mBACf,WAAW,OAAO,sBACd;;;;;;;;;;KCEA,mBACJ,SAAS,OAAO,mBAChB,WAAW,OAAO,mBAClB,SAAS,eAAe,sBACpB;EAAU;EAAc"}
package/dist/ssr.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as Cleanup } from "./Cleanup-D0wvGW5d.mjs";
2
- import { a as buildStyles, i as StreamSource, n as fromServer, r as fromElement, s as buildClasses, t as fromWebSocket } from "./fromWebSocket-B52kN3-W.mjs";
2
+ import { a as StreamSource, c as buildClasses, i as fromElement, n as fromWebSocket, o as buildStyles, r as fromServer, t as AsyncOptions } from "./AsyncOptions-ClS1_ERX.mjs";
3
3
  //#region src/render/formatAttributeText.d.ts
4
4
  declare function formatAttributeText(value: any): string;
5
5
  //#endregion
@@ -15,12 +15,34 @@ declare function formatServerText(value: any): string;
15
15
  //#endregion
16
16
  //#region src/ssr/$serverAsync.d.ts
17
17
  /**
18
- * Server no-op for the client-side `$async` getter primitive. Server renders
19
- * are synchronous, so an async value never resolves during SSR — `@await`
18
+ * Server counterpart of the client-side `$async` getter primitive.
19
+ *
20
+ * Without options (the default), this is a no-op stub: server renders are
21
+ * synchronous, an async value never resolves during SSR, and `@await`
20
22
  * boundaries render their `with` branch and never read the getter. The stub
21
23
  * exists so server builds importing `$async` don't emit a dangling import.
24
+ * A read inside a boundary's render pass is still recorded, so the boundary
25
+ * degrades to the `with` branch + client fetch instead of shipping content
26
+ * that would suspend on the client.
27
+ *
28
+ * With `{ source: "server" }` the getter participates in the server's
29
+ * collect/render pass (ASYNC.md → "Server rendering"):
30
+ *
31
+ * - **Collect pass** — calls the thunk (starting the fetch) and records the
32
+ * promise on the enclosing boundary, so sibling reads start in one wave.
33
+ * Returns `undefined`; the speculative pass's output is discarded.
34
+ * - **Render pass** — returns the settled value from the recorded promise at
35
+ * the same position in read order, without calling the thunk again. A
36
+ * rejection is re-thrown here so `@try`/`@error` handles it like any other
37
+ * render error. Running past the recorded reads throws a sentinel that
38
+ * degrades the boundary (the render pass no longer matches the collect
39
+ * pass, so the shipped HTML can't be trusted).
40
+ *
41
+ * Outside any `@await` boundary (including in a `with` branch) a
42
+ * `source: "server"` read returns `undefined` without fetching — the same
43
+ * undefined-then-recover contract the client has for un-boundaried reads.
22
44
  */
23
- declare function $serverAsync<T>(_fn: () => Promise<T>): T;
45
+ declare function $serverAsync<T>(fn: () => Promise<T>, options?: AsyncOptions): T;
24
46
  //#endregion
25
47
  //#region src/ssr/$serverBatch.d.ts
26
48
  declare function $serverBatch<T>(fn: () => T): T;
@@ -78,17 +100,94 @@ declare function $serverUnwrap<T extends Record<PropertyKey, any>>(object: T): T
78
100
  //#region src/ssr/$serverWatch.d.ts
79
101
  declare function $serverWatch<T extends Record<PropertyKey, any>>(object: T): T;
80
102
  //#endregion
103
+ //#region src/ssr/runServerAwait.d.ts
104
+ /**
105
+ * Renders an `@await` boundary on the server (compiled as `t_await_server`).
106
+ *
107
+ * The generated code calls this for every `@await`, passing the boundary's
108
+ * site id, a closure that renders the content branch, and one that renders
109
+ * the `with` branch. It always returns a `{ body, head }` pair to append:
110
+ *
111
+ * - **Inside a boundary's render pass** — this boundary was already started
112
+ * during the collect pass; its body/head is the finished HTML (awaited if
113
+ * still in flight). A missing entry means the render pass doesn't match
114
+ * the collect pass, which degrades the enclosing boundary.
115
+ * - **Inside a collect pass or a component flush** — start the full
116
+ * lifecycle (collect, settle, render) without blocking the enclosing
117
+ * render, and return a sentinel comment as the body. Sibling reads and
118
+ * nested boundaries keep rendering, so every fetch in the pass starts in
119
+ * one wave. The sentinel is substituted later: by the enclosing boundary's
120
+ * render pass (which re-invokes this helper and awaits the finished HTML),
121
+ * or by the component flush (which replaces top-level sentinels directly).
122
+ *
123
+ * The lifecycle: render content once to start the `source: "server"` fetches
124
+ * (discarding the output), wait for them with a per-getter timeout, then
125
+ * render again with the settled values. The second pass ships wrapped in
126
+ * hydration markers plus a `<!--t-await:...-->` comment carrying the values
127
+ * for the client's `$async` hydration path. Any instability — timeout, a
128
+ * render pass that reads a different set of getters, a client-fetch read —
129
+ * degrades to the `with` branch + client fetch, exactly the pre-`source`
130
+ * behavior.
131
+ *
132
+ * @param id The boundary's site id (unique per `@await` in the component)
133
+ * @param renderContent Renders the content branch (may read `$async` getters)
134
+ * @param renderWith Renders the `with` branch, or null for empty
135
+ */
136
+ declare function runServerAwait(id: string, renderContent: () => Promise<{
137
+ body: string;
138
+ head: string;
139
+ }>, renderWith: (() => Promise<{
140
+ body: string;
141
+ head: string;
142
+ }>) | null): {
143
+ body: string;
144
+ head: string;
145
+ } | Promise<{
146
+ body: string;
147
+ head: string;
148
+ }>;
149
+ //#endregion
150
+ //#region src/ssr/serverSentinels.d.ts
151
+ /** The body/head pair that server components and boundaries render into. */
152
+ interface ServerHtml {
153
+ body: string;
154
+ head: string;
155
+ }
156
+ /**
157
+ * Renders a component's (or a degraded boundary's `with` branch's) markup
158
+ * with a fresh flush root as the enclosing await state, then substitutes any
159
+ * boundary sentinels with their final HTML.
160
+ *
161
+ * Boundaries created during the render stash their lifecycle promise under a
162
+ * sentinel comment and return the sentinel immediately, so the render itself
163
+ * never blocks on a fetch — every `source: "server"` read in the render
164
+ * starts in the same wave. Substitution awaits the stashed promises at the
165
+ * end, which is the only point an async component render actually waits.
166
+ *
167
+ * On the client this has no counterpart: `render` here is the compiled
168
+ * component body closure that the generated component function returns.
169
+ */
170
+ declare function serverFlush(render: () => Promise<ServerHtml>): Promise<ServerHtml>;
171
+ //#endregion
81
172
  //#region src/types/ServerSlotRender.d.ts
82
- type ServerSlotRender = ($slot?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>) => string;
173
+ /**
174
+ * Renders a slot fill to HTML. Async so the fill can contain an `@await`
175
+ * boundary with a `source: "server"` getter (ASYNC.md → "Server rendering").
176
+ */
177
+ type ServerSlotRender = ($slot?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>) => Promise<string>;
83
178
  //#endregion
84
179
  //#region src/types/ServerComponent.d.ts
85
180
  /**
86
- * A component that generates HTML
181
+ * A component that generates HTML. Server components are async so that a
182
+ * `source: "server"` `@await` boundary can fetch during the render (see
183
+ * ASYNC.md → "Server rendering"); call sites await the result, which also
184
+ * accepts the plain object a component without boundaries effectively
185
+ * resolves to.
87
186
  */
88
- type ServerComponent = ($props?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>, $slots?: Record<string, ServerSlotRender>) => {
187
+ type ServerComponent = ($props?: Record<PropertyKey, any>, $context?: Record<PropertyKey, any>, $slots?: Record<string, ServerSlotRender>) => Promise<{
89
188
  body: string;
90
189
  head: string;
91
- };
190
+ }>;
92
191
  //#endregion
93
- export { $serverAsync as $async, $serverBatch as $batch, $serverBind as $bind, $serverCache as $cache, $serverOnmount as $onmount, $serverPeek as $peek, $serverPending as $pending, $serverRefresh as $refresh, $serverRun as $run, $serverStream as $stream, $serverUnwrap as $unwrap, $serverWatch as $watch, type ServerComponent, type ServerSlotRender, type StreamSource, fromElement, fromServer, fromWebSocket, formatAttributeText as t_attr, buildClasses as t_class, formatServerText as t_fmt, buildStyles as t_style };
192
+ export { $serverAsync as $async, $serverBatch as $batch, $serverBind as $bind, $serverCache as $cache, $serverOnmount as $onmount, $serverPeek as $peek, $serverPending as $pending, $serverRefresh as $refresh, $serverRun as $run, $serverStream as $stream, $serverUnwrap as $unwrap, $serverWatch as $watch, type AsyncOptions, type ServerComponent, type ServerSlotRender, type StreamSource, fromElement, fromServer, fromWebSocket, formatAttributeText as t_attr, runServerAwait as t_await_server, buildClasses as t_class, formatServerText as t_fmt, serverFlush as t_server_flush, buildStyles as t_style };
94
193
  //# sourceMappingURL=ssr.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.d.mts","names":[],"sources":["../src/render/formatAttributeText.ts","../src/ssr/formatText.ts","../src/ssr/$serverAsync.ts","../src/ssr/$serverBatch.ts","../src/ssr/$serverBind.ts","../src/ssr/$serverCache.ts","../src/ssr/$serverOnmount.ts","../src/ssr/$serverPeek.ts","../src/ssr/$serverPending.ts","../src/ssr/$serverRefresh.ts","../src/ssr/$serverRun.ts","../src/ssr/$serverStream.ts","../src/ssr/$serverUnwrap.ts","../src/ssr/$serverWatch.ts","../src/types/ServerSlotRender.ts","../src/types/ServerComponent.ts"],"mappings":";;;iBAAwB,oBAAoB;;;;;;;;;;iBCepB,iBAAiB;;;;;;;;;iBCTjB,aAAa,GAAG,WAAW,QAAQ,KAAK;;;iBCNxC,aAAa,GAAG,UAAU,IAAI;;;;;;;;;iBCM9B,YACvB,QAAQ,OAAO,mBACf,QAAQ,OAAO,kCACZ;;;iBCToB,aAAa,GAAG,UAAU,IAAI;;;iBCE9B,eAAe,WAAW;;;iBCF1B,YAAY,GAAG,UAAU,IAAI;;;;;;;;iBCK7B,eAAe;;;;;;;;iBCAf,eAAe;;;iBCHf,WAAW,WAAW;;;;;;;;iBCKtB,cAAc,GACrC,SAAS,aAAa,IACtB,WAAW,OAAO,YAClB;EAAa;;;;iBCVU,cAAc,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;iBCA9D,aAAa,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;KCAhF,oBACJ,QAAQ,OAAO,mBACf,WAAW,OAAO;;;;;;KCGd,mBACJ,SAAS,OAAO,mBAChB,WAAW,OAAO,mBAClB,SAAS,eAAe;EAClB;EAAc"}
1
+ {"version":3,"file":"ssr.d.mts","names":[],"sources":["../src/render/formatAttributeText.ts","../src/ssr/formatText.ts","../src/ssr/$serverAsync.ts","../src/ssr/$serverBatch.ts","../src/ssr/$serverBind.ts","../src/ssr/$serverCache.ts","../src/ssr/$serverOnmount.ts","../src/ssr/$serverPeek.ts","../src/ssr/$serverPending.ts","../src/ssr/$serverRefresh.ts","../src/ssr/$serverRun.ts","../src/ssr/$serverStream.ts","../src/ssr/$serverUnwrap.ts","../src/ssr/$serverWatch.ts","../src/ssr/runServerAwait.ts","../src/ssr/serverSentinels.ts","../src/types/ServerSlotRender.ts","../src/types/ServerComponent.ts"],"mappings":";;;iBAAwB,oBAAoB;;;;;;;;;;iBCepB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC2BjB,aAAa,GAAG,UAAU,QAAQ,IAAI,UAAU,eAAe;;;iBC1C/D,aAAa,GAAG,UAAU,IAAI;;;;;;;;;iBCM9B,YACvB,QAAQ,OAAO,mBACf,QAAQ,OAAO,kCACZ;;;iBCToB,aAAa,GAAG,UAAU,IAAI;;;iBCE9B,eAAe,WAAW;;;iBCF1B,YAAY,GAAG,UAAU,IAAI;;;;;;;;iBCK7B,eAAe;;;;;;;;iBCAf,eAAe;;;iBCHf,WAAW,WAAW;;;;;;;;iBCKtB,cAAc,GACrC,SAAS,aAAa,IACtB,WAAW,OAAO,YAClB;EAAa;;;;iBCVU,cAAc,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;iBCA9D,aAAa,UAAU,OAAO,mBAAmB,QAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCgD7D,eACvB,YACA,qBAAqB;EAAU;EAAc;IAC7C,mBAAmB;EAAU;EAAc;;EACvC;EAAc;IAAiB;EAAU;EAAc;;;;;UC7C3C;EAChB;EACA;;;;;;;;;;;;;;;;iBAiBqB,YAAY,cAAc,QAAQ,cAAc,QAAQ;;;;;;;KCtBzE,oBACJ,QAAQ,OAAO,mBACf,WAAW,OAAO,sBACd;;;;;;;;;;KCEA,mBACJ,SAAS,OAAO,mBAChB,WAAW,OAAO,mBAClB,SAAS,eAAe,sBACpB;EAAU;EAAc"}