@constela/compiler 0.7.1 → 0.9.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
@@ -1,6 +1,6 @@
1
1
  # @constela/compiler
2
2
 
3
- Compiler for the Constela UI framework - transforms AST to optimized runtime programs.
3
+ Transforms Constela JSON programs into optimized runtime code.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,96 +8,88 @@ Compiler for the Constela UI framework - transforms AST to optimized runtime pro
8
8
  npm install @constela/compiler
9
9
  ```
10
10
 
11
- ## Overview
11
+ ## Usage
12
12
 
13
- This package transforms validated Constela AST into optimized `CompiledProgram` structures for runtime execution. It implements a three-pass compiler pipeline:
14
-
15
- 1. **Validate Pass** - Schema and syntax validation
16
- 2. **Analyze Pass** - Semantic analysis and context collection
17
- 3. **Transform Pass** - AST-to-Program transformation
18
-
19
- ## API Reference
20
-
21
- ### compile
22
-
23
- Main compilation function that orchestrates the entire pipeline.
24
-
25
- ```typescript
26
- import { compile } from '@constela/compiler';
27
-
28
- const result = compile(input);
29
-
30
- if (result.ok) {
31
- // Success
32
- console.log(result.program);
33
- } else {
34
- // Failure - array of errors
35
- console.error(result.errors);
36
- }
13
+ ```bash
14
+ constela compile app.json --out dist/app.compiled.json
37
15
  ```
38
16
 
39
- **Parameters:**
40
- - `input: unknown` - Raw program input (typically parsed JSON)
41
-
42
- **Returns:** `CompileResult`
43
- - Success: `{ ok: true, program: CompiledProgram }`
44
- - Failure: `{ ok: false, errors: ConstelaError[] }`
45
-
46
- ### Individual Passes
47
-
48
- #### validatePass
49
-
50
- ```typescript
51
- import { validatePass } from '@constela/compiler';
17
+ ## JSON Input
52
18
 
53
- const result = validatePass(input);
54
- if (result.ok) {
55
- // result.program is a validated Program
19
+ ```json
20
+ {
21
+ "version": "1.0",
22
+ "state": {
23
+ "count": { "type": "number", "initial": 0 }
24
+ },
25
+ "actions": [
26
+ {
27
+ "name": "increment",
28
+ "steps": [{ "do": "update", "target": "count", "operation": "increment" }]
29
+ }
30
+ ],
31
+ "view": {
32
+ "kind": "element",
33
+ "tag": "button",
34
+ "props": { "onClick": { "event": "click", "action": "increment" } },
35
+ "children": [{ "kind": "text", "value": { "expr": "state", "name": "count" } }]
36
+ }
56
37
  }
57
38
  ```
58
39
 
59
- #### analyzePass
60
-
61
- ```typescript
62
- import { analyzePass } from '@constela/compiler';
63
-
64
- const result = analyzePass(validatedProgram);
65
- if (result.ok) {
66
- // result.context contains analysis results
67
- console.log(result.context.stateNames);
68
- console.log(result.context.actionNames);
69
- }
70
- ```
40
+ ## Compiler Pipeline
71
41
 
72
- **AnalysisContext:**
73
- - `stateNames: Set<string>` - State field identifiers
74
- - `actionNames: Set<string>` - Action names
75
- - `componentNames: Set<string>` - Component identifiers
76
- - `routeParams: Set<string>` - Route parameter names
77
- - `importNames: Set<string>` - External import names
78
- - `dataNames: Set<string>` - Data source names
79
- - `refNames: Set<string>` - DOM element reference names
42
+ The compiler transforms JSON programs through three passes:
80
43
 
81
- #### transformPass
44
+ 1. **Validate** - JSON Schema validation
45
+ 2. **Analyze** - Semantic analysis (state, actions, components, routes)
46
+ 3. **Transform** - AST to optimized runtime program
82
47
 
83
- ```typescript
84
- import { transformPass } from '@constela/compiler';
48
+ ## CompiledProgram Structure
85
49
 
