@constela/compiler 0.6.1 → 0.7.1

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 ADDED
@@ -0,0 +1,278 @@
1
+ # @constela/compiler
2
+
3
+ Compiler for the Constela UI framework - transforms AST to optimized runtime programs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @constela/compiler
9
+ ```
10
+
11
+ ## Overview
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
+ }
37
+ ```
38
+
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';
52
+
53
+ const result = validatePass(input);
54
+ if (result.ok) {
55
+ // result.program is a validated Program
56
+ }
57
+ ```
58
+
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
+ ```
71
+
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
80
+
81
+ #### transformPass
82
+
83
+ ```typescript
84
+ import { transformPass } from '@constela/compiler';
85
+
86
+ const compiledProgram = transformPass(program, analysisContext);
87
+ ```
88
+
89
+ ## Layout Compilation
90
+
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
101
+ }
102
+ ```
103
+
104
+ **Layout Validations:**
105
+ - At least one slot exists
106
+ - No duplicate named slots
107
+ - No duplicate default slots
108
+ - Slots not inside loops
109
+
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
133
+
134
+ ## CompiledProgram Structure
135
+
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>;
160
+ }
161
+ ```
162
+
163
+ ## Compiled Types
164
+
165
+ ### CompiledExpression (13 types)
166
+
167
+ All expression types are preserved with optimizations:
168
+
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)
187
+
188
+ ```typescript
189
+ type CompiledNode =
190
+ | CompiledElementNode
191
+ | CompiledTextNode
192
+ | CompiledIfNode
193
+ | CompiledEachNode
194
+ | CompiledMarkdownNode
195
+ | CompiledCodeNode
196
+ | CompiledSlotNode;
197
+ ```
198
+
199
+ ### CompiledAction
200
+
201
+ ```typescript
202
+ interface CompiledAction {
203
+ name: string;
204
+ params?: Record<string, { type: string }>;
205
+ steps: CompiledStep[];
206
+ }
207
+ ```
208
+
209
+ ### CompiledStep (12 types)
210
+
211
+ ```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
+ ```
226
+
227
+ ## Error Handling
228
+
229
+ The compiler collects multiple errors during the analyze pass:
230
+
231
+ ```typescript
232
+ const result = compile(input);
233
+
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
+ }
240
+ ```
241
+
242
+ Errors include JSON Pointer paths for precise location reporting.
243
+
244
+ ## Example
245
+
246
+ ```typescript
247
+ import { compile } from '@constela/compiler';
248
+
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
+ }
274
+ ```
275
+
276
+ ## License
277
+
278
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Program, ConstelaError, LayoutProgram, ComponentDef, ViewNode } from '@constela/core';
1
+ import { Program, ConstelaError, Expression, LayoutProgram, ComponentDef, ViewNode } from '@constela/core';
2
2
  export { createUndefinedVarError } from '@constela/core';
3
3
 
