@constela/compiler 0.4.0 → 0.5.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Program, ConstelaError } from '@constela/core';
1
+ import { Program, ConstelaError, LayoutProgram, ComponentDef, ViewNode } from '@constela/core';
2
2
  export { createUndefinedVarError } from '@constela/core';
3
3
 
4
4
  /**
@@ -16,6 +16,9 @@ interface AnalysisContext {
16
16
  stateNames: Set<string>;
17
17
  actionNames: Set<string>;
18
18
  componentNames: Set<string>;
19
+ routeParams: Set<string>;
20
+ importNames: Set<string>;
21
+ dataNames: Set<string>;
19
22
  }
20
23
  interface AnalyzePassSuccess {
21
24
  ok: true;
@@ -40,6 +43,7 @@ type AnalyzePassResult = AnalyzePassSuccess | AnalyzePassFailure;
40
43
  * - Validates component references and props
41
44
  * - Detects component cycles
42
45
  * - Validates param references in component definitions
46
+ * - Validates data sources and getStaticPaths
43
47
  *
44
48
  * @param programAst - Validated AST from validate pass
45
49
  * @returns AnalyzePassResult
@@ -53,20 +57,36 @@ declare function analyzePass(programAst: Program): AnalyzePassResult;
53
57
  * that is optimized for runtime execution.
54
58
  */
55
59
 
60
+ interface CompiledRouteDefinition {
61
+ path: string;
62
+ params: string[];
63
+ title?: CompiledExpression;
64
+ layout?: string;
65
+ meta?: Record<string, CompiledExpression>;
66
+ }
67
+ interface CompiledLifecycleHooks {
68
+ onMount?: string;
69
+ onUnmount?: string;
70
+ onRouteEnter?: string;
71
+ onRouteLeave?: string;
72
+ }
56
73
  interface CompiledProgram {
57
74
  version: '1.0';
75
+ route?: CompiledRouteDefinition;
76
+ lifecycle?: CompiledLifecycleHooks;
58
77
  state: Record<string, {
59
78
  type: string;
60
79
  initial: unknown;
61
80
  }>;
62
81
  actions: Record<string, CompiledAction>;
63
82
  view: CompiledNode;
83
+ importData?: Record<string, unknown>;
64
84
  }
65
85
  interface CompiledAction {
66
86
  name: string;
67
87
  steps: CompiledActionStep[];
68
88
  }
