@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 +218 -0
- package/dist/abstract-actor.d.ts +204 -0
- package/dist/abstract-actor.d.ts.map +1 -0
- package/dist/abstract-actor.js +79 -0
- package/dist/abstract-actor.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/src/abstract-actor.ts +207 -0
- package/src/index.ts +19 -0
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"}
|
package/dist/index.d.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
|
+
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";
|