@xmachines/play-actor 1.0.0-beta.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/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # @xmachines/play-actor
2
+
3
+ **Abstract Actor base class with signal protocol for XMachines Play Architecture**
4
+
5
+ Foundation for all actor implementations, enforcing XState compatibility and reactive signal contracts.
6
+
7
+ ## Overview
8
+
9
+ `@xmachines/play-actor` provides `AbstractActor`, a base class that extends XState's `Actor` while enforcing the Play Architecture's signal protocol. It maintains XState ecosystem compatibility (inspection tools, devtools) while exposing reactive signals for infrastructure layer communication.
10
+
11
+ Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
12
+
13
+ - **Actor Authority (INV-01):** Actor is sole source of truth for state transitions
14
+ - **Signal-Only Reactivity (INV-05):** Infrastructure observes via TC39 Signals, never directly queries
15
+ - **Passive Infrastructure (INV-04):** Infrastructure reflects, never decides
16
+
17
+ **Note:** This is an abstract base class. Concrete implementations are provided by adapters (see [@xmachines/play-xstate](../play-xstate)).
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install xstate@^5.0.0
23
+ npm install @xmachines/play-actor
24
+ ```
25
+
26
+ ## Current Exports
27
+
28
+ - `AbstractActor`
29
+ - `Routable` (type)
30
+ - `Viewable` (type)
31
+
32
+ **Peer dependencies:**
33
+
34
+ - `xstate` ^5.0.0 - State machine runtime (XState compatibility)
35
+ - `@xmachines/play-signals` - TC39 Signals primitives
36
+ - `@xmachines/play` - Protocol types (PlayEvent, etc.)
37
+
38
+ ## Quick Start
39
+
40
+ **Usage:** This is an abstract base class — use concrete implementations:
41
+
42
+ ```typescript
43
+ import { definePlayer } from "@xmachines/play-xstate";
44
+
45
+ // definePlayer returns PlayerActor (extends AbstractActor)
46
+ const createPlayer = definePlayer({ machine, catalog });
47
+ const actor = createPlayer();
48
+ actor.start();
49
+
50
+ // Signal protocol properties (from AbstractActor)
51
+ console.log(actor.state.get()); // Current snapshot
52
+ console.log(actor.currentRoute.get()); // Derived route
53
+ console.log(actor.currentView.get()); // Derived view structure
54
+ ```
55
+
56
+ ## API Reference
57
+
58
+ ### AbstractActor<TLogic>
59
+
60
+ Abstract base class defining signal protocol:
61
+
62
+ **Abstract Properties (must implement):**
63
+
64
+ - `state: Signal.State<any>` - Reactive snapshot of current state
65
+ - `currentRoute: Signal.Computed<string | null>` - Derived navigation path
66
+ - `currentView: Signal.Computed<Record<string, any> | null>` - Derived UI structure
67
+ - `catalog: any` - Component catalog
68
+
69
+ **Inherited from XState Actor:**
70
+
71
+ - `send(event: PlayEvent): void` - Send event to actor
72
+ - `start(): void` - Start the actor
73
+ - `stop(): void` - Stop the actor
74
+ - `getSnapshot(): Snapshot` - Get current XState snapshot
75
+
76
+ **Example implementation pattern:**
77
+
78
+ ```typescript
79
+ import { AbstractActor } from "@xmachines/play-actor";
80
+ import { Signal } from "@xmachines/play-signals";
81
+
82
+ class PlayerActor<TLogic extends AnyActorLogic> extends AbstractActor<TLogic> {
83
+ // Implement required signal properties
84
+ state = new Signal.State(this.internalActor.getSnapshot());
85
+
86
+ currentRoute = new Signal.Computed(() => {
87
+ const snapshot = this.state.get();
88
+ return deriveRoute(snapshot);
89
+ });
90
+
91
+ currentView = new Signal.Computed(() => {
92
+ const snapshot = this.state.get();
93
+ return snapshot.meta?.view ?? null;
94
+ });
95
+
96
+ catalog = this.config.catalog;
97
+
98
+ // Internal XState actor
99
+ private internalActor: Actor<TLogic>;
100
+
101
+ constructor(logic: TLogic, catalog: any) {
102
+ super(logic);
103
+ this.internalActor = createActor(logic);
104
+
105
+ // Subscribe to XState transitions
106
+ this.internalActor.subscribe((snapshot) => {
107
+ this.state.set(snapshot); // Update signal
108
+ });
109
+ }
110
+
111
+ override start(): void {
112
+ this.internalActor.start();
113
+ }
114
+
115
+ override stop(): void {
116
+ this.internalActor.stop();
117
+ }
118
+
119
+ override send(event: PlayEvent): void {
120
+ this.internalActor.send(event as any);
121
+ }
122
+ }
123
+ ```
124
+
125
+ **Complete API:** See [API Documentation](../../docs/api/@xmachines/play-actor)
126
+
127
+ ## Examples
128
+
129
+ ### Infrastructure Observing Signals
130
+
131
+ ```typescript
132
+ import { AbstractActor } from "@xmachines/play-actor";
133
+ import { Signal } from "@xmachines/play-signals";
134
+
135
+ function syncUrlToActor(actor: AbstractActor<any>) {
136
+ // Infrastructure passively observes actor's route signal
137
+ const watcher = new Signal.subtle.Watcher(() => {
138
+ queueMicrotask(() => {
139
+ const pending = watcher.getPending();
140
+ if (pending.length > 0) {
141
+ const route = actor.currentRoute.get();
142
+ if (route !== null) {
143
+ // Update browser URL (Passive Infrastructure)
144
+ window.history.replaceState(null, "", route);
145
+ }
146
+ watcher.watch(...pending); // Re-watch
147
+ }
148
+ });
149
+ });
150
+
151
+ watcher.watch(actor.currentRoute);
152
+ actor.currentRoute.get(); // Initial read
153
+
154
+ return () => watcher.unwatch(actor.currentRoute);
155
+ }
156
+ ```
157
+
158
+ ### Browser Navigation Sending Events
159
+
160
+ ```typescript
161
+ import { AbstractActor } from "@xmachines/play-actor";
162
+
163
+ function connectBrowserNavigation(actor: AbstractActor<any>) {
164
+ const handlePopstate = () => {
165
+ const path = window.location.pathname;
166
+
167
+ // Browser event sent to actor (Actor Authority)
168
+ // Actor guards decide if navigation is valid
169
+ actor.send({ type: "play.route", to: path });
170
+ };
171
+
172
+ window.addEventListener("popstate", handlePopstate);
173
+
174
+ return () => {
175
+ window.removeEventListener("popstate", handlePopstate);
176
+ };
177
+ }
178
+ ```
179
+
180
+ ## Architecture
181
+
182
+ This base class enforces three architectural invariants:
183
+
184
+ 1. **Actor Authority (INV-01):**
185
+ - Actor decides all state transitions via guards
186
+ - Infrastructure sends events, actor validates and processes
187
+ - Actor's decision is final — no override by infrastructure
188
+
189
+ 2. **Signal-Only Reactivity (INV-05):**
190
+ - All reactive state exposed via TC39 Signals
191
+ - Infrastructure uses `Signal.subtle.Watcher` to observe
192
+ - No direct queries (`getSnapshot()` for internal use only)
193
+
194
+ 3. **Passive Infrastructure (INV-04):**
195
+ - Infrastructure reflects actor state (via signals)
196
+ - Infrastructure never decides transitions
197
+ - Browser/router events sent as commands to actor
198
+
199
+ ## XState Compatibility
200
+
201
+ `AbstractActor` extends XState's `Actor<TLogic>` to maintain:
202
+
203
+ - **Type Safety:** Generic `TLogic extends AnyActorLogic` parameter
204
+ - **Inspection API:** XState Inspector can attach to actors
205
+ - **DevTools Integration:** Standard XState devtools work
206
+ - **Ecosystem Tools:** Works with XState visualization, testing libraries
207
+
208
+ **Snapshot Format:** Standard XState snapshots (state + context) remain unchanged — signals are accessible via actor properties, not snapshots.
209
+
210
+ ## Related Packages
211
+
212
+ - **[@xmachines/play-xstate](../play-xstate)** - Concrete PlayerActor implementation
213
+ - **[@xmachines/play-signals](../play-signals)** - TC39 Signals primitives
214
+ - **[@xmachines/play](../play)** - Protocol types (PlayEvent, RouterBridge)
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,204 @@
1
+ /**
2
+ * AbstractActor base class for Play Architecture
3
+ *
4
+ * Extends XState Actor to maintain ecosystem compatibility (inspection, devtools)
5
+ * while enforcing signal protocol for Actor ↔ Infrastructure communication.
6
+ *
7
+ * Per RFC section 5.3, the Actor exposes a minimal protocol:
8
+ * - state: Current machine state snapshot
9
+ * - send: Event dispatch method
10
+ *
11
+ * Optional capabilities are provided via interfaces:
12
+ * - Routable: For actors that support routing
13
+ * - Viewable: For actors that support view rendering
14
+ *
15
+ * Concrete implementations are created by adapters (e.g., @xmachines/play-xstate).
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ import { Actor, type AnyActorLogic } from "xstate";
20
+ import type { Signal } from "@xmachines/play-signals";
21
+ /**
22
+ * Optional capability: Routing support
23
+ *
24
+ * Actors implementing this interface can derive a route from their state.
25
+ * Router adapters observe the currentRoute signal to sync browser URLs.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * class MyActor extends AbstractActor implements Routable {
30
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
31
+ * }
32
+ *
33
+ * // Router requires Routable
34
+ * function connectRouter<T extends AbstractActor & Routable>(actor: T) {
35
+ * watcher.watch(actor.currentRoute);
36
+ * }
37
+ * ```
38
+ */
39
+ export interface Routable {
40
+ /**
41
+ * Current route signal
42
+ *
43
+ * Computed signal derived from state machine. Infrastructure observes to sync browser URL.
44
+ *
45
+ * Invariant: Passive Infrastructure - Infrastructure reflects route, never decides.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const watcher = new Signal.subtle.Watcher(() => {
50
+ * const route = actor.currentRoute.get();
51
+ * console.log('Route changed:', route);
52
+ * });
53
+ * watcher.watch(actor.currentRoute);
54
+ * ```
55
+ */
56
+ readonly currentRoute: Signal.Computed<string | null>;
57
+ }
58
+ /**
59
+ * Optional capability: View rendering support
60
+ *
61
+ * Actors implementing this interface can derive view structures from their state.
62
+ * Renderers observe the currentView signal to update the UI.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * class MyActor extends AbstractActor implements Viewable {
67
+ * currentView = new Signal.State(null);
68
+ * catalog = { HomePage: HomeComponent };
69
+ * }
70
+ *
71
+ * // Renderer requires Viewable
72
+ * function renderView<T extends AbstractActor & Viewable>(actor: T) {
73
+ * const view = actor.currentView.get();
74
+ * return catalog[view.component];
75
+ * }
76
+ * ```
77
+ */
78
+ export interface Viewable {
79
+ /**
80
+ * Current view signal
81
+ *
82
+ * State signal containing UI structure schema from meta.view. Infrastructure renders view.
83
+ *
84
+ * Invariant: Logic-Driven UI - View structure is defined by business logic, not JSX.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const watcher = new Signal.subtle.Watcher(() => {
89
+ * const view = actor.currentView.get();
90
+ * console.log('View changed:', view);
91
+ * });
92
+ * watcher.watch(actor.currentView);
93
+ * ```
94
+ */
95
+ readonly currentView: Signal.State<any>;
96
+ /**
97
+ * Component catalog for view resolution
98
+ *
99
+ * Maps component names to actual component implementations.
100
+ * Used by renderers to resolve view.component to actual UI components.
101
+ */
102
+ readonly catalog: any;
103
+ }
104
+ /**
105
+ * Abstract base class for Play Architecture actors.
106
+ *
107
+ * Extends XState Actor to maintain ecosystem compatibility (inspection, devtools)
108
+ * while enforcing minimal signal protocol for Actor ↔ Infrastructure communication.
109
+ *
110
+ * The core protocol contains only:
111
+ * - state: Reactive state snapshot
112
+ * - send: Event dispatch method
113
+ *
114
+ * Optional capabilities (routing, view rendering) are provided via interfaces:
115
+ * - Implement Routable for routing support
116
+ * - Implement Viewable for view rendering support
117
+ *
118
+ * Concrete implementations created by @xmachines/play-xstate adapter.
119
+ *
120
+ * @typeParam TLogic - XState actor logic type (maintains type safety)
121
+ *
122
+ * Invariant: Actor Authority - Actor is the sole source of truth for state transitions.
123
+ * Invariant: Signal-Only Reactivity - Infrastructure observes via TC39 Signals.
124
+ * Invariant: Passive Infrastructure - Infrastructure reflects, never decides.
125
+ *
126
+ * @example
127
+ * Simple actor (no routing, no view)
128
+ * ```typescript
129
+ * class SimpleActor extends AbstractActor<any> {
130
+ * state = new Signal.State({...});
131
+ * send(event) { ... }
132
+ * }
133
+ * ```
134
+ *
135
+ * @example
136
+ * Routable actor
137
+ * ```typescript
138
+ * class RoutableActor extends AbstractActor<any> implements Routable {
139
+ * state = new Signal.State({...});
140
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
141
+ * send(event) { ... }
142
+ * }
143
+ * ```
144
+ *
145
+ * @example
146
+ * Full-featured actor (routing + view)
147
+ * ```typescript
148
+ * class PlayerActor extends AbstractActor<any> implements Routable, Viewable {
149
+ * state = new Signal.State({...});
150
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
151
+ * currentView = new Signal.State(null);
152
+ * catalog = {};
153
+ * send(event) { ... }
154
+ * }
155
+ * ```
156
+ *
157
+ * @see {@link https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md#53-actor-protocol | RFC Play v1 Section 5.3}
158
+ * @see {@link Routable} for routing capability
159
+ * @see {@link Viewable} for view rendering capability
160
+ */
161
+ export declare abstract class AbstractActor<TLogic extends AnyActorLogic> extends Actor<TLogic> {
162
+ /**
163
+ * Reactive snapshot of current actor state.
164
+ *
165
+ * Infrastructure observes this signal to react to state changes without
166
+ * directly coupling to the Actor's internal state machine implementation.
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * // Infrastructure observes state signal
171
+ * const watcher = new Signal.subtle.Watcher(() => {
172
+ * console.log('Actor state changed:', actor.state.get());
173
+ * });
174
+ * watcher.watch(actor.state);
175
+ * ```
176
+ */
177
+ abstract state: Signal.State<any>;
178
+ /**
179
+ * Send event to Actor
180
+ *
181
+ * Infrastructure forwards user intents (navigation, domain events, custom events)
182
+ * as events to the Actor. The Actor's state machine guards determine whether
183
+ * each event is valid from the current state.
184
+ *
185
+ * @param event - Event object with type property (e.g., PlayEvent, PlayRouteEvent)
186
+ *
187
+ * Invariant: Actor Authority - Only Actor decides whether an event is valid.
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * // Infrastructure forwards user intent
192
+ * actor.send({ type: 'auth.login', userId: '123' });
193
+ * // Actor's guards determine if event is allowed
194
+ * ```
195
+ *
196
+ * @remarks
197
+ * Accepts any event object with a type property. Core events (PlayEvent) are in
198
+ * @xmachines/play, routing events (PlayRouteEvent) are in @xmachines/play-router.
199
+ */
200
+ abstract send(event: {
201
+ readonly type: string;
202
+ } & Record<string, any>): void;
203
+ }
204
+ //# sourceMappingURL=abstract-actor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract-actor.d.ts","sourceRoot":"","sources":["../src/abstract-actor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,QAAQ;IACxB;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACtD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,QAAQ;IACxB;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAExC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,8BAAsB,aAAa,CAAC,MAAM,SAAS,aAAa,CAAE,SAAQ,KAAK,CAAC,MAAM,CAAC;IACtF;;;;;;;;;;;;;;OAcG;IACH,SAAgB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEzC;;;;;;;;;;;;;;;;;;;;;OAqBG;aACsB,IAAI,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;CAC3F"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * AbstractActor base class for Play Architecture
3
+ *
4
+ * Extends XState Actor to maintain ecosystem compatibility (inspection, devtools)
5
+ * while enforcing signal protocol for Actor ↔ Infrastructure communication.
6
+ *
7
+ * Per RFC section 5.3, the Actor exposes a minimal protocol:
8
+ * - state: Current machine state snapshot
9
+ * - send: Event dispatch method
10
+ *
11
+ * Optional capabilities are provided via interfaces:
12
+ * - Routable: For actors that support routing
13
+ * - Viewable: For actors that support view rendering
14
+ *
15
+ * Concrete implementations are created by adapters (e.g., @xmachines/play-xstate).
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ import { Actor } from "xstate";
20
+ /**
21
+ * Abstract base class for Play Architecture actors.
22
+ *
23
+ * Extends XState Actor to maintain ecosystem compatibility (inspection, devtools)
24
+ * while enforcing minimal signal protocol for Actor ↔ Infrastructure communication.
25
+ *
26
+ * The core protocol contains only:
27
+ * - state: Reactive state snapshot
28
+ * - send: Event dispatch method
29
+ *
30
+ * Optional capabilities (routing, view rendering) are provided via interfaces:
31
+ * - Implement Routable for routing support
32
+ * - Implement Viewable for view rendering support
33
+ *
34
+ * Concrete implementations created by @xmachines/play-xstate adapter.
35
+ *
36
+ * @typeParam TLogic - XState actor logic type (maintains type safety)
37
+ *
38
+ * Invariant: Actor Authority - Actor is the sole source of truth for state transitions.
39
+ * Invariant: Signal-Only Reactivity - Infrastructure observes via TC39 Signals.
40
+ * Invariant: Passive Infrastructure - Infrastructure reflects, never decides.
41
+ *
42
+ * @example
43
+ * Simple actor (no routing, no view)
44
+ * ```typescript
45
+ * class SimpleActor extends AbstractActor<any> {
46
+ * state = new Signal.State({...});
47
+ * send(event) { ... }
48
+ * }
49
+ * ```
50
+ *
51
+ * @example
52
+ * Routable actor
53
+ * ```typescript
54
+ * class RoutableActor extends AbstractActor<any> implements Routable {
55
+ * state = new Signal.State({...});
56
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
57
+ * send(event) { ... }
58
+ * }
59
+ * ```
60
+ *
61
+ * @example
62
+ * Full-featured actor (routing + view)
63
+ * ```typescript
64
+ * class PlayerActor extends AbstractActor<any> implements Routable, Viewable {
65
+ * state = new Signal.State({...});
66
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
67
+ * currentView = new Signal.State(null);
68
+ * catalog = {};
69
+ * send(event) { ... }
70
+ * }
71
+ * ```
72
+ *
73
+ * @see {@link https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md#53-actor-protocol | RFC Play v1 Section 5.3}
74
+ * @see {@link Routable} for routing capability
75
+ * @see {@link Viewable} for view rendering capability
76
+ */
77
+ export class AbstractActor extends Actor {
78
+ }
79
+ //# sourceMappingURL=abstract-actor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract-actor.js","sourceRoot":"","sources":["../src/abstract-actor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAsB,MAAM,QAAQ,CAAC;AAyFnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,MAAM,OAAgB,aAA4C,SAAQ,KAAa;CAyCtF"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @xmachines/play-actor - Abstract Actor base class for Play Architecture
3
+ *
4
+ * This package provides AbstractActor, a minimal base class that extends XState Actor
5
+ * while enforcing the Play Architecture's signal protocol (RFC section 5.3).
6
+ *
7
+ * The core protocol is minimal (state + send). Optional capabilities are provided
8
+ * via interfaces:
9
+ * - Routable: For actors that support routing
10
+ * - Viewable: For actors that support view rendering
11
+ *
12
+ * Maintains XState ecosystem compatibility (inspection, devtools) while exposing
13
+ * reactive signals for Infrastructure layer communication.
14
+ *
15
+ * @packageDocumentation
16
+ * @see {@link https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md#53-actor-protocol | RFC Play v1 Section 5.3}
17
+ */
18
+ export { AbstractActor, type Routable, type Viewable } from "./abstract-actor.js";
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @xmachines/play-actor - Abstract Actor base class for Play Architecture
3
+ *
4
+ * This package provides AbstractActor, a minimal base class that extends XState Actor
5
+ * while enforcing the Play Architecture's signal protocol (RFC section 5.3).
6
+ *
7
+ * The core protocol is minimal (state + send). Optional capabilities are provided
8
+ * via interfaces:
9
+ * - Routable: For actors that support routing
10
+ * - Viewable: For actors that support view rendering
11
+ *
12
+ * Maintains XState ecosystem compatibility (inspection, devtools) while exposing
13
+ * reactive signals for Infrastructure layer communication.
14
+ *
15
+ * @packageDocumentation
16
+ * @see {@link https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md#53-actor-protocol | RFC Play v1 Section 5.3}
17
+ */
18
+ export { AbstractActor } from "./abstract-actor.js";
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAgC,MAAM,qBAAqB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@xmachines/play-actor",
3
+ "version": "1.0.0-beta.1",
4
+ "private": false,
5
+ "description": "Abstract Actor base class for XMachines Play Architecture",
6
+ "keywords": [
7
+ "actor",
8
+ "play-architecture",
9
+ "signals",
10
+ "xmachines",
11
+ "xstate"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "XMachines Contributors",
15
+ "files": [
16
+ "dist",
17
+ "src",
18
+ "README.md"
19
+ ],
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "scripts": {
28
+ "build": "tsc --build",
29
+ "clean": "rm -rf dist *.tsbuildinfo",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "vitest run",
32
+ "lint": "oxlint .",
33
+ "lint:fix": "oxlint --fix .",
34
+ "format": "oxfmt .",
35
+ "format:check": "oxfmt --check .",
36
+ "prepublishOnly": "npm run build"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^25.4.0",
40
+ "xstate": "^5.28.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@xmachines/play": "1.0.0-beta.1",
44
+ "@xmachines/play-signals": "1.0.0-beta.1",
45
+ "xstate": "^5.28.0"
46
+ },
47
+ "engines": {
48
+ "node": ">=22.0.0"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * AbstractActor base class for Play Architecture
3
+ *
4
+ * Extends XState Actor to maintain ecosystem compatibility (inspection, devtools)
5
+ * while enforcing signal protocol for Actor ↔ Infrastructure communication.
6
+ *
7
+ * Per RFC section 5.3, the Actor exposes a minimal protocol:
8
+ * - state: Current machine state snapshot
9
+ * - send: Event dispatch method
10
+ *
11
+ * Optional capabilities are provided via interfaces:
12
+ * - Routable: For actors that support routing
13
+ * - Viewable: For actors that support view rendering
14
+ *
15
+ * Concrete implementations are created by adapters (e.g., @xmachines/play-xstate).
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ import { Actor, type AnyActorLogic } from "xstate";
21
+ import type { Signal } from "@xmachines/play-signals";
22
+
23
+ /**
24
+ * Optional capability: Routing support
25
+ *
26
+ * Actors implementing this interface can derive a route from their state.
27
+ * Router adapters observe the currentRoute signal to sync browser URLs.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * class MyActor extends AbstractActor implements Routable {
32
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
33
+ * }
34
+ *
35
+ * // Router requires Routable
36
+ * function connectRouter<T extends AbstractActor & Routable>(actor: T) {
37
+ * watcher.watch(actor.currentRoute);
38
+ * }
39
+ * ```
40
+ */
41
+ export interface Routable {
42
+ /**
43
+ * Current route signal
44
+ *
45
+ * Computed signal derived from state machine. Infrastructure observes to sync browser URL.
46
+ *
47
+ * Invariant: Passive Infrastructure - Infrastructure reflects route, never decides.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const watcher = new Signal.subtle.Watcher(() => {
52
+ * const route = actor.currentRoute.get();
53
+ * console.log('Route changed:', route);
54
+ * });
55
+ * watcher.watch(actor.currentRoute);
56
+ * ```
57
+ */
58
+ readonly currentRoute: Signal.Computed<string | null>;
59
+ }
60
+
61
+ /**
62
+ * Optional capability: View rendering support
63
+ *
64
+ * Actors implementing this interface can derive view structures from their state.
65
+ * Renderers observe the currentView signal to update the UI.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * class MyActor extends AbstractActor implements Viewable {
70
+ * currentView = new Signal.State(null);
71
+ * catalog = { HomePage: HomeComponent };
72
+ * }
73
+ *
74
+ * // Renderer requires Viewable
75
+ * function renderView<T extends AbstractActor & Viewable>(actor: T) {
76
+ * const view = actor.currentView.get();
77
+ * return catalog[view.component];
78
+ * }
79
+ * ```
80
+ */
81
+ export interface Viewable {
82
+ /**
83
+ * Current view signal
84
+ *
85
+ * State signal containing UI structure schema from meta.view. Infrastructure renders view.
86
+ *
87
+ * Invariant: Logic-Driven UI - View structure is defined by business logic, not JSX.
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const watcher = new Signal.subtle.Watcher(() => {
92
+ * const view = actor.currentView.get();
93
+ * console.log('View changed:', view);
94
+ * });
95
+ * watcher.watch(actor.currentView);
96
+ * ```
97
+ */
98
+ readonly currentView: Signal.State<any>;
99
+
100
+ /**
101
+ * Component catalog for view resolution
102
+ *
103
+ * Maps component names to actual component implementations.
104
+ * Used by renderers to resolve view.component to actual UI components.
105
+ */
106
+ readonly catalog: any;
107
+ }
108
+
109
+ /**
110
+ * Abstract base class for Play Architecture actors.
111
+ *
112
+ * Extends XState Actor to maintain ecosystem compatibility (inspection, devtools)
113
+ * while enforcing minimal signal protocol for Actor ↔ Infrastructure communication.
114
+ *
115
+ * The core protocol contains only:
116
+ * - state: Reactive state snapshot
117
+ * - send: Event dispatch method
118
+ *
119
+ * Optional capabilities (routing, view rendering) are provided via interfaces:
120
+ * - Implement Routable for routing support
121
+ * - Implement Viewable for view rendering support
122
+ *
123
+ * Concrete implementations created by @xmachines/play-xstate adapter.
124
+ *
125
+ * @typeParam TLogic - XState actor logic type (maintains type safety)
126
+ *
127
+ * Invariant: Actor Authority - Actor is the sole source of truth for state transitions.
128
+ * Invariant: Signal-Only Reactivity - Infrastructure observes via TC39 Signals.
129
+ * Invariant: Passive Infrastructure - Infrastructure reflects, never decides.
130
+ *
131
+ * @example
132
+ * Simple actor (no routing, no view)
133
+ * ```typescript
134
+ * class SimpleActor extends AbstractActor<any> {
135
+ * state = new Signal.State({...});
136
+ * send(event) { ... }
137
+ * }
138
+ * ```
139
+ *
140
+ * @example
141
+ * Routable actor
142
+ * ```typescript
143
+ * class RoutableActor extends AbstractActor<any> implements Routable {
144
+ * state = new Signal.State({...});
145
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
146
+ * send(event) { ... }
147
+ * }
148
+ * ```
149
+ *
150
+ * @example
151
+ * Full-featured actor (routing + view)
152
+ * ```typescript
153
+ * class PlayerActor extends AbstractActor<any> implements Routable, Viewable {
154
+ * state = new Signal.State({...});
155
+ * currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
156
+ * currentView = new Signal.State(null);
157
+ * catalog = {};
158
+ * send(event) { ... }
159
+ * }
160
+ * ```
161
+ *
162
+ * @see {@link https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md#53-actor-protocol | RFC Play v1 Section 5.3}
163
+ * @see {@link Routable} for routing capability
164
+ * @see {@link Viewable} for view rendering capability
165
+ */
166
+ export abstract class AbstractActor<TLogic extends AnyActorLogic> extends Actor<TLogic> {
167
+ /**
168
+ * Reactive snapshot of current actor state.
169
+ *
170
+ * Infrastructure observes this signal to react to state changes without
171
+ * directly coupling to the Actor's internal state machine implementation.
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * // Infrastructure observes state signal
176
+ * const watcher = new Signal.subtle.Watcher(() => {
177
+ * console.log('Actor state changed:', actor.state.get());
178
+ * });
179
+ * watcher.watch(actor.state);
180
+ * ```
181
+ */
182
+ public abstract state: Signal.State<any>;
183
+
184
+ /**
185
+ * Send event to Actor
186
+ *
187
+ * Infrastructure forwards user intents (navigation, domain events, custom events)
188
+ * as events to the Actor. The Actor's state machine guards determine whether
189
+ * each event is valid from the current state.
190
+ *
191
+ * @param event - Event object with type property (e.g., PlayEvent, PlayRouteEvent)
192
+ *
193
+ * Invariant: Actor Authority - Only Actor decides whether an event is valid.
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * // Infrastructure forwards user intent
198
+ * actor.send({ type: 'auth.login', userId: '123' });
199
+ * // Actor's guards determine if event is allowed
200
+ * ```
201
+ *
202
+ * @remarks
203
+ * Accepts any event object with a type property. Core events (PlayEvent) are in
204
+ * @xmachines/play, routing events (PlayRouteEvent) are in @xmachines/play-router.
205
+ */
206
+ public abstract override send(event: { readonly type: string } & Record<string, any>): void;
207
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @xmachines/play-actor - Abstract Actor base class for Play Architecture
3
+ *
4
+ * This package provides AbstractActor, a minimal base class that extends XState Actor
5
+ * while enforcing the Play Architecture's signal protocol (RFC section 5.3).
6
+ *
7
+ * The core protocol is minimal (state + send). Optional capabilities are provided
8
+ * via interfaces:
9
+ * - Routable: For actors that support routing
10
+ * - Viewable: For actors that support view rendering
11
+ *
12
+ * Maintains XState ecosystem compatibility (inspection, devtools) while exposing
13
+ * reactive signals for Infrastructure layer communication.
14
+ *
15
+ * @packageDocumentation
16
+ * @see {@link https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md#53-actor-protocol | RFC Play v1 Section 5.3}
17
+ */
18
+
19
+ export { AbstractActor, type Routable, type Viewable } from "./abstract-actor.js";