@constela/compiler 0.9.0 → 0.10.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/README.md CHANGED
@@ -45,6 +45,14 @@ The compiler transforms JSON programs through three passes:
45
45
  2. **Analyze** - Semantic analysis (state, actions, components, routes)
46
46
  3. **Transform** - AST to optimized runtime program
47
47
 
48
+ ## Supported Features
49
+
50
+ - **setPath** - Compiles to efficient path-based state updates
51
+ - **Key-based each** - Compiles key expressions for list diffing
52
+ - **WebSocket connections** - Compiles connection definitions and send/close actions
53
+ - **concat expression** - Compiles string concatenation expressions
54
+ - **Object payloads** - Supports object-shaped event handler payloads with expression fields
55
+
48
56
  ## CompiledProgram Structure
49
57
 
50
58
  ```json
@@ -63,6 +71,13 @@ The compiler transforms JSON programs through three passes:
63
71
  "state": {
64
72
  "count": { "type": "number", "initial": 0 }
65
73
  },
74
+ "connections": {
75
+ "chat": {
76
+ "type": "websocket",
77
+ "url": { ... },
78
+ "onMessage": { "action": "handleMessage" }
79
+ }
80
+ },
66
81
  "actions": {
67
82
  "increment": {
68
83
  "name": "increment",
package/dist/index.d.ts CHANGED
@@ -217,7 +217,26 @@ interface CompiledCloseStep {
217
217
  do: 'close';
218
218
  connection: string;
219
219
  }
220
- type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode | CompiledSlotNode;
220
+ /**
221
+ * Compiled local action
222
+ */
223
+ interface CompiledLocalAction {
224
+ name: string;
225
+ steps: CompiledActionStep[];
226
+ }
227
+ /**
228
+ * Compiled local state node - wraps component with local state
229
+ */
230
+ interface CompiledLocalStateNode {
231
+ kind: 'localState';
232
+ state: Record<string, {
233
+ type: string;
234
+ initial: unknown;
235
+ }>;
236
+ actions: Record<string, CompiledLocalAction>;
237
+ child: CompiledNode;
238
+ }
239
+ type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode | CompiledSlotNode | CompiledLocalStateNode;
221
240
  interface CompiledElementNode {
222
241
  kind: 'element';
223
242
  tag: string;
@@ -256,7 +275,7 @@ interface CompiledSlotNode {
256
275
  kind: 'slot';
257
276
  name?: string;
258
277
  }
259
- type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr | CompiledStyleExpr;
278
+ type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr | CompiledStyleExpr | CompiledConcatExpr;
260
279
  interface CompiledLitExpr {
261
280
  expr: 'lit';
262
281
  value: string | number | boolean | null | unknown[];
@@ -322,10 +341,14 @@ interface CompiledStyleExpr {
322
341
  name: string;
323
342
  variants?: Record<string, CompiledExpression>;
324
343
  }
344
+ interface CompiledConcatExpr {
345
+ expr: 'concat';
346
+ items: CompiledExpression[];
347
+ }
325
348
  interface CompiledEventHandler {
326
349
  event: string;
327
350
  action: string;
328
- payload?: CompiledExpression;
351
+ payload?: CompiledExpression | Record<string, CompiledExpression>;
329
352
  }
330
353
  /**
331
354
  * Transforms the validated and analyzed AST into a CompiledProgram
@@ -455,4 +478,4 @@ declare function transformLayoutPass(layout: LayoutProgram, _context: LayoutAnal
455
478
  */
456
479
  declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, layoutParams?: Record<string, Expression>, slots?: Record<string, ViewNode>): CompiledProgram;
457
480
 
458
- export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledBinExpr, type CompiledCallStep, type CompiledClipboardStep, type CompiledCloseStep, type CompiledCodeNode, type CompiledCondExpr, type CompiledDataExpr, type CompiledDisposeStep, type CompiledDomStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledGetExpr, type CompiledIfNode, type CompiledIfStep, type CompiledImportExpr, type CompiledImportStep, type CompiledIndexExpr, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledLitExpr, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledNotExpr, type CompiledProgram, type CompiledRefExpr, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSendStep, type CompiledSetPathStep, type CompiledSetStep, type CompiledSlotNode, type CompiledStateExpr, type CompiledStorageStep, type CompiledSubscribeStep, type CompiledTextNode, type CompiledUpdateStep, type CompiledVarExpr, type LayoutAnalysisContext, type LayoutAnalysisFailure, type LayoutAnalysisResult, type LayoutAnalysisSuccess, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzeLayoutPass, analyzePass, compile, composeLayoutWithPage, transformLayoutPass, transformPass, validatePass };
481
+ export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledBinExpr, type CompiledCallStep, type CompiledClipboardStep, type CompiledCloseStep, type CompiledCodeNode, type CompiledConcatExpr, type CompiledCondExpr, type CompiledDataExpr, type CompiledDisposeStep, type CompiledDomStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledGetExpr, type CompiledIfNode, type CompiledIfStep, type CompiledImportExpr, type CompiledImportStep, type CompiledIndexExpr, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledLitExpr, type CompiledLocalAction, type CompiledLocalStateNode, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledNotExpr, type CompiledParamExpr, type CompiledProgram, type CompiledRefExpr, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSendStep, type CompiledSetPathStep, type CompiledSetStep, type CompiledSlotNode, type CompiledStateExpr, type CompiledStorageStep, type CompiledStyleExpr, type CompiledSubscribeStep, type CompiledTextNode, type CompiledUpdateStep, type CompiledVarExpr, type LayoutAnalysisContext, type LayoutAnalysisFailure, type LayoutAnalysisResult, type LayoutAnalysisSuccess, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzeLayoutPass, analyzePass, compile, composeLayoutWithPage, transformLayoutPass, transformPass, validatePass };
package/dist/index.js CHANGED
@@ -44,6 +44,8 @@ import {
44
44
  createUndefinedRefError,
45
45
  createUndefinedStyleError,
46
46
  createUndefinedVariantError,
47
+ createUndefinedLocalStateError,
48
+ createLocalActionInvalidStepError,
47
49
  findSimilarNames,
48
50
  isEventHandler,
49
51
  DATA_SOURCE_TYPES,
@@ -157,12 +159,19 @@ function checkDuplicateActions(ast2) {
157
159
  function validateExpression(expr, path, context, scope, paramScope) {
158
160
  const errors = [];
159
161
  switch (expr.expr) {
160
- case "state":
161
- if (!context.stateNames.has(expr.name)) {
162
- const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
162
+ case "state": {
163
+ const isGlobalState = context.stateNames.has(expr.name);
164
+ const isLocalState = paramScope?.localStateNames?.has(expr.name) ?? false;
165
+ if (!isGlobalState && !isLocalState) {
166
+ const availableNames = /* @__PURE__ */ new Set([
167
+ ...context.stateNames,
168
+ ...paramScope?.localStateNames ?? []
169
+ ]);
170
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, availableNames);
163
171
  errors.push(createUndefinedStateError(expr.name, path, errorOptions));
164
172
  }
165
173
  break;
174
+ }
166
175
  case "var":
167
176
  if (!scope.has(expr.name)) {
168
177
  errors.push(createUndefinedVarError(expr.name, path));
@@ -706,8 +715,14 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
706
715
  for (const [propName, propValue] of Object.entries(node.props)) {
707
716
  const propPath = buildPath(path, "props", propName);
708
717
  if (isEventHandler(propValue)) {
709
- if (!context.actionNames.has(propValue.action)) {
710
- const errorOptions = createErrorOptionsWithSuggestion(propValue.action, context.actionNames);
718
+ const isGlobalAction = context.actionNames.has(propValue.action);
719
+ const isLocalAction = paramScope?.localActionNames?.has(propValue.action) ?? false;
720
+ if (!isGlobalAction && !isLocalAction) {
721
+ const availableNames = /* @__PURE__ */ new Set([
722
+ ...context.actionNames,
723
+ ...paramScope?.localActionNames ?? []
724
+ ]);
725
+ const errorOptions = createErrorOptionsWithSuggestion(propValue.action, availableNames);
711
726
  errors.push(createUndefinedActionError(propValue.action, propPath, errorOptions));
712
727
  }
713
728
  if (propValue.payload) {
@@ -910,21 +925,58 @@ function detectComponentCycles(programAst, context) {
910
925
  }
911
926
  return errors;
912
927
  }
928
+ function validateLocalActions(localActions, localStateNames, componentPath) {
929
+ const errors = [];
930
+ for (let i = 0; i < localActions.length; i++) {
931
+ const action = localActions[i];
932
+ if (!action) continue;
933
+ const actionPath = buildPath(componentPath, "localActions", i);
934
+ for (let j = 0; j < action.steps.length; j++) {
935
+ const step = action.steps[j];
936
+ if (!step) continue;
937
+ const stepPath = buildPath(actionPath, "steps", j);
938
+ const stepDo = step.do;
939
+ if (stepDo !== "set" && stepDo !== "update" && stepDo !== "setPath") {
940
+ errors.push(createLocalActionInvalidStepError(stepDo, stepPath));
941
+ continue;
942
+ }
943
+ if (!localStateNames.has(step.target)) {
944
+ const errorOptions = createErrorOptionsWithSuggestion(step.target, localStateNames);
945
+ errors.push(createUndefinedLocalStateError(step.target, buildPath(stepPath, "target"), errorOptions));
946
+ }
947
+ }
948
+ }
949
+ return errors;
950
+ }
913
951
  function validateComponents(programAst, context) {
914
952
  const errors = [];
915
953
  if (!programAst.components) return errors;
916
954
  for (const [name, def] of Object.entries(programAst.components)) {
955
+ const componentPath = buildPath("", "components", name);
956
+ const localStateNames = new Set(
957
+ def.localState ? Object.keys(def.localState) : []
958
+ );
959
+ const localActionNames = new Set(
960
+ def.localActions ? def.localActions.map((a) => a.name) : []
961
+ );
917
962
  const paramNames = new Set(
918
963
  def.params ? Object.keys(def.params) : []
919
964
  );
920
965
  const paramScope = {
921
966
  params: paramNames,
922
- componentName: name
967
+ componentName: name,
968
+ localStateNames,
969
+ localActionNames
923
970
  };
971
+ if (def.localActions && def.localActions.length > 0) {
972
+ errors.push(
973
+ ...validateLocalActions(def.localActions, localStateNames, componentPath)
974
+ );
975
+ }
924
976
  errors.push(
925
977
  ...validateViewNode(
926
978
  def.view,
927
- buildPath("", "components", name, "view"),
979
+ buildPath(componentPath, "view"),
928
980
  context,
929
981
  /* @__PURE__ */ new Set(),
930
982
  { insideComponent: true, paramScope }
@@ -1178,6 +1230,11 @@ function transformExpression(expr, ctx) {
1178
1230
  }
1179
1231
  return styleExpr;
1180
1232
  }
1233
+ case "concat":
1234
+ return {
1235
+ expr: "concat",
1236
+ items: expr.items.map((item) => transformExpression(item, ctx))
1237
+ };
1181
1238
  }
1182
1239
  }
1183
1240
  function transformEventHandler(handler, ctx) {
@@ -1475,7 +1532,16 @@ function transformViewNode(node, ctx) {
1475
1532
  currentParams: params,
1476
1533
  currentChildren: children
1477
1534
  };
1478
- return transformViewNode(def.view, newCtx);
1535
+ const expandedView = transformViewNode(def.view, newCtx);
1536
+ if (def.localState && Object.keys(def.localState).length > 0) {
1537
+ return {
1538
+ kind: "localState",
1539
+ state: transformLocalState(def.localState),
1540
+ actions: transformLocalActions(def.localActions ?? []),
1541
+ child: expandedView
1542
+ };
1543
+ }
1544
+ return expandedView;
1479
1545
  }
1480
1546
  case "markdown":
1481
1547
  return {
@@ -1514,6 +1580,23 @@ function transformState(state) {
1514
1580
  }
1515
1581
  return compiledState;
1516
1582
  }
1583
+ function transformLocalState(localState) {
1584
+ const result = {};
1585
+ for (const [name, field] of Object.entries(localState)) {
1586
+ result[name] = { type: field.type, initial: field.initial };
1587
+ }
1588
+ return result;
1589
+ }
1590
+ function transformLocalActions(localActions) {
1591
+ const result = {};
1592
+ for (const action of localActions) {
1593
+ result[action.name] = {
1594
+ name: action.name,
1595
+ steps: action.steps.map(transformActionStep)
1596
+ };
1597
+ }
1598
+ return result;
1599
+ }
1517
1600
  function transformActions(actions) {
1518
1601
  const compiledActions = {};
1519
1602
  for (const action of actions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/compiler",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Compiler for Constela UI framework - AST to Program transformation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@constela/core": "0.9.0"
18
+ "@constela/core": "0.10.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.10.0",