@unlaxer/tramli 1.2.0 → 1.2.1

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.
@@ -47,6 +47,8 @@ export declare class Builder<S extends string> {
47
47
  private checkRequiresProducesFrom;
48
48
  private checkAutoExternalConflict;
49
49
  private checkTerminalNoOutgoing;
50
+ private checkSubFlowNestingDepth;
51
+ private checkSubFlowCircularRef;
50
52
  private checkSubFlowExitCompleteness;
51
53
  }
52
54
  export declare class FromBuilder<S extends string> {
@@ -119,6 +119,8 @@ export class Builder {
119
119
  this.checkAutoExternalConflict(def, errors);
120
120
  this.checkTerminalNoOutgoing(def, errors);
121
121
  this.checkSubFlowExitCompleteness(def, errors);
122
+ this.checkSubFlowNestingDepth(def, errors, 0);
123
+ this.checkSubFlowCircularRef(def, errors, new Set());
122
124
  if (errors.length > 0) {
123
125
  throw new FlowError('INVALID_FLOW_DEFINITION', `Flow '${this.name}' has ${errors.length} validation error(s):\n - ${errors.join('\n - ')}`);
124
126
  }
@@ -322,6 +324,29 @@ export class Builder {
322
324
  }
323
325
  }
324
326
  }
327
+ checkSubFlowNestingDepth(def, errors, depth) {
328
+ if (depth > 3) {
329
+ errors.push(`SubFlow nesting depth exceeds maximum of 3 (flow: ${def.name})`);
330
+ return;
331
+ }
332
+ for (const t of def.transitions) {
333
+ if (t.type === 'sub_flow' && t.subFlowDefinition) {
334
+ this.checkSubFlowNestingDepth(t.subFlowDefinition, errors, depth + 1);
335
+ }
336
+ }
337
+ }
338
+ checkSubFlowCircularRef(def, errors, visited) {
339
+ if (visited.has(def.name)) {
340
+ errors.push(`Circular sub-flow reference detected: ${[...visited].join(' -> ')} -> ${def.name}`);
341
+ return;
342
+ }
343
+ visited.add(def.name);
344
+ for (const t of def.transitions) {
345
+ if (t.type === 'sub_flow' && t.subFlowDefinition) {
346
+ this.checkSubFlowCircularRef(t.subFlowDefinition, errors, new Set(visited));
347
+ }
348
+ }
349
+ }
325
350
  checkSubFlowExitCompleteness(def, errors) {
326
351
  for (const t of def.transitions) {
327
352
  if (t.type !== 'sub_flow' || !t.subFlowDefinition)
@@ -163,6 +163,9 @@ export class FlowEngine {
163
163
  this.store.recordTransition(parentFlow.id, from, target, `subFlow:${subDef.name}/${subFlow.exitState}`, parentFlow.context);
164
164
  return 1;
165
165
  }
166
+ // Error bubbling: no exit mapping → fall back to parent's error transitions
167
+ this.handleError(parentFlow, parentFlow.currentState);
168
+ return 1;
166
169
  }
167
170
  return 0; // sub-flow stopped at external
168
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlaxer/tramli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Constrained flow engine — state machines that prevent invalid transitions at build time",
5
5
  "type": "module",
6
6
  "exports": {