@constela/compiler 0.9.1 → 0.11.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 CHANGED
@@ -50,6 +50,8 @@ The compiler transforms JSON programs through three passes:
50
50
  - **setPath** - Compiles to efficient path-based state updates
51
51
  - **Key-based each** - Compiles key expressions for list diffing
52
52
  - **WebSocket connections** - Compiles connection definitions and send/close actions
53
+ - **concat expression** - Compiles string concatenation expressions
54
+ - **Object payloads** - Supports object-shaped event handler payloads with expression fields
53
55
 
54
56
  ## CompiledProgram Structure
55
57
 
package/dist/index.d.ts CHANGED
@@ -89,7 +89,7 @@ interface CompiledAction {
89
89
  name: string;
90
90
  steps: CompiledActionStep[];
91
91
  }
92
- type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledSetPathStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep | CompiledDomStep | CompiledIfStep | CompiledSendStep | CompiledCloseStep;
92
+ type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledSetPathStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep | CompiledDomStep | CompiledIfStep | CompiledSendStep | CompiledCloseStep | CompiledDelayStep | CompiledIntervalStep | CompiledClearTimerStep | CompiledFocusStep;
93
93
  interface CompiledSetStep {
94
94
  do: 'set';
95
95
  target: string;
@@ -217,7 +217,61 @@ interface CompiledCloseStep {
217
217
  do: 'close';
218
218
  connection: string;
219
219
  }
220
- type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode | CompiledSlotNode;
220
+ /**
221
+ * Compiled delay step - executes steps after a delay (setTimeout equivalent)
222
+ */
223
+ interface CompiledDelayStep {
224
+ do: 'delay';
225
+ ms: CompiledExpression;
226
+ then: CompiledActionStep[];
227
+ result?: string;
228
+ }
229
+ /**
230
+ * Compiled interval step - executes an action repeatedly (setInterval equivalent)
231
+ */
232
+ interface CompiledIntervalStep {
233
+ do: 'interval';
234
+ ms: CompiledExpression;
235
+ action: string;
236
+ result?: string;
237
+ }
238
+ /**
239
+ * Compiled clearTimer step - clears a timer (clearTimeout/clearInterval equivalent)
240
+ */
241
+ interface CompiledClearTimerStep {
242
+ do: 'clearTimer';
243
+ target: CompiledExpression;
244
+ }
245
+ /**
246
+ * Compiled focus step - manages form element focus
247
+ */
248
+ interface CompiledFocusStep {
249
+ do: 'focus';
250
+ target: CompiledExpression;
251
+ operation: 'focus' | 'blur' | 'select';
252
+ onSuccess?: CompiledActionStep[];
253
+ onError?: CompiledActionStep[];
254
+ }
255
+ /**
256
+ * Compiled local action
257
+ */
258
+ interface CompiledLocalAction {
259
+ name: string;
260
+ steps: CompiledActionStep[];
261
+ }
262
+ /**
263
+ * Compiled local state node - wraps component with local state
264
+ */
265
+ interface CompiledLocalStateNode {
266
+ kind: 'localState';
267
+ state: Record<string, {
268
+ type: string;
269
+ initial: unknown;
270
+ }>;
271
+ actions: Record<string, CompiledLocalAction>;
272
+ child: CompiledNode;
273
+ }
274
+ type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode | CompiledSlotNode | CompiledPortalNode | CompiledLocalStateNode;
221
275
  interface CompiledElementNode {
222
276
  kind: 'element';
223
277
  tag: string;
@@ -256,7 +310,12 @@ interface CompiledSlotNode {
256
310
  kind: 'slot';
257
311
  name?: string;
258
312
  }
259
- type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr | CompiledStyleExpr | CompiledConcatExpr;
313
+ interface CompiledPortalNode {
314
+ kind: 'portal';
315
+ target: 'body' | 'head' | string;
316
+ children: CompiledNode[];
317
+ }
318
+ type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr | CompiledStyleExpr | CompiledConcatExpr | CompiledValidityExpr;
260
319
  interface CompiledLitExpr {
261
320
  expr: 'lit';
262
321
  value: string | number | boolean | null | unknown[];
@@ -326,10 +385,26 @@ interface CompiledConcatExpr {
326
385
  expr: 'concat';
327
386
  items: CompiledExpression[];
328
387
  }
388
+ interface CompiledValidityExpr {
389
+ expr: 'validity';
390
+ ref: string;
391
+ property?: string;
392
+ }
393
+ /**
394
+ * Compiled event handler options for special events like intersect
395
+ */
396
+ interface CompiledEventHandlerOptions {
397
+ threshold?: number;
398
+ rootMargin?: string;
399
+ once?: boolean;
400
+ }
329
401
  interface CompiledEventHandler {
330
402
  event: string;
331
403
  action: string;
332
404
  payload?: CompiledExpression | Record<string, CompiledExpression>;
405
+ debounce?: number;
406
+ throttle?: number;
407
+ options?: CompiledEventHandlerOptions;
333
408
  }
334
409
  /**
335
410
  * Transforms the validated and analyzed AST into a CompiledProgram
@@ -459,4 +534,4 @@ declare function transformLayoutPass(layout: LayoutProgram, _context: LayoutAnal
459
534
  */
460
535
  declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, layoutParams?: Record<string, Expression>, slots?: Record<string, ViewNode>): CompiledProgram;
461
536
 
462
- export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledBinExpr, type CompiledCallStep, type CompiledClipboardStep, type CompiledCloseStep, type CompiledCodeNode, type CompiledConcatExpr, type CompiledCondExpr, type CompiledDataExpr, type CompiledDisposeStep, type CompiledDomStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledGetExpr, type CompiledIfNode, type CompiledIfStep, type CompiledImportExpr, type CompiledImportStep, type CompiledIndexExpr, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledLitExpr, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledNotExpr, type CompiledParamExpr, type CompiledProgram, type CompiledRefExpr, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSendStep, type CompiledSetPathStep, type CompiledSetStep, type CompiledSlotNode, type CompiledStateExpr, type CompiledStorageStep, type CompiledStyleExpr, type CompiledSubscribeStep, type CompiledTextNode, type CompiledUpdateStep, type CompiledVarExpr, type LayoutAnalysisContext, type LayoutAnalysisFailure, type LayoutAnalysisResult, type LayoutAnalysisSuccess, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzeLayoutPass, analyzePass, compile, composeLayoutWithPage, transformLayoutPass, transformPass, validatePass };
537
+ export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledBinExpr, type CompiledCallStep, type CompiledClearTimerStep, type CompiledClipboardStep, type CompiledCloseStep, type CompiledCodeNode, type CompiledConcatExpr, type CompiledCondExpr, type CompiledDataExpr, type CompiledDelayStep, type CompiledDisposeStep, type CompiledDomStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledEventHandlerOptions, type CompiledExpression, type CompiledFetchStep, type CompiledFocusStep, type CompiledGetExpr, type CompiledIfNode, type CompiledIfStep, type CompiledImportExpr, type CompiledImportStep, type CompiledIndexExpr, type CompiledIntervalStep, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledLitExpr, type CompiledLocalAction, type CompiledLocalStateNode, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledNotExpr, type CompiledParamExpr, type CompiledPortalNode, type CompiledProgram, type CompiledRefExpr, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSendStep, type CompiledSetPathStep, type CompiledSetStep, type CompiledSlotNode, type CompiledStateExpr, type CompiledStorageStep, type CompiledStyleExpr, type CompiledSubscribeStep, type CompiledTextNode, type CompiledUpdateStep, type CompiledValidityExpr, type CompiledVarExpr, type LayoutAnalysisContext, type LayoutAnalysisFailure, type LayoutAnalysisResult, type LayoutAnalysisSuccess, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzeLayoutPass, analyzePass, compile, composeLayoutWithPage, transformLayoutPass, transformPass, validatePass };
package/dist/index.js CHANGED
@@ -44,6 +44,8 @@ import {
44
44
  createUndefinedRefError,
45
45
  createUndefinedStyleError,
46
46
  createUndefinedVariantError,
47
+ createUndefinedLocalStateError,
48
+ createLocalActionInvalidStepError,
47
49
  findSimilarNames,
48
50
  isEventHandler,
49
51
  DATA_SOURCE_TYPES,
@@ -157,12 +159,19 @@ function checkDuplicateActions(ast2) {
157
159
  function validateExpression(expr, path, context, scope, paramScope) {
158
160
  const errors = [];
159
161
  switch (expr.expr) {
160
- case "state":
161
- if (!context.stateNames.has(expr.name)) {
162
- const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
162
+ case "state": {
163
+ const isGlobalState = context.stateNames.has(expr.name);
164
+ const isLocalState = paramScope?.localStateNames?.has(expr.name) ?? false;
165
+ if (!isGlobalState && !isLocalState) {
166
+ const availableNames = /* @__PURE__ */ new Set([
167
+ ...context.stateNames,
168
+ ...paramScope?.localStateNames ?? []
169
+ ]);
170
+ const errorOptions = createErrorOptionsWithSuggestion(expr.name, availableNames);
163
171
  errors.push(createUndefinedStateError(expr.name, path, errorOptions));
164
172
  }
165
173
  break;
174
+ }
166
175
  case "var":
167
176
  if (!scope.has(expr.name)) {
168
177
  errors.push(createUndefinedVarError(expr.name, path));
@@ -706,8 +715,14 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
706
715
  for (const [propName, propValue] of Object.entries(node.props)) {
707
716
  const propPath = buildPath(path, "props", propName);
708
717
  if (isEventHandler(propValue)) {
709
- if (!context.actionNames.has(propValue.action)) {
710
- const errorOptions = createErrorOptionsWithSuggestion(propValue.action, context.actionNames);
718
+ const isGlobalAction = context.actionNames.has(propValue.action);
719
+ const isLocalAction = paramScope?.localActionNames?.has(propValue.action) ?? false;
720
+ if (!isGlobalAction && !isLocalAction) {
721
+ const availableNames = /* @__PURE__ */ new Set([
722
+ ...context.actionNames,
723
+ ...paramScope?.localActionNames ?? []
724
+ ]);
725
+ const errorOptions = createErrorOptionsWithSuggestion(propValue.action, availableNames);
711
726
  errors.push(createUndefinedActionError(propValue.action, propPath, errorOptions));
712
727
  }
713
728
  if (propValue.payload) {
@@ -910,21 +925,58 @@ function detectComponentCycles(programAst, context) {
910
925
  }
911
926
  return errors;
912
927
  }
928
+ function validateLocalActions(localActions, localStateNames, componentPath) {
929
+ const errors = [];
930
+ for (let i = 0; i < localActions.length; i++) {
931
+ const action = localActions[i];
932
+ if (!action) continue;
933
+ const actionPath = buildPath(componentPath, "localActions", i);
934
+ for (let j = 0; j < action.steps.length; j++) {
935
+ const step = action.steps[j];
936
+ if (!step) continue;
937
+ const stepPath = buildPath(actionPath, "steps", j);
938
+ const stepDo = step.do;
939
+ if (stepDo !== "set" && stepDo !== "update" && stepDo !== "setPath") {
940
+ errors.push(createLocalActionInvalidStepError(stepDo, stepPath));
941
+ continue;
942
+ }
943
+ if (!localStateNames.has(step.target)) {
944
+ const errorOptions = createErrorOptionsWithSuggestion(step.target, localStateNames);
945
+ errors.push(createUndefinedLocalStateError(step.target, buildPath(stepPath, "target"), errorOptions));
946
+ }
947
+ }
948
+ }
949
+ return errors;
950
+ }
913
951
  function validateComponents(programAst, context) {
914
952
  const errors = [];
915
953
  if (!programAst.components) return errors;
916
954
  for (const [name, def] of Object.entries(programAst.components)) {
955
+ const componentPath = buildPath("", "components", name);
956
+ const localStateNames = new Set(
957
+ def.localState ? Object.keys(def.localState) : []
958
+ );
959
+ const localActionNames = new Set(
960
+ def.localActions ? def.localActions.map((a) => a.name) : []
961
+ );
917
962
  const paramNames = new Set(
918
963
  def.params ? Object.keys(def.params) : []
919
964
  );
920
965
  const paramScope = {
921
966
  params: paramNames,
922
- componentName: name
967
+ componentName: name,
968
+ localStateNames,
969
+ localActionNames
923
970
  };
971
+ if (def.localActions && def.localActions.length > 0) {
972
+ errors.push(
973
+ ...validateLocalActions(def.localActions, localStateNames, componentPath)
974
+ );
975
+ }
924
976
  errors.push(
925
977
  ...validateViewNode(
926
978
  def.view,
927
- buildPath("", "components", name, "view"),
979
+ buildPath(componentPath, "view"),
928
980
  context,
929
981
  /* @__PURE__ */ new Set(),
930
982
  { insideComponent: true, paramScope }
@@ -1183,6 +1235,16 @@ function transformExpression(expr, ctx) {
1183
1235
  expr: "concat",
1184
1236
  items: expr.items.map((item) => transformExpression(item, ctx))
1185
1237
  };
1238
+ case "validity": {
1239
+ const validityExpr = {
1240
+ expr: "validity",
1241
+ ref: expr.ref
1242
+ };
1243
+ if (expr.property) {
1244
+ validityExpr.property = expr.property;
1245
+ }
1246
+ return validityExpr;
1247
+ }
1186
1248
  }
1187
1249
  }
1188
1250
  function transformEventHandler(handler, ctx) {
@@ -1193,6 +1255,24 @@ function transformEventHandler(handler, ctx) {
1193
1255
  if (handler.payload) {
1194
1256
  result.payload = transformExpression(handler.payload, ctx);
1195
1257
  }
1258
+ if (handler.debounce !== void 0) {
1259
+ result.debounce = handler.debounce;
1260
+ }
1261
+ if (handler.throttle !== void 0) {
1262
+ result.throttle = handler.throttle;
1263
+ }
1264
+ if (handler.options) {
1265
+ result.options = {};
1266
+ if (handler.options.threshold !== void 0) {
1267
+ result.options.threshold = handler.options.threshold;
1268
+ }
1269
+ if (handler.options.rootMargin !== void 0) {
1270
+ result.options.rootMargin = handler.options.rootMargin;
1271
+ }
1272
+ if (handler.options.once !== void 0) {
1273
+ result.options.once = handler.options.once;
1274
+ }
1275
+ }
1196
1276
  return result;
1197
1277
  }
1198
1278
  var emptyContext = { components: {} };
@@ -1384,6 +1464,52 @@ function transformActionStep(step) {
1384
1464
  connection: closeStep.connection
1385
1465
  };
1386
1466
  }
1467
+ case "delay": {
1468
+ const delayStep = step;
1469
+ const compiledDelayStep = {
1470
+ do: "delay",
1471
+ ms: transformExpression(delayStep.ms, emptyContext),
1472
+ then: delayStep.then.map(transformActionStep)
1473
+ };
1474
+ if (delayStep.result) {
1475
+ compiledDelayStep.result = delayStep.result;
1476
+ }
1477
+ return compiledDelayStep;
1478
+ }
1479
+ case "interval": {
1480
+ const intervalStep = step;
1481
+ const compiledIntervalStep = {
1482
+ do: "interval",
1483
+ ms: transformExpression(intervalStep.ms, emptyContext),
1484
+ action: intervalStep.action
1485
+ };
1486
+ if (intervalStep.result) {
1487
+ compiledIntervalStep.result = intervalStep.result;
1488
+ }
1489
+ return compiledIntervalStep;
1490
+ }
1491
+ case "clearTimer": {
1492
+ const clearTimerStep = step;
1493
+ return {
1494
+ do: "clearTimer",
1495
+ target: transformExpression(clearTimerStep.target, emptyContext)
1496
+ };
1497
+ }
1498
+ case "focus": {
1499
+ const focusStep = step;
1500
+ const compiledFocusStep = {
1501
+ do: "focus",
1502
+ target: transformExpression(focusStep.target, emptyContext),
1503
+ operation: focusStep.operation
1504
+ };
1505
+ if (focusStep.onSuccess) {
1506
+ compiledFocusStep.onSuccess = focusStep.onSuccess.map(transformActionStep);
1507
+ }
1508
+ if (focusStep.onError) {
1509
+ compiledFocusStep.onError = focusStep.onError.map(transformActionStep);
1510
+ }
1511
+ return compiledFocusStep;
1512
+ }
1387
1513
  }
1388
1514
  }
1389
1515
  function flattenSlotChildren(children, ctx) {
@@ -1480,7 +1606,16 @@ function transformViewNode(node, ctx) {
1480
1606
  currentParams: params,
1481
1607
  currentChildren: children
1482
1608
  };
1483
- return transformViewNode(def.view, newCtx);
1609
+ const expandedView = transformViewNode(def.view, newCtx);
1610
+ if (def.localState && Object.keys(def.localState).length > 0) {
1611
+ return {
1612
+ kind: "localState",
1613
+ state: transformLocalState(def.localState),
1614
+ actions: transformLocalActions(def.localActions ?? []),
1615
+ child: expandedView
1616
+ };
1617
+ }
1618
+ return expandedView;
1484
1619
  }
1485
1620
  case "markdown":
1486
1621
  return {
@@ -1507,6 +1642,18 @@ function transformViewNode(node, ctx) {
1507
1642
  }
1508
1643
  return { kind: "text", value: { expr: "lit", value: "" } };
1509
1644
  }
1645
+ case "portal": {
1646
+ const portalNode = node;
1647
+ const compiledChildren = [];
1648
+ for (const child of portalNode.children) {
1649
+ compiledChildren.push(transformViewNode(child, ctx));
1650
+ }
1651
+ return {
1652
+ kind: "portal",
1653
+ target: portalNode.target,
1654
+ children: compiledChildren
1655
+ };
1656
+ }
1510
1657
  }
1511
1658
  }
1512
1659
  function transformState(state) {
@@ -1519,6 +1666,23 @@ function transformState(state) {
1519
1666
  }
1520
1667
  return compiledState;
1521
1668
  }
1669
+ function transformLocalState(localState) {
1670
+ const result = {};
1671
+ for (const [name, field] of Object.entries(localState)) {
1672
+ result[name] = { type: field.type, initial: field.initial };
1673
+ }
1674
+ return result;
1675
+ }
1676
+ function transformLocalActions(localActions) {
1677
+ const result = {};
1678
+ for (const action of localActions) {
1679
+ result[action.name] = {
1680
+ name: action.name,
1681
+ steps: action.steps.map(transformActionStep)
1682
+ };
1683
+ }
1684
+ return result;
1685
+ }
1522
1686
  function transformActions(actions) {
1523
1687
  const compiledActions = {};
1524
1688
  for (const action of actions) {
@@ -2176,6 +2340,14 @@ function transformViewNode2(node, ctx) {
2176
2340
  language: node.language,
2177
2341
  content: node.content
2178
2342
  };
2343
+ case "portal":
2344
+ return {
2345
+ kind: "portal",
2346
+ target: node.target,
2347
+ children: (node.children ?? []).map(
2348
+ (child) => transformViewNode2(child, ctx)
2349
+ )
2350
+ };
2179
2351
  }
2180
2352
  }
2181
2353
  function transformLayoutPass(layout, _context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/compiler",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "Compiler for Constela UI framework - AST to Program transformation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@constela/core": "0.9.1"
18
+ "@constela/core": "0.11.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.10.0",