@constela/server 13.0.0 → 14.0.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/README.md +98 -0
- package/dist/index.js +10 -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
|
@@ -403,6 +403,8 @@ function evaluate(expr, ctx) {
|
|
|
403
403
|
return expr.value;
|
|
404
404
|
case "state":
|
|
405
405
|
return ctx.state.get(expr.name);
|
|
406
|
+
case "local":
|
|
407
|
+
return ctx.locals[expr.name];
|
|
406
408
|
case "var": {
|
|
407
409
|
let varName = expr.name;
|
|
408
410
|
let pathParts = [];
|
|
@@ -502,6 +504,9 @@ function evaluate(expr, ctx) {
|
|
|
502
504
|
}
|
|
503
505
|
case "call": {
|
|
504
506
|
const callExpr = expr;
|
|
507
|
+
if (callExpr.target === null) {
|
|
508
|
+
return void 0;
|
|
509
|
+
}
|
|
505
510
|
const target = evaluate(callExpr.target, ctx);
|
|
506
511
|
if (target == null) return void 0;
|
|
507
512
|
const args = callExpr.args?.map((arg) => {
|
|
@@ -1152,6 +1157,8 @@ function evaluate2(expr, ctx) {
|
|
|
1152
1157
|
return expr.value;
|
|
1153
1158
|
case "state":
|
|
1154
1159
|
return ctx.state.get(expr.name);
|
|
1160
|
+
case "local":
|
|
1161
|
+
return ctx.locals[expr.name];
|
|
1155
1162
|
case "var": {
|
|
1156
1163
|
let varName = expr.name;
|
|
1157
1164
|
let pathParts = [];
|
|
@@ -1253,6 +1260,9 @@ function evaluate2(expr, ctx) {
|
|
|
1253
1260
|
}
|
|
1254
1261
|
case "call": {
|
|
1255
1262
|
const callExpr = expr;
|
|
1263
|
+
if (callExpr.target === null) {
|
|
1264
|
+
return void 0;
|
|
1265
|
+
}
|
|
1256
1266
|
const target = evaluate2(callExpr.target, ctx);
|
|
1257
1267
|
if (target == null) return void 0;
|
|
1258
1268
|
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.1",
|
|
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.7",
|
|
19
|
+
"@constela/core": "^0.18.1"
|
|
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.1",
|
|
33
|
+
"@constela/compiler": "0.15.7"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=20.0.0"
|