86
- const compiledProgram = transformPass(program, analysisContext);
50
+ ```json
51
+ {
52
+ "version": "1.0",
53
+ "route": {
54
+ "path": "/users/:id",
55
+ "params": ["id"],
56
+ "title": { ... },
57
+ "layout": "MainLayout"
58
+ },
59
+ "lifecycle": {
60
+ "onMount": "loadData",
61
+ "onUnmount": "cleanup"
62
+ },
63
+ "state": {
64
+ "count": { "type": "number", "initial": 0 }
65
+ },
66
+ "actions": {
67
+ "increment": {
68
+ "name": "increment",
69
+ "steps": [{ "do": "update", "target": "count", "operation": "increment" }]
70
+ }
71
+ },
72
+ "view": { ... }
73
+ }
87
74
  ```
88
75
 
89
76
  ## Layout Compilation
90
77
 
91
- Layouts have a separate compilation path with additional validation.
92
-
93
- ### analyzeLayoutPass
94
-
95
- ```typescript
96
- import { analyzeLayoutPass } from '@constela/compiler';
97
-
98
- const result = analyzeLayoutPass(layoutProgram);
99
- if (result.ok) {
100
- // result.context contains layout-specific analysis
78
+ Layouts are compiled separately with slot validation:
79
+
80
+ ```json
81
+ {
82
+ "version": "1.0",
83
+ "type": "layout",
84
+ "view": {
85
+ "kind": "element",
86
+ "tag": "div",
87
+ "children": [
88
+ { "kind": "component", "name": "Header" },
89
+ { "kind": "slot" },
90
+ { "kind": "component", "name": "Footer" }
91
+ ]
92
+ }
101
93
  }
