@praxisjs/fsm 0.2.2 → 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 CHANGED
@@ -1,5 +1,61 @@
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
+
52
+ ## 0.2.3
53
+
54
+ ### Patch Changes
55
+
56
+ - Updated dependencies [d11a10a]
57
+ - @praxisjs/core@0.4.2
58
+
3
59
  ## 0.2.2
4
60
 
5
61
  ### 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 Light();
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 Bulb();
26
- const b = new Bulb();
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 Widget();
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 Btn();
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 wrapped = Transition("machine", "toggle")(original, {});
51
- const instance = {
52
- machine: createMachine(TOGGLE_DEF),
53
- };
54
- wrapped.call(instance);
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 wrapped = Transition("machine", "toggle")(original, {});
61
- wrapped.call({});
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 wrapped = Transition("machine", "toggle")(original, {});
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
- wrapped.call(instance);
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 wrapped = Transition("machine", "toggle")(original, {});
100
+ const ctx = mockMethodContext("doTransition");
101
+ Transition("machine", "toggle")(original, ctx);
79
102
  const instance = { machine: createMachine(TOGGLE_DEF) };
80
- wrapped.call(instance, 42);
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":"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"}
1
+ {"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"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"}
@@ -87,6 +87,47 @@ describe("createMachine", () => {
87
87
  m.send("START");
88
88
  expect(onExit).toHaveBeenCalledOnce();
89
89
  });
90
+ it("onExit throwing prevents the state change and keeps history clean", () => {
91
+ const m = createMachine({
92
+ initial: "idle",
93
+ states: {
94
+ idle: {
95
+ on: { START: "active" },
96
+ onExit: () => { throw new Error("exit failed"); },
97
+ },
98
+ active: {},
99
+ },
100
+ });
101
+ expect(() => m.send("START")).toThrow("exit failed");
102
+ expect(m.state()).toBe("idle");
103
+ expect(m.history()).toHaveLength(0);
104
+ });
105
+ it("onEnter throwing after state change leaves machine in the new state", () => {
106
+ const m = createMachine({
107
+ initial: "idle",
108
+ states: {
109
+ idle: { on: { START: "active" } },
110
+ active: { onEnter: () => { throw new Error("enter failed"); } },
111
+ },
112
+ });
113
+ expect(() => m.send("START")).toThrow("enter failed");
114
+ // State was already committed before onEnter ran
115
+ expect(m.state()).toBe("active");
116
+ expect(m.history()).toHaveLength(1);
117
+ });
118
+ it("reset() calls onEnter of the initial state", () => {
119
+ const onEnter = vi.fn();
120
+ const m = createMachine({
121
+ initial: "idle",
122
+ states: {
123
+ idle: { on: { START: "active" }, onEnter },
124
+ active: {},
125
+ },
126
+ });
127
+ m.send("START");
128
+ m.reset();
129
+ expect(onEnter).toHaveBeenCalledOnce();
130
+ });
90
131
  it("calls onTransition callback", () => {
91
132
  const onTransition = vi.fn();
92
133
  const m = createMachine({
@@ -1 +1 @@
1
- {"version":3,"file":"machine.test.js","sourceRoot":"","sources":["../../src/__tests__/machine.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"machine.test.js","sourceRoot":"","sources":["../../src/__tests__/machine.test.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  import { type MachineDefinition } from "./machine";
2
- export declare function Transition(machineProp: string, event: string): (value: (...args: unknown[]) => unknown, _context: ClassMethodDecoratorContext) => (...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, _context: ClassDecoratorContext) => void;
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
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEhF,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,IAEzD,OAAO,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EACtC,UAAU,2BAA2B,KACpC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAUnC;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC7D,UAAU,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,EACnC,WAAW,SAAY,IAEN,OAAO,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,UAAU,qBAAqB,KAAG,IAAI,CAW7G"}
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"}
@@ -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
- return function (value, _context) {
4
- return function (...args) {
5
- const machine = this[machineProp];
6
- if (!machine) {
7
- console.warn(`[Transition] "${machineProp}" is not a state machine.`);
8
- return;
9
- }
10
- if (machine.send(event))
11
- return value.apply(this, args);
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
- export function StateMachine(definition, propertyKey = "machine") {
16
- return function (value, _context) {
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
- Object.defineProperty(value.prototype, propertyKey, {
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
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAwC,MAAM,WAAW,CAAC;AAEhF,MAAM,UAAU,UAAU,CAAC,WAAmB,EAAE,KAAa;IAC3D,OAAO,UACL,KAAsC,EACtC,QAAqC;QAErC,OAAO,UAAyC,GAAG,IAAe;YAChE,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAwC,CAAC;YACzE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,iBAAiB,WAAW,2BAA2B,CAAC,CAAC;gBACtE,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1D,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAmC,EACnC,WAAW,GAAG,SAAS;IAEvB,OAAO,UAAU,KAAmD,EAAE,QAA+B;QACnG,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAyB,CAAC;QACtD,MAAM,CAAC,cAAc,CAAE,KAA+B,CAAC,SAAS,EAAE,WAAW,EAAE;YAC7E,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,CAAC;AACJ,CAAC"}
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
@@ -1,4 +1,3 @@
1
- export { createMachine } from "./machine";
2
1
  export type { Machine, MachineDefinition, StateMap } from "./machine";
3
2
  export { Transition, StateMachine } from "./decorators";
4
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
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
@@ -1,3 +1,2 @@
1
- export { createMachine } from "./machine";
2
1
  export { Transition, StateMachine } from "./decorators";
3
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
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.2.2",
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.4.1",
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 Light() as Record<string, unknown>;
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 Bulb() as Record<string, unknown>;
29
- const b = new Bulb() as Record<string, unknown>;
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 Widget() as Record<string, unknown>;
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 Btn() as Record<string, unknown>;
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 wrapped = Transition("machine", "toggle")(
56
- original,
57
- {} as ClassMethodDecoratorContext,
58
- );
59
- const instance = {
60
- machine: createMachine(TOGGLE_DEF),
61
- } as Record<string, unknown>;
62
- wrapped.call(instance);
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 wrapped = Transition("machine", "toggle")(
70
- original,
71
- {} as ClassMethodDecoratorContext,
72
- );
73
- wrapped.call({} as Record<string, unknown>);
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 wrapped = Transition("machine", "toggle")(
82
- original,
83
- {} as ClassMethodDecoratorContext,
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
- wrapped.call(instance);
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 wrapped = Transition("machine", "toggle")(
96
- original,
97
- {} as ClassMethodDecoratorContext,
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
- wrapped.call(instance, 42);
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
  });
@@ -105,6 +105,50 @@ describe("createMachine", () => {
105
105
  expect(onExit).toHaveBeenCalledOnce();
106
106
  });
107
107
 
108
+ it("onExit throwing prevents the state change and keeps history clean", () => {
109
+ const m = createMachine<"idle" | "active", "START">({
110
+ initial: "idle",
111
+ states: {
112
+ idle: {
113
+ on: { START: "active" },
114
+ onExit: () => { throw new Error("exit failed"); },
115
+ },
116
+ active: {},
117
+ },
118
+ });
119
+ expect(() => m.send("START")).toThrow("exit failed");
120
+ expect(m.state()).toBe("idle");
121
+ expect(m.history()).toHaveLength(0);
122
+ });
123
+
124
+ it("onEnter throwing after state change leaves machine in the new state", () => {
125
+ const m = createMachine<"idle" | "active", "START">({
126
+ initial: "idle",
127
+ states: {
128
+ idle: { on: { START: "active" } },
129
+ active: { onEnter: () => { throw new Error("enter failed"); } },
130
+ },
131
+ });
132
+ expect(() => m.send("START")).toThrow("enter failed");
133
+ // State was already committed before onEnter ran
134
+ expect(m.state()).toBe("active");
135
+ expect(m.history()).toHaveLength(1);
136
+ });
137
+
138
+ it("reset() calls onEnter of the initial state", () => {
139
+ const onEnter = vi.fn();
140
+ const m = createMachine<"idle" | "active", "START">({
141
+ initial: "idle",
142
+ states: {
143
+ idle: { on: { START: "active" }, onEnter },
144
+ active: {},
145
+ },
146
+ });
147
+ m.send("START");
148
+ m.reset();
149
+ expect(onEnter).toHaveBeenCalledOnce();
150
+ });
151
+
108
152
  it("calls onTransition callback", () => {
109
153
  const onTransition = vi.fn();
110
154
  const m = createMachine<"a" | "b", "NEXT">({
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
- return function (
5
- value: (...args: unknown[]) => unknown,
6
- _context: ClassMethodDecoratorContext,
7
- ): (...args: unknown[]) => unknown {
8
- return function (this: Record<string, unknown>, ...args: unknown[]) {
9
- const machine = this[machineProp] as Machine<string, string> | undefined;
10
- if (!machine) {
11
- console.warn(`[Transition] "${machineProp}" is not a state machine.`);
12
- return;
13
- }
14
- if (machine.send(event)) return value.apply(this, args);
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
- export function StateMachine<S extends string, E extends string>(
20
- definition: MachineDefinition<S, E>,
21
- propertyKey = "machine",
22
- ) {
23
- return function (value: abstract new (...args: unknown[]) => unknown, _context: ClassDecoratorContext): void {
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
- Object.defineProperty((value as { prototype: object }).prototype, propertyKey, {
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
@@ -1,3 +1,2 @@
1
- export { createMachine } from "./machine";
2
1
  export type { Machine, MachineDefinition, StateMap } from "./machine";
3
2
  export { Transition, StateMachine } from "./decorators";