69
- type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledFetchStep;
89
+ type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep;
70
90
  interface CompiledSetStep {
71
91
  do: 'set';
72
92
  target: string;
@@ -89,6 +109,30 @@ interface CompiledFetchStep {
89
109
  onSuccess?: CompiledActionStep[];
90
110
  onError?: CompiledActionStep[];
91
111
  }
112
+ interface CompiledStorageStep {
113
+ do: 'storage';
114
+ operation: 'get' | 'set' | 'remove';
115
+ key: CompiledExpression;
116
+ value?: CompiledExpression;
117
+ storage: 'local' | 'session';
118
+ result?: string;
119
+ onSuccess?: CompiledActionStep[];
120
+ onError?: CompiledActionStep[];
121
+ }
122
+ interface CompiledClipboardStep {
123
+ do: 'clipboard';
124
+ operation: 'write' | 'read';
125
+ value?: CompiledExpression;
126
+ result?: string;
127
+ onSuccess?: CompiledActionStep[];
128
+ onError?: CompiledActionStep[];
129
+ }
130
+ interface CompiledNavigateStep {
131
+ do: 'navigate';
132
+ url: CompiledExpression;
133
+ target?: '_self' | '_blank';
134
+ replace?: boolean;
135
+ }
92
136
  type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode;
93
137
  interface CompiledElementNode {
94
138
  kind: 'element';
@@ -123,7 +167,7 @@ interface CompiledCodeNode {
123
167
  language: CompiledExpression;
124
168
  content: CompiledExpression;
125
169
  }
126
- type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr;
170
+ type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr;
127
171
  interface CompiledLitExpr {
128
172
  expr: 'lit';
129
173
  value: string | number | boolean | null | unknown[];
@@ -158,6 +202,16 @@ interface CompiledGetExpr {
158
202
  base: CompiledExpression;
159
203
  path: string;
160
204
  }
205
+ interface CompiledRouteExpr {
206
+ expr: 'route';
207
+ name: string;
208
+ source: 'param' | 'query' | 'path';
209
+ }
210
+ interface CompiledImportExpr {
211
+ expr: 'import';
212
+ name: string;
213
+ path?: string;
214
+ }
161
215
  interface CompiledEventHandler {
162
216
  event: string;
163
217
  action: string;
@@ -168,9 +222,10 @@ interface CompiledEventHandler {
168
222
  *
169
223
  * @param ast - Validated AST from validate pass
170
224
  * @param _context - Analysis context from analyze pass (unused in current implementation)
225
+ * @param importData - Optional resolved import data to include in the compiled program
171
226
  * @returns CompiledProgram
172
227
  */
173
- declare function transformPass(ast: Program, _context: AnalysisContext): CompiledProgram;
228
+ declare function transformPass(ast: Program, _context: AnalysisContext, importData?: Record<string, unknown>): CompiledProgram;
174
229
 
175
230
  /**
176
231
  * Main compile function for @constela/compiler
@@ -222,4 +277,71 @@ type ValidatePassResult = ValidatePassSuccess | ValidatePassFailure;
222
277
  */
223
278
  declare function validatePass(input: unknown): ValidatePassResult;
224
279
 
225
- export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledCodeNode, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledIfNode, type CompiledMarkdownNode, type CompiledNode, type CompiledProgram, type CompiledSetStep, type CompiledTextNode, type CompiledUpdateStep, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzePass, compile, transformPass, validatePass };
280
+ /**
281
+ * Layout Analysis Pass - Semantic validation for layout programs
282
+ *
283
+ * This pass performs semantic analysis on layout programs:
284
+ * - Validates that at least one slot node exists
285
+ * - Detects duplicate named slots
286
+ * - Validates state and action references within layouts
287
+ * - Warns/errors for slots inside loops
288
+ */
289
+
290
+ interface LayoutAnalysisContext {
291
+ stateNames: Set<string>;
292
+ actionNames: Set<string>;
293
+ componentNames: Set<string>;
294
+ routeParams: Set<string>;
295
+ importNames: Set<string>;
296
+ slotNames: Set<string>;
297
+ hasDefaultSlot: boolean;
298
+ }
299
+ interface LayoutAnalysisSuccess {
300
+ ok: true;
301
+ context: LayoutAnalysisContext;
302
+ }
303
+ interface LayoutAnalysisFailure {
304
+ ok: false;
305
+ errors: ConstelaError[];
306
+ }
307
+ type LayoutAnalysisResult = LayoutAnalysisSuccess | LayoutAnalysisFailure;
308
+ /**
309
+ * Performs static analysis on a layout program
310
+ *
311
+ * - Validates at least one slot exists
312
+ * - Detects duplicate slot names
313
+ * - Validates state references
314
+ * - Validates action references
315
+ *
316
+ * @param layout - Layout program to analyze
317
+ * @returns LayoutAnalysisResult
318
+ */
319
+ declare function analyzeLayoutPass(layout: LayoutProgram): LayoutAnalysisResult;
320
+
321
+ /**
322
+ * Layout Transform Pass - Layout to CompiledProgram transformation and composition
323
+ *
324
+ * This pass transforms layout programs and composes them with page programs.
325
+ */
326
+
327
+ interface CompiledLayoutProgram {
328
+ version: '1.0';
329
+ type: 'layout';
330
+ state: Record<string, {
331
+ type: string;
332
+ initial: unknown;
333
+ }>;
334
+ actions: CompiledAction[];
335
+ view: CompiledNode;
336
+ components?: Record<string, ComponentDef> | undefined;
337
+ }
338
+ /**
339
+ * Transforms a layout program into a compiled layout
340
+ */
341
+ declare function transformLayoutPass(layout: LayoutProgram, _context: LayoutAnalysisContext): CompiledLayoutProgram;
342
+ /**
343
+ * Composes a layout with a page, inserting page content into slots
344
+ */
345
+ declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, slots?: Record<string, ViewNode>): CompiledProgram;
346
+
347
+ export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledClipboardStep, type CompiledCodeNode, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledIfNode, type CompiledImportExpr, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledProgram, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSetStep, type CompiledStorageStep, 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 };
package/dist/index.js CHANGED
@@ -27,18 +27,57 @@ import {
27
27
  createSchemaError,
28
28
  createOperationInvalidForTypeError,
29
29
  createOperationMissingFieldError,
30
- isEventHandler
30
+ createUndefinedRouteParamError,
31
+ createRouteNotDefinedError,
32
+ createUndefinedImportError,
33
+ createImportsNotDefinedError,
34
+ createInvalidDataSourceError,
35
+ createUndefinedDataSourceError,
36
+ createDataNotDefinedError,
37
+ createUndefinedDataError,
38
+ createInvalidStorageOperationError,
39
+ createInvalidStorageTypeError,
40
+ createStorageSetMissingValueError,
41
+ createInvalidClipboardOperationError,
42
+ createClipboardWriteMissingValueError,
43
+ createInvalidNavigateTargetError,
44
+ isEventHandler,
45
+ DATA_SOURCE_TYPES,
46
+ DATA_TRANSFORMS,
47
+ STORAGE_OPERATIONS,
48
+ STORAGE_TYPES,
49
+ CLIPBOARD_OPERATIONS,
50
+ NAVIGATE_TARGETS
31
51
  } from "@constela/core";
32
52
  function buildPath(base, ...segments) {
33
53
  return segments.reduce((p, s) => `${p}/${s}`, base);
34
54
  }
55
+ function extractRouteParams(path) {
56
+ const params = [];
57
+ const segments = path.split("/");
58
+ for (const segment of segments) {
59
+ if (segment.startsWith(":")) {
60
+ params.push(segment.slice(1));
61
+ }
62
+ }
63
+ return params;
64
+ }
35
65
  function collectContext(ast2) {
36
66
  const stateNames = new Set(Object.keys(ast2.state));
37
67
  const actionNames = new Set(ast2.actions.map((a) => a.name));
38
68
  const componentNames = new Set(
39
69
  ast2.components ? Object.keys(ast2.components) : []
40
70
  );
41
- return { stateNames, actionNames, componentNames };
71
+ const routeParams = new Set(
72
+ ast2.route ? extractRouteParams(ast2.route.path) : []
73
+ );
74
+ const importNames = new Set(
75
+ ast2.imports ? Object.keys(ast2.imports) : []
76
+ );
77
+ const dataNames = new Set(
78
+ ast2.data ? Object.keys(ast2.data) : []
79
+ );
80
+ return { stateNames, actionNames, componentNames, routeParams, importNames, dataNames };
42
81
  }
43
82
  function checkDuplicateActions(ast2) {
44
83
  const errors = [];
@@ -71,6 +110,33 @@ function validateExpression(expr, path, context, scope, paramScope) {
71
110
  errors.push(createUndefinedParamError(expr.name, path));
72
111
  }
73
112
  break;
113
+ case "route": {
114
+ if (!hasRoute) {
115
+ errors.push(createRouteNotDefinedError(path));
116
+ } else {
117
+ const source = expr.source ?? "param";
118
+ if (source === "param" && !context.routeParams.has(expr.name)) {
119
+ errors.push(createUndefinedRouteParamError(expr.name, path));
120
+ }
121
+ }
122
+ break;
123
+ }
124
+ case "import": {
125
+ if (!hasImports) {
126
+ errors.push(createImportsNotDefinedError(path));
127
+ } else if (!context.importNames.has(expr.name)) {
128
+ errors.push(createUndefinedImportError(expr.name, path));
129
+ }
130
+ break;
131
+ }
132
+ case "data": {
133
+ if (!hasData) {
134
+ errors.push(createDataNotDefinedError(path));
135
+ } else if (!context.dataNames.has(expr.name)) {
136
+ errors.push(createUndefinedDataError(expr.name, path));
137
+ }
138
+ break;
139
+ }
74
140
  case "bin":
75
141
  errors.push(...validateExpression(expr.left, buildPath(path, "left"), context, scope, paramScope));
76
142
  errors.push(...validateExpression(expr.right, buildPath(path, "right"), context, scope, paramScope));
@@ -174,6 +240,88 @@ function validateActionStep(step, path, context) {
174
240
  }
175
241
  }
176
242
  break;
243
+ case "storage": {
244
+ const storageStep = step;
245
+ if (!STORAGE_OPERATIONS.includes(storageStep.operation)) {
246
+ errors.push(createInvalidStorageOperationError(storageStep.operation, path));
247
+ }
248
+ if (!STORAGE_TYPES.includes(storageStep.storage)) {
249
+ errors.push(createInvalidStorageTypeError(storageStep.storage, path));
250
+ }
251
+ errors.push(
252
+ ...validateExpressionStateOnly(storageStep.key, buildPath(path, "key"), context)
253
+ );
254
+ if (storageStep.operation === "set" && !storageStep.value) {
255
+ errors.push(createStorageSetMissingValueError(path));
256
+ }
257
+ if (storageStep.value) {
258
+ errors.push(
259
+ ...validateExpressionStateOnly(storageStep.value, buildPath(path, "value"), context)
260
+ );
261
+ }
262
+ if (storageStep.onSuccess) {
263
+ for (let i = 0; i < storageStep.onSuccess.length; i++) {
264
+ const successStep = storageStep.onSuccess[i];
265
+ if (successStep === void 0) continue;
266
+ errors.push(
267
+ ...validateActionStep(successStep, buildPath(path, "onSuccess", i), context)
268
+ );
269
+ }
270
+ }
271
+ if (storageStep.onError) {
272
+ for (let i = 0; i < storageStep.onError.length; i++) {
273
+ const errorStep = storageStep.onError[i];
274
+ if (errorStep === void 0) continue;
275
+ errors.push(
276
+ ...validateActionStep(errorStep, buildPath(path, "onError", i), context)
277
+ );
278
+ }
279
+ }
280
+ break;
281
+ }
282
+ case "clipboard": {
283
+ const clipboardStep = step;
284
+ if (!CLIPBOARD_OPERATIONS.includes(clipboardStep.operation)) {
285
+ errors.push(createInvalidClipboardOperationError(clipboardStep.operation, path));
286
+ }
287
+ if (clipboardStep.operation === "write" && !clipboardStep.value) {
288
+ errors.push(createClipboardWriteMissingValueError(path));
289
+ }
290
+ if (clipboardStep.value) {
291
+ errors.push(
292
+ ...validateExpressionStateOnly(clipboardStep.value, buildPath(path, "value"), context)
293
+ );
294
+ }
295
+ if (clipboardStep.onSuccess) {
296
+ for (let i = 0; i < clipboardStep.onSuccess.length; i++) {
297
+ const successStep = clipboardStep.onSuccess[i];
298
+ if (successStep === void 0) continue;
299
+ errors.push(
300
+ ...validateActionStep(successStep, buildPath(path, "onSuccess", i), context)
301
+ );
302
+ }
303
+ }
304
+ if (clipboardStep.onError) {
305
+ for (let i = 0; i < clipboardStep.onError.length; i++) {
306
+ const errorStep = clipboardStep.onError[i];
307
+ if (errorStep === void 0) continue;
308
+ errors.push(
309
+ ...validateActionStep(errorStep, buildPath(path, "onError", i), context)
310
+ );
311
+ }
312
+ }
313
+ break;
314
+ }
315
+ case "navigate": {
316
+ const navigateStep = step;
317
+ errors.push(
318
+ ...validateExpressionStateOnly(navigateStep.url, buildPath(path, "url"), context)
319
+ );
320
+ if (navigateStep.target !== void 0 && !NAVIGATE_TARGETS.includes(navigateStep.target)) {
321
+ errors.push(createInvalidNavigateTargetError(navigateStep.target, path));
322
+ }
323
+ break;
324
+ }
177
325
  }
178
326
  return errors;
179
327
  }
