@constela/core 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -6,10 +6,16 @@
6
6
  */
7
7
  declare const BINARY_OPERATORS: readonly ["+", "-", "*", "/", "==", "!=", "<", "<=", ">", ">=", "&&", "||"];
8
8
  type BinaryOperator = (typeof BINARY_OPERATORS)[number];
9
- declare const UPDATE_OPERATIONS: readonly ["increment", "decrement", "push", "pop", "remove"];
9
+ declare const UPDATE_OPERATIONS: readonly ["increment", "decrement", "push", "pop", "remove", "toggle", "merge", "replaceAt", "insertAt", "splice"];
10
10
  type UpdateOperation = (typeof UPDATE_OPERATIONS)[number];
11
11
  declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE"];
12
12
  type HttpMethod = (typeof HTTP_METHODS)[number];
13
+ declare const PARAM_TYPES: readonly ["string", "number", "boolean", "json"];
14
+ type ParamType = (typeof PARAM_TYPES)[number];
15
+ interface ParamDef {
16
+ type: ParamType;
17
+ required?: boolean;
18
+ }
13
19
  /**
14
20
  * Literal expression - represents a constant value
15
21
  */
@@ -48,7 +54,32 @@ interface NotExpr {
48
54
  expr: 'not';
49
55
  operand: Expression;
50
56
  }
51
- type Expression = LitExpr | StateExpr | VarExpr | BinExpr | NotExpr;
57
+ /**
58
+ * Param expression - references a component parameter
59
+ */
60
+ interface ParamExpr {
61
+ expr: 'param';
62
+ name: string;
63
+ path?: string;
64
+ }
65
+ /**
66
+ * Cond expression - conditional if/then/else
67
+ */
68
+ interface CondExpr {
69
+ expr: 'cond';
70
+ if: Expression;
71
+ then: Expression;
72
+ else: Expression;
73
+ }
74
+ /**
75
+ * Get expression - property access
76
+ */
77
+ interface GetExpr {
78
+ expr: 'get';
79
+ base: Expression;
80
+ path: string;
81
+ }
82
+ type Expression = LitExpr | StateExpr | VarExpr | BinExpr | NotExpr | ParamExpr | CondExpr | GetExpr;
52
83
  /**
53
84
  * Number state field
54
85
  */
@@ -70,7 +101,21 @@ interface ListField {
70
101
  type: 'list';
71
102
  initial: unknown[];
72
103
  }
73
- type StateField = NumberField | StringField | ListField;
104
+ /**
105
+ * Boolean state field
106
+ */
107
+ interface BooleanField {
108
+ type: 'boolean';
109
+ initial: boolean;
110
+ }
111
+ /**
112
+ * Object state field
113
+ */
114
+ interface ObjectField {
115
+ type: 'object';
116
+ initial: Record<string, unknown>;
117
+ }
118
+ type StateField = NumberField | StringField | ListField | BooleanField | ObjectField;
74
119
  /**
75
120
  * Set step - sets a state field to a new value
76
121
  */
@@ -81,12 +126,31 @@ interface SetStep {
81
126
  }
82
127
  /**
83
128
  * Update step - performs an operation on a state field
129
+ *
130
+ * Operations and their required fields:
131
+ * - increment/decrement: Numeric operations. Optional `value` for amount (default: 1)
132
+ * - push: Add item to array. Requires `value`
133
+ * - pop: Remove last item from array. No additional fields
134
+ * - remove: Remove item from array by value or index. Requires `value`
135
+ * - toggle: Flip boolean value. No additional fields
136
+ * - merge: Shallow merge object. Requires `value` (object)
137
+ * - replaceAt: Replace array item at index. Requires `index` and `value`
138
+ * - insertAt: Insert item at array index. Requires `index` and `value`
139
+ * - splice: Delete and/or insert items. Requires `index` and `deleteCount`, optional `value` (array)
140
+ *
141
+ * @property target - The state field name to update
142
+ * @property operation - The update operation to perform
143
+ * @property value - Value for push/merge/replaceAt/insertAt/splice operations
144
+ * @property index - Array index for replaceAt/insertAt/splice operations
145
+ * @property deleteCount - Number of items to delete for splice operation
84
146
  */
85
147
  interface UpdateStep {
86
148
  do: 'update';
87
149
  target: string;
88
150
  operation: UpdateOperation;
89
151
  value?: Expression;
152
+ index?: Expression;
153
+ deleteCount?: Expression;
90
154
  }
91
155
  /**
92
156
  * Fetch step - makes an HTTP request
@@ -152,7 +216,26 @@ interface EachNode {
152
216
  key?: Expression;
153
217
  body: ViewNode;
154
218
  }
155
- type ViewNode = ElementNode | TextNode | IfNode | EachNode;
219
+ /**
220
+ * Component node - invokes a defined component
221
+ */
222
+ interface ComponentNode {
223
+ kind: 'component';
224
+ name: string;
225
+ props?: Record<string, Expression>;
226
+ children?: ViewNode[];
227
+ }
228
+ /**
229
+ * Slot node - placeholder for children in component definition
230
+ */
231
+ interface SlotNode {
232
+ kind: 'slot';
233
+ }
234
+ type ViewNode = ElementNode | TextNode | IfNode | EachNode | ComponentNode | SlotNode;
235
+ interface ComponentDef {
236
+ params?: Record<string, ParamDef>;
237
+ view: ViewNode;
238
+ }
156
239
  /**
157
240
  * Program - the root of a Constela AST
158
241
  */
