@constela/start 1.8.25 → 1.8.27
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/{chunk-IZPFJSX3.js → chunk-C7LIB2RS.js} +129 -8
- package/dist/{chunk-E4SJFPTB.js → chunk-VL6BMHBL.js} +108 -7
- package/dist/cli/index.js +2 -2
- package/dist/index.d.ts +12 -0
- package/dist/index.js +2 -2
- package/dist/runtime/entry-client.js +76 -4
- package/dist/runtime/entry-server.d.ts +5 -0
- package/dist/runtime/entry-server.js +1 -1
- package/package.json +7 -6
|
@@ -1,5 +1,104 @@
|
|
|
1
1
|
// src/runtime/entry-server.ts
|
|
2
2
|
import { renderToString } from "@constela/server";
|
|
3
|
+
|
|
4
|
+
// src/runtime/theme.ts
|
|
5
|
+
function escapeCssValue(value) {
|
|
6
|
+
return value.replace(/[;<>{}]/g, "");
|
|
7
|
+
}
|
|
8
|
+
function escapeJsString(str) {
|
|
9
|
+
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/<\/script>/gi, "<\\/script>");
|
|
10
|
+
}
|
|
11
|
+
function generateThemeCss(options) {
|
|
12
|
+
const { config, cssPrefix: optionsPrefix } = options;
|
|
13
|
+
if (!config) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
const { colors, darkColors, fonts, cssPrefix: configPrefix } = config;
|
|
17
|
+
const prefix = optionsPrefix ?? configPrefix ?? "";
|
|
18
|
+
const hasColors = colors && Object.keys(colors).length > 0;
|
|
19
|
+
const hasDarkColors = darkColors && Object.keys(darkColors).length > 0;
|
|
20
|
+
const hasFonts = fonts && Object.keys(fonts).length > 0;
|
|
21
|
+
if (!hasColors && !hasDarkColors && !hasFonts) {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
const lines = [];
|
|
25
|
+
const rootVars = [];
|
|
26
|
+
const separator = prefix && !prefix.endsWith("-") ? "-" : "";
|
|
27
|
+
if (colors) {
|
|
28
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
29
|
+
if (value !== void 0) {
|
|
30
|
+
rootVars.push(` --${prefix}${separator}${key}: ${escapeCssValue(value)};`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (fonts) {
|
|
35
|
+
for (const [key, value] of Object.entries(fonts)) {
|
|
36
|
+
if (value !== void 0) {
|
|
37
|
+
rootVars.push(` --${prefix}${separator}font-${key}: ${escapeCssValue(value)};`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (rootVars.length > 0) {
|
|
42
|
+
lines.push(":root {");
|
|
43
|
+
lines.push(...rootVars);
|
|
44
|
+
lines.push("}");
|
|
45
|
+
}
|
|
46
|
+
if (darkColors) {
|
|
47
|
+
const darkVars = [];
|
|
48
|
+
for (const [key, value] of Object.entries(darkColors)) {
|
|
49
|
+
if (value !== void 0) {
|
|
50
|
+
darkVars.push(` --${prefix}${separator}${key}: ${escapeCssValue(value)};`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (darkVars.length > 0) {
|
|
54
|
+
lines.push(".dark {");
|
|
55
|
+
lines.push(...darkVars);
|
|
56
|
+
lines.push("}");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
function generateThemeScript(storageKey = "theme") {
|
|
62
|
+
if (storageKey === "") {
|
|
63
|
+
storageKey = "theme";
|
|
64
|
+
}
|
|
65
|
+
const escapedKey = escapeJsString(storageKey);
|
|
66
|
+
return `(function() {
|
|
67
|
+
try {
|
|
68
|
+
var theme;
|
|
69
|
+
var cookies = document.cookie.split(';');
|
|
70
|
+
for (var i = 0; i < cookies.length; i++) {
|
|
71
|
+
var cookie = cookies[i].trim();
|
|
72
|
+
if (cookie.indexOf('${escapedKey}=') === 0) {
|
|
73
|
+
theme = decodeURIComponent(cookie.substring('${escapedKey}='.length));
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!theme) {
|
|
78
|
+
theme = localStorage.getItem('${escapedKey}');
|
|
79
|
+
}
|
|
80
|
+
if (theme === 'dark') {
|
|
81
|
+
document.documentElement.classList.add('dark');
|
|
82
|
+
} else if (theme === 'light') {
|
|
83
|
+
document.documentElement.classList.remove('dark');
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {}
|
|
86
|
+
})();`;
|
|
87
|
+
}
|
|
88
|
+
function getHtmlThemeClass(config, cookieMode) {
|
|
89
|
+
if (cookieMode === "dark") {
|
|
90
|
+
return "dark";
|
|
91
|
+
}
|
|
92
|
+
if (cookieMode === "light" || cookieMode === "system") {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
if (config?.mode === "dark") {
|
|
96
|
+
return "dark";
|
|
97
|
+
}
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/runtime/entry-server.ts
|
|
3
102
|
async function renderPage(program, ctx) {
|
|
4
103
|
const stateOverrides = {};
|
|
5
104
|
if (ctx.cookies?.["theme"] && program.state?.["theme"]) {
|
|
@@ -26,7 +125,7 @@ async function renderPage(program, ctx) {
|
|
|
26
125
|
}
|
|
27
126
|
return await renderToString(program, options);
|
|
28
127
|
}
|
|
29
|
-
function
|
|
128
|
+
function escapeJsString2(str) {
|
|
30
129
|
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
31
130
|
}
|
|
32
131
|
function escapeJsonForScript(json) {
|
|
@@ -70,7 +169,7 @@ function generateHydrationScript(program, widgets, route, hmrUrl) {
|
|
|
70
169
|
}).join("\n") : "";
|
|
71
170
|
const widgetMounting = hasWidgets ? widgets.map((widget) => {
|
|
72
171
|
const jsId = toJsIdentifier(widget.id);
|
|
73
|
-
const escapedId =
|
|
172
|
+
const escapedId = escapeJsString2(widget.id);
|
|
74
173
|
return `
|
|
75
174
|
const container_${jsId} = document.getElementById('${escapedId}');
|
|
76
175
|
if (container_${jsId}) {
|
|
@@ -93,14 +192,16 @@ if (container_${jsId}) {
|
|
|
93
192
|
}`;
|
|
94
193
|
let hmrSetup = "";
|
|
95
194
|
if (enableHmr) {
|
|
96
|
-
const escapedHmrUrl =
|
|
195
|
+
const escapedHmrUrl = escapeJsString2(hmrUrl);
|
|
97
196
|
const handlerOptions = route ? `{
|
|
98
197
|
container: document.getElementById('app'),
|
|
99
198
|
program,
|
|
100
|
-
route
|
|
199
|
+
route,
|
|
200
|
+
skipInitialRender: true
|
|
101
201
|
}` : `{
|
|
102
202
|
container: document.getElementById('app'),
|
|
103
|
-
program
|
|
203
|
+
program,
|
|
204
|
+
skipInitialRender: true
|
|
104
205
|
}`;
|
|
105
206
|
hmrSetup = `
|
|
106
207
|
|
|
@@ -128,7 +229,15 @@ function wrapHtml(content, hydrationScript, head, options) {
|
|
|
128
229
|
}
|
|
129
230
|
langAttr = ` lang="${options.lang}"`;
|
|
130
231
|
}
|
|
131
|
-
|
|
232
|
+
let htmlClass = "";
|
|
233
|
+
if (options?.themeConfig) {
|
|
234
|
+
const themeClass = getHtmlThemeClass(options.themeConfig, options.themeCookie);
|
|
235
|
+
if (themeClass) {
|
|
236
|
+
htmlClass = ` class="${themeClass}"`;
|
|
237
|
+
}
|
|
238
|
+
} else if (options?.defaultTheme === "dark" || options?.theme === "dark") {
|
|
239
|
+
htmlClass = ' class="dark"';
|
|
240
|
+
}
|
|
132
241
|
let processedScript = hydrationScript;
|
|
133
242
|
let importMapScript = "";
|
|
134
243
|
if (options?.runtimePath) {
|
|
@@ -153,8 +262,20 @@ ${importMapJson}
|
|
|
153
262
|
</script>
|
|
154
263
|
`;
|
|
155
264
|
}
|
|
265
|
+
let themeCssStyle = "";
|
|
266
|
+
if (options?.themeConfig) {
|
|
267
|
+
const themeCss = generateThemeCss({ config: options.themeConfig });
|
|
268
|
+
if (themeCss) {
|
|
269
|
+
themeCssStyle = `<style>${themeCss}</style>
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
156
273
|
let themeScript = "";
|
|
157
|
-
if (options?.
|
|
274
|
+
if (options?.themeConfig) {
|
|
275
|
+
const storageKey = options.themeStorageKey ?? "theme";
|
|
276
|
+
themeScript = `<script>${generateThemeScript(storageKey)}</script>
|
|
277
|
+
`;
|
|
278
|
+
} else if (options?.themeStorageKey) {
|
|
158
279
|
if (!/^[a-zA-Z0-9_-]+$/.test(options.themeStorageKey)) {
|
|
159
280
|
throw new Error(`Invalid themeStorageKey: ${options.themeStorageKey}. Only alphanumeric characters, underscores, and hyphens are allowed.`);
|
|
160
281
|
}
|
|
@@ -193,7 +314,7 @@ ${importMapJson}
|
|
|
193
314
|
<head>
|
|
194
315
|
<meta charset="utf-8">
|
|
195
316
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
196
|
-
${themeScript}${importMapScript}${head ?? ""}
|
|
317
|
+
${themeCssStyle}${themeScript}${importMapScript}${head ?? ""}
|
|
197
318
|
</head>
|
|
198
319
|
<body>
|
|
199
320
|
<div id="app">${content}</div>
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
generateMetaTags,
|
|
4
4
|
renderPage,
|
|
5
5
|
wrapHtml
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-C7LIB2RS.js";
|
|
7
7
|
|
|
8
8
|
// src/router/file-router.ts
|
|
9
9
|
import fg from "fast-glob";
|
|
@@ -770,6 +770,8 @@ async function mdxContentToNode(content, options) {
|
|
|
770
770
|
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
771
771
|
import { basename, dirname, extname as extname2, join as join3 } from "path";
|
|
772
772
|
import fg2 from "fast-glob";
|
|
773
|
+
import { isAiDataSource } from "@constela/core";
|
|
774
|
+
import { createDslGenerator } from "@constela/ai";
|
|
773
775
|
var mdxContentToNode2 = mdxContentToNode;
|
|
774
776
|
function resolveJsonRefs(json) {
|
|
775
777
|
const cloned = JSON.parse(JSON.stringify(json));
|
|
@@ -1099,6 +1101,19 @@ async function loadApi(url, transform) {
|
|
|
1099
1101
|
throw new Error(`Network error: ${error.message}`);
|
|
1100
1102
|
}
|
|
1101
1103
|
}
|
|
1104
|
+
async function loadAi(dataSource, generator) {
|
|
1105
|
+
const gen = generator ?? createDslGenerator({
|
|
1106
|
+
provider: dataSource.provider
|
|
1107
|
+
});
|
|
1108
|
+
const result = await gen.generate({
|
|
1109
|
+
prompt: dataSource.prompt,
|
|
1110
|
+
output: dataSource.output
|
|
1111
|
+
});
|
|
1112
|
+
if (!result.validated && result.errors && result.errors.length > 0) {
|
|
1113
|
+
throw new Error(`AI generated DSL validation failed: ${result.errors.join(", ")}`);
|
|
1114
|
+
}
|
|
1115
|
+
return result.dsl;
|
|
1116
|
+
}
|
|
1102
1117
|
function evaluateParamExpression(expr, item) {
|
|
1103
1118
|
switch (expr.expr) {
|
|
1104
1119
|
case "lit":
|
|
@@ -1211,6 +1226,12 @@ var DataLoader = class {
|
|
|
1211
1226
|
}
|
|
1212
1227
|
data = await loadApi(dataSource.url, dataSource.transform);
|
|
1213
1228
|
break;
|
|
1229
|
+
case "ai":
|
|
1230
|
+
if (!isAiDataSource(dataSource)) {
|
|
1231
|
+
throw new Error(`Invalid AI data source '${name}'`);
|
|
1232
|
+
}
|
|
1233
|
+
data = await loadAi(dataSource);
|
|
1234
|
+
break;
|
|
1214
1235
|
default:
|
|
1215
1236
|
throw new Error(`Unknown data source type: ${dataSource.type}`);
|
|
1216
1237
|
}
|
|
@@ -1373,8 +1394,65 @@ function parseCookies(cookieHeader) {
|
|
|
1373
1394
|
}
|
|
1374
1395
|
return cookies;
|
|
1375
1396
|
}
|
|
1397
|
+
function wrapHtmlStream(contentStream, hydrationScript, options) {
|
|
1398
|
+
const encoder = new TextEncoder();
|
|
1399
|
+
const lang = options?.lang ?? "en";
|
|
1400
|
+
const themeClass = options?.theme === "dark" ? 'class="dark"' : "";
|
|
1401
|
+
const docStart = `<!DOCTYPE html>
|
|
1402
|
+
<html lang="${lang}" ${themeClass}>
|
|
1403
|
+
<head>
|
|
1404
|
+
<meta charset="UTF-8">
|
|
1405
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1406
|
+
${options?.head ?? ""}
|
|
1407
|
+
</head>
|
|
1408
|
+
<body>
|
|
1409
|
+
<div id="app">`;
|
|
1410
|
+
const docEnd = `</div>
|
|
1411
|
+
${options?.runtimePath ? `<script type="module" src="${options.runtimePath}"></script>` : ""}
|
|
1412
|
+
<script type="module">
|
|
1413
|
+
${hydrationScript}
|
|
1414
|
+
</script>
|
|
1415
|
+
</body>
|
|
1416
|
+
</html>`;
|
|
1417
|
+
let contentReader = null;
|
|
1418
|
+
let startSent = false;
|
|
1419
|
+
let contentDone = false;
|
|
1420
|
+
return new ReadableStream({
|
|
1421
|
+
async start() {
|
|
1422
|
+
contentReader = contentStream.getReader();
|
|
1423
|
+
},
|
|
1424
|
+
async pull(controller) {
|
|
1425
|
+
try {
|
|
1426
|
+
if (!startSent) {
|
|
1427
|
+
controller.enqueue(encoder.encode(docStart));
|
|
1428
|
+
startSent = true;
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (!contentDone && contentReader) {
|
|
1432
|
+
const { done, value } = await contentReader.read();
|
|
1433
|
+
if (done) {
|
|
1434
|
+
contentDone = true;
|
|
1435
|
+
controller.enqueue(encoder.encode(docEnd));
|
|
1436
|
+
controller.close();
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
controller.enqueue(value);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
controller.close();
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
controller.error(error);
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
cancel(reason) {
|
|
1448
|
+
if (contentReader) {
|
|
1449
|
+
contentReader.cancel(reason);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1376
1454
|
function createAdapter(options) {
|
|
1377
|
-
const { routes, loadModule = defaultLoadModule } = options;
|
|
1455
|
+
const { routes, loadModule = defaultLoadModule, streaming = false } = options;
|
|
1378
1456
|
async function fetch2(request) {
|
|
1379
1457
|
try {
|
|
1380
1458
|
const url = new URL(request.url);
|
|
@@ -1435,6 +1513,29 @@ function createAdapter(options) {
|
|
|
1435
1513
|
initialTheme = themeState.initial;
|
|
1436
1514
|
}
|
|
1437
1515
|
}
|
|
1516
|
+
if (streaming) {
|
|
1517
|
+
const encoder = new TextEncoder();
|
|
1518
|
+
const contentStream = new ReadableStream({
|
|
1519
|
+
start(controller) {
|
|
1520
|
+
controller.enqueue(encoder.encode(content));
|
|
1521
|
+
controller.close();
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
const streamOptions = {
|
|
1525
|
+
lang: "en"
|
|
1526
|
+
};
|
|
1527
|
+
if (initialTheme) {
|
|
1528
|
+
streamOptions.theme = initialTheme;
|
|
1529
|
+
}
|
|
1530
|
+
const htmlStream = wrapHtmlStream(contentStream, hydrationScript, streamOptions);
|
|
1531
|
+
return new Response(htmlStream, {
|
|
1532
|
+
status: 200,
|
|
1533
|
+
headers: {
|
|
1534
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1535
|
+
"Transfer-Encoding": "chunked"
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1438
1539
|
const html = wrapHtml(content, hydrationScript, void 0, initialTheme ? {
|
|
1439
1540
|
theme: initialTheme,
|
|
1440
1541
|
defaultTheme: initialTheme,
|
|
@@ -2948,14 +3049,14 @@ async function resolveConfig(fileConfig, cliOptions) {
|
|
|
2948
3049
|
|
|
2949
3050
|
// src/build/index.ts
|
|
2950
3051
|
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
2951
|
-
import { mkdir as mkdir2, writeFile, cp, readdir } from "fs/promises";
|
|
3052
|
+
import { mkdir as mkdir2, writeFile as writeFile2, cp, readdir } from "fs/promises";
|
|
2952
3053
|
import { join as join11, dirname as dirname6, relative as relative5, basename as basename4, isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
|
|
2953
3054
|
import { isCookieInitialExpr as isCookieInitialExpr3 } from "@constela/core";
|
|
2954
3055
|
|
|
2955
3056
|
// src/build/bundler.ts
|
|
2956
3057
|
import * as esbuild from "esbuild";
|
|
2957
3058
|
import { existsSync as existsSync8 } from "fs";
|
|
2958
|
-
import { mkdir, readFile } from "fs/promises";
|
|
3059
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2959
3060
|
import { createRequire } from "module";
|
|
2960
3061
|
import { join as join10, dirname as dirname5, isAbsolute as isAbsolute2, relative as relative4 } from "path";
|
|
2961
3062
|
import { fileURLToPath } from "url";
|
|
@@ -3703,14 +3804,14 @@ async function build2(options) {
|
|
|
3703
3804
|
const program = await convertToCompiledProgram(processedPageInfo);
|
|
3704
3805
|
const html = await renderPageToHtml(program, params, routePath, runtimePath, cssPath, processedPageInfo.page.externalImports, processedPageInfo.widgets, seoLang);
|
|
3705
3806
|
await mkdir2(dirname6(outputPath), { recursive: true });
|
|
3706
|
-
await
|
|
3807
|
+
await writeFile2(outputPath, html, "utf-8");
|
|
3707
3808
|
generatedFiles.push(outputPath);
|
|
3708
3809
|
const slugValue = params["slug"];
|
|
3709
3810
|
if (slugValue && (slugValue === "index" || slugValue.endsWith("/index"))) {
|
|
3710
3811
|
const parentOutputPath = join11(dirname6(dirname6(outputPath)), "index.html");
|
|
3711
3812
|
if (!generatedFiles.includes(parentOutputPath)) {
|
|
3712
3813
|
await mkdir2(dirname6(parentOutputPath), { recursive: true });
|
|
3713
|
-
await
|
|
3814
|
+
await writeFile2(parentOutputPath, html, "utf-8");
|
|
3714
3815
|
generatedFiles.push(parentOutputPath);
|
|
3715
3816
|
}
|
|
3716
3817
|
}
|
|
@@ -3737,7 +3838,7 @@ async function build2(options) {
|
|
|
3737
3838
|
const program = await convertToCompiledProgram(pageInfo);
|
|
3738
3839
|
const html = await renderPageToHtml(program, {}, routePath, runtimePath, cssPath, pageInfo.page.externalImports, pageInfo.widgets, seoLang);
|
|
3739
3840
|
await mkdir2(dirname6(outputPath), { recursive: true });
|
|
3740
|
-
await
|
|
3841
|
+
await writeFile2(outputPath, html, "utf-8");
|
|
3741
3842
|
generatedFiles.push(outputPath);
|
|
3742
3843
|
}
|
|
3743
3844
|
}
|
package/dist/cli/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ interface ConstelaConfig {
|
|
|
80
80
|
edge?: {
|
|
81
81
|
adapter?: 'cloudflare' | 'vercel' | 'deno' | 'node';
|
|
82
82
|
};
|
|
83
|
+
/** Enable streaming SSR */
|
|
84
|
+
streaming?: boolean;
|
|
85
|
+
/** Flush strategy for streaming SSR */
|
|
86
|
+
streamingFlushStrategy?: 'immediate' | 'batched' | 'manual';
|
|
83
87
|
}
|
|
84
88
|
/**
|
|
85
89
|
* Development server options
|
|
@@ -97,6 +101,8 @@ interface DevServerOptions {
|
|
|
97
101
|
seo?: {
|
|
98
102
|
lang?: string;
|
|
99
103
|
};
|
|
104
|
+
/** Enable streaming SSR */
|
|
105
|
+
streaming?: boolean;
|
|
100
106
|
}
|
|
101
107
|
/**
|
|
102
108
|
* Build options
|
|
@@ -115,6 +121,8 @@ interface BuildOptions {
|
|
|
115
121
|
seo?: {
|
|
116
122
|
lang?: string;
|
|
117
123
|
};
|
|
124
|
+
/** Enable streaming SSR */
|
|
125
|
+
streaming?: boolean;
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
/**
|
|
@@ -279,6 +287,10 @@ interface AdapterOptions {
|
|
|
279
287
|
platform: PlatformAdapter;
|
|
280
288
|
routes: ScannedRoute[];
|
|
281
289
|
loadModule?: (file: string) => Promise<unknown>;
|
|
290
|
+
/** Enable streaming SSR */
|
|
291
|
+
streaming?: boolean;
|
|
292
|
+
/** Flush strategy for streaming SSR */
|
|
293
|
+
streamingFlushStrategy?: 'immediate' | 'batched' | 'manual';
|
|
282
294
|
}
|
|
283
295
|
interface EdgeAdapter {
|
|
284
296
|
fetch: (request: Request) => Promise<Response>;
|
package/dist/index.js
CHANGED
|
@@ -28,14 +28,14 @@ import {
|
|
|
28
28
|
transformCsv,
|
|
29
29
|
transformMdx,
|
|
30
30
|
transformYaml
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-VL6BMHBL.js";
|
|
32
32
|
import {
|
|
33
33
|
evaluateMetaExpression,
|
|
34
34
|
generateHydrationScript,
|
|
35
35
|
generateMetaTags,
|
|
36
36
|
renderPage,
|
|
37
37
|
wrapHtml
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-C7LIB2RS.js";
|
|
39
39
|
|
|
40
40
|
// src/build/ssg.ts
|
|
41
41
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -5,6 +5,58 @@ import {
|
|
|
5
5
|
createHMRHandler,
|
|
6
6
|
createErrorOverlay
|
|
7
7
|
} from "@constela/runtime";
|
|
8
|
+
|
|
9
|
+
// src/runtime/theme-provider.ts
|
|
10
|
+
import { createThemeProvider } from "@constela/runtime";
|
|
11
|
+
var instance = null;
|
|
12
|
+
var ThemeProvider = {
|
|
13
|
+
/**
|
|
14
|
+
* Initializes the ThemeProvider with the given configuration.
|
|
15
|
+
* Creates a new instance if one doesn't exist.
|
|
16
|
+
*/
|
|
17
|
+
init(config) {
|
|
18
|
+
if (instance) {
|
|
19
|
+
instance.destroy();
|
|
20
|
+
}
|
|
21
|
+
instance = createThemeProvider({ config });
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* Sets the color scheme mode.
|
|
25
|
+
*/
|
|
26
|
+
setMode(mode) {
|
|
27
|
+
if (instance) {
|
|
28
|
+
instance.setMode(mode);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Applies CSS variables to the document.
|
|
33
|
+
* Called internally by init, but can be called manually if needed.
|
|
34
|
+
*/
|
|
35
|
+
applyCssVariables() {
|
|
36
|
+
if (instance) {
|
|
37
|
+
const currentMode = instance.getMode();
|
|
38
|
+
instance.setMode(currentMode);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* Destroys the ThemeProvider instance and cleans up resources.
|
|
43
|
+
*/
|
|
44
|
+
destroy() {
|
|
45
|
+
if (instance) {
|
|
46
|
+
instance.destroy();
|
|
47
|
+
instance = null;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Gets the underlying ThemeProvider instance.
|
|
52
|
+
* Returns null if not initialized.
|
|
53
|
+
*/
|
|
54
|
+
getInstance() {
|
|
55
|
+
return instance;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/runtime/entry-client.ts
|
|
8
60
|
function initClient(options) {
|
|
9
61
|
const { program, container, escapeHandlers = [], route } = options;
|
|
10
62
|
const appInstance = hydrateApp({ program, container, ...route && { route } });
|
|
@@ -36,6 +88,16 @@ function initClient(options) {
|
|
|
36
88
|
} catch {
|
|
37
89
|
}
|
|
38
90
|
}
|
|
91
|
+
const programTheme = program.theme;
|
|
92
|
+
const hasThemeProvider = !!programTheme;
|
|
93
|
+
if (hasThemeProvider) {
|
|
94
|
+
ThemeProvider.init(programTheme);
|
|
95
|
+
ThemeProvider.applyCssVariables();
|
|
96
|
+
const currentTheme = appInstance.getState?.("theme");
|
|
97
|
+
if (currentTheme === "dark") {
|
|
98
|
+
document.documentElement.classList.add("dark");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
39
101
|
if (program.state?.["theme"]) {
|
|
40
102
|
const updateThemeClass = (value) => {
|
|
41
103
|
if (value === "dark") {
|
|
@@ -44,11 +106,18 @@ function initClient(options) {
|
|
|
44
106
|
document.documentElement.classList.remove("dark");
|
|
45
107
|
}
|
|
46
108
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
109
|
+
if (!hasThemeProvider) {
|
|
110
|
+
const currentTheme = appInstance.getState?.("theme");
|
|
111
|
+
if (currentTheme) {
|
|
112
|
+
updateThemeClass(currentTheme);
|
|
113
|
+
}
|
|
50
114
|
}
|
|
51
|
-
const unsubscribeTheme = appInstance.subscribe("theme",
|
|
115
|
+
const unsubscribeTheme = appInstance.subscribe("theme", (value) => {
|
|
116
|
+
updateThemeClass(value);
|
|
117
|
+
if (hasThemeProvider && typeof value === "string") {
|
|
118
|
+
ThemeProvider.setMode(value);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
52
121
|
cleanupFns.push(unsubscribeTheme);
|
|
53
122
|
}
|
|
54
123
|
let destroyed = false;
|
|
@@ -59,6 +128,9 @@ function initClient(options) {
|
|
|
59
128
|
for (const cleanup of cleanupFns) {
|
|
60
129
|
cleanup();
|
|
61
130
|
}
|
|
131
|
+
if (hasThemeProvider) {
|
|
132
|
+
ThemeProvider.destroy();
|
|
133
|
+
}
|
|
62
134
|
appInstance.destroy();
|
|
63
135
|
},
|
|
64
136
|
setState(name, value) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CompiledRouteDefinition, CompiledExpression, CompiledProgram } from '@constela/compiler';
|
|
2
|
+
import { ThemeConfig, ColorScheme } from '@constela/core';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Server-side entry point for Constela applications
|
|
@@ -23,6 +24,10 @@ interface WrapHtmlOptions {
|
|
|
23
24
|
themeStorageKey?: string;
|
|
24
25
|
/** Default theme to use when no stored preference exists */
|
|
25
26
|
defaultTheme?: 'dark' | 'light';
|
|
27
|
+
/** Full theme configuration from program */
|
|
28
|
+
themeConfig?: ThemeConfig;
|
|
29
|
+
/** Theme from cookie (takes precedence over themeConfig.mode) */
|
|
30
|
+
themeCookie?: ColorScheme;
|
|
26
31
|
}
|
|
27
32
|
interface WidgetConfig {
|
|
28
33
|
/** The DOM element ID where the widget should be mounted */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/start",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.27",
|
|
4
4
|
"description": "Meta-framework for Constela applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,11 +44,12 @@
|
|
|
44
44
|
"@tailwindcss/postcss": "^4.0.0",
|
|
45
45
|
"tailwindcss": "^4.0.0",
|
|
46
46
|
"ws": "^8.18.0",
|
|
47
|
-
"@constela/compiler": "0.14.
|
|
48
|
-
"@constela/core": "0.16.
|
|
49
|
-
"@constela/
|
|
50
|
-
"@constela/
|
|
51
|
-
"@constela/
|
|
47
|
+
"@constela/compiler": "0.14.7",
|
|
48
|
+
"@constela/core": "0.16.2",
|
|
49
|
+
"@constela/ai": "1.0.2",
|
|
50
|
+
"@constela/server": "12.0.2",
|
|
51
|
+
"@constela/router": "18.0.1",
|
|
52
|
+
"@constela/runtime": "0.19.7"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@types/ws": "^8.5.0",
|