@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/{fromWebSocket-DeB35Q4w.d.cts → AsyncOptions-BtMe2PAd.d.cts} +21 -2
- package/dist/AsyncOptions-BtMe2PAd.d.cts.map +1 -0
- package/dist/{fromWebSocket-B52kN3-W.d.mts → AsyncOptions-ClS1_ERX.d.mts} +21 -2
- package/dist/AsyncOptions-ClS1_ERX.d.mts.map +1 -0
- package/dist/{Effect-BTKurOpr.d.mts → Effect-DazmAdyC.d.mts} +3 -3
- package/dist/{Effect-BTKurOpr.d.mts.map → Effect-DazmAdyC.d.mts.map} +1 -1
- package/dist/{Effect-COl3aRJV.d.cts → Effect-Dewn7MLc.d.cts} +3 -3
- package/dist/{Effect-COl3aRJV.d.cts.map → Effect-Dewn7MLc.d.cts.map} +1 -1
- package/dist/compile.cjs +139 -33
- package/dist/compile.mjs +139 -33
- package/dist/compile.mjs.map +1 -1
- package/dist/dev.d.cts +1 -1
- package/dist/dev.d.mts +1 -1
- package/dist/index.cjs +60 -8
- package/dist/index.d.cts +23 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +23 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +60 -8
- package/dist/index.mjs.map +1 -1
- package/dist/ssr.cjs +292 -4
- package/dist/ssr.d.cts +108 -9
- package/dist/ssr.d.cts.map +1 -1
- package/dist/ssr.d.mts +108 -9
- package/dist/ssr.d.mts.map +1 -1
- package/dist/ssr.mjs +291 -5
- package/dist/ssr.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/fromWebSocket-B52kN3-W.d.mts.map +0 -1
- package/dist/fromWebSocket-DeB35Q4w.d.cts.map +0 -1
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
|
|
25
|
+
//#region src/ssr/serverAwaitState.ts
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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(
|
|
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
|
|
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
|
|
19
|
-
*
|
|
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>(
|
|
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
|
-
|
|
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
|
package/dist/ssr.d.cts.map
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
19
|
-
*
|
|
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>(
|
|
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
|
-
|
|
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
|
package/dist/ssr.d.mts.map
CHANGED
|
@@ -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
|
|
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"}
|