@@ -161,6 +244,7 @@ interface Program {
161
244
  state: Record<string, StateField>;
162
245
  actions: ActionDefinition[];
163
246
  view: ViewNode;
247
+ components?: Record<string, ComponentDef>;
164
248
  }
165
249
  type ConstelaAst = Program;
166
250
 
@@ -202,6 +286,30 @@ declare function isBinExpr(value: unknown): value is BinExpr;
202
286
  * Use the AST validator for full recursive validation instead.
203
287
  */
204
288
  declare function isNotExpr(value: unknown): value is NotExpr;
289
+ /**
290
+ * Checks if value is a param expression
291
+ */
292
+ declare function isParamExpr(value: unknown): value is ParamExpr;
293
+ /**
294
+ * Checks if value is a cond expression
295
+ *
296
+ * Note: This performs shallow validation only - it checks that `if`, `then`, and `else`
297
+ * are objects but does not recursively validate they are valid Expressions.
298
+ * This is intentional for performance: deep validation would require traversing
299
+ * potentially deeply nested expression trees on every type guard call.
300
+ * Use the AST validator for full recursive validation instead.
301
+ */
302
+ declare function isCondExpr(value: unknown): value is CondExpr;
303
+ /**
304
+ * Checks if value is a get expression
305
+ *
306
+ * Note: This performs shallow validation only - it checks that `base` is
307
+ * an object but does not recursively validate it is a valid Expression.
308
+ * This is intentional for performance: deep validation would require traversing
309
+ * potentially deeply nested expression trees on every type guard call.
310
+ * Use the AST validator for full recursive validation instead.
311
+ */
312
+ declare function isGetExpr(value: unknown): value is GetExpr;
205
313
  /**
206
314
  * Checks if value is any valid expression
207
315
  */
@@ -222,6 +330,14 @@ declare function isIfNode(value: unknown): value is IfNode;
222
330
  * Checks if value is an each node
223
331
  */
224
332
  declare function isEachNode(value: unknown): value is EachNode;
333
+ /**
334
+ * Checks if value is a component node
335
+ */
336
+ declare function isComponentNode(value: unknown): value is ComponentNode;
337
+ /**
338
+ * Checks if value is a slot node
339
+ */
340
+ declare function isSlotNode(value: unknown): value is SlotNode;
225
341
  /**
226
342
  * Checks if value is any valid view node
227
343
  */
@@ -254,6 +370,14 @@ declare function isStringField(value: unknown): value is StringField;
254
370
  * Checks if value is a list field
255
371
  */
256
372
  declare function isListField(value: unknown): value is ListField;
373
+ /**
374
+ * Checks if value is a boolean field
375
+ */
376
+ declare function isBooleanField(value: unknown): value is BooleanField;
377
+ /**
378
+ * Checks if value is an object field
379
+ */
380
+ declare function isObjectField(value: unknown): value is ObjectField;
257
381
  /**
258
382
  * Checks if value is any valid state field
259
383
  */
@@ -269,7 +393,7 @@ declare function isEventHandler(value: unknown): value is EventHandler;
269
393
  * This module defines error types, the ConstelaError class,
270
394
  * and factory functions for creating specific errors.
271
395
  */
272
- type ErrorCode = 'SCHEMA_INVALID' | 'UNDEFINED_STATE' | 'UNDEFINED_ACTION' | 'VAR_UNDEFINED' | 'DUPLICATE_ACTION' | 'UNSUPPORTED_VERSION';
396
+ 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';
273
397
  /**
274
398
  * Custom error class for Constela validation errors
275
399
  */
@@ -314,6 +438,42 @@ declare function createUndefinedVarError(varName: string, path?: string): Conste
314
438
  * Creates an unsupported version error
315
439
  */
316
440
  declare function createUnsupportedVersionError(version: string): ConstelaError;
441
+ /**
442
+ * Creates a component not found error
443
+ */
444
+ declare function createComponentNotFoundError(name: string, path?: string): ConstelaError;
445
+ /**
446
+ * Creates a missing required prop error
447
+ */
448
+ declare function createComponentPropMissingError(componentName: string, propName: string, path?: string): ConstelaError;
449
+ /**
450
+ * Creates a component cycle error
451
+ */
452
+ declare function createComponentCycleError(cycle: string[], path?: string): ConstelaError;
453
+ /**
454
+ * Creates a prop type mismatch error
455
+ */
456
+ declare function createComponentPropTypeError(componentName: string, propName: string, expected: string, actual: string, path?: string): ConstelaError;
457
+ /**
458
+ * Creates an undefined param reference error
459
+ */
460
+ declare function createUndefinedParamError(paramName: string, path?: string): ConstelaError;
461
+ /**
462
+ * Creates an operation invalid for type error
463
+ */
464
+ declare function createOperationInvalidForTypeError(operation: string, stateType: string, path?: string): ConstelaError;
465
+ /**
466
+ * Creates an operation missing field error
467
+ */
468
+ declare function createOperationMissingFieldError(operation: string, field: string, path?: string): ConstelaError;
469
+ /**
470
+ * Creates an operation unknown error
471
+ */
472
+ declare function createOperationUnknownError(operation: string, path?: string): ConstelaError;
473
+ /**
474
+ * Creates a cond else required error
475
+ */
476
+ declare function createCondElseRequiredError(path?: string): ConstelaError;
317
477
 
