@constela/server 17.0.0 → 18.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/dist/index.d.ts CHANGED
@@ -1,23 +1,13 @@
1
1
  import { CompiledProgram } from '@constela/compiler';
2
- import { StreamingRenderOptions } from '@constela/core';
2
+ import { StylePreset, StreamingRenderOptions } from '@constela/core';
3
3
 
4
4
  /**
5
5
  * SSR Renderer
6
6
  *
7
7
  * Renders CompiledProgram to HTML string for Server-Side Rendering.
8
+ * Uses the unified evaluate module from @constela/core.
8
9
  */
9
10
 
10
- /**
11
- * Style preset definition for SSR
12
- */
13
- interface StylePreset$1 {
14
- base: string;
15
- variants?: Record<string, Record<string, string>>;
16
- defaultVariants?: Record<string, string>;
17
- compoundVariants?: Array<Record<string, string> & {
18
- class: string;
19
- }>;
20
- }
21
11
  /**
22
12
  * Options for renderToString
23
13
  */
@@ -28,7 +18,7 @@ interface RenderOptions {
28
18
  path?: string;
29
19
  };
30
20
  imports?: Record<string, unknown>;
31
- styles?: Record<string, StylePreset$1>;
21
+ styles?: Record<string, StylePreset>;
32
22
  stateOverrides?: Record<string, unknown>;
33
23
  cookies?: Record<string, string>;
34
24
  }
@@ -46,6 +36,7 @@ declare function renderToString(program: CompiledProgram, options?: RenderOption
46
36
  *
47
37
  * Renders CompiledProgram to a ReadableStream for Server-Side Rendering.
48
38
  * Uses Web Streams API for Edge Runtime compatibility.
39
+ * Uses the unified evaluate module from @constela/core.
49
40
  *
50
41
  * Features:
51
42
  * - Three flush strategies: immediate, batched, manual
@@ -54,17 +45,6 @@ declare function renderToString(program: CompiledProgram, options?: RenderOption
54
45
  * - Suspense boundary support for async content
55
46
  */
56
47
 
57
- /**
58
- * Style preset definition for SSR
59
- */
60
- interface StylePreset {
61
- base: string;
62
- variants?: Record<string, Record<string, string>>;
63
- defaultVariants?: Record<string, string>;
64
- compoundVariants?: Array<Record<string, string> & {
65
- class: string;
66
- }>;
67
- }
68
48
  /**
69
49
  * Extended options for streaming render
70
50
  */
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/renderer.ts
2
- import { isCookieInitialExpr, callGlobalFunction } from "@constela/core";
2
+ import { isCookieInitialExpr, evaluate as coreEvaluate } from "@constela/core";
3
3
 
4
4
  // src/markdown.ts
5
5
  import { marked } from "marked";
@@ -108,7 +108,37 @@ async function parseMarkdownSSRAsync(content) {
108
108
  });
109
109
  }
110
110
 
111
- // src/renderer.ts
111
+ // src/shared.ts
112
+ import { GLOBAL_FUNCTIONS } from "@constela/core";
113
+ var ssrAdapter = {
114
+ resolveRef: () => null,
115
+ resolveValidity: () => false,
116
+ resolveGlobal(name) {
117
+ const safeGlobals = {
118
+ JSON,
119
+ Math,
120
+ Date,
121
+ Object,
122
+ Array,
123
+ String,
124
+ Number,
125
+ Boolean,
126
+ console,
127
+ ...GLOBAL_FUNCTIONS
128
+ };
129
+ return safeGlobals[name];
130
+ }
131
+ };
132
+ function toCoreContext(ctx) {
133
+ return {
134
+ state: ctx.state,
135
+ locals: ctx.locals,
136
+ route: ctx.route,
137
+ imports: ctx.imports,
138
+ styles: ctx.styles,
139
+ env: ssrAdapter
140
+ };
141
+ }
112
142
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
113
143
  "area",
114
144
  "base",
@@ -125,551 +155,6 @@ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
125
155
  "track",
126
156
  "wbr"
127
157
  ]);
