@constela/core 0.1.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.js ADDED
@@ -0,0 +1,976 @@
1
+ // src/types/ast.ts
2
+ var BINARY_OPERATORS = [
3
+ "+",
4
+ "-",
5
+ "*",
6
+ "/",
7
+ "==",
8
+ "!=",
9
+ "<",
10
+ "<=",
11
+ ">",
12
+ ">=",
13
+ "&&",
14
+ "||"
15
+ ];
16
+ var UPDATE_OPERATIONS = [
17
+ "increment",
18
+ "decrement",
19
+ "push",
20
+ "pop",
21
+ "remove"
22
+ ];
23
+ var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE"];
24
+
25
+ // src/types/guards.ts
26
+ function isObject(value) {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28
+ }
29
+ function isLitExpr(value) {
30
+ if (!isObject(value)) return false;
31
+ if (value["expr"] !== "lit") return false;
32
+ return "value" in value;
33
+ }
34
+ function isStateExpr(value) {
35
+ if (!isObject(value)) return false;
36
+ if (value["expr"] !== "state") return false;
37
+ return typeof value["name"] === "string";
38
+ }
39
+ function isVarExpr(value) {
40
+ if (!isObject(value)) return false;
41
+ if (value["expr"] !== "var") return false;
42
+ return typeof value["name"] === "string";
43
+ }
44
+ function isBinExpr(value) {
45
+ if (!isObject(value)) return false;
46
+ if (value["expr"] !== "bin") return false;
47
+ if (!BINARY_OPERATORS.includes(value["op"])) {
48
+ return false;
49
+ }
50
+ if (!isObject(value["left"]) || !isObject(value["right"])) return false;
51
+ return true;
52
+ }
53
+ function isNotExpr(value) {
54
+ if (!isObject(value)) return false;
55
+ if (value["expr"] !== "not") return false;
56
+ if (!isObject(value["operand"])) return false;
57
+ return true;
58
+ }
59
+ function isExpression(value) {
60
+ return isLitExpr(value) || isStateExpr(value) || isVarExpr(value) || isBinExpr(value) || isNotExpr(value);
61
+ }
62
+ function isElementNode(value) {
63
+ if (!isObject(value)) return false;
64
+ if (value["kind"] !== "element") return false;
65
+ return typeof value["tag"] === "string";
66
+ }
67
+ function isTextNode(value) {
68
+ if (!isObject(value)) return false;
69
+ if (value["kind"] !== "text") return false;
70
+ return isObject(value["value"]);
71
+ }
72
+ function isIfNode(value) {
73
+ if (!isObject(value)) return false;
74
+ if (value["kind"] !== "if") return false;
75
+ if (!isObject(value["condition"])) return false;
76
+ if (!isObject(value["then"])) return false;
77
+ return true;
78
+ }
79
+ function isEachNode(value) {
80
+ if (!isObject(value)) return false;
81
+ if (value["kind"] !== "each") return false;
82
+ if (!isObject(value["items"])) return false;
83
+ if (typeof value["as"] !== "string") return false;
84
+ if (!isObject(value["body"])) return false;
85
+ return true;
86
+ }
87
+ function isViewNode(value) {
88
+ return isElementNode(value) || isTextNode(value) || isIfNode(value) || isEachNode(value);
89
+ }
90
+ function isSetStep(value) {
91
+ if (!isObject(value)) return false;
92
+ if (value["do"] !== "set") return false;
93
+ if (typeof value["target"] !== "string") return false;
94
+ if (!isObject(value["value"])) return false;
95
+ return true;
96
+ }
97
+ function isUpdateStep(value) {
98
+ if (!isObject(value)) return false;
99
+ if (value["do"] !== "update") return false;
100
+ if (typeof value["target"] !== "string") return false;
101
+ if (!UPDATE_OPERATIONS.includes(value["operation"])) {
102
+ return false;
103
+ }
104
+ return true;
105
+ }
106
+ function isFetchStep(value) {
107
+ if (!isObject(value)) return false;
108
+ if (value["do"] !== "fetch") return false;
109
+ if (!isObject(value["url"])) return false;
110
+ if ("method" in value && value["method"] !== void 0) {
111
+ if (!HTTP_METHODS.includes(value["method"])) {
112
+ return false;
113
+ }
114
+ }
115
+ return true;
116
+ }
117
+ function isActionStep(value) {
118
+ return isSetStep(value) || isUpdateStep(value) || isFetchStep(value);
119
+ }
120
+ function isNumberField(value) {
121
+ if (!isObject(value)) return false;
122
+ if (value["type"] !== "number") return false;
123
+ return typeof value["initial"] === "number";
124
+ }
125
+ function isStringField(value) {
126
+ if (!isObject(value)) return false;
127
+ if (value["type"] !== "string") return false;
128
+ return typeof value["initial"] === "string";
129
+ }
130
+ function isListField(value) {
131
+ if (!isObject(value)) return false;
132
+ if (value["type"] !== "list") return false;
133
+ return Array.isArray(value["initial"]);
134
+ }
135
+ function isStateField(value) {
136
+ return isNumberField(value) || isStringField(value) || isListField(value);
137
+ }
138
+ function isEventHandler(value) {
139
+ if (!isObject(value)) return false;
140
+ if (!("event" in value) || typeof value["event"] !== "string") return false;
141
+ if (!("action" in value) || typeof value["action"] !== "string") return false;
142
+ return true;
143
+ }
144
+
145
+ // src/types/error.ts
146
+ var ConstelaError = class _ConstelaError extends Error {
147
+ code;
148
+ path;
149
+ constructor(code, message, path) {
150
+ super(message);
151
+ this.name = "ConstelaError";
152
+ this.code = code;
153
+ this.path = path;
154
+ Object.setPrototypeOf(this, _ConstelaError.prototype);
155
+ }
156
+ /**
157
+ * Converts the error to a JSON-serializable object
158
+ */
159
+ toJSON() {
160
+ return {
161
+ code: this.code,
162
+ message: this.message,
163
+ path: this.path
164
+ };
165
+ }
166
+ };
167
+ function isConstelaError(value) {
168
+ return value instanceof ConstelaError;
169
+ }
170
+ function createSchemaError(message, path) {
171
+ return new ConstelaError("SCHEMA_INVALID", message, path);
172
+ }
173
+ function createUndefinedStateError(stateName, path) {
174
+ return new ConstelaError(
175
+ "UNDEFINED_STATE",
176
+ `Undefined state reference: '${stateName}' is not defined in state`,
177
+ path
178
+ );
179
+ }
180
+ function createUndefinedActionError(actionName, path) {
181
+ return new ConstelaError(
182
+ "UNDEFINED_ACTION",
183
+ `Undefined action reference: '${actionName}' is not defined in actions`,
184
+ path
185
+ );
186
+ }
187
+ function createDuplicateActionError(actionName, path) {
188
+ return new ConstelaError(
189
+ "DUPLICATE_ACTION",
190
+ `Duplicate action name: '${actionName}' is already defined`,
191
+ path
192
+ );
193
+ }
194
+ function createUndefinedVarError(varName, path) {
195
+ return new ConstelaError(
196
+ "VAR_UNDEFINED",
197
+ `Undefined variable reference: '${varName}' is not defined in scope`,
198
+ path
199
+ );
200
+ }
201
+ function createUnsupportedVersionError(version) {
202
+ return new ConstelaError(
203
+ "UNSUPPORTED_VERSION",
204
+ `Unsupported version: '${version}'. Supported versions: 1.0`,
205
+ "/version"
206
+ );
207
+ }
208
+
209
+ // src/schema/validator.ts
210
+ import Ajv from "ajv";
211
+
212
+ // src/schema/ast.schema.ts
213
+ var astSchema = {
214
+ $schema: "http://json-schema.org/draft-07/schema#",
215
+ $id: "https://constela.dev/schemas/ast.json",
216
+ title: "Constela AST",
217
+ description: "Schema for Constela UI framework AST",
218
+ type: "object",
219
+ required: ["version", "state", "actions", "view"],
220
+ additionalProperties: false,
221
+ properties: {
222
+ version: {
223
+ type: "string",
224
+ const: "1.0"
225
+ },
226
+ state: {
227
+ type: "object",
228
+ additionalProperties: {
229
+ $ref: "#/$defs/StateField"
230
+ }
231
+ },
232
+ actions: {
233
+ type: "array",
234
+ items: {
235
+ $ref: "#/$defs/ActionDefinition"
236
+ }
237
+ },
238
+ view: {
239
+ $ref: "#/$defs/ViewNode"
240
+ }
241
+ },
242
+ $defs: {
243
+ // ==================== Expressions ====================
244
+ Expression: {
245
+ oneOf: [
246
+ { $ref: "#/$defs/LitExpr" },
247
+ { $ref: "#/$defs/StateExpr" },
248
+ { $ref: "#/$defs/VarExpr" },
249
+ { $ref: "#/$defs/BinExpr" },
250
+ { $ref: "#/$defs/NotExpr" }
251
+ ]
252
+ },
253
+ LitExpr: {
254
+ type: "object",
255
+ required: ["expr", "value"],
256
+ additionalProperties: false,
257
+ properties: {
258
+ expr: { type: "string", const: "lit" },
259
+ value: {
260
+ oneOf: [
261
+ { type: "string" },
262
+ { type: "number" },
263
+ { type: "boolean" },
264
+ { type: "null" },
265
+ { type: "array" }
266
+ ]
267
+ }
268
+ }
269
+ },
270
+ StateExpr: {
271
+ type: "object",
272
+ required: ["expr", "name"],
273
+ additionalProperties: false,
274
+ properties: {
275
+ expr: { type: "string", const: "state" },
276
+ name: { type: "string" }
277
+ }
278
+ },
279
+ VarExpr: {
280
+ type: "object",
281
+ required: ["expr", "name"],
282
+ additionalProperties: false,
283
+ properties: {
284
+ expr: { type: "string", const: "var" },
285
+ name: { type: "string" },
286
+ path: { type: "string" }
287
+ }
288
+ },
289
+ BinExpr: {
290
+ type: "object",
291
+ required: ["expr", "op", "left", "right"],
292
+ additionalProperties: false,
293
+ properties: {
294
+ expr: { type: "string", const: "bin" },
295
+ op: {
296
+ type: "string",
297
+ enum: ["+", "-", "*", "/", "==", "!=", "<", "<=", ">", ">=", "&&", "||"]
298
+ },
299
+ left: { $ref: "#/$defs/Expression" },
300
+ right: { $ref: "#/$defs/Expression" }
301
+ }
302
+ },
303
+ NotExpr: {
304
+ type: "object",
305
+ required: ["expr", "operand"],
306
+ additionalProperties: false,
307
+ properties: {
308
+ expr: { type: "string", const: "not" },
309
+ operand: { $ref: "#/$defs/Expression" }
310
+ }
311
+ },
312
+ // ==================== State Fields ====================
313
+ StateField: {
314
+ oneOf: [
315
+ { $ref: "#/$defs/NumberField" },
316
+ { $ref: "#/$defs/StringField" },
317
+ { $ref: "#/$defs/ListField" }
318
+ ]
319
+ },
320
+ NumberField: {
321
+ type: "object",
322
+ required: ["type", "initial"],
323
+ additionalProperties: false,
324
+ properties: {
325
+ type: { type: "string", const: "number" },
326
+ initial: { type: "number" }
327
+ }
328
+ },
329
+ StringField: {
330
+ type: "object",
331
+ required: ["type", "initial"],
332
+ additionalProperties: false,
333
+ properties: {
334
+ type: { type: "string", const: "string" },
335
+ initial: { type: "string" }
336
+ }
337
+ },
338
+ ListField: {
339
+ type: "object",
340
+ required: ["type", "initial"],
341
+ additionalProperties: false,
342
+ properties: {
343
+ type: { type: "string", const: "list" },
344
+ initial: { type: "array" }
345
+ }
346
+ },
347
+ // ==================== Action Steps ====================
348
+ ActionStep: {
349
+ oneOf: [
350
+ { $ref: "#/$defs/SetStep" },
351
+ { $ref: "#/$defs/UpdateStep" },
352
+ { $ref: "#/$defs/FetchStep" }
353
+ ]
354
+ },
355
+ SetStep: {
356
+ type: "object",
357
+ required: ["do", "target", "value"],
358
+ additionalProperties: false,
359
+ properties: {
360
+ do: { type: "string", const: "set" },
361
+ target: { type: "string" },
362
+ value: { $ref: "#/$defs/Expression" }
363
+ }
364
+ },
365
+ UpdateStep: {
366
+ type: "object",
367
+ required: ["do", "target", "operation"],
368
+ additionalProperties: false,
369
+ properties: {
370
+ do: { type: "string", const: "update" },
371
+ target: { type: "string" },
372
+ operation: {
373
+ type: "string",
374
+ enum: ["increment", "decrement", "push", "pop", "remove"]
375
+ },
376
+ value: { $ref: "#/$defs/Expression" }
377
+ }
378
+ },
379
+ FetchStep: {
380
+ type: "object",
381
+ required: ["do", "url"],
382
+ additionalProperties: false,
383
+ properties: {
384
+ do: { type: "string", const: "fetch" },
385
+ url: { $ref: "#/$defs/Expression" },
386
+ method: {
387
+ type: "string",
388
+ enum: ["GET", "POST", "PUT", "DELETE"]
389
+ },
390
+ body: { $ref: "#/$defs/Expression" },
391
+ result: { type: "string" },
392
+ onSuccess: {
393
+ type: "array",
394
+ items: { $ref: "#/$defs/ActionStep" }
395
+ },
396
+ onError: {
397
+ type: "array",
398
+ items: { $ref: "#/$defs/ActionStep" }
399
+ }
400
+ }
401
+ },
402
+ // ==================== Event Handler ====================
403
+ EventHandler: {
404
+ type: "object",
405
+ required: ["event", "action"],
406
+ additionalProperties: false,
407
+ properties: {
408
+ event: { type: "string" },
409
+ action: { type: "string" },
410
+ payload: { $ref: "#/$defs/Expression" }
411
+ }
412
+ },
413
+ // ==================== Action Definition ====================
414
+ ActionDefinition: {
415
+ type: "object",
416
+ required: ["name", "steps"],
417
+ additionalProperties: false,
418
+ properties: {
419
+ name: { type: "string" },
420
+ steps: {
421
+ type: "array",
422
+ items: { $ref: "#/$defs/ActionStep" }
423
+ }
424
+ }
425
+ },
426
+ // ==================== View Nodes ====================
427
+ ViewNode: {
428
+ oneOf: [
429
+ { $ref: "#/$defs/ElementNode" },
430
+ { $ref: "#/$defs/TextNode" },
431
+ { $ref: "#/$defs/IfNode" },
432
+ { $ref: "#/$defs/EachNode" }
433
+ ]
434
+ },
435
+ ElementNode: {
436
+ type: "object",
437
+ required: ["kind", "tag"],
438
+ additionalProperties: false,
439
+ properties: {
440
+ kind: { type: "string", const: "element" },
441
+ tag: { type: "string" },
442
+ props: {
443
+ type: "object",
444
+ additionalProperties: {
445
+ oneOf: [
446
+ { $ref: "#/$defs/Expression" },
447
+ { $ref: "#/$defs/EventHandler" }
448
+ ]
449
+ }
450
+ },
451
+ children: {
452
+ type: "array",
453
+ items: { $ref: "#/$defs/ViewNode" }
454
+ }
455
+ }
456
+ },
457
+ TextNode: {
458
+ type: "object",
459
+ required: ["kind", "value"],
460
+ additionalProperties: false,
461
+ properties: {
462
+ kind: { type: "string", const: "text" },
463
+ value: { $ref: "#/$defs/Expression" }
464
+ }
465
+ },
466
+ IfNode: {
467
+ type: "object",
468
+ required: ["kind", "condition", "then"],
469
+ additionalProperties: false,
470
+ properties: {
471
+ kind: { type: "string", const: "if" },
472
+ condition: { $ref: "#/$defs/Expression" },
473
+ then: { $ref: "#/$defs/ViewNode" },
474
+ else: { $ref: "#/$defs/ViewNode" }
475
+ }
476
+ },
477
+ EachNode: {
478
+ type: "object",
479
+ required: ["kind", "items", "as", "body"],
480
+ additionalProperties: false,
481
+ properties: {
482
+ kind: { type: "string", const: "each" },
483
+ items: { $ref: "#/$defs/Expression" },
484
+ as: { type: "string" },
485
+ index: { type: "string" },
486
+ key: { $ref: "#/$defs/Expression" },
487
+ body: { $ref: "#/$defs/ViewNode" }
488
+ }
489
+ }
490
+ }
491
+ };
492
+
493
+ // src/schema/validator.ts
494
+ var ajv = new Ajv({
495
+ allErrors: true,
496
+ verbose: true,
497
+ strict: false
498
+ });
499
+ var validate = ajv.compile(astSchema);
500
+ function isObject2(value) {
501
+ return typeof value === "object" && value !== null && !Array.isArray(value);
502
+ }
503
+ var VALID_VIEW_KINDS = ["element", "text", "if", "each"];
504
+ var VALID_EXPR_TYPES = ["lit", "state", "var", "bin", "not"];
505
+ var VALID_ACTION_TYPES = ["set", "update", "fetch"];
506
+ var VALID_STATE_TYPES = ["number", "string", "list"];
507
+ var VALID_BIN_OPS = BINARY_OPERATORS;
508
+ var VALID_UPDATE_OPS = UPDATE_OPERATIONS;
509
+ var VALID_HTTP_METHODS = HTTP_METHODS;
510
+ function validateViewNode(node, path) {
511
+ if (!isObject2(node)) {
512
+ return { path, message: "must be an object" };
513
+ }
514
+ const kind = node["kind"];
515
+ if (typeof kind !== "string") {
516
+ return { path: path + "/kind", message: "kind is required" };
517
+ }
518
+ if (!VALID_VIEW_KINDS.includes(kind)) {
519
+ return { path: path + "/kind", message: "must be one of: element, text, if, each" };
520
+ }
521
+ switch (kind) {
522
+ case "element":
523
+ if (typeof node["tag"] !== "string") {
524
+ return { path: path + "/tag", message: "tag is required" };
525
+ }
526
+ if (node["props"] !== void 0 && isObject2(node["props"])) {
527
+ for (const [propName, propValue] of Object.entries(node["props"])) {
528
+ if (isObject2(propValue) && "event" in propValue) {
529
+ } else {
530
+ const error = validateExpression(propValue, path + "/props/" + propName);
531
+ if (error) return error;
532
+ }
533
+ }
534
+ }
535
+ if (Array.isArray(node["children"])) {
536
+ for (let i = 0; i < node["children"].length; i++) {
537
+ const error = validateViewNode(node["children"][i], path + "/children/" + i);
538
+ if (error) return error;
539
+ }
540
+ }
541
+ break;
542
+ case "text":
543
+ if (!("value" in node)) {
544
+ return { path: path + "/value", message: "value is required" };
545
+ }
546
+ return validateExpression(node["value"], path + "/value");
547
+ case "if":
548
+ if (!("condition" in node)) {
549
+ return { path: path + "/condition", message: "condition is required" };
550
+ }
551
+ if (!("then" in node)) {
552
+ return { path: path + "/then", message: "then is required" };
553
+ }
554
+ {
555
+ const condError = validateExpression(node["condition"], path + "/condition");
556
+ if (condError) return condError;
557
+ const thenError = validateViewNode(node["then"], path + "/then");
558
+ if (thenError) return thenError;
559
+ if ("else" in node) {
560
+ const elseError = validateViewNode(node["else"], path + "/else");
561
+ if (elseError) return elseError;
562
+ }
563
+ }
564
+ break;
565
+ case "each":
566
+ if (!("items" in node)) {
567
+ return { path: path + "/items", message: "items is required" };
568
+ }
569
+ if (typeof node["as"] !== "string") {
570
+ return { path: path + "/as", message: "as is required" };
571
+ }
572
+ if (!("body" in node)) {
573
+ return { path: path + "/body", message: "body is required" };
574
+ }
575
+ {
576
+ const itemsError = validateExpression(node["items"], path + "/items");
577
+ if (itemsError) return itemsError;
578
+ const bodyError = validateViewNode(node["body"], path + "/body");
579
+ if (bodyError) return bodyError;
580
+ }
581
+ break;
582
+ }
583
+ return null;
584
+ }
585
+ function validateExpression(expr, path) {
586
+ if (!isObject2(expr)) {
587
+ return { path, message: "must be an object" };
588
+ }
589
+ const exprType = expr["expr"];
590
+ if (typeof exprType !== "string") {
591
+ return { path: path + "/expr", message: "expr is required" };
592
+ }
593
+ if (!VALID_EXPR_TYPES.includes(exprType)) {
594
+ return { path: path + "/expr", message: "must be one of: lit, state, var, bin, not" };
595
+ }
596
+ switch (exprType) {
597
+ case "lit":
598
+ if (!("value" in expr)) {
599
+ return { path: path + "/value", message: "value is required" };
600
+ }
601
+ break;
602
+ case "state":
603
+ case "var":
604
+ if (typeof expr["name"] !== "string") {
605
+ return { path: path + "/name", message: "name is required" };
606
+ }
607
+ break;
608
+ case "bin":
609
+ if (!("op" in expr)) {
610
+ return { path: path + "/op", message: "op is required" };
611
+ }
612
+ if (!VALID_BIN_OPS.includes(expr["op"])) {
613
+ return { path: path + "/op", message: "must be a valid operator" };
614
+ }
615
+ if (!("left" in expr)) {
616
+ return { path: path + "/left", message: "left is required" };
617
+ }
618
+ if (!("right" in expr)) {
619
+ return { path: path + "/right", message: "right is required" };
620
+ }
621
+ {
622
+ const leftError = validateExpression(expr["left"], path + "/left");
623
+ if (leftError) return leftError;
624
+ const rightError = validateExpression(expr["right"], path + "/right");
625
+ if (rightError) return rightError;
626
+ }
627
+ break;
628
+ case "not":
629
+ if (!("operand" in expr)) {
630
+ return { path: path + "/operand", message: "operand is required" };
631
+ }
632
+ return validateExpression(expr["operand"], path + "/operand");
633
+ }
634
+ return null;
635
+ }
636
+ function validateActionStep(step, path) {
637
+ if (!isObject2(step)) {
638
+ return { path, message: "must be an object" };
639
+ }
640
+ const doType = step["do"];
641
+ if (typeof doType !== "string") {
642
+ return { path: path + "/do", message: "do is required" };
643
+ }
644
+ if (!VALID_ACTION_TYPES.includes(doType)) {
645
+ return { path: path + "/do", message: "must be one of: set, update, fetch" };
646
+ }
647
+ switch (doType) {
648
+ case "set":
649
+ if (typeof step["target"] !== "string") {
650
+ return { path: path + "/target", message: "target is required" };
651
+ }
652
+ if (!("value" in step)) {
653
+ return { path: path + "/value", message: "value is required" };
654
+ }
655
+ return validateExpression(step["value"], path + "/value");
656
+ case "update":
657
+ if (typeof step["target"] !== "string") {
658
+ return { path: path + "/target", message: "target is required" };
659
+ }
660
+ if (!("operation" in step)) {
661
+ return { path: path + "/operation", message: "operation is required" };
662
+ }
663
+ if (!VALID_UPDATE_OPS.includes(step["operation"])) {
664
+ return { path: path + "/operation", message: "must be a valid operation" };
665
+ }
666
+ if ("value" in step) {
667
+ return validateExpression(step["value"], path + "/value");
668
+ }
669
+ break;
670
+ case "fetch":
671
+ if (!("url" in step)) {
672
+ return { path: path + "/url", message: "url is required" };
673
+ }
674
+ {
675
+ const urlError = validateExpression(step["url"], path + "/url");
676
+ if (urlError) return urlError;
677
+ }
678
+ if ("method" in step) {
679
+ if (!VALID_HTTP_METHODS.includes(step["method"])) {
680
+ return { path: path + "/method", message: "must be a valid HTTP method" };
681
+ }
682
+ }
683
+ if ("body" in step) {
684
+ const bodyError = validateExpression(step["body"], path + "/body");
685
+ if (bodyError) return bodyError;
686
+ }
687
+ break;
688
+ }
689
+ return null;
690
+ }
691
+ function validateStateField(field, path) {
692
+ if (!isObject2(field)) {
693
+ return { path, message: "must be an object" };
694
+ }
695
+ const fieldType = field["type"];
696
+ if (typeof fieldType !== "string") {
697
+ return { path: path + "/type", message: "type is required" };
698
+ }
699
+ if (!VALID_STATE_TYPES.includes(fieldType)) {
700
+ return { path: path + "/type", message: "must be one of: number, string, list" };
701
+ }
702
+ if (!("initial" in field)) {
703
+ return { path: path + "/initial", message: "initial is required" };
704
+ }
705
+ switch (fieldType) {
706
+ case "number":
707
+ if (typeof field["initial"] !== "number") {
708
+ return { path: path + "/initial", message: "must be a number" };
709
+ }
710
+ break;
711
+ case "string":
712
+ if (typeof field["initial"] !== "string") {
713
+ return { path: path + "/initial", message: "must be a string" };
714
+ }
715
+ break;
716
+ case "list":
717
+ if (!Array.isArray(field["initial"])) {
718
+ return { path: path + "/initial", message: "must be an array" };
719
+ }
720
+ break;
721
+ }
722
+ return null;
723
+ }
724
+ function customValidateAst(input) {
725
+ if (isObject2(input["state"])) {
726
+ for (const [name, field] of Object.entries(input["state"])) {
727
+ const error = validateStateField(field, "/state/" + name);
728
+ if (error) return error;
729
+ }
730
+ }
731
+ if (Array.isArray(input["actions"])) {
732
+ for (let i = 0; i < input["actions"].length; i++) {
733
+ const action = input["actions"][i];
734
+ if (isObject2(action) && Array.isArray(action["steps"])) {
735
+ for (let j = 0; j < action["steps"].length; j++) {
736
+ const error = validateActionStep(action["steps"][j], "/actions/" + i + "/steps/" + j);
737
+ if (error) return error;
738
+ }
739
+ }
740
+ }
741
+ }
742
+ if ("view" in input) {
743
+ const error = validateViewNode(input["view"], "/view");
744
+ if (error) return error;
745
+ }
746
+ return null;
747
+ }
748
+ function findTopLevelRequiredError(errors) {
749
+ for (const error of errors) {
750
+ if (error.keyword === "required" && error.instancePath === "") {
751
+ const params = error.params;
752
+ if (typeof params["missingProperty"] === "string") {
753
+ return {
754
+ path: "/" + params["missingProperty"],
755
+ message: error.message ?? "Required field missing"
756
+ };
757
+ }
758
+ }
759
+ }
760
+ return null;
761
+ }
762
+ function collectSemanticContext(ast) {
763
+ const stateNames = /* @__PURE__ */ new Set();
764
+ const actionNames = /* @__PURE__ */ new Set();
765
+ if (isObject2(ast["state"])) {
766
+ for (const name of Object.keys(ast["state"])) {
767
+ stateNames.add(name);
768
+ }
769
+ }
770
+ if (Array.isArray(ast["actions"])) {
771
+ for (const action of ast["actions"]) {
772
+ if (isObject2(action) && typeof action["name"] === "string") {
773
+ actionNames.add(action["name"]);
774
+ }
775
+ }
776
+ }
777
+ return { stateNames, actionNames };
778
+ }
779
+ function validateStateReferences(node, path, stateNames) {
780
+ if (!isObject2(node)) return null;
781
+ if (node["expr"] === "state" && typeof node["name"] === "string") {
782
+ if (!stateNames.has(node["name"])) {
783
+ return createUndefinedStateError(node["name"], path + "/" + node["name"]);
784
+ }
785
+ }
786
+ for (const [key, value] of Object.entries(node)) {
787
+ if (isObject2(value)) {
788
+ const error = validateStateReferences(value, path + "/" + key, stateNames);
789
+ if (error) return error;
790
+ } else if (Array.isArray(value)) {
791
+ for (let i = 0; i < value.length; i++) {
792
+ const error = validateStateReferences(value[i], path + "/" + key + "/" + i, stateNames);
793
+ if (error) return error;
794
+ }
795
+ }
796
+ }
797
+ return null;
798
+ }
799
+ function validateActionTargets(ast, stateNames) {
800
+ if (!Array.isArray(ast["actions"])) return null;
801
+ for (let i = 0; i < ast["actions"].length; i++) {
802
+ const action = ast["actions"][i];
803
+ if (!isObject2(action) || !Array.isArray(action["steps"])) continue;
804
+ for (let j = 0; j < action["steps"].length; j++) {
805
+ const step = action["steps"][j];
806
+ if (!isObject2(step)) continue;
807
+ if ((step["do"] === "set" || step["do"] === "update") && typeof step["target"] === "string") {
808
+ if (!stateNames.has(step["target"])) {
809
+ return createUndefinedStateError(step["target"], "/actions/" + i + "/steps/" + j + "/target");
810
+ }
811
+ }
812
+ }
813
+ }
814
+ return null;
815
+ }
816
+ function validateActionReferences(node, path, actionNames) {
817
+ if (!isObject2(node)) return null;
818
+ if (isObject2(node["props"])) {
819
+ for (const [propName, propValue] of Object.entries(node["props"])) {
820
+ if (isObject2(propValue) && "event" in propValue && "action" in propValue) {
821
+ const actionName = propValue["action"];
822
+ if (typeof actionName === "string" && !actionNames.has(actionName)) {
823
+ return createUndefinedActionError(actionName, path + "/props/" + propName);
824
+ }
825
+ }
826
+ }
827
+ }
828
+ if (Array.isArray(node["children"])) {
829
+ for (let i = 0; i < node["children"].length; i++) {
830
+ const error = validateActionReferences(
831
+ node["children"][i],
832
+ path + "/children/" + i,
833
+ actionNames
834
+ );
835
+ if (error) return error;
836
+ }
837
+ }
838
+ if (isObject2(node["then"])) {
839
+ const error = validateActionReferences(node["then"], path + "/then", actionNames);
840
+ if (error) return error;
841
+ }
842
+ if (isObject2(node["else"])) {
843
+ const error = validateActionReferences(node["else"], path + "/else", actionNames);
844
+ if (error) return error;
845
+ }
846
+ if (isObject2(node["body"])) {
847
+ const error = validateActionReferences(node["body"], path + "/body", actionNames);
848
+ if (error) return error;
849
+ }
850
+ return null;
851
+ }
852
+ function validateDuplicateActions(ast) {
853
+ if (!Array.isArray(ast["actions"])) return null;
854
+ const seenNames = /* @__PURE__ */ new Set();
855
+ for (let i = 0; i < ast["actions"].length; i++) {
856
+ const action = ast["actions"][i];
857
+ if (!isObject2(action) || typeof action["name"] !== "string") continue;
858
+ const name = action["name"];
859
+ if (seenNames.has(name)) {
860
+ return createDuplicateActionError(name, "/actions/" + i);
861
+ }
862
+ seenNames.add(name);
863
+ }
864
+ return null;
865
+ }
866
+ function performSemanticValidation(ast) {
867
+ const { stateNames, actionNames } = collectSemanticContext(ast);
868
+ const duplicateError = validateDuplicateActions(ast);
869
+ if (duplicateError) return duplicateError;
870
+ if (isObject2(ast["view"])) {
871
+ const stateRefError = validateStateReferences(ast["view"], "/view", stateNames);
872
+ if (stateRefError) return stateRefError;
873
+ }
874
+ const actionTargetError = validateActionTargets(ast, stateNames);
875
+ if (actionTargetError) return actionTargetError;
876
+ if (isObject2(ast["view"])) {
877
+ const actionRefError = validateActionReferences(ast["view"], "/view", actionNames);
878
+ if (actionRefError) return actionRefError;
879
+ }
880
+ return null;
881
+ }
882
+ function validateAst(input) {
883
+ if (!isObject2(input)) {
884
+ return {
885
+ ok: false,
886
+ error: createSchemaError("Input must be an object", "/")
887
+ };
888
+ }
889
+ if ("version" in input && input["version"] !== "1.0") {
890
+ if (typeof input["version"] === "string") {
891
+ return {
892
+ ok: false,
893
+ error: createUnsupportedVersionError(input["version"])
894
+ };
895
+ }
896
+ }
897
+ const valid = validate(input);
898
+ if (!valid && validate.errors && validate.errors.length > 0) {
899
+ const topLevelError = findTopLevelRequiredError(validate.errors);
900
+ if (topLevelError) {
901
+ return {
902
+ ok: false,
903
+ error: createSchemaError(topLevelError.message, topLevelError.path)
904
+ };
905
+ }
906
+ const customError = customValidateAst(input);
907
+ if (customError) {
908
+ return {
909
+ ok: false,
910
+ error: createSchemaError(customError.message, customError.path)
911
+ };
912
+ }
913
+ const firstError = validate.errors[0];
914
+ if (firstError) {
915
+ return {
916
+ ok: false,
917
+ error: createSchemaError(
918
+ firstError.message ?? "Schema validation failed",
919
+ firstError.instancePath || "/"
920
+ )
921
+ };
922
+ }
923
+ }
924
+ if (!valid) {
925
+ return {
926
+ ok: false,
927
+ error: createSchemaError("Schema validation failed", "/")
928
+ };
929
+ }
930
+ const semanticError = performSemanticValidation(input);
931
+ if (semanticError) {
932
+ return {
933
+ ok: false,
934
+ error: semanticError
935
+ };
936
+ }
937
+ return {
938
+ ok: true,
939
+ ast: input
940
+ };
941
+ }
942
+ export {
943
+ BINARY_OPERATORS,
944
+ ConstelaError,
945
+ HTTP_METHODS,
946
+ UPDATE_OPERATIONS,
947
+ astSchema,
948
+ createDuplicateActionError,
949
+ createSchemaError,
950
+ createUndefinedActionError,
951
+ createUndefinedStateError,
952
+ createUndefinedVarError,
953
+ createUnsupportedVersionError,
954
+ isActionStep,
955
+ isBinExpr,
956
+ isConstelaError,
957
+ isEachNode,
958
+ isElementNode,
959
+ isEventHandler,
960
+ isExpression,
961
+ isFetchStep,
962
+ isIfNode,
963
+ isListField,
964
+ isLitExpr,
965
+ isNotExpr,
966
+ isNumberField,
967
+ isSetStep,
968
+ isStateExpr,
969
+ isStateField,
970
+ isStringField,
971
+ isTextNode,
972
+ isUpdateStep,
973
+ isVarExpr,
974
+ isViewNode,
975
+ validateAst
976
+ };