4
4
  /**
@@ -63,6 +63,7 @@ interface CompiledRouteDefinition {
63
63
  params: string[];
64
64
  title?: CompiledExpression;
65
65
  layout?: string;
66
+ layoutParams?: Record<string, Expression>;
66
67
  meta?: Record<string, CompiledExpression>;
67
68
  }
68
69
  interface CompiledLifecycleHooks {
@@ -87,7 +88,7 @@ interface CompiledAction {
87
88
  name: string;
88
89
  steps: CompiledActionStep[];
89
90
  }
90
- type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep;
91
+ type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep | CompiledDomStep | CompiledIfStep;
91
92
  interface CompiledSetStep {
92
93
  do: 'set';
93
94
  target: string;
@@ -178,7 +179,23 @@ interface CompiledDisposeStep {
178
179
  do: 'dispose';
179
180
  target: CompiledExpression;
180
181
  }
181
- type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode;
182
+ /**
183
+ * Compiled DOM manipulation step
184
+ */
185
+ interface CompiledDomStep {
186
+ do: 'dom';
187
+ operation: 'addClass' | 'removeClass' | 'toggleClass' | 'setAttribute' | 'removeAttribute';
188
+ selector: CompiledExpression;
189
+ value?: CompiledExpression;
190
+ attribute?: string;
191
+ }
192
+ interface CompiledIfStep {
193
+ do: 'if';
194
+ condition: CompiledExpression;
195
+ then: CompiledActionStep[];
196
+ else?: CompiledActionStep[];
197
+ }
198
+ type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode | CompiledSlotNode;
182
199
  interface CompiledElementNode {
183
200
  kind: 'element';
184
201
  tag: string;
@@ -213,7 +230,11 @@ interface CompiledCodeNode {
213
230
  language: CompiledExpression;
214
231
  content: CompiledExpression;
215
232
  }
216
- type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledRefExpr;
233
+ interface CompiledSlotNode {
234
+ kind: 'slot';
235
+ name?: string;
236
+ }
237
+ type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr;
217
238
  interface CompiledLitExpr {
218
239
  expr: 'lit';
219
240
  value: string | number | boolean | null | unknown[];
@@ -221,6 +242,7 @@ interface CompiledLitExpr {
221
242
  interface CompiledStateExpr {
222
243
  expr: 'state';
223
244
  name: string;
245
+ path?: string;
224
246
  }
225
247
  interface CompiledVarExpr {
226
248
  expr: 'var';
@@ -258,6 +280,21 @@ interface CompiledImportExpr {
258
280
  name: string;
259
281
  path?: string;
260
282
  }
283
+ interface CompiledDataExpr {
284
+ expr: 'data';
285
+ name: string;
286
+ path?: string;
287
+ }
288
+ interface CompiledIndexExpr {
289
+ expr: 'index';
290
+ base: CompiledExpression;
291
+ key: CompiledExpression;
292
+ }
293
+ interface CompiledParamExpr {
294
+ expr: 'param';
295
+ name: string;
296
+ path?: string;
297
+ }
261
298
  interface CompiledEventHandler {
262
299
  event: string;
263
300
  action: string;
@@ -380,6 +417,7 @@ interface CompiledLayoutProgram {
380
417
  actions: CompiledAction[];
381
418
  view: CompiledNode;
382
419
  components?: Record<string, ComponentDef> | undefined;
420
+ importData?: Record<string, unknown>;
383
421
  }
384
422
  /**
385
423
  * Transforms a layout program into a compiled layout
@@ -388,6 +426,6 @@ declare function transformLayoutPass(layout: LayoutProgram, _context: LayoutAnal
388
426
  /**
389
427
  * Composes a layout with a page, inserting page content into slots
390
428
  */
391
- declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, slots?: Record<string, ViewNode>): CompiledProgram;
429
+ declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, layoutParams?: Record<string, Expression>, slots?: Record<string, ViewNode>): CompiledProgram;
392
430
 
393
- export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledCallStep, type CompiledClipboardStep, type CompiledCodeNode, type CompiledDisposeStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledIfNode, type CompiledImportExpr, type CompiledImportStep, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledProgram, type CompiledRefExpr, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSetStep, type CompiledStorageStep, type CompiledSubscribeStep, type CompiledTextNode, type CompiledUpdateStep, type LayoutAnalysisContext, type LayoutAnalysisFailure, type LayoutAnalysisResult, type LayoutAnalysisSuccess, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzeLayoutPass, analyzePass, compile, composeLayoutWithPage, transformLayoutPass, transformPass, validatePass };
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 };
package/dist/index.js CHANGED
@@ -958,11 +958,16 @@ function transformExpression(expr, ctx) {
958
958
  expr: "lit",
959
959
  value: expr.value
960
960
  };
961
- case "state":
962
- return {
961
+ case "state": {
962
+ const stateExpr = {
963
963
  expr: "state",
964
964
  name: expr.name
965
965
  };
966
+ if (expr.path) {
967
+ stateExpr.path = expr.path;
968
+ }
969
+ return stateExpr;
970
+ }
966
971
  case "var": {
967
972
  const varExpr = {
968
973
  expr: "var",
@@ -1052,6 +1057,12 @@ function transformExpression(expr, ctx) {
1052
1057
  }
1053
1058
  case "ref":
1054
1059
  return { expr: "ref", name: expr.name };
1060
+ case "index":
1061
+ return {
1062
+ expr: "index",
1063
+ base: transformExpression(expr.base, ctx),
1064
+ key: transformExpression(expr.key, ctx)
1065
+ };
1055
1066
  }
1056
1067
  }
1057
1068
  function transformEventHandler(handler, ctx) {
@@ -1219,6 +1230,16 @@ function transformActionStep(step) {
1219
1230
  target: transformExpression(disposeStep.target, emptyContext)
1220
1231
  };
1221
1232
  }
1233
+ case "dom": {
1234
+ const domStep = step;
1235
+ return {
1236
+ do: "dom",
1237
+ operation: domStep.operation,
1238
+ selector: transformExpression(domStep.selector, emptyContext),
1239
+ ...domStep.value && { value: transformExpression(domStep.value, emptyContext) },
1240
+ ...domStep.attribute && { attribute: domStep.attribute }
1241
+ };
1242
+ }
1222
1243
  }
1223
1244
  }