128
- var SAFE_ARRAY_METHODS = /* @__PURE__ */ new Set([
129
- "length",
130
- "at",
131
- "includes",
132
- "slice",
133
- "indexOf",
134
- "join",
135
- "filter",
136
- "map",
137
- "find",
138
- "findIndex",
139
- "some",
140
- "every"
141
- ]);
142
- var SAFE_STRING_METHODS = /* @__PURE__ */ new Set([
143
- "length",
144
- "charAt",
145
- "substring",
146
- "slice",
147
- "split",
148
- "trim",
149
- "toUpperCase",
150
- "toLowerCase",
151
- "replace",
152
- "includes",
153
- "startsWith",
154
- "endsWith",
155
- "indexOf"
156
- ]);
157
- var SAFE_MATH_METHODS = /* @__PURE__ */ new Set([
158
- "min",
159
- "max",
160
- "round",
161
- "floor",
162
- "ceil",
163
- "abs",
164
- "sqrt",
165
- "pow",
166
- "random",
167
- "sin",
168
- "cos",
169
- "tan"
170
- ]);
171
- var SAFE_DATE_STATIC_METHODS = /* @__PURE__ */ new Set(["now", "parse"]);
172
- var SAFE_DATE_INSTANCE_METHODS = /* @__PURE__ */ new Set([
173
- "toISOString",
174
- "toDateString",
175
- "toTimeString",
176
- "getTime",
177
- "getFullYear",
178
- "getMonth",
179
- "getDate",
180
- "getHours",
181
- "getMinutes",
182
- "getSeconds",
183
- "getMilliseconds"
184
- ]);
185
- function isEventHandler(value) {
186
- return typeof value === "object" && value !== null && "event" in value && "action" in value;
187
- }
188
- function createLambdaFunction(lambda, ctx) {
189
- return (item, index) => {
190
- const lambdaLocals = {
191
- ...ctx.locals,
192
- [lambda.param]: item
193
- };
194
- if (lambda.index !== void 0) {
195
- lambdaLocals[lambda.index] = index;
196
- }
197
- return evaluate(lambda.body, { ...ctx, locals: lambdaLocals });
198
- };
199
- }
200
- function callArrayMethod(target, method, args, ctx, rawArgs) {
201
- if (!SAFE_ARRAY_METHODS.has(method)) return void 0;
202
- switch (method) {
203
- case "length":
204
- return target.length;
205
- case "at": {
206
- const index = typeof args[0] === "number" ? args[0] : 0;
207
- return target.at(index);
208
- }
209
- case "includes": {
210
- const searchElement = args[0];
211
- const fromIndex = typeof args[1] === "number" ? args[1] : void 0;
212
- return target.includes(searchElement, fromIndex);
213
- }
214
- case "slice": {
215
- const start = typeof args[0] === "number" ? args[0] : void 0;
216
- const end = typeof args[1] === "number" ? args[1] : void 0;
217
- return target.slice(start, end);
218
- }
219
- case "indexOf": {
220
- const searchElement = args[0];
221
- const fromIndex = typeof args[1] === "number" ? args[1] : void 0;
222
- return target.indexOf(searchElement, fromIndex);
223
- }
224
- case "join": {
225
- const separator = typeof args[0] === "string" ? args[0] : ",";
226
- return target.join(separator);
227
- }
228
- case "filter": {
229
- const lambdaExpr = rawArgs?.[0];
230
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
231
- const fn = createLambdaFunction(lambdaExpr, ctx);
232
- return target.filter((item, index) => !!fn(item, index));
233
- }
234
- case "map": {
235
- const lambdaExpr = rawArgs?.[0];
236
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
237
- const fn = createLambdaFunction(lambdaExpr, ctx);
238
- return target.map((item, index) => fn(item, index));
239
- }
240
- case "find": {
241
- const lambdaExpr = rawArgs?.[0];
242
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
243
- const fn = createLambdaFunction(lambdaExpr, ctx);
244
- return target.find((item, index) => !!fn(item, index));
245
- }
246
- case "findIndex": {
247
- const lambdaExpr = rawArgs?.[0];
248
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
249
- const fn = createLambdaFunction(lambdaExpr, ctx);
250
- return target.findIndex((item, index) => !!fn(item, index));
251
- }
252
- case "some": {
253
- const lambdaExpr = rawArgs?.[0];
254
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
255
- const fn = createLambdaFunction(lambdaExpr, ctx);
256
- return target.some((item, index) => !!fn(item, index));
257
- }
258
- case "every": {
259
- const lambdaExpr = rawArgs?.[0];
260
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
261
- const fn = createLambdaFunction(lambdaExpr, ctx);
262
- return target.every((item, index) => !!fn(item, index));
263
- }
264
- default:
265
- return void 0;
266
- }
267
- }
268
- function callStringMethod(target, method, args) {
269
- if (!SAFE_STRING_METHODS.has(method)) return void 0;
270
- switch (method) {
271
- case "length":
272
- return target.length;
273
- case "charAt": {
274
- const index = typeof args[0] === "number" ? args[0] : 0;
275
- return target.charAt(index);
276
- }
277
- case "substring": {
278
- const start = typeof args[0] === "number" ? args[0] : 0;
279
- const end = typeof args[1] === "number" ? args[1] : void 0;
280
- return target.substring(start, end);
281
- }
282
- case "slice": {
283
- const start = typeof args[0] === "number" ? args[0] : void 0;
284
- const end = typeof args[1] === "number" ? args[1] : void 0;
285
- return target.slice(start, end);
286
- }
287
- case "split": {
288
- const separator = typeof args[0] === "string" ? args[0] : "";
289
- return target.split(separator);
290
- }
291
- case "trim":
292
- return target.trim();
293
- case "toUpperCase":
294
- return target.toUpperCase();
295
- case "toLowerCase":
296
- return target.toLowerCase();
297
- case "replace": {
298
- const search = typeof args[0] === "string" ? args[0] : "";
299
- const replace = typeof args[1] === "string" ? args[1] : "";
300
- return target.replace(search, replace);
301
- }
302
- case "includes": {
303
- const search = typeof args[0] === "string" ? args[0] : "";
304
- const position = typeof args[1] === "number" ? args[1] : void 0;
305
- return target.includes(search, position);
306
- }
307
- case "startsWith": {
308
- const search = typeof args[0] === "string" ? args[0] : "";
309
- const position = typeof args[1] === "number" ? args[1] : void 0;
310
- return target.startsWith(search, position);
311
- }
312
- case "endsWith": {
313
- const search = typeof args[0] === "string" ? args[0] : "";
314
- const length = typeof args[1] === "number" ? args[1] : void 0;
315
- return target.endsWith(search, length);
316
- }
317
- case "indexOf": {
318
- const search = typeof args[0] === "string" ? args[0] : "";
319
- const position = typeof args[1] === "number" ? args[1] : void 0;
320
- return target.indexOf(search, position);
321
- }
322
- default:
323
- return void 0;
324
- }
325
- }
326
- function callMathMethod(method, args) {
327
- if (!SAFE_MATH_METHODS.has(method)) return void 0;
328
- const numbers = args.filter((a) => typeof a === "number");
329
- switch (method) {
330
- case "min":
331
- return numbers.length > 0 ? Math.min(...numbers) : void 0;
332
- case "max":
333
- return numbers.length > 0 ? Math.max(...numbers) : void 0;
334
- case "round":
335
- return numbers[0] !== void 0 ? Math.round(numbers[0]) : void 0;
336
- case "floor":
337
- return numbers[0] !== void 0 ? Math.floor(numbers[0]) : void 0;
338
- case "ceil":
339
- return numbers[0] !== void 0 ? Math.ceil(numbers[0]) : void 0;
340
- case "abs":
341
- return numbers[0] !== void 0 ? Math.abs(numbers[0]) : void 0;
342
- case "sqrt":
343
- return numbers[0] !== void 0 ? Math.sqrt(numbers[0]) : void 0;
344
- case "pow":
345
- return numbers[0] !== void 0 && numbers[1] !== void 0 ? Math.pow(numbers[0], numbers[1]) : void 0;
346
- case "random":
347
- return Math.random();
348
- case "sin":
349
- return numbers[0] !== void 0 ? Math.sin(numbers[0]) : void 0;
350
- case "cos":
351
- return numbers[0] !== void 0 ? Math.cos(numbers[0]) : void 0;
352
- case "tan":
353
- return numbers[0] !== void 0 ? Math.tan(numbers[0]) : void 0;
354
- default:
355
- return void 0;
356
- }
357
- }
358
- function callDateStaticMethod(method, args) {
359
- if (!SAFE_DATE_STATIC_METHODS.has(method)) return void 0;
360
- switch (method) {
361
- case "now":
362
- return Date.now();
363
- case "parse": {
364
- const dateString = args[0];
365
- return typeof dateString === "string" ? Date.parse(dateString) : void 0;
366
- }
367
- default:
368
- return void 0;
369
- }
370
- }
371
- function callDateInstanceMethod(target, method) {
372
- if (!SAFE_DATE_INSTANCE_METHODS.has(method)) return void 0;
373
- switch (method) {
374
- case "toISOString":
375
- return target.toISOString();
376
- case "toDateString":
377
- return target.toDateString();
378
- case "toTimeString":
379
- return target.toTimeString();
380
- case "getTime":
381
- return target.getTime();
382
- case "getFullYear":
383
- return target.getFullYear();
384
- case "getMonth":
385
- return target.getMonth();
386
- case "getDate":
387
- return target.getDate();
388
- case "getHours":
389
- return target.getHours();
390
- case "getMinutes":
391
- return target.getMinutes();
392
- case "getSeconds":
393
- return target.getSeconds();
394
- case "getMilliseconds":
395
- return target.getMilliseconds();
396
- default:
397
- return void 0;
398
- }
399
- }
400
- function evaluate(expr, ctx) {
401
- switch (expr.expr) {
402
- case "lit":
403
- return expr.value;
404
- case "state":
405
- return ctx.state.get(expr.name);
406
- case "local":
407
- return ctx.locals[expr.name];
408
- case "var": {
409
- let varName = expr.name;
410
- let pathParts = [];
411
- if (varName.includes(".")) {
412
- const parts = varName.split(".");
413
- varName = parts[0];
414
- pathParts = parts.slice(1);
415
- }
416
- if (expr.path) {
417
- pathParts = pathParts.concat(expr.path.split("."));
418
- }
419
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
420
- for (const part of pathParts) {
421
- if (forbiddenKeys.has(part)) {
422
- return void 0;
423
- }
424
- }
425
- let value = ctx.locals[varName];
426
- for (const part of pathParts) {
427
- if (value == null) break;
428
- value = value[part];
429
- }
430
- return value;
431
- }
432
- case "bin":
433
- return evaluateBinary(expr.op, expr.left, expr.right, ctx);
434
- case "not":
435
- return !evaluate(expr.operand, ctx);
436
- case "cond":
437
- return evaluate(expr.if, ctx) ? evaluate(expr.then, ctx) : evaluate(expr.else, ctx);
438
- case "get": {
439
- const baseValue = evaluate(expr.base, ctx);
440
- if (baseValue == null) return void 0;
441
- const pathParts = expr.path.split(".");
442
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
443
- let value = baseValue;
444
- for (const part of pathParts) {
445
- if (forbiddenKeys.has(part)) return void 0;
446
- if (value == null) return void 0;
447
- value = value[part];
448
- }
449
- return value;
450
- }
451
- case "route": {
452
- const source = expr.source ?? "param";
453
- const routeCtx = ctx.route;
454
- if (!routeCtx) return "";
455
- switch (source) {
456
- case "param":
457
- return routeCtx.params[expr.name] ?? "";
458
- case "query":
459
- return routeCtx.query[expr.name] ?? "";
460
- case "path":
461
- return routeCtx.path;
462
- }
463
- }
464
- case "import": {
465
- const importData = ctx.imports?.[expr.name];
466
- if (importData === void 0) return void 0;
467
- if (expr.path) {
468
- return getNestedValue(importData, expr.path);
469
- }
470
- return importData;
471
- }
472
- case "data": {
473
- const dataValue = ctx.imports?.[expr.name];
474
- if (dataValue === void 0) return void 0;
475
- if (expr.path) {
476
- return getNestedValue(dataValue, expr.path);
477
- }
478
- return dataValue;
479
- }
480
- case "ref":
481
- return null;
482
- case "index": {
483
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
484
- const base = evaluate(expr.base, ctx);
485
- const key = evaluate(expr.key, ctx);
486
- if (base == null || key == null) return void 0;
487
- if (typeof key === "string" && forbiddenKeys.has(key)) return void 0;
488
- return base[key];
489
- }
490
- case "param": {
491
- return void 0;
492
- }
493
- case "style": {
494
- return evaluateStyle(expr, ctx);
495
- }
496
- case "concat": {
497
- return expr.items.map((item) => {
498
- const val = evaluate(item, ctx);
499
- return val == null ? "" : String(val);
500
- }).join("");
501
- }
502
- case "validity": {
503
- return false;
504
- }
505
- case "call": {
506
- const callExpr = expr;
507
- if (callExpr.target === null) {
508
- const globalArgs = callExpr.args?.map((arg) => {
509
- if (arg.expr === "lambda") return arg;
510
- return evaluate(arg, ctx);
511
- }) ?? [];
512
- return callGlobalFunction(callExpr.method, globalArgs);
513
- }
514
- const target = evaluate(callExpr.target, ctx);
515
- if (target == null) return void 0;
516
- const args = callExpr.args?.map((arg) => {
517
- if (arg.expr === "lambda") return arg;
518
- return evaluate(arg, ctx);
519
- }) ?? [];
520
- if (Array.isArray(target)) {
521
- return callArrayMethod(target, callExpr.method, args, ctx, callExpr.args);
522
- }
523
- if (typeof target === "string") {
524
- return callStringMethod(target, callExpr.method, args);
525
- }
526
- if (target === Math) {
527
- return callMathMethod(callExpr.method, args);
528
- }
529
- if (target === Date) {
530
- return callDateStaticMethod(callExpr.method, args);
531
- }
532
- if (target instanceof Date) {
533
- return callDateInstanceMethod(target, callExpr.method);
534
- }
535
- return void 0;
536
- }
537
- case "lambda": {
538
- return void 0;
539
- }
540
- case "array": {
541
- const arrayExpr = expr;
542
- return arrayExpr.elements.map((elem) => evaluate(elem, ctx));
543
- }
544
- case "obj": {
545
- const objExpr = expr;
546
- const result = {};
547
- for (const [key, value] of Object.entries(objExpr.props)) {
548
- result[key] = evaluate(value, ctx);
549
- }
550
- return result;
551
- }
552
- default: {
553
- const _exhaustiveCheck = expr;
554
- throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustiveCheck)}`);
555
- }
556
- }
557
- }
558
- function getNestedValue(obj, path) {
559
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
560
- const parts = path.split(".");
561
- let value = obj;
562
- for (const part of parts) {
563
- if (forbiddenKeys.has(part)) {
564
- return void 0;
565
- }
566
- if (value == null) {
567
- return void 0;
568
- }
569
- if (Array.isArray(value)) {
570
- const index = Number(part);
571
- if (Number.isInteger(index) && index >= 0) {
572
- value = value[index];
573
- } else {
574
- value = value[part];
575
- }
576
- } else if (typeof value === "object") {
577
- value = value[part];
578
- } else {
579
- return void 0;
580
- }
581
- }
582
- return value;
583
- }
584
- function evaluateBinary(op, left, right, ctx) {
585
- if (op === "&&") {
586
- const leftVal2 = evaluate(left, ctx);
587
- if (!leftVal2) return leftVal2;
588
- return evaluate(right, ctx);
589
- }
590
- if (op === "||") {
591
- const leftVal2 = evaluate(left, ctx);
592
- if (leftVal2) return leftVal2;
593
- return evaluate(right, ctx);
594
- }
595
- const leftVal = evaluate(left, ctx);
596
- const rightVal = evaluate(right, ctx);
597
- switch (op) {
598
- case "+":
599
- if (typeof leftVal === "number" && typeof rightVal === "number") {
600
- return leftVal + rightVal;
601
- }
602
- return String(leftVal) + String(rightVal);
603
- case "-":
604
- return (typeof leftVal === "number" ? leftVal : 0) - (typeof rightVal === "number" ? rightVal : 0);
605
- case "*":
606
- return (typeof leftVal === "number" ? leftVal : 0) * (typeof rightVal === "number" ? rightVal : 0);
607
- case "/": {
608
- const dividend = typeof leftVal === "number" ? leftVal : 0;
609
- const divisor = typeof rightVal === "number" ? rightVal : 0;
610
- if (divisor === 0) {
611
- return dividend === 0 ? NaN : dividend > 0 ? Infinity : -Infinity;
612
- }
613
- return dividend / divisor;
614
- }
615
- case "==":
616
- return leftVal === rightVal;
617
- case "!=":
618
- return leftVal !== rightVal;
619
- case "<":
620
- if (typeof leftVal === "number" && typeof rightVal === "number") {
621
- return leftVal < rightVal;
622
- }
623
- return String(leftVal) < String(rightVal);
624
- case "<=":
625
- if (typeof leftVal === "number" && typeof rightVal === "number") {
626
- return leftVal <= rightVal;
627
- }
628
- return String(leftVal) <= String(rightVal);
629
- case ">":
630
- if (typeof leftVal === "number" && typeof rightVal === "number") {
631
- return leftVal > rightVal;
632
- }
633
- return String(leftVal) > String(rightVal);
634
- case ">=":
635
- if (typeof leftVal === "number" && typeof rightVal === "number") {
636
- return leftVal >= rightVal;
637
- }
638
- return String(leftVal) >= String(rightVal);
639
- default:
640
- throw new Error("Unknown binary operator: " + op);
641
- }
642
- }
643
- function evaluateStyle(expr, ctx) {
644
- const preset = ctx.styles?.[expr.name];
645
- if (!preset) return "";
646
- let classes = preset.base;
647
- if (preset.variants) {
648
- for (const variantKey of Object.keys(preset.variants)) {
649
- let variantValueStr = null;
650
- if (expr.variants?.[variantKey]) {
651
- let variantValue;
652
- try {
653
- variantValue = evaluate(expr.variants[variantKey], ctx);
654
- } catch {
655
- continue;
656
- }
657
- if (variantValue != null) {
658
- variantValueStr = String(variantValue);
659
- }
660
- } else if (preset.defaultVariants?.[variantKey] !== void 0) {
661
- variantValueStr = preset.defaultVariants[variantKey];
662
- }
663
- if (variantValueStr !== null) {
664
- const variantClasses = preset.variants[variantKey]?.[variantValueStr];
665
- if (variantClasses) {
666
- classes += " " + variantClasses;
667
- }
668
- }
669
- }
670
- }
671
- return classes.trim();
672
- }
673
158
  function formatValue(value) {
674
159
  if (value === null || value === void 0) {
675
160
  return "";
@@ -679,6 +164,11 @@ function formatValue(value) {
679
164
  }
680
165
  return String(value);
681
166
  }
167
+
168
+ // src/renderer.ts
169
+ function isEventHandler(value) {
170
+ return typeof value === "object" && value !== null && "event" in value && "action" in value;
171
+ }
682
172
  async function renderNode(node, ctx) {
683
173
  switch (node.kind) {
684
174
  case "element":
@@ -720,7 +210,7 @@ async function renderElement(node, ctx) {
720
210
  if (isEventHandler(propValue)) {
721
211
  continue;
722
212
  }
723
- const value = evaluate(propValue, ctx);
213
+ const value = coreEvaluate(propValue, toCoreContext(ctx));
724
214
  if (value === false) {
725
215
  continue;
726
216
  }
@@ -746,11 +236,11 @@ async function renderElement(node, ctx) {
746
236
  return `<${tag}${attrs}>${childrenHtml}</${tag}>`;
747
237
  }
748
238
  function renderText(node, ctx) {
749
- const value = evaluate(node.value, ctx);
239
+ const value = coreEvaluate(node.value, toCoreContext(ctx));
750
240
  return escapeHtml(formatValue(value));
751
241
  }
752
242
  async function renderIf(node, ctx) {
753
- const condition = evaluate(node.condition, ctx);
243
+ const condition = coreEvaluate(node.condition, toCoreContext(ctx));
754
244
  if (condition) {
755
245
  const content = await renderNode(node.then, ctx);
756
246
  return `<!--if:then-->${content}`;
@@ -762,7 +252,7 @@ async function renderIf(node, ctx) {
762
252
  return "<!--if:none-->";
763
253
  }
764
254
  async function renderEach(node, ctx) {
765
- const items = evaluate(node.items, ctx);
255
+ const items = coreEvaluate(node.items, toCoreContext(ctx));
766
256
  if (!Array.isArray(items)) {
767
257
  return "";
768
258
  }
@@ -785,13 +275,14 @@ async function renderEach(node, ctx) {
785
275
  return result;
786
276
  }
787
277
  async function renderMarkdown(node, ctx) {
788
- const content = evaluate(node.content, ctx);
278
+ const content = coreEvaluate(node.content, toCoreContext(ctx));
789
279
  const html = await parseMarkdownSSRAsync(formatValue(content));
790
280
  return `<div class="constela-markdown">${html}</div>`;
791
281
  }
792
282
  async function renderCode(node, ctx) {
793
- const language = formatValue(evaluate(node.language, ctx));
794
- const content = formatValue(evaluate(node.content, ctx));
283
+ const coreCtx = toCoreContext(ctx);
284
+ const language = formatValue(coreEvaluate(node.language, coreCtx));
285
+ const content = formatValue(coreEvaluate(node.content, coreCtx));
795
286
  const highlightedCode = await renderCodeSSR(content, language);
796
287
  const languageBadge = language ? `<div class="absolute right-12 top-3 z-10 rounded bg-muted-foreground/20 px-2 py-0.5 text-xs font-medium text-muted-foreground">${escapeHtml(language)}</div>` : "";
797
288
  const copyButton = `<button class="constela-copy-btn absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-md border border-border bg-background/80 opacity-0 transition-opacity hover:bg-muted group-hover:opacity-100" data-copy-target="code" aria-label="Copy code"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>`;
@@ -810,7 +301,7 @@ async function renderLocalState(node, ctx) {
810
301
  const initial = field.initial;
811
302
  if (initial && typeof initial === "object" && "expr" in initial) {
812
303
  const evalCtx = { ...ctx, locals: progressiveLocals };
813
- localStateValues[name] = evaluate(initial, evalCtx);
304
+ localStateValues[name] = coreEvaluate(initial, toCoreContext(evalCtx));
814
305
  } else {
815
306
  localStateValues[name] = initial;
816
307
  }
@@ -881,575 +372,11 @@ async function renderToString(program, options) {
881
372
  }
882
373
 
883
374
  // src/streaming.ts
884
- import { isCookieInitialExpr as isCookieInitialExpr2 } from "@constela/core";
885
- var VOID_ELEMENTS2 = /* @__PURE__ */ new Set([
886
- "area",
887
- "base",
888
- "br",
889
- "col",
890
- "embed",
891
- "hr",
892
- "img",
893
- "input",
894
- "link",
895
- "meta",
896
- "param",
897
- "source",
898
- "track",
899
- "wbr"
900
- ]);
901
- var SAFE_ARRAY_METHODS2 = /* @__PURE__ */ new Set([
902
- "length",
903
- "at",
904
- "includes",
905
- "slice",
906
- "indexOf",
907
- "join",
908
- "filter",
909
- "map",
910
- "find",
911
- "findIndex",
912
- "some",
913
- "every"
914
- ]);
915
- var SAFE_STRING_METHODS2 = /* @__PURE__ */ new Set([
916
- "length",
917
- "charAt",
918
- "substring",
919
- "slice",
920
- "split",
921
- "trim",
922
- "toUpperCase",
923
- "toLowerCase",
924
- "replace",
925
- "includes",
926
- "startsWith",
927
- "endsWith",
928
- "indexOf"
929
- ]);
930
- var SAFE_MATH_METHODS2 = /* @__PURE__ */ new Set([
931
- "min",
932
- "max",
933
- "round",
934
- "floor",
935
- "ceil",
936
- "abs",
937
- "sqrt",
938
- "pow",
939
- "random",
940
- "sin",
941
- "cos",
942
- "tan"
943
- ]);
944
- var SAFE_DATE_STATIC_METHODS2 = /* @__PURE__ */ new Set(["now", "parse"]);
945
- var SAFE_DATE_INSTANCE_METHODS2 = /* @__PURE__ */ new Set([
946
- "toISOString",
947
- "toDateString",
948
- "toTimeString",
949
- "getTime",
950
- "getFullYear",
951
- "getMonth",
952
- "getDate",
953
- "getHours",
954
- "getMinutes",
955
- "getSeconds",
956
- "getMilliseconds"
957
- ]);
375
+ import { isCookieInitialExpr as isCookieInitialExpr2, evaluate as coreEvaluate2 } from "@constela/core";
958
376
  var CHUNK_SIZE_THRESHOLD = 1024;
959
377
  function isEventHandler2(value) {
960
378
  return typeof value === "object" && value !== null && "event" in value && "action" in value;
961
379
  }
962
- function createLambdaFunction2(lambda, ctx) {
963
- return (item, index) => {
964
- const lambdaLocals = {
965
- ...ctx.locals,
966
- [lambda.param]: item
967
- };
968
- if (lambda.index !== void 0) {
969
- lambdaLocals[lambda.index] = index;
970
- }
971
- return evaluate2(lambda.body, { ...ctx, locals: lambdaLocals });
972
- };
973
- }
974
- function callArrayMethod2(target, method, args, ctx, rawArgs) {
975
- if (!SAFE_ARRAY_METHODS2.has(method)) return void 0;
976
- switch (method) {
977
- case "length":
978
- return target.length;
979
- case "at": {
980
- const index = typeof args[0] === "number" ? args[0] : 0;
981
- return target.at(index);
982
- }
983
- case "includes": {
984
- const searchElement = args[0];
985
- const fromIndex = typeof args[1] === "number" ? args[1] : void 0;
986
- return target.includes(searchElement, fromIndex);
987
- }
988
- case "slice": {
989
- const start = typeof args[0] === "number" ? args[0] : void 0;
990
- const end = typeof args[1] === "number" ? args[1] : void 0;
991
- return target.slice(start, end);
992
- }
993
- case "indexOf": {
994
- const searchElement = args[0];
995
- const fromIndex = typeof args[1] === "number" ? args[1] : void 0;
996
- return target.indexOf(searchElement, fromIndex);
997
- }
998
- case "join": {
999
- const separator = typeof args[0] === "string" ? args[0] : ",";
1000
- return target.join(separator);
1001
- }
1002
- case "filter": {
1003
- const lambdaExpr = rawArgs?.[0];
1004
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
1005
- const fn = createLambdaFunction2(lambdaExpr, ctx);
1006
- return target.filter((item, index) => !!fn(item, index));
1007
- }
1008
- case "map": {
1009
- const lambdaExpr = rawArgs?.[0];
1010
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
1011
- const fn = createLambdaFunction2(lambdaExpr, ctx);
1012
- return target.map((item, index) => fn(item, index));
1013
- }
1014
- case "find": {
1015
- const lambdaExpr = rawArgs?.[0];
1016
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
1017
- const fn = createLambdaFunction2(lambdaExpr, ctx);
1018
- return target.find((item, index) => !!fn(item, index));
1019
- }
1020
- case "findIndex": {
1021
- const lambdaExpr = rawArgs?.[0];
1022
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
1023
- const fn = createLambdaFunction2(lambdaExpr, ctx);
1024
- return target.findIndex((item, index) => !!fn(item, index));
1025
- }
1026
- case "some": {
1027
- const lambdaExpr = rawArgs?.[0];
1028
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
1029
- const fn = createLambdaFunction2(lambdaExpr, ctx);
1030
- return target.some((item, index) => !!fn(item, index));
1031
- }
1032
- case "every": {
1033
- const lambdaExpr = rawArgs?.[0];
1034
- if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
1035
- const fn = createLambdaFunction2(lambdaExpr, ctx);
1036
- return target.every((item, index) => !!fn(item, index));
1037
- }
1038
- default:
1039
- return void 0;
1040
- }
1041
- }
1042
- function callStringMethod2(target, method, args) {
1043
- if (!SAFE_STRING_METHODS2.has(method)) return void 0;
1044
- switch (method) {
1045
- case "length":
1046
- return target.length;
1047
- case "charAt": {
1048
- const index = typeof args[0] === "number" ? args[0] : 0;
1049
- return target.charAt(index);
1050
- }
1051
- case "substring": {
1052
- const start = typeof args[0] === "number" ? args[0] : 0;
1053
- const end = typeof args[1] === "number" ? args[1] : void 0;
1054
- return target.substring(start, end);
1055
- }
1056
- case "slice": {
1057
- const start = typeof args[0] === "number" ? args[0] : void 0;
1058
- const end = typeof args[1] === "number" ? args[1] : void 0;
1059
- return target.slice(start, end);
1060
- }
1061
- case "split": {
1062
- const separator = typeof args[0] === "string" ? args[0] : "";
1063
- return target.split(separator);
1064
- }
1065
- case "trim":
1066
- return target.trim();
1067
- case "toUpperCase":
1068
- return target.toUpperCase();
1069
- case "toLowerCase":
1070
- return target.toLowerCase();
1071
- case "replace": {
1072
- const search = typeof args[0] === "string" ? args[0] : "";
1073
- const replace = typeof args[1] === "string" ? args[1] : "";
1074
- return target.replace(search, replace);
1075
- }
1076
- case "includes": {
1077
- const search = typeof args[0] === "string" ? args[0] : "";
1078
- const position = typeof args[1] === "number" ? args[1] : void 0;
1079
- return target.includes(search, position);
1080
- }
1081
- case "startsWith": {
1082
- const search = typeof args[0] === "string" ? args[0] : "";
1083
- const position = typeof args[1] === "number" ? args[1] : void 0;
1084
- return target.startsWith(search, position);
1085
- }
1086
- case "endsWith": {
1087
- const search = typeof args[0] === "string" ? args[0] : "";
1088
- const length = typeof args[1] === "number" ? args[1] : void 0;
1089
- return target.endsWith(search, length);
1090
- }
1091
- case "indexOf": {
1092
- const search = typeof args[0] === "string" ? args[0] : "";
1093
- const position = typeof args[1] === "number" ? args[1] : void 0;
1094
- return target.indexOf(search, position);
1095
- }
1096
- default:
1097
- return void 0;
1098
- }
1099
- }
1100
- function callMathMethod2(method, args) {
1101
- if (!SAFE_MATH_METHODS2.has(method)) return void 0;
1102
- const numbers = args.filter((a) => typeof a === "number");
1103
- switch (method) {
1104
- case "min":
1105
- return numbers.length > 0 ? Math.min(...numbers) : void 0;
1106
- case "max":
1107
- return numbers.length > 0 ? Math.max(...numbers) : void 0;
1108
- case "round":
1109
- return numbers[0] !== void 0 ? Math.round(numbers[0]) : void 0;
1110
- case "floor":
1111
- return numbers[0] !== void 0 ? Math.floor(numbers[0]) : void 0;
1112
- case "ceil":
1113
- return numbers[0] !== void 0 ? Math.ceil(numbers[0]) : void 0;
1114
- case "abs":
1115
- return numbers[0] !== void 0 ? Math.abs(numbers[0]) : void 0;
1116
- case "sqrt":
1117
- return numbers[0] !== void 0 ? Math.sqrt(numbers[0]) : void 0;
1118
- case "pow":
1119
- return numbers[0] !== void 0 && numbers[1] !== void 0 ? Math.pow(numbers[0], numbers[1]) : void 0;
1120
- case "random":
1121
- return Math.random();
1122
- case "sin":
1123
- return numbers[0] !== void 0 ? Math.sin(numbers[0]) : void 0;
1124
- case "cos":
1125
- return numbers[0] !== void 0 ? Math.cos(numbers[0]) : void 0;
1126
- case "tan":
1127
- return numbers[0] !== void 0 ? Math.tan(numbers[0]) : void 0;
1128
- default:
1129
- return void 0;
1130
- }
1131
- }
1132
- function callDateStaticMethod2(method, args) {
1133
- if (!SAFE_DATE_STATIC_METHODS2.has(method)) return void 0;
1134
- switch (method) {
1135
- case "now":
1136
- return Date.now();
1137
- case "parse": {
1138
- const dateString = args[0];
1139
- return typeof dateString === "string" ? Date.parse(dateString) : void 0;
1140
- }
1141
- default:
1142
- return void 0;
1143
- }
1144
- }
1145
- function callDateInstanceMethod2(target, method) {
1146
- if (!SAFE_DATE_INSTANCE_METHODS2.has(method)) return void 0;
1147
- switch (method) {
1148
- case "toISOString":
1149
- return target.toISOString();
1150
- case "toDateString":
1151
- return target.toDateString();
1152
- case "toTimeString":
1153
- return target.toTimeString();
1154
- case "getTime":
1155
- return target.getTime();
1156
- case "getFullYear":
1157
- return target.getFullYear();
1158
- case "getMonth":
1159
- return target.getMonth();
1160
- case "getDate":
1161
- return target.getDate();
1162
- case "getHours":
1163
- return target.getHours();
1164
- case "getMinutes":
1165
- return target.getMinutes();
1166
- case "getSeconds":
1167
- return target.getSeconds();
1168
- case "getMilliseconds":
1169
- return target.getMilliseconds();
1170
- default:
1171
- return void 0;
1172
- }
1173
- }
1174
- function evaluate2(expr, ctx) {
1175
- switch (expr.expr) {
1176
- case "lit":
1177
- return expr.value;
1178
- case "state":
1179
- return ctx.state.get(expr.name);
1180
- case "local":
1181
- return ctx.locals[expr.name];
1182
- case "var": {
1183
- let varName = expr.name;
1184
- let pathParts = [];
1185
- if (varName.includes(".")) {
1186
- const parts = varName.split(".");
1187
- varName = parts[0];
1188
- pathParts = parts.slice(1);
1189
- }
1190
- if (expr.path) {
1191
- pathParts = pathParts.concat(expr.path.split("."));
1192
- }
1193
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1194
- for (const part of pathParts) {
1195
- if (forbiddenKeys.has(part)) {
1196
- return void 0;
1197
- }
1198
- }
1199
- let value = ctx.locals[varName];
1200
- for (const part of pathParts) {
1201
- if (value == null) break;
1202
- value = value[part];
1203
- }
1204
- return value;
1205
- }
1206
- case "bin":
1207
- return evaluateBinary2(expr.op, expr.left, expr.right, ctx);
1208
- case "not":
1209
- return !evaluate2(expr.operand, ctx);
1210
- case "cond":
1211
- return evaluate2(expr.if, ctx) ? evaluate2(expr.then, ctx) : evaluate2(expr.else, ctx);
1212
- case "get": {
1213
- const baseValue = evaluate2(expr.base, ctx);
1214
- if (baseValue == null) return void 0;
1215
- const pathParts = expr.path.split(".");
1216
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1217
- let value = baseValue;
1218
- for (const part of pathParts) {
1219
- if (forbiddenKeys.has(part)) return void 0;
1220
- if (value == null) return void 0;
1221
- value = value[part];
1222
- }
1223
- return value;
1224
- }
1225
- case "route": {
1226
- const source = expr.source ?? "param";
1227
- const routeCtx = ctx.route;
1228
- if (!routeCtx) return "";
1229
- switch (source) {
1230
- case "param":
1231
- return routeCtx.params[expr.name] ?? "";
1232
- case "query":
1233
- return routeCtx.query[expr.name] ?? "";
1234
- case "path":
1235
- return routeCtx.path;
1236
- default:
1237
- return "";
1238
- }
1239
- }
1240
- case "import": {
1241
- const importData = ctx.imports?.[expr.name];
1242
- if (importData === void 0) return void 0;
1243
- if (expr.path) {
1244
- return getNestedValue2(importData, expr.path);
1245
- }
1246
- return importData;
1247
- }
1248
- case "data": {
1249
- const dataValue = ctx.imports?.[expr.name];
1250
- if (dataValue === void 0) return void 0;
1251
- if (expr.path) {
1252
- return getNestedValue2(dataValue, expr.path);
1253
- }
1254
- return dataValue;
1255
- }
1256
- case "ref":
1257
- return null;
1258
- case "index": {
1259
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1260
- const base = evaluate2(expr.base, ctx);
1261
- const key = evaluate2(expr.key, ctx);
1262
- if (base == null || key == null) return void 0;
1263
- if (typeof key === "string" && forbiddenKeys.has(key)) return void 0;
1264
- return base[key];
1265
- }
1266
- case "param": {
1267
- return void 0;
1268
- }
1269
- case "style": {
1270
- return evaluateStyle2(expr, ctx);
1271
- }
1272
- case "concat": {
1273
- return expr.items.map((item) => {
1274
- const val = evaluate2(item, ctx);
1275
- return val == null ? "" : String(val);
1276
- }).join("");
1277
- }
1278
- case "validity": {
1279
- return false;
1280
- }
1281
- case "call": {
1282
- const callExpr = expr;
1283
- if (callExpr.target === null) {
1284
- return void 0;
1285
- }
1286
- const target = evaluate2(callExpr.target, ctx);
1287
- if (target == null) return void 0;
1288
- const args = callExpr.args?.map((arg) => {
1289
- if (arg.expr === "lambda") return arg;
1290
- return evaluate2(arg, ctx);
1291
- }) ?? [];
1292
- if (Array.isArray(target)) {
1293
- return callArrayMethod2(target, callExpr.method, args, ctx, callExpr.args);
1294
- }
1295
- if (typeof target === "string") {
1296
- return callStringMethod2(target, callExpr.method, args);
1297
- }
1298
- if (target === Math) {
1299
- return callMathMethod2(callExpr.method, args);
1300
- }
1301
- if (target === Date) {
1302
- return callDateStaticMethod2(callExpr.method, args);
1303
- }
1304
- if (target instanceof Date) {
1305
- return callDateInstanceMethod2(target, callExpr.method);
1306
- }
1307
- return void 0;
1308
- }
1309
- case "lambda": {
1310
- return void 0;
1311
- }
1312
- case "array": {
1313
- const arrayExpr = expr;
1314
- return arrayExpr.elements.map((elem) => evaluate2(elem, ctx));
1315
- }
1316
- case "obj": {
1317
- const objExpr = expr;
1318
- const result = {};
1319
- for (const [key, value] of Object.entries(objExpr.props)) {
1320
- result[key] = evaluate2(value, ctx);
1321
- }
1322
- return result;
1323
- }
1324
- default: {
1325
- return void 0;
1326
- }
1327
- }
1328
- }
1329
- function getNestedValue2(obj, path) {
1330
- const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1331
- const parts = path.split(".");
1332
- let value = obj;
1333
- for (const part of parts) {
1334
- if (forbiddenKeys.has(part)) {
1335
- return void 0;
1336
- }
1337
- if (value == null) {
1338
- return void 0;
1339
- }
1340
- if (Array.isArray(value)) {
1341
- const index = Number(part);
1342
- if (Number.isInteger(index) && index >= 0) {
1343
- value = value[index];
1344
- } else {
1345
- value = value[part];
1346
- }
1347
- } else if (typeof value === "object") {
1348
- value = value[part];
1349
- } else {
1350
- return void 0;
1351
- }
1352
- }
1353
- return value;
1354
- }
1355
- function evaluateBinary2(op, left, right, ctx) {
1356
- if (op === "&&") {
1357
- const leftVal2 = evaluate2(left, ctx);
1358
- if (!leftVal2) return leftVal2;
1359
- return evaluate2(right, ctx);
1360
- }
1361
- if (op === "||") {
1362
- const leftVal2 = evaluate2(left, ctx);
1363
- if (leftVal2) return leftVal2;
1364
- return evaluate2(right, ctx);
1365
- }
1366
- const leftVal = evaluate2(left, ctx);
1367
- const rightVal = evaluate2(right, ctx);
1368
- switch (op) {
1369
- case "+":
1370
- if (typeof leftVal === "number" && typeof rightVal === "number") {
1371
- return leftVal + rightVal;
1372
- }
1373
- return String(leftVal) + String(rightVal);
1374
- case "-":
1375
- return (typeof leftVal === "number" ? leftVal : 0) - (typeof rightVal === "number" ? rightVal : 0);
1376
- case "*":
1377
- return (typeof leftVal === "number" ? leftVal : 0) * (typeof rightVal === "number" ? rightVal : 0);
1378
- case "/": {
1379
- const dividend = typeof leftVal === "number" ? leftVal : 0;
1380
- const divisor = typeof rightVal === "number" ? rightVal : 0;
1381
- if (divisor === 0) {
1382
- return dividend === 0 ? NaN : dividend > 0 ? Infinity : -Infinity;
1383
- }
1384
- return dividend / divisor;
1385
- }
1386
- case "==":
1387
- return leftVal === rightVal;
1388
- case "!=":
1389
- return leftVal !== rightVal;
1390
- case "<":
1391
- if (typeof leftVal === "number" && typeof rightVal === "number") {
1392
- return leftVal < rightVal;
1393
- }
1394
- return String(leftVal) < String(rightVal);
1395
- case "<=":
1396
- if (typeof leftVal === "number" && typeof rightVal === "number") {
1397
- return leftVal <= rightVal;
1398
- }
1399
- return String(leftVal) <= String(rightVal);
1400
- case ">":
1401
- if (typeof leftVal === "number" && typeof rightVal === "number") {
1402
- return leftVal > rightVal;
1403
- }
1404
- return String(leftVal) > String(rightVal);
1405
- case ">=":
1406
- if (typeof leftVal === "number" && typeof rightVal === "number") {
1407
- return leftVal >= rightVal;
1408
- }
1409
- return String(leftVal) >= String(rightVal);
1410
- default:
1411
- throw new Error("Unknown binary operator: " + op);
1412
- }
1413
- }
1414
- function evaluateStyle2(expr, ctx) {
1415
- const preset = ctx.styles?.[expr.name];
1416
- if (!preset) return "";
1417
- let classes = preset.base;
1418
- if (preset.variants) {
1419
- for (const variantKey of Object.keys(preset.variants)) {
1420
- let variantValueStr = null;
1421
- if (expr.variants?.[variantKey]) {
1422
- let variantValue;
1423
- try {
1424
- variantValue = evaluate2(expr.variants[variantKey], ctx);
1425
- } catch {
1426
- continue;
1427
- }
1428
- if (variantValue != null) {
1429
- variantValueStr = String(variantValue);
1430
- }
1431
- } else if (preset.defaultVariants?.[variantKey] !== void 0) {
1432
- variantValueStr = preset.defaultVariants[variantKey];
1433
- }
1434
- if (variantValueStr !== null) {
1435
- const variantClasses = preset.variants[variantKey]?.[variantValueStr];
1436
- if (variantClasses) {
1437
- classes += " " + variantClasses;
1438
- }
1439
- }
1440
- }
1441
- }
1442
- return classes.trim();
1443
- }
1444
- function formatValue2(value) {
1445
- if (value === null || value === void 0) {
1446
- return "";
1447
- }
1448
- if (typeof value === "object") {
1449
- return JSON.stringify(value);
1450
- }
1451
- return String(value);
1452
- }
1453
380
  function flush(ctx, force = false) {
1454
381
  if (ctx.aborted) return;
1455
382
  const { buffer, options, controller } = ctx;
@@ -1535,14 +462,14 @@ async function renderSuspenseToStream(node, ctx) {
1535
462
  async function renderElementToStream(node, ctx) {
1536
463
  if (checkAbort(ctx)) return;
1537
464
  const tag = node.tag;
1538
- const isVoid = VOID_ELEMENTS2.has(tag);
465
+ const isVoid = VOID_ELEMENTS.has(tag);
1539
466
  let attrs = "";
1540
467
  if (node.props) {
1541
468
  for (const [propName, propValue] of Object.entries(node.props)) {
1542
469
  if (isEventHandler2(propValue)) {
1543
470
  continue;
1544
471
  }
1545
- const value = evaluate2(propValue, ctx);
472
+ const value = coreEvaluate2(propValue, toCoreContext(ctx));
1546
473
  if (value === false) {
1547
474
  continue;
1548
475
  }
@@ -1569,12 +496,12 @@ async function renderElementToStream(node, ctx) {
1569
496
  write(ctx, "</" + tag + ">");
1570
497
  }
1571
498
  function renderTextToStream(node, ctx) {
1572
- const value = evaluate2(node.value, ctx);
1573
- write(ctx, escapeHtml(formatValue2(value)));
499
+ const value = coreEvaluate2(node.value, toCoreContext(ctx));
500
+ write(ctx, escapeHtml(formatValue(value)));
1574
501
  }
1575
502
  async function renderIfToStream(node, ctx) {
1576
503
  if (checkAbort(ctx)) return;
1577
- const condition = evaluate2(node.condition, ctx);
504
+ const condition = coreEvaluate2(node.condition, toCoreContext(ctx));
1578
505
  if (condition) {
1579
506
  write(ctx, "<!--if:then-->");
1580
507
  await renderNodeToStream(node.then, ctx);
@@ -1587,7 +514,7 @@ async function renderIfToStream(node, ctx) {
1587
514
  }
1588
515
  async function renderEachToStream(node, ctx) {
1589
516
  if (checkAbort(ctx)) return;
1590
- const items = evaluate2(node.items, ctx);
517
+ const items = coreEvaluate2(node.items, toCoreContext(ctx));
1591
518
  if (!Array.isArray(items)) {
1592
519
  return;
1593
520
  }
@@ -1612,12 +539,13 @@ async function renderEachToStream(node, ctx) {
1612
539
  }
1613
540
  }
1614
541
  async function renderMarkdownToStream(node, ctx) {
1615
- const content = evaluate2(node.content, ctx);
1616
- write(ctx, '<div class="constela-markdown">' + escapeHtml(formatValue2(content)) + "</div>");
542
+ const content = coreEvaluate2(node.content, toCoreContext(ctx));
543
+ write(ctx, '<div class="constela-markdown">' + escapeHtml(formatValue(content)) + "</div>");
1617
544
  }
1618
545
  async function renderCodeToStream(node, ctx) {
1619
- const language = formatValue2(evaluate2(node.language, ctx));
1620
- const content = formatValue2(evaluate2(node.content, ctx));
546
+ const coreCtx = toCoreContext(ctx);
547
+ const language = formatValue(coreEvaluate2(node.language, coreCtx));
548
+ const content = formatValue(coreEvaluate2(node.content, coreCtx));
1621
549
  const languageBadge = language ? '<div class="absolute right-12 top-3 z-10 rounded bg-muted-foreground/20 px-2 py-0.5 text-xs font-medium text-muted-foreground">' + escapeHtml(language) + "</div>" : "";
1622
550
  const copyButton = '<button class="constela-copy-btn absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-md border border-border bg-background/80 opacity-0 transition-opacity hover:bg-muted group-hover:opacity-100" data-copy-target="code" aria-label="Copy code"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>';
1623
551
  write(ctx, '<div class="constela-code" data-code-content="' + escapeHtml(content) + '"><div class="group relative">' + languageBadge + copyButton + "<pre><code>" + escapeHtml(content) + "</code></pre></div></div>");
@@ -1636,7 +564,7 @@ async function renderLocalStateToStream(node, ctx) {
1636
564
  const initial = field.initial;
1637
565
  if (initial && typeof initial === "object" && "expr" in initial) {
1638
566
  const evalCtx = { ...ctx, locals: progressiveLocals };
1639
- localStateValues[name] = evaluate2(initial, evalCtx);
567
+ localStateValues[name] = coreEvaluate2(initial, toCoreContext(evalCtx));
1640
568
  } else {
1641
569
  localStateValues[name] = initial;
1642
570
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/server",
3
- "version": "17.0.0",
3
+ "version": "18.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.16",
19
- "@constela/core": "^0.21.0"
18
+ "@constela/compiler": "^0.15.21",
19
+ "@constela/core": "^0.22.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/core": "0.21.0",
33
- "@constela/compiler": "0.15.16"
32
+ "@constela/compiler": "0.15.21",
33
+ "@constela/core": "0.22.0"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=20.0.0"