@constela/server 12.0.2 → 13.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 +70 -3
- package/dist/index.js +886 -0
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CompiledProgram } from '@constela/compiler';
|
|
2
|
+
import { StreamingRenderOptions } from '@constela/core';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* SSR Renderer
|
|
@@ -9,7 +10,7 @@ import { CompiledProgram } from '@constela/compiler';
|
|
|
9
10
|
/**
|
|
10
11
|
* Style preset definition for SSR
|
|
11
12
|
*/
|
|
12
|
-
interface StylePreset {
|
|
13
|
+
interface StylePreset$1 {
|
|
13
14
|
base: string;
|
|
14
15
|
variants?: Record<string, Record<string, string>>;
|
|
15
16
|
defaultVariants?: Record<string, string>;
|
|
@@ -27,7 +28,7 @@ interface RenderOptions {
|
|
|
27
28
|
path?: string;
|
|
28
29
|
};
|
|
29
30
|
imports?: Record<string, unknown>;
|
|
30
|
-
styles?: Record<string, StylePreset>;
|
|
31
|
+
styles?: Record<string, StylePreset$1>;
|
|
31
32
|
stateOverrides?: Record<string, unknown>;
|
|
32
33
|
cookies?: Record<string, string>;
|
|
33
34
|
}
|
|
@@ -40,4 +41,70 @@ interface RenderOptions {
|
|
|
40
41
|
*/
|
|
41
42
|
declare function renderToString(program: CompiledProgram, options?: RenderOptions): Promise<string>;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Streaming SSR Renderer
|
|
46
|
+
*
|
|
47
|
+
* Renders CompiledProgram to a ReadableStream for Server-Side Rendering.
|
|
48
|
+
* Uses Web Streams API for Edge Runtime compatibility.
|
|
49
|
+
*
|
|
50
|
+
* Features:
|
|
51
|
+
* - Three flush strategies: immediate, batched, manual
|
|
52
|
+
* - Backpressure support via controller.desiredSize
|
|
53
|
+
* - AbortSignal support for cancellation
|
|
54
|
+
* - Suspense boundary support for async content
|
|
55
|
+
*/
|
|
56
|
+
|
|
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
|
+
/**
|
|
69
|
+
* Extended options for streaming render
|
|
70
|
+
*/
|
|
71
|
+
interface StreamRenderOptions {
|
|
72
|
+
route?: {
|
|
73
|
+
params?: Record<string, string>;
|
|
74
|
+
query?: Record<string, string>;
|
|
75
|
+
path?: string;
|
|
76
|
+
};
|
|
77
|
+
imports?: Record<string, unknown>;
|
|
78
|
+
styles?: Record<string, StylePreset>;
|
|
79
|
+
stateOverrides?: Record<string, unknown>;
|
|
80
|
+
cookies?: Record<string, string>;
|
|
81
|
+
signal?: AbortSignal;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Options for HTML transform stream
|
|
85
|
+
*/
|
|
86
|
+
interface HtmlTransformOptions {
|
|
87
|
+
title: string;
|
|
88
|
+
lang?: string;
|
|
89
|
+
meta?: Record<string, string>;
|
|
90
|
+
stylesheets?: string[];
|
|
91
|
+
scripts?: string[];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Renders a CompiledProgram to a ReadableStream.
|
|
95
|
+
*
|
|
96
|
+
* @param program - The compiled program to render
|
|
97
|
+
* @param streamOptions - Streaming options (flush strategy, etc.)
|
|
98
|
+
* @param options - Optional render options including route context
|
|
99
|
+
* @returns ReadableStream of HTML strings
|
|
100
|
+
*/
|
|
101
|
+
declare function renderToStream(program: CompiledProgram, streamOptions: StreamingRenderOptions, options?: StreamRenderOptions): ReadableStream<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a TransformStream that wraps content with HTML document structure.
|
|
104
|
+
*
|
|
105
|
+
* @param options - HTML document options (title, meta, stylesheets, scripts)
|
|
106
|
+
* @returns TransformStream that wraps content with HTML structure
|
|
107
|
+
*/
|
|
108
|
+
declare function createHtmlTransformStream(options: HtmlTransformOptions): TransformStream<string, string>;
|
|
109
|
+
|
|
110
|
+
export { type HtmlTransformOptions, type RenderOptions, type StreamRenderOptions, createHtmlTransformStream, renderToStream, renderToString };
|
package/dist/index.js
CHANGED
|
@@ -682,6 +682,12 @@ async function renderNode(node, ctx) {
|
|
|
682
682
|
return await renderPortal(node, ctx);
|
|
683
683
|
case "localState":
|
|
684
684
|
return await renderLocalState(node, ctx);
|
|
685
|
+
case "island":
|
|
686
|
+
return await renderIsland(node, ctx);
|
|
687
|
+
case "suspense":
|
|
688
|
+
return await renderNode(node.content, ctx);
|
|
689
|
+
case "errorBoundary":
|
|
690
|
+
return await renderNode(node.content, ctx);
|
|
685
691
|
default: {
|
|
686
692
|
const _exhaustiveCheck = node;
|
|
687
693
|
throw new Error(`Unknown node kind: ${JSON.stringify(_exhaustiveCheck)}`);
|
|
@@ -794,6 +800,34 @@ async function renderLocalState(node, ctx) {
|
|
|
794
800
|
};
|
|
795
801
|
return await renderNode(node.child, childCtx);
|
|
796
802
|
}
|
|
803
|
+
async function renderIsland(node, ctx) {
|
|
804
|
+
const islandStateValues = {};
|
|
805
|
+
if (node.state) {
|
|
806
|
+
for (const [name, field] of Object.entries(node.state)) {
|
|
807
|
+
islandStateValues[name] = field.initial;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
const islandState = new Map(ctx.state);
|
|
811
|
+
for (const [name, value] of Object.entries(islandStateValues)) {
|
|
812
|
+
islandState.set(name, value);
|
|
813
|
+
}
|
|
814
|
+
const islandCtx = {
|
|
815
|
+
...ctx,
|
|
816
|
+
state: islandState
|
|
817
|
+
};
|
|
818
|
+
const content = await renderNode(node.content, islandCtx);
|
|
819
|
+
const dataAttrs = [
|
|
820
|
+
`data-island-id="${escapeHtml(node.id)}"`,
|
|
821
|
+
`data-island-strategy="${escapeHtml(node.strategy)}"`
|
|
822
|
+
];
|
|
823
|
+
if (node.strategyOptions) {
|
|
824
|
+
dataAttrs.push(`data-island-options="${escapeHtml(JSON.stringify(node.strategyOptions))}"`);
|
|
825
|
+
}
|
|
826
|
+
if (node.state) {
|
|
827
|
+
dataAttrs.push(`data-island-state="${escapeHtml(JSON.stringify(islandStateValues))}"`);
|
|
828
|
+
}
|
|
829
|
+
return `<div ${dataAttrs.join(" ")}>${content}</div>`;
|
|
830
|
+
}
|
|
797
831
|
async function renderToString(program, options) {
|
|
798
832
|
const state = /* @__PURE__ */ new Map();
|
|
799
833
|
for (const [name, field] of Object.entries(program.state)) {
|
|
@@ -820,6 +854,858 @@ async function renderToString(program, options) {
|
|
|
820
854
|
};
|
|
821
855
|
return await renderNode(program.view, ctx);
|
|
822
856
|
}
|
|
857
|
+
|
|
858
|
+
// src/streaming.ts
|
|
859
|
+
import { isCookieInitialExpr as isCookieInitialExpr2 } from "@constela/core";
|
|
860
|
+
var VOID_ELEMENTS2 = /* @__PURE__ */ new Set([
|
|
861
|
+
"area",
|
|
862
|
+
"base",
|
|
863
|
+
"br",
|
|
864
|
+
"col",
|
|
865
|
+
"embed",
|
|
866
|
+
"hr",
|
|
867
|
+
"img",
|
|
868
|
+
"input",
|
|
869
|
+
"link",
|
|
870
|
+
"meta",
|
|
871
|
+
"param",
|
|
872
|
+
"source",
|
|
873
|
+
"track",
|
|
874
|
+
"wbr"
|
|
875
|
+
]);
|
|
876
|
+
var SAFE_ARRAY_METHODS2 = /* @__PURE__ */ new Set([
|
|
877
|
+
"length",
|
|
878
|
+
"at",
|
|
879
|
+
"includes",
|
|
880
|
+
"slice",
|
|
881
|
+
"indexOf",
|
|
882
|
+
"join",
|
|
883
|
+
"filter",
|
|
884
|
+
"map",
|
|
885
|
+
"find",
|
|
886
|
+
"findIndex",
|
|
887
|
+
"some",
|
|
888
|
+
"every"
|
|
889
|
+
]);
|
|
890
|
+
var SAFE_STRING_METHODS2 = /* @__PURE__ */ new Set([
|
|
891
|
+
"length",
|
|
892
|
+
"charAt",
|
|
893
|
+
"substring",
|
|
894
|
+
"slice",
|
|
895
|
+
"split",
|
|
896
|
+
"trim",
|
|
897
|
+
"toUpperCase",
|
|
898
|
+
"toLowerCase",
|
|
899
|
+
"replace",
|
|
900
|
+
"includes",
|
|
901
|
+
"startsWith",
|
|
902
|
+
"endsWith",
|
|
903
|
+
"indexOf"
|
|
904
|
+
]);
|
|
905
|
+
var SAFE_MATH_METHODS2 = /* @__PURE__ */ new Set([
|
|
906
|
+
"min",
|
|
907
|
+
"max",
|
|
908
|
+
"round",
|
|
909
|
+
"floor",
|
|
910
|
+
"ceil",
|
|
911
|
+
"abs",
|
|
912
|
+
"sqrt",
|
|
913
|
+
"pow",
|
|
914
|
+
"random",
|
|
915
|
+
"sin",
|
|
916
|
+
"cos",
|
|
917
|
+
"tan"
|
|
918
|
+
]);
|
|
919
|
+
var SAFE_DATE_STATIC_METHODS2 = /* @__PURE__ */ new Set(["now", "parse"]);
|
|
920
|
+
var SAFE_DATE_INSTANCE_METHODS2 = /* @__PURE__ */ new Set([
|
|
921
|
+
"toISOString",
|
|
922
|
+
"toDateString",
|
|
923
|
+
"toTimeString",
|
|
924
|
+
"getTime",
|
|
925
|
+
"getFullYear",
|
|
926
|
+
"getMonth",
|
|
927
|
+
"getDate",
|
|
928
|
+
"getHours",
|
|
929
|
+
"getMinutes",
|
|
930
|
+
"getSeconds",
|
|
931
|
+
"getMilliseconds"
|
|
932
|
+
]);
|
|
933
|
+
var CHUNK_SIZE_THRESHOLD = 1024;
|
|
934
|
+
function isEventHandler2(value) {
|
|
935
|
+
return typeof value === "object" && value !== null && "event" in value && "action" in value;
|
|
936
|
+
}
|
|
937
|
+
function createLambdaFunction2(lambda, ctx) {
|
|
938
|
+
return (item, index) => {
|
|
939
|
+
const lambdaLocals = {
|
|
940
|
+
...ctx.locals,
|
|
941
|
+
[lambda.param]: item
|
|
942
|
+
};
|
|
943
|
+
if (lambda.index !== void 0) {
|
|
944
|
+
lambdaLocals[lambda.index] = index;
|
|
945
|
+
}
|
|
946
|
+
return evaluate2(lambda.body, { ...ctx, locals: lambdaLocals });
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function callArrayMethod2(target, method, args, ctx, rawArgs) {
|
|
950
|
+
if (!SAFE_ARRAY_METHODS2.has(method)) return void 0;
|
|
951
|
+
switch (method) {
|
|
952
|
+
case "length":
|
|
953
|
+
return target.length;
|
|
954
|
+
case "at": {
|
|
955
|
+
const index = typeof args[0] === "number" ? args[0] : 0;
|
|
956
|
+
return target.at(index);
|
|
957
|
+
}
|
|
958
|
+
case "includes": {
|
|
959
|
+
const searchElement = args[0];
|
|
960
|
+
const fromIndex = typeof args[1] === "number" ? args[1] : void 0;
|
|
961
|
+
return target.includes(searchElement, fromIndex);
|
|
962
|
+
}
|
|
963
|
+
case "slice": {
|
|
964
|
+
const start = typeof args[0] === "number" ? args[0] : void 0;
|
|
965
|
+
const end = typeof args[1] === "number" ? args[1] : void 0;
|
|
966
|
+
return target.slice(start, end);
|
|
967
|
+
}
|
|
968
|
+
case "indexOf": {
|
|
969
|
+
const searchElement = args[0];
|
|
970
|
+
const fromIndex = typeof args[1] === "number" ? args[1] : void 0;
|
|
971
|
+
return target.indexOf(searchElement, fromIndex);
|
|
972
|
+
}
|
|
973
|
+
case "join": {
|
|
974
|
+
const separator = typeof args[0] === "string" ? args[0] : ",";
|
|
975
|
+
return target.join(separator);
|
|
976
|
+
}
|
|
977
|
+
case "filter": {
|
|
978
|
+
const lambdaExpr = rawArgs?.[0];
|
|
979
|
+
if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
|
|
980
|
+
const fn = createLambdaFunction2(lambdaExpr, ctx);
|
|
981
|
+
return target.filter((item, index) => !!fn(item, index));
|
|
982
|
+
}
|
|
983
|
+
case "map": {
|
|
984
|
+
const lambdaExpr = rawArgs?.[0];
|
|
985
|
+
if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
|
|
986
|
+
const fn = createLambdaFunction2(lambdaExpr, ctx);
|
|
987
|
+
return target.map((item, index) => fn(item, index));
|
|
988
|
+
}
|
|
989
|
+
case "find": {
|
|
990
|
+
const lambdaExpr = rawArgs?.[0];
|
|
991
|
+
if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
|
|
992
|
+
const fn = createLambdaFunction2(lambdaExpr, ctx);
|
|
993
|
+
return target.find((item, index) => !!fn(item, index));
|
|
994
|
+
}
|
|
995
|
+
case "findIndex": {
|
|
996
|
+
const lambdaExpr = rawArgs?.[0];
|
|
997
|
+
if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
|
|
998
|
+
const fn = createLambdaFunction2(lambdaExpr, ctx);
|
|
999
|
+
return target.findIndex((item, index) => !!fn(item, index));
|
|
1000
|
+
}
|
|
1001
|
+
case "some": {
|
|
1002
|
+
const lambdaExpr = rawArgs?.[0];
|
|
1003
|
+
if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
|
|
1004
|
+
const fn = createLambdaFunction2(lambdaExpr, ctx);
|
|
1005
|
+
return target.some((item, index) => !!fn(item, index));
|
|
1006
|
+
}
|
|
1007
|
+
case "every": {
|
|
1008
|
+
const lambdaExpr = rawArgs?.[0];
|
|
1009
|
+
if (!lambdaExpr || lambdaExpr.expr !== "lambda") return void 0;
|
|
1010
|
+
const fn = createLambdaFunction2(lambdaExpr, ctx);
|
|
1011
|
+
return target.every((item, index) => !!fn(item, index));
|
|
1012
|
+
}
|
|
1013
|
+
default:
|
|
1014
|
+
return void 0;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function callStringMethod2(target, method, args) {
|
|
1018
|
+
if (!SAFE_STRING_METHODS2.has(method)) return void 0;
|
|
1019
|
+
switch (method) {
|
|
1020
|
+
case "length":
|
|
1021
|
+
return target.length;
|
|
1022
|
+
case "charAt": {
|
|
1023
|
+
const index = typeof args[0] === "number" ? args[0] : 0;
|
|
1024
|
+
return target.charAt(index);
|
|
1025
|
+
}
|
|
1026
|
+
case "substring": {
|
|
1027
|
+
const start = typeof args[0] === "number" ? args[0] : 0;
|
|
1028
|
+
const end = typeof args[1] === "number" ? args[1] : void 0;
|
|
1029
|
+
return target.substring(start, end);
|
|
1030
|
+
}
|
|
1031
|
+
case "slice": {
|
|
1032
|
+
const start = typeof args[0] === "number" ? args[0] : void 0;
|
|
1033
|
+
const end = typeof args[1] === "number" ? args[1] : void 0;
|
|
1034
|
+
return target.slice(start, end);
|
|
1035
|
+
}
|
|
1036
|
+
case "split": {
|
|
1037
|
+
const separator = typeof args[0] === "string" ? args[0] : "";
|
|
1038
|
+
return target.split(separator);
|
|
1039
|
+
}
|
|
1040
|
+
case "trim":
|
|
1041
|
+
return target.trim();
|
|
1042
|
+
case "toUpperCase":
|
|
1043
|
+
return target.toUpperCase();
|
|
1044
|
+
case "toLowerCase":
|
|
1045
|
+
return target.toLowerCase();
|
|
1046
|
+
case "replace": {
|
|
1047
|
+
const search = typeof args[0] === "string" ? args[0] : "";
|
|
1048
|
+
const replace = typeof args[1] === "string" ? args[1] : "";
|
|
1049
|
+
return target.replace(search, replace);
|
|
1050
|
+
}
|
|
1051
|
+
case "includes": {
|
|
1052
|
+
const search = typeof args[0] === "string" ? args[0] : "";
|
|
1053
|
+
const position = typeof args[1] === "number" ? args[1] : void 0;
|
|
1054
|
+
return target.includes(search, position);
|
|
1055
|
+
}
|
|
1056
|
+
case "startsWith": {
|
|
1057
|
+
const search = typeof args[0] === "string" ? args[0] : "";
|
|
1058
|
+
const position = typeof args[1] === "number" ? args[1] : void 0;
|
|
1059
|
+
return target.startsWith(search, position);
|
|
1060
|
+
}
|
|
1061
|
+
case "endsWith": {
|
|
1062
|
+
const search = typeof args[0] === "string" ? args[0] : "";
|
|
1063
|
+
const length = typeof args[1] === "number" ? args[1] : void 0;
|
|
1064
|
+
return target.endsWith(search, length);
|
|
1065
|
+
}
|
|
1066
|
+
case "indexOf": {
|
|
1067
|
+
const search = typeof args[0] === "string" ? args[0] : "";
|
|
1068
|
+
const position = typeof args[1] === "number" ? args[1] : void 0;
|
|
1069
|
+
return target.indexOf(search, position);
|
|
1070
|
+
}
|
|
1071
|
+
default:
|
|
1072
|
+
return void 0;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
function callMathMethod2(method, args) {
|
|
1076
|
+
if (!SAFE_MATH_METHODS2.has(method)) return void 0;
|
|
1077
|
+
const numbers = args.filter((a) => typeof a === "number");
|
|
1078
|
+
switch (method) {
|
|
1079
|
+
case "min":
|
|
1080
|
+
return numbers.length > 0 ? Math.min(...numbers) : void 0;
|
|
1081
|
+
case "max":
|
|
1082
|
+
return numbers.length > 0 ? Math.max(...numbers) : void 0;
|
|
1083
|
+
case "round":
|
|
1084
|
+
return numbers[0] !== void 0 ? Math.round(numbers[0]) : void 0;
|
|
1085
|
+
case "floor":
|
|
1086
|
+
return numbers[0] !== void 0 ? Math.floor(numbers[0]) : void 0;
|
|
1087
|
+
case "ceil":
|
|
1088
|
+
return numbers[0] !== void 0 ? Math.ceil(numbers[0]) : void 0;
|
|
1089
|
+
case "abs":
|
|
1090
|
+
return numbers[0] !== void 0 ? Math.abs(numbers[0]) : void 0;
|
|
1091
|
+
case "sqrt":
|
|
1092
|
+
return numbers[0] !== void 0 ? Math.sqrt(numbers[0]) : void 0;
|
|
1093
|
+
case "pow":
|
|
1094
|
+
return numbers[0] !== void 0 && numbers[1] !== void 0 ? Math.pow(numbers[0], numbers[1]) : void 0;
|
|
1095
|
+
case "random":
|
|
1096
|
+
return Math.random();
|
|
1097
|
+
case "sin":
|
|
1098
|
+
return numbers[0] !== void 0 ? Math.sin(numbers[0]) : void 0;
|
|
1099
|
+
case "cos":
|
|
1100
|
+
return numbers[0] !== void 0 ? Math.cos(numbers[0]) : void 0;
|
|
1101
|
+
case "tan":
|
|
1102
|
+
return numbers[0] !== void 0 ? Math.tan(numbers[0]) : void 0;
|
|
1103
|
+
default:
|
|
1104
|
+
return void 0;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
function callDateStaticMethod2(method, args) {
|
|
1108
|
+
if (!SAFE_DATE_STATIC_METHODS2.has(method)) return void 0;
|
|
1109
|
+
switch (method) {
|
|
1110
|
+
case "now":
|
|
1111
|
+
return Date.now();
|
|
1112
|
+
case "parse": {
|
|
1113
|
+
const dateString = args[0];
|
|
1114
|
+
return typeof dateString === "string" ? Date.parse(dateString) : void 0;
|
|
1115
|
+
}
|
|
1116
|
+
default:
|
|
1117
|
+
return void 0;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
function callDateInstanceMethod2(target, method) {
|
|
1121
|
+
if (!SAFE_DATE_INSTANCE_METHODS2.has(method)) return void 0;
|
|
1122
|
+
switch (method) {
|
|
1123
|
+
case "toISOString":
|
|
1124
|
+
return target.toISOString();
|
|
1125
|
+
case "toDateString":
|
|
1126
|
+
return target.toDateString();
|
|
1127
|
+
case "toTimeString":
|
|
1128
|
+
return target.toTimeString();
|
|
1129
|
+
case "getTime":
|
|
1130
|
+
return target.getTime();
|
|
1131
|
+
case "getFullYear":
|
|
1132
|
+
return target.getFullYear();
|
|
1133
|
+
case "getMonth":
|
|
1134
|
+
return target.getMonth();
|
|
1135
|
+
case "getDate":
|
|
1136
|
+
return target.getDate();
|
|
1137
|
+
case "getHours":
|
|
1138
|
+
return target.getHours();
|
|
1139
|
+
case "getMinutes":
|
|
1140
|
+
return target.getMinutes();
|
|
1141
|
+
case "getSeconds":
|
|
1142
|
+
return target.getSeconds();
|
|
1143
|
+
case "getMilliseconds":
|
|
1144
|
+
return target.getMilliseconds();
|
|
1145
|
+
default:
|
|
1146
|
+
return void 0;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function evaluate2(expr, ctx) {
|
|
1150
|
+
switch (expr.expr) {
|
|
1151
|
+
case "lit":
|
|
1152
|
+
return expr.value;
|
|
1153
|
+
case "state":
|
|
1154
|
+
return ctx.state.get(expr.name);
|
|
1155
|
+
case "var": {
|
|
1156
|
+
let varName = expr.name;
|
|
1157
|
+
let pathParts = [];
|
|
1158
|
+
if (varName.includes(".")) {
|
|
1159
|
+
const parts = varName.split(".");
|
|
1160
|
+
varName = parts[0];
|
|
1161
|
+
pathParts = parts.slice(1);
|
|
1162
|
+
}
|
|
1163
|
+
if (expr.path) {
|
|
1164
|
+
pathParts = pathParts.concat(expr.path.split("."));
|
|
1165
|
+
}
|
|
1166
|
+
const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1167
|
+
for (const part of pathParts) {
|
|
1168
|
+
if (forbiddenKeys.has(part)) {
|
|
1169
|
+
return void 0;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
let value = ctx.locals[varName];
|
|
1173
|
+
for (const part of pathParts) {
|
|
1174
|
+
if (value == null) break;
|
|
1175
|
+
value = value[part];
|
|
1176
|
+
}
|
|
1177
|
+
return value;
|
|
1178
|
+
}
|
|
1179
|
+
case "bin":
|
|
1180
|
+
return evaluateBinary2(expr.op, expr.left, expr.right, ctx);
|
|
1181
|
+
case "not":
|
|
1182
|
+
return !evaluate2(expr.operand, ctx);
|
|
1183
|
+
case "cond":
|
|
1184
|
+
return evaluate2(expr.if, ctx) ? evaluate2(expr.then, ctx) : evaluate2(expr.else, ctx);
|
|
1185
|
+
case "get": {
|
|
1186
|
+
const baseValue = evaluate2(expr.base, ctx);
|
|
1187
|
+
if (baseValue == null) return void 0;
|
|
1188
|
+
const pathParts = expr.path.split(".");
|
|
1189
|
+
const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1190
|
+
let value = baseValue;
|
|
1191
|
+
for (const part of pathParts) {
|
|
1192
|
+
if (forbiddenKeys.has(part)) return void 0;
|
|
1193
|
+
if (value == null) return void 0;
|
|
1194
|
+
value = value[part];
|
|
1195
|
+
}
|
|
1196
|
+
return value;
|
|
1197
|
+
}
|
|
1198
|
+
case "route": {
|
|
1199
|
+
const source = expr.source ?? "param";
|
|
1200
|
+
const routeCtx = ctx.route;
|
|
1201
|
+
if (!routeCtx) return "";
|
|
1202
|
+
switch (source) {
|
|
1203
|
+
case "param":
|
|
1204
|
+
return routeCtx.params[expr.name] ?? "";
|
|
1205
|
+
case "query":
|
|
1206
|
+
return routeCtx.query[expr.name] ?? "";
|
|
1207
|
+
case "path":
|
|
1208
|
+
return routeCtx.path;
|
|
1209
|
+
default:
|
|
1210
|
+
return "";
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
case "import": {
|
|
1214
|
+
const importData = ctx.imports?.[expr.name];
|
|
1215
|
+
if (importData === void 0) return void 0;
|
|
1216
|
+
if (expr.path) {
|
|
1217
|
+
return getNestedValue2(importData, expr.path);
|
|
1218
|
+
}
|
|
1219
|
+
return importData;
|
|
1220
|
+
}
|
|
1221
|
+
case "data": {
|
|
1222
|
+
const dataValue = ctx.imports?.[expr.name];
|
|
1223
|
+
if (dataValue === void 0) return void 0;
|
|
1224
|
+
if (expr.path) {
|
|
1225
|
+
return getNestedValue2(dataValue, expr.path);
|
|
1226
|
+
}
|
|
1227
|
+
return dataValue;
|
|
1228
|
+
}
|
|
1229
|
+
case "ref":
|
|
1230
|
+
return null;
|
|
1231
|
+
case "index": {
|
|
1232
|
+
const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1233
|
+
const base = evaluate2(expr.base, ctx);
|
|
1234
|
+
const key = evaluate2(expr.key, ctx);
|
|
1235
|
+
if (base == null || key == null) return void 0;
|
|
1236
|
+
if (typeof key === "string" && forbiddenKeys.has(key)) return void 0;
|
|
1237
|
+
return base[key];
|
|
1238
|
+
}
|
|
1239
|
+
case "param": {
|
|
1240
|
+
return void 0;
|
|
1241
|
+
}
|
|
1242
|
+
case "style": {
|
|
1243
|
+
return evaluateStyle2(expr, ctx);
|
|
1244
|
+
}
|
|
1245
|
+
case "concat": {
|
|
1246
|
+
return expr.items.map((item) => {
|
|
1247
|
+
const val = evaluate2(item, ctx);
|
|
1248
|
+
return val == null ? "" : String(val);
|
|
1249
|
+
}).join("");
|
|
1250
|
+
}
|
|
1251
|
+
case "validity": {
|
|
1252
|
+
return false;
|
|
1253
|
+
}
|
|
1254
|
+
case "call": {
|
|
1255
|
+
const callExpr = expr;
|
|
1256
|
+
const target = evaluate2(callExpr.target, ctx);
|
|
1257
|
+
if (target == null) return void 0;
|
|
1258
|
+
const args = callExpr.args?.map((arg) => {
|
|
1259
|
+
if (arg.expr === "lambda") return arg;
|
|
1260
|
+
return evaluate2(arg, ctx);
|
|
1261
|
+
}) ?? [];
|
|
1262
|
+
if (Array.isArray(target)) {
|
|
1263
|
+
return callArrayMethod2(target, callExpr.method, args, ctx, callExpr.args);
|
|
1264
|
+
}
|
|
1265
|
+
if (typeof target === "string") {
|
|
1266
|
+
return callStringMethod2(target, callExpr.method, args);
|
|
1267
|
+
}
|
|
1268
|
+
if (target === Math) {
|
|
1269
|
+
return callMathMethod2(callExpr.method, args);
|
|
1270
|
+
}
|
|
1271
|
+
if (target === Date) {
|
|
1272
|
+
return callDateStaticMethod2(callExpr.method, args);
|
|
1273
|
+
}
|
|
1274
|
+
if (target instanceof Date) {
|
|
1275
|
+
return callDateInstanceMethod2(target, callExpr.method);
|
|
1276
|
+
}
|
|
1277
|
+
return void 0;
|
|
1278
|
+
}
|
|
1279
|
+
case "lambda": {
|
|
1280
|
+
return void 0;
|
|
1281
|
+
}
|
|
1282
|
+
case "array": {
|
|
1283
|
+
const arrayExpr = expr;
|
|
1284
|
+
return arrayExpr.elements.map((elem) => evaluate2(elem, ctx));
|
|
1285
|
+
}
|
|
1286
|
+
default: {
|
|
1287
|
+
return void 0;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function getNestedValue2(obj, path) {
|
|
1292
|
+
const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1293
|
+
const parts = path.split(".");
|
|
1294
|
+
let value = obj;
|
|
1295
|
+
for (const part of parts) {
|
|
1296
|
+
if (forbiddenKeys.has(part)) {
|
|
1297
|
+
return void 0;
|
|
1298
|
+
}
|
|
1299
|
+
if (value == null) {
|
|
1300
|
+
return void 0;
|
|
1301
|
+
}
|
|
1302
|
+
if (Array.isArray(value)) {
|
|
1303
|
+
const index = Number(part);
|
|
1304
|
+
if (Number.isInteger(index) && index >= 0) {
|
|
1305
|
+
value = value[index];
|
|
1306
|
+
} else {
|
|
1307
|
+
value = value[part];
|
|
1308
|
+
}
|
|
1309
|
+
} else if (typeof value === "object") {
|
|
1310
|
+
value = value[part];
|
|
1311
|
+
} else {
|
|
1312
|
+
return void 0;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return value;
|
|
1316
|
+
}
|
|
1317
|
+
function evaluateBinary2(op, left, right, ctx) {
|
|
1318
|
+
if (op === "&&") {
|
|
1319
|
+
const leftVal2 = evaluate2(left, ctx);
|
|
1320
|
+
if (!leftVal2) return leftVal2;
|
|
1321
|
+
return evaluate2(right, ctx);
|
|
1322
|
+
}
|
|
1323
|
+
if (op === "||") {
|
|
1324
|
+
const leftVal2 = evaluate2(left, ctx);
|
|
1325
|
+
if (leftVal2) return leftVal2;
|
|
1326
|
+
return evaluate2(right, ctx);
|
|
1327
|
+
}
|
|
1328
|
+
const leftVal = evaluate2(left, ctx);
|
|
1329
|
+
const rightVal = evaluate2(right, ctx);
|
|
1330
|
+
switch (op) {
|
|
1331
|
+
case "+":
|
|
1332
|
+
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
1333
|
+
return leftVal + rightVal;
|
|
1334
|
+
}
|
|
1335
|
+
return String(leftVal) + String(rightVal);
|
|
1336
|
+
case "-":
|
|
1337
|
+
return (typeof leftVal === "number" ? leftVal : 0) - (typeof rightVal === "number" ? rightVal : 0);
|
|
1338
|
+
case "*":
|
|
1339
|
+
return (typeof leftVal === "number" ? leftVal : 0) * (typeof rightVal === "number" ? rightVal : 0);
|
|
1340
|
+
case "/": {
|
|
1341
|
+
const dividend = typeof leftVal === "number" ? leftVal : 0;
|
|
1342
|
+
const divisor = typeof rightVal === "number" ? rightVal : 0;
|
|
1343
|
+
if (divisor === 0) {
|
|
1344
|
+
return dividend === 0 ? NaN : dividend > 0 ? Infinity : -Infinity;
|
|
1345
|
+
}
|
|
1346
|
+
return dividend / divisor;
|
|
1347
|
+
}
|
|
1348
|
+
case "==":
|
|
1349
|
+
return leftVal === rightVal;
|
|
1350
|
+
case "!=":
|
|
1351
|
+
return leftVal !== rightVal;
|
|
1352
|
+
case "<":
|
|
1353
|
+
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
1354
|
+
return leftVal < rightVal;
|
|
1355
|
+
}
|
|
1356
|
+
return String(leftVal) < String(rightVal);
|
|
1357
|
+
case "<=":
|
|
1358
|
+
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
1359
|
+
return leftVal <= rightVal;
|
|
1360
|
+
}
|
|
1361
|
+
return String(leftVal) <= String(rightVal);
|
|
1362
|
+
case ">":
|
|
1363
|
+
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
1364
|
+
return leftVal > rightVal;
|
|
1365
|
+
}
|
|
1366
|
+
return String(leftVal) > String(rightVal);
|
|
1367
|
+
case ">=":
|
|
1368
|
+
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
1369
|
+
return leftVal >= rightVal;
|
|
1370
|
+
}
|
|
1371
|
+
return String(leftVal) >= String(rightVal);
|
|
1372
|
+
default:
|
|
1373
|
+
throw new Error("Unknown binary operator: " + op);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
function evaluateStyle2(expr, ctx) {
|
|
1377
|
+
const preset = ctx.styles?.[expr.name];
|
|
1378
|
+
if (!preset) return "";
|
|
1379
|
+
let classes = preset.base;
|
|
1380
|
+
if (preset.variants) {
|
|
1381
|
+
for (const variantKey of Object.keys(preset.variants)) {
|
|
1382
|
+
let variantValueStr = null;
|
|
1383
|
+
if (expr.variants?.[variantKey]) {
|
|
1384
|
+
let variantValue;
|
|
1385
|
+
try {
|
|
1386
|
+
variantValue = evaluate2(expr.variants[variantKey], ctx);
|
|
1387
|
+
} catch {
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (variantValue != null) {
|
|
1391
|
+
variantValueStr = String(variantValue);
|
|
1392
|
+
}
|
|
1393
|
+
} else if (preset.defaultVariants?.[variantKey] !== void 0) {
|
|
1394
|
+
variantValueStr = preset.defaultVariants[variantKey];
|
|
1395
|
+
}
|
|
1396
|
+
if (variantValueStr !== null) {
|
|
1397
|
+
const variantClasses = preset.variants[variantKey]?.[variantValueStr];
|
|
1398
|
+
if (variantClasses) {
|
|
1399
|
+
classes += " " + variantClasses;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return classes.trim();
|
|
1405
|
+
}
|
|
1406
|
+
function formatValue2(value) {
|
|
1407
|
+
if (value === null || value === void 0) {
|
|
1408
|
+
return "";
|
|
1409
|
+
}
|
|
1410
|
+
if (typeof value === "object") {
|
|
1411
|
+
return JSON.stringify(value);
|
|
1412
|
+
}
|
|
1413
|
+
return String(value);
|
|
1414
|
+
}
|
|
1415
|
+
function flush(ctx, force = false) {
|
|
1416
|
+
if (ctx.aborted) return;
|
|
1417
|
+
const { buffer, options, controller } = ctx;
|
|
1418
|
+
if (buffer.length === 0) return;
|
|
1419
|
+
switch (options.flushStrategy) {
|
|
1420
|
+
case "immediate":
|
|
1421
|
+
controller.enqueue(buffer);
|
|
1422
|
+
ctx.buffer = "";
|
|
1423
|
+
break;
|
|
1424
|
+
case "batched":
|
|
1425
|
+
if (force || buffer.length >= CHUNK_SIZE_THRESHOLD) {
|
|
1426
|
+
controller.enqueue(buffer);
|
|
1427
|
+
ctx.buffer = "";
|
|
1428
|
+
}
|
|
1429
|
+
break;
|
|
1430
|
+
case "manual":
|
|
1431
|
+
if (force) {
|
|
1432
|
+
controller.enqueue(buffer);
|
|
1433
|
+
ctx.buffer = "";
|
|
1434
|
+
}
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function write(ctx, content) {
|
|
1439
|
+
if (ctx.aborted) return;
|
|
1440
|
+
ctx.buffer += content;
|
|
1441
|
+
flush(ctx);
|
|
1442
|
+
}
|
|
1443
|
+
function checkAbort(ctx) {
|
|
1444
|
+
if (ctx.signal?.aborted) {
|
|
1445
|
+
ctx.aborted = true;
|
|
1446
|
+
return true;
|
|
1447
|
+
}
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
async function renderNodeToStream(node, ctx) {
|
|
1451
|
+
if (checkAbort(ctx)) return;
|
|
1452
|
+
switch (node.kind) {
|
|
1453
|
+
case "element":
|
|
1454
|
+
await renderElementToStream(node, ctx);
|
|
1455
|
+
break;
|
|
1456
|
+
case "text":
|
|
1457
|
+
renderTextToStream(node, ctx);
|
|
1458
|
+
break;
|
|
1459
|
+
case "if":
|
|
1460
|
+
await renderIfToStream(node, ctx);
|
|
1461
|
+
break;
|
|
1462
|
+
case "each":
|
|
1463
|
+
await renderEachToStream(node, ctx);
|
|
1464
|
+
break;
|
|
1465
|
+
case "markdown":
|
|
1466
|
+
await renderMarkdownToStream(node, ctx);
|
|
1467
|
+
break;
|
|
1468
|
+
case "code":
|
|
1469
|
+
await renderCodeToStream(node, ctx);
|
|
1470
|
+
break;
|
|
1471
|
+
case "slot":
|
|
1472
|
+
break;
|
|
1473
|
+
case "portal":
|
|
1474
|
+
await renderPortalToStream(node, ctx);
|
|
1475
|
+
break;
|
|
1476
|
+
case "localState":
|
|
1477
|
+
await renderLocalStateToStream(node, ctx);
|
|
1478
|
+
break;
|
|
1479
|
+
default: {
|
|
1480
|
+
const unknownNode = node;
|
|
1481
|
+
if (unknownNode.kind === "suspense") {
|
|
1482
|
+
await renderSuspenseToStream(unknownNode, ctx);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
async function renderSuspenseToStream(node, ctx) {
|
|
1488
|
+
write(ctx, '<div data-suspense-id="' + escapeHtml(node.id) + '">');
|
|
1489
|
+
await renderNodeToStream(node.fallback, ctx);
|
|
1490
|
+
write(ctx, "</div>");
|
|
1491
|
+
if (node.children && node.children.length > 0) {
|
|
1492
|
+
for (const child of node.children) {
|
|
1493
|
+
await renderNodeToStream(child, ctx);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
async function renderElementToStream(node, ctx) {
|
|
1498
|
+
if (checkAbort(ctx)) return;
|
|
1499
|
+
const tag = node.tag;
|
|
1500
|
+
const isVoid = VOID_ELEMENTS2.has(tag);
|
|
1501
|
+
let attrs = "";
|
|
1502
|
+
if (node.props) {
|
|
1503
|
+
for (const [propName, propValue] of Object.entries(node.props)) {
|
|
1504
|
+
if (isEventHandler2(propValue)) {
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
const value = evaluate2(propValue, ctx);
|
|
1508
|
+
if (value === false) {
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (value === true) {
|
|
1512
|
+
attrs += " " + propName;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
if (value === null || value === void 0) {
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
attrs += " " + propName + '="' + escapeHtml(String(value)) + '"';
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (isVoid) {
|
|
1522
|
+
write(ctx, "<" + tag + attrs + " />");
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
write(ctx, "<" + tag + attrs + ">");
|
|
1526
|
+
if (node.children) {
|
|
1527
|
+
for (const child of node.children) {
|
|
1528
|
+
await renderNodeToStream(child, ctx);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
write(ctx, "</" + tag + ">");
|
|
1532
|
+
}
|
|
1533
|
+
function renderTextToStream(node, ctx) {
|
|
1534
|
+
const value = evaluate2(node.value, ctx);
|
|
1535
|
+
write(ctx, escapeHtml(formatValue2(value)));
|
|
1536
|
+
}
|
|
1537
|
+
async function renderIfToStream(node, ctx) {
|
|
1538
|
+
if (checkAbort(ctx)) return;
|
|
1539
|
+
const condition = evaluate2(node.condition, ctx);
|
|
1540
|
+
if (condition) {
|
|
1541
|
+
write(ctx, "<!--if:then-->");
|
|
1542
|
+
await renderNodeToStream(node.then, ctx);
|
|
1543
|
+
} else if (node.else) {
|
|
1544
|
+
write(ctx, "<!--if:else-->");
|
|
1545
|
+
await renderNodeToStream(node.else, ctx);
|
|
1546
|
+
} else {
|
|
1547
|
+
write(ctx, "<!--if:none-->");
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
async function renderEachToStream(node, ctx) {
|
|
1551
|
+
if (checkAbort(ctx)) return;
|
|
1552
|
+
const items = evaluate2(node.items, ctx);
|
|
1553
|
+
if (!Array.isArray(items)) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
for (let index = 0; index < items.length; index++) {
|
|
1557
|
+
if (checkAbort(ctx)) return;
|
|
1558
|
+
const item = items[index];
|
|
1559
|
+
const itemLocals = {
|
|
1560
|
+
...ctx.locals,
|
|
1561
|
+
[node.as]: item
|
|
1562
|
+
};
|
|
1563
|
+
if (node.index) {
|
|
1564
|
+
itemLocals[node.index] = index;
|
|
1565
|
+
}
|
|
1566
|
+
const itemCtx = {
|
|
1567
|
+
...ctx,
|
|
1568
|
+
locals: itemLocals
|
|
1569
|
+
};
|
|
1570
|
+
await renderNodeToStream(node.body, itemCtx);
|
|
1571
|
+
if (index > 0 && index % 10 === 0) {
|
|
1572
|
+
flush(ctx);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
async function renderMarkdownToStream(node, ctx) {
|
|
1577
|
+
const content = evaluate2(node.content, ctx);
|
|
1578
|
+
write(ctx, '<div class="constela-markdown">' + escapeHtml(formatValue2(content)) + "</div>");
|
|
1579
|
+
}
|
|
1580
|
+
async function renderCodeToStream(node, ctx) {
|
|
1581
|
+
const language = formatValue2(evaluate2(node.language, ctx));
|
|
1582
|
+
const content = formatValue2(evaluate2(node.content, ctx));
|
|
1583
|
+
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>" : "";
|
|
1584
|
+
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>';
|
|
1585
|
+
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>");
|
|
1586
|
+
}
|
|
1587
|
+
async function renderPortalToStream(node, ctx) {
|
|
1588
|
+
write(ctx, "<!--portal:" + node.target + "-->");
|
|
1589
|
+
for (const child of node.children) {
|
|
1590
|
+
await renderNodeToStream(child, ctx);
|
|
1591
|
+
}
|
|
1592
|
+
write(ctx, "<!--/portal-->");
|
|
1593
|
+
}
|
|
1594
|
+
async function renderLocalStateToStream(node, ctx) {
|
|
1595
|
+
const localStateValues = {};
|
|
1596
|
+
for (const [name, field] of Object.entries(node.state)) {
|
|
1597
|
+
localStateValues[name] = field.initial;
|
|
1598
|
+
}
|
|
1599
|
+
const childCtx = {
|
|
1600
|
+
...ctx,
|
|
1601
|
+
locals: {
|
|
1602
|
+
...ctx.locals,
|
|
1603
|
+
...localStateValues
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
await renderNodeToStream(node.child, childCtx);
|
|
1607
|
+
}
|
|
1608
|
+
function renderToStream(program, streamOptions, options) {
|
|
1609
|
+
const state = /* @__PURE__ */ new Map();
|
|
1610
|
+
for (const [name, field] of Object.entries(program.state)) {
|
|
1611
|
+
const stateField = field;
|
|
1612
|
+
const overrideValue = options?.stateOverrides?.[name];
|
|
1613
|
+
if (overrideValue !== void 0) {
|
|
1614
|
+
state.set(name, overrideValue);
|
|
1615
|
+
} else if (isCookieInitialExpr2(stateField.initial)) {
|
|
1616
|
+
const cookieInitial = stateField.initial;
|
|
1617
|
+
const cookieValue = options?.cookies?.[cookieInitial.key];
|
|
1618
|
+
state.set(name, cookieValue !== void 0 ? cookieValue : cookieInitial.default);
|
|
1619
|
+
} else {
|
|
1620
|
+
state.set(name, stateField.initial);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
const signal = options?.signal;
|
|
1624
|
+
return new ReadableStream({
|
|
1625
|
+
async start(controller) {
|
|
1626
|
+
const baseCtx = {
|
|
1627
|
+
state,
|
|
1628
|
+
locals: {},
|
|
1629
|
+
route: options?.route ? {
|
|
1630
|
+
params: options.route.params ?? {},
|
|
1631
|
+
query: options.route.query ?? {},
|
|
1632
|
+
path: options.route.path ?? ""
|
|
1633
|
+
} : void 0,
|
|
1634
|
+
imports: options?.imports ?? program.importData,
|
|
1635
|
+
styles: options?.styles,
|
|
1636
|
+
controller,
|
|
1637
|
+
options: streamOptions,
|
|
1638
|
+
buffer: "",
|
|
1639
|
+
aborted: false
|
|
1640
|
+
};
|
|
1641
|
+
const ctx = signal ? { ...baseCtx, signal } : baseCtx;
|
|
1642
|
+
if (signal) {
|
|
1643
|
+
signal.addEventListener("abort", () => {
|
|
1644
|
+
ctx.aborted = true;
|
|
1645
|
+
try {
|
|
1646
|
+
controller.close();
|
|
1647
|
+
} catch {
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
try {
|
|
1652
|
+
await renderNodeToStream(program.view, ctx);
|
|
1653
|
+
flush(ctx, true);
|
|
1654
|
+
if (!ctx.aborted) {
|
|
1655
|
+
controller.close();
|
|
1656
|
+
}
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
if (!ctx.aborted) {
|
|
1659
|
+
controller.error(error);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
},
|
|
1663
|
+
cancel() {
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
function createHtmlTransformStream(options) {
|
|
1668
|
+
let isFirstChunk = true;
|
|
1669
|
+
return new TransformStream({
|
|
1670
|
+
transform(chunk, controller) {
|
|
1671
|
+
if (isFirstChunk) {
|
|
1672
|
+
const shell = buildDocumentShell(options);
|
|
1673
|
+
controller.enqueue(shell + chunk);
|
|
1674
|
+
isFirstChunk = false;
|
|
1675
|
+
} else {
|
|
1676
|
+
controller.enqueue(chunk);
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
flush(controller) {
|
|
1680
|
+
const scripts = buildScriptTags(options.scripts);
|
|
1681
|
+
controller.enqueue(scripts + "</body></html>");
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
function buildDocumentShell(options) {
|
|
1686
|
+
const lang = options.lang ?? "en";
|
|
1687
|
+
let head = "<head>";
|
|
1688
|
+
head += '<meta charset="UTF-8">';
|
|
1689
|
+
head += "<title>" + escapeHtml(options.title) + "</title>";
|
|
1690
|
+
if (options.meta) {
|
|
1691
|
+
for (const [name, content] of Object.entries(options.meta)) {
|
|
1692
|
+
head += '<meta name="' + escapeHtml(name) + '" content="' + escapeHtml(content) + '">';
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (options.stylesheets) {
|
|
1696
|
+
for (const href of options.stylesheets) {
|
|
1697
|
+
head += '<link rel="stylesheet" href="' + escapeHtml(href) + '">';
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
head += "</head>";
|
|
1701
|
+
return '<!DOCTYPE html><html lang="' + escapeHtml(lang) + '">' + head + "<body>";
|
|
1702
|
+
}
|
|
1703
|
+
function buildScriptTags(scripts) {
|
|
1704
|
+
if (!scripts || scripts.length === 0) return "";
|
|
1705
|
+
return scripts.map((src) => '<script src="' + escapeHtml(src) + '"></script>').join("");
|
|
1706
|
+
}
|
|
823
1707
|
export {
|
|
1708
|
+
createHtmlTransformStream,
|
|
1709
|
+
renderToStream,
|
|
824
1710
|
renderToString
|
|
825
1711
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.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.
|
|
19
|
-
"@constela/core": "^0.
|
|
18
|
+
"@constela/compiler": "^0.15.0",
|
|
19
|
+
"@constela/core": "^0.17.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/compiler": "0.
|
|
33
|
-
"@constela/core": "0.
|
|
32
|
+
"@constela/compiler": "0.15.0",
|
|
33
|
+
"@constela/core": "0.17.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=20.0.0"
|