@praxisjs/fsm 1.0.11 → 2.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,42 @@
1
1
  # @praxisjs/fsm
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - c54d96b: `@StateMachine` converted from a class decorator to a field decorator.
8
+
9
+ Declare the machine as a typed field — no `declare`, no `propertyKey` second argument. The field name is the machine reference used in `@Transition`.
10
+
11
+ ```tsx
12
+ // before
13
+ @StateMachine({ initial: 'idle', states: { ... } }, 'machine')
14
+ @Component()
15
+ class Foo extends StatefulComponent {
16
+ declare machine: Machine<State, Event>
17
+ }
18
+
19
+ // after
20
+ @Component()
21
+ class Foo extends StatefulComponent {
22
+ @StateMachine({ initial: 'idle', states: { ... } })
23
+ machine!: Machine<State, Event>
24
+ }
25
+ ```
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [cfb0de2]
30
+ - @praxisjs/decorators@1.0.2
31
+
32
+ ## 1.0.12
33
+
34
+ ### Patch Changes
35
+
36
+ - Updated dependencies [74d414a]
37
+ - @praxisjs/core@1.6.0
38
+ - @praxisjs/decorators@1.0.1
39
+
3
40
  ## 1.0.11
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,36 +36,38 @@ function mockMethodContext(name) {
24
36
  }
25
37
  // ── StateMachine ──────────────────────────────────────────────────────────────
