@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 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 imports = hasWidgets ? `import { hydrateApp, createApp } from '@constela/runtime';` : `import { hydrateApp } from '@constela/runtime';`;
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-OXGKHULH.js";
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 hydrationScript = generateHydrationScript(composedProgram, widgets, routeContext);
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 hmrScript = hmrServer ? `<script>window.__CONSTELA_HMR_URL__ = "ws://${host}:${hmrServer.port}";</script>` : "";
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
@@ -4,8 +4,8 @@ import {
4
4
  hyperlink,
5
5
  loadConfig,
6
6
  resolveConfig
7
- } from "../chunk-L3BK4NOH.js";
8
- import "../chunk-OXGKHULH.js";
7
+ } from "../chunk-QAO2W6TZ.js";
8
+ import "../chunk-IZPFJSX3.js";
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -28,14 +28,14 @@ import {
28
28
  transformCsv,
29
29
  transformMdx,
30
30
  transformYaml
31
- } from "./chunk-L3BK4NOH.js";
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-OXGKHULH.js";
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
  *
@@ -4,7 +4,7 @@ import {
4
4
  generateMetaTags,
5
5
  renderPage,
6
6
  wrapHtml
7
- } from "../chunk-OXGKHULH.js";
7
+ } from "../chunk-IZPFJSX3.js";
8
8
  export {
9
9
  evaluateMetaExpression,
10
10
  generateHydrationScript,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/start",
3
- "version": "1.8.21",
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/compiler": "0.14.4",
48
- "@constela/server": "11.0.1",
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/runtime": "0.19.2",
51
- "@constela/core": "0.15.2"
51
+ "@constela/core": "0.16.0",
52
+ "@constela/server": "12.0.0"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/ws": "^8.5.0",