@@ -187,6 +335,33 @@ function validateExpressionStateOnly(expr, path, context) {
187
335
  break;
188
336
  case "var":
189
337
  break;
338
+ case "route": {
339
+ if (!hasRoute) {
340
+ errors.push(createRouteNotDefinedError(path));
341
+ } else {
342
+ const source = expr.source ?? "param";
343
+ if (source === "param" && !context.routeParams.has(expr.name)) {
344
+ errors.push(createUndefinedRouteParamError(expr.name, path));
345
+ }
346
+ }
347
+ break;
348
+ }
349
+ case "import": {
350
+ if (!hasImports) {
351
+ errors.push(createImportsNotDefinedError(path));
352
+ } else if (!context.importNames.has(expr.name)) {
353
+ errors.push(createUndefinedImportError(expr.name, path));
354
+ }
355
+ break;
356
+ }
357
+ case "data": {
358
+ if (!hasData) {
359
+ errors.push(createDataNotDefinedError(path));
360
+ } else if (!context.dataNames.has(expr.name)) {
361
+ errors.push(createUndefinedDataError(expr.name, path));
362
+ }
363
+ break;
364
+ }
190
365
  case "bin":
191
366
  errors.push(...validateExpressionStateOnly(expr.left, buildPath(path, "left"), context));
192
367
  errors.push(...validateExpressionStateOnly(expr.right, buildPath(path, "right"), context));
@@ -219,6 +394,33 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
219
394
  break;
220
395
  case "var":
221
396
  break;
397
+ case "route": {
398
+ if (!hasRoute) {
399
+ errors.push(createRouteNotDefinedError(path));
400
+ } else {
401
+ const source = expr.source ?? "param";
402
+ if (source === "param" && !context.routeParams.has(expr.name)) {
403
+ errors.push(createUndefinedRouteParamError(expr.name, path));
404
+ }
405
+ }
406
+ break;
407
+ }
408
+ case "import": {
409
+ if (!hasImports) {
410
+ errors.push(createImportsNotDefinedError(path));
411
+ } else if (!context.importNames.has(expr.name)) {
412
+ errors.push(createUndefinedImportError(expr.name, path));
413
+ }
414
+ break;
415
+ }
416
+ case "data": {
417
+ if (!hasData) {
418
+ errors.push(createDataNotDefinedError(path));
419
+ } else if (!context.dataNames.has(expr.name)) {
420
+ errors.push(createUndefinedDataError(expr.name, path));
421
+ }
422
+ break;
423
+ }
222
424
  case "bin":
223
425
  errors.push(
224
426
  ...validateExpressionInEventPayload(expr.left, buildPath(path, "left"), context, scope)
@@ -260,7 +462,7 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
260
462
  }
261
463
  function validateViewNode(node, path, context, scope, options = { insideComponent: false }) {
262
464
  const errors = [];
263
- const { insideComponent, paramScope } = options;
465
+ const { insideComponent, insideLayout, paramScope } = options;
264
466
  switch (node.kind) {
265
467
  case "element":
266
468
  if (node.props) {
@@ -343,9 +545,9 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
343
545
  break;
344
546
  }
345
547
  case "slot":
346
- if (!insideComponent) {
548
+ if (!insideComponent && !insideLayout) {
347
549
  errors.push(
348
- createSchemaError(`Slot can only be used inside component definitions`, path)
550
+ createSchemaError(`Slot can only be used inside component definitions or layouts`, path)
349
551
  );
350
552
  }
351
553
  break;
@@ -372,6 +574,26 @@ function validateComponentProps(node, componentDef, path, context, scope, paramS
372
574
  return errors;
373
575
  }
374
576
  var ast;
577
+ var hasRoute;
578
+ var hasImports;
579
+ var hasData;
580
+ function validateRouteDefinition(route, context) {
581
+ const errors = [];
582
+ const emptyScope = /* @__PURE__ */ new Set();
583
+ if (route.title) {
584
+ errors.push(
585
+ ...validateExpression(route.title, "/route/title", context, emptyScope)
586
+ );
587
+ }
588
+ if (route.meta) {
589
+ for (const [key, value] of Object.entries(route.meta)) {
590
+ errors.push(
591
+ ...validateExpression(value, `/route/meta/${key}`, context, emptyScope)
592
+ );
593
+ }
594
+ }
595
+ return errors;
596
+ }
375
597
  function collectComponentCalls(node) {
376
598
  const calls = /* @__PURE__ */ new Set();
377
599
  switch (node.kind) {
@@ -472,6 +694,18 @@ function validateComponents(programAst, context) {
472
694
  }
473
695
  return errors;
474
696
  }
697
+ function validateLifecycleHooks(lifecycle, context) {
698
+ const errors = [];
699
+ if (!lifecycle) return errors;
700
+ const hooks = ["onMount", "onUnmount", "onRouteEnter", "onRouteLeave"];
701
+ for (const hook of hooks) {
702
+ const actionName = lifecycle[hook];
703
+ if (actionName && !context.actionNames.has(actionName)) {
704
+ errors.push(createUndefinedActionError(actionName, `/lifecycle/${hook}`));
705
+ }
706
+ }
707
+ return errors;
708
+ }
475
709
  function validateActions(programAst, context) {
476
710
  const errors = [];
477
711
  for (let i = 0; i < programAst.actions.length; i++) {
@@ -487,17 +721,81 @@ function validateActions(programAst, context) {
487
721
  }
488
722
  return errors;
489
723
  }
724
+ function validateDataSources(programAst, context) {
725
+ const errors = [];
726
+ if (!programAst.data) return errors;
727
+ for (const [name, source] of Object.entries(programAst.data)) {
728
+ const path = `/data/${name}`;
729
+ if (!DATA_SOURCE_TYPES.includes(source.type)) {
730
+ errors.push(createInvalidDataSourceError(name, `invalid type '${source.type}'`, path));
731
+ continue;
732
+ }
733
+ if (source.transform !== void 0) {
734
+ if (!DATA_TRANSFORMS.includes(source.transform)) {
735
+ errors.push(createInvalidDataSourceError(name, `invalid transform '${source.transform}'`, path));
736
+ }
737
+ }
738
+ switch (source.type) {
739
+ case "glob":
740
+ if (typeof source.pattern !== "string") {
741
+ errors.push(createInvalidDataSourceError(name, `glob type requires 'pattern' field`, path));
742
+ }
743
+ break;
744
+ case "file":
745
+ if (typeof source.path !== "string") {
746
+ errors.push(createInvalidDataSourceError(name, `file type requires 'path' field`, path));
747
+ }
748
+ break;
749
+ case "api":
750
+ if (typeof source.url !== "string") {
751
+ errors.push(createInvalidDataSourceError(name, `api type requires 'url' field`, path));
752
+ }
753
+ break;
754
+ }
755
+ }
756
+ return errors;
757
+ }
758
+ function validateGetStaticPaths(programAst, context) {
759
+ const errors = [];
760
+ const getStaticPaths = programAst.route?.getStaticPaths;
761
+ if (!getStaticPaths) return errors;
762
+ const path = "/route/getStaticPaths";
763
+ if (!programAst.data) {
764
+ errors.push(createDataNotDefinedError(path));
765
+ return errors;
766
+ }
767
+ if (!context.dataNames.has(getStaticPaths.source)) {
768
+ errors.push(createUndefinedDataSourceError(getStaticPaths.source, path));
769
+ }
770
+ for (const [paramName, paramExpr] of Object.entries(getStaticPaths.params)) {
771
+ errors.push(
772
+ ...validateExpressionStateOnly(paramExpr, `${path}/params/${paramName}`, context)
773
+ );
774
+ }
775
+ return errors;
776
+ }
490
777
  function analyzePass(programAst) {
491
778
  ast = programAst;
779
+ hasRoute = !!programAst.route;
780
+ hasImports = !!programAst.imports;
781
+ hasData = !!programAst.data;
782
+ const isLayout = programAst.type === "layout";
492
783
  const context = collectContext(programAst);
493
784
  const errors = [];
494
785
  errors.push(...checkDuplicateActions(programAst));
786
+ errors.push(...validateDataSources(programAst, context));
787
+ errors.push(...validateGetStaticPaths(programAst, context));
495
788
  errors.push(...validateActions(programAst, context));
789
+ errors.push(...validateLifecycleHooks(programAst.lifecycle, context));
496
790
  errors.push(...detectComponentCycles(programAst, context));
497
791
  errors.push(...validateComponents(programAst, context));
792
+ if (programAst.route) {
793
+ errors.push(...validateRouteDefinition(programAst.route, context));
794
+ }
498
795
  errors.push(
499
796
  ...validateViewNode(programAst.view, "/view", context, /* @__PURE__ */ new Set(), {
500
- insideComponent: false
797
+ insideComponent: false,
798
+ insideLayout: isLayout
501
799
  })
502
800
  );
503
801
  if (errors.length > 0) {
@@ -588,6 +886,32 @@ function transformExpression(expr, ctx) {
588
886
  base: transformExpression(expr.base, ctx),
589
887
  path: expr.path
590
888
  };
889
+ case "route":
890
+ return {
891
+ expr: "route",
892
+ name: expr.name,
893
+ source: expr.source ?? "param"
894
+ };
895
+ case "import": {
896
+ const importExpr = {
897
+ expr: "import",
898
+ name: expr.name
899
+ };
900
+ if (expr.path) {
901
+ importExpr.path = expr.path;
902
+ }
903
+ return importExpr;
904
+ }
905
+ case "data": {
906
+ const dataExpr = {
907
+ expr: "import",
908
+ name: expr.name
909
+ };
910
+ if (expr.path) {
911
+ dataExpr.path = expr.path;
912
+ }
913
+ return dataExpr;
914
+ }
591
915
  }
592
916
  }
593
917
  function transformEventHandler(handler, ctx) {
@@ -648,6 +972,62 @@ function transformActionStep(step) {
648
972
  }
649
973
  return fetchStep;
650
974
  }
975
+ case "storage": {
976
+ const storageStep = step;
977
+ const compiledStorageStep = {
978
+ do: "storage",
979
+ operation: storageStep.operation,
980
+ key: transformExpression(storageStep.key, emptyContext),
981
+ storage: storageStep.storage
982
+ };
983
+ if (storageStep.value) {
984
+ compiledStorageStep.value = transformExpression(storageStep.value, emptyContext);
985
+ }
986
+ if (storageStep.result) {
987
+ compiledStorageStep.result = storageStep.result;
988
+ }
989
+ if (storageStep.onSuccess) {
990
+ compiledStorageStep.onSuccess = storageStep.onSuccess.map(transformActionStep);
991
+ }
992
+ if (storageStep.onError) {
993
+ compiledStorageStep.onError = storageStep.onError.map(transformActionStep);
994
+ }
995
+ return compiledStorageStep;
996
+ }
997
+ case "clipboard": {
998
+ const clipboardStep = step;
999
+ const compiledClipboardStep = {
1000
+ do: "clipboard",
1001
+ operation: clipboardStep.operation
1002
+ };
1003
+ if (clipboardStep.value) {
1004
+ compiledClipboardStep.value = transformExpression(clipboardStep.value, emptyContext);
1005
+ }
1006
+ if (clipboardStep.result) {
1007
+ compiledClipboardStep.result = clipboardStep.result;
1008
+ }
1009
+ if (clipboardStep.onSuccess) {
1010
+ compiledClipboardStep.onSuccess = clipboardStep.onSuccess.map(transformActionStep);
1011
+ }
1012
+ if (clipboardStep.onError) {
1013
+ compiledClipboardStep.onError = clipboardStep.onError.map(transformActionStep);
1014
+ }
1015
+ return compiledClipboardStep;
1016
+ }
1017
+ case "navigate": {
1018
+ const navigateStep = step;
1019
+ const compiledNavigateStep = {
1020
+ do: "navigate",
1021
+ url: transformExpression(navigateStep.url, emptyContext)
1022
+ };
1023
+ if (navigateStep.target) {
1024
+ compiledNavigateStep.target = navigateStep.target;
1025
+ }
1026
+ if (navigateStep.replace !== void 0) {
1027
+ compiledNavigateStep.replace = navigateStep.replace;
1028
+ }
1029
+ return compiledNavigateStep;
1030
+ }
651
1031
  }
652
1032
  }
653
1033
  function flattenSlotChildren(children, ctx) {
@@ -790,16 +1170,75 @@ function transformActions(actions) {
790
1170
  }
791
1171
  return compiledActions;
792
1172
  }
793
- function transformPass(ast2, _context) {
1173
+ function extractRouteParams2(path) {
1174
+ const params = [];
1175
+ const segments = path.split("/");
1176
+ for (const segment of segments) {
1177
+ if (segment.startsWith(":")) {
1178
+ params.push(segment.slice(1));
1179
+ }
1180
+ }
1181
+ return params;
1182
+ }
1183
+ function transformRouteDefinition(route, ctx) {
1184
+ const compiled = {
1185
+ path: route.path,
1186
+ params: extractRouteParams2(route.path)
1187
+ };
1188
+ if (route.title) {
1189
+ compiled.title = transformExpression(route.title, ctx);
1190
+ }
1191
+ if (route.layout) {
1192
+ compiled.layout = route.layout;
1193
+ }
1194
+ if (route.meta) {
1195
+ compiled.meta = {};
1196
+ for (const [key, value] of Object.entries(route.meta)) {
1197
+ compiled.meta[key] = transformExpression(value, ctx);
1198
+ }
1199
+ }
1200
+ return compiled;
1201
+ }
1202
+ function transformLifecycleHooks(lifecycle) {
1203
+ if (!lifecycle) return void 0;
1204
+ const hasAnyHook = lifecycle.onMount || lifecycle.onUnmount || lifecycle.onRouteEnter || lifecycle.onRouteLeave;
1205
+ if (!hasAnyHook) return void 0;
1206
+ const result = {};
1207
+ if (lifecycle.onMount) {
1208
+ result.onMount = lifecycle.onMount;
1209
+ }
1210
+ if (lifecycle.onUnmount) {
1211
+ result.onUnmount = lifecycle.onUnmount;
1212
+ }
1213
+ if (lifecycle.onRouteEnter) {
1214
+ result.onRouteEnter = lifecycle.onRouteEnter;
1215
+ }
1216
+ if (lifecycle.onRouteLeave) {
1217
+ result.onRouteLeave = lifecycle.onRouteLeave;
1218
+ }
1219
+ return result;
1220
+ }
1221
+ function transformPass(ast2, _context, importData) {
794
1222
  const ctx = {
795
1223
  components: ast2.components || {}
796
1224
  };
797
- return {
1225
+ const result = {
798
1226
  version: "1.0",
799
1227
  state: transformState(ast2.state),
800
1228
  actions: transformActions(ast2.actions),
801
1229
  view: transformViewNode(ast2.view, ctx)
802
1230
  };
1231
+ if (ast2.route) {
1232
+ result.route = transformRouteDefinition(ast2.route, ctx);
1233
+ }
1234
+ const lifecycle = transformLifecycleHooks(ast2.lifecycle);
1235
+ if (lifecycle) {
1236
+ result.lifecycle = lifecycle;
1237
+ }
1238
+ if (importData && Object.keys(importData).length > 0) {
1239
+ result.importData = importData;
1240
+ }
1241
+ return result;
803
1242
  }
804
1243
 
805
1244
  // src/compile.ts
@@ -827,10 +1266,452 @@ function compile(input) {
827
1266
 
828
1267
  // src/index.ts
829
1268
  import { createUndefinedVarError as createUndefinedVarError2 } from "@constela/core";
1269
+
1270
+ // src/passes/analyze-layout.ts
1271
+ import {
1272
+ createLayoutMissingSlotError,
1273
+ createDuplicateSlotNameError,
1274
+ createDuplicateDefaultSlotError,
1275
+ createSlotInLoopError,
1276
+ createUndefinedStateError as createUndefinedStateError2,
1277
+ createUndefinedActionError as createUndefinedActionError2,
1278
+ isEventHandler as isEventHandler3
1279
+ } from "@constela/core";
1280
+ function buildPath2(base, ...segments) {
1281
+ return segments.reduce((p, s) => `${p}/${s}`, base);
1282
+ }
1283
+ function collectContext2(layout) {
1284
+ const stateNames = new Set(layout.state ? Object.keys(layout.state) : []);
1285
+ const actionNames = new Set(layout.actions ? layout.actions.map((a) => a.name) : []);
1286
+ const componentNames = new Set(
1287
+ layout.components ? Object.keys(layout.components) : []
1288
+ );
1289
+ return { stateNames, actionNames, componentNames, routeParams: /* @__PURE__ */ new Set(), importNames: /* @__PURE__ */ new Set() };
1290
+ }
1291
+ function findSlotNodes(node, path, slots, inLoop = false) {
1292
+ if (node.kind === "slot") {
1293
+ slots.push({
1294
+ name: node.name,
1295
+ path,
1296
+ inLoop
1297
+ });
1298
+ return;
1299
+ }
1300
+ if (node.kind === "element" && node.children) {
1301
+ for (let i = 0; i < node.children.length; i++) {
1302
+ const child = node.children[i];
1303
+ if (child) {
1304
+ findSlotNodes(child, buildPath2(path, "children", i), slots, inLoop);
1305
+ }
1306
+ }
1307
+ }
1308
+ if (node.kind === "if") {
1309
+ findSlotNodes(node.then, buildPath2(path, "then"), slots, inLoop);
1310
+ if (node.else) {
1311
+ findSlotNodes(node.else, buildPath2(path, "else"), slots, inLoop);
1312
+ }
1313
+ }
1314
+ if (node.kind === "each") {
1315
+ findSlotNodes(node.body, buildPath2(path, "body"), slots, true);
1316
+ }
1317
+ if (node.kind === "component" && node.children) {
1318
+ for (let i = 0; i < node.children.length; i++) {
1319
+ const child = node.children[i];
1320
+ if (child) {
1321
+ findSlotNodes(child, buildPath2(path, "children", i), slots, inLoop);
1322
+ }
1323
+ }
1324
+ }
1325
+ }
1326
+ function validateSlots(layout) {
1327
+ const errors = [];
1328
+ const slots = [];
1329
+ const slotNames = /* @__PURE__ */ new Set();
1330
+ let hasDefaultSlot = false;
1331
+ findSlotNodes(layout.view, "/view", slots);
1332
+ if (slots.length === 0) {
1333
+ errors.push(createLayoutMissingSlotError("/view"));
1334
+ return { errors, slotNames, hasDefaultSlot };
1335
+ }
1336
+ const seenNames = /* @__PURE__ */ new Map();
1337
+ let defaultSlotPath;
1338
+ for (const slot of slots) {
1339
+ if (slot.inLoop) {
1340
+ errors.push(createSlotInLoopError(slot.path));
1341
+ continue;
1342
+ }
1343
+ if (slot.name === void 0 || slot.name === "") {
1344
+ if (defaultSlotPath !== void 0) {
1345
+ errors.push(createDuplicateDefaultSlotError(slot.path));
1346
+ } else {
1347
+ defaultSlotPath = slot.path;
1348
+ hasDefaultSlot = true;
1349
+ }
1350
+ } else {
1351
+ const existingPath = seenNames.get(slot.name);
1352
+ if (existingPath !== void 0) {
1353
+ errors.push(createDuplicateSlotNameError(slot.name, slot.path));
1354
+ } else {
1355
+ seenNames.set(slot.name, slot.path);
1356
+ slotNames.add(slot.name);
1357
+ }
1358
+ }
1359
+ }
1360
+ return { errors, slotNames, hasDefaultSlot };
1361
+ }
1362
+ function validateExpression2(expr, path, stateNames) {
1363
+ const errors = [];
1364
+ switch (expr.expr) {
1365
+ case "state":
1366
+ if (!stateNames.has(expr.name)) {
1367
+ errors.push(createUndefinedStateError2(expr.name, path));
1368
+ }
1369
+ break;
1370
+ case "bin":
1371
+ errors.push(...validateExpression2(expr.left, buildPath2(path, "left"), stateNames));
1372
+ errors.push(...validateExpression2(expr.right, buildPath2(path, "right"), stateNames));
1373
+ break;
1374
+ case "not":
1375
+ errors.push(...validateExpression2(expr.operand, buildPath2(path, "operand"), stateNames));
1376
+ break;
1377
+ case "cond":
1378
+ errors.push(...validateExpression2(expr.if, buildPath2(path, "if"), stateNames));
1379
+ errors.push(...validateExpression2(expr.then, buildPath2(path, "then"), stateNames));
1380
+ errors.push(...validateExpression2(expr.else, buildPath2(path, "else"), stateNames));
1381
+ break;
1382
+ case "get":
1383
+ errors.push(...validateExpression2(expr.base, buildPath2(path, "base"), stateNames));
1384
+ break;
1385
+ case "lit":
1386
+ case "var":
1387
+ case "param":
1388
+ case "route":
1389
+ case "import":
1390
+ break;
1391
+ }
1392
+ return errors;
1393
+ }
1394
+ function validateViewNode2(node, path, stateNames, actionNames) {
1395
+ const errors = [];
1396
+ switch (node.kind) {
1397
+ case "element":
1398
+ if (node.props) {
1399
+ for (const [propName, propValue] of Object.entries(node.props)) {
1400
+ const propPath = buildPath2(path, "props", propName);
1401
+ if (isEventHandler3(propValue)) {
1402
+ if (!actionNames.has(propValue.action)) {
1403
+ errors.push(createUndefinedActionError2(propValue.action, propPath));
1404
+ }
1405
+ if (propValue.payload) {
1406
+ errors.push(
1407
+ ...validateExpression2(propValue.payload, buildPath2(propPath, "payload"), stateNames)
1408
+ );
1409
+ }
1410
+ } else {
1411
+ errors.push(...validateExpression2(propValue, propPath, stateNames));
1412
+ }
1413
+ }
1414
+ }
1415
+ if (node.children) {
1416
+ for (let i = 0; i < node.children.length; i++) {
1417
+ const child = node.children[i];
1418
+ if (child) {
1419
+ errors.push(
1420
+ ...validateViewNode2(child, buildPath2(path, "children", i), stateNames, actionNames)
1421
+ );
1422
+ }
1423
+ }
1424
+ }
1425
+ break;
1426
+ case "text":
1427
+ errors.push(...validateExpression2(node.value, buildPath2(path, "value"), stateNames));
1428
+ break;
1429
+ case "if":
1430
+ errors.push(
1431
+ ...validateExpression2(node.condition, buildPath2(path, "condition"), stateNames)
1432
+ );
1433
+ errors.push(...validateViewNode2(node.then, buildPath2(path, "then"), stateNames, actionNames));
1434
+ if (node.else) {
1435
+ errors.push(...validateViewNode2(node.else, buildPath2(path, "else"), stateNames, actionNames));
1436
+ }
1437
+ break;
1438
+ case "each":
1439
+ errors.push(...validateExpression2(node.items, buildPath2(path, "items"), stateNames));
1440
+ errors.push(...validateViewNode2(node.body, buildPath2(path, "body"), stateNames, actionNames));
1441
+ break;
1442
+ case "slot":
1443
+ break;
1444
+ }
1445
+ return errors;
1446
+ }
1447
+ function analyzeLayoutPass(layout) {
1448
+ const baseContext = collectContext2(layout);
1449
+ const errors = [];
1450
+ const { errors: slotErrors, slotNames, hasDefaultSlot } = validateSlots(layout);
1451
+ errors.push(...slotErrors);
1452
+ if (slotErrors.length > 0) {
1453
+ return {
1454
+ ok: false,
1455
+ errors
1456
+ };
1457
+ }
1458
+ errors.push(
1459
+ ...validateViewNode2(
1460
+ layout.view,
1461
+ "/view",
1462
+ baseContext.stateNames,
1463
+ baseContext.actionNames
1464
+ )
1465
+ );
1466
+ if (errors.length > 0) {
1467
+ return {
1468
+ ok: false,
1469
+ errors
1470
+ };
1471
+ }
1472
+ return {
1473
+ ok: true,
1474
+ context: {
1475
+ ...baseContext,
1476
+ slotNames,
1477
+ hasDefaultSlot
1478
+ }
1479
+ };
1480
+ }
1481
+
1482
+ // src/passes/transform-layout.ts
1483
+ function transformState2(state) {
1484
+ if (!state) return {};
1485
+ const result = {};
1486
+ for (const [name, field] of Object.entries(state)) {
1487
+ result[name] = {
1488
+ type: field.type,
1489
+ initial: field.initial
1490
+ };
1491
+ }
1492
+ return result;
1493
+ }
1494
+ function transformActions2(actions) {
1495
+ if (!actions) return [];
1496
+ return actions.map((action) => ({
1497
+ name: action.name,
1498
+ steps: action.steps.map((step) => {
1499
+ if (step.do === "set") {
1500
+ return {
1501
+ do: "set",
1502
+ target: step.target,
1503
+ value: { expr: "lit", value: null }
1504
+ // Simplified for now
1505
+ };
1506
+ }
1507
+ if (step.do === "update") {
1508
+ return {
1509
+ do: "update",
1510
+ target: step.target,
1511
+ operation: step.operation
1512
+ };
1513
+ }
1514
+ return {
1515
+ do: "fetch",
1516
+ url: { expr: "lit", value: "" }
1517
+ };
1518
+ })
1519
+ }));
1520
+ }
1521
+ function transformViewNode2(node, ctx) {
1522
+ switch (node.kind) {
1523
+ case "element": {
1524
+ const result = {
1525
+ kind: "element",
1526
+ tag: node.tag
1527
+ };
1528
+ if (node.props) {
1529
+ result.props = {};
1530
+ for (const [key, value] of Object.entries(node.props)) {
1531
+ if ("event" in value) {
1532
+ result.props[key] = {
1533
+ event: value.event,
1534
+ action: value.action
1535
+ };
1536
+ } else {
1537
+ result.props[key] = value;
1538
+ }
1539
+ }
1540
+ }
1541
+ if (node.children && node.children.length > 0) {
1542
+ result.children = node.children.map(
1543
+ (child) => transformViewNode2(child, ctx)
1544
+ );
1545
+ }
1546
+ return result;
1547
+ }
1548
+ case "text":
1549
+ return {
1550
+ kind: "text",
1551
+ value: node.value
1552
+ };
1553
+ case "if": {
1554
+ const result = {
1555
+ kind: "if",
1556
+ condition: node.condition,
1557
+ then: transformViewNode2(node.then, ctx)
1558
+ };
1559
+ if (node.else) {
1560
+ result.else = transformViewNode2(node.else, ctx);
1561
+ }
1562
+ return result;
1563
+ }
1564
+ case "each":
1565
+ return {
1566
+ kind: "each",
1567
+ items: node.items,
1568
+ as: node.as,
1569
+ body: transformViewNode2(node.body, ctx)
1570
+ };
1571
+ case "slot":
1572
+ return {
1573
+ kind: "slot",
1574
+ name: node.name
1575
+ };
1576
+ case "component": {
1577
+ const def = ctx.components[node.name];
1578
+ if (def) {
1579
+ return transformViewNode2(def.view, ctx);
1580
+ }
1581
+ return { kind: "element", tag: "div" };
1582
+ }
1583
+ case "markdown":
1584
+ return {
1585
+ kind: "markdown",
1586
+ content: node.content
1587
+ };
1588
+ case "code":
1589
+ return {
1590
+ kind: "code",
1591
+ language: node.language,
1592
+ content: node.content
1593
+ };
1594
+ }
1595
+ }
1596
+ function transformLayoutPass(layout, _context) {
1597
+ const ctx = {
1598
+ components: layout.components || {}
1599
+ };
1600
+ return {
1601
+ version: "1.0",
1602
+ type: "layout",
1603
+ state: transformState2(layout.state),
1604
+ actions: transformActions2(layout.actions),
1605
+ view: transformViewNode2(layout.view, ctx),
1606
+ components: layout.components
1607
+ };
1608
+ }
1609
+ function deepCloneNode(node) {
1610
+ return JSON.parse(JSON.stringify(node));
1611
+ }
1612
+ function replaceSlots(node, defaultContent, namedContent) {
1613
+ if (node.kind === "slot") {
1614
+ const slotName = node.name;
1615
+ if (slotName && namedContent?.[slotName]) {
1616
+ return deepCloneNode(namedContent[slotName]);
1617
+ }
1618
+ return deepCloneNode(defaultContent);
1619
+ }
1620
+ if (node.kind === "element") {
1621
+ const children = node.children;
1622
+ if (children && children.length > 0) {
1623
+ const newChildren = children.map((child) => replaceSlots(child, defaultContent, namedContent));
1624
+ return {
1625
+ ...node,
1626
+ children: newChildren
1627
+ };
1628
+ }
1629
+ return node;
1630
+ }
1631
+ if (node.kind === "if") {
1632
+ const ifNode = node;
1633
+ const result = {
1634
+ ...node,
1635
+ then: replaceSlots(ifNode.then, defaultContent, namedContent)
1636
+ };
1637
+ if (ifNode.else) {
1638
+ result.else = replaceSlots(ifNode.else, defaultContent, namedContent);
1639
+ }
1640
+ return result;
1641
+ }
1642
+ if (node.kind === "each") {
1643
+ const eachNode = node;
1644
+ return {
1645
+ ...node,
1646
+ body: replaceSlots(eachNode.body, defaultContent, namedContent)
1647
+ };
1648
+ }
1649
+ return node;
1650
+ }
1651
+ function composeLayoutWithPage(layout, page, slots) {
1652
+ const layoutView = deepCloneNode(layout.view);
1653
+ const namedContent = slots ? Object.fromEntries(
1654
+ Object.entries(slots).map(([name, node]) => [name, node])
1655
+ ) : void 0;
1656
+ const composedView = replaceSlots(layoutView, page.view, namedContent);
1657
+ const mergedState = {};
1658
+ for (const [name, field] of Object.entries(page.state)) {
1659
+ mergedState[name] = field;
1660
+ }
1661
+ for (const [name, field] of Object.entries(layout.state)) {
1662
+ if (name in mergedState) {
1663
+ mergedState[`$layout.${name}`] = field;
1664
+ } else {
1665
+ mergedState[name] = field;
1666
+ }
1667
+ }
1668
+ const getActionsArray = (actions) => {
1669
+ if (Array.isArray(actions)) return actions;
1670
+ if (typeof actions === "object" && actions !== null) {
1671
+ return Object.values(actions);
1672
+ }
1673
+ return [];
1674
+ };
1675
+ const pageActions = getActionsArray(page.actions);
1676
+ const layoutActions = getActionsArray(layout.actions);
1677
+ const pageActionNames = new Set(pageActions.map((a) => a.name));
1678
+ const mergedActions = {};
1679
+ for (const action of pageActions) {
1680
+ mergedActions[action.name] = action;
1681
+ }
1682
+ for (const action of layoutActions) {
1683
+ if (pageActionNames.has(action.name)) {
1684
+ const prefixedName = `$layout.${action.name}`;
1685
+ mergedActions[prefixedName] = { ...action, name: prefixedName };
1686
+ } else {
1687
+ mergedActions[action.name] = action;
1688
+ }
1689
+ }
1690
+ const mergedComponents = {
1691
+ ...layout.components || {},
1692
+ ...page.components || {}
1693
+ };
1694
+ const result = {
1695
+ version: "1.0",
1696
+ state: mergedState,
1697
+ actions: mergedActions,
1698
+ view: composedView
1699
+ };
1700
+ if (page.route) {
1701
+ result.route = page.route;
1702
+ }
1703
+ if (Object.keys(mergedComponents).length > 0) {
1704
+ result.components = mergedComponents;
1705
+ }
1706
+ return result;
1707
+ }
830
1708
  export {
1709
+ analyzeLayoutPass,
831
1710
  analyzePass,
832
1711
  compile,
1712
+ composeLayoutWithPage,
833
1713
  createUndefinedVarError2 as createUndefinedVarError,
1714
+ transformLayoutPass,
834
1715
  transformPass,
835
1716
  validatePass
836
1717
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/compiler",
3
- "version": "0.4.0",
3
+ "version": "0.5.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.4.0"
18
+ "@constela/core": "0.5.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.10.0",