@constela/core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,302 @@
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
+ 13 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
+ | `style` | `{ "expr": "style", "name": "button", "variants": {...} }` | Style reference |
62
+
63
+ **Binary Operators:** `+`, `-`, `*`, `/`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&&`, `||`
64
+
65
+ ## View Node Types
66
+
67
+ 8 node types for building UI:
68
+
69
+ ```json
70
+ // Element node
71
+ { "kind": "element", "tag": "div", "props": { ... }, "children": [ ... ] }
72
+
73
+ // Text node
74
+ { "kind": "text", "value": { "expr": "state", "name": "count" } }
75
+
76
+ // Conditional node
77
+ { "kind": "if", "condition": { ... }, "then": { ... }, "else": { ... } }
78
+
79
+ // Loop node
80
+ { "kind": "each", "items": { "expr": "state", "name": "todos" }, "as": "item", "body": { ... } }
81
+
82
+ // Component node
83
+ { "kind": "component", "name": "Button", "props": { "label": { ... } } }
84
+
85
+ // Slot node (for layouts)
86
+ { "kind": "slot" }
87
+ { "kind": "slot", "name": "sidebar" }
88
+
89
+ // Markdown node
90
+ { "kind": "markdown", "content": { "expr": "state", "name": "content" } }
91
+
92
+ // Code block node
93
+ { "kind": "code", "code": { ... }, "language": { ... } }
94
+ ```
95
+
96
+ ## Action Step Types
97
+
98
+ 11 step types for declarative actions:
99
+
100
+ ```json
101
+ // Set state value
102
+ { "do": "set", "target": "query", "value": { "expr": "lit", "value": "" } }
103
+
104
+ // Update with operation
105
+ { "do": "update", "target": "count", "operation": "increment" }
106
+ { "do": "update", "target": "todos", "operation": "push", "value": { ... } }
107
+
108
+ // HTTP request
109
+ { "do": "fetch", "url": { ... }, "method": "GET", "onSuccess": [ ... ], "onError": [ ... ] }
110
+
111
+ // Storage operation
112
+ { "do": "storage", "operation": "get", "key": { ... }, "storage": "local" }
113
+
114
+ // Clipboard operation
115
+ { "do": "clipboard", "operation": "write", "value": { ... } }
116
+
117
+ // Navigation
118
+ { "do": "navigate", "url": { ... } }
119
+
120
+ // Dynamic import
121
+ { "do": "import", "module": "chart.js", "result": "Chart" }
122
+
123
+ // External function call
124
+ { "do": "call", "ref": "Chart", "method": "create", "args": [ ... ] }
125
+
126
+ // Event subscription
127
+ { "do": "subscribe", "ref": "eventSource", "event": "message", "action": "handleMessage" }
128
+
129
+ // Resource disposal
130
+ { "do": "dispose", "ref": "chartInstance" }
131
+
132
+ // DOM manipulation
133
+ { "do": "dom", "operation": "addClass", "ref": "myElement", "value": { ... } }
134
+ ```
135
+
136
+ **Update Operations:**
137
+
138
+ | Operation | State Type | Description |
139
+ |-----------|------------|-------------|
140
+ | `increment` | number | Add to number |
141
+ | `decrement` | number | Subtract from number |
142
+ | `push` | list | Add item to end |
143
+ | `pop` | list | Remove last item |
144
+ | `remove` | list | Remove by value/index |
145
+ | `toggle` | boolean | Flip boolean |
146
+ | `merge` | object | Shallow merge |
147
+ | `replaceAt` | list | Replace at index |
148
+ | `insertAt` | list | Insert at index |
149
+ | `splice` | list | Delete/insert items |
150
+
151
+ ## Lifecycle Hooks
152
+
153
+ ```json
154
+ {
155
+ "lifecycle": {
156
+ "onMount": "loadData",
157
+ "onUnmount": "cleanup",
158
+ "onRouteEnter": "fetchData",
159
+ "onRouteLeave": "saveState"
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## Style System
165
+
166
+ Define reusable style presets with variants (similar to CVA/Tailwind Variants):
167
+
168
+ ```json
169
+ {
170
+ "styles": {
171
+ "button": {
172
+ "base": "px-4 py-2 rounded font-medium",
173
+ "variants": {
174
+ "variant": {
175
+ "primary": "bg-blue-500 text-white",
176
+ "secondary": "bg-gray-200 text-gray-800"
177
+ },
178
+ "size": {
179
+ "sm": "text-sm",
180
+ "md": "text-base",
181
+ "lg": "text-lg"
182
+ }
183
+ },
184
+ "defaultVariants": {
185
+ "variant": "primary",
186
+ "size": "md"
187
+ }
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ Use styles with `StyleExpr`:
194
+
195
+ ```json
196
+ {
197
+ "kind": "element",
198
+ "tag": "button",
199
+ "props": {
200
+ "className": {
201
+ "expr": "style",
202
+ "name": "button",
203
+ "variants": {
204
+ "variant": { "expr": "lit", "value": "primary" },
205
+ "size": { "expr": "state", "name": "buttonSize" }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ ```
211
+
212
+ ## Error Codes
213
+
214
+ | Code | Description |
215
+ |------|-------------|
216
+ | `SCHEMA_INVALID` | JSON Schema validation error |
217
+ | `UNSUPPORTED_VERSION` | Unsupported version string |
218
+ | `UNDEFINED_STATE` | Reference to undefined state |
219
+ | `UNDEFINED_ACTION` | Reference to undefined action |
220
+ | `DUPLICATE_ACTION` | Duplicate action name |
221
+ | `VAR_UNDEFINED` | Undefined variable reference |
222
+ | `COMPONENT_NOT_FOUND` | Undefined component |
223
+ | `COMPONENT_PROP_MISSING` | Missing required prop |
224
+ | `COMPONENT_CYCLE` | Circular component reference |
225
+ | `COMPONENT_PROP_TYPE` | Prop type mismatch |
226
+ | `PARAM_UNDEFINED` | Undefined parameter |
227
+ | `OPERATION_INVALID_FOR_TYPE` | Invalid operation for state type |
228
+ | `OPERATION_MISSING_FIELD` | Missing required field for operation |
229
+ | `ROUTE_NOT_DEFINED` | Route not defined |
230
+ | `UNDEFINED_ROUTE_PARAM` | Undefined route parameter |
231
+ | `LAYOUT_MISSING_SLOT` | Layout missing slot node |
232
+ | `LAYOUT_NOT_FOUND` | Referenced layout not found |
233
+ | `INVALID_SLOT_NAME` | Invalid slot name |
234
+ | `DUPLICATE_SLOT_NAME` | Duplicate slot name |
235
+ | `DUPLICATE_DEFAULT_SLOT` | Multiple default slots |
236
+ | `SLOT_IN_LOOP` | Slot inside loop |
237
+ | `UNDEFINED_DATA_SOURCE` | Undefined data source |
238
+ | `UNDEFINED_IMPORT` | Undefined import reference |
239
+ | `UNDEFINED_REF` | Undefined element ref |
240
+ | `INVALID_STORAGE_OPERATION` | Invalid storage operation |
241
+ | `INVALID_CLIPBOARD_OPERATION` | Invalid clipboard operation |
242
+ | `INVALID_NAVIGATE_TARGET` | Invalid navigate target |
243
+ | `UNDEFINED_STYLE` | Reference to undefined style preset |
244
+ | `UNDEFINED_VARIANT` | Reference to undefined style variant |
245
+
246
+ ### Error Suggestions
247
+
248
+ Errors for undefined references include "Did you mean?" suggestions using Levenshtein distance:
249
+
250
+ ```typescript
251
+ import { findSimilarNames } from '@constela/core';
252
+
253
+ const candidates = new Set(['counter', 'items', 'query']);
254
+ const similar = findSimilarNames('count', candidates);
255
+ // Returns: ['counter'] - similar names within distance 2
256
+ ```
257
+
258
+ ## Internal API
259
+
260
+ > For framework developers only.
261
+
262
+ ### validateAst
263
+
264
+ ```typescript
265
+ import { validateAst } from '@constela/core';
266
+
267
+ const result = validateAst(input);
268
+ if (result.ok) {
269
+ console.log('Valid program:', result.program);
270
+ } else {
271
+ console.error('Validation failed:', result.error);
272
+ }
273
+ ```
274
+
275
+ ### Type Guards
276
+
277
+ 47 type guard functions for runtime type checking:
278
+
279
+ ```typescript
280
+ import {
281
+ isLitExpr, isStateExpr, isVarExpr, isBinExpr,
282
+ isElementNode, isTextNode, isIfNode, isEachNode,
283
+ isSetStep, isUpdateStep, isFetchStep,
284
+ isNumberField, isStringField, isListField,
285
+ } from '@constela/core';
286
+ ```
287
+
288
+ ### ConstelaError
289
+
290
+ ```typescript
291
+ import { ConstelaError } from '@constela/core';
292
+
293
+ const error = new ConstelaError(
294
+ 'UNDEFINED_STATE',
295
+ 'State "count" is not defined',
296
+ '/view/children/0/props/onClick'
297
+ );
298
+ ```
299
+
300
+ ## License
301
+
302
+ 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
  */
@@ -191,6 +208,22 @@ interface UpdateStep {
191
208
  index?: Expression;
192
209
  deleteCount?: Expression;
193
210
  }
211
+ /**
212
+ * SetPath step - sets a value at a specific path within a state field
213
+ *
214
+ * This enables fine-grained state updates like `posts[5].liked = true`
215
+ * without re-creating the entire state.
216
+ *
217
+ * @property target - The state field name (e.g., "posts")
218
+ * @property path - The path within the state field (Expression that evaluates to string or array)
219
+ * @property value - The value to set at the path
220
+ */
221
+ interface SetPathStep {
222
+ do: 'setPath';
223
+ target: string;
224
+ path: Expression;
225
+ value: Expression;
226
+ }
194
227
  /**
195
228
  * Fetch step - makes an HTTP request
196
229
  */
@@ -275,7 +308,32 @@ interface DisposeStep {
275
308
  do: 'dispose';
276
309
  target: Expression;
277
310
  }
278
- type ActionStep = SetStep | UpdateStep | FetchStep | StorageStep | ClipboardStep | NavigateStep | ImportStep | CallStep | SubscribeStep | DisposeStep;
311
+ /**
312
+ * DOM step - manipulate DOM elements (add/remove classes, attributes)
313
+ */
314
+ interface DomStep {
315
+ do: 'dom';
316
+ operation: 'addClass' | 'removeClass' | 'toggleClass' | 'setAttribute' | 'removeAttribute';
317
+ selector: Expression;
318
+ value?: Expression;
319
+ attribute?: string;
320
+ }
321
+ /**
322
+ * Send step - sends data through a named WebSocket connection
323
+ */
324
+ interface SendStep {
325
+ do: 'send';
326
+ connection: string;
327
+ data: Expression;
328
+ }
329
+ /**
330
+ * Close step - closes a named WebSocket connection
331
+ */
332
+ interface CloseStep {
333
+ do: 'close';
334
+ connection: string;
335
+ }
336
+ type ActionStep = SetStep | UpdateStep | SetPathStep | FetchStep | StorageStep | ClipboardStep | NavigateStep | ImportStep | CallStep | SubscribeStep | DisposeStep | DomStep | SendStep | CloseStep;
279
337
  /**
280
338
  * Event handler - binds an event to an action
281
339
  */
@@ -365,6 +423,22 @@ interface ComponentDef {
365
423
  params?: Record<string, ParamDef>;
366
424
  view: ViewNode;
367
425
  }
426
+ /**
427
+ * Compound variant - applies additional classes when multiple variants match
428
+ */
429
+ interface CompoundVariant {
430
+ [key: string]: string;
431
+ class: string;
432
+ }
433
+ /**
434
+ * Style preset - defines a reusable style with variants (CVA-like pattern)
435
+ */
436
+ interface StylePreset {
437
+ base: string;
438
+ variants?: Record<string, Record<string, string>>;
439
+ defaultVariants?: Record<string, string>;
440
+ compoundVariants?: CompoundVariant[];
441
+ }
368
442
  /**
369
443
  * Data transform types for build-time content loading
370
444
  */
@@ -407,6 +481,7 @@ interface RouteDefinition {
407
481
  path: string;
408
482
  title?: Expression;
409
483
  layout?: string;
484
+ layoutParams?: Record<string, Expression>;
410
485
  meta?: Record<string, Expression>;
411
486
  getStaticPaths?: StaticPathsDefinition;
412
487
  }
@@ -427,6 +502,7 @@ interface Program {
427
502
  route?: RouteDefinition;
428
503
  imports?: Record<string, string>;
429
504
  data?: Record<string, DataSource>;
505
+ styles?: Record<string, StylePreset>;
430
506
  lifecycle?: LifecycleHooks;
431
507
  state: Record<string, StateField>;
432
508
  actions: ActionDefinition[];
@@ -441,6 +517,8 @@ type ConstelaAst = Program;
441
517
  interface LayoutProgram {
442
518
  version: '1.0';
443
519
  type: 'layout';
520
+ imports?: Record<string, string>;
521
+ importData?: Record<string, unknown>;
444
522
  state?: Record<string, StateField>;
445
523
  actions?: ActionDefinition[];
446
524
  view: ViewNode;
@@ -529,6 +607,10 @@ declare function isDataExpr(value: unknown): value is DataExpr;
529
607
  * Checks if value is a ref expression
530
608
  */
531
609
  declare function isRefExpr(value: unknown): value is RefExpr;
610
+ /**
611
+ * Checks if value is a style expression
612
+ */
613
+ declare function isStyleExpr(value: unknown): value is StyleExpr;
532
614
  /**
533
615
  * Checks if value is a data source
534
616
  */
@@ -677,14 +759,33 @@ declare function isLifecycleHooks(value: unknown): value is LifecycleHooks;
677
759
  * This module defines error types, the ConstelaError class,
678
760
  * and factory functions for creating specific errors.
679
761
  */
680
- 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';
762
+ 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';
763
+ /**
764
+ * Options for creating enhanced ConstelaError instances
765
+ */
766
+ interface ErrorOptions {
767
+ severity?: 'error' | 'warning' | 'info' | undefined;
768
+ suggestion?: string | undefined;
769
+ expected?: string | undefined;
770
+ actual?: string | undefined;
771
+ context?: {
772
+ availableNames?: string[] | undefined;
773
+ } | undefined;
774
+ }
681
775
  /**
682
776
  * Custom error class for Constela validation errors
683
777
  */
684
778
  declare class ConstelaError extends Error {
685
779
  readonly code: ErrorCode;
686
780
  readonly path: string | undefined;
687
- constructor(code: ErrorCode, message: string, path?: string | undefined);
781
+ readonly severity: 'error' | 'warning' | 'info';
782
+ readonly suggestion: string | undefined;
783
+ readonly expected: string | undefined;
784
+ readonly actual: string | undefined;
785
+ readonly context: {
786
+ availableNames?: string[] | undefined;
787
+ } | undefined;
788
+ constructor(code: ErrorCode, message: string, path?: string | undefined, options?: ErrorOptions);
688
789
  /**
689
790
  * Converts the error to a JSON-serializable object
690
791
  */
@@ -692,6 +793,13 @@ declare class ConstelaError extends Error {
692
793
  code: ErrorCode;
693
794
  message: string;
694
795
  path: string | undefined;
796
+ severity: 'error' | 'warning' | 'info';
797
+ suggestion: string | undefined;
798
+ expected: string | undefined;
799
+ actual: string | undefined;
800
+ context: {
801
+ availableNames?: string[] | undefined;
802
+ } | undefined;
695
803
  };
696
804
  }
697
805
  /**
@@ -705,11 +813,11 @@ declare function createSchemaError(message: string, path?: string): ConstelaErro
705
813
  /**
706
814
  * Creates an undefined state reference error
707
815
  */
708
- declare function createUndefinedStateError(stateName: string, path?: string): ConstelaError;
816
+ declare function createUndefinedStateError(stateName: string, path?: string, options?: ErrorOptions): ConstelaError;
709
817
  /**
710
818
  * Creates an undefined action reference error
711
819
  */
712
- declare function createUndefinedActionError(actionName: string, path?: string): ConstelaError;
820
+ declare function createUndefinedActionError(actionName: string, path?: string, options?: ErrorOptions): ConstelaError;
713
821
  /**
714
822
  * Creates a duplicate action name error
715
823
  */
@@ -725,7 +833,7 @@ declare function createUnsupportedVersionError(version: string): ConstelaError;
725
833
  /**
726
834
  * Creates a component not found error
727
835
  */
728
- declare function createComponentNotFoundError(name: string, path?: string): ConstelaError;
836
+ declare function createComponentNotFoundError(name: string, path?: string, options?: ErrorOptions): ConstelaError;
729
837
  /**
730
838
  * Creates a missing required prop error
731
839
  */
@@ -842,6 +950,28 @@ declare function createClipboardWriteMissingValueError(path?: string): ConstelaE
842
950
  * Creates an invalid navigate target error
843
951
  */
844
952
  declare function createInvalidNavigateTargetError(target: string, path?: string): ConstelaError;
953
+ /**
954
+ * Creates an undefined style reference error
955
+ */
956
+ declare function createUndefinedStyleError(styleName: string, path?: string, options?: ErrorOptions): ConstelaError;
957
+ /**
958
+ * Creates an undefined variant key error
959
+ */
960
+ declare function createUndefinedVariantError(variantKey: string, styleName: string, path?: string, options?: ErrorOptions): ConstelaError;
961
+ /**
962
+ * Finds similar names from a set of candidates using Levenshtein distance
963
+ * and prefix matching.
964
+ *
965
+ * Matching strategies:
966
+ * 1. Levenshtein distance <= maxDistance (default: 2)
967
+ * 2. Target is a prefix of candidate with minimum 3 characters
968
+ *
969
+ * @param target - The target string to find similar names for
970
+ * @param candidates - A set of candidate names to search through
971
+ * @param maxDistance - Maximum Levenshtein distance to consider (default: 2)
972
+ * @returns Array of similar names sorted by distance (closest first)
973
+ */
974
+ declare function findSimilarNames(target: string, candidates: Set<string>, maxDistance?: number): string[];
845
975
 
846
976
  /**
847
977
  * AST Validator for Constela
@@ -901,6 +1031,12 @@ declare const astSchema: {
901
1031
  readonly view: {
902
1032
  readonly $ref: "#/$defs/ViewNode";
903
1033
  };
1034
+ readonly styles: {
1035
+ readonly type: "object";
1036
+ readonly additionalProperties: {
1037
+ readonly $ref: "#/$defs/StylePreset";
1038
+ };
1039
+ };
904
1040
  readonly components: {
905
1041
  readonly type: "object";
906
1042
  readonly additionalProperties: {
@@ -926,6 +1062,10 @@ declare const astSchema: {
926
1062
  readonly $ref: "#/$defs/CondExpr";
927
1063
  }, {
928
1064
  readonly $ref: "#/$defs/GetExpr";
1065
+ }, {
1066
+ readonly $ref: "#/$defs/IndexExpr";
1067
+ }, {
1068
+ readonly $ref: "#/$defs/StyleExpr";
929
1069
  }];
930
1070
  };
931
1071
  readonly LitExpr: {
@@ -964,6 +1104,9 @@ declare const astSchema: {
964
1104
  readonly name: {
965
1105
  readonly type: "string";
966
1106
  };
1107
+ readonly path: {
1108
+ readonly type: "string";
1109
+ };
967
1110
  };
968
1111
  };
969
1112
  readonly VarExpr: {
@@ -1072,6 +1215,86 @@ declare const astSchema: {
1072
1215
  };
1073
1216
  };
1074
1217
  };
1218
+ readonly IndexExpr: {
1219
+ readonly type: "object";
1220
+ readonly required: readonly ["expr", "base", "key"];
1221
+ readonly additionalProperties: false;
1222
+ readonly properties: {
1223
+ readonly expr: {
1224
+ readonly type: "string";
1225
+ readonly const: "index";
1226
+ };
1227
+ readonly base: {
1228
+ readonly $ref: "#/$defs/Expression";
1229
+ };
1230
+ readonly key: {
1231
+ readonly $ref: "#/$defs/Expression";
1232
+ };
1233
+ };
1234
+ };
1235
+ readonly StyleExpr: {
1236
+ readonly type: "object";
1237
+ readonly required: readonly ["expr", "name"];
1238
+ readonly additionalProperties: false;
1239
+ readonly properties: {
1240
+ readonly expr: {
1241
+ readonly type: "string";
1242
+ readonly const: "style";
1243
+ };
1244
+ readonly name: {
1245
+ readonly type: "string";
1246
+ };
1247
+ readonly variants: {
1248
+ readonly type: "object";
1249
+ readonly additionalProperties: {
1250
+ readonly $ref: "#/$defs/Expression";
1251
+ };
1252
+ };
1253
+ };
1254
+ };
1255
+ readonly StylePreset: {
1256
+ readonly type: "object";
1257
+ readonly required: readonly ["base"];
1258
+ readonly additionalProperties: false;
1259
+ readonly properties: {
1260
+ readonly base: {
1261
+ readonly type: "string";
1262
+ };
1263
+ readonly variants: {
1264
+ readonly type: "object";
1265
+ readonly additionalProperties: {
1266
+ readonly type: "object";
1267
+ readonly additionalProperties: {
1268
+ readonly type: "string";
1269
+ };
1270
+ };
1271
+ };
1272
+ readonly defaultVariants: {
1273
+ readonly type: "object";
1274
+ readonly additionalProperties: {
1275
+ readonly type: "string";
1276
+ };
1277
+ };
1278
+ readonly compoundVariants: {
1279
+ readonly type: "array";
1280
+ readonly items: {
1281
+ readonly $ref: "#/$defs/CompoundVariant";
1282
+ };
1283
+ };
1284
+ };
1285
+ };
1286
+ readonly CompoundVariant: {
1287
+ readonly type: "object";
1288
+ readonly required: readonly ["class"];
1289
+ readonly additionalProperties: {
1290
+ readonly type: "string";
1291
+ };
1292
+ readonly properties: {
1293
+ readonly class: {
1294
+ readonly type: "string";
1295
+ };
1296
+ };
1297
+ };
1075
1298
  readonly StateField: {
1076
1299
  readonly oneOf: readonly [{
1077
1300
  readonly $ref: "#/$defs/NumberField";
@@ -1486,4 +1709,4 @@ declare const astSchema: {
1486
1709
  };
1487
1710
  };
1488
1711
 
1489
- 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 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 };
1712
+ export { type ActionDefinition, type ActionStep, BINARY_OPERATORS, type BinExpr, type BinaryOperator, type BooleanField, CLIPBOARD_OPERATIONS, type CallStep, type ClipboardOperation, type ClipboardStep, type CloseStep, 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 SendStep, type SetPathStep, 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.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Core types, schema, and validator for Constela UI framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",