318
478
  /**
319
479
  * AST Validator for Constela
@@ -372,6 +532,12 @@ declare const astSchema: {
372
532
  readonly view: {
373
533
  readonly $ref: "#/$defs/ViewNode";
374
534
  };
535
+ readonly components: {
536
+ readonly type: "object";
537
+ readonly additionalProperties: {
538
+ readonly $ref: "#/$defs/ComponentDef";
539
+ };
540
+ };
375
541
  };
376
542
  readonly $defs: {
377
543
  readonly Expression: {
@@ -385,6 +551,12 @@ declare const astSchema: {
385
551
  readonly $ref: "#/$defs/BinExpr";
386
552
  }, {
387
553
  readonly $ref: "#/$defs/NotExpr";
554
+ }, {
555
+ readonly $ref: "#/$defs/ParamExpr";
556
+ }, {
557
+ readonly $ref: "#/$defs/CondExpr";
558
+ }, {
559
+ readonly $ref: "#/$defs/GetExpr";
388
560
  }];
389
561
  };
390
562
  readonly LitExpr: {
@@ -477,6 +649,60 @@ declare const astSchema: {
477
649
  };
478
650
  };
479
651
  };
652
+ readonly ParamExpr: {
653
+ readonly type: "object";
654
+ readonly required: readonly ["expr", "name"];
655
+ readonly additionalProperties: false;
656
+ readonly properties: {
657
+ readonly expr: {
658
+ readonly type: "string";
659
+ readonly const: "param";
660
+ };
661
+ readonly name: {
662
+ readonly type: "string";
663
+ };
664
+ readonly path: {
665
+ readonly type: "string";
666
+ };
667
+ };
668
+ };
669
+ readonly CondExpr: {
670
+ readonly type: "object";
671
+ readonly required: readonly ["expr", "if", "then", "else"];
672
+ readonly additionalProperties: false;
673
+ readonly properties: {
674
+ readonly expr: {
675
+ readonly type: "string";
676
+ readonly const: "cond";
677
+ };
678
+ readonly if: {
679
+ readonly $ref: "#/$defs/Expression";
680
+ };
681
+ readonly then: {
682
+ readonly $ref: "#/$defs/Expression";
683
+ };
684
+ readonly else: {
685
+ readonly $ref: "#/$defs/Expression";
686
+ };
687
+ };
688
+ };
689
+ readonly GetExpr: {
690
+ readonly type: "object";
691
+ readonly required: readonly ["expr", "base", "path"];
692
+ readonly additionalProperties: false;
693
+ readonly properties: {
694
+ readonly expr: {
695
+ readonly type: "string";
696
+ readonly const: "get";
697
+ };
698
+ readonly base: {
699
+ readonly $ref: "#/$defs/Expression";
700
+ };
701
+ readonly path: {
702
+ readonly type: "string";
703
+ };
704
+ };
705
+ };
480
706
  readonly StateField: {
481
707
  readonly oneOf: readonly [{
482
708
  readonly $ref: "#/$defs/NumberField";
@@ -484,6 +710,10 @@ declare const astSchema: {
484
710
  readonly $ref: "#/$defs/StringField";
485
711
  }, {
486
712
  readonly $ref: "#/$defs/ListField";
713
+ }, {
714
+ readonly $ref: "#/$defs/BooleanField";
715
+ }, {
716
+ readonly $ref: "#/$defs/ObjectField";
487
717
  }];
488
718
  };
489
719
  readonly NumberField: {
@@ -528,6 +758,34 @@ declare const astSchema: {
528
758
  };
529
759
  };
530
760
  };
761
+ readonly BooleanField: {
762
+ readonly type: "object";
763
+ readonly required: readonly ["type", "initial"];
764
+ readonly additionalProperties: false;
765
+ readonly properties: {
766
+ readonly type: {
767
+ readonly type: "string";
768
+ readonly const: "boolean";
769
+ };
770
+ readonly initial: {
771
+ readonly type: "boolean";
772
+ };
773
+ };
774
+ };
775
+ readonly ObjectField: {
776
+ readonly type: "object";
777
+ readonly required: readonly ["type", "initial"];
778
+ readonly additionalProperties: false;
779
+ readonly properties: {
780
+ readonly type: {
781
+ readonly type: "string";
782
+ readonly const: "object";
783
+ };
784
+ readonly initial: {
785
+ readonly type: "object";
786
+ };
787
+ };
788
+ };
531
789
  readonly ActionStep: {
532
790
  readonly oneOf: readonly [{
533
791
  readonly $ref: "#/$defs/SetStep";
@@ -568,11 +826,17 @@ declare const astSchema: {
568
826
  };
569
827
  readonly operation: {
570
828
  readonly type: "string";
571
- readonly enum: readonly ["increment", "decrement", "push", "pop", "remove"];
829
+ readonly enum: readonly ["increment", "decrement", "push", "pop", "remove", "toggle", "merge", "replaceAt", "insertAt", "splice"];
572
830
  };
573
831
  readonly value: {
574
832
  readonly $ref: "#/$defs/Expression";
575
833
  };
834
+ readonly index: {
835
+ readonly $ref: "#/$defs/Expression";
836
+ };
837
+ readonly deleteCount: {
838
+ readonly $ref: "#/$defs/Expression";
839
+ };
576
840
  };
577
841
  };
578
842
  readonly FetchStep: {
@@ -652,6 +916,10 @@ declare const astSchema: {
652
916
  readonly $ref: "#/$defs/IfNode";
653
917
  }, {
654
918
  readonly $ref: "#/$defs/EachNode";
919
+ }, {
920
+ readonly $ref: "#/$defs/ComponentNode";
921
+ }, {
922
+ readonly $ref: "#/$defs/SlotNode";
655
923
  }];
656
924
  };
657
925
  readonly ElementNode: {
@@ -744,7 +1012,74 @@ declare const astSchema: {
744
1012
  };
745
1013
  };
746
1014
  };
1015
+ readonly ComponentNode: {
1016
+ readonly type: "object";
1017
+ readonly required: readonly ["kind", "name"];
1018
+ readonly additionalProperties: false;
1019
+ readonly properties: {
1020
+ readonly kind: {
1021
+ readonly type: "string";
1022
+ readonly const: "component";
1023
+ };
1024
+ readonly name: {
1025
+ readonly type: "string";
1026
+ };
1027
+ readonly props: {
1028
+ readonly type: "object";
1029
+ readonly additionalProperties: {
1030
+ readonly $ref: "#/$defs/Expression";
1031
+ };
1032
+ };
1033
+ readonly children: {
1034
+ readonly type: "array";
1035
+ readonly items: {
1036
+ readonly $ref: "#/$defs/ViewNode";
1037
+ };
1038
+ };
1039
+ };
1040
+ };
1041
+ readonly SlotNode: {
1042
+ readonly type: "object";
1043
+ readonly required: readonly ["kind"];
1044
+ readonly additionalProperties: false;
1045
+ readonly properties: {
1046
+ readonly kind: {
1047
+ readonly type: "string";
1048
+ readonly const: "slot";
1049
+ };
1050
+ };
1051
+ };
1052
+ readonly ParamDef: {
1053
+ readonly type: "object";
1054
+ readonly required: readonly ["type"];
1055
+ readonly additionalProperties: false;
1056
+ readonly properties: {
1057
+ readonly type: {
1058
+ readonly type: "string";
1059
+ readonly enum: readonly ["string", "number", "boolean", "json"];
1060
+ };
1061
+ readonly required: {
1062
+ readonly type: "boolean";
1063
+ };
1064
+ };
1065
+ };
1066
+ readonly ComponentDef: {
1067
+ readonly type: "object";
1068
+ readonly required: readonly ["view"];
1069
+ readonly additionalProperties: false;
1070
+ readonly properties: {
1071
+ readonly params: {
1072
+ readonly type: "object";
1073
+ readonly additionalProperties: {
1074
+ readonly $ref: "#/$defs/ParamDef";
1075
+ };
1076
+ };
1077
+ readonly view: {
1078
+ readonly $ref: "#/$defs/ViewNode";
1079
+ };
1080
+ };
1081
+ };
747
1082
  };
748
1083
  };
749
1084
 
750
- export { type ActionDefinition, type ActionStep, BINARY_OPERATORS, type BinExpr, type BinaryOperator, type ConstelaAst, ConstelaError, type EachNode, type ElementNode, type ErrorCode, type EventHandler, type Expression, type FetchStep, HTTP_METHODS, type HttpMethod, type IfNode, type ListField, type LitExpr, type NotExpr, type NumberField, type Program, type SetStep, type StateExpr, type StateField, type StringField, type TextNode, UPDATE_OPERATIONS, type UpdateOperation, type UpdateStep, type ValidationFailure, type ValidationResult, type ValidationSuccess, type VarExpr, type ViewNode, astSchema, createDuplicateActionError, createSchemaError, createUndefinedActionError, createUndefinedStateError, createUndefinedVarError, createUnsupportedVersionError, isActionStep, isBinExpr, isConstelaError, isEachNode, isElementNode, isEventHandler, isExpression, isFetchStep, isIfNode, isListField, isLitExpr, isNotExpr, isNumberField, isSetStep, isStateExpr, isStateField, isStringField, isTextNode, isUpdateStep, isVarExpr, isViewNode, validateAst };
1085
+ export { type ActionDefinition, type ActionStep, BINARY_OPERATORS, type BinExpr, type BinaryOperator, type BooleanField, type ComponentDef, type ComponentNode, type CondExpr, type ConstelaAst, ConstelaError, type EachNode, type ElementNode, type ErrorCode, type EventHandler, type Expression, type FetchStep, type GetExpr, HTTP_METHODS, type HttpMethod, type IfNode, type ListField, type LitExpr, type NotExpr, type NumberField, type ObjectField, PARAM_TYPES, type ParamDef, type ParamExpr, type ParamType, type Program, type SetStep, type SlotNode, type StateExpr, type StateField, type StringField, type TextNode, UPDATE_OPERATIONS, type UpdateOperation, type UpdateStep, type ValidationFailure, type ValidationResult, type ValidationSuccess, type VarExpr, type ViewNode, astSchema, createComponentCycleError, createComponentNotFoundError, createComponentPropMissingError, createComponentPropTypeError, createCondElseRequiredError, createDuplicateActionError, createOperationInvalidForTypeError, createOperationMissingFieldError, createOperationUnknownError, createSchemaError, createUndefinedActionError, createUndefinedParamError, createUndefinedStateError, createUndefinedVarError, createUnsupportedVersionError, isActionStep, isBinExpr, isBooleanField, isComponentNode, isCondExpr, isConstelaError, isEachNode, isElementNode, isEventHandler, isExpression, isFetchStep, isGetExpr, isIfNode, isListField, isLitExpr, isNotExpr, isNumberField, isObjectField, isParamExpr, isSetStep, isSlotNode, isStateExpr, isStateField, isStringField, isTextNode, isUpdateStep, isVarExpr, isViewNode, validateAst };
package/dist/index.js CHANGED
@@ -18,9 +18,15 @@ var UPDATE_OPERATIONS = [
18
18
  "decrement",
19
19
  "push",
20
20
  "pop",
21
- "remove"
21
+ "remove",
22
+ "toggle",
23
+ "merge",
24
+ "replaceAt",
25
+ "insertAt",
26
+ "splice"
22
27
  ];
23
28
  var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE"];
29
+ var PARAM_TYPES = ["string", "number", "boolean", "json"];
24
30
 
25
31
  // src/types/guards.ts
26
32
  function isObject(value) {
@@ -56,8 +62,30 @@ function isNotExpr(value) {
56
62
  if (!isObject(value["operand"])) return false;
57
63
  return true;
58
64
  }
65
+ function isParamExpr(value) {
66
+ if (!isObject(value)) return false;
67
+ if (value["expr"] !== "param") return false;
68
+ if (typeof value["name"] !== "string") return false;
69
+ if ("path" in value && value["path"] !== void 0) {
70
+ if (typeof value["path"] !== "string") return false;
71
+ }
72
+ return true;
73
+ }
74
+ function isCondExpr(value) {
75
+ if (!isObject(value)) return false;
76
+ if (value["expr"] !== "cond") return false;
77
+ if (!isObject(value["if"]) || !isObject(value["then"]) || !isObject(value["else"])) return false;
78
+ return true;
79
+ }
80
+ function isGetExpr(value) {
81
+ if (!isObject(value)) return false;
82
+ if (value["expr"] !== "get") return false;
83
+ if (!isObject(value["base"])) return false;
84
+ if (typeof value["path"] !== "string") return false;
85
+ return true;
86
+ }
59
87
  function isExpression(value) {
60
- return isLitExpr(value) || isStateExpr(value) || isVarExpr(value) || isBinExpr(value) || isNotExpr(value);
88
+ return isLitExpr(value) || isStateExpr(value) || isVarExpr(value) || isBinExpr(value) || isNotExpr(value) || isParamExpr(value) || isCondExpr(value) || isGetExpr(value);
61
89
  }
62
90
  function isElementNode(value) {
63
91
  if (!isObject(value)) return false;
@@ -84,8 +112,24 @@ function isEachNode(value) {
84
112
  if (!isObject(value["body"])) return false;
85
113
  return true;
86
114
  }
115
+ function isComponentNode(value) {
116
+ if (!isObject(value)) return false;
117
+ if (value["kind"] !== "component") return false;
118
+ if (typeof value["name"] !== "string") return false;
119
+ if ("props" in value && value["props"] !== void 0) {
120
+ if (!isObject(value["props"])) return false;
121
+ }
122
+ if ("children" in value && value["children"] !== void 0) {
123
+ if (!Array.isArray(value["children"])) return false;
124
+ }
125
+ return true;
126
+ }
127
+ function isSlotNode(value) {
128
+ if (!isObject(value)) return false;
129
+ return value["kind"] === "slot";
130
+ }
87
131
  function isViewNode(value) {
88
- return isElementNode(value) || isTextNode(value) || isIfNode(value) || isEachNode(value);
132
+ return isElementNode(value) || isTextNode(value) || isIfNode(value) || isEachNode(value) || isComponentNode(value) || isSlotNode(value);
89
133
  }
90
134
  function isSetStep(value) {
91
135
  if (!isObject(value)) return false;
@@ -132,8 +176,18 @@ function isListField(value) {
132
176
  if (value["type"] !== "list") return false;
133
177
  return Array.isArray(value["initial"]);
134
178
  }
179
+ function isBooleanField(value) {
180
+ if (!isObject(value)) return false;
181
+ if (value["type"] !== "boolean") return false;
182
+ return typeof value["initial"] === "boolean";
183
+ }
184
+ function isObjectField(value) {
185
+ if (!isObject(value)) return false;
186
+ if (value["type"] !== "object") return false;
187
+ return typeof value["initial"] === "object" && value["initial"] !== null;
188
+ }
135
189
  function isStateField(value) {
136
- return isNumberField(value) || isStringField(value) || isListField(value);
190
+ return isNumberField(value) || isStringField(value) || isListField(value) || isBooleanField(value) || isObjectField(value);
137
191
  }
138
192
  function isEventHandler(value) {
139
193
  if (!isObject(value)) return false;
@@ -205,6 +259,69 @@ function createUnsupportedVersionError(version) {
205
259
  "/version"
206
260
  );
207
261
  }
262
+ function createComponentNotFoundError(name, path) {
263
+ return new ConstelaError(
264
+ "COMPONENT_NOT_FOUND",
265
+ `Component '${name}' is not defined in components`,
266
+ path
267
+ );
268
+ }
269
+ function createComponentPropMissingError(componentName, propName, path) {
270
+ return new ConstelaError(
271
+ "COMPONENT_PROP_MISSING",
272
+ `Component '${componentName}' requires prop '${propName}'`,
273
+ path
274
+ );
275
+ }
276
+ function createComponentCycleError(cycle, path) {
277
+ return new ConstelaError(
278
+ "COMPONENT_CYCLE",
279
+ `Circular component reference detected: ${cycle.join(" -> ")}`,
280
+ path
281
+ );
282
+ }
283
+ function createComponentPropTypeError(componentName, propName, expected, actual, path) {
284
+ return new ConstelaError(
285
+ "COMPONENT_PROP_TYPE",
286
+ `Component '${componentName}' prop '${propName}' expects ${expected}, got ${actual}`,
287
+ path
288
+ );
289
+ }
290
+ function createUndefinedParamError(paramName, path) {
291
+ return new ConstelaError(
292
+ "PARAM_UNDEFINED",
293
+ `Undefined param reference: '${paramName}' is not defined in component params`,
294
+ path
295
+ );
296
+ }
297
+ function createOperationInvalidForTypeError(operation, stateType, path) {
298
+ return new ConstelaError(
299
+ "OPERATION_INVALID_FOR_TYPE",
300
+ `Operation '${operation}' is not valid for state type '${stateType}'`,
301
+ path
302
+ );
303
+ }
304
+ function createOperationMissingFieldError(operation, field, path) {
305
+ return new ConstelaError(
306
+ "OPERATION_MISSING_FIELD",
307
+ `Operation '${operation}' requires field '${field}'`,
308
+ path
309
+ );
310
+ }
311
+ function createOperationUnknownError(operation, path) {
312
+ return new ConstelaError(
313
+ "OPERATION_UNKNOWN",
314
+ `Unknown operation: '${operation}'`,
315
+ path
316
+ );
317
+ }
318
+ function createCondElseRequiredError(path) {
319
+ return new ConstelaError(
320
+ "EXPR_COND_ELSE_REQUIRED",
321
+ `Cond expression requires 'else' field`,
322
+ path
323
+ );
324
+ }
208
325
 
209
326
  // src/schema/validator.ts
210
327
  import Ajv from "ajv";
@@ -237,6 +354,10 @@ var astSchema = {
237
354
  },
238
355
  view: {
239
356
  $ref: "#/$defs/ViewNode"
357
+ },
358
+ components: {
359
+ type: "object",
360
+ additionalProperties: { $ref: "#/$defs/ComponentDef" }
240
361
  }
241
362
  },
242
363
  $defs: {
@@ -247,7 +368,10 @@ var astSchema = {
247
368
  { $ref: "#/$defs/StateExpr" },
248
369
  { $ref: "#/$defs/VarExpr" },
249
370
  { $ref: "#/$defs/BinExpr" },
250
- { $ref: "#/$defs/NotExpr" }
371
+ { $ref: "#/$defs/NotExpr" },
372
+ { $ref: "#/$defs/ParamExpr" },
373
+ { $ref: "#/$defs/CondExpr" },
374
+ { $ref: "#/$defs/GetExpr" }
251
375
  ]
252
376
  },
253
377
  LitExpr: {
@@ -309,12 +433,45 @@ var astSchema = {
309
433
  operand: { $ref: "#/$defs/Expression" }
310
434
  }
311
435
  },
436
+ ParamExpr: {
437
+ type: "object",
438
+ required: ["expr", "name"],
439
+ additionalProperties: false,
440
+ properties: {
441
+ expr: { type: "string", const: "param" },
442
+ name: { type: "string" },
443
+ path: { type: "string" }
444
+ }
445
+ },
446
+ CondExpr: {
447
+ type: "object",
448
+ required: ["expr", "if", "then", "else"],
449
+ additionalProperties: false,
450
+ properties: {
451
+ expr: { type: "string", const: "cond" },
452
+ if: { $ref: "#/$defs/Expression" },
453
+ then: { $ref: "#/$defs/Expression" },
454
+ else: { $ref: "#/$defs/Expression" }
455
+ }
456
+ },
457
+ GetExpr: {
458
+ type: "object",
459
+ required: ["expr", "base", "path"],
460
+ additionalProperties: false,
461
+ properties: {
462
+ expr: { type: "string", const: "get" },
463
+ base: { $ref: "#/$defs/Expression" },
464
+ path: { type: "string" }
465
+ }
466
+ },
312
467
  // ==================== State Fields ====================
313
468
  StateField: {
314
469
  oneOf: [
315
470
  { $ref: "#/$defs/NumberField" },
316
471
  { $ref: "#/$defs/StringField" },
317
- { $ref: "#/$defs/ListField" }
472
+ { $ref: "#/$defs/ListField" },
473
+ { $ref: "#/$defs/BooleanField" },
474
+ { $ref: "#/$defs/ObjectField" }
318
475
  ]
319
476
  },
320
477
  NumberField: {
@@ -344,6 +501,24 @@ var astSchema = {
344
501
  initial: { type: "array" }
345
502
  }
346
503
  },
504
+ BooleanField: {
505
+ type: "object",
506
+ required: ["type", "initial"],
507
+ additionalProperties: false,
508
+ properties: {
509
+ type: { type: "string", const: "boolean" },
510
+ initial: { type: "boolean" }
511
+ }
512
+ },
513
+ ObjectField: {
514
+ type: "object",
515
+ required: ["type", "initial"],
516
+ additionalProperties: false,
517
+ properties: {
518
+ type: { type: "string", const: "object" },
519
+ initial: { type: "object" }
520
+ }
521
+ },
347
522
  // ==================== Action Steps ====================
348
523
  ActionStep: {
349
524
  oneOf: [
@@ -371,9 +546,11 @@ var astSchema = {
371
546
  target: { type: "string" },
372
547
  operation: {
373
548
  type: "string",
374
- enum: ["increment", "decrement", "push", "pop", "remove"]
549
+ enum: ["increment", "decrement", "push", "pop", "remove", "toggle", "merge", "replaceAt", "insertAt", "splice"]
375
550
  },
376
- value: { $ref: "#/$defs/Expression" }
551
+ value: { $ref: "#/$defs/Expression" },
552
+ index: { $ref: "#/$defs/Expression" },
553
+ deleteCount: { $ref: "#/$defs/Expression" }
377
554
  }
378
555
  },
379
556
  FetchStep: {
@@ -429,7 +606,9 @@ var astSchema = {
429
606
  { $ref: "#/$defs/ElementNode" },
430
607
  { $ref: "#/$defs/TextNode" },
431
608
  { $ref: "#/$defs/IfNode" },
432
- { $ref: "#/$defs/EachNode" }
609
+ { $ref: "#/$defs/EachNode" },
610
+ { $ref: "#/$defs/ComponentNode" },
611
+ { $ref: "#/$defs/SlotNode" }
433
612
  ]
434
613
  },
435
614
  ElementNode: {
@@ -486,6 +665,56 @@ var astSchema = {
486
665
  key: { $ref: "#/$defs/Expression" },
487
666
  body: { $ref: "#/$defs/ViewNode" }
488
667
  }
668
+ },
669
+ ComponentNode: {
670
+ type: "object",
671
+ required: ["kind", "name"],
672
+ additionalProperties: false,
673
+ properties: {
674
+ kind: { type: "string", const: "component" },
675
+ name: { type: "string" },
676
+ props: {
677
+ type: "object",
678
+ additionalProperties: { $ref: "#/$defs/Expression" }
679
+ },
680
+ children: {
681
+ type: "array",
682
+ items: { $ref: "#/$defs/ViewNode" }
683
+ }
684
+ }
685
+ },
686
+ SlotNode: {
687
+ type: "object",
688
+ required: ["kind"],
689
+ additionalProperties: false,
690
+ properties: {
691
+ kind: { type: "string", const: "slot" }
692
+ }
693
+ },
694
+ // ==================== Component Definition ====================
695
+ ParamDef: {
696
+ type: "object",
697
+ required: ["type"],
698
+ additionalProperties: false,
699
+ properties: {
700
+ type: {
701
+ type: "string",
702
+ enum: ["string", "number", "boolean", "json"]
703
+ },
704
+ required: { type: "boolean" }
705
+ }
706
+ },
707
+ ComponentDef: {
708
+ type: "object",
709
+ required: ["view"],
710
+ additionalProperties: false,
711
+ properties: {
712
+ params: {
713
+ type: "object",
714
+ additionalProperties: { $ref: "#/$defs/ParamDef" }
715
+ },
716
+ view: { $ref: "#/$defs/ViewNode" }
717
+ }
489
718
  }
490
719
  }
491
720
  };
@@ -500,10 +729,11 @@ var validate = ajv.compile(astSchema);
500
729
  function isObject2(value) {
501
730
  return typeof value === "object" && value !== null && !Array.isArray(value);
502
731
  }
503
- var VALID_VIEW_KINDS = ["element", "text", "if", "each"];
504
- var VALID_EXPR_TYPES = ["lit", "state", "var", "bin", "not"];
732
+ var VALID_VIEW_KINDS = ["element", "text", "if", "each", "component", "slot"];
733
+ var VALID_EXPR_TYPES = ["lit", "state", "var", "bin", "not", "param"];
734
+ var VALID_PARAM_TYPES = ["string", "number", "boolean", "json"];
505
735
  var VALID_ACTION_TYPES = ["set", "update", "fetch"];
506
- var VALID_STATE_TYPES = ["number", "string", "list"];
736
+ var VALID_STATE_TYPES = ["number", "string", "list", "boolean", "object"];
507
737
  var VALID_BIN_OPS = BINARY_OPERATORS;
508
738
  var VALID_UPDATE_OPS = UPDATE_OPERATIONS;
509
739
  var VALID_HTTP_METHODS = HTTP_METHODS;
@@ -516,7 +746,7 @@ function validateViewNode(node, path) {
516
746
  return { path: path + "/kind", message: "kind is required" };
517
747
  }
518
748
  if (!VALID_VIEW_KINDS.includes(kind)) {
519
- return { path: path + "/kind", message: "must be one of: element, text, if, each" };
749
+ return { path: path + "/kind", message: "must be one of: element, text, if, each, component, slot" };
520
750
  }
521
751
  switch (kind) {
522
752
  case "element":
@@ -579,6 +809,25 @@ function validateViewNode(node, path) {
579
809
  if (bodyError) return bodyError;
580
810
  }
581
811
  break;
812
+ case "component":
813
+ if (typeof node["name"] !== "string") {
814
+ return { path: path + "/name", message: "name is required" };
815
+ }
816
+ if (node["props"] !== void 0 && isObject2(node["props"])) {
817
+ for (const [propName, propValue] of Object.entries(node["props"])) {
818
+ const error = validateExpression(propValue, path + "/props/" + propName);
819
+ if (error) return error;
820
+ }
821
+ }
822
+ if (Array.isArray(node["children"])) {
823
+ for (let i = 0; i < node["children"].length; i++) {
824
+ const error = validateViewNode(node["children"][i], path + "/children/" + i);
825
+ if (error) return error;
826
+ }
827
+ }
828
+ break;
829
+ case "slot":
830
+ break;
582
831
  }
583
832
  return null;
584
833
  }
@@ -591,7 +840,7 @@ function validateExpression(expr, path) {
591
840
  return { path: path + "/expr", message: "expr is required" };
592
841
  }
593
842
  if (!VALID_EXPR_TYPES.includes(exprType)) {
594
- return { path: path + "/expr", message: "must be one of: lit, state, var, bin, not" };
843
+ return { path: path + "/expr", message: "must be one of: lit, state, var, bin, not, param" };
595
844
  }
596
845
  switch (exprType) {
597
846
  case "lit":
@@ -630,6 +879,14 @@ function validateExpression(expr, path) {
630
879
  return { path: path + "/operand", message: "operand is required" };
631
880
  }
632
881
  return validateExpression(expr["operand"], path + "/operand");
882
+ case "param":
883
+ if (typeof expr["name"] !== "string") {
884
+ return { path: path + "/name", message: "name is required" };
885
+ }
886
+ if ("path" in expr && typeof expr["path"] !== "string") {
887
+ return { path: path + "/path", message: "path must be a string" };
888
+ }
889
+ break;
633
890
  }
634
891
  return null;
635
892
  }
@@ -718,7 +975,50 @@ function validateStateField(field, path) {
718
975
  return { path: path + "/initial", message: "must be an array" };
719
976
  }
720
977
  break;
978
+ case "boolean":
979
+ if (typeof field["initial"] !== "boolean") {
980
+ return { path: path + "/initial", message: "must be a boolean" };
981
+ }
982
+ break;
983
+ case "object":
984
+ if (!isObject2(field["initial"])) {
985
+ return { path: path + "/initial", message: "must be an object" };
986
+ }
987
+ break;
988
+ }
989
+ return null;
990
+ }
991
+ function validateParamDef(param, path) {
992
+ if (!isObject2(param)) {
993
+ return { path, message: "must be an object" };
994
+ }
995
+ const paramType = param["type"];
996
+ if (typeof paramType !== "string") {
997
+ return { path: path + "/type", message: "type is required" };
998
+ }
999
+ if (!VALID_PARAM_TYPES.includes(paramType)) {
1000
+ return { path: path + "/type", message: "must be one of: string, number, boolean, json" };
1001
+ }
1002
+ if ("required" in param && typeof param["required"] !== "boolean") {
1003
+ return { path: path + "/required", message: "required must be a boolean" };
1004
+ }
1005
+ return null;
1006
+ }
1007
+ function validateComponentDef(def, path) {
1008
+ if (!isObject2(def)) {
1009
+ return { path, message: "must be an object" };
1010
+ }
1011
+ if (!("view" in def)) {
1012
+ return { path: path + "/view", message: "view is required" };
1013
+ }
1014
+ if ("params" in def && isObject2(def["params"])) {
1015
+ for (const [paramName, paramDef] of Object.entries(def["params"])) {
1016
+ const error = validateParamDef(paramDef, path + "/params/" + paramName);
1017
+ if (error) return error;
1018
+ }
721
1019
  }
1020
+ const viewError = validateViewNode(def["view"], path + "/view");
1021
+ if (viewError) return viewError;
722
1022
  return null;
723
1023
  }
724
1024
  function customValidateAst(input) {
@@ -743,6 +1043,12 @@ function customValidateAst(input) {
743
1043
  const error = validateViewNode(input["view"], "/view");
744
1044
  if (error) return error;
745
1045
  }
1046
+ if ("components" in input && isObject2(input["components"])) {
1047
+ for (const [name, def] of Object.entries(input["components"])) {
1048
+ const error = validateComponentDef(def, "/components/" + name);
1049
+ if (error) return error;
1050
+ }
1051
+ }
746
1052
  return null;
747
1053
  }
748
1054
  function findTopLevelRequiredError(errors) {
@@ -943,28 +1249,45 @@ export {
943
1249
  BINARY_OPERATORS,
944
1250
  ConstelaError,
945
1251
  HTTP_METHODS,
1252
+ PARAM_TYPES,
946
1253
  UPDATE_OPERATIONS,
947
1254
  astSchema,
1255
+ createComponentCycleError,
1256
+ createComponentNotFoundError,
1257
+ createComponentPropMissingError,
1258
+ createComponentPropTypeError,
1259
+ createCondElseRequiredError,
948
1260
  createDuplicateActionError,
1261
+ createOperationInvalidForTypeError,
1262
+ createOperationMissingFieldError,
1263
+ createOperationUnknownError,
949
1264
  createSchemaError,
950
1265
  createUndefinedActionError,
1266
+ createUndefinedParamError,
951
1267
  createUndefinedStateError,
952
1268
  createUndefinedVarError,
953
1269
  createUnsupportedVersionError,
954
1270
  isActionStep,
955
1271
  isBinExpr,
1272
+ isBooleanField,
1273
+ isComponentNode,
1274
+ isCondExpr,
956
1275
  isConstelaError,
957
1276
  isEachNode,
958
1277
  isElementNode,
959
1278
  isEventHandler,
960
1279
  isExpression,
961
1280
  isFetchStep,
1281
+ isGetExpr,
962
1282
  isIfNode,
963
1283
  isListField,
964
1284
  isLitExpr,
965
1285
  isNotExpr,
966
1286
  isNumberField,
1287
+ isObjectField,
1288
+ isParamExpr,
967
1289
  isSetStep,
1290
+ isSlotNode,
968
1291
  isStateExpr,
969
1292
  isStateField,
970
1293
  isStringField,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Core types, schema, and validator for Constela UI framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",