1224
1245
  function flattenSlotChildren(children, ctx) {
@@ -1385,6 +1406,9 @@ function transformRouteDefinition(route, ctx) {
1385
1406
  if (route.layout) {
1386
1407
  compiled.layout = route.layout;
1387
1408
  }
1409
+ if (route.layoutParams) {
1410
+ compiled.layoutParams = route.layoutParams;
1411
+ }
1388
1412
  if (route.meta) {
1389
1413
  compiled.meta = {};
1390
1414
  for (const [key, value] of Object.entries(route.meta)) {
@@ -1685,31 +1709,254 @@ function transformState2(state) {
1685
1709
  }
1686
1710
  return result;
1687
1711
  }
1688
- function transformActions2(actions) {
1689
- if (!actions) return [];
1690
- return actions.map((action) => ({
1691
- name: action.name,
1692
- steps: action.steps.map((step) => {
1693
- if (step.do === "set") {
1694
- return {
1695
- do: "set",
1696
- target: step.target,
1697
- value: { expr: "lit", value: null }
1698
- // Simplified for now
1699
- };
1712
+ function transformExpression2(expr) {
1713
+ switch (expr.expr) {
1714
+ case "lit":
1715
+ return { expr: "lit", value: expr.value };
1716
+ case "state":
1717
+ return { expr: "state", name: expr.name };
1718
+ case "var": {
1719
+ const varExpr = { expr: "var", name: expr.name };
1720
+ if (expr.path) {
1721
+ varExpr.path = expr.path;
1700
1722
  }
1701
- if (step.do === "update") {
1702
- return {
1703
- do: "update",
1704
- target: step.target,
1705
- operation: step.operation
1706
- };
1723
+ return varExpr;
1724
+ }
1725
+ case "bin":
1726
+ return {
1727
+ expr: "bin",
1728
+ op: expr.op,
1729
+ left: transformExpression2(expr.left),
1730
+ right: transformExpression2(expr.right)
1731
+ };
1732
+ case "not":
1733
+ return {
1734
+ expr: "not",
1735
+ operand: transformExpression2(expr.operand)
1736
+ };
1737
+ case "cond":
1738
+ return {
1739
+ expr: "cond",
1740
+ if: transformExpression2(expr.if),
1741
+ then: transformExpression2(expr.then),
1742
+ else: transformExpression2(expr.else)
1743
+ };
1744
+ case "get":
1745
+ return {
1746
+ expr: "get",
1747
+ base: transformExpression2(expr.base),
1748
+ path: expr.path
1749
+ };
1750
+ case "route":
1751
+ return {
1752
+ expr: "route",
1753
+ name: expr.name,
1754
+ source: expr.source ?? "param"
1755
+ };
1756
+ case "import": {
1757
+ const importExpr = { expr: "import", name: expr.name };
1758
+ if (expr.path) {
1759
+ importExpr.path = expr.path;
1760
+ }
1761
+ return importExpr;
1762
+ }
1763
+ case "data": {
1764
+ const dataExpr = { expr: "import", name: expr.name };
1765
+ if (expr.path) {
1766
+ dataExpr.path = expr.path;
1767
+ }
1768
+ return dataExpr;
1769
+ }
1770
+ case "param": {
1771
+ const paramExpr = { expr: "param", name: expr.name };
1772
+ if (expr.path) {
1773
+ paramExpr.path = expr.path;
1707
1774
  }
1775
+ return paramExpr;
1776
+ }
1777
+ case "ref":
1778
+ return { expr: "ref", name: expr.name };
1779
+ default:
1780
+ return { expr: "lit", value: null };
1781
+ }
1782
+ }
1783
+ function transformActionStep2(step) {
1784
+ switch (step.do) {
1785
+ case "set":
1708
1786
  return {
1787
+ do: "set",
1788
+ target: step.target,
1789
+ value: transformExpression2(step.value)
1790
+ };
1791
+ case "update": {
1792
+ const updateStep = {
1793
+ do: "update",
1794
+ target: step.target,
1795
+ operation: step.operation
1796
+ };
1797
+ if (step.value) {
1798
+ updateStep.value = transformExpression2(step.value);
1799
+ }
1800
+ if (step.index) {
1801
+ updateStep.index = transformExpression2(step.index);
1802
+ }
1803
+ if (step.deleteCount) {
1804
+ updateStep.deleteCount = transformExpression2(step.deleteCount);
1805
+ }
1806
+ return updateStep;
1807
+ }
1808
+ case "fetch": {
1809
+ const fetchStep = {
1709
1810
  do: "fetch",
1710
- url: { expr: "lit", value: "" }
1811
+ url: transformExpression2(step.url)
1711
1812
  };
1712
- })
1813
+ if (step.method) {
1814
+ fetchStep.method = step.method;
1815
+ }
1816
+ if (step.body) {
1817
+ fetchStep.body = transformExpression2(step.body);
1818
+ }
1819
+ if (step.result) {
1820
+ fetchStep.result = step.result;
1821
+ }
1822
+ if (step.onSuccess) {
1823
+ fetchStep.onSuccess = step.onSuccess.map(transformActionStep2);
1824
+ }
1825
+ if (step.onError) {
1826
+ fetchStep.onError = step.onError.map(transformActionStep2);
1827
+ }
1828
+ return fetchStep;
1829
+ }
1830
+ case "storage": {
1831
+ const storageStep = step;
1832
+ const compiledStorageStep = {
1833
+ do: "storage",
1834
+ operation: storageStep.operation,
1835
+ key: transformExpression2(storageStep.key),
1836
+ storage: storageStep.storage
1837
+ };
1838
+ if (storageStep.value) {
1839
+ compiledStorageStep.value = transformExpression2(storageStep.value);
1840
+ }
1841
+ if (storageStep.result) {
1842
+ compiledStorageStep.result = storageStep.result;
1843
+ }
1844
+ if (storageStep.onSuccess) {
1845
+ compiledStorageStep.onSuccess = storageStep.onSuccess.map(transformActionStep2);
1846
+ }
1847
+ if (storageStep.onError) {
1848
+ compiledStorageStep.onError = storageStep.onError.map(transformActionStep2);
1849
+ }
1850
+ return compiledStorageStep;
1851
+ }
1852
+ case "clipboard": {
1853
+ const clipboardStep = step;
1854
+ const compiledClipboardStep = {
1855
+ do: "clipboard",
1856
+ operation: clipboardStep.operation
1857
+ };
1858
+ if (clipboardStep.value) {
1859
+ compiledClipboardStep.value = transformExpression2(clipboardStep.value);
1860
+ }
1861
+ if (clipboardStep.result) {
1862
+ compiledClipboardStep.result = clipboardStep.result;
1863
+ }
1864
+ if (clipboardStep.onSuccess) {
1865
+ compiledClipboardStep.onSuccess = clipboardStep.onSuccess.map(transformActionStep2);
1866
+ }
1867
+ if (clipboardStep.onError) {
1868
+ compiledClipboardStep.onError = clipboardStep.onError.map(transformActionStep2);
1869
+ }
1870
+ return compiledClipboardStep;
1871
+ }
1872
+ case "navigate": {
1873
+ const navigateStep = step;
1874
+ const compiledNavigateStep = {
1875
+ do: "navigate",
1876
+ url: transformExpression2(navigateStep.url)
1877
+ };
1878
+ if (navigateStep.target) {
1879
+ compiledNavigateStep.target = navigateStep.target;
1880
+ }
1881
+ if (navigateStep.replace !== void 0) {
1882
+ compiledNavigateStep.replace = navigateStep.replace;
1883
+ }
1884
+ return compiledNavigateStep;
1885
+ }
1886
+ case "import": {
1887
+ const importStep = step;
1888
+ const compiledImportStep = {
1889
+ do: "import",
1890
+ module: importStep.module,
1891
+ result: importStep.result
1892
+ };
1893
+ if (importStep.onSuccess) {
1894
+ compiledImportStep.onSuccess = importStep.onSuccess.map(transformActionStep2);
1895
+ }
1896
+ if (importStep.onError) {
1897
+ compiledImportStep.onError = importStep.onError.map(transformActionStep2);
1898
+ }
1899
+ return compiledImportStep;
1900
+ }
1901
+ case "call": {
1902
+ const callStep = step;
1903
+ const compiledCallStep = {
1904
+ do: "call",
1905
+ target: transformExpression2(callStep.target)
1906
+ };
1907
+ if (callStep.args) {
1908
+ compiledCallStep.args = callStep.args.map((arg) => transformExpression2(arg));
1909
+ }
1910
+ if (callStep.result) {
1911
+ compiledCallStep.result = callStep.result;
1912
+ }
1913
+ if (callStep.onSuccess) {
1914
+ compiledCallStep.onSuccess = callStep.onSuccess.map(transformActionStep2);
1915
+ }
1916
+ if (callStep.onError) {
1917
+ compiledCallStep.onError = callStep.onError.map(transformActionStep2);
1918
+ }
1919
+ return compiledCallStep;
1920
+ }
1921
+ case "subscribe": {
1922
+ const subscribeStep = step;
1923
+ return {
1924
+ do: "subscribe",
1925
+ target: transformExpression2(subscribeStep.target),
1926
+ event: subscribeStep.event,
1927
+ action: subscribeStep.action
1928
+ };
1929
+ }
1930
+ case "dispose": {
1931
+ const disposeStep = step;
1932
+ return {
1933
+ do: "dispose",
1934
+ target: transformExpression2(disposeStep.target)
1935
+ };
1936
+ }
1937
+ case "dom": {
1938
+ const domStep = step;
1939
+ return {
1940
+ do: "dom",
1941
+ operation: domStep.operation,
1942
+ selector: transformExpression2(domStep.selector),
1943
+ ...domStep.value && { value: transformExpression2(domStep.value) },
1944
+ ...domStep.attribute && { attribute: domStep.attribute }
1945
+ };
1946
+ }
1947
+ default:
1948
+ return {
1949
+ do: "set",
1950
+ target: "_unknown",
1951
+ value: { expr: "lit", value: null }
1952
+ };
1953
+ }
1954
+ }
1955
+ function transformActions2(actions) {
1956
+ if (!actions) return [];
1957
+ return actions.map((action) => ({
1958
+ name: action.name,
1959
+ steps: action.steps.map(transformActionStep2)
1713
1960
  }));
1714
1961
  }
1715
1962
  function transformViewNode2(node, ctx) {
@@ -1791,7 +2038,7 @@ function transformLayoutPass(layout, _context) {
1791
2038
  const ctx = {
1792
2039
  components: layout.components || {}
1793
2040
  };
1794
- return {
2041
+ const result = {
1795
2042
  version: "1.0",
1796
2043
  type: "layout",
1797
2044
  state: transformState2(layout.state),
@@ -1799,17 +2046,161 @@ function transformLayoutPass(layout, _context) {
1799
2046
  view: transformViewNode2(layout.view, ctx),
1800
2047
  components: layout.components
1801
2048
  };
2049
+ if (layout.importData && Object.keys(layout.importData).length > 0) {
2050
+ result.importData = layout.importData;
2051
+ }
2052
+ return result;
1802
2053
  }
1803
2054
  function deepCloneNode(node) {
1804
2055
  return JSON.parse(JSON.stringify(node));
1805
2056
  }
2057
+ function isParamExpression(value) {
2058
+ return typeof value === "object" && value !== null && value.expr === "param" && typeof value.name === "string";
2059
+ }
2060
+ function resolveParamExpression(paramExpr, layoutParams) {
2061
+ const resolvedValue = layoutParams[paramExpr.name];
2062
+ if (!resolvedValue) {
2063
+ return { expr: "lit", value: null };
2064
+ }
2065
+ if (paramExpr.path) {
2066
+ return {
2067
+ expr: "get",
2068
+ base: resolvedValue,
2069
+ path: paramExpr.path
2070
+ };
2071
+ }
2072
+ return resolvedValue;
2073
+ }
2074
+ function resolveExpressionValue(value, layoutParams) {
2075
+ if (!value || typeof value !== "object") {
2076
+ return value;
2077
+ }
2078
+ if (isParamExpression(value)) {
2079
+ return resolveParamExpression(value, layoutParams);
2080
+ }
2081
+ if (Array.isArray(value)) {
2082
+ return value.map((item) => resolveExpressionValue(item, layoutParams));
2083
+ }
2084
+ const obj = value;
2085
+ const result = {};
2086
+ for (const [key, val] of Object.entries(obj)) {
2087
+ result[key] = resolveExpressionValue(val, layoutParams);
2088
+ }
2089
+ return result;
2090
+ }
2091
+ function resolvePropsParams(props, layoutParams) {
2092
+ const result = {};
2093
+ for (const [key, value] of Object.entries(props)) {
2094
+ result[key] = resolveExpressionValue(value, layoutParams);
2095
+ }
2096
+ return result;
2097
+ }
2098
+ function resolveParamExpressions(node, layoutParams) {
2099
+ switch (node.kind) {
2100
+ case "element": {
2101
+ const elementNode = node;
2102
+ const result = {
2103
+ kind: "element",
2104
+ tag: elementNode.tag
2105
+ };
2106
+ if (elementNode.props) {
2107
+ result.props = resolvePropsParams(
2108
+ elementNode.props,
2109
+ layoutParams
2110
+ );
2111
+ }
2112
+ if (elementNode.children && elementNode.children.length > 0) {
2113
+ result.children = elementNode.children.map(
2114
+ (child) => resolveParamExpressions(child, layoutParams)
2115
+ );
2116
+ }
2117
+ return result;
2118
+ }
2119
+ case "text": {
2120
+ const textNode = node;
2121
+ return {
2122
+ kind: "text",
2123
+ value: resolveExpressionValue(textNode.value, layoutParams)
2124
+ };
2125
+ }
2126
+ case "if": {
2127
+ const ifNode = node;
2128
+ const result = {
2129
+ kind: "if",
2130
+ condition: resolveExpressionValue(ifNode.condition, layoutParams),
2131
+ then: resolveParamExpressions(ifNode.then, layoutParams)
2132
+ };
2133
+ if (ifNode.else) {
2134
+ result.else = resolveParamExpressions(
2135
+ ifNode.else,
2136
+ layoutParams
2137
+ );
2138
+ }
2139
+ return result;
2140
+ }
2141
+ case "each": {
2142
+ const eachNode = node;
2143
+ return {
2144
+ kind: "each",
2145
+ items: resolveExpressionValue(eachNode.items, layoutParams),
2146
+ as: eachNode.as,
2147
+ body: resolveParamExpressions(eachNode.body, layoutParams)
2148
+ };
2149
+ }
2150
+ default:
2151
+ return node;
2152
+ }
2153
+ }
2154
+ function processNamedSlotsOnly(node, namedContent) {
2155
+ if (node.kind === "slot") {
2156
+ const slotName = node.name;
2157
+ if (slotName && namedContent[slotName]) {
2158
+ return deepCloneNode(namedContent[slotName]);
2159
+ }
2160
+ return node;
2161
+ }
2162
+ if (node.kind === "element") {
2163
+ const children = node.children;
2164
+ if (children && children.length > 0) {
2165
+ const newChildren = children.map((child) => processNamedSlotsOnly(child, namedContent));
2166
+ return {
2167
+ ...node,
2168
+ children: newChildren
2169
+ };
2170
+ }
2171
+ return node;
2172
+ }
2173
+ if (node.kind === "if") {
2174
+ const ifNode = node;
2175
+ const result = {
2176
+ ...node,
2177
+ then: processNamedSlotsOnly(ifNode.then, namedContent)
2178
+ };
2179
+ if (ifNode.else) {
2180
+ result.else = processNamedSlotsOnly(ifNode.else, namedContent);
2181
+ }
2182
+ return result;
2183
+ }
2184
+ if (node.kind === "each") {
2185
+ const eachNode = node;
2186
+ return {
2187
+ ...node,
2188
+ body: processNamedSlotsOnly(eachNode.body, namedContent)
2189
+ };
2190
+ }
2191
+ return node;
2192
+ }
1806
2193
  function replaceSlots(node, defaultContent, namedContent) {
1807
2194
  if (node.kind === "slot") {
1808
2195
  const slotName = node.name;
1809
2196
  if (slotName && namedContent?.[slotName]) {
1810
2197
  return deepCloneNode(namedContent[slotName]);
1811
2198
  }
1812
- return deepCloneNode(defaultContent);
2199
+ const clonedDefault = deepCloneNode(defaultContent);
2200
+ if (namedContent && Object.keys(namedContent).length > 0) {
2201
+ return processNamedSlotsOnly(clonedDefault, namedContent);
2202
+ }
2203
+ return clonedDefault;
1813
2204
  }
1814
2205
  if (node.kind === "element") {
1815
2206
  const children = node.children;
@@ -1842,11 +2233,31 @@ function replaceSlots(node, defaultContent, namedContent) {
1842
2233
  }
1843
2234
  return node;
1844
2235
  }
1845
- function composeLayoutWithPage(layout, page, slots) {
1846
- const layoutView = deepCloneNode(layout.view);
1847
- const namedContent = slots ? Object.fromEntries(
1848
- Object.entries(slots).map(([name, node]) => [name, node])
1849
- ) : void 0;
2236
+ function extractMdxSlotsFromImportData(importData) {
2237
+ if (!importData) return void 0;
2238
+ for (const [, dataSource] of Object.entries(importData)) {
2239
+ if (!Array.isArray(dataSource)) continue;
2240
+ for (const item of dataSource) {
2241
+ if (typeof item === "object" && item !== null && "content" in item && typeof item.content === "object") {
2242
+ const content = item.content;
2243
+ return { "mdx-content": content };
2244
+ }
2245
+ }
2246
+ }
2247
+ return void 0;
2248
+ }
2249
+ function composeLayoutWithPage(layout, page, layoutParams, slots) {
2250
+ let layoutView = deepCloneNode(layout.view);
2251
+ const resolvedParams = layoutParams ?? {};
2252
+ layoutView = resolveParamExpressions(layoutView, resolvedParams);
2253
+ let namedContent;
2254
+ if (slots) {
2255
+ namedContent = Object.fromEntries(
2256
+ Object.entries(slots).map(([name, node]) => [name, node])
2257
+ );
2258
+ } else {
2259
+ namedContent = extractMdxSlotsFromImportData(page.importData);
2260
+ }
1850
2261
  const composedView = replaceSlots(layoutView, page.view, namedContent);
1851
2262
  const mergedState = {};
1852
2263
  for (const [name, field] of Object.entries(page.state)) {
@@ -1897,6 +2308,16 @@ function composeLayoutWithPage(layout, page, slots) {
1897
2308
  if (Object.keys(mergedComponents).length > 0) {
1898
2309
  result.components = mergedComponents;
1899
2310
  }
2311
+ const mergedImportData = {
2312
+ ...layout.importData || {},
2313
+ ...page.importData || {}
2314
+ };
2315
+ if (Object.keys(mergedImportData).length > 0) {
2316
+ result.importData = mergedImportData;
2317
+ }
2318
+ if (page.lifecycle) {
2319
+ result.lifecycle = page.lifecycle;
2320
+ }
1900
2321
  return result;
1901
2322
  }
1902
2323
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/compiler",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Compiler for Constela UI framework - AST to Program transformation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",