26
38
  describe("StateMachine decorator", () => {
27
- it("adds a machine property via prototype", () => {
28
- class Light {
29
- }
30
- const Wrapped = StateMachine(TOGGLE_DEF)(Light, {});
31
- const instance = new Wrapped();
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
- class Bulb {
37
- }
38
- const Wrapped = StateMachine(TOGGLE_DEF)(Bulb, {});
39
- const a = new Wrapped();
40
- const b = new Wrapped();
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("uses a custom property key", () => {
46
- class Widget {
47
- }
48
- const Wrapped = StateMachine(TOGGLE_DEF, "fsm")(Widget, {});
49
- const instance = new Wrapped();
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
- class Btn {
54
- }
55
- const Wrapped = StateMachine(TOGGLE_DEF)(Btn, {});
56
- const instance = new Wrapped();
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
  });
59
73
  });
@@ -91,7 +105,6 @@ describe("Transition decorator", () => {
91
105
  };
92
106
  ctx.runInitializers(instance);
93
107
  const method = instance.doTransition;
94
- // "toggle" is not a valid event from "on" state
95
108
  method();
96
109
  expect(original).not.toHaveBeenCalled();
97
110
  });
@@ -105,12 +118,11 @@ describe("Transition decorator", () => {
105
118
  method(42);
106
119
  expect(original).toHaveBeenCalledWith(42);
107
120
  });
108
- it("@Transition on method in class without @StateMachine — warns and returns undefined", () => {
121
+ it("@Transition on method without @StateMachine — warns and returns undefined", () => {
109
122
  const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
110
123
  const original = vi.fn(() => "ran");
111
124
  const ctx = mockMethodContext("doTransition");
112
125
  Transition("machine", "toggle")(original, ctx);
113
- // Instance has no "machine" property at all
114
126
  const instance = {};
115
127
  ctx.runInitializers(instance);
116
128
  const method = instance.doTransition;
@@ -121,29 +133,4 @@ describe("Transition decorator", () => {
121
133
  warn.mockRestore();
122
134
  });
123
135
  });
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
136
  //# sourceMappingURL=decorators.test.js.map
@@ -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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"}
@@ -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>, propertyKey?: string): (value: abstract new (...args: unknown[]) => unknown, context: ClassDecoratorContext) => abstract new (...args: unknown[]) => unknown;
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
@@ -1 +1 @@
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,GAiB5B,CAC7B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAEtC,OAAO,EAAE,2BAA2B,CAAC,GAAG,CAAC,KACtC,IAAI,CACV;AAqCD,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC7D,UAAU,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,EACnC,WAAW,SAAY,GAOQ,CAC7B,KAAK,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EACnD,OAAO,EAAE,qBAAqB,KAC3B,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAClD"}
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"}
@@ -1,4 +1,4 @@
1
- import { createMethodDecorator, createClassDecorator, ClassBehavior, } from "@praxisjs/decorators";
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
- class StateMachineBehavior extends ClassBehavior {
21
- constructor(definition, propertyKey) {
22
- super();
23
- this.definition = definition;
24
- this.propertyKey = propertyKey;
25
- }
26
- create(_instance) {
27
- return {};
28
- }
29
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
- initialize(Enhanced) {
31
- const machines = new WeakMap();
32
- const { definition, propertyKey } = this;
33
- Object.defineProperty(Enhanced.prototype, propertyKey, {
34
- get() {
35
- if (!machines.has(this))
36
- machines.set(this, createMachine(definition));
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
@@ -1 +1 @@
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,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,oBAGJ,SAAQ,aAAa;IACrB,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,CAClB,QAAkC,CAAC,SAAS,EAC7C,WAAW,EACX;YACE,GAAG;gBACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;oBACrB,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;gBAChD,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAkB,CAAC;YAC7C,CAAC;YACD,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;SACnB,CACF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAC1B,UAAmC,EACnC,WAAW,GAAG,SAAS;IAEvB,MAAM,SAAS,GAAG,oBAAoB,CACpC,IAAI,oBAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,CAClD,CAAC;IACF,2DAA2D;IAE3D,OAAO,SAG0C,CAAC;AACpD,CAAC"}
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": "1.0.11",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,8 +14,8 @@
14
14
  "typescript": "^5.9.3"
15
15
  },
16
16
  "dependencies": {
17
- "@praxisjs/core": "1.5.0",
18
- "@praxisjs/decorators": "1.0.0",
17
+ "@praxisjs/core": "1.6.0",
18
+ "@praxisjs/decorators": "1.0.2",
19
19
  "@praxisjs/shared": "0.2.0"
20
20
  },
21
21
  "scripts": {
@@ -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: { on: { toggle: "off" as const } },
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,35 +43,45 @@ function mockMethodContext(name: string) {
29
43
  // ── StateMachine ──────────────────────────────────────────────────────────────
30
44
 
31
45
  describe("StateMachine decorator", () => {
32
- it("adds a machine property via prototype", () => {
33
- class Light {}
34
- const Wrapped = StateMachine(TOGGLE_DEF)(Light, {} as ClassDecoratorContext) as unknown as typeof Light;
35
- const instance = new Wrapped() as Record<string, unknown>;
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 ReturnType<typeof createMachine>).state()).toBe("off");
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
- class Bulb {}
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>;
45
- (a.machine as ReturnType<typeof createMachine>).send("toggle");
46
- expect((a.machine as ReturnType<typeof createMachine>).state()).toBe("on");
47
- expect((b.machine as ReturnType<typeof createMachine>).state()).toBe("off");
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("uses a custom property key", () => {
51
- class Widget {}
52
- const Wrapped = StateMachine(TOGGLE_DEF, "fsm")(Widget, {} as ClassDecoratorContext) as unknown as typeof Widget;
53
- const instance = new Wrapped() as Record<string, unknown>;
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
- class Btn {}
59
- const Wrapped = StateMachine(TOGGLE_DEF)(Btn, {} as ClassDecoratorContext) as unknown as typeof Btn;
60
- const instance = new Wrapped() as Record<string, unknown>;
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
  });
63
87
  });
@@ -105,7 +129,6 @@ describe("Transition decorator", () => {
105
129
  ctx.runInitializers(instance);
106
130
  const method = (instance as Record<string, () => unknown>).doTransition;
107
131
 
108
- // "toggle" is not a valid event from "on" state
109
132
  method();
110
133
  expect(original).not.toHaveBeenCalled();
111
134
  });
@@ -123,13 +146,12 @@ describe("Transition decorator", () => {
123
146
  expect(original).toHaveBeenCalledWith(42);
124
147
  });
125
148
 
126
- it("@Transition on method in class without @StateMachine — warns and returns undefined", () => {
149
+ it("@Transition on method without @StateMachine — warns and returns undefined", () => {
127
150
  const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
128
151
  const original = vi.fn(() => "ran");
129
152
  const ctx = mockMethodContext("doTransition");
130
153
  Transition("machine", "toggle")(original, ctx as unknown as ClassMethodDecoratorContext);
131
154
 
132
- // Instance has no "machine" property at all
133
155
  const instance = {} as Record<string, unknown>;
134
156
  ctx.runInitializers(instance);
135
157
  const method = (instance as Record<string, () => unknown>).doTransition;
@@ -141,27 +163,3 @@ describe("Transition decorator", () => {
141
163
  warn.mockRestore();
142
164
  });
143
165
  });
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 } from "./machine";
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 decorator = createClassDecorator(
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 decorator as unknown as (
80
- value: abstract new (...args: unknown[]) => unknown,
81
- context: ClassDecoratorContext,
82
- ) => abstract new (...args: unknown[]) => unknown;
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
  }