@constela/core 0.6.0 → 0.8.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 ADDED
@@ -0,0 +1,239 @@
1
+ # @constela/core
2
+
3
+ Core types and validation for Constela JSON programs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @constela/core
9
+ ```
10
+
11
+ ## JSON Program Structure
12
+
13
+ ```json
14
+ {
15
+ "version": "1.0",
16
+ "route": { "path": "/", "layout": "MainLayout" },
17
+ "imports": { "config": "./data/config.json" },
18
+ "data": { "posts": { "type": "glob", "pattern": "content/*.mdx" } },
19
+ "lifecycle": { "onMount": "loadData" },
20
+ "state": { ... },
21
+ "actions": [ ... ],
22
+ "view": { ... },
23
+ "components": { ... }
24
+ }
25
+ ```
26
+
27
+ All fields except `version`, `state`, `actions`, and `view` are optional.
28
+
29
+ ## State Types
30
+
31
+ 5 supported state types:
32
+
33
+ ```json
34
+ {
35
+ "count": { "type": "number", "initial": 0 },
36
+ "query": { "type": "string", "initial": "" },
37
+ "items": { "type": "list", "initial": [] },
38
+ "isVisible": { "type": "boolean", "initial": true },
39
+ "form": { "type": "object", "initial": { "name": "", "email": "" } }
40
+ }
41
+ ```
42
+
43
+ ## Expression Types
44
+
45
+ 12 expression types for constrained computation:
46
+
47
+ | Type | JSON Example | Description |
48
+ |------|-------------|-------------|
49
+ | `lit` | `{ "expr": "lit", "value": "Hello" }` | Literal value |
50
+ | `state` | `{ "expr": "state", "name": "count" }` | State reference |
51
+ | `var` | `{ "expr": "var", "name": "item" }` | Loop/event variable |
52
+ | `bin` | `{ "expr": "bin", "op": "+", "left": ..., "right": ... }` | Binary operation |
53
+ | `not` | `{ "expr": "not", "operand": ... }` | Logical negation |
54
+ | `param` | `{ "expr": "param", "name": "title" }` | Component parameter |
55
+ | `cond` | `{ "expr": "cond", "if": ..., "then": ..., "else": ... }` | Conditional |
56
+ | `get` | `{ "expr": "get", "base": ..., "path": "user.name" }` | Property access |
57
+ | `route` | `{ "expr": "route", "name": "id", "source": "param" }` | Route parameter |
58
+ | `import` | `{ "expr": "import", "name": "config" }` | External data |
59
+ | `data` | `{ "expr": "data", "name": "posts" }` | Build-time data |
60
+ | `ref` | `{ "expr": "ref", "name": "inputEl" }` | DOM element ref |
61
+
62
+ **Binary Operators:** `+`, `-`, `*`, `/`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&&`, `||`
63
+
64
+ ## View Node Types
65
+
66
+ 8 node types for building UI:
67
+
68
+ ```json
69
+ // Element node
70
+ { "kind": "element", "tag": "div", "props": { ... }, "children": [ ... ] }
71
+
72
+ // Text node
73
+ { "kind": "text", "value": { "expr": "state", "name": "count" } }
74
+
75
+ // Conditional node
76
+ { "kind": "if", "condition": { ... }, "then": { ... }, "else": { ... } }
77
+
78
+ // Loop node
79
+ { "kind": "each", "items": { "expr": "state", "name": "todos" }, "as": "item", "body": { ... } }
80
+
81
+ // Component node
82
+ { "kind": "component", "name": "Button", "props": { "label": { ... } } }
83
+
84
+ // Slot node (for layouts)
85
+ { "kind": "slot" }
86
+ { "kind": "slot", "name": "sidebar" }
87
+
88
+ // Markdown node
89
+ { "kind": "markdown", "content": { "expr": "state", "name": "content" } }
90
+
91
+ // Code block node
92
+ { "kind": "code", "code": { ... }, "language": { ... } }
93
+ ```
94
+
95
+ ## Action Step Types
96
+
97
+ 11 step types for declarative actions:
98
+
99
+ ```json
100
+ // Set state value
101
+ { "do": "set", "target": "query", "value": { "expr": "lit", "value": "" } }
102
+
103
+ // Update with operation
104
+ { "do": "update", "target": "count", "operation": "increment" }
105
+ { "do": "update", "target": "todos", "operation": "push", "value": { ... } }
106
+
107
+ // HTTP request
108
+ { "do": "fetch", "url": { ... }, "method": "GET", "onSuccess": [ ... ], "onError": [ ... ] }
109
+
110
+ // Storage operation
111
+ { "do": "storage", "operation": "get", "key": { ... }, "storage": "local" }
112
+
113
+ // Clipboard operation
114
+ { "do": "clipboard", "operation": "write", "value": { ... } }
115
+
116
+ // Navigation
117
+ { "do": "navigate", "url": { ... } }
118
+
119
+ // Dynamic import
120
+ { "do": "import", "module": "chart.js", "result": "Chart" }
121
+
122
+ // External function call
123
+ { "do": "call", "ref": "Chart", "method": "create", "args": [ ... ] }
124
+
125
+ // Event subscription
126
+ { "do": "subscribe", "ref": "eventSource", "event": "message", "action": "handleMessage" }
127
+
128
+ // Resource disposal
129
+ { "do": "dispose", "ref": "chartInstance" }
130
+
131
+ // DOM manipulation
132
+ { "do": "dom", "operation": "addClass", "ref": "myElement", "value": { ... } }
133
+ ```
134
+
135
+ **Update Operations:**
136
+
137
+ | Operation | State Type | Description |
138
+ |-----------|------------|-------------|
139
+ | `increment` | number | Add to number |
140
+ | `decrement` | number | Subtract from number |
141
+ | `push` | list | Add item to end |
142
+ | `pop` | list | Remove last item |
143
+ | `remove` | list | Remove by value/index |
144
+ | `toggle` | boolean | Flip boolean |
145
+ | `merge` | object | Shallow merge |
146
+ | `replaceAt` | list | Replace at index |
147
+ | `insertAt` | list | Insert at index |
148
+ | `splice` | list | Delete/insert items |
149
+
150
+ ## Lifecycle Hooks
151
+
152
+ ```json
153
+ {
154
+ "lifecycle": {
155
+ "onMount": "loadData",
156
+ "onUnmount": "cleanup",
157
+ "onRouteEnter": "fetchData",
158
+ "onRouteLeave": "saveState"
159
+ }
160
+ }
161
+ ```
162
+
163
+ ## Error Codes
164
+
165
+ | Code | Description |
166
+ |------|-------------|
167
+ | `SCHEMA_INVALID` | JSON Schema validation error |
168
+ | `UNSUPPORTED_VERSION` | Unsupported version string |
169
+ | `UNDEFINED_STATE` | Reference to undefined state |
170
+ | `UNDEFINED_ACTION` | Reference to undefined action |
171
+ | `DUPLICATE_ACTION` | Duplicate action name |
172
+ | `VAR_UNDEFINED` | Undefined variable reference |
173
+ | `COMPONENT_NOT_FOUND` | Undefined component |
174
+ | `COMPONENT_PROP_MISSING` | Missing required prop |
175
+ | `COMPONENT_CYCLE` | Circular component reference |
176
+ | `COMPONENT_PROP_TYPE` | Prop type mismatch |
177
+ | `PARAM_UNDEFINED` | Undefined parameter |
178
+ | `OPERATION_INVALID_FOR_TYPE` | Invalid operation for state type |
179
+ | `OPERATION_MISSING_FIELD` | Missing required field for operation |
180
+ | `ROUTE_NOT_DEFINED` | Route not defined |
181
+ | `UNDEFINED_ROUTE_PARAM` | Undefined route parameter |
182
+ | `LAYOUT_MISSING_SLOT` | Layout missing slot node |
183
+ | `LAYOUT_NOT_FOUND` | Referenced layout not found |
184
+ | `INVALID_SLOT_NAME` | Invalid slot name |
185
+ | `DUPLICATE_SLOT_NAME` | Duplicate slot name |
186
+ | `DUPLICATE_DEFAULT_SLOT` | Multiple default slots |
187
+ | `SLOT_IN_LOOP` | Slot inside loop |
188
+ | `UNDEFINED_DATA_SOURCE` | Undefined data source |
189
+ | `UNDEFINED_IMPORT` | Undefined import reference |
190
+ | `UNDEFINED_REF` | Undefined element ref |
191
+ | `INVALID_STORAGE_OPERATION` | Invalid storage operation |
192
+ | `INVALID_CLIPBOARD_OPERATION` | Invalid clipboard operation |
193
+ | `INVALID_NAVIGATE_TARGET` | Invalid navigate target |
194
+
195
+ ## Internal API
196
+
197
+ > For framework developers only.
198
+
199
+ ### validateAst
200
+
201
+ ```typescript
202
+ import { validateAst } from '@constela/core';
203
+
204
+ const result = validateAst(input);
205
+ if (result.ok) {
206
+ console.log('Valid program:', result.program);
207
+ } else {
208
+ console.error('Validation failed:', result.error);
209
+ }
210
+ ```
211
+
212
+ ### Type Guards
213
+
214
+ 47 type guard functions for runtime type checking:
215
+
216
+ ```typescript
217
+ import {
218
+ isLitExpr, isStateExpr, isVarExpr, isBinExpr,
219
+ isElementNode, isTextNode, isIfNode, isEachNode,
220
+ isSetStep, isUpdateStep, isFetchStep,
221
+ isNumberField, isStringField, isListField,
222
+ } from '@constela/core';
223
+ ```
224
+
225
+ ### ConstelaError
226
+
227
+ ```typescript
228
+ import { ConstelaError } from '@constela/core';
229
+
230
+ const error = new ConstelaError(
231
+ 'UNDEFINED_STATE',
232
+ 'State "count" is not defined',
233
+ '/view/children/0/props/onClick'
234
+ );
235
+ ```
236
+
237
+ ## License
238
+
239
+ MIT
package/dist/index.d.ts CHANGED
@@ -37,6 +37,7 @@ interface LitExpr {
37
37
  interface StateExpr {
38
38
  expr: 'state';
39
39
  name: string;
40
+ path?: string;
40
41
  }
41
42
  /**
42
43
  * Variable expression - references a loop variable or event data
@@ -118,7 +119,23 @@ interface RefExpr {
118
119
  expr: 'ref';
119
120
  name: string;
120
121
  }
121
- type Expression = LitExpr | StateExpr | VarExpr | BinExpr | NotExpr | ParamExpr | CondExpr | GetExpr | RouteExpr | ImportExpr | DataExpr | RefExpr;
122
+ /**
123
+ * Index expression - dynamic property/array access
124
+ */
125
+ interface IndexExpr {
126
+ expr: 'index';
127
+ base: Expression;
128
+ key: Expression;
129
+ }
130
+ /**
131
+ * Style expression - references a style preset with optional variant values
132
+ */
133
+ interface StyleExpr {
134
+ expr: 'style';
135
+ name: string;
136
+ variants?: Record<string, Expression>;
137
+ }
138
+ type Expression = LitExpr | StateExpr | VarExpr | BinExpr | NotExpr | ParamExpr | CondExpr | GetExpr | RouteExpr | ImportExpr | DataExpr | RefExpr | IndexExpr | StyleExpr;
122
139
  /**
123
140
  * Number state field
124
141
  */
@@ -275,7 +292,17 @@ interface DisposeStep {
275
292
  do: 'dispose';
276
293
  target: Expression;
277
294
  }
278
- type ActionStep = SetStep | UpdateStep | FetchStep | StorageStep | ClipboardStep | NavigateStep | ImportStep | CallStep | SubscribeStep | DisposeStep;
295
+ /**
296
+ * DOM step - manipulate DOM elements (add/remove classes, attributes)
297
+ */
298
+ interface DomStep {
299
+ do: 'dom';
300
+ operation: 'addClass' | 'removeClass' | 'toggleClass' | 'setAttribute' | 'removeAttribute';
301
+ selector: Expression;
302
+ value?: Expression;
303
+ attribute?: string;
304
+ }
305
+ type ActionStep = SetStep | UpdateStep | FetchStep | StorageStep | ClipboardStep | NavigateStep | ImportStep | CallStep | SubscribeStep | DisposeStep | DomStep;
279
306
  /**
280
307
  * Event handler - binds an event to an action
281
308
  */
@@ -365,6 +392,22 @@ interface ComponentDef {
365
392
  params?: Record<string, ParamDef>;
366
393
  view: ViewNode;
367
394
  }
395
+ /**
396
+ * Compound variant - applies additional classes when multiple variants match
397
+ */
398
+ interface CompoundVariant {
399
+ [key: string]: string;
400
+ class: string;
401
+ }
402
+ /**
403
+ * Style preset - defines a reusable style with variants (CVA-like pattern)
404
+ */
405
+ interface StylePreset {
406
+ base: string;
407
+ variants?: Record<string, Record<string, string>>;
408
+ defaultVariants?: Record<string, string>;
409
+ compoundVariants?: CompoundVariant[];
410
+ }
368
411
  /**
369
412
  * Data transform types for build-time content loading
370
413
  */
@@ -375,6 +418,13 @@ type DataTransform = (typeof DATA_TRANSFORMS)[number];
375
418
  */
376
419
  declare const DATA_SOURCE_TYPES: readonly ["glob", "file", "api"];
377
420
  type DataSourceType = (typeof DATA_SOURCE_TYPES)[number];
421
+ /**
422
+ * Reference to imported components for MDX transformation
423
+ */
424
+ interface ComponentsRef {
425
+ expr: 'import';
426
+ name: string;
427
+ }
378
428
  /**
379
429
  * Data source for build-time content loading
380
430
  */
@@ -384,6 +434,7 @@ interface DataSource {
384
434
  path?: string;
385
435
  url?: string;
386
436
  transform?: DataTransform;
437
+ components?: string | ComponentsRef;
387
438
  }
388
439
  /**
389
440
  * Static paths definition for SSG
@@ -399,6 +450,7 @@ interface RouteDefinition {
399
450
  path: string;
400
451
  title?: Expression;
401
452
  layout?: string;
453
+ layoutParams?: Record<string, Expression>;
402
454
  meta?: Record<string, Expression>;
403
455
  getStaticPaths?: StaticPathsDefinition;
404
456
  }
@@ -419,6 +471,7 @@ interface Program {
419
471
  route?: RouteDefinition;
420
472
  imports?: Record<string, string>;
421
473
  data?: Record<string, DataSource>;
474
+ styles?: Record<string, StylePreset>;
422
475
  lifecycle?: LifecycleHooks;
423
476
  state: Record<string, StateField>;
424
477
  actions: ActionDefinition[];
@@ -433,6 +486,8 @@ type ConstelaAst = Program;
433
486
  interface LayoutProgram {
434
487
  version: '1.0';
435
488
  type: 'layout';
489
+ imports?: Record<string, string>;
490
+ importData?: Record<string, unknown>;
436
491
  state?: Record<string, StateField>;
437
492
  actions?: ActionDefinition[];
438
493
  view: ViewNode;
@@ -521,6 +576,10 @@ declare function isDataExpr(value: unknown): value is DataExpr;
521
576
  * Checks if value is a ref expression
522
577
  */
523
578
  declare function isRefExpr(value: unknown): value is RefExpr;
579
+ /**
580
+ * Checks if value is a style expression
581
+ */
582
+ declare function isStyleExpr(value: unknown): value is StyleExpr;
524
583
  /**
525
584
  * Checks if value is a data source
526
585
  */
@@ -669,14 +728,33 @@ declare function isLifecycleHooks(value: unknown): value is LifecycleHooks;
669
728
  * This module defines error types, the ConstelaError class,
670
729
  * and factory functions for creating specific errors.
671
730
  */
672
- type ErrorCode = 'SCHEMA_INVALID' | 'UNDEFINED_STATE' | 'UNDEFINED_ACTION' | 'VAR_UNDEFINED' | 'DUPLICATE_ACTION' | 'UNSUPPORTED_VERSION' | 'COMPONENT_NOT_FOUND' | 'COMPONENT_PROP_MISSING' | 'COMPONENT_CYCLE' | 'COMPONENT_PROP_TYPE' | 'PARAM_UNDEFINED' | 'OPERATION_INVALID_FOR_TYPE' | 'OPERATION_MISSING_FIELD' | 'OPERATION_UNKNOWN' | 'EXPR_INVALID_BASE' | 'EXPR_INVALID_CONDITION' | 'EXPR_COND_ELSE_REQUIRED' | 'UNDEFINED_ROUTE_PARAM' | 'ROUTE_NOT_DEFINED' | 'UNDEFINED_IMPORT' | 'IMPORTS_NOT_DEFINED' | 'LAYOUT_MISSING_SLOT' | 'LAYOUT_NOT_FOUND' | 'INVALID_SLOT_NAME' | 'DUPLICATE_SLOT_NAME' | 'DUPLICATE_DEFAULT_SLOT' | 'SLOT_IN_LOOP' | 'INVALID_DATA_SOURCE' | 'UNDEFINED_DATA_SOURCE' | 'DATA_NOT_DEFINED' | 'UNDEFINED_DATA' | 'UNDEFINED_REF' | 'INVALID_STORAGE_OPERATION' | 'INVALID_STORAGE_TYPE' | 'STORAGE_SET_MISSING_VALUE' | 'INVALID_CLIPBOARD_OPERATION' | 'CLIPBOARD_WRITE_MISSING_VALUE' | 'INVALID_NAVIGATE_TARGET';
731
+ type ErrorCode = 'SCHEMA_INVALID' | 'UNDEFINED_STATE' | 'UNDEFINED_ACTION' | 'VAR_UNDEFINED' | 'DUPLICATE_ACTION' | 'UNSUPPORTED_VERSION' | 'COMPONENT_NOT_FOUND' | 'COMPONENT_PROP_MISSING' | 'COMPONENT_CYCLE' | 'COMPONENT_PROP_TYPE' | 'PARAM_UNDEFINED' | 'OPERATION_INVALID_FOR_TYPE' | 'OPERATION_MISSING_FIELD' | 'OPERATION_UNKNOWN' | 'EXPR_INVALID_BASE' | 'EXPR_INVALID_CONDITION' | 'EXPR_COND_ELSE_REQUIRED' | 'UNDEFINED_ROUTE_PARAM' | 'ROUTE_NOT_DEFINED' | 'UNDEFINED_IMPORT' | 'IMPORTS_NOT_DEFINED' | 'LAYOUT_MISSING_SLOT' | 'LAYOUT_NOT_FOUND' | 'INVALID_SLOT_NAME' | 'DUPLICATE_SLOT_NAME' | 'DUPLICATE_DEFAULT_SLOT' | 'SLOT_IN_LOOP' | 'INVALID_DATA_SOURCE' | 'UNDEFINED_DATA_SOURCE' | 'DATA_NOT_DEFINED' | 'UNDEFINED_DATA' | 'UNDEFINED_REF' | 'INVALID_STORAGE_OPERATION' | 'INVALID_STORAGE_TYPE' | 'STORAGE_SET_MISSING_VALUE' | 'INVALID_CLIPBOARD_OPERATION' | 'CLIPBOARD_WRITE_MISSING_VALUE' | 'INVALID_NAVIGATE_TARGET' | 'UNDEFINED_STYLE' | 'UNDEFINED_VARIANT';
732
+ /**
733
+ * Options for creating enhanced ConstelaError instances
734
+ */
735
+ interface ErrorOptions {
736
+ severity?: 'error' | 'warning' | 'info' | undefined;
737
+ suggestion?: string | undefined;
738
+ expected?: string | undefined;
739
+ actual?: string | undefined;
740
+ context?: {
741
+ availableNames?: string[] | undefined;
742
+ } | undefined;
743
+ }
673
744
  /**
674
745
  * Custom error class for Constela validation errors
675
746
  */
676
747
  declare class ConstelaError extends Error {
677
748
  readonly code: ErrorCode;
678
749
  readonly path: string | undefined;
679
- constructor(code: ErrorCode, message: string, path?: string | undefined);
750
+ readonly severity: 'error' | 'warning' | 'info';
751
+ readonly suggestion: string | undefined;
752
+ readonly expected: string | undefined;
753
+ readonly actual: string | undefined;
754
+ readonly context: {
755
+ availableNames?: string[] | undefined;
756
+ } | undefined;
757
+ constructor(code: ErrorCode, message: string, path?: string | undefined, options?: ErrorOptions);
680
758
  /**
681
759
  * Converts the error to a JSON-serializable object
682
760
  */
@@ -684,6 +762,13 @@ declare class ConstelaError extends Error {
684
762
  code: ErrorCode;
685
763
  message: string;
686
764
  path: string | undefined;
765
+ severity: 'error' | 'warning' | 'info';
766
+ suggestion: string | undefined;
767
+ expected: string | undefined;
768
+ actual: string | undefined;
769
+ context: {
770
+ availableNames?: string[] | undefined;
771
+ } | undefined;
687
772
  };
688
773
  }
689
774
  /**
@@ -697,11 +782,11 @@ declare function createSchemaError(message: string, path?: string): ConstelaErro
697
782
  /**
698
783
  * Creates an undefined state reference error
699
784
  */
700
- declare function createUndefinedStateError(stateName: string, path?: string): ConstelaError;
785
+ declare function createUndefinedStateError(stateName: string, path?: string, options?: ErrorOptions): ConstelaError;
701
786
  /**
702
787
  * Creates an undefined action reference error
703
788
  */
704
- declare function createUndefinedActionError(actionName: string, path?: string): ConstelaError;
789
+ declare function createUndefinedActionError(actionName: string, path?: string, options?: ErrorOptions): ConstelaError;
705
790
  /**
706
791
  * Creates a duplicate action name error
707
792
  */
@@ -717,7 +802,7 @@ declare function createUnsupportedVersionError(version: string): ConstelaError;
717
802
  /**
718
803
  * Creates a component not found error
719
804
  */
720
- declare function createComponentNotFoundError(name: string, path?: string): ConstelaError;
805
+ declare function createComponentNotFoundError(name: string, path?: string, options?: ErrorOptions): ConstelaError;
721
806
  /**
722
807
  * Creates a missing required prop error
723
808
  */
@@ -834,6 +919,28 @@ declare function createClipboardWriteMissingValueError(path?: string): ConstelaE
834
919
  * Creates an invalid navigate target error
835
920
  */
836
921
  declare function createInvalidNavigateTargetError(target: string, path?: string): ConstelaError;
922
+ /**
923
+ * Creates an undefined style reference error
924
+ */
925
+ declare function createUndefinedStyleError(styleName: string, path?: string, options?: ErrorOptions): ConstelaError;
926
+ /**
927
+ * Creates an undefined variant key error
928
+ */
929
+ declare function createUndefinedVariantError(variantKey: string, styleName: string, path?: string, options?: ErrorOptions): ConstelaError;
930
+ /**
931
+ * Finds similar names from a set of candidates using Levenshtein distance
932
+ * and prefix matching.
933
+ *
934
+ * Matching strategies:
935
+ * 1. Levenshtein distance <= maxDistance (default: 2)
936
+ * 2. Target is a prefix of candidate with minimum 3 characters
937
+ *
938
+ * @param target - The target string to find similar names for
939
+ * @param candidates - A set of candidate names to search through
940
+ * @param maxDistance - Maximum Levenshtein distance to consider (default: 2)
941
+ * @returns Array of similar names sorted by distance (closest first)
942
+ */
943
+ declare function findSimilarNames(target: string, candidates: Set<string>, maxDistance?: number): string[];
837
944
 
838
945
  /**
839
946
  * AST Validator for Constela
@@ -893,6 +1000,12 @@ declare const astSchema: {
893
1000
  readonly view: {
894
1001
  readonly $ref: "#/$defs/ViewNode";
895
1002
  };
1003
+ readonly styles: {
1004
+ readonly type: "object";
1005
+ readonly additionalProperties: {
1006
+ readonly $ref: "#/$defs/StylePreset";
1007
+ };
1008
+ };
896
1009
  readonly components: {
897
1010
  readonly type: "object";
898
1011
  readonly additionalProperties: {
@@ -918,6 +1031,10 @@ declare const astSchema: {
918
1031
  readonly $ref: "#/$defs/CondExpr";
919
1032
  }, {
920
1033
  readonly $ref: "#/$defs/GetExpr";
1034
+ }, {
1035
+ readonly $ref: "#/$defs/IndexExpr";
1036
+ }, {
1037
+ readonly $ref: "#/$defs/StyleExpr";
921
1038
  }];
922
1039
  };
923
1040
  readonly LitExpr: {
@@ -956,6 +1073,9 @@ declare const astSchema: {
956
1073
  readonly name: {
957
1074
  readonly type: "string";
958
1075
  };
1076
+ readonly path: {
1077
+ readonly type: "string";
1078
+ };
959
1079
  };
960
1080
  };
961
1081
  readonly VarExpr: {
@@ -1064,6 +1184,86 @@ declare const astSchema: {
1064
1184
  };
1065
1185
  };
1066
1186
  };
1187
+ readonly IndexExpr: {
1188
+ readonly type: "object";
1189
+ readonly required: readonly ["expr", "base", "key"];
1190
+ readonly additionalProperties: false;
1191
+ readonly properties: {
1192
+ readonly expr: {
1193
+ readonly type: "string";
1194
+ readonly const: "index";
1195
+ };
1196
+ readonly base: {
1197
+ readonly $ref: "#/$defs/Expression";
1198
+ };
1199
+ readonly key: {
1200
+ readonly $ref: "#/$defs/Expression";
1201
+ };
1202
+ };
1203
+ };
1204
+ readonly StyleExpr: {
1205
+ readonly type: "object";
1206
+ readonly required: readonly ["expr", "name"];
1207
+ readonly additionalProperties: false;
1208
+ readonly properties: {
1209
+ readonly expr: {
1210
+ readonly type: "string";
1211
+ readonly const: "style";
1212
+ };
1213
+ readonly name: {
1214
+ readonly type: "string";
1215
+ };
1216
+ readonly variants: {
1217
+ readonly type: "object";
1218
+ readonly additionalProperties: {
1219
+ readonly $ref: "#/$defs/Expression";
1220
+ };
1221
+ };
1222
+ };
1223
+ };
1224
+ readonly StylePreset: {
1225
+ readonly type: "object";
1226
+ readonly required: readonly ["base"];
1227
+ readonly additionalProperties: false;
1228
+ readonly properties: {
1229
+ readonly base: {
1230
+ readonly type: "string";
1231
+ };
1232
+ readonly variants: {
1233
+ readonly type: "object";
1234
+ readonly additionalProperties: {
1235
+ readonly type: "object";
1236
+ readonly additionalProperties: {
1237
+ readonly type: "string";
1238
+ };
1239
+ };
1240
+ };
1241
+ readonly defaultVariants: {
1242
+ readonly type: "object";
1243
+ readonly additionalProperties: {
1244
+ readonly type: "string";
1245
+ };
1246
+ };
1247
+ readonly compoundVariants: {
1248
+ readonly type: "array";
1249
+ readonly items: {
1250
+ readonly $ref: "#/$defs/CompoundVariant";
1251
+ };
1252
+ };
1253
+ };
1254
+ };
1255
+ readonly CompoundVariant: {
1256
+ readonly type: "object";
1257
+ readonly required: readonly ["class"];
1258
+ readonly additionalProperties: {
1259
+ readonly type: "string";
1260
+ };
1261
+ readonly properties: {
1262
+ readonly class: {
1263
+ readonly type: "string";
1264
+ };
1265
+ };
1266
+ };
1067
1267
  readonly StateField: {
1068
1268
  readonly oneOf: readonly [{
1069
1269
  readonly $ref: "#/$defs/NumberField";
@@ -1478,4 +1678,4 @@ declare const astSchema: {
1478
1678
  };
1479
1679
  };
1480
1680
 
1481
- export { type ActionDefinition, type ActionStep, BINARY_OPERATORS, type BinExpr, type BinaryOperator, type BooleanField, CLIPBOARD_OPERATIONS, type CallStep, type ClipboardOperation, type ClipboardStep, type CodeNode, type ComponentDef, type ComponentNode, type CondExpr, type ConstelaAst, ConstelaError, type ConstelaProgram, DATA_SOURCE_TYPES, DATA_TRANSFORMS, type DataExpr, type DataSource, type DataSourceType, type DataTransform, type DisposeStep, type EachNode, type ElementNode, type ErrorCode, type EventHandler, type Expression, type FetchStep, type GetExpr, HTTP_METHODS, type HttpMethod, type IfNode, type ImportExpr, type ImportStep, type LayoutProgram, type LifecycleHooks, type ListField, type LitExpr, type MarkdownNode, NAVIGATE_TARGETS, type NavigateStep, type NavigateTarget, type NotExpr, type NumberField, type ObjectField, PARAM_TYPES, type ParamDef, type ParamExpr, type ParamType, type Program, type RefExpr, type RouteDefinition, type RouteExpr, STORAGE_OPERATIONS, STORAGE_TYPES, type SetStep, type SlotNode, type StateExpr, type StateField, type StaticPathsDefinition, type StorageOperation, type StorageStep, type StorageType, type StringField, type SubscribeStep, type TextNode, UPDATE_OPERATIONS, type UpdateOperation, type UpdateStep, type ValidationFailure, type ValidationResult, type ValidationSuccess, type VarExpr, type ViewNode, astSchema, createClipboardWriteMissingValueError, createComponentCycleError, createComponentNotFoundError, createComponentPropMissingError, createComponentPropTypeError, createCondElseRequiredError, createDataNotDefinedError, createDuplicateActionError, createDuplicateDefaultSlotError, createDuplicateSlotNameError, createImportsNotDefinedError, createInvalidClipboardOperationError, createInvalidDataSourceError, createInvalidNavigateTargetError, createInvalidSlotNameError, createInvalidStorageOperationError, createInvalidStorageTypeError, createLayoutMissingSlotError, createLayoutNotFoundError, createOperationInvalidForTypeError, createOperationMissingFieldError, createOperationUnknownError, createRouteNotDefinedError, createSchemaError, createSlotInLoopError, createStorageSetMissingValueError, createUndefinedActionError, createUndefinedDataError, createUndefinedDataSourceError, createUndefinedImportError, createUndefinedParamError, createUndefinedRefError, createUndefinedRouteParamError, createUndefinedStateError, createUndefinedVarError, createUnsupportedVersionError, isActionStep, isBinExpr, isBooleanField, isCallStep, isClipboardStep, isCodeNode, isComponentNode, isCondExpr, isConstelaError, isDataExpr, isDataSource, isDisposeStep, isEachNode, isElementNode, isEventHandler, isExpression, isFetchStep, isGetExpr, isIfNode, isImportExpr, isImportStep, isLayoutProgram, isLifecycleHooks, isListField, isLitExpr, isMarkdownNode, isNamedSlotNode, isNavigateStep, isNotExpr, isNumberField, isObjectField, isParamExpr, isRefExpr, isRouteDefinition, isRouteExpr, isSetStep, isSlotNode, isStateExpr, isStateField, isStaticPathsDefinition, isStorageStep, isStringField, isSubscribeStep, isTextNode, isUpdateStep, isVarExpr, isViewNode, validateAst };
1681
+ export { type ActionDefinition, type ActionStep, BINARY_OPERATORS, type BinExpr, type BinaryOperator, type BooleanField, CLIPBOARD_OPERATIONS, type CallStep, type ClipboardOperation, type ClipboardStep, type CodeNode, type ComponentDef, type ComponentNode, type ComponentsRef, type CompoundVariant, type CondExpr, type ConstelaAst, ConstelaError, type ConstelaProgram, DATA_SOURCE_TYPES, DATA_TRANSFORMS, type DataExpr, type DataSource, type DataSourceType, type DataTransform, type DisposeStep, type DomStep, type EachNode, type ElementNode, type ErrorCode, type ErrorOptions, type EventHandler, type Expression, type FetchStep, type GetExpr, HTTP_METHODS, type HttpMethod, type IfNode, type ImportExpr, type ImportStep, type LayoutProgram, type LifecycleHooks, type ListField, type LitExpr, type MarkdownNode, NAVIGATE_TARGETS, type NavigateStep, type NavigateTarget, type NotExpr, type NumberField, type ObjectField, PARAM_TYPES, type ParamDef, type ParamExpr, type ParamType, type Program, type RefExpr, type RouteDefinition, type RouteExpr, STORAGE_OPERATIONS, STORAGE_TYPES, type SetStep, type SlotNode, type StateExpr, type StateField, type StaticPathsDefinition, type StorageOperation, type StorageStep, type StorageType, type StringField, type StyleExpr, type StylePreset, type SubscribeStep, type TextNode, UPDATE_OPERATIONS, type UpdateOperation, type UpdateStep, type ValidationFailure, type ValidationResult, type ValidationSuccess, type VarExpr, type ViewNode, astSchema, createClipboardWriteMissingValueError, createComponentCycleError, createComponentNotFoundError, createComponentPropMissingError, createComponentPropTypeError, createCondElseRequiredError, createDataNotDefinedError, createDuplicateActionError, createDuplicateDefaultSlotError, createDuplicateSlotNameError, createImportsNotDefinedError, createInvalidClipboardOperationError, createInvalidDataSourceError, createInvalidNavigateTargetError, createInvalidSlotNameError, createInvalidStorageOperationError, createInvalidStorageTypeError, createLayoutMissingSlotError, createLayoutNotFoundError, createOperationInvalidForTypeError, createOperationMissingFieldError, createOperationUnknownError, createRouteNotDefinedError, createSchemaError, createSlotInLoopError, createStorageSetMissingValueError, createUndefinedActionError, createUndefinedDataError, createUndefinedDataSourceError, createUndefinedImportError, createUndefinedParamError, createUndefinedRefError, createUndefinedRouteParamError, createUndefinedStateError, createUndefinedStyleError, createUndefinedVarError, createUndefinedVariantError, createUnsupportedVersionError, findSimilarNames, isActionStep, isBinExpr, isBooleanField, isCallStep, isClipboardStep, isCodeNode, isComponentNode, isCondExpr, isConstelaError, isDataExpr, isDataSource, isDisposeStep, isEachNode, isElementNode, isEventHandler, isExpression, isFetchStep, isGetExpr, isIfNode, isImportExpr, isImportStep, isLayoutProgram, isLifecycleHooks, isListField, isLitExpr, isMarkdownNode, isNamedSlotNode, isNavigateStep, isNotExpr, isNumberField, isObjectField, isParamExpr, isRefExpr, isRouteDefinition, isRouteExpr, isSetStep, isSlotNode, isStateExpr, isStateField, isStaticPathsDefinition, isStorageStep, isStringField, isStyleExpr, isSubscribeStep, isTextNode, isUpdateStep, isVarExpr, isViewNode, validateAst };
package/dist/index.js CHANGED
@@ -123,6 +123,22 @@ function isRefExpr(value) {
123
123
  if (value["expr"] !== "ref") return false;
124
124
  return typeof value["name"] === "string";
125
125
  }
126
+ function isIndexExpr(value) {
127
+ if (!isObject(value)) return false;
128
+ if (value["expr"] !== "index") return false;
129
+ if (!("base" in value)) return false;
130
+ if (!("key" in value)) return false;
131
+ return true;
132
+ }
133
+ function isStyleExpr(value) {
134
+ if (!isObject(value)) return false;
135
+ if (value["expr"] !== "style") return false;
136
+ if (typeof value["name"] !== "string") return false;
137
+ if ("variants" in value && value["variants"] !== void 0) {
138
+ if (!isObject(value["variants"])) return false;
139
+ }
140
+ return true;
141
+ }
126
142
  function isDataSource(value) {
127
143
  if (!isObject(value)) return false;
128
144
  const type = value["type"];
@@ -159,7 +175,7 @@ function isRouteDefinition(value) {
159
175
  return true;
160
176
  }
161
177
  function isExpression(value) {
162
- return isLitExpr(value) || isStateExpr(value) || isVarExpr(value) || isBinExpr(value) || isNotExpr(value) || isParamExpr(value) || isCondExpr(value) || isGetExpr(value) || isRouteExpr(value) || isImportExpr(value) || isDataExpr(value) || isRefExpr(value);
178
+ return isLitExpr(value) || isStateExpr(value) || isVarExpr(value) || isBinExpr(value) || isNotExpr(value) || isParamExpr(value) || isCondExpr(value) || isGetExpr(value) || isRouteExpr(value) || isImportExpr(value) || isDataExpr(value) || isRefExpr(value) || isIndexExpr(value) || isStyleExpr(value);
163
179
  }
164
180
  function isElementNode(value) {
165
181
  if (!isObject(value)) return false;
@@ -375,11 +391,21 @@ function isLifecycleHooks(value) {
375
391
  var ConstelaError = class _ConstelaError extends Error {
376
392
  code;
377
393
  path;
378
- constructor(code, message, path) {
394
+ severity;
395
+ suggestion;
396
+ expected;
397
+ actual;
398
+ context;
399
+ constructor(code, message, path, options) {
379
400
  super(message);
380
401
  this.name = "ConstelaError";
381
402
  this.code = code;
382
403
  this.path = path;
404
+ this.severity = options?.severity ?? "error";
405
+ this.suggestion = options?.suggestion;
406
+ this.expected = options?.expected;
407
+ this.actual = options?.actual;
408
+ this.context = options?.context;
383
409
  Object.setPrototypeOf(this, _ConstelaError.prototype);
384
410
  }
385
411
  /**
@@ -389,7 +415,12 @@ var ConstelaError = class _ConstelaError extends Error {
389
415
  return {
390
416
  code: this.code,
391
417
  message: this.message,
392
- path: this.path
418
+ path: this.path,
419
+ severity: this.severity,
420
+ suggestion: this.suggestion,
421
+ expected: this.expected,
422
+ actual: this.actual,
423
+ context: this.context
393
424
  };
394
425
  }
395
426
  };
@@ -399,18 +430,20 @@ function isConstelaError(value) {
399
430
  function createSchemaError(message, path) {
400
431
  return new ConstelaError("SCHEMA_INVALID", message, path);
401
432
  }
402
- function createUndefinedStateError(stateName, path) {
433
+ function createUndefinedStateError(stateName, path, options) {
403
434
  return new ConstelaError(
404
435
  "UNDEFINED_STATE",
405
436
  `Undefined state reference: '${stateName}' is not defined in state`,
406
- path
437
+ path,
438
+ options
407
439
  );
408
440
  }
409
- function createUndefinedActionError(actionName, path) {
441
+ function createUndefinedActionError(actionName, path, options) {
410
442
  return new ConstelaError(
411
443
  "UNDEFINED_ACTION",
412
444
  `Undefined action reference: '${actionName}' is not defined in actions`,
413
- path
445
+ path,
446
+ options
414
447
  );
415
448
  }
416
449
  function createDuplicateActionError(actionName, path) {
@@ -434,11 +467,12 @@ function createUnsupportedVersionError(version) {
434
467
  "/version"
435
468
  );
436
469
  }
437
- function createComponentNotFoundError(name, path) {
470
+ function createComponentNotFoundError(name, path, options) {
438
471
  return new ConstelaError(
439
472
  "COMPONENT_NOT_FOUND",
440
473
  `Component '${name}' is not defined in components`,
441
- path
474
+ path,
475
+ options
442
476
  );
443
477
  }
444
478
  function createComponentPropMissingError(componentName, propName, path) {
@@ -644,13 +678,68 @@ function createInvalidNavigateTargetError(target, path) {
644
678
  path
645
679
  );
646
680
  }
681
+ function createUndefinedStyleError(styleName, path, options) {
682
+ return new ConstelaError(
683
+ "UNDEFINED_STYLE",
684
+ `Undefined style reference: '${styleName}' is not defined in styles`,
685
+ path,
686
+ options
687
+ );
688
+ }
689
+ function createUndefinedVariantError(variantKey, styleName, path, options) {
690
+ return new ConstelaError(
691
+ "UNDEFINED_VARIANT",
692
+ `Undefined variant key: '${variantKey}' is not defined in style '${styleName}'`,
693
+ path,
694
+ options
695
+ );
696
+ }
697
+ function levenshteinDistance(a, b) {
698
+ const aLen = a.length;
699
+ const bLen = b.length;
700
+ let prev = Array.from({ length: bLen + 1 }, (_, j) => j);
701
+ let curr = new Array(bLen + 1).fill(0);
702
+ for (let i = 1; i <= aLen; i++) {
703
+ curr[0] = i;
704
+ for (let j = 1; j <= bLen; j++) {
705
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
706
+ const deletion = (prev[j] ?? 0) + 1;
707
+ const insertion = (curr[j - 1] ?? 0) + 1;
708
+ const substitution = (prev[j - 1] ?? 0) + cost;
709
+ curr[j] = Math.min(deletion, insertion, substitution);
710
+ }
711
+ [prev, curr] = [curr, prev];
712
+ }
713
+ return prev[bLen] ?? 0;
714
+ }
715
+ function findSimilarNames(target, candidates, maxDistance = 2) {
716
+ const results = [];
717
+ for (const candidate of candidates) {
718
+ const distance = levenshteinDistance(target, candidate);
719
+ if (distance <= maxDistance) {
720
+ results.push({ name: candidate, distance });
721
+ continue;
722
+ }
723
+ if (target.length >= 3 && candidate.toLowerCase().startsWith(target.toLowerCase())) {
724
+ const prefixDistance = candidate.length - target.length;
725
+ results.push({ name: candidate, distance: prefixDistance });
726
+ }
727
+ }
728
+ results.sort((a, b) => {
729
+ if (a.distance !== b.distance) {
730
+ return a.distance - b.distance;
731
+ }
732
+ return a.name.localeCompare(b.name);
733
+ });
734
+ return results.map((r) => r.name);
735
+ }
647
736
 
648
737
  // src/schema/validator.ts
649
738
  function isObject2(value) {
650
739
  return typeof value === "object" && value !== null && !Array.isArray(value);
651
740
  }
652
741
  var VALID_VIEW_KINDS = ["element", "text", "if", "each", "component", "slot", "markdown", "code"];
653
- var VALID_EXPR_TYPES = ["lit", "state", "var", "bin", "not", "param", "cond", "get"];
742
+ var VALID_EXPR_TYPES = ["lit", "state", "var", "bin", "not", "param", "cond", "get", "style"];
654
743
  var VALID_PARAM_TYPES = ["string", "number", "boolean", "json"];
655
744
  var VALID_ACTION_TYPES = ["set", "update", "fetch"];
656
745
  var VALID_STATE_TYPES = ["number", "string", "list", "boolean", "object"];
@@ -777,7 +866,7 @@ function validateExpression(expr, path) {
777
866
  return { path: path + "/expr", message: "expr is required" };
778
867
  }
779
868
  if (!VALID_EXPR_TYPES.includes(exprType)) {
780
- return { path: path + "/expr", message: "must be one of: lit, state, var, bin, not, param, cond, get" };
869
+ return { path: path + "/expr", message: "must be one of: lit, state, var, bin, not, param, cond, get, style" };
781
870
  }
782
871
  switch (exprType) {
783
872
  case "lit":
@@ -851,6 +940,20 @@ function validateExpression(expr, path) {
851
940
  return { path: path + "/path", message: "path is required" };
852
941
  }
853
942
  return validateExpression(expr["base"], path + "/base");
943
+ case "style":
944
+ if (typeof expr["name"] !== "string") {
945
+ return { path: path + "/name", message: "name is required" };
946
+ }
947
+ if ("variants" in expr && expr["variants"] !== void 0) {
948
+ if (!isObject2(expr["variants"])) {
949
+ return { path: path + "/variants", message: "variants must be an object" };
950
+ }
951
+ for (const [variantKey, variantValue] of Object.entries(expr["variants"])) {
952
+ const error = validateExpression(variantValue, path + "/variants/" + variantKey);
953
+ if (error) return error;
954
+ }
955
+ }
956
+ break;
854
957
  }
855
958
  return null;
856
959
  }
@@ -985,6 +1088,61 @@ function validateComponentDef(def, path) {
985
1088
  if (viewError) return viewError;
986
1089
  return null;
987
1090
  }
1091
+ function validateStylePreset(preset, path) {
1092
+ if (!isObject2(preset)) {
1093
+ return { path, message: "must be an object" };
1094
+ }
1095
+ if (!("base" in preset)) {
1096
+ return { path: path + "/base", message: "base is required" };
1097
+ }
1098
+ if (typeof preset["base"] !== "string") {
1099
+ return { path: path + "/base", message: "base must be a string" };
1100
+ }
1101
+ if ("variants" in preset && preset["variants"] !== void 0) {
1102
+ if (!isObject2(preset["variants"])) {
1103
+ return { path: path + "/variants", message: "variants must be an object" };
1104
+ }
1105
+ for (const [variantKey, variantOptions] of Object.entries(preset["variants"])) {
1106
+ if (!isObject2(variantOptions)) {
1107
+ return { path: path + "/variants/" + variantKey, message: "variant options must be an object" };
1108
+ }
1109
+ for (const [optionKey, optionValue] of Object.entries(variantOptions)) {
1110
+ if (typeof optionValue !== "string") {
1111
+ return { path: path + "/variants/" + variantKey + "/" + optionKey, message: "variant value must be a string" };
1112
+ }
1113
+ }
1114
+ }
1115
+ }
1116
+ if ("defaultVariants" in preset && preset["defaultVariants"] !== void 0) {
1117
+ if (!isObject2(preset["defaultVariants"])) {
1118
+ return { path: path + "/defaultVariants", message: "defaultVariants must be an object" };
1119
+ }
1120
+ const variantKeys = isObject2(preset["variants"]) ? Object.keys(preset["variants"]) : [];
1121
+ for (const [key, value] of Object.entries(preset["defaultVariants"])) {
1122
+ if (typeof value !== "string") {
1123
+ return { path: path + "/defaultVariants/" + key, message: "default variant value must be a string" };
1124
+ }
1125
+ if (!variantKeys.includes(key)) {
1126
+ return { path: path + "/defaultVariants/" + key, message: `'${key}' is not a defined variant` };
1127
+ }
1128
+ }
1129
+ }
1130
+ if ("compoundVariants" in preset && preset["compoundVariants"] !== void 0) {
1131
+ if (!Array.isArray(preset["compoundVariants"])) {
1132
+ return { path: path + "/compoundVariants", message: "compoundVariants must be an array" };
1133
+ }
1134
+ for (let i = 0; i < preset["compoundVariants"].length; i++) {
1135
+ const compound = preset["compoundVariants"][i];
1136
+ if (!isObject2(compound)) {
1137
+ return { path: path + "/compoundVariants/" + i, message: "compound variant must be an object" };
1138
+ }
1139
+ if (typeof compound["class"] !== "string") {
1140
+ return { path: path + "/compoundVariants/" + i + "/class", message: "class is required" };
1141
+ }
1142
+ }
1143
+ }
1144
+ return null;
1145
+ }
988
1146
  function customValidateAst(input) {
989
1147
  if (isObject2(input["state"])) {
990
1148
  for (const [name, field] of Object.entries(input["state"])) {
@@ -992,6 +1150,12 @@ function customValidateAst(input) {
992
1150
  if (error) return error;
993
1151
  }
994
1152
  }
1153
+ if ("styles" in input && isObject2(input["styles"])) {
1154
+ for (const [name, preset] of Object.entries(input["styles"])) {
1155
+ const error = validateStylePreset(preset, "/styles/" + name);
1156
+ if (error) return error;
1157
+ }
1158
+ }
995
1159
  if (Array.isArray(input["actions"])) {
996
1160
  for (let i = 0; i < input["actions"].length; i++) {
997
1161
  const action = input["actions"][i];
@@ -1015,6 +1179,15 @@ function customValidateAst(input) {
1015
1179
  }
1016
1180
  return null;
1017
1181
  }
1182
+ function createErrorOptionsWithSuggestion(name, availableNames) {
1183
+ const availableNamesArray = Array.from(availableNames);
1184
+ const similarNames = findSimilarNames(name, availableNames);
1185
+ const suggestion = similarNames.length > 0 ? `Did you mean '${similarNames[0]}'?` : void 0;
1186
+ return {
1187
+ suggestion,
1188
+ context: { availableNames: availableNamesArray }
1189
+ };
1190
+ }
1018
1191
  function collectSemanticContext(ast) {
1019
1192
  const stateNames = /* @__PURE__ */ new Set();
1020
1193
  const actionNames = /* @__PURE__ */ new Set();
@@ -1036,7 +1209,8 @@ function validateStateReferences(node, path, stateNames) {
1036
1209
  if (!isObject2(node)) return null;
1037
1210
  if (node["expr"] === "state" && typeof node["name"] === "string") {
1038
1211
  if (!stateNames.has(node["name"])) {
1039
- return createUndefinedStateError(node["name"], path + "/" + node["name"]);
1212
+ const errorOptions = createErrorOptionsWithSuggestion(node["name"], stateNames);
1213
+ return createUndefinedStateError(node["name"], path + "/" + node["name"], errorOptions);
1040
1214
  }
1041
1215
  }
1042
1216
  for (const [key, value] of Object.entries(node)) {
@@ -1062,7 +1236,8 @@ function validateActionTargets(ast, stateNames) {
1062
1236
  if (!isObject2(step)) continue;
1063
1237
  if ((step["do"] === "set" || step["do"] === "update") && typeof step["target"] === "string") {
1064
1238
  if (!stateNames.has(step["target"])) {
1065
- return createUndefinedStateError(step["target"], "/actions/" + i + "/steps/" + j + "/target");
1239
+ const errorOptions = createErrorOptionsWithSuggestion(step["target"], stateNames);
1240
+ return createUndefinedStateError(step["target"], "/actions/" + i + "/steps/" + j + "/target", errorOptions);
1066
1241
  }
1067
1242
  }
1068
1243
  }
@@ -1076,7 +1251,8 @@ function validateActionReferences(node, path, actionNames) {
1076
1251
  if (isObject2(propValue) && "event" in propValue && "action" in propValue) {
1077
1252
  const actionName = propValue["action"];
1078
1253
  if (typeof actionName === "string" && !actionNames.has(actionName)) {
1079
- return createUndefinedActionError(actionName, path + "/props/" + propName);
1254
+ const errorOptions = createErrorOptionsWithSuggestion(actionName, actionNames);
1255
+ return createUndefinedActionError(actionName, path + "/props/" + propName, errorOptions);
1080
1256
  }
1081
1257
  }
1082
1258
  }
@@ -1223,6 +1399,10 @@ var astSchema = {
1223
1399
  view: {
1224
1400
  $ref: "#/$defs/ViewNode"
1225
1401
  },
1402
+ styles: {
1403
+ type: "object",
1404
+ additionalProperties: { $ref: "#/$defs/StylePreset" }
1405
+ },
1226
1406
  components: {
1227
1407
  type: "object",
1228
1408
  additionalProperties: { $ref: "#/$defs/ComponentDef" }
@@ -1239,7 +1419,9 @@ var astSchema = {
1239
1419
  { $ref: "#/$defs/NotExpr" },
1240
1420
  { $ref: "#/$defs/ParamExpr" },
1241
1421
  { $ref: "#/$defs/CondExpr" },
1242
- { $ref: "#/$defs/GetExpr" }
1422
+ { $ref: "#/$defs/GetExpr" },
1423
+ { $ref: "#/$defs/IndexExpr" },
1424
+ { $ref: "#/$defs/StyleExpr" }
1243
1425
  ]
1244
1426
  },
1245
1427
  LitExpr: {
@@ -1265,7 +1447,8 @@ var astSchema = {
1265
1447
  additionalProperties: false,
1266
1448
  properties: {
1267
1449
  expr: { type: "string", const: "state" },
1268
- name: { type: "string" }
1450
+ name: { type: "string" },
1451
+ path: { type: "string" }
1269
1452
  }
1270
1453
  },
1271
1454
  VarExpr: {
@@ -1332,6 +1515,61 @@ var astSchema = {
1332
1515
  path: { type: "string" }
1333
1516
  }
1334
1517
  },
1518
+ IndexExpr: {
1519
+ type: "object",
1520
+ required: ["expr", "base", "key"],
1521
+ additionalProperties: false,
1522
+ properties: {
1523
+ expr: { type: "string", const: "index" },
1524
+ base: { $ref: "#/$defs/Expression" },
1525
+ key: { $ref: "#/$defs/Expression" }
1526
+ }
1527
+ },
1528
+ StyleExpr: {
1529
+ type: "object",
1530
+ required: ["expr", "name"],
1531
+ additionalProperties: false,
1532
+ properties: {
1533
+ expr: { type: "string", const: "style" },
1534
+ name: { type: "string" },
1535
+ variants: {
1536
+ type: "object",
1537
+ additionalProperties: { $ref: "#/$defs/Expression" }
1538
+ }
1539
+ }
1540
+ },
1541
+ // ==================== Style Presets ====================
1542
+ StylePreset: {
1543
+ type: "object",
1544
+ required: ["base"],
1545
+ additionalProperties: false,
1546
+ properties: {
1547
+ base: { type: "string" },
1548
+ variants: {
1549
+ type: "object",
1550
+ additionalProperties: {
1551
+ type: "object",
1552
+ additionalProperties: { type: "string" }
1553
+ }
1554
+ },
1555
+ defaultVariants: {
1556
+ type: "object",
1557
+ additionalProperties: { type: "string" }
1558
+ },
1559
+ compoundVariants: {
1560
+ type: "array",
1561
+ items: { $ref: "#/$defs/CompoundVariant" }
1562
+ }
1563
+ }
1564
+ },
1565
+ CompoundVariant: {
1566
+ type: "object",
1567
+ required: ["class"],
1568
+ additionalProperties: { type: "string" },
1569
+ properties: {
1570
+ class: { type: "string" }
1571
+ }
1572
+ },
1335
1573
  // ==================== State Fields ====================
1336
1574
  StateField: {
1337
1575
  oneOf: [
@@ -1654,8 +1892,11 @@ export {
1654
1892
  createUndefinedRefError,
1655
1893
  createUndefinedRouteParamError,
1656
1894
  createUndefinedStateError,
1895
+ createUndefinedStyleError,
1657
1896
  createUndefinedVarError,
1897
+ createUndefinedVariantError,
1658
1898
  createUnsupportedVersionError,
1899
+ findSimilarNames,
1659
1900
  isActionStep,
1660
1901
  isBinExpr,
1661
1902
  isBooleanField,
@@ -1698,6 +1939,7 @@ export {
1698
1939
  isStaticPathsDefinition,
1699
1940
  isStorageStep,
1700
1941
  isStringField,
1942
+ isStyleExpr,
1701
1943
  isSubscribeStep,
1702
1944
  isTextNode,
1703
1945
  isUpdateStep,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/core",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Core types, schema, and validator for Constela UI framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",