@praxisjs/fsm 0.2.3 → 1.0.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/CHANGELOG.md +49 -0
- package/dist/__tests__/decorators.test.js +45 -20
- 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 +36 -15
- package/dist/decorators.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/decorators.test.ts +54 -32
- package/src/decorators.ts +50 -20
- package/src/index.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @praxisjs/fsm
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 3372878: Migrate all packages from functional APIs to a decorator-first design.
|
|
8
|
+
|
|
9
|
+
**`@praxisjs/core`**
|
|
10
|
+
|
|
11
|
+
- Added `Composable` abstract base class for building class-based composables
|
|
12
|
+
- Removed `resource`, `createResource`, `Resource`, `ResourceStatus`, `ResourceOptions` from public exports — use `@Resource` from `@praxisjs/decorators` instead
|
|
13
|
+
|
|
14
|
+
**`@praxisjs/motion`**
|
|
15
|
+
|
|
16
|
+
- Replaced `useMotion`, `tween`, `spring`, `createTransition`, `Animate`, `easings`, `resolveEasing` with `@Tween` and `@Spring` decorators
|
|
17
|
+
|
|
18
|
+
**`@praxisjs/di`**
|
|
19
|
+
|
|
20
|
+
- Replaced `useService` and `createScope` with a `@Scope` decorator
|
|
21
|
+
- Renamed exported type `Scope` to `ScopeType` to free the name for the new decorator
|
|
22
|
+
|
|
23
|
+
**`@praxisjs/fsm`**
|
|
24
|
+
|
|
25
|
+
- Removed `createMachine` — use the `@StateMachine` and `@Transition` decorators directly
|
|
26
|
+
|
|
27
|
+
**`@praxisjs/router`**
|
|
28
|
+
|
|
29
|
+
- Removed `createRouter`, `lazy`, `useRouter`, `useParams`, `useQuery`, `useLocation`
|
|
30
|
+
- Added `@RouterConfig`, `@Lazy`, `@InjectRouter`, `@Params`, `@Query`, `@Location` decorators
|
|
31
|
+
|
|
32
|
+
**`@praxisjs/store`**
|
|
33
|
+
|
|
34
|
+
- Removed `createStore` — use the `@Store` and `@UseStore` decorators directly
|
|
35
|
+
|
|
36
|
+
**`@praxisjs/composables`**
|
|
37
|
+
|
|
38
|
+
- Replaced all `use*` composable functions with class-based composables extending `Composable`:
|
|
39
|
+
`WindowSize`, `ScrollPosition`, `ElementSize`, `Intersection`, `Focus`, `MediaQuery`, `ColorScheme`, `Mouse`, `KeyCombo`, `Idle`, `Clipboard`, `Geolocation`, `TimeAgo`, `Pagination`
|
|
40
|
+
|
|
41
|
+
**`@praxisjs/concurrent`**
|
|
42
|
+
|
|
43
|
+
- Removed `task`, `queue`, `pool` and their instance types — use `@Task`, `@Queue`, `@Pool` decorators instead
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- Updated dependencies [3372878]
|
|
48
|
+
- Updated dependencies [feaa478]
|
|
49
|
+
- @praxisjs/core@1.0.0
|
|
50
|
+
- @praxisjs/decorators@0.5.0
|
|
51
|
+
|
|
3
52
|
## 0.2.3
|
|
4
53
|
|
|
5
54
|
### Patch Changes
|
|
@@ -8,22 +8,36 @@ const TOGGLE_DEF = {
|
|
|
8
8
|
on: { on: { toggle: "off" } },
|
|
9
9
|
},
|
|
10
10
|
};
|
|
11
|
+
// Helper to simulate TC39 method decorator context
|
|
12
|
+
function mockMethodContext(name) {
|
|
13
|
+
const initializers = [];
|
|
14
|
+
return {
|
|
15
|
+
name,
|
|
16
|
+
kind: "method",
|
|
17
|
+
addInitializer(fn) {
|
|
18
|
+
initializers.push(fn);
|
|
19
|
+
},
|
|
20
|
+
runInitializers(instance) {
|
|
21
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
11
25
|
// ── StateMachine ──────────────────────────────────────────────────────────────
|
|
12
26
|
describe("StateMachine decorator", () => {
|
|
13
27
|
it("adds a machine property via prototype", () => {
|
|
14
28
|
class Light {
|
|
15
29
|
}
|
|
16
|
-
StateMachine(TOGGLE_DEF)(Light, {});
|
|
17
|
-
const instance = new
|
|
30
|
+
const Wrapped = StateMachine(TOGGLE_DEF)(Light, {});
|
|
31
|
+
const instance = new Wrapped();
|
|
18
32
|
expect(instance.machine).toBeDefined();
|
|
19
33
|
expect(instance.machine.state()).toBe("off");
|
|
20
34
|
});
|
|
21
35
|
it("returns a separate machine per instance", () => {
|
|
22
36
|
class Bulb {
|
|
23
37
|
}
|
|
24
|
-
StateMachine(TOGGLE_DEF)(Bulb, {});
|
|
25
|
-
const a = new
|
|
26
|
-
const b = new
|
|
38
|
+
const Wrapped = StateMachine(TOGGLE_DEF)(Bulb, {});
|
|
39
|
+
const a = new Wrapped();
|
|
40
|
+
const b = new Wrapped();
|
|
27
41
|
a.machine.send("toggle");
|
|
28
42
|
expect(a.machine.state()).toBe("on");
|
|
29
43
|
expect(b.machine.state()).toBe("off");
|
|
@@ -31,15 +45,15 @@ describe("StateMachine decorator", () => {
|
|
|
31
45
|
it("uses a custom property key", () => {
|
|
32
46
|
class Widget {
|
|
33
47
|
}
|
|
34
|
-
StateMachine(TOGGLE_DEF, "fsm")(Widget, {});
|
|
35
|
-
const instance = new
|
|
48
|
+
const Wrapped = StateMachine(TOGGLE_DEF, "fsm")(Widget, {});
|
|
49
|
+
const instance = new Wrapped();
|
|
36
50
|
expect(instance.fsm).toBeDefined();
|
|
37
51
|
});
|
|
38
52
|
it("returns the same instance on repeated access", () => {
|
|
39
53
|
class Btn {
|
|
40
54
|
}
|
|
41
|
-
StateMachine(TOGGLE_DEF)(Btn, {});
|
|
42
|
-
const instance = new
|
|
55
|
+
const Wrapped = StateMachine(TOGGLE_DEF)(Btn, {});
|
|
56
|
+
const instance = new Wrapped();
|
|
43
57
|
expect(instance.machine).toBe(instance.machine);
|
|
44
58
|
});
|
|
45
59
|
});
|
|
@@ -47,37 +61,48 @@ describe("StateMachine decorator", () => {
|
|
|
47
61
|
describe("Transition decorator", () => {
|
|
48
62
|
it("calls the original method when send succeeds", () => {
|
|
49
63
|
const original = vi.fn(() => "ran");
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
const ctx = mockMethodContext("doTransition");
|
|
65
|
+
Transition("machine", "toggle")(original, ctx);
|
|
66
|
+
const instance = { machine: createMachine(TOGGLE_DEF) };
|
|
67
|
+
ctx.runInitializers(instance);
|
|
68
|
+
const method = instance.doTransition;
|
|
69
|
+
method();
|
|
55
70
|
expect(original).toHaveBeenCalled();
|
|
56
71
|
});
|
|
57
72
|
it("returns without calling method when machine is missing", () => {
|
|
58
73
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
59
74
|
const original = vi.fn();
|
|
60
|
-
const
|
|
61
|
-
|
|
75
|
+
const ctx = mockMethodContext("doTransition");
|
|
76
|
+
Transition("machine", "toggle")(original, ctx);
|
|
77
|
+
const instance = {};
|
|
78
|
+
ctx.runInitializers(instance);
|
|
79
|
+
const method = instance.doTransition;
|
|
80
|
+
method();
|
|
62
81
|
expect(original).not.toHaveBeenCalled();
|
|
63
82
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining("[Transition]"));
|
|
64
83
|
warn.mockRestore();
|
|
65
84
|
});
|
|
66
85
|
it("does not call method when transition is invalid", () => {
|
|
67
86
|
const original = vi.fn();
|
|
68
|
-
const
|
|
87
|
+
const ctx = mockMethodContext("doTransition");
|
|
88
|
+
Transition("machine", "toggle")(original, ctx);
|
|
69
89
|
const instance = {
|
|
70
90
|
machine: createMachine({ initial: "on", states: { on: {} } }),
|
|
71
91
|
};
|
|
92
|
+
ctx.runInitializers(instance);
|
|
93
|
+
const method = instance.doTransition;
|
|
72
94
|
// "toggle" is not a valid event from "on" state
|
|
73
|
-
|
|
95
|
+
method();
|
|
74
96
|
expect(original).not.toHaveBeenCalled();
|
|
75
97
|
});
|
|
76
98
|
it("passes through arguments to the original method", () => {
|
|
77
99
|
const original = vi.fn((_x) => undefined);
|
|
78
|
-
const
|
|
100
|
+
const ctx = mockMethodContext("doTransition");
|
|
101
|
+
Transition("machine", "toggle")(original, ctx);
|
|
79
102
|
const instance = { machine: createMachine(TOGGLE_DEF) };
|
|
80
|
-
|
|
103
|
+
ctx.runInitializers(instance);
|
|
104
|
+
const method = instance.doTransition;
|
|
105
|
+
method(42);
|
|
81
106
|
expect(original).toHaveBeenCalledWith(42);
|
|
82
107
|
});
|
|
83
108
|
});
|
|
@@ -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;AAE3C,MAAM,UAAU,GAAG;IACjB,OAAO,EAAE,KAAc;IACvB,MAAM,EAAE;QACN,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAa,EAAE,EAAE;QACtC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAc,EAAE,EAAE;KACvC;CACO,CAAC;AAEX,iFAAiF;AAEjF,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,KAAK;SAAG;QACd,YAAY,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAA2B,
|
|
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
1
|
import { type MachineDefinition } from "./machine";
|
|
2
|
-
export declare function Transition(machineProp: string, event: string): (value: (...args: unknown[]) => unknown,
|
|
3
|
-
export declare function StateMachine<S extends string, E extends string>(definition: MachineDefinition<S, E>, propertyKey?: string): (value: abstract new (...args: unknown[]) => unknown,
|
|
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>, propertyKey?: string): (value: abstract new (...args: unknown[]) => unknown, context: ClassDecoratorContext) => abstract new (...args: unknown[]) => unknown;
|
|
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":"AAQA,OAAO,EAA+B,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEhF,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAe5B,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,2BAA2B,CAAC,GAAG,CAAC,KAAK,IAAI,CAC3H;AA6BD,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC7D,UAAU,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,EACnC,WAAW,SAAY,GAKQ,CAAC,KAAK,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,qBAAqB,KAAK,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CACrK"}
|
package/dist/decorators.js
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
|
+
import { createMethodDecorator, createClassDecorator, ClassBehavior, } from "@praxisjs/decorators";
|
|
1
2
|
import { createMachine } from "./machine";
|
|
2
3
|
export function Transition(machineProp, event) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
const decorator = createMethodDecorator({
|
|
5
|
+
wrap(original, instance) {
|
|
6
|
+
return (...args) => {
|
|
7
|
+
const machine = instance[machineProp];
|
|
8
|
+
if (!machine) {
|
|
9
|
+
console.warn(`[Transition] "${machineProp}" is not a state machine.`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (machine.send(event))
|
|
13
|
+
return original.apply(instance, args);
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
// FSM decorators work on any class, not just StatefulComponent
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
return decorator;
|
|
14
20
|
}
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
class StateMachineBehavior extends ClassBehavior {
|
|
22
|
+
constructor(definition, propertyKey) {
|
|
23
|
+
super();
|
|
24
|
+
this.definition = definition;
|
|
25
|
+
this.propertyKey = propertyKey;
|
|
26
|
+
}
|
|
27
|
+
create(_instance) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
initialize(Enhanced) {
|
|
17
32
|
const machines = new WeakMap();
|
|
18
|
-
|
|
33
|
+
const { definition, propertyKey } = this;
|
|
34
|
+
Object.defineProperty(Enhanced.prototype, propertyKey, {
|
|
19
35
|
get() {
|
|
20
36
|
if (!machines.has(this))
|
|
21
37
|
machines.set(this, createMachine(definition));
|
|
@@ -24,6 +40,11 @@ export function StateMachine(definition, propertyKey = "machine") {
|
|
|
24
40
|
enumerable: true,
|
|
25
41
|
configurable: true,
|
|
26
42
|
});
|
|
27
|
-
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function StateMachine(definition, propertyKey = "machine") {
|
|
46
|
+
const decorator = createClassDecorator(new StateMachineBehavior(definition, propertyKey));
|
|
47
|
+
// FSM decorators work on any class, not just RootComponent
|
|
48
|
+
return decorator;
|
|
28
49
|
}
|
|
29
50
|
//# 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":"AACA,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,GAEd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAwC,MAAM,WAAW,CAAC;AAEhF,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,CAAwC,CAAC;gBAC1G,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;oBAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACjE,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IACH,+DAA+D;IAC/D,8DAA8D;IAC9D,OAAO,SAAmH,CAAC;AAC7H,CAAC;AAED,MAAM,oBAAyD,SAAQ,aAAa;IAClF,YACmB,UAAmC,EACnC,WAAmB;QAEpC,KAAK,EAAE,CAAC;QAHS,eAAU,GAAV,UAAU,CAAyB;QACnC,gBAAW,GAAX,WAAW,CAAQ;IAGtC,CAAC;IAED,MAAM,CAAC,SAAwB;QAC7B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,8DAA8D;IAC9D,UAAU,CAAC,QAAyC;QAClD,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAyB,CAAC;QACtD,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;QACzC,MAAM,CAAC,cAAc,CAAE,QAAkC,CAAC,SAAS,EAAE,WAAW,EAAE;YAChF,GAAG;gBACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;gBACvE,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAkB,CAAC;YAC7C,CAAC;YACD,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAC1B,UAAmC,EACnC,WAAW,GAAG,SAAS;IAEvB,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,oBAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1F,2DAA2D;IAE3D,OAAO,SAA6J,CAAC;AACvK,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/fsm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"typescript": "^5.9.3"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@praxisjs/core": "0.
|
|
17
|
+
"@praxisjs/core": "1.0.0",
|
|
18
|
+
"@praxisjs/decorators": "0.5.0",
|
|
18
19
|
"@praxisjs/shared": "0.2.0"
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
@@ -11,22 +11,37 @@ const TOGGLE_DEF = {
|
|
|
11
11
|
},
|
|
12
12
|
} as const;
|
|
13
13
|
|
|
14
|
+
// Helper to simulate TC39 method decorator context
|
|
15
|
+
function mockMethodContext(name: string) {
|
|
16
|
+
const initializers: Array<(this: object) => void> = [];
|
|
17
|
+
return {
|
|
18
|
+
name,
|
|
19
|
+
kind: "method" as const,
|
|
20
|
+
addInitializer(fn: (this: object) => void) {
|
|
21
|
+
initializers.push(fn);
|
|
22
|
+
},
|
|
23
|
+
runInitializers(instance: object) {
|
|
24
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
// ── StateMachine ──────────────────────────────────────────────────────────────
|
|
15
30
|
|
|
16
31
|
describe("StateMachine decorator", () => {
|
|
17
32
|
it("adds a machine property via prototype", () => {
|
|
18
33
|
class Light {}
|
|
19
|
-
StateMachine(TOGGLE_DEF)(Light, {} as ClassDecoratorContext);
|
|
20
|
-
const instance = new
|
|
34
|
+
const Wrapped = StateMachine(TOGGLE_DEF)(Light, {} as ClassDecoratorContext) as unknown as typeof Light;
|
|
35
|
+
const instance = new Wrapped() as Record<string, unknown>;
|
|
21
36
|
expect(instance.machine).toBeDefined();
|
|
22
37
|
expect((instance.machine as ReturnType<typeof createMachine>).state()).toBe("off");
|
|
23
38
|
});
|
|
24
39
|
|
|
25
40
|
it("returns a separate machine per instance", () => {
|
|
26
41
|
class Bulb {}
|
|
27
|
-
StateMachine(TOGGLE_DEF)(Bulb, {} as ClassDecoratorContext);
|
|
28
|
-
const a = new
|
|
29
|
-
const b = new
|
|
42
|
+
const Wrapped = StateMachine(TOGGLE_DEF)(Bulb, {} as ClassDecoratorContext) as unknown as typeof Bulb;
|
|
43
|
+
const a = new Wrapped() as Record<string, unknown>;
|
|
44
|
+
const b = new Wrapped() as Record<string, unknown>;
|
|
30
45
|
(a.machine as ReturnType<typeof createMachine>).send("toggle");
|
|
31
46
|
expect((a.machine as ReturnType<typeof createMachine>).state()).toBe("on");
|
|
32
47
|
expect((b.machine as ReturnType<typeof createMachine>).state()).toBe("off");
|
|
@@ -34,15 +49,15 @@ describe("StateMachine decorator", () => {
|
|
|
34
49
|
|
|
35
50
|
it("uses a custom property key", () => {
|
|
36
51
|
class Widget {}
|
|
37
|
-
StateMachine(TOGGLE_DEF, "fsm")(Widget, {} as ClassDecoratorContext);
|
|
38
|
-
const instance = new
|
|
52
|
+
const Wrapped = StateMachine(TOGGLE_DEF, "fsm")(Widget, {} as ClassDecoratorContext) as unknown as typeof Widget;
|
|
53
|
+
const instance = new Wrapped() as Record<string, unknown>;
|
|
39
54
|
expect(instance.fsm).toBeDefined();
|
|
40
55
|
});
|
|
41
56
|
|
|
42
57
|
it("returns the same instance on repeated access", () => {
|
|
43
58
|
class Btn {}
|
|
44
|
-
StateMachine(TOGGLE_DEF)(Btn, {} as ClassDecoratorContext);
|
|
45
|
-
const instance = new
|
|
59
|
+
const Wrapped = StateMachine(TOGGLE_DEF)(Btn, {} as ClassDecoratorContext) as unknown as typeof Btn;
|
|
60
|
+
const instance = new Wrapped() as Record<string, unknown>;
|
|
46
61
|
expect(instance.machine).toBe(instance.machine);
|
|
47
62
|
});
|
|
48
63
|
});
|
|
@@ -52,25 +67,28 @@ describe("StateMachine decorator", () => {
|
|
|
52
67
|
describe("Transition decorator", () => {
|
|
53
68
|
it("calls the original method when send succeeds", () => {
|
|
54
69
|
const original = vi.fn(() => "ran");
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const ctx = mockMethodContext("doTransition");
|
|
71
|
+
Transition("machine", "toggle")(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
72
|
+
|
|
73
|
+
const instance = { machine: createMachine(TOGGLE_DEF) } as Record<string, unknown>;
|
|
74
|
+
ctx.runInitializers(instance);
|
|
75
|
+
const method = (instance as Record<string, () => unknown>).doTransition;
|
|
76
|
+
|
|
77
|
+
method();
|
|
63
78
|
expect(original).toHaveBeenCalled();
|
|
64
79
|
});
|
|
65
80
|
|
|
66
81
|
it("returns without calling method when machine is missing", () => {
|
|
67
82
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
68
83
|
const original = vi.fn();
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
const ctx = mockMethodContext("doTransition");
|
|
85
|
+
Transition("machine", "toggle")(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
86
|
+
|
|
87
|
+
const instance = {} as Record<string, unknown>;
|
|
88
|
+
ctx.runInitializers(instance);
|
|
89
|
+
const method = (instance as Record<string, () => unknown>).doTransition;
|
|
90
|
+
|
|
91
|
+
method();
|
|
74
92
|
expect(original).not.toHaveBeenCalled();
|
|
75
93
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining("[Transition]"));
|
|
76
94
|
warn.mockRestore();
|
|
@@ -78,26 +96,30 @@ describe("Transition decorator", () => {
|
|
|
78
96
|
|
|
79
97
|
it("does not call method when transition is invalid", () => {
|
|
80
98
|
const original = vi.fn();
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
99
|
+
const ctx = mockMethodContext("doTransition");
|
|
100
|
+
Transition("machine", "toggle")(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
101
|
+
|
|
85
102
|
const instance = {
|
|
86
103
|
machine: createMachine({ initial: "on" as const, states: { on: {} } }),
|
|
87
104
|
} as Record<string, unknown>;
|
|
105
|
+
ctx.runInitializers(instance);
|
|
106
|
+
const method = (instance as Record<string, () => unknown>).doTransition;
|
|
107
|
+
|
|
88
108
|
// "toggle" is not a valid event from "on" state
|
|
89
|
-
|
|
109
|
+
method();
|
|
90
110
|
expect(original).not.toHaveBeenCalled();
|
|
91
111
|
});
|
|
92
112
|
|
|
93
113
|
it("passes through arguments to the original method", () => {
|
|
94
114
|
const original = vi.fn((_x: unknown) => undefined);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
115
|
+
const ctx = mockMethodContext("doTransition");
|
|
116
|
+
Transition("machine", "toggle")(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
117
|
+
|
|
99
118
|
const instance = { machine: createMachine(TOGGLE_DEF) } as Record<string, unknown>;
|
|
100
|
-
|
|
119
|
+
ctx.runInitializers(instance);
|
|
120
|
+
const method = (instance as Record<string, (...a: unknown[]) => unknown>).doTransition;
|
|
121
|
+
|
|
122
|
+
method(42);
|
|
101
123
|
expect(original).toHaveBeenCalledWith(42);
|
|
102
124
|
});
|
|
103
125
|
});
|
package/src/decorators.ts
CHANGED
|
@@ -1,28 +1,48 @@
|
|
|
1
|
+
import type { RootComponent } from "@praxisjs/core/internal";
|
|
2
|
+
import {
|
|
3
|
+
createMethodDecorator,
|
|
4
|
+
createClassDecorator,
|
|
5
|
+
ClassBehavior,
|
|
6
|
+
type ClassEnhancement,
|
|
7
|
+
} from "@praxisjs/decorators";
|
|
8
|
+
|
|
1
9
|
import { createMachine, type Machine, type MachineDefinition } from "./machine";
|
|
2
10
|
|
|
3
11
|
export function Transition(machineProp: string, event: string) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
const decorator = createMethodDecorator({
|
|
13
|
+
wrap(original, instance) {
|
|
14
|
+
return (...args: unknown[]) => {
|
|
15
|
+
const machine = (instance as Record<string, unknown>)[machineProp] as Machine<string, string> | undefined;
|
|
16
|
+
if (!machine) {
|
|
17
|
+
console.warn(`[Transition] "${machineProp}" is not a state machine.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (machine.send(event)) return original.apply(instance, args);
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
// FSM decorators work on any class, not just StatefulComponent
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
return decorator as unknown as (value: (...args: unknown[]) => unknown, context: ClassMethodDecoratorContext<any>) => void;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
class StateMachineBehavior<S extends string, E extends string> extends ClassBehavior {
|
|
30
|
+
constructor(
|
|
31
|
+
private readonly definition: MachineDefinition<S, E>,
|
|
32
|
+
private readonly propertyKey: string,
|
|
33
|
+
) {
|
|
34
|
+
super();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
create(_instance: RootComponent): ClassEnhancement {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
initialize(Enhanced: new (...args: any[]) => unknown): void {
|
|
24
43
|
const machines = new WeakMap<object, Machine<S, E>>();
|
|
25
|
-
|
|
44
|
+
const { definition, propertyKey } = this;
|
|
45
|
+
Object.defineProperty((Enhanced as { prototype: object }).prototype, propertyKey, {
|
|
26
46
|
get(this: object): Machine<S, E> {
|
|
27
47
|
if (!machines.has(this)) machines.set(this, createMachine(definition));
|
|
28
48
|
return machines.get(this) as Machine<S, E>;
|
|
@@ -30,5 +50,15 @@ export function StateMachine<S extends string, E extends string>(
|
|
|
30
50
|
enumerable: true,
|
|
31
51
|
configurable: true,
|
|
32
52
|
});
|
|
33
|
-
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function StateMachine<S extends string, E extends string>(
|
|
57
|
+
definition: MachineDefinition<S, E>,
|
|
58
|
+
propertyKey = "machine",
|
|
59
|
+
) {
|
|
60
|
+
const decorator = createClassDecorator(new StateMachineBehavior(definition, propertyKey));
|
|
61
|
+
// FSM decorators work on any class, not just RootComponent
|
|
62
|
+
|
|
63
|
+
return decorator as unknown as (value: abstract new (...args: unknown[]) => unknown, context: ClassDecoratorContext) => abstract new (...args: unknown[]) => unknown;
|
|
34
64
|
}
|
package/src/index.ts
CHANGED