102
94
  ```
103
95
 
@@ -107,170 +99,72 @@ if (result.ok) {
107
99
  - No duplicate default slots
108
100
  - Slots not inside loops
109
101
 
110
- ### transformLayoutPass
111
-
112
- ```typescript
113
- import { transformLayoutPass } from '@constela/compiler';
114
-
115
- const compiledLayout = transformLayoutPass(layoutProgram, layoutContext);
116
- ```
117
-
118
- ### composeLayoutWithPage
119
-
120
- Composes a compiled layout with a page program.
121
-
122
- ```typescript
123
- import { composeLayoutWithPage } from '@constela/compiler';
124
-
125
- const composedProgram = composeLayoutWithPage(compiledLayout, compiledPage);
126
- ```
127
-
128
- **Composition Process:**
129
- - Merges state from both layout and page
130
- - Merges actions from both
131
- - Replaces slot nodes with page content
132
- - Named slots match by name
102
+ ## Error Handling
133
103
 
134
- ## CompiledProgram Structure
104
+ Structured errors with JSON Pointer paths:
135
105
 
136
- ```typescript
137
- interface CompiledProgram {
138
- version: '1.0';
139
- route?: {
140
- path: string;
141
- params: string[];
142
- title?: CompiledExpression;
143
- layout?: string;
144
- layoutParams?: Record<string, CompiledExpression>;
145
- meta?: Record<string, CompiledExpression>;
146
- };
147
- lifecycle?: {
148
- onMount?: string;
149
- onUnmount?: string;
150
- onRouteEnter?: string;
151
- onRouteLeave?: string;
152
- };
153
- state: Record<string, {
154
- type: 'number' | 'string' | 'list' | 'boolean' | 'object';
155
- initial: unknown;
156
- }>;
157
- actions: Record<string, CompiledAction>;
158
- view: CompiledNode;
159
- importData?: Record<string, unknown>;
106
+ ```json
107
+ {
108
+ "code": "UNDEFINED_STATE",
109
+ "message": "State \"count\" is not defined",
110
+ "path": "/view/children/0/props/onClick"
160
111
  }
161
112
  ```
162
113
 
163
- ## Compiled Types
114
+ **Error Codes:**
115
+ - `SCHEMA_INVALID` - JSON Schema validation error
116
+ - `UNDEFINED_STATE` - Reference to undefined state
117
+ - `UNDEFINED_ACTION` - Reference to undefined action
118
+ - `DUPLICATE_ACTION` - Duplicate action name
119
+ - `VAR_UNDEFINED` - Undefined variable reference
120
+ - `COMPONENT_NOT_FOUND` - Undefined component
121
+ - `COMPONENT_PROP_MISSING` - Missing required prop
122
+ - `COMPONENT_CYCLE` - Circular component reference
123
+ - `OPERATION_INVALID_FOR_TYPE` - Invalid operation for state type
124
+ - `LAYOUT_MISSING_SLOT` - Layout missing slot node
125
+ - `LAYOUT_NOT_FOUND` - Referenced layout not found
164
126
 
165
- ### CompiledExpression (13 types)
127
+ ## Internal API
166
128
 
167
- All expression types are preserved with optimizations:
129
+ > For framework developers only. End users should use the CLI.
168
130
 
169
- ```typescript
170
- type CompiledExpression =
171
- | CompiledLitExpr
172
- | CompiledStateExpr
173
- | CompiledVarExpr
174
- | CompiledBinExpr
175
- | CompiledNotExpr
176
- | CompiledCondExpr
177
- | CompiledGetExpr
178
- | CompiledRouteExpr
179
- | CompiledImportExpr
180
- | CompiledDataExpr
181
- | CompiledRefExpr
182
- | CompiledIndexExpr
183
- | CompiledParamExpr;
184
- ```
185
-
186
- ### CompiledNode (7 types)
131
+ ### compile
187
132
 
188
133
  ```typescript
189
- type CompiledNode =
190
- | CompiledElementNode
191
- | CompiledTextNode
192
- | CompiledIfNode
193
- | CompiledEachNode
194
- | CompiledMarkdownNode
195
- | CompiledCodeNode
196
- | CompiledSlotNode;
197
- ```
134
+ import { compile } from '@constela/compiler';
198
135
 
199
- ### CompiledAction
136
+ const result = compile(jsonInput);
200
137
 
201
- ```typescript
202
- interface CompiledAction {
203
- name: string;
204
- params?: Record<string, { type: string }>;
205
- steps: CompiledStep[];
138
+ if (result.ok) {
139
+ console.log(result.program);
140
+ } else {
141
+ console.error(result.errors);
206
142
  }
207
143
  ```
208
144
 
209
- ### CompiledStep (12 types)
145
+ ### Individual Passes
210
146
 
211
147
  ```typescript
212
- type CompiledStep =
213
- | CompiledSetStep
214
- | CompiledUpdateStep
215
- | CompiledFetchStep
216
- | CompiledStorageStep
217
- | CompiledClipboardStep
218
- | CompiledNavigateStep
219
- | CompiledImportStep
220
- | CompiledCallStep
221
- | CompiledSubscribeStep
222
- | CompiledDisposeStep
223
- | CompiledDomStep
224
- | CompiledIfStep;
225
- ```
148
+ import { validatePass, analyzePass, transformPass } from '@constela/compiler';
226
149
 
227
- ## Error Handling
228
-
229
- The compiler collects multiple errors during the analyze pass:
150
+ // Step 1: Validate
151
+ const validated = validatePass(input);
230
152
 
231
- ```typescript
232
- const result = compile(input);
153
+ // Step 2: Analyze
154
+ const analyzed = analyzePass(validated.program);
233
155
 
234
- if (!result.ok) {
235
- for (const error of result.errors) {
236
- console.log(`[${error.code}] ${error.message}`);
237
- console.log(` at ${error.path}`);
238
- }
239
- }
156
+ // Step 3: Transform
157
+ const compiled = transformPass(analyzed.program, analyzed.context);
240
158
  ```
241
159
 
242
- Errors include JSON Pointer paths for precise location reporting.
243
-
244
- ## Example
160
+ ### Layout Compilation
245
161
 
246
162
  ```typescript
247
- import { compile } from '@constela/compiler';
163
+ import { analyzeLayoutPass, transformLayoutPass, composeLayoutWithPage } from '@constela/compiler';
248
164
 
249
- const program = {
250
- version: '1.0',
251
- state: {
252
- count: { type: 'number', initial: 0 }
253
- },
254
- actions: [
255
- {
256
- name: 'increment',
257
- steps: [{ do: 'update', target: 'count', operation: 'increment' }]
258
- }
259
- ],
260
- view: {
261
- kind: 'element',
262
- tag: 'button',
263
- props: { onClick: { event: 'click', action: 'increment' } },
264
- children: [{ kind: 'text', value: { expr: 'state', name: 'count' } }]
265
- }
266
- };
267
-
268
- const result = compile(program);
269
-
270
- if (result.ok) {
271
- // result.program is ready for runtime
272
- console.log(result.program.actions.increment);
273
- }
165
+ const layoutResult = analyzeLayoutPass(layoutProgram);
166
+ const compiledLayout = transformLayoutPass(layoutProgram, layoutResult.context);
167
+ const composedProgram = composeLayoutWithPage(compiledLayout, compiledPage);
274
168
  ```
275
169
 
276
170
  ## License
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ interface AnalysisContext {
20
20
  importNames: Set<string>;
21
21
  dataNames: Set<string>;
22
22
  refNames: Set<string>;
23
+ styleNames: Set<string>;
23
24
  }
24
25
  interface AnalyzePassSuccess {
25
26
  ok: true;
@@ -88,7 +89,7 @@ interface CompiledAction {
88
89
  name: string;
89
90
  steps: CompiledActionStep[];
90
91
  }
91
- type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep | CompiledDomStep | CompiledIfStep;
92
+ type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledSetPathStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep | CompiledDomStep | CompiledIfStep | CompiledSendStep | CompiledCloseStep;
92
93
  interface CompiledSetStep {
93
94
  do: 'set';
94
95
  target: string;
@@ -102,6 +103,12 @@ interface CompiledUpdateStep {
102
103
  index?: CompiledExpression;
103
104
  deleteCount?: CompiledExpression;
104
105
  }
106
+ interface CompiledSetPathStep {
107
+ do: 'setPath';
108
+ target: string;
109
+ path: CompiledExpression;
110
+ value: CompiledExpression;
111
+ }
105
112
  interface CompiledFetchStep {
106
113
  do: 'fetch';
107
114
  url: CompiledExpression;
@@ -195,6 +202,21 @@ interface CompiledIfStep {
195
202
  then: CompiledActionStep[];
196
203
  else?: CompiledActionStep[];
197
204
  }
205
+ /**
206
+ * Compiled send step - sends data through a named WebSocket connection
207
+ */
208
+ interface CompiledSendStep {
209
+ do: 'send';
210
+ connection: string;
211
+ data: CompiledExpression;
212
+ }
213
+ /**
214
+ * Compiled close step - closes a named WebSocket connection
215
+ */
216
+ interface CompiledCloseStep {
217
+ do: 'close';
218
+ connection: string;
219
+ }
198
220
  type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode | CompiledSlotNode;
199
221
  interface CompiledElementNode {
200
222
  kind: 'element';
@@ -234,7 +256,7 @@ interface CompiledSlotNode {
234
256
  kind: 'slot';
235
257
  name?: string;
236
258
  }
237
- type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr;
259
+ type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr | CompiledStyleExpr;
238
260
  interface CompiledLitExpr {
239
261
  expr: 'lit';
240
262
  value: string | number | boolean | null | unknown[];
@@ -295,6 +317,11 @@ interface CompiledParamExpr {
295
317
  name: string;
296
318
  path?: string;
297
319
  }
320
+ interface CompiledStyleExpr {
321
+ expr: 'style';
322
+ name: string;
323
+ variants?: Record<string, CompiledExpression>;
324
+ }
298
325
  interface CompiledEventHandler {
299
326
  event: string;
300
327
  action: string;
@@ -428,4 +455,4 @@ declare function transformLayoutPass(layout: LayoutProgram, _context: LayoutAnal
428
455
  */
429
456
  declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, layoutParams?: Record<string, Expression>, slots?: Record<string, ViewNode>): CompiledProgram;
430
457
 
431
- 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 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 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 };
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 };
package/dist/index.js CHANGED
@@ -42,6 +42,9 @@ import {
42
42
  createClipboardWriteMissingValueError,
43
43
  createInvalidNavigateTargetError,
44
44
  createUndefinedRefError,
45
+ createUndefinedStyleError,
46
+ createUndefinedVariantError,
47
+ findSimilarNames,
45
48
  isEventHandler,
46
49
  DATA_SOURCE_TYPES,
47
50
  DATA_TRANSFORMS,
@@ -53,6 +56,15 @@ import {
53
56
  function buildPath(base, ...segments) {
54
57
  return segments.reduce((p, s) => `${p}/${s}`, base);
55
58
  }
59
+ function createErrorOptionsWithSuggestion(name, availableNames) {
60
+ const availableNamesArray = Array.from(availableNames);
61
+ const similarNames = findSimilarNames(name, availableNames);
62
+ const suggestion = similarNames.length > 0 ? `Did you mean '${similarNames[0]}'?` : void 0;
63
+ return {
64
+ suggestion,
65
+ context: { availableNames: availableNamesArray }
66
+ };
67
+ }
56
68
  function extractRouteParams(path) {
57
69
  const params = [];
58
70
  const segments = path.split("/");
@@ -124,7 +136,10 @@ function collectContext(programAst) {
124
136
  programAst.data ? Object.keys(programAst.data) : []
125
137
  );
126
138
  const refNames = collectRefs(programAst.view);
127
- return { stateNames, actionNames, componentNames, routeParams, importNames, dataNames, refNames };
139
+ const styleNames = new Set(
140
+ programAst.styles ? Object.keys(programAst.styles) : []
141
+ );
142
+ return { stateNames, actionNames, componentNames, routeParams, importNames, dataNames, refNames, styleNames };
128
143
  }
129
144
  function checkDuplicateActions(ast2) {
130
145
  const errors = [];
@@ -144,7 +159,8 @@ function validateExpression(expr, path, context, scope, paramScope) {
144
159
  switch (expr.expr) {
145
160
  case "state":
146
161
  if (!context.stateNames.has(expr.name)) {
147
- errors.push(createUndefinedStateError(expr.name, path));
162
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
163
+ errors.push(createUndefinedStateError(expr.name, path, errorOptions));
148
164
  }
149
165
  break;
150
166
  case "var":
@@ -207,6 +223,32 @@ function validateExpression(expr, path, context, scope, paramScope) {
207
223
  case "get":
208
224
  errors.push(...validateExpression(expr.base, buildPath(path, "base"), context, scope, paramScope));
209
225
  break;
226
+ case "style":
227
+ errors.push(...validateStyleExpression(expr, path, context, scope, paramScope));
228
+ break;
229
+ }
230
+ return errors;
231
+ }
232
+ function validateStyleExpression(expr, path, context, scope, paramScope) {
233
+ const errors = [];
234
+ if (!context.styleNames.has(expr.name)) {
235
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.styleNames);
236
+ errors.push(createUndefinedStyleError(expr.name, path, errorOptions));
237
+ return errors;
238
+ }
239
+ if (expr.variants) {
240
+ const stylePreset = ast.styles?.[expr.name];
241
+ const availableVariants = new Set(
242
+ stylePreset?.variants ? Object.keys(stylePreset.variants) : []
243
+ );
244
+ for (const [variantKey, variantValue] of Object.entries(expr.variants)) {
245
+ const variantPath = buildPath(path, "variants", variantKey);
246
+ if (!availableVariants.has(variantKey)) {
247
+ const errorOptions = createErrorOptionsWithSuggestion(variantKey, availableVariants);
248
+ errors.push(createUndefinedVariantError(variantKey, expr.name, variantPath, errorOptions));
249
+ }
250
+ errors.push(...validateExpression(variantValue, variantPath, context, scope, paramScope));
251
+ }
210
252
  }
211
253
  return errors;
212
254
  }
@@ -215,7 +257,8 @@ function validateActionStep(step, path, context) {
215
257
  switch (step.do) {
216
258
  case "set":
217
259
  if (!context.stateNames.has(step.target)) {
218
- errors.push(createUndefinedStateError(step.target, buildPath(path, "target")));
260
+ const errorOptions = createErrorOptionsWithSuggestion(step.target, context.stateNames);
261
+ errors.push(createUndefinedStateError(step.target, buildPath(path, "target"), errorOptions));
219
262
  }
220
263
  errors.push(
221
264
  ...validateExpressionStateOnly(step.value, buildPath(path, "value"), context)
@@ -223,7 +266,8 @@ function validateActionStep(step, path, context) {
223
266
  break;
224
267
  case "update": {
225
268
  if (!context.stateNames.has(step.target)) {
226
- errors.push(createUndefinedStateError(step.target, buildPath(path, "target")));
269
+ const errorOptions = createErrorOptionsWithSuggestion(step.target, context.stateNames);
270
+ errors.push(createUndefinedStateError(step.target, buildPath(path, "target"), errorOptions));
227
271
  } else {
228
272
  const stateField = ast.state[step.target];
229
273
  if (stateField) {
@@ -437,7 +481,8 @@ function validateActionStep(step, path, context) {
437
481
  ...validateExpressionStateOnly(subscribeStep.target, buildPath(path, "target"), context)
438
482
  );
439
483
  if (!context.actionNames.has(subscribeStep.action)) {
440
- errors.push(createUndefinedActionError(subscribeStep.action, buildPath(path, "action")));
484
+ const errorOptions = createErrorOptionsWithSuggestion(subscribeStep.action, context.actionNames);
485
+ errors.push(createUndefinedActionError(subscribeStep.action, buildPath(path, "action"), errorOptions));
441
486
  }
442
487
  break;
443
488
  }
@@ -456,7 +501,8 @@ function validateExpressionStateOnly(expr, path, context) {
456
501
  switch (expr.expr) {
457
502
  case "state":
458
503
  if (!context.stateNames.has(expr.name)) {
459
- errors.push(createUndefinedStateError(expr.name, path));
504
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
505
+ errors.push(createUndefinedStateError(expr.name, path, errorOptions));
460
506
  }
461
507
  break;
462
508
  case "var":
@@ -513,6 +559,32 @@ function validateExpressionStateOnly(expr, path, context) {
513
559
  case "get":
514
560
  errors.push(...validateExpressionStateOnly(expr.base, buildPath(path, "base"), context));
515
561
  break;
562
+ case "style":
563
+ errors.push(...validateStyleExpressionStateOnly(expr, path, context));
564
+ break;
565
+ }
566
+ return errors;
567
+ }
568
+ function validateStyleExpressionStateOnly(expr, path, context) {
569
+ const errors = [];
570
+ if (!context.styleNames.has(expr.name)) {
571
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.styleNames);
572
+ errors.push(createUndefinedStyleError(expr.name, path, errorOptions));
573
+ return errors;
574
+ }
575
+ if (expr.variants) {
576
+ const stylePreset = ast.styles?.[expr.name];
577
+ const availableVariants = new Set(
578
+ stylePreset?.variants ? Object.keys(stylePreset.variants) : []
579
+ );
580
+ for (const [variantKey, variantValue] of Object.entries(expr.variants)) {
581
+ const variantPath = buildPath(path, "variants", variantKey);
582
+ if (!availableVariants.has(variantKey)) {
583
+ const errorOptions = createErrorOptionsWithSuggestion(variantKey, availableVariants);
584
+ errors.push(createUndefinedVariantError(variantKey, expr.name, variantPath, errorOptions));
585
+ }
586
+ errors.push(...validateExpressionStateOnly(variantValue, variantPath, context));
587
+ }
516
588
  }
517
589
  return errors;
518
590
  }
@@ -521,7 +593,8 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
521
593
  switch (expr.expr) {
522
594
  case "state":
523
595
  if (!context.stateNames.has(expr.name)) {
524
- errors.push(createUndefinedStateError(expr.name, path));
596
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
597
+ errors.push(createUndefinedStateError(expr.name, path, errorOptions));
525
598
  }
526
599
  break;
527
600
  case "var":
@@ -595,6 +668,32 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
595
668
  ...validateExpressionInEventPayload(expr.base, buildPath(path, "base"), context, scope)
596
669
  );
597
670
  break;
671
+ case "style":
672
+ errors.push(...validateStyleExpressionInEventPayload(expr, path, context, scope));
673
+ break;
674
+ }
675
+ return errors;
676
+ }
677
+ function validateStyleExpressionInEventPayload(expr, path, context, scope) {
678
+ const errors = [];
679
+ if (!context.styleNames.has(expr.name)) {
680
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.styleNames);
681
+ errors.push(createUndefinedStyleError(expr.name, path, errorOptions));
682
+ return errors;
683
+ }
684
+ if (expr.variants) {
685
+ const stylePreset = ast.styles?.[expr.name];
686
+ const availableVariants = new Set(
687
+ stylePreset?.variants ? Object.keys(stylePreset.variants) : []
688
+ );
689
+ for (const [variantKey, variantValue] of Object.entries(expr.variants)) {
690
+ const variantPath = buildPath(path, "variants", variantKey);
691
+ if (!availableVariants.has(variantKey)) {
692
+ const errorOptions = createErrorOptionsWithSuggestion(variantKey, availableVariants);
693
+ errors.push(createUndefinedVariantError(variantKey, expr.name, variantPath, errorOptions));
694
+ }
695
+ errors.push(...validateExpressionInEventPayload(variantValue, variantPath, context, scope));
696
+ }
598
697
  }
599
698
  return errors;
600
699
  }
@@ -608,7 +707,8 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
608
707
  const propPath = buildPath(path, "props", propName);
609
708
  if (isEventHandler(propValue)) {
610
709
  if (!context.actionNames.has(propValue.action)) {
611
- errors.push(createUndefinedActionError(propValue.action, propPath));
710
+ const errorOptions = createErrorOptionsWithSuggestion(propValue.action, context.actionNames);
711
+ errors.push(createUndefinedActionError(propValue.action, propPath, errorOptions));
612
712
  }
613
713
  if (propValue.payload) {
614
714
  errors.push(
@@ -662,7 +762,8 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
662
762
  }
663
763
  case "component": {
664
764
  if (!context.componentNames.has(node.name)) {
665
- errors.push(createComponentNotFoundError(node.name, path));
765
+ const errorOptions = createErrorOptionsWithSuggestion(node.name, context.componentNames);
766
+ errors.push(createComponentNotFoundError(node.name, path, errorOptions));
666
767
  } else {
667
768
  const componentDef = ast.components?.[node.name];
668
769
  if (componentDef) {
@@ -839,7 +940,8 @@ function validateLifecycleHooks(lifecycle, context) {
839
940
  for (const hook of hooks) {
840
941
  const actionName = lifecycle[hook];
841
942
  if (actionName && !context.actionNames.has(actionName)) {
842
- errors.push(createUndefinedActionError(actionName, `/lifecycle/${hook}`));
943
+ const errorOptions = createErrorOptionsWithSuggestion(actionName, context.actionNames);
944
+ errors.push(createUndefinedActionError(actionName, `/lifecycle/${hook}`, errorOptions));
843
945
  }
844
946
  }
845
947
  return errors;
@@ -1063,6 +1165,19 @@ function transformExpression(expr, ctx) {
1063
1165
  base: transformExpression(expr.base, ctx),
1064
1166
  key: transformExpression(expr.key, ctx)
1065
1167
  };
1168
+ case "style": {
1169
+ const styleExpr = {
1170
+ expr: "style",
1171
+ name: expr.name
1172
+ };
1173
+ if (expr.variants) {
1174
+ styleExpr.variants = {};
1175
+ for (const [key, value] of Object.entries(expr.variants)) {
1176
+ styleExpr.variants[key] = transformExpression(value, ctx);
1177
+ }
1178
+ }
1179
+ return styleExpr;
1180
+ }
1066
1181
  }
1067
1182
  }
1068
1183
  function transformEventHandler(handler, ctx) {
@@ -1101,6 +1216,15 @@ function transformActionStep(step) {
1101
1216
  }
1102
1217
  return updateStep;
1103
1218
  }
1219
+ case "setPath": {
1220
+ const setPathStep = step;
1221
+ return {
1222
+ do: "setPath",
1223
+ target: setPathStep.target,
1224
+ path: transformExpression(setPathStep.path, emptyContext),
1225
+ value: transformExpression(setPathStep.value, emptyContext)
1226
+ };
1227
+ }
1104
1228
  case "fetch": {
1105
1229
  const fetchStep = {
1106
1230
  do: "fetch",
@@ -1240,6 +1364,21 @@ function transformActionStep(step) {
1240
1364
  ...domStep.attribute && { attribute: domStep.attribute }
1241
1365
  };
1242
1366
  }
1367
+ case "send": {
1368
+ const sendStep = step;
1369
+ return {
1370
+ do: "send",
1371
+ connection: sendStep.connection,
1372
+ data: transformExpression(sendStep.data, emptyContext)
1373
+ };
1374
+ }
1375
+ case "close": {
1376
+ const closeStep = step;
1377
+ return {
1378
+ do: "close",
1379
+ connection: closeStep.connection
1380
+ };
1381
+ }
1243
1382
  }
1244
1383
  }
1245
1384
  function flattenSlotChildren(children, ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/compiler",
3
- "version": "0.7.1",
3
+ "version": "0.9.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.7.0"
18
+ "@constela/core": "0.9.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.10.0",