Skip to content

Commit adb9230

Browse files
Remove handler tree depth ceiling
1 parent fc08038 commit adb9230

19 files changed

Lines changed: 2445 additions & 352 deletions

.changeset/deep-handlers-infer.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@typeonce/effect-machine": patch
3+
---
4+
5+
Remove the library-owned eight-level handler-tree inference ceiling. Nested
6+
handler validation and accumulated state, error, service, choice, history, and
7+
output evidence now continue until TypeScript's normal compiler limits.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ const Counter = Machine.make({
6666
})
6767
```
6868

69+
Handler objects mirror the state definition recursively. `effect-machine` does
70+
not impose a fixed handler-tree depth; inference continues until TypeScript's
71+
normal, shape-dependent compiler limits.
72+
6973
`initial` is always a function. For a machine with an input schema, the
7074
initializer receives the decoded input.
7175

docs/agent-guide.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -791,10 +791,11 @@ error defaults to `unknown`. Atoms created with an `AtomRuntime<R, E>` include
791791
`E` in their startup error type. Use `ChildOf<ParentAtom, Child>` to infer that
792792
exact channel from a parent instead of restating it manually.
793793

794-
### Handler tree is too deeply nested
794+
### Handler tree reaches a compiler instantiation limit
795795

796-
Type inference traverses at most eight nested handler objects. Split or flatten
797-
a deeper statechart instead of casting away the diagnostic.
796+
`effect-machine` does not impose a fixed handler-tree depth. Inference follows
797+
the nested handler object until TypeScript reaches its normal, shape-dependent
798+
compiler resource or instantiation limits.
798799

799800
## Unsupported and intentionally imperative features
800801

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Machine } from "@typeonce/effect-machine"
2+
import { Schema } from "effect"
3+
4+
export const NodeState = Schema.TaggedStruct("Node", {})
5+
6+
export const States = Machine.defineStates({
7+
n0: {
8+
schema: NodeState,
9+
initial: "n1",
10+
states: {
11+
n1: {
12+
schema: NodeState,
13+
initial: "n2",
14+
states: {
15+
n2: {
16+
schema: NodeState,
17+
initial: "n3",
18+
states: {
19+
n3: {
20+
schema: NodeState,
21+
initial: "n4",
22+
states: {
23+
n4: {
24+
schema: NodeState,
25+
initial: "n5",
26+
states: {
27+
n5: {
28+
schema: NodeState,
29+
initial: "n6",
30+
states: {
31+
n6: {
32+
schema: NodeState,
33+
initial: "n7",
34+
states: {
35+
n7: {
36+
schema: NodeState,
37+
initial: "n8",
38+
states: {
39+
n8: {
40+
schema: NodeState,
41+
initial: "n9",
42+
states: {
43+
n9: {
44+
schema: NodeState,
45+
initial: "n10",
46+
states: {
47+
n10: {
48+
schema: NodeState,
49+
initial: "n11",
50+
states: {
51+
n11: {
52+
schema: NodeState,
53+
initial: "n12",
54+
states: {
55+
n12: {
56+
schema: NodeState,
57+
type: "final",
58+
output: Schema.String
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}
84+
})
85+
86+
export const machine = Machine.make({
87+
states: States.states,
88+
events: [],
89+
initial: (): never => {
90+
throw new Error("type-performance fixture")
91+
}
92+
})

perf/types/handle-depth-12.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Machine } from "@typeonce/effect-machine"
2+
import { Context, Data, Effect } from "effect"
3+
import { machine } from "./handle-depth-12-control.js"
4+
5+
type Equal<Left, Right> = (<Type>() => Type extends Left ? 1 : 2) extends (<Type>() => Type extends Right ? 1 : 2) ?
6+
true :
7+
false
8+
type Expect<Value extends true> = Value
9+
10+
class DeepService extends Context.Service<DeepService, string>()("perf/depth-12/DeepService") {}
11+
class DeepFailure extends Data.TaggedError("DeepFailure")<{}> {}
12+
13+
const handled = machine.handle({
14+
n0: {
15+
states: {
16+
n1: {
17+
states: {
18+
n2: {
19+
states: {
20+
n3: {
21+
states: {
22+
n4: {
23+
states: {
24+
n5: {
25+
states: {
26+
n6: {
27+
states: {
28+
n7: {
29+
states: {
30+
n8: {
31+
states: {
32+
n9: {
33+
states: {
34+
n10: {
35+
states: {
36+
n11: {
37+
states: {
38+
n12: {
39+
entry: () =>
40+
Effect.flatMap(DeepService, () => Effect.fail(new DeepFailure())),
41+
output: () => "done"
42+
}
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
}
55+
}
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
67+
})
68+
69+
type ErrorIsExact = Expect<Equal<Machine.Machine.Error<typeof handled>, DeepFailure>>
70+
type ServicesAreExact = Expect<Equal<Machine.Machine.Services<typeof handled>, DeepService>>
71+
type EveryStateIsHandled = Expect<Equal<Machine.Machine.UnhandledStates<typeof handled>, never>>
72+
73+
void Machine.planInitial(handled)
74+
export type { ErrorIsExact, EveryStateIsHandled, ServicesAreExact }
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { Machine } from "@typeonce/effect-machine"
2+
import { Schema } from "effect"
3+
4+
export const NodeState = Schema.TaggedStruct("Node", {})
5+
6+
export const States = Machine.defineStates({
7+
n0: {
8+
schema: NodeState,
9+
initial: "n1",
10+
states: {
11+
n1: {
12+
schema: NodeState,
13+
initial: "n2",
14+
states: {
15+
n2: {
16+
schema: NodeState,
17+
initial: "n3",
18+
states: {
19+
n3: {
20+
schema: NodeState,
21+
initial: "n4",
22+
states: {
23+
n4: {
24+
schema: NodeState,
25+
initial: "n5",
26+
states: {
27+
n5: {
28+
schema: NodeState,
29+
initial: "n6",
30+
states: {
31+
n6: {
32+
schema: NodeState,
33+
initial: "n7",
34+
states: {
35+
n7: {
36+
schema: NodeState,
37+
initial: "n8",
38+
states: {
39+
n8: {
40+
schema: NodeState,
41+
initial: "n9",
42+
states: {
43+
n9: {
44+
schema: NodeState,
45+
initial: "n10",
46+
states: {
47+
n10: {
48+
schema: NodeState,
49+
initial: "n11",
50+
states: {
51+
n11: {
52+
schema: NodeState,
53+
initial: "n12",
54+
states: {
55+
n12: {
56+
schema: NodeState,
57+
initial: "n13",
58+
states: {
59+
n13: {
60+
schema: NodeState,
61+
initial: "n14",
62+
states: {
63+
n14: {
64+
schema: NodeState,
65+
initial: "n15",
66+
states: {
67+
n15: {
68+
schema: NodeState,
69+
initial: "n16",
70+
states: {
71+
n16: {
72+
schema: NodeState,
73+
type: "final",
74+
output: Schema.String
75+
}
76+
}
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}
108+
})
109+
110+
export const machine = Machine.make({
111+
states: States.states,
112+
events: [],
113+
initial: (): never => {
114+
throw new Error("type-performance fixture")
115+
}
116+
})

0 commit comments

Comments
 (0)