@constela/start 1.8.21 → 1.8.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/{chunk-OXGKHULH.js → chunk-IZPFJSX3.js} +36 -3
- package/dist/{chunk-L3BK4NOH.js → chunk-QAO2W6TZ.js} +25 -4
- package/dist/cli/index.js +2 -2
- package/dist/index.js +2 -2
- package/dist/runtime/entry-server.d.ts +3 -1
- package/dist/runtime/entry-server.js +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -166,6 +166,63 @@ Define reusable layouts:
|
|
|
166
166
|
}
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
### Components with Local State in Layouts
|
|
170
|
+
|
|
171
|
+
Layout components can use `localState` and `localActions` for instance-scoped state:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"version": "1.0",
|
|
176
|
+
"type": "layout",
|
|
177
|
+
"components": {
|
|
178
|
+
"Sidebar": {
|
|
179
|
+
"params": { "items": { "type": "list" } },
|
|
180
|
+
"localState": {
|
|
181
|
+
"expandedCategory": { "type": "string", "initial": "" }
|
|
182
|
+
},
|
|
183
|
+
"localActions": [
|
|
184
|
+
{
|
|
185
|
+
"name": "toggleCategory",
|
|
186
|
+
"steps": [{ "do": "set", "target": "expandedCategory", "value": { "expr": "var", "name": "payload" } }]
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"view": {
|
|
190
|
+
"kind": "each",
|
|
191
|
+
"items": { "expr": "param", "name": "items" },
|
|
192
|
+
"as": "item",
|
|
193
|
+
"body": {
|
|
194
|
+
"kind": "element",
|
|
195
|
+
"tag": "div",
|
|
196
|
+
"children": [
|
|
197
|
+
{
|
|
198
|
+
"kind": "element",
|
|
199
|
+
"tag": "button",
|
|
200
|
+
"props": {
|
|
201
|
+
"onClick": {
|
|
202
|
+
"event": "click",
|
|
203
|
+
"action": "toggleCategory",
|
|
204
|
+
"payload": { "expr": "var", "name": "item", "path": "name" }
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"children": [
|
|
208
|
+
{ "kind": "text", "value": { "expr": "var", "name": "item", "path": "name" } }
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"view": { ... }
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Key features:
|
|
221
|
+
- Components in layouts support `localState` and `localActions`
|
|
222
|
+
- `param` expressions inside components are substituted with prop values
|
|
223
|
+
- Works with dynamic props from `each` loops
|
|
224
|
+
- Each component instance maintains independent state
|
|
225
|
+
|
|
169
226
|
Use layout in pages:
|
|
170
227
|
|
|
171
228
|
```json
|
|
@@ -47,10 +47,20 @@ function toJsIdentifier(id) {
|
|
|
47
47
|
}
|
|
48
48
|
return result;
|
|
49
49
|
}
|
|
50
|
-
function generateHydrationScript(program, widgets, route) {
|
|
50
|
+
function generateHydrationScript(program, widgets, route, hmrUrl) {
|
|
51
51
|
const serializedProgram = escapeJsonForScript(serializeProgram(program));
|
|
52
52
|
const hasWidgets = widgets && widgets.length > 0;
|
|
53
|
-
const
|
|
53
|
+
const enableHmr = hmrUrl && hmrUrl.length > 0;
|
|
54
|
+
let imports;
|
|
55
|
+
if (enableHmr) {
|
|
56
|
+
const baseImports = ["hydrateApp", "createHMRClient", "createHMRHandler", "createErrorOverlay"];
|
|
57
|
+
if (hasWidgets) {
|
|
58
|
+
baseImports.push("createApp");
|
|
59
|
+
}
|
|
60
|
+
imports = `import { ${baseImports.join(", ")} } from '@constela/runtime';`;
|
|
61
|
+
} else {
|
|
62
|
+
imports = hasWidgets ? `import { hydrateApp, createApp } from '@constela/runtime';` : `import { hydrateApp } from '@constela/runtime';`;
|
|
63
|
+
}
|
|
54
64
|
const widgetDeclarations = hasWidgets ? widgets.map((widget) => {
|
|
55
65
|
const jsId = toJsIdentifier(widget.id);
|
|
56
66
|
const serializedWidget = escapeJsonForScript(
|
|
@@ -81,11 +91,34 @@ if (container_${jsId}) {
|
|
|
81
91
|
program,
|
|
82
92
|
container: document.getElementById('app')
|
|
83
93
|
}`;
|
|
94
|
+
let hmrSetup = "";
|
|
95
|
+
if (enableHmr) {
|
|
96
|
+
const escapedHmrUrl = escapeJsString(hmrUrl);
|
|
97
|
+
const handlerOptions = route ? `{
|
|
98
|
+
container: document.getElementById('app'),
|
|
99
|
+
program,
|
|
100
|
+
route
|
|
101
|
+
}` : `{
|
|
102
|
+
container: document.getElementById('app'),
|
|
103
|
+
program
|
|
104
|
+
}`;
|
|
105
|
+
hmrSetup = `
|
|
106
|
+
|
|
107
|
+
const overlay = createErrorOverlay();
|
|
108
|
+
const handler = createHMRHandler(${handlerOptions});
|
|
109
|
+
const client = createHMRClient({
|
|
110
|
+
url: '${escapedHmrUrl}',
|
|
111
|
+
onUpdate: (file, newProgram) => { handler.handleUpdate(newProgram); },
|
|
112
|
+
onError: (file, errors) => { overlay.show(errors); },
|
|
113
|
+
onConnect: () => { console.log('[HMR] Connected'); }
|
|
114
|
+
});
|
|
115
|
+
client.connect();`;
|
|
116
|
+
}
|
|
84
117
|
return `${imports}
|
|
85
118
|
|
|
86
119
|
const program = ${serializedProgram};
|
|
87
120
|
${routeDeclaration ? "\n" + routeDeclaration : ""}${widgetDeclarations ? "\n" + widgetDeclarations : ""}
|
|
88
|
-
hydrateApp(${hydrateOptions});${widgetMounting}`;
|
|
121
|
+
hydrateApp(${hydrateOptions});${hmrSetup}${widgetMounting}`;
|
|
89
122
|
}
|
|
90
123
|
function wrapHtml(content, hydrationScript, head, options) {
|
|
91
124
|
let langAttr = "";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
generateMetaTags,
|
|
4
4
|
renderPage,
|
|
5
5
|
wrapHtml
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-IZPFJSX3.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
|
}
|
|
@@ -2728,15 +2749,15 @@ async function createDevServer(options = {}) {
|
|
|
2728
2749
|
query: Object.fromEntries(url.searchParams.entries()),
|
|
2729
2750
|
path: pathname
|
|
2730
2751
|
};
|
|
2731
|
-
const
|
|
2752
|
+
const hmrUrl = hmrServer ? `ws://${host}:${hmrServer.port}` : void 0;
|
|
2753
|
+
const hydrationScript = generateHydrationScript(composedProgram, widgets, routeContext, hmrUrl);
|
|
2732
2754
|
const metaTags = generateMetaTags(composedProgram.route, {
|
|
2733
2755
|
params: match.params,
|
|
2734
2756
|
query: Object.fromEntries(url.searchParams.entries()),
|
|
2735
2757
|
path: pathname
|
|
2736
2758
|
});
|
|
2737
2759
|
const cssHead = css ? (Array.isArray(css) ? css : [css]).map((p) => `<link rel="stylesheet" href="/${p}">`).join("\n") : "";
|
|
2738
|
-
const
|
|
2739
|
-
const head = [metaTags, cssHead, hmrScript].filter(Boolean).join("\n");
|
|
2760
|
+
const head = [metaTags, cssHead].filter(Boolean).join("\n");
|
|
2740
2761
|
const themeState = composedProgram.state?.["theme"];
|
|
2741
2762
|
let initialTheme;
|
|
2742
2763
|
if (themeState) {
|
package/dist/cli/index.js
CHANGED
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-QAO2W6TZ.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-IZPFJSX3.js";
|
|
39
39
|
|
|
40
40
|
// src/build/ssg.ts
|
|
41
41
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -54,13 +54,15 @@ interface HydrationRouteContext {
|
|
|
54
54
|
* - Serializes the program data
|
|
55
55
|
* - Calls hydrateApp with the program and container element
|
|
56
56
|
* - Optionally mounts widgets using createApp
|
|
57
|
+
* - When hmrUrl is provided, sets up HMR client, handler, and error overlay
|
|
57
58
|
*
|
|
58
59
|
* @param program - The compiled program to hydrate
|
|
59
60
|
* @param widgets - Optional array of widget configurations to mount after hydration
|
|
60
61
|
* @param route - Optional route context for dynamic routes
|
|
62
|
+
* @param hmrUrl - Optional WebSocket URL for HMR connection (development mode)
|
|
61
63
|
* @returns JavaScript module code as string
|
|
62
64
|
*/
|
|
63
|
-
declare function generateHydrationScript(program: CompiledProgram, widgets?: WidgetConfig[], route?: HydrationRouteContext): string;
|
|
65
|
+
declare function generateHydrationScript(program: CompiledProgram, widgets?: WidgetConfig[], route?: HydrationRouteContext, hmrUrl?: string): string;
|
|
64
66
|
/**
|
|
65
67
|
* Wraps rendered content in a complete HTML document.
|
|
66
68
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/start",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.23",
|
|
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/
|
|
48
|
-
"@constela/
|
|
47
|
+
"@constela/ai": "1.0.0",
|
|
48
|
+
"@constela/compiler": "0.14.5",
|
|
49
|
+
"@constela/runtime": "0.19.3",
|
|
49
50
|
"@constela/router": "18.0.0",
|
|
50
|
-
"@constela/
|
|
51
|
-
"@constela/
|
|
51
|
+
"@constela/core": "0.16.0",
|
|
52
|
+
"@constela/server": "12.0.0"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@types/ws": "^8.5.0",
|