@constela/server 13.0.0 → 14.0.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.
- package/README.md +98 -0
- package/dist/index.js +6 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -201,6 +201,104 @@ Pass style presets via `RenderOptions.styles` for evaluation.
|
|
|
201
201
|
.dark .shiki span { color: var(--shiki-dark); }
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
## Streaming SSR
|
|
205
|
+
|
|
206
|
+
Render to a ReadableStream for progressive HTML delivery:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { renderToStream, createHtmlTransformStream } from '@constela/server';
|
|
210
|
+
|
|
211
|
+
// Render program to stream
|
|
212
|
+
const contentStream = renderToStream(compiledProgram, {
|
|
213
|
+
flushStrategy: 'batched',
|
|
214
|
+
}, {
|
|
215
|
+
route: { params: { id: '123' }, query: {}, path: '/posts/123' },
|
|
216
|
+
imports: { config: siteConfig },
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Wrap with HTML document structure
|
|
220
|
+
const htmlStream = contentStream.pipeThrough(
|
|
221
|
+
createHtmlTransformStream({
|
|
222
|
+
title: 'My Page',
|
|
223
|
+
lang: 'en',
|
|
224
|
+
stylesheets: ['/styles.css'],
|
|
225
|
+
scripts: ['/client.js'],
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Use with Response (Edge/Workers)
|
|
230
|
+
return new Response(htmlStream, {
|
|
231
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Flush Strategies:**
|
|
236
|
+
|
|
237
|
+
| Strategy | Description |
|
|
238
|
+
|----------|-------------|
|
|
239
|
+
| `immediate` | Flush each chunk as soon as it's ready |
|
|
240
|
+
| `batched` | Flush when buffer exceeds 1KB threshold |
|
|
241
|
+
| `manual` | Only flush at the end (for small pages) |
|
|
242
|
+
|
|
243
|
+
**StreamingRenderOptions:**
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
interface StreamingRenderOptions {
|
|
247
|
+
flushStrategy: 'immediate' | 'batched' | 'manual';
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Abort Signal Support
|
|
252
|
+
|
|
253
|
+
Cancel streaming when the client disconnects:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
|
|
258
|
+
const stream = renderToStream(program, { flushStrategy: 'batched' }, {
|
|
259
|
+
signal: controller.signal,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Cancel on client disconnect
|
|
263
|
+
request.signal.addEventListener('abort', () => {
|
|
264
|
+
controller.abort();
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Suspense Boundaries
|
|
269
|
+
|
|
270
|
+
Server-side suspense for async content:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"view": {
|
|
275
|
+
"kind": "suspense",
|
|
276
|
+
"id": "user-data",
|
|
277
|
+
"fallback": {
|
|
278
|
+
"kind": "element",
|
|
279
|
+
"tag": "div",
|
|
280
|
+
"props": { "className": { "expr": "lit", "value": "skeleton" } },
|
|
281
|
+
"children": []
|
|
282
|
+
},
|
|
283
|
+
"content": {
|
|
284
|
+
"kind": "component",
|
|
285
|
+
"name": "UserProfile",
|
|
286
|
+
"props": { "user": { "expr": "data", "name": "user" } }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Renders with markers for client-side hydration:
|
|
293
|
+
|
|
294
|
+
```html
|
|
295
|
+
<div data-suspense-id="user-data">
|
|
296
|
+
<!-- Fallback content first -->
|
|
297
|
+
<div class="skeleton"></div>
|
|
298
|
+
</div>
|
|
299
|
+
<!-- Resolved content follows -->
|
|
300
|
+
```
|
|
301
|
+
|
|
204
302
|
## Security
|
|
205
303
|
|
|
206
304
|
- **HTML Escaping** - All text output is escaped
|
package/dist/index.js
CHANGED
|
@@ -502,6 +502,9 @@ function evaluate(expr, ctx) {
|
|
|
502
502
|
}
|
|
503
503
|
case "call": {
|
|
504
504
|
const callExpr = expr;
|
|
505
|
+
if (callExpr.target === null) {
|
|
506
|
+
return void 0;
|
|
507
|
+
}
|
|
505
508
|
const target = evaluate(callExpr.target, ctx);
|
|
506
509
|
if (target == null) return void 0;
|
|
507
510
|
const args = callExpr.args?.map((arg) => {
|
|
@@ -1253,6 +1256,9 @@ function evaluate2(expr, ctx) {
|
|
|
1253
1256
|
}
|
|
1254
1257
|
case "call": {
|
|
1255
1258
|
const callExpr = expr;
|
|
1259
|
+
if (callExpr.target === null) {
|
|
1260
|
+
return void 0;
|
|
1261
|
+
}
|
|
1256
1262
|
const target = evaluate2(callExpr.target, ctx);
|
|
1257
1263
|
if (target == null) return void 0;
|
|
1258
1264
|
const args = callExpr.args?.map((arg) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "Server-side rendering for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@constela/compiler": "^0.15.
|
|
19
|
-
"@constela/core": "^0.
|
|
18
|
+
"@constela/compiler": "^0.15.6",
|
|
19
|
+
"@constela/core": "^0.18.0"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"isomorphic-dompurify": "^2.35.0",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"tsup": "^8.0.0",
|
|
30
30
|
"typescript": "^5.3.0",
|
|
31
31
|
"vitest": "^2.0.0",
|
|
32
|
-
"@constela/
|
|
33
|
-
"@constela/
|
|
32
|
+
"@constela/core": "0.18.0",
|
|
33
|
+
"@constela/compiler": "0.15.6"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=20.0.0"
|