@praxisjs/fsm 1.0.12 → 2.0.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.
- package/CHANGELOG.md +37 -0
- package/dist/__tests__/decorators.test.js +47 -51
- package/dist/__tests__/decorators.test.js.map +1 -1
- package/dist/decorators.d.ts +2 -2
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +19 -28
- package/dist/decorators.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/decorators.test.ts +60 -51
- package/src/decorators.ts +21 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @praxisjs/fsm
|
|
2
2
|
|
|
3
|
+
## 2.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [378cc54]
|
|
8
|
+
- @praxisjs/core@1.7.0
|
|
9
|
+
- @praxisjs/decorators@1.1.0
|
|
10
|
+
|
|
11
|
+
## 2.0.0
|
|
12
|
+
|
|
13
|
+
### Major Changes
|
|
14
|
+
|
|
15
|
+
- c54d96b: `@StateMachine` converted from a class decorator to a field decorator.
|
|
16
|
+
|
|
17
|
+
Declare the machine as a typed field — no `declare`, no `propertyKey` second argument. The field name is the machine reference used in `@Transition`.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// before
|
|
21
|
+
@StateMachine({ initial: 'idle', states: { ... } }, 'machine')
|
|
22
|
+
@Component()
|
|
23
|
+
class Foo extends StatefulComponent {
|
|
24
|
+
declare machine: Machine<State, Event>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// after
|
|
28
|
+
@Component()
|
|
29
|
+
class Foo extends StatefulComponent {
|
|
30
|
+
@StateMachine({ initial: 'idle', states: { ... } })
|
|
31
|
+
machine!: Machine<State, Event>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [cfb0de2]
|
|
38
|
+
- @praxisjs/decorators@1.0.2
|
|
39
|
+
|
|
3
40
|
## 1.0.12
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
|
@@ -8,15 +8,27 @@ const TOGGLE_DEF = {
|
|
|
8
8
|
on: { on: { toggle: "off" } },
|
|
9
9
|
},
|
|
10
10
|
};
|
|
11
|
+
// Helper to simulate TC39 field decorator context
|
|
12
|
+
function fieldCtx(name) {
|
|
13
|
+
const initializers = [];
|
|
14
|
+
return {
|
|
15
|
+
ctx: {
|
|
16
|
+
name,
|
|
17
|
+
kind: "field",
|
|
18
|
+
addInitializer(fn) { initializers.push(fn); },
|
|
19
|
+
},
|
|
20
|
+
run(instance) {
|
|
21
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
11
25
|
// Helper to simulate TC39 method decorator context
|
|
12
26
|
function mockMethodContext(name) {
|
|
13
27
|
const initializers = [];
|
|
14
28
|
return {
|
|
15
29
|
name,
|
|
16
30
|
kind: "method",
|
|
17
|
-
addInitializer(fn) {
|
|
18
|
-
initializers.push(fn);
|
|
19
|
-
},
|
|
31
|
+
addInitializer(fn) { initializers.push(fn); },
|
|
20
32
|
runInitializers(instance) {
|
|
21
33
|
initializers.forEach((fn) => { fn.call(instance); });
|
|
22
34
|
},
|
|
@@ -24,38 +36,49 @@ function mockMethodContext(name) {
|
|
|
24
36
|
}
|
|
25
37
|
// ── StateMachine ──────────────────────────────────────────────────────────────
|
|
26
38
|
describe("StateMachine decorator", () => {
|
|
27
|
-
it("
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
39
|
+
it("injects a machine via field decorator", () => {
|
|
40
|
+
const { ctx, run } = fieldCtx("machine");
|
|
41
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
42
|
+
const instance = {};
|
|
43
|
+
run(instance);
|
|
32
44
|
expect(instance.machine).toBeDefined();
|
|
33
45
|
expect(instance.machine.state()).toBe("off");
|
|
34
46
|
});
|
|
35
47
|
it("returns a separate machine per instance", () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
48
|
+
const { ctx, run } = fieldCtx("machine");
|
|
49
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
50
|
+
const a = {};
|
|
51
|
+
const b = {};
|
|
52
|
+
run(a);
|
|
53
|
+
run(b);
|
|
41
54
|
a.machine.send("toggle");
|
|
42
55
|
expect(a.machine.state()).toBe("on");
|
|
43
56
|
expect(b.machine.state()).toBe("off");
|
|
44
57
|
});
|
|
45
|
-
it("
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
58
|
+
it("supports a custom field name", () => {
|
|
59
|
+
const { ctx, run } = fieldCtx("fsm");
|
|
60
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
61
|
+
const instance = {};
|
|
62
|
+
run(instance);
|
|
50
63
|
expect(instance.fsm).toBeDefined();
|
|
64
|
+
expect(instance.fsm.state()).toBe("off");
|
|
51
65
|
});
|
|
52
|
-
it("returns the same instance on repeated access", () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
66
|
+
it("returns the same machine instance on repeated access", () => {
|
|
67
|
+
const { ctx, run } = fieldCtx("machine");
|
|
68
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
69
|
+
const instance = {};
|
|
70
|
+
run(instance);
|
|
57
71
|
expect(instance.machine).toBe(instance.machine);
|
|
58
72
|
});
|
|
73
|
+
it("setter is a no-op — assigning to the field does not replace the machine", () => {
|
|
74
|
+
const { ctx, run } = fieldCtx("machine");
|
|
75
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
76
|
+
const instance = {};
|
|
77
|
+
run(instance);
|
|
78
|
+
const original = instance.machine;
|
|
79
|
+
instance.machine = "overwrite-attempt"; // triggers set(): void {} (no-op)
|
|
80
|
+
expect(instance.machine).toBe(original);
|
|
81
|
+
});
|
|
59
82
|
});
|
|
60
83
|
// ── Transition ────────────────────────────────────────────────────────────────
|
|
61
84
|
describe("Transition decorator", () => {
|
|
@@ -91,7 +114,6 @@ describe("Transition decorator", () => {
|
|
|
91
114
|
};
|
|
92
115
|
ctx.runInitializers(instance);
|
|
93
116
|
const method = instance.doTransition;
|
|
94
|
-
// "toggle" is not a valid event from "on" state
|
|
95
117
|
method();
|
|
96
118
|
expect(original).not.toHaveBeenCalled();
|
|
97
119
|
});
|
|
@@ -105,12 +127,11 @@ describe("Transition decorator", () => {
|
|
|
105
127
|
method(42);
|
|
106
128
|
expect(original).toHaveBeenCalledWith(42);
|
|
107
129
|
});
|
|
108
|
-
it("@Transition on method
|
|
130
|
+
it("@Transition on method without @StateMachine — warns and returns undefined", () => {
|
|
109
131
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
110
132
|
const original = vi.fn(() => "ran");
|
|
111
133
|
const ctx = mockMethodContext("doTransition");
|
|
112
134
|
Transition("machine", "toggle")(original, ctx);
|
|
113
|
-
// Instance has no "machine" property at all
|
|
114
135
|
const instance = {};
|
|
115
136
|
ctx.runInitializers(instance);
|
|
116
137
|
const method = instance.doTransition;
|
|
@@ -121,29 +142,4 @@ describe("Transition decorator", () => {
|
|
|
121
142
|
warn.mockRestore();
|
|
122
143
|
});
|
|
123
144
|
});
|
|
124
|
-
// ── StateMachine on subclass ───────────────────────────────────────────────────
|
|
125
|
-
describe("StateMachine decorator on subclass", () => {
|
|
126
|
-
it("machine is accessible on subclass instance", () => {
|
|
127
|
-
class Base {
|
|
128
|
-
}
|
|
129
|
-
class Child extends Base {
|
|
130
|
-
}
|
|
131
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {});
|
|
132
|
-
const instance = new Wrapped();
|
|
133
|
-
expect(instance.machine).toBeDefined();
|
|
134
|
-
expect(instance.machine.state()).toBe("off");
|
|
135
|
-
});
|
|
136
|
-
it("instances of the decorated subclass are isolated", () => {
|
|
137
|
-
class Base {
|
|
138
|
-
}
|
|
139
|
-
class Child extends Base {
|
|
140
|
-
}
|
|
141
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {});
|
|
142
|
-
const a = new Wrapped();
|
|
143
|
-
const b = new Wrapped();
|
|
144
|
-
a.machine.send("toggle");
|
|
145
|
-
expect(a.machine.state()).toBe("on");
|
|
146
|
-
expect(b.machine.state()).toBe("off");
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
145
|
//# sourceMappingURL=decorators.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAElD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"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"}
|
package/dist/decorators.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MachineDefinition } from "./machine";
|
|
1
|
+
import { type Machine, type MachineDefinition } from "./machine";
|
|
2
2
|
export declare function Transition(machineProp: string, event: string): (value: (...args: unknown[]) => unknown, context: ClassMethodDecoratorContext<any>) => void;
|
|
3
|
-
export declare function StateMachine<S extends string, E extends string>(definition: MachineDefinition<S, E
|
|
3
|
+
export declare function StateMachine<S extends string, E extends string>(definition: MachineDefinition<S, E>): <T extends object>(_value: undefined, context: ClassFieldDecoratorContext<T, Machine<S, E>>) => void;
|
|
4
4
|
//# sourceMappingURL=decorators.d.ts.map
|
package/dist/decorators.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,OAAO,EAAE,KAAK,iBAAiB,EAAG,MAAM,WAAW,CAAC;AAEjF,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAiB5B,CAC7B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAEtC,OAAO,EAAE,2BAA2B,CAAC,GAAG,CAAC,KACtC,IAAI,CACV;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC7D,UAAU,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,IAIlB,CAAC,SAAS,MAAM,EAC/B,QAAQ,SAAS,EACjB,SAAS,0BAA0B,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KACpD,IAAI,CAgBR"}
|
package/dist/decorators.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMethodDecorator,
|
|
1
|
+
import { createMethodDecorator, } from "@praxisjs/decorators";
|
|
2
2
|
import { createMachine } from "./machine";
|
|
3
3
|
export function Transition(machineProp, event) {
|
|
4
4
|
const decorator = createMethodDecorator({
|
|
@@ -17,33 +17,24 @@ export function Transition(machineProp, event) {
|
|
|
17
17
|
// FSM decorators work on any class, not just StatefulComponent
|
|
18
18
|
return decorator;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return machines.get(this);
|
|
38
|
-
},
|
|
39
|
-
enumerable: true,
|
|
40
|
-
configurable: true,
|
|
20
|
+
export function StateMachine(definition) {
|
|
21
|
+
const machines = new WeakMap();
|
|
22
|
+
return function (_value, context) {
|
|
23
|
+
context.addInitializer(function () {
|
|
24
|
+
const name = context.name;
|
|
25
|
+
Object.defineProperty(this, name, {
|
|
26
|
+
get() {
|
|
27
|
+
const self = this;
|
|
28
|
+
if (!machines.has(self))
|
|
29
|
+
machines.set(self, createMachine(definition));
|
|
30
|
+
return machines.get(self);
|
|
31
|
+
},
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
33
|
+
set() { },
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
});
|
|
41
37
|
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
export function StateMachine(definition, propertyKey = "machine") {
|
|
45
|
-
const decorator = createClassDecorator(new StateMachineBehavior(definition, propertyKey));
|
|
46
|
-
// FSM decorators work on any class, not just RootComponent
|
|
47
|
-
return decorator;
|
|
38
|
+
};
|
|
48
39
|
}
|
|
49
40
|
//# sourceMappingURL=decorators.js.map
|
package/dist/decorators.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAyC,MAAM,WAAW,CAAC;AAEjF,MAAM,UAAU,UAAU,CAAC,WAAmB,EAAE,KAAa;IAC3D,MAAM,SAAS,GAAG,qBAAqB,CAAC;QACtC,IAAI,CAAC,QAAQ,EAAE,QAAQ;YACrB,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,MAAM,OAAO,GAAI,QAAoC,CAAC,WAAW,CAEpD,CAAC;gBACd,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,CAAC,IAAI,CAAC,iBAAiB,WAAW,2BAA2B,CAAC,CAAC;oBACtE,OAAO;gBACT,CAAC;gBACD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;oBACrB,OAAO,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAY,CAAC;YACrD,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IACH,+DAA+D;IAC/D,OAAO,SAIE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAmC;IAEnC,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAyB,CAAC;IAEtD,OAAO,UACL,MAAiB,EACjB,OAAqD;QAErD,OAAO,CAAC,cAAc,CAAC;YACrB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAC1B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE;gBAChC,GAAG;oBACD,MAAM,IAAI,GAAG,IAAc,CAAC;oBAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;wBAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;oBACvE,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAkB,CAAC;gBAC7C,CAAC;gBACD,gEAAgE;gBAChE,GAAG,KAAU,CAAC;gBACd,UAAU,EAAE,IAAI;gBAChB,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/fsm",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"typescript": "^5.9.3"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@praxisjs/core": "1.
|
|
18
|
-
"@praxisjs/
|
|
19
|
-
"@praxisjs/
|
|
17
|
+
"@praxisjs/core": "1.7.0",
|
|
18
|
+
"@praxisjs/decorators": "1.1.0",
|
|
19
|
+
"@praxisjs/shared": "0.2.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "tsc",
|
|
@@ -2,24 +2,38 @@ import { describe, it, expect, vi } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
import { Transition, StateMachine } from "../decorators";
|
|
4
4
|
import { createMachine } from "../machine";
|
|
5
|
+
import type { Machine } from "../machine";
|
|
5
6
|
|
|
6
7
|
const TOGGLE_DEF = {
|
|
7
8
|
initial: "off" as const,
|
|
8
9
|
states: {
|
|
9
10
|
off: { on: { toggle: "on" as const } },
|
|
10
|
-
on:
|
|
11
|
+
on: { on: { toggle: "off" as const } },
|
|
11
12
|
},
|
|
12
13
|
} as const;
|
|
13
14
|
|
|
15
|
+
// Helper to simulate TC39 field decorator context
|
|
16
|
+
function fieldCtx(name: string) {
|
|
17
|
+
const initializers: Array<(this: object) => void> = [];
|
|
18
|
+
return {
|
|
19
|
+
ctx: {
|
|
20
|
+
name,
|
|
21
|
+
kind: "field" as const,
|
|
22
|
+
addInitializer(fn: (this: object) => void) { initializers.push(fn); },
|
|
23
|
+
} as ClassFieldDecoratorContext,
|
|
24
|
+
run(instance: object) {
|
|
25
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
// Helper to simulate TC39 method decorator context
|
|
15
31
|
function mockMethodContext(name: string) {
|
|
16
32
|
const initializers: Array<(this: object) => void> = [];
|
|
17
33
|
return {
|
|
18
34
|
name,
|
|
19
35
|
kind: "method" as const,
|
|
20
|
-
addInitializer(fn: (this: object) => void) {
|
|
21
|
-
initializers.push(fn);
|
|
22
|
-
},
|
|
36
|
+
addInitializer(fn: (this: object) => void) { initializers.push(fn); },
|
|
23
37
|
runInitializers(instance: object) {
|
|
24
38
|
initializers.forEach((fn) => { fn.call(instance); });
|
|
25
39
|
},
|
|
@@ -29,37 +43,58 @@ function mockMethodContext(name: string) {
|
|
|
29
43
|
// ── StateMachine ──────────────────────────────────────────────────────────────
|
|
30
44
|
|
|
31
45
|
describe("StateMachine decorator", () => {
|
|
32
|
-
it("
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
it("injects a machine via field decorator", () => {
|
|
47
|
+
const { ctx, run } = fieldCtx("machine");
|
|
48
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
49
|
+
|
|
50
|
+
const instance: Record<string, unknown> = {};
|
|
51
|
+
run(instance);
|
|
36
52
|
expect(instance.machine).toBeDefined();
|
|
37
|
-
expect((instance.machine as
|
|
53
|
+
expect((instance.machine as Machine<string, string>).state()).toBe("off");
|
|
38
54
|
});
|
|
39
55
|
|
|
40
56
|
it("returns a separate machine per instance", () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
const { ctx, run } = fieldCtx("machine");
|
|
58
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
59
|
+
|
|
60
|
+
const a: Record<string, unknown> = {};
|
|
61
|
+
const b: Record<string, unknown> = {};
|
|
62
|
+
run(a);
|
|
63
|
+
run(b);
|
|
64
|
+
(a.machine as Machine<string, string>).send("toggle");
|
|
65
|
+
expect((a.machine as Machine<string, string>).state()).toBe("on");
|
|
66
|
+
expect((b.machine as Machine<string, string>).state()).toBe("off");
|
|
48
67
|
});
|
|
49
68
|
|
|
50
|
-
it("
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
it("supports a custom field name", () => {
|
|
70
|
+
const { ctx, run } = fieldCtx("fsm");
|
|
71
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
72
|
+
|
|
73
|
+
const instance: Record<string, unknown> = {};
|
|
74
|
+
run(instance);
|
|
54
75
|
expect(instance.fsm).toBeDefined();
|
|
76
|
+
expect((instance.fsm as Machine<string, string>).state()).toBe("off");
|
|
55
77
|
});
|
|
56
78
|
|
|
57
|
-
it("returns the same instance on repeated access", () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
it("returns the same machine instance on repeated access", () => {
|
|
80
|
+
const { ctx, run } = fieldCtx("machine");
|
|
81
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
82
|
+
|
|
83
|
+
const instance: Record<string, unknown> = {};
|
|
84
|
+
run(instance);
|
|
61
85
|
expect(instance.machine).toBe(instance.machine);
|
|
62
86
|
});
|
|
87
|
+
|
|
88
|
+
it("setter is a no-op — assigning to the field does not replace the machine", () => {
|
|
89
|
+
const { ctx, run } = fieldCtx("machine");
|
|
90
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
91
|
+
|
|
92
|
+
const instance: Record<string, unknown> = {};
|
|
93
|
+
run(instance);
|
|
94
|
+
const original = instance.machine;
|
|
95
|
+
instance.machine = "overwrite-attempt"; // triggers set(): void {} (no-op)
|
|
96
|
+
expect(instance.machine).toBe(original);
|
|
97
|
+
});
|
|
63
98
|
});
|
|
64
99
|
|
|
65
100
|
// ── Transition ────────────────────────────────────────────────────────────────
|
|
@@ -105,7 +140,6 @@ describe("Transition decorator", () => {
|
|
|
105
140
|
ctx.runInitializers(instance);
|
|
106
141
|
const method = (instance as Record<string, () => unknown>).doTransition;
|
|
107
142
|
|
|
108
|
-
// "toggle" is not a valid event from "on" state
|
|
109
143
|
method();
|
|
110
144
|
expect(original).not.toHaveBeenCalled();
|
|
111
145
|
});
|
|
@@ -123,13 +157,12 @@ describe("Transition decorator", () => {
|
|
|
123
157
|
expect(original).toHaveBeenCalledWith(42);
|
|
124
158
|
});
|
|
125
159
|
|
|
126
|
-
it("@Transition on method
|
|
160
|
+
it("@Transition on method without @StateMachine — warns and returns undefined", () => {
|
|
127
161
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
128
162
|
const original = vi.fn(() => "ran");
|
|
129
163
|
const ctx = mockMethodContext("doTransition");
|
|
130
164
|
Transition("machine", "toggle")(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
131
165
|
|
|
132
|
-
// Instance has no "machine" property at all
|
|
133
166
|
const instance = {} as Record<string, unknown>;
|
|
134
167
|
ctx.runInitializers(instance);
|
|
135
168
|
const method = (instance as Record<string, () => unknown>).doTransition;
|
|
@@ -141,27 +174,3 @@ describe("Transition decorator", () => {
|
|
|
141
174
|
warn.mockRestore();
|
|
142
175
|
});
|
|
143
176
|
});
|
|
144
|
-
|
|
145
|
-
// ── StateMachine on subclass ───────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
describe("StateMachine decorator on subclass", () => {
|
|
148
|
-
it("machine is accessible on subclass instance", () => {
|
|
149
|
-
class Base {}
|
|
150
|
-
class Child extends Base {}
|
|
151
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {} as ClassDecoratorContext) as unknown as typeof Child;
|
|
152
|
-
const instance = new Wrapped() as Record<string, unknown>;
|
|
153
|
-
expect(instance.machine).toBeDefined();
|
|
154
|
-
expect((instance.machine as ReturnType<typeof createMachine>).state()).toBe("off");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("instances of the decorated subclass are isolated", () => {
|
|
158
|
-
class Base {}
|
|
159
|
-
class Child extends Base {}
|
|
160
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {} as ClassDecoratorContext) as unknown as typeof Child;
|
|
161
|
-
const a = new Wrapped() as Record<string, unknown>;
|
|
162
|
-
const b = new Wrapped() as Record<string, unknown>;
|
|
163
|
-
(a.machine as ReturnType<typeof createMachine>).send("toggle");
|
|
164
|
-
expect((a.machine as ReturnType<typeof createMachine>).state()).toBe("on");
|
|
165
|
-
expect((b.machine as ReturnType<typeof createMachine>).state()).toBe("off");
|
|
166
|
-
});
|
|
167
|
-
});
|
package/src/decorators.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import type { RootComponent } from "@praxisjs/core/internal";
|
|
2
1
|
import {
|
|
3
2
|
createMethodDecorator,
|
|
4
|
-
createClassDecorator,
|
|
5
|
-
ClassBehavior,
|
|
6
|
-
type ClassEnhancement,
|
|
7
3
|
} from "@praxisjs/decorators";
|
|
8
4
|
|
|
9
|
-
import { createMachine, type Machine, type MachineDefinition
|
|
5
|
+
import { createMachine, type Machine, type MachineDefinition } from "./machine";
|
|
10
6
|
|
|
11
7
|
export function Transition(machineProp: string, event: string) {
|
|
12
8
|
const decorator = createMethodDecorator({
|
|
@@ -32,52 +28,28 @@ export function Transition(machineProp: string, event: string) {
|
|
|
32
28
|
) => void;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
class StateMachineBehavior<
|
|
36
|
-
S extends string,
|
|
37
|
-
E extends string,
|
|
38
|
-
> extends ClassBehavior {
|
|
39
|
-
constructor(
|
|
40
|
-
private readonly definition: MachineDefinition<S, E>,
|
|
41
|
-
private readonly propertyKey: string,
|
|
42
|
-
) {
|
|
43
|
-
super();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
create(_instance: RootComponent): ClassEnhancement {
|
|
47
|
-
return {};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
-
initialize(Enhanced: new (...args: any[]) => unknown): void {
|
|
52
|
-
const machines = new WeakMap<object, Machine<S, E>>();
|
|
53
|
-
const { definition, propertyKey } = this;
|
|
54
|
-
Object.defineProperty(
|
|
55
|
-
(Enhanced as { prototype: object }).prototype,
|
|
56
|
-
propertyKey,
|
|
57
|
-
{
|
|
58
|
-
get(this: object): Machine<S, E> {
|
|
59
|
-
if (!machines.has(this))
|
|
60
|
-
machines.set(this, createMachine(definition));
|
|
61
|
-
return machines.get(this) as Machine<S, E>;
|
|
62
|
-
},
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: true,
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
31
|
export function StateMachine<S extends string, E extends string>(
|
|
71
32
|
definition: MachineDefinition<S, E>,
|
|
72
|
-
propertyKey = "machine",
|
|
73
33
|
) {
|
|
74
|
-
const
|
|
75
|
-
new StateMachineBehavior(definition, propertyKey),
|
|
76
|
-
);
|
|
77
|
-
// FSM decorators work on any class, not just RootComponent
|
|
34
|
+
const machines = new WeakMap<object, Machine<S, E>>();
|
|
78
35
|
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
context:
|
|
82
|
-
)
|
|
36
|
+
return function <T extends object>(
|
|
37
|
+
_value: undefined,
|
|
38
|
+
context: ClassFieldDecoratorContext<T, Machine<S, E>>,
|
|
39
|
+
): void {
|
|
40
|
+
context.addInitializer(function (this: object) {
|
|
41
|
+
const name = context.name;
|
|
42
|
+
Object.defineProperty(this, name, {
|
|
43
|
+
get(): Machine<S, E> {
|
|
44
|
+
const self = this as object;
|
|
45
|
+
if (!machines.has(self)) machines.set(self, createMachine(definition));
|
|
46
|
+
return machines.get(self) as Machine<S, E>;
|
|
47
|
+
},
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
49
|
+
set(): void {},
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
};
|
|
83
55
|
}
|