@xmachines/play-actor 1.0.0-beta.9 → 1.1.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/LICENSE +21 -0
- package/README.md +142 -159
- package/dist/abstract-actor.d.ts +130 -150
- package/dist/abstract-actor.d.ts.map +1 -1
- package/dist/abstract-actor.js +38 -45
- package/dist/abstract-actor.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/provider-guards.d.ts +54 -0
- package/dist/provider-guards.d.ts.map +1 -0
- package/dist/provider-guards.js +69 -0
- package/dist/provider-guards.js.map +1 -0
- package/package.json +22 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikael Karon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,219 +1,202 @@
|
|
|
1
1
|
# @xmachines/play-actor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Abstract Actor base class for XMachines Play Architecture.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.npmjs.com/package/@xmachines/play-actor)
|
|
6
7
|
|
|
7
|
-
|
|
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)).
|
|
8
|
+
Part of the [xmachines-js monorepo](../../README.md).
|
|
18
9
|
|
|
19
10
|
## Installation
|
|
20
11
|
|
|
21
12
|
```bash
|
|
22
|
-
|
|
23
|
-
npm install @xmachines/play-actor
|
|
13
|
+
pnpm add @xmachines/play-actor
|
|
24
14
|
```
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
**Peer dependencies** — install alongside the package:
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
**Peer dependencies:**
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add xstate @xmachines/play @xmachines/play-signals
|
|
20
|
+
```
|
|
33
21
|
|
|
34
|
-
|
|
35
|
-
- `@xmachines/play-signals` - TC39 Signals primitives
|
|
36
|
-
- `@xmachines/play` - Protocol types (PlayEvent, etc.)
|
|
22
|
+
## Overview
|
|
37
23
|
|
|
38
|
-
|
|
24
|
+
`@xmachines/play-actor` provides `AbstractActor`, a minimal base class that extends the XState `Actor` class while enforcing the Play Architecture's **signal protocol** (RFC section 5.3). It exposes reactive TC39 Signals for infrastructure-layer communication while preserving full XState ecosystem compatibility (devtools, inspection).
|
|
39
25
|
|
|
40
|
-
|
|
26
|
+
The core protocol is deliberately minimal:
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
| Property | Type | Description |
|
|
29
|
+
| -------- | ------------------------- | ---------------------------------------- |
|
|
30
|
+
| `state` | `Signal.State<unknown>` | Reactive snapshot of current actor state |
|
|
31
|
+
| `send` | `(event: TEvent) => void` | Event dispatch method |
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
const createPlayer = definePlayer({ machine, catalog });
|
|
47
|
-
const actor = createPlayer();
|
|
48
|
-
actor.start();
|
|
33
|
+
Optional capabilities are declared as separate interfaces — a concrete actor opts in only to what it needs:
|
|
49
34
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
| Interface | Property | Description |
|
|
36
|
+
| ---------- | ----------------------------------------------- | ------------------------------------- |
|
|
37
|
+
| `Routable` | `currentRoute: Signal.Computed<string \| null>` | Current route path derived from state |
|
|
38
|
+
| `Routable` | `initialRoute: string \| null` | Route the actor starts on |
|
|
39
|
+
| `Viewable` | `currentView: Signal.State<PlaySpec \| null>` | Current JSON-render view spec |
|
|
55
40
|
|
|
56
|
-
|
|
41
|
+
Concrete implementations are created by adapters such as [`@xmachines/play-xstate`](../play-xstate/README.md).
|
|
57
42
|
|
|
58
|
-
|
|
43
|
+
## API Summary
|
|
59
44
|
|
|
60
|
-
|
|
45
|
+
### `AbstractActor<TLogic, TEvent>`
|
|
61
46
|
|
|
62
|
-
|
|
47
|
+
Abstract base class extending XState `Actor<TLogic>`.
|
|
63
48
|
|
|
64
|
-
|
|
49
|
+
A subclass **is** the actor: hand the logic and its options to `super()` so one
|
|
50
|
+
instance holds the running machine, and reach XState's own `send` through the
|
|
51
|
+
prototype — `send` is declared abstract here only to narrow the event type, and
|
|
52
|
+
TypeScript forbids `super` calls to an abstract member.
|
|
65
53
|
|
|
66
|
-
|
|
54
|
+
```ts
|
|
55
|
+
import { AbstractActor } from "@xmachines/play-actor";
|
|
56
|
+
import { Signal } from "@xmachines/play-signals";
|
|
57
|
+
import { Actor, type ActorOptions, type AnyActorLogic } from "xstate";
|
|
58
|
+
|
|
59
|
+
class MyActor extends AbstractActor<AnyActorLogic> {
|
|
60
|
+
// Required: reactive state signal
|
|
61
|
+
state: Signal.State<unknown>;
|
|
62
|
+
|
|
63
|
+
constructor(logic: AnyActorLogic, options?: ActorOptions<AnyActorLogic>) {
|
|
64
|
+
super(logic, options);
|
|
65
|
+
this.state = new Signal.State(this.getSnapshot());
|
|
66
|
+
super.subscribe((snapshot) => this.state.set(snapshot));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Required: typed event dispatch
|
|
70
|
+
override send(event: { type: string }): void {
|
|
71
|
+
Actor.prototype.send.call(this, event);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
With a typed event union:
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
```ts
|
|
79
|
+
type AuthEvent = { type: "auth.login"; username: string } | { type: "auth.logout" };
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
class AuthActor extends AbstractActor<AnyActorLogic, AuthEvent> {
|
|
82
|
+
state = new Signal.State({ isAuthenticated: false, username: null });
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
override send(event: AuthEvent): void {
|
|
85
|
+
Actor.prototype.send.call(this, event);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
76
89
|
|
|
77
|
-
|
|
90
|
+
### `typedSpec<TContext>(spec)`
|
|
78
91
|
|
|
79
|
-
|
|
80
|
-
- `start(): void` - Start the actor
|
|
81
|
-
- `stop(): void` - Stop the actor
|
|
82
|
-
- `getSnapshot()` - Get current XState snapshot (typed as `SnapshotFrom<TLogic>`)
|
|
92
|
+
Identity helper that constrains a `PlaySpec` object's `contextProps` to keys of a specific machine context type. This enables compile-time validation and IDE autocomplete without any runtime cost.
|
|
83
93
|
|
|
84
|
-
|
|
94
|
+
```ts
|
|
95
|
+
import { typedSpec } from "@xmachines/play-actor";
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class PlayerActor<TLogic extends AnyActorLogic>
|
|
92
|
-
extends AbstractActor<TLogic>
|
|
93
|
-
implements Routable, Viewable
|
|
94
|
-
{
|
|
95
|
-
// Required: reactive state snapshot
|
|
96
|
-
state = new Signal.State<AnyMachineSnapshot>(this.getSnapshot() as AnyMachineSnapshot);
|
|
97
|
-
|
|
98
|
-
// Routable: derived navigation path
|
|
99
|
-
currentRoute = new Signal.Computed(() => {
|
|
100
|
-
return deriveRoute(this.state.get());
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Viewable: current UI structure — Signal.State, updated at state entry (not computed)
|
|
104
|
-
currentView = new Signal.State<ViewMetadata | null>(null);
|
|
105
|
-
|
|
106
|
-
// Viewable: component catalog
|
|
107
|
-
catalog: Record<string, unknown>;
|
|
108
|
-
|
|
109
|
-
constructor(logic: TLogic, catalog: Record<string, unknown>) {
|
|
110
|
-
super(logic);
|
|
111
|
-
this.catalog = catalog;
|
|
112
|
-
|
|
113
|
-
// Subscribe to XState transitions and update signals
|
|
114
|
-
this.subscribe((snapshot) => {
|
|
115
|
-
this.state.set(snapshot as AnyMachineSnapshot);
|
|
116
|
-
// Update currentView based on snapshot meta...
|
|
117
|
-
});
|
|
118
|
-
}
|
|
97
|
+
interface DashboardCtx {
|
|
98
|
+
username: string;
|
|
99
|
+
params: Record<string, string>;
|
|
100
|
+
query: Record<string, string>;
|
|
119
101
|
}
|
|
102
|
+
|
|
103
|
+
// In an XState machine meta block:
|
|
104
|
+
meta: {
|
|
105
|
+
view: typedSpec<DashboardCtx>({
|
|
106
|
+
root: "root",
|
|
107
|
+
contextProps: ["username"], // ✓ key of DashboardCtx
|
|
108
|
+
// contextProps: ["usernaem"], // ✗ compile error
|
|
109
|
+
elements: {
|
|
110
|
+
root: { type: "Dashboard", props: {}, children: [] },
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
120
113
|
}
|
|
121
114
|
```
|
|
122
115
|
|
|
123
|
-
|
|
116
|
+
### `PlaySpec`
|
|
124
117
|
|
|
125
|
-
|
|
118
|
+
Extends `@xmachines/json-render-core`'s `Spec` with an optional `contextProps` field — an explicit allowlist of machine context fields that are merged into element props at view derivation time.
|
|
126
119
|
|
|
127
|
-
|
|
120
|
+
```ts
|
|
121
|
+
import type { PlaySpec } from "@xmachines/play-actor";
|
|
128
122
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
queueMicrotask(() => {
|
|
137
|
-
const pending = watcher.getPending();
|
|
138
|
-
if (pending.length > 0) {
|
|
139
|
-
const route = actor.currentRoute.get();
|
|
140
|
-
if (route !== null) {
|
|
141
|
-
// Update browser URL (Passive Infrastructure)
|
|
142
|
-
window.history.replaceState(null, "", route);
|
|
143
|
-
}
|
|
144
|
-
watcher.watch(...pending); // Re-watch
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
watcher.watch(actor.currentRoute);
|
|
150
|
-
actor.currentRoute.get(); // Initial read
|
|
151
|
-
|
|
152
|
-
return () => watcher.unwatch(actor.currentRoute);
|
|
153
|
-
}
|
|
123
|
+
const spec: PlaySpec = {
|
|
124
|
+
root: "root",
|
|
125
|
+
contextProps: ["username"], // only these keys are exposed to components
|
|
126
|
+
elements: {
|
|
127
|
+
root: { type: "Profile", props: { username: undefined }, children: [] },
|
|
128
|
+
},
|
|
129
|
+
};
|
|
154
130
|
```
|
|
155
131
|
|
|
156
|
-
###
|
|
132
|
+
### `Routable`
|
|
157
133
|
|
|
158
|
-
|
|
159
|
-
import { AbstractActor } from "@xmachines/play-actor";
|
|
134
|
+
Interface for actors that support routing.
|
|
160
135
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Browser event sent to actor (Actor Authority)
|
|
166
|
-
// Actor guards decide if navigation is valid
|
|
167
|
-
actor.send({ type: "play.route", to: path });
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
window.addEventListener("popstate", handlePopstate);
|
|
136
|
+
```ts
|
|
137
|
+
import type { Routable } from "@xmachines/play-actor";
|
|
138
|
+
import { Signal } from "@xmachines/play-signals";
|
|
171
139
|
|
|
172
|
-
|
|
173
|
-
|
|
140
|
+
// Implement in a concrete actor (note: RoutableActor interface is exported from @xmachines/play-router):
|
|
141
|
+
class MyRoutableActor extends AbstractActor<AnyActorLogic> implements Routable {
|
|
142
|
+
state = new Signal.State({});
|
|
143
|
+
currentRoute = new Signal.Computed(() => this.state.get().path ?? null);
|
|
144
|
+
initialRoute = "/";
|
|
145
|
+
send = (event) => {
|
|
146
|
+
/* dispatch */
|
|
174
147
|
};
|
|
175
148
|
}
|
|
176
149
|
```
|
|
177
150
|
|
|
178
|
-
|
|
151
|
+
### `Viewable`
|
|
179
152
|
|
|
180
|
-
|
|
153
|
+
Interface for actors that expose a renderable view signal.
|
|
181
154
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
155
|
+
```ts
|
|
156
|
+
import type { Viewable } from "@xmachines/play-actor";
|
|
157
|
+
import type { PlaySpec } from "@xmachines/play-actor";
|
|
158
|
+
import { Signal } from "@xmachines/play-signals";
|
|
159
|
+
|
|
160
|
+
// currentView carries PlaySpec | null
|
|
161
|
+
const signal = new Signal.State<PlaySpec | null>(null);
|
|
162
|
+
const viewable: Viewable = { currentView: signal };
|
|
163
|
+
```
|
|
186
164
|
|
|
187
|
-
|
|
188
|
-
- All reactive state exposed via TC39 Signals
|
|
189
|
-
- Infrastructure uses `Signal.subtle.Watcher` to observe
|
|
190
|
-
- No direct queries (`getSnapshot()` for internal use only)
|
|
165
|
+
### `BaseActorProviderProps<TRegistry>`
|
|
191
166
|
|
|
192
|
-
|
|
193
|
-
- Infrastructure reflects actor state (via signals)
|
|
194
|
-
- Infrastructure never decides transitions
|
|
195
|
-
- Browser/router events sent as commands to actor
|
|
167
|
+
Framework-agnostic base props shared by every `ActorProvider` implementation (React, Vue, Solid, Svelte). Framework renderer packages extend this interface.
|
|
196
168
|
|
|
197
|
-
|
|
169
|
+
```ts
|
|
170
|
+
import type { BaseActorProviderProps } from "@xmachines/play-actor";
|
|
171
|
+
import type { DefineRegistryResult } from "@xmachines/json-render-react";
|
|
198
172
|
|
|
199
|
-
|
|
173
|
+
interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
|
|
174
|
+
fallback?: React.ReactNode;
|
|
175
|
+
children: React.ReactNode;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
200
178
|
|
|
201
|
-
|
|
202
|
-
- **Inspection API:** XState Inspector can attach to actors
|
|
203
|
-
- **DevTools Integration:** Standard XState devtools work
|
|
204
|
-
- **Ecosystem Tools:** Works with XState visualization, testing libraries
|
|
179
|
+
### `BaseViewContextValue<TRegistry>`
|
|
205
180
|
|
|
206
|
-
|
|
181
|
+
Framework-agnostic base for every framework's `ViewContextValue`. Holds `spec`, `handlers`, `registry`, and `store` fields that are identical across React, Vue, Solid, and Svelte.
|
|
207
182
|
|
|
208
|
-
##
|
|
183
|
+
## Testing
|
|
209
184
|
|
|
210
|
-
|
|
211
|
-
- **[@xmachines/play-signals](../play-signals)** - TC39 Signals primitives
|
|
212
|
-
- **[@xmachines/play](../play)** - Protocol types (PlayEvent, RouterBridge)
|
|
185
|
+
Run the test suite for this package in isolation:
|
|
213
186
|
|
|
214
|
-
|
|
187
|
+
```bash
|
|
188
|
+
# From the package directory
|
|
189
|
+
pnpm test
|
|
190
|
+
|
|
191
|
+
# From the monorepo root (workspace-scoped)
|
|
192
|
+
pnpm --filter @xmachines/play-actor test
|
|
193
|
+
|
|
194
|
+
# Watch mode
|
|
195
|
+
pnpm --filter @xmachines/play-actor run test:watch
|
|
196
|
+
```
|
|
215
197
|
|
|
216
|
-
|
|
198
|
+
## Requirements
|
|
217
199
|
|
|
218
|
-
|
|
219
|
-
|
|
200
|
+
- **Node.js** `>=22.0.0`
|
|
201
|
+
- **TypeScript** `>=5.7` (strict mode)
|
|
202
|
+
- **ESM only** — `"type": "module"`
|
package/dist/abstract-actor.d.ts
CHANGED
|
@@ -16,205 +16,185 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @packageDocumentation
|
|
18
18
|
*/
|
|
19
|
-
import { Actor, type AnyActorLogic } from "xstate";
|
|
19
|
+
import { Actor, type AnyActorLogic, type EventObject } from "xstate";
|
|
20
20
|
import type { Signal } from "@xmachines/play-signals";
|
|
21
|
+
import type { Spec, StateStore, RenderErrorHandler, ActionHandler } from "@xmachines/json-render-core";
|
|
21
22
|
/**
|
|
22
23
|
* Optional capability: Routing support
|
|
24
|
+
*/
|
|
25
|
+
export interface Routable {
|
|
26
|
+
readonly currentRoute: Signal.Computed<string | null>;
|
|
27
|
+
readonly initialRoute: string | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* XMachines extension of `@xmachines/json-render-core` `Spec`.
|
|
23
31
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
32
|
+
* Adds `contextProps` — an explicit allowlist of machine context fields that
|
|
33
|
+
* `deriveCurrentView` merges into element props as low-priority slots. Only
|
|
34
|
+
* fields named here are ever exposed to components; nothing leaks from context
|
|
35
|
+
* without an opt-in declaration.
|
|
26
36
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
-
* ```
|
|
37
|
+
* Use `typedSpec<TContext>(...)` at the definition site to validate `contextProps`
|
|
38
|
+
* entries against your machine's context type at compile time.
|
|
38
39
|
*/
|
|
39
|
-
export interface
|
|
40
|
+
export interface PlaySpec extends Spec {
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* Invariant: Passive Infrastructure - Infrastructure reflects route, never decides.
|
|
42
|
+
* Explicit allowlist of machine context field names to expose as prop slots.
|
|
43
|
+
* Each named field is merged into every spec element's `props` at view derivation
|
|
44
|
+
* time, filling any slot whose current value is `undefined`.
|
|
46
45
|
*
|
|
47
|
-
*
|
|
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
|
-
* ```
|
|
46
|
+
* Use `typedSpec<TContext>(...)` to constrain entries to `keyof TContext & string`.
|
|
55
47
|
*/
|
|
56
|
-
readonly
|
|
48
|
+
readonly contextProps?: readonly string[];
|
|
57
49
|
}
|
|
58
50
|
/**
|
|
59
|
-
*
|
|
51
|
+
* Identity helper that constrains a `PlaySpec` object's `contextProps` to keys
|
|
52
|
+
* of a specific machine context type, giving compile-time validation and IDE
|
|
53
|
+
* autocomplete at the definition site.
|
|
54
|
+
*
|
|
55
|
+
* XState's `meta` field is typed as `Record<string, unknown>`, so TypeScript
|
|
56
|
+
* cannot infer the constraint from context. `typedSpec<MyCtx>(...)` is the
|
|
57
|
+
* opt-in mechanism that activates enforcement where the spec is written.
|
|
60
58
|
*
|
|
61
|
-
*
|
|
62
|
-
* Renderers observe the currentView signal to update the UI.
|
|
59
|
+
* At runtime this is a no-op — the spec object is returned unchanged.
|
|
63
60
|
*
|
|
64
61
|
* @example
|
|
65
|
-
* ```
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* interface DashboardCtx {
|
|
64
|
+
* username: string;
|
|
65
|
+
* params: Record<string, string>;
|
|
66
|
+
* query: Record<string, string>;
|
|
69
67
|
* }
|
|
70
68
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
69
|
+
* meta: {
|
|
70
|
+
* view: typedSpec<DashboardCtx>({
|
|
71
|
+
* root: "root",
|
|
72
|
+
* contextProps: ["username"], // ✓ key of DashboardCtx
|
|
73
|
+
* // contextProps: ["usernaem"], // ✗ compile error
|
|
74
|
+
* elements: { root: { type: "Dashboard", props: {}, children: [] } },
|
|
75
|
+
* }),
|
|
75
76
|
* }
|
|
76
77
|
* ```
|
|
77
78
|
*/
|
|
79
|
+
export declare function typedSpec<TContext extends object>(spec: Omit<PlaySpec, "contextProps"> & {
|
|
80
|
+
readonly contextProps?: readonly (keyof TContext & string)[];
|
|
81
|
+
}): PlaySpec;
|
|
78
82
|
/**
|
|
79
|
-
*
|
|
83
|
+
* Actor capability for exposing renderable view state.
|
|
80
84
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
85
|
+
* `Viewable` marks actors that publish a `currentView` signal.
|
|
86
|
+
* Renderers such as `PlayRenderer` consume this contract to resolve the
|
|
87
|
+
* current view description into concrete UI without embedding view logic inside the
|
|
88
|
+
* framework adapter.
|
|
83
89
|
*/
|
|
84
|
-
export interface ViewMetadata {
|
|
85
|
-
/** Name of the component to render from the catalog */
|
|
86
|
-
component: string;
|
|
87
|
-
/** Props to pass to the component */
|
|
88
|
-
props: Record<string, unknown>;
|
|
89
|
-
}
|
|
90
90
|
export interface Viewable {
|
|
91
91
|
/**
|
|
92
|
-
* Current view signal
|
|
93
|
-
*
|
|
94
|
-
* State signal containing UI structure schema from meta.view. Infrastructure renders view.
|
|
95
|
-
*
|
|
96
|
-
* Invariant: Logic-Driven UI - View structure is defined by business logic, not JSX.
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```typescript
|
|
100
|
-
* const watcher = new Signal.subtle.Watcher(() => {
|
|
101
|
-
* const view = actor.currentView.get();
|
|
102
|
-
* console.log('View changed:', view);
|
|
103
|
-
* });
|
|
104
|
-
* watcher.watch(actor.currentView);
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
readonly currentView: Signal.State<ViewMetadata | null>;
|
|
108
|
-
/**
|
|
109
|
-
* Component catalog for view resolution
|
|
92
|
+
* Current view signal. Contains the json-render PlaySpec for the current machine
|
|
93
|
+
* state, or null when no view is active.
|
|
110
94
|
*
|
|
111
|
-
*
|
|
112
|
-
* Used by renderers to resolve view.component to actual UI components.
|
|
95
|
+
* Infrastructure renders view — Logic-Driven UI invariant.
|
|
113
96
|
*/
|
|
114
|
-
readonly
|
|
97
|
+
readonly currentView: Signal.State<PlaySpec | null>;
|
|
115
98
|
}
|
|
116
99
|
/**
|
|
117
|
-
*
|
|
100
|
+
* Framework-agnostic base for every framework's `ViewContextValue`.
|
|
118
101
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* The core protocol contains only:
|
|
123
|
-
* - state: Reactive state snapshot
|
|
124
|
-
* - send: Event dispatch method
|
|
102
|
+
* Holds the three fields that are identical across React, Vue, Solid, and Svelte.
|
|
103
|
+
* `registry` is framework-specific (each framework has its own `ComponentRegistry`
|
|
104
|
+
* type) so it is typed via `TRegistry` — the same generic used in `BaseActorProviderProps`.
|
|
125
105
|
*
|
|
126
|
-
*
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
106
|
+
* @typeParam TRegistry - The framework's component registry type (e.g. `ComponentRegistry` from `@xmachines/json-render-react`).
|
|
107
|
+
*/
|
|
108
|
+
export interface BaseViewContextValue<TRegistry extends object> {
|
|
109
|
+
/** The current PlaySpec to render. */
|
|
110
|
+
spec: PlaySpec;
|
|
111
|
+
/** Action handlers resolved against the live StateStore. */
|
|
112
|
+
handlers: Record<string, ActionHandler>;
|
|
113
|
+
/** Component registry from registryResult.registry. */
|
|
114
|
+
registry: TRegistry;
|
|
115
|
+
/** The active StateStore — pass to JSONUIProvider/JsonUIProvider as `store` to share state across providers. */
|
|
116
|
+
store: StateStore;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Framework-agnostic base props shared by every `ActorProvider` implementation
|
|
120
|
+
* (React, Vue, Solid, Svelte). `TRegistry` captures the framework-specific
|
|
121
|
+
* `DefineRegistryResult` type; `RenderErrorHandler` is sourced from
|
|
122
|
+
* `@xmachines/json-render-core` so no second generic is needed.
|
|
131
123
|
*
|
|
132
|
-
*
|
|
124
|
+
* Framework packages extend this with their `fallback`, `onError`, and `children` fields.
|
|
133
125
|
*
|
|
134
|
-
*
|
|
135
|
-
* Invariant: Signal-Only Reactivity - Infrastructure observes via TC39 Signals.
|
|
136
|
-
* Invariant: Passive Infrastructure - Infrastructure reflects, never decides.
|
|
126
|
+
* @typeParam TRegistry - The framework's `DefineRegistryResult` type.
|
|
137
127
|
*
|
|
138
128
|
* @example
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* state = new Signal.State({...});
|
|
143
|
-
* send(event) { ... }
|
|
144
|
-
* }
|
|
145
|
-
* ```
|
|
129
|
+
* ```ts
|
|
130
|
+
* import type { BaseActorProviderProps } from "@xmachines/play-actor";
|
|
131
|
+
* import type { DefineRegistryResult } from "@xmachines/json-render-react";
|
|
146
132
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* class RoutableActor extends AbstractActor<AnyActorLogic> implements Routable {
|
|
151
|
-
* state = new Signal.State({...});
|
|
152
|
-
* currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
|
|
153
|
-
* send(event) { ... }
|
|
133
|
+
* interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
|
|
134
|
+
* fallback?: React.ReactNode;
|
|
135
|
+
* children: React.ReactNode;
|
|
154
136
|
* }
|
|
155
137
|
* ```
|
|
138
|
+
*/
|
|
139
|
+
export interface BaseActorProviderProps<TRegistry extends {
|
|
140
|
+
registry: object;
|
|
141
|
+
handlers: (...args: never[]) => unknown;
|
|
142
|
+
}> {
|
|
143
|
+
/** Actor instance with currentView signal (requires Viewable capability). */
|
|
144
|
+
actor: AbstractActor<AnyActorLogic> & Viewable;
|
|
145
|
+
/** Full result from defineRegistry() — contains the component registry and action handlers factory. */
|
|
146
|
+
registryResult: TRegistry;
|
|
147
|
+
/**
|
|
148
|
+
* Optional external StateStore (controlled mode).
|
|
149
|
+
* When provided, spec.state is ignored and this store is the single source of truth.
|
|
150
|
+
* When omitted, a fresh @xstate/store atom is created per view transition from spec.state.
|
|
151
|
+
*/
|
|
152
|
+
store?: StateStore;
|
|
153
|
+
/**
|
|
154
|
+
* Called when an individual catalog component throws during render.
|
|
155
|
+
* Takes precedence over any onRenderError set via defineRegistry.
|
|
156
|
+
*/
|
|
157
|
+
onRenderError?: RenderErrorHandler;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Abstract base class for Play Architecture actors.
|
|
156
161
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* class PlayerActor extends AbstractActor<AnyActorLogic> implements Routable, Viewable {
|
|
161
|
-
* state = new Signal.State({...});
|
|
162
|
-
* currentRoute = new Signal.Computed(() => deriveRoute(this.state.get()));
|
|
163
|
-
* currentView = new Signal.State(null);
|
|
164
|
-
* catalog = {};
|
|
165
|
-
* send(event) { ... }
|
|
166
|
-
* }
|
|
167
|
-
* ```
|
|
162
|
+
* Provides signal-driven state observation that integrates with XState ecosystem
|
|
163
|
+
* tooling (devtools, inspection) while exposing reactive signals for
|
|
164
|
+
* Infrastructure layer communication.
|
|
168
165
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
166
|
+
* **A subclass IS the actor.** Forward the logic *and* its options to
|
|
167
|
+
* `super(logic, options)`, then observe `this`. Holding a separately
|
|
168
|
+
* constructed actor alongside leaves this instance running as an empty second
|
|
169
|
+
* actor, and every inherited member — `system`, `sessionId`, `clock`, the
|
|
170
|
+
* internal `_send` that receives `sendTo()` traffic, and anything a future
|
|
171
|
+
* XState version adds — answers from that empty one until it is individually
|
|
172
|
+
* forwarded.
|
|
173
|
+
*
|
|
174
|
+
* @typeParam TLogic - XState actor logic type
|
|
175
|
+
* @typeParam TEvent - Event type constraint (defaults to EventObject)
|
|
172
176
|
*/
|
|
173
|
-
export declare abstract class AbstractActor<TLogic extends AnyActorLogic> extends Actor<TLogic> {
|
|
177
|
+
export declare abstract class AbstractActor<TLogic extends AnyActorLogic, TEvent extends EventObject = EventObject> extends Actor<TLogic> {
|
|
174
178
|
/**
|
|
175
179
|
* Reactive snapshot of current actor state.
|
|
176
180
|
*
|
|
177
|
-
* Typed as `Signal.State<unknown>` at the abstract level; concrete implementations
|
|
178
|
-
* narrow this to the actual snapshot type (e.g., `Signal.State<AnyMachineSnapshot>`
|
|
179
|
-
* in `@xmachines/play-xstate`'s `PlayerActor`).
|
|
180
|
-
*
|
|
181
181
|
* Infrastructure observes this signal to react to state changes without
|
|
182
|
-
* directly coupling to the
|
|
183
|
-
*
|
|
184
|
-
* @example
|
|
185
|
-
* ```typescript
|
|
186
|
-
* // Infrastructure observes state signal
|
|
187
|
-
* const watcher = new Signal.subtle.Watcher(() => {
|
|
188
|
-
* console.log('Actor state changed:', actor.state.get());
|
|
189
|
-
* });
|
|
190
|
-
* watcher.watch(actor.state);
|
|
191
|
-
* ```
|
|
182
|
+
* directly coupling to the actor's internal state machine implementation.
|
|
192
183
|
*/
|
|
193
184
|
abstract state: Signal.State<unknown>;
|
|
194
185
|
/**
|
|
195
|
-
* Send event to Actor
|
|
196
|
-
*
|
|
197
|
-
* Infrastructure forwards user intents (navigation, domain events, custom events)
|
|
198
|
-
* as events to the Actor. The Actor's state machine guards determine whether
|
|
199
|
-
* each event is valid from the current state.
|
|
200
|
-
*
|
|
201
|
-
* @param event - Event object with type property (e.g., PlayEvent, PlayRouteEvent)
|
|
202
|
-
*
|
|
203
|
-
* Invariant: Actor Authority - Only Actor decides whether an event is valid.
|
|
186
|
+
* Send event to Actor.
|
|
204
187
|
*
|
|
205
|
-
*
|
|
206
|
-
* ```typescript
|
|
207
|
-
* // Infrastructure forwards user intent
|
|
208
|
-
* actor.send({ type: 'auth.login', userId: '123' });
|
|
209
|
-
* // Actor's guards determine if event is allowed
|
|
210
|
-
* ```
|
|
188
|
+
* Constrained to TEvent for type safety in concrete implementations.
|
|
211
189
|
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
190
|
+
* Note for implementations that wrap `send` (validating the event, or
|
|
191
|
+
* notifying hooks around it): this declaration is abstract purely to narrow
|
|
192
|
+
* the event type, and TypeScript forbids `super` calls to an abstract
|
|
193
|
+
* member. Reach XState's own implementation with
|
|
194
|
+
* `Actor.prototype.send.call(this, event)` instead. Making this concrete
|
|
195
|
+
* would allow `super.send()` but would force every existing subclass to add
|
|
196
|
+
* an `override` modifier — a breaking change for adapters outside this repo.
|
|
215
197
|
*/
|
|
216
|
-
abstract send(event:
|
|
217
|
-
readonly type: string;
|
|
218
|
-
} & Record<string, unknown>): void;
|
|
198
|
+
abstract send(event: TEvent): void;
|
|
219
199
|
}
|
|
220
200
|
//# sourceMappingURL=abstract-actor.d.ts.map
|
|
@@ -1 +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;
|
|
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,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EACX,IAAI,EACJ,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,MAAM,6BAA6B,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAS,SAAQ,IAAI;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,SAAS,CAAC,QAAQ,SAAS,MAAM,EAChD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,GAAG;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC;CAC7D,GACC,QAAQ,CAEV;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;CACpD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB,CAAC,SAAS,SAAS,MAAM;IAC7D,sCAAsC;IACtC,IAAI,EAAE,QAAQ,CAAC;IACf,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACxC,uDAAuD;IACvD,QAAQ,EAAE,SAAS,CAAC;IACpB,gHAAgH;IAChH,KAAK,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,sBAAsB,CACtC,SAAS,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAA;CAAE;IAE/E,6EAA6E;IAC7E,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;IAC/C,uGAAuG;IACvG,cAAc,EAAE,SAAS,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,8BAAsB,aAAa,CAClC,MAAM,SAAS,aAAa,EAC5B,MAAM,SAAS,WAAW,GAAG,WAAW,CACvC,SAAQ,KAAK,CAAC,MAAM,CAAC;IACtB;;;;;OAKG;IACH,SAAgB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAE7C;;;;;;;;;;;;OAYG;aACsB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAClD"}
|
package/dist/abstract-actor.js
CHANGED
|
@@ -18,61 +18,54 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { Actor } from "xstate";
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
21
|
+
* Identity helper that constrains a `PlaySpec` object's `contextProps` to keys
|
|
22
|
+
* of a specific machine context type, giving compile-time validation and IDE
|
|
23
|
+
* autocomplete at the definition site.
|
|
29
24
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* -
|
|
25
|
+
* XState's `meta` field is typed as `Record<string, unknown>`, so TypeScript
|
|
26
|
+
* cannot infer the constraint from context. `typedSpec<MyCtx>(...)` is the
|
|
27
|
+
* opt-in mechanism that activates enforcement where the spec is written.
|
|
33
28
|
*
|
|
34
|
-
*
|
|
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.
|
|
29
|
+
* At runtime this is a no-op — the spec object is returned unchanged.
|
|
41
30
|
*
|
|
42
31
|
* @example
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* interface DashboardCtx {
|
|
34
|
+
* username: string;
|
|
35
|
+
* params: Record<string, string>;
|
|
36
|
+
* query: Record<string, string>;
|
|
48
37
|
* }
|
|
49
|
-
* ```
|
|
50
38
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
39
|
+
* meta: {
|
|
40
|
+
* view: typedSpec<DashboardCtx>({
|
|
41
|
+
* root: "root",
|
|
42
|
+
* contextProps: ["username"], // ✓ key of DashboardCtx
|
|
43
|
+
* // contextProps: ["usernaem"], // ✗ compile error
|
|
44
|
+
* elements: { root: { type: "Dashboard", props: {}, children: [] } },
|
|
45
|
+
* }),
|
|
58
46
|
* }
|
|
59
47
|
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function typedSpec(spec) {
|
|
50
|
+
return spec;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Abstract base class for Play Architecture actors.
|
|
60
54
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
55
|
+
* Provides signal-driven state observation that integrates with XState ecosystem
|
|
56
|
+
* tooling (devtools, inspection) while exposing reactive signals for
|
|
57
|
+
* Infrastructure layer communication.
|
|
58
|
+
*
|
|
59
|
+
* **A subclass IS the actor.** Forward the logic *and* its options to
|
|
60
|
+
* `super(logic, options)`, then observe `this`. Holding a separately
|
|
61
|
+
* constructed actor alongside leaves this instance running as an empty second
|
|
62
|
+
* actor, and every inherited member — `system`, `sessionId`, `clock`, the
|
|
63
|
+
* internal `_send` that receives `sendTo()` traffic, and anything a future
|
|
64
|
+
* XState version adds — answers from that empty one until it is individually
|
|
65
|
+
* forwarded.
|
|
72
66
|
*
|
|
73
|
-
* @
|
|
74
|
-
* @
|
|
75
|
-
* @see {@link Viewable} for view rendering capability
|
|
67
|
+
* @typeParam TLogic - XState actor logic type
|
|
68
|
+
* @typeParam TEvent - Event type constraint (defaults to EventObject)
|
|
76
69
|
*/
|
|
77
70
|
export class AbstractActor extends Actor {
|
|
78
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abstract-actor.js","sourceRoot":"","sources":["../src/abstract-actor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"abstract-actor.js","sourceRoot":"","sources":["../src/abstract-actor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAwC,MAAM,QAAQ,CAAC;AAuCrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,SAAS,CACxB,IAEC;IAED,OAAO,IAAgB,CAAC;AACzB,CAAC;AAiFD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAgB,aAGpB,SAAQ,KAAa;CAuBtB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* reactive signals for Infrastructure layer communication.
|
|
14
14
|
*
|
|
15
15
|
* @packageDocumentation
|
|
16
|
-
* @see
|
|
16
|
+
* @see [Play RFC](../../docs/rfc/play.md)
|
|
17
17
|
*/
|
|
18
|
-
export { AbstractActor, type Routable, type Viewable, type
|
|
18
|
+
export { AbstractActor, typedSpec, type Routable, type Viewable, type PlaySpec, type BaseActorProviderProps, type BaseViewContextValue, } from "./abstract-actor.js";
|
|
19
|
+
export { toAtomState, attachRenderErrorHandler } from "./provider-guards.js";
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACN,aAAa,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACN,aAAa,EACb,SAAS,EACT,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* reactive signals for Infrastructure layer communication.
|
|
14
14
|
*
|
|
15
15
|
* @packageDocumentation
|
|
16
|
-
* @see
|
|
16
|
+
* @see [Play RFC](../../docs/rfc/play.md)
|
|
17
17
|
*/
|
|
18
|
-
export { AbstractActor, } from "./abstract-actor.js";
|
|
18
|
+
export { AbstractActor, typedSpec, } from "./abstract-actor.js";
|
|
19
|
+
export { toAtomState, attachRenderErrorHandler } from "./provider-guards.js";
|
|
19
20
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACN,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACN,aAAa,EACb,SAAS,GAMT,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared provider guards for framework ActorProvider implementations.
|
|
3
|
+
*
|
|
4
|
+
* Every framework integration (React, Vue, Solid, Svelte, DOM) performs the same
|
|
5
|
+
* two rituals when bridging an actor's view to a json-render tree:
|
|
6
|
+
*
|
|
7
|
+
* 1. Sanitize `spec.state` before seeding a fresh `@xstate/store` atom
|
|
8
|
+
* ({@link toAtomState}).
|
|
9
|
+
* 2. Inject a component-level `onRenderError` handler into the component
|
|
10
|
+
* registry without mutating the caller's registry
|
|
11
|
+
* ({@link attachRenderErrorHandler}).
|
|
12
|
+
*
|
|
13
|
+
* Centralising them here keeps the guard semantics identical across frameworks.
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
import type { RenderErrorHandler } from "@xmachines/json-render-core";
|
|
18
|
+
/**
|
|
19
|
+
* Safely coerce a spec's `state` field to a plain object for `createAtom`.
|
|
20
|
+
*
|
|
21
|
+
* `spec.state` is typed as `unknown` in `PlaySpec`. At runtime it can be
|
|
22
|
+
* `null`, `undefined`, a primitive, or a plain object depending on what the
|
|
23
|
+
* machine author put in the view spec. `createAtom` requires a plain object as
|
|
24
|
+
* its initial value — anything else produces a broken store at runtime.
|
|
25
|
+
*
|
|
26
|
+
* Only plain objects (prototype is `Object.prototype` or `null`) are accepted
|
|
27
|
+
* and returned as-is. Everything else — `null`, `undefined`, primitives,
|
|
28
|
+
* arrays, class instances, and built-in objects (Date, Map, Set, etc.) — falls
|
|
29
|
+
* back to a fresh `{}`, preventing silent broken-store bugs at runtime.
|
|
30
|
+
*
|
|
31
|
+
* @param state - The raw `spec.state` value from a `PlaySpec`.
|
|
32
|
+
* @returns `state` itself when it is a plain object, otherwise a new empty object.
|
|
33
|
+
*/
|
|
34
|
+
export declare function toAtomState(state: unknown): Record<string, unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* Clone a component registry and inject an `onRenderError` handler.
|
|
37
|
+
*
|
|
38
|
+
* The handler is defined as a non-enumerable, configurable own property on the
|
|
39
|
+
* clone (per D-19 — one injection convention for all framework renderers), so
|
|
40
|
+
* it overrides any handler set at `defineRegistry` level without showing up
|
|
41
|
+
* when the registry's component entries are enumerated.
|
|
42
|
+
*
|
|
43
|
+
* The caller's registry is never mutated — a shallow clone is returned. Callers
|
|
44
|
+
* that need per-instance handlers (e.g. an `onRenderError` prop on
|
|
45
|
+
* `ActorProvider`) can therefore share one `defineRegistry` result across
|
|
46
|
+
* providers safely.
|
|
47
|
+
*
|
|
48
|
+
* @typeParam TRegistry - The framework-specific component registry type.
|
|
49
|
+
* @param registry - The component registry from `defineRegistry().registry`.
|
|
50
|
+
* @param handler - Called with `(error, componentName)` when a catalog component throws during render.
|
|
51
|
+
* @returns A shallow clone of `registry` with `onRenderError` attached.
|
|
52
|
+
*/
|
|
53
|
+
export declare function attachRenderErrorHandler<TRegistry extends object>(registry: TRegistry, handler: RenderErrorHandler): TRegistry;
|
|
54
|
+
//# sourceMappingURL=provider-guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-guards.d.ts","sourceRoot":"","sources":["../src/provider-guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQnE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,SAAS,MAAM,EAChE,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE,kBAAkB,GACzB,SAAS,CAQX"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared provider guards for framework ActorProvider implementations.
|
|
3
|
+
*
|
|
4
|
+
* Every framework integration (React, Vue, Solid, Svelte, DOM) performs the same
|
|
5
|
+
* two rituals when bridging an actor's view to a json-render tree:
|
|
6
|
+
*
|
|
7
|
+
* 1. Sanitize `spec.state` before seeding a fresh `@xstate/store` atom
|
|
8
|
+
* ({@link toAtomState}).
|
|
9
|
+
* 2. Inject a component-level `onRenderError` handler into the component
|
|
10
|
+
* registry without mutating the caller's registry
|
|
11
|
+
* ({@link attachRenderErrorHandler}).
|
|
12
|
+
*
|
|
13
|
+
* Centralising them here keeps the guard semantics identical across frameworks.
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Safely coerce a spec's `state` field to a plain object for `createAtom`.
|
|
19
|
+
*
|
|
20
|
+
* `spec.state` is typed as `unknown` in `PlaySpec`. At runtime it can be
|
|
21
|
+
* `null`, `undefined`, a primitive, or a plain object depending on what the
|
|
22
|
+
* machine author put in the view spec. `createAtom` requires a plain object as
|
|
23
|
+
* its initial value — anything else produces a broken store at runtime.
|
|
24
|
+
*
|
|
25
|
+
* Only plain objects (prototype is `Object.prototype` or `null`) are accepted
|
|
26
|
+
* and returned as-is. Everything else — `null`, `undefined`, primitives,
|
|
27
|
+
* arrays, class instances, and built-in objects (Date, Map, Set, etc.) — falls
|
|
28
|
+
* back to a fresh `{}`, preventing silent broken-store bugs at runtime.
|
|
29
|
+
*
|
|
30
|
+
* @param state - The raw `spec.state` value from a `PlaySpec`.
|
|
31
|
+
* @returns `state` itself when it is a plain object, otherwise a new empty object.
|
|
32
|
+
*/
|
|
33
|
+
export function toAtomState(state) {
|
|
34
|
+
if (state !== null && typeof state === "object" && !Array.isArray(state)) {
|
|
35
|
+
const proto = Object.getPrototypeOf(state);
|
|
36
|
+
if (proto === Object.prototype || proto === null) {
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Clone a component registry and inject an `onRenderError` handler.
|
|
44
|
+
*
|
|
45
|
+
* The handler is defined as a non-enumerable, configurable own property on the
|
|
46
|
+
* clone (per D-19 — one injection convention for all framework renderers), so
|
|
47
|
+
* it overrides any handler set at `defineRegistry` level without showing up
|
|
48
|
+
* when the registry's component entries are enumerated.
|
|
49
|
+
*
|
|
50
|
+
* The caller's registry is never mutated — a shallow clone is returned. Callers
|
|
51
|
+
* that need per-instance handlers (e.g. an `onRenderError` prop on
|
|
52
|
+
* `ActorProvider`) can therefore share one `defineRegistry` result across
|
|
53
|
+
* providers safely.
|
|
54
|
+
*
|
|
55
|
+
* @typeParam TRegistry - The framework-specific component registry type.
|
|
56
|
+
* @param registry - The component registry from `defineRegistry().registry`.
|
|
57
|
+
* @param handler - Called with `(error, componentName)` when a catalog component throws during render.
|
|
58
|
+
* @returns A shallow clone of `registry` with `onRenderError` attached.
|
|
59
|
+
*/
|
|
60
|
+
export function attachRenderErrorHandler(registry, handler) {
|
|
61
|
+
const clone = { ...registry };
|
|
62
|
+
Object.defineProperty(clone, "onRenderError", {
|
|
63
|
+
value: handler,
|
|
64
|
+
enumerable: false,
|
|
65
|
+
configurable: true,
|
|
66
|
+
});
|
|
67
|
+
return clone;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=provider-guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-guards.js","sourceRoot":"","sources":["../src/provider-guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAY,CAAC;QACtD,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAClD,OAAO,KAAgC,CAAC;QACzC,CAAC;IACF,CAAC;IACD,OAAO,EAAE,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,wBAAwB,CACvC,QAAmB,EACnB,OAA2B;IAE3B,MAAM,KAAK,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC9B,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE;QAC7C,KAAK,EAAE,OAAO;QACd,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,IAAI;KAClB,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-actor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Abstract Actor base class for XMachines Play Architecture",
|
|
6
6
|
"keywords": [
|
|
@@ -12,12 +12,18 @@
|
|
|
12
12
|
],
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "XMachines Contributors",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+ssh://git@gitlab.com/xmachin-es/xmachines-js.git",
|
|
18
|
+
"directory": "packages/play-actor"
|
|
19
|
+
},
|
|
15
20
|
"files": [
|
|
16
21
|
"dist",
|
|
17
22
|
"README.md",
|
|
18
23
|
"LICENSE"
|
|
19
24
|
],
|
|
20
25
|
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
21
27
|
"exports": {
|
|
22
28
|
".": {
|
|
23
29
|
"types": "./dist/index.d.ts",
|
|
@@ -29,28 +35,31 @@
|
|
|
29
35
|
},
|
|
30
36
|
"scripts": {
|
|
31
37
|
"build": "tsc --build",
|
|
32
|
-
"clean": "rm -rf dist *.tsbuildinfo node_modules/.
|
|
33
|
-
"typecheck": "tsc --noEmit",
|
|
38
|
+
"clean": "rm -rf dist *.tsbuildinfo coverage node_modules/.svelte2tsx-* node_modules/.vite*",
|
|
34
39
|
"test": "vitest",
|
|
35
40
|
"lint": "oxlint .",
|
|
36
41
|
"lint:fix": "oxlint --fix .",
|
|
37
42
|
"format": "oxfmt .",
|
|
38
|
-
"format:check": "oxfmt --check ."
|
|
39
|
-
|
|
43
|
+
"format:check": "oxfmt --check ."
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@xmachines/json-render-core": "^0.19.0-xm.2"
|
|
40
47
|
},
|
|
41
48
|
"devDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"
|
|
45
|
-
"
|
|
49
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
50
|
+
"@types/node": "^26.2.0",
|
|
51
|
+
"oxfmt": "^0.64.0",
|
|
52
|
+
"oxlint": "^1.79.0",
|
|
53
|
+
"vitest": "^4.1.11",
|
|
54
|
+
"xstate": "^5.31.0"
|
|
46
55
|
},
|
|
47
56
|
"peerDependencies": {
|
|
48
|
-
"@xmachines/play": "1.
|
|
49
|
-
"@xmachines/play-signals": "1.
|
|
50
|
-
"xstate": "^5.
|
|
57
|
+
"@xmachines/play": "1.1.0",
|
|
58
|
+
"@xmachines/play-signals": "1.1.0",
|
|
59
|
+
"xstate": "^5.31.0"
|
|
51
60
|
},
|
|
52
61
|
"engines": {
|
|
53
62
|
"node": ">=22.0.0"
|
|
54
63
|
},
|
|
55
|
-
"_devDependencies_note": "xstate appears in both peerDependencies and devDependencies intentionally. devDependencies provides workspace resolution for local builds and tests. peerDependencies declares the consumer version constraint. Both are pinned to ^5.
|
|
64
|
+
"_devDependencies_note": "xstate appears in both peerDependencies and devDependencies intentionally. devDependencies provides workspace resolution for local builds and tests. peerDependencies declares the consumer version constraint. Both are pinned to ^5.30.0 to prevent drift."
|
|
56
65
|
}
|