@xmachines/play-actor 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -183
- package/dist/index.d.ts +13 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -16
- package/dist/index.js.map +1 -1
- package/dist/play-actor.d.ts +88 -0
- package/dist/play-actor.d.ts.map +1 -0
- package/dist/play-actor.js +28 -0
- package/dist/play-actor.js.map +1 -0
- package/package.json +15 -17
- package/dist/abstract-actor.d.ts +0 -207
- package/dist/abstract-actor.d.ts.map +0 -1
- package/dist/abstract-actor.js +0 -76
- package/dist/abstract-actor.js.map +0 -1
- package/dist/context-projection.d.ts +0 -116
- package/dist/context-projection.d.ts.map +0 -1
- package/dist/context-projection.js +0 -236
- package/dist/context-projection.js.map +0 -1
- package/dist/failure-latch.d.ts +0 -157
- package/dist/failure-latch.d.ts.map +0 -1
- package/dist/failure-latch.js +0 -110
- package/dist/failure-latch.js.map +0 -1
- package/dist/provider-guards.d.ts +0 -58
- package/dist/provider-guards.d.ts.map +0 -1
- package/dist/provider-guards.js +0 -73
- package/dist/provider-guards.js.map +0 -1
- package/dist/view-store-lifecycle.d.ts +0 -82
- package/dist/view-store-lifecycle.d.ts.map +0 -1
- package/dist/view-store-lifecycle.js +0 -97
- package/dist/view-store-lifecycle.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @xmachines/play-actor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The actor contract of the XMachines Play Architecture. It names no state machine library.
|
|
4
4
|
|
|
5
|
-
[](https://opensource.org/licenses/MIT) [](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/@xmachines/play-actor)
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -13,227 +13,108 @@ pnpm add @xmachines/play-actor
|
|
|
13
13
|
**Peer dependencies.** Install them with the package:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
pnpm add
|
|
16
|
+
pnpm add @xmachines/play-signals
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Overview
|
|
20
20
|
|
|
21
|
-
`@xmachines/play-actor` gives you `
|
|
21
|
+
`@xmachines/play-actor` gives you `PlayActor`, the contract that every layer above reads.
|
|
22
|
+
An actor of XMachines holds exactly two things:
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
| Member | Type | Description |
|
|
25
|
+
| ------- | ------------------------- | ------------------------------------------ |
|
|
26
|
+
| `state` | `Signal.State<TSnapshot>` | The reactive snapshot of the current state |
|
|
27
|
+
| `send` | `(event: TEvent) => void` | The typed event dispatch |
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
| `send` | `(event: TEvent) => void` | Event dispatch method |
|
|
29
|
+
**It is an interface, and no base class.** A state machine library is the concern of an
|
|
30
|
+
adapter, and [`@xmachines/play-xstate`](../play-xstate/README.md) is the adapter of XState
|
|
31
|
+
v5. This package therefore names `xstate` nowhere.
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
The contract was an abstract CLASS that extended `Actor` of `xstate`, and the class carried
|
|
34
|
+
no implementation at all: both of its members were abstract. Its whole effect was to force
|
|
35
|
+
the inheritance of XState on every consumer of the contract, including
|
|
36
|
+
[`@xmachines/play-view`](../play-view/README.md) and
|
|
37
|
+
[`@xmachines/play-router`](../play-router/README.md), which read `currentView`,
|
|
38
|
+
`currentRoute` and `send`, and no other member.
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
| ---------- | ----------------------------------------------- | ------------------------------------- |
|
|
34
|
-
| `Routable` | `currentRoute: Signal.Computed<string \| null>` | Current route path derived from state |
|
|
35
|
-
| `Routable` | `initialRoute: string \| null` | The route where the actor starts |
|
|
36
|
-
| `Viewable` | `currentView: Signal.State<PlaySpec \| null>` | Current JSON-render view spec |
|
|
40
|
+
### The capabilities are optional, and separate
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
A capability lives with the package that holds its shared machinery. Neither one implies
|
|
43
|
+
the other, and neither one names an engine.
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
| Capability | Interface | Package | Adds |
|
|
46
|
+
| ---------- | ---------- | ---------------------------------------------------- | ------------------------------ |
|
|
47
|
+
| Routing | `Routable` | [`@xmachines/play-router`](../play-router/README.md) | `currentRoute`, `initialRoute` |
|
|
48
|
+
| View | `Viewable` | [`@xmachines/play-view`](../play-view/README.md) | `currentView` |
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
## API Summary
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
### `PlayActor<TSnapshot, TEvent>`
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
instance holds the running machine. Reach the `send` method of XState through the
|
|
48
|
-
prototype. This class declares `send` as abstract for one reason only: to narrow the
|
|
49
|
-
event type. TypeScript forbids a `super` call to an abstract member.
|
|
54
|
+
Both type parameters carry their real type through, and neither one erases it.
|
|
50
55
|
|
|
51
56
|
```ts
|
|
52
|
-
import {
|
|
57
|
+
import type { PlayActor } from "@xmachines/play-actor";
|
|
53
58
|
import { Signal } from "@xmachines/play-signals";
|
|
54
|
-
import { Actor, type ActorOptions, type AnyActorLogic } from "xstate";
|
|
55
|
-
|
|
56
|
-
class MyActor extends AbstractActor<AnyActorLogic> {
|
|
57
|
-
// Required: reactive state signal
|
|
58
|
-
state: Signal.State<unknown>;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
super.subscribe((snapshot) => this.state.set(snapshot));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Required: typed event dispatch
|
|
67
|
-
override send(event: { type: string }): void {
|
|
68
|
-
Actor.prototype.send.call(this, event);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
With a typed event union:
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
// imports as in the previous example
|
|
77
|
-
type AuthEvent = { type: "auth.login"; username: string } | { type: "auth.logout" };
|
|
60
|
+
type Snapshot = { readonly value: string };
|
|
61
|
+
type AuthEvent =
|
|
62
|
+
{ readonly type: "auth.login"; readonly username: string } | { readonly type: "auth.logout" };
|
|
78
63
|
|
|
79
|
-
class AuthActor
|
|
80
|
-
state = new Signal.State({
|
|
64
|
+
class AuthActor implements PlayActor<Snapshot, AuthEvent> {
|
|
65
|
+
readonly state = new Signal.State<Snapshot>({ value: "guest" });
|
|
81
66
|
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
send(event: AuthEvent): void {
|
|
68
|
+
if (event.type === "auth.login") this.state.set({ value: "authenticated" });
|
|
84
69
|
}
|
|
85
70
|
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### `typedSpec(spec)`
|
|
89
|
-
|
|
90
|
-
This identity helper gives a view-spec literal the type `PlaySpec` at the definition site. The
|
|
91
|
-
XState `meta` field has the type `Record<string, unknown>`. Therefore this helper is the place
|
|
92
|
-
where the spec shape receives the compile-time check and the IDE autocomplete. The helper has no
|
|
93
|
-
cost at run time.
|
|
94
71
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// In an XState machine meta block:
|
|
99
|
-
meta: {
|
|
100
|
-
view: typedSpec({
|
|
101
|
-
root: "root",
|
|
102
|
-
elements: {
|
|
103
|
-
root: {
|
|
104
|
-
type: "Dashboard",
|
|
105
|
-
props: { username: { $state: "/context/username" } },
|
|
106
|
-
children: [],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
}),
|
|
110
|
-
}
|
|
72
|
+
// The snapshot type reaches the reader, and it is no `unknown`:
|
|
73
|
+
const actor: PlayActor<Snapshot, AuthEvent> = new AuthActor();
|
|
74
|
+
const value: string = actor.state.get().value;
|
|
111
75
|
```
|
|
112
76
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
This type extends the `Spec` type of `@xmachines/json-render-core`. Each derived view receives
|
|
116
|
-
the complete machine context in its state store, under the read-only **`/context` subtree**. A
|
|
117
|
-
spec therefore reads the context through the ordinary `{ $state: "/context/…" }` grammar: in a
|
|
118
|
-
prop, in a `visible` condition, and in `repeat.statePath`.
|
|
119
|
-
|
|
120
|
-
`/context` is read-only by design. Nothing can write to it. The machine context changes through
|
|
121
|
-
an event only. A `$bindState` write or a `setState` write under `/context` throws an error, and
|
|
122
|
-
the error names the event to send. This is the model: the bindable ephemeral state is at the
|
|
123
|
-
root of the store, from `spec.state`; the domain state is in the machine, and it changes through
|
|
124
|
-
events that are meaningful and easy to inspect.
|
|
125
|
-
|
|
126
|
-
The path shows the origin of each value. `/context/params/username` comes from the URL.
|
|
127
|
-
`/context/username` belongs to the machine. One value can never hide the other.
|
|
128
|
-
|
|
129
|
-
The model projects everything, and this has two consequences. The first consequence is
|
|
130
|
-
**exposure**. The complete context is visible to the client in the view store, which includes a
|
|
131
|
-
debug panel, an inspector, and a validator. The context is a client-side value in each case, so
|
|
132
|
-
keep a secret out of it.
|
|
133
|
-
|
|
134
|
-
The second consequence is **emission granularity**. The emit gate compares the context field by
|
|
135
|
-
field, at the top level only. Therefore an event that changes any field emits the view again
|
|
136
|
-
with the same `viewKey`. A provider refreshes `/context` in the live store, and it does not seed
|
|
137
|
-
the store again. The component does not remount, and the ephemeral view state and the focus
|
|
138
|
-
stay. However, a new emission is still a render pass in the framework layer. Keep
|
|
139
|
-
high-frequency ephemeral data, such as a draft for each keystroke or a timer, in the view store
|
|
140
|
-
(`spec.state` with `$bindState`) or in a child actor. The domain state belongs in the context. A
|
|
141
|
-
keystroke does not.
|
|
77
|
+
A plain object satisfies the contract as well. Nothing here asks for a class.
|
|
142
78
|
|
|
143
79
|
```ts
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
root: "root",
|
|
148
|
-
elements: {
|
|
149
|
-
root: {
|
|
150
|
-
type: "Profile",
|
|
151
|
-
props: { username: { $state: "/context/username" } },
|
|
152
|
-
children: [],
|
|
153
|
-
},
|
|
154
|
-
},
|
|
80
|
+
const actor: PlayActor<Snapshot, AuthEvent> = {
|
|
81
|
+
state: new Signal.State<Snapshot>({ value: "guest" }),
|
|
82
|
+
send: () => {},
|
|
155
83
|
};
|
|
156
84
|
```
|
|
157
85
|
|
|
158
|
-
|
|
159
|
-
> implicit prop-enrichment pass. That pass merged the allowlisted context fields and the URL
|
|
160
|
-
> params into the props of every element. We removed it, because it put values into components
|
|
161
|
-
> that never asked for them, and it let URL data from the user hide machine-owned state. The
|
|
162
|
-
> field was then a projection filter for a short time. We removed that filter too, because a
|
|
163
|
-
> limit on what a view can read added machinery without a real problem to solve. Always
|
|
164
|
-
> validate the **derived** view (`actor.currentView.get()`), not the raw `meta.view`. The
|
|
165
|
-
> `state` of the derived spec carries the projection, so a tool such as `validateSpec` sees a
|
|
166
|
-
> spec that is consistent with itself.
|
|
86
|
+
### `ActorEvent`
|
|
167
87
|
|
|
168
|
-
|
|
88
|
+
The minimal shape of an event that an actor receives: `{ readonly type: string }`. It is
|
|
89
|
+
the constraint of `TEvent`, and it is the base of `PlayEvent` of
|
|
90
|
+
[`@xmachines/play`](../play/README.md).
|
|
169
91
|
|
|
170
|
-
|
|
92
|
+
### An actor with a capability
|
|
171
93
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
import { Signal } from "@xmachines/play-signals";
|
|
175
|
-
import type { AnyActorLogic, EventObject } from "xstate";
|
|
176
|
-
|
|
177
|
-
// Implement in a concrete actor (note: RoutableActor interface is exported from @xmachines/play-router):
|
|
178
|
-
class MyRoutableActor extends AbstractActor<AnyActorLogic> implements Routable {
|
|
179
|
-
state = new Signal.State<{ path?: string }>({});
|
|
180
|
-
currentRoute = new Signal.Computed<string | null>(() => this.state.get().path ?? null);
|
|
181
|
-
initialRoute = "/";
|
|
182
|
-
override send(event: EventObject): void {
|
|
183
|
-
/* dispatch */
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### `Viewable`
|
|
189
|
-
|
|
190
|
-
Interface for actors that expose a renderable view signal.
|
|
94
|
+
A concrete actor implements the contract and the capabilities that it needs. It extends the
|
|
95
|
+
actor class of its own engine, and the contract stays out of the inheritance.
|
|
191
96
|
|
|
192
97
|
```ts
|
|
193
|
-
import type {
|
|
194
|
-
import type {
|
|
98
|
+
import type { PlayActor } from "@xmachines/play-actor";
|
|
99
|
+
import type { Routable } from "@xmachines/play-router";
|
|
195
100
|
import { Signal } from "@xmachines/play-signals";
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
```ts
|
|
207
|
-
import type { BaseActorProviderProps } from "@xmachines/play-actor";
|
|
208
|
-
import type { DefineRegistryResult } from "@xmachines/json-render-react";
|
|
209
|
-
|
|
210
|
-
interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
|
|
211
|
-
fallback?: React.ReactNode;
|
|
212
|
-
children: React.ReactNode;
|
|
101
|
+
import { Actor, type AnyActorLogic } from "xstate";
|
|
102
|
+
|
|
103
|
+
class MyRoutableActor
|
|
104
|
+
extends Actor<AnyActorLogic>
|
|
105
|
+
implements PlayActor<unknown, { readonly type: string }>, Routable
|
|
106
|
+
{
|
|
107
|
+
readonly state = new Signal.State<unknown>({});
|
|
108
|
+
readonly currentRoute = new Signal.Computed<string | null>(() => null);
|
|
109
|
+
readonly initialRoute = "/";
|
|
213
110
|
}
|
|
214
111
|
```
|
|
215
112
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
The framework-agnostic base of the `ViewContextValue` type in each framework. It holds the `spec`, `handlers`, `registry`, and `store` fields. These fields are identical in React, Vue, Solid, and Svelte.
|
|
219
|
-
|
|
220
|
-
## Testing
|
|
221
|
-
|
|
222
|
-
Run the test suite for this package in isolation:
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
# From the package directory
|
|
226
|
-
pnpm test
|
|
227
|
-
|
|
228
|
-
# From the monorepo root (workspace-scoped)
|
|
229
|
-
pnpm --filter @xmachines/play-actor test
|
|
230
|
-
|
|
231
|
-
# Watch mode
|
|
232
|
-
pnpm --filter @xmachines/play-actor run test:watch
|
|
233
|
-
```
|
|
113
|
+
[`@xmachines/play-xstate`](../play-xstate/README.md) does exactly this, and it composes
|
|
114
|
+
each capability with `compose(PlayerActor, withRouting, withView)`.
|
|
234
115
|
|
|
235
116
|
## Requirements
|
|
236
117
|
|
|
237
|
-
- **Node.js** `>=
|
|
118
|
+
- **Node.js** `>=24.0.0`
|
|
238
119
|
- **TypeScript** `>=5.7` (strict mode)
|
|
239
120
|
- **ESM only** — `"type": "module"`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @xmachines/play-actor - the
|
|
2
|
+
* @xmachines/play-actor - the actor contract of the Play Architecture
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The contract is minimal, and it names no engine: `state` and `send`. A state machine
|
|
5
|
+
* library is the concern of an adapter, and `@xmachines/play-xstate` is the adapter of
|
|
6
|
+
* XState v5.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - Routable
|
|
11
|
-
*
|
|
8
|
+
* A capability is optional, and each one lives with the package that holds its shared
|
|
9
|
+
* machinery:
|
|
10
|
+
* - `Routable`, for an actor with a routing support, is in `@xmachines/play-router`, with
|
|
11
|
+
* the bridge that reads it
|
|
12
|
+
* - `Viewable`, for an actor with a view rendering, is in `@xmachines/play-view`, with the
|
|
13
|
+
* view store lifecycle, the context projection and the provider guards
|
|
12
14
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* communication with the infrastructure layer.
|
|
15
|
+
* A consumer that needs one capability installs one package, and a consumer that needs
|
|
16
|
+
* neither installs neither. Neither capability implies the other.
|
|
16
17
|
*
|
|
17
18
|
* @packageDocumentation
|
|
18
19
|
* @see [Play RFC](../../docs/rfc/play.md)
|
|
19
20
|
*/
|
|
20
|
-
export
|
|
21
|
-
export { toAtomState, attachRenderErrorHandler } from "./provider-guards.js";
|
|
22
|
-
export { CONTEXT_STATE_KEY, guardContextWrites, refreshContextSubtree, composePlayState, shallowEqualExcept, reuseComposedState, } from "./context-projection.js";
|
|
23
|
-
export { createViewStoreLifecycle } from "./view-store-lifecycle.js";
|
|
24
|
-
export { createFailureLatch, createReportGuard, sameViewInputs } from "./failure-latch.js";
|
|
25
|
-
export type { FailureLatch, ReportGuard, ReportGuardMessages, ViewInputs, } from "./failure-latch.js";
|
|
26
|
-
export type { ViewStoreLifecycle, ViewStoreResolution, ResolveViewStoreOptions, } from "./view-store-lifecycle.js";
|
|
21
|
+
export type { PlayActor, ActorEvent } from "./play-actor.js";
|
|
27
22
|
//# 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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @xmachines/play-actor - the
|
|
2
|
+
* @xmachines/play-actor - the actor contract of the Play Architecture
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The contract is minimal, and it names no engine: `state` and `send`. A state machine
|
|
5
|
+
* library is the concern of an adapter, and `@xmachines/play-xstate` is the adapter of
|
|
6
|
+
* XState v5.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - Routable
|
|
11
|
-
*
|
|
8
|
+
* A capability is optional, and each one lives with the package that holds its shared
|
|
9
|
+
* machinery:
|
|
10
|
+
* - `Routable`, for an actor with a routing support, is in `@xmachines/play-router`, with
|
|
11
|
+
* the bridge that reads it
|
|
12
|
+
* - `Viewable`, for an actor with a view rendering, is in `@xmachines/play-view`, with the
|
|
13
|
+
* view store lifecycle, the context projection and the provider guards
|
|
12
14
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* communication with the infrastructure layer.
|
|
15
|
+
* A consumer that needs one capability installs one package, and a consumer that needs
|
|
16
|
+
* neither installs neither. Neither capability implies the other.
|
|
16
17
|
*
|
|
17
18
|
* @packageDocumentation
|
|
18
19
|
* @see [Play RFC](../../docs/rfc/play.md)
|
|
19
20
|
*/
|
|
20
|
-
export {
|
|
21
|
-
export { toAtomState, attachRenderErrorHandler } from "./provider-guards.js";
|
|
22
|
-
export { CONTEXT_STATE_KEY, guardContextWrites, refreshContextSubtree, composePlayState, shallowEqualExcept, reuseComposedState, } from "./context-projection.js";
|
|
23
|
-
export { createViewStoreLifecycle } from "./view-store-lifecycle.js";
|
|
24
|
-
export { createFailureLatch, createReportGuard, sameViewInputs } from "./failure-latch.js";
|
|
21
|
+
export {};
|
|
25
22
|
//# 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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The actor contract of the Play Architecture.
|
|
3
|
+
*
|
|
4
|
+
* An actor of XMachines holds exactly two things, and this file names both:
|
|
5
|
+
*
|
|
6
|
+
* - `state`: the reactive snapshot, as a TC39 Signal
|
|
7
|
+
* - `send`: the typed event dispatch
|
|
8
|
+
*
|
|
9
|
+
* It names NO engine. A state machine library is the concern of an adapter, and
|
|
10
|
+
* `@xmachines/play-xstate` is the adapter of XState v5.
|
|
11
|
+
*
|
|
12
|
+
* The contract was an abstract CLASS that extended `Actor` of `xstate`, and the class
|
|
13
|
+
* carried no implementation at all: both members were abstract, and `instanceof` of it
|
|
14
|
+
* appeared nowhere. Its whole effect was to force the inheritance of XState on every
|
|
15
|
+
* consumer of the contract, including `@xmachines/play-view` and
|
|
16
|
+
* `@xmachines/play-router`, which read `currentView`, `currentRoute` and `send`, and no
|
|
17
|
+
* other member.
|
|
18
|
+
*
|
|
19
|
+
* A capability is optional, and each one lives with the package that holds its shared
|
|
20
|
+
* machinery: `Routable` is in `@xmachines/play-router`, beside the bridge that reads it,
|
|
21
|
+
* and `Viewable` is in `@xmachines/play-view`, beside the view store lifecycle. Neither
|
|
22
|
+
* one names an engine, and neither one implies the other.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
* @see [Play RFC](../../docs/rfc/play.md)
|
|
26
|
+
*/
|
|
27
|
+
import type { Signal } from "@xmachines/play-signals";
|
|
28
|
+
/**
|
|
29
|
+
* The minimal shape of an event that an actor receives.
|
|
30
|
+
*
|
|
31
|
+
* It is the base of `PlayEvent` of `@xmachines/play`, and this package declares it again
|
|
32
|
+
* rather than import it: the constraint must accept the event union of a CONCRETE machine,
|
|
33
|
+
* and `PlayEvent` carries `Record<string, unknown>` in its default payload. An interface of
|
|
34
|
+
* a union member holds no index signature, so it satisfies that default never.
|
|
35
|
+
*/
|
|
36
|
+
export interface ActorEvent {
|
|
37
|
+
readonly type: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The actor contract of the Play Architecture. It names NO engine.
|
|
41
|
+
*
|
|
42
|
+
* An actor of XMachines holds exactly two things, and this interface names both: the
|
|
43
|
+
* reactive snapshot, and the typed event dispatch. A state machine library is the concern
|
|
44
|
+
* of an adapter, and `@xmachines/play-xstate` is the adapter of XState v5.
|
|
45
|
+
*
|
|
46
|
+
* Both type parameters carry their real type through, and neither one erases it.
|
|
47
|
+
* `AbstractActor` declares `Signal.State<unknown>`, so every consumer of a snapshot reads
|
|
48
|
+
* `unknown` and asserts its way back.
|
|
49
|
+
*
|
|
50
|
+
* A capability is optional, and each one lives with the package that holds its shared
|
|
51
|
+
* machinery: `Routable` is in `@xmachines/play-router`, and `Viewable` is in
|
|
52
|
+
* `@xmachines/play-view`. Neither one names an engine either, and neither one implies the
|
|
53
|
+
* other.
|
|
54
|
+
*
|
|
55
|
+
* @typeParam TSnapshot - The type of the snapshot that `state` holds, for example `SnapshotFrom<TMachine>`.
|
|
56
|
+
* @typeParam TEvent - The event union that `send` accepts, for example `EventFromLogic<TMachine>`.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import type { PlayActor } from "@xmachines/play-actor";
|
|
61
|
+
* import type { Viewable } from "@xmachines/play-view";
|
|
62
|
+
*
|
|
63
|
+
* // A renderer reads the view capability and the send of the contract. It names no
|
|
64
|
+
* // engine, and it keeps the event union of the machine.
|
|
65
|
+
* function mount<TSnapshot, TEvent extends ActorEvent>(
|
|
66
|
+
* actor: PlayActor<TSnapshot, TEvent> & Viewable,
|
|
67
|
+
* ): void {
|
|
68
|
+
* actor.currentView.get();
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export interface PlayActor<TSnapshot = unknown, TEvent extends ActorEvent = ActorEvent> {
|
|
73
|
+
/**
|
|
74
|
+
* The reactive snapshot of the current actor state.
|
|
75
|
+
*
|
|
76
|
+
* The infrastructure observes this signal, and it reacts to each state change. It
|
|
77
|
+
* therefore holds no coupling to the internal state machine of the actor.
|
|
78
|
+
*/
|
|
79
|
+
readonly state: Signal.State<TSnapshot>;
|
|
80
|
+
/**
|
|
81
|
+
* Sends an event to the actor.
|
|
82
|
+
*
|
|
83
|
+
* `TEvent` gives the type safety: a concrete adapter binds it to the event union of its
|
|
84
|
+
* own machine, and the compiler then refuses an event that the machine does not declare.
|
|
85
|
+
*/
|
|
86
|
+
send(event: TEvent): void;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=play-actor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"play-actor.d.ts","sourceRoot":"","sources":["../src/play-actor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,SAAS,CAAC,SAAS,GAAG,OAAO,EAAE,MAAM,SAAS,UAAU,GAAG,UAAU;IACrF;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAExC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The actor contract of the Play Architecture.
|
|
3
|
+
*
|
|
4
|
+
* An actor of XMachines holds exactly two things, and this file names both:
|
|
5
|
+
*
|
|
6
|
+
* - `state`: the reactive snapshot, as a TC39 Signal
|
|
7
|
+
* - `send`: the typed event dispatch
|
|
8
|
+
*
|
|
9
|
+
* It names NO engine. A state machine library is the concern of an adapter, and
|
|
10
|
+
* `@xmachines/play-xstate` is the adapter of XState v5.
|
|
11
|
+
*
|
|
12
|
+
* The contract was an abstract CLASS that extended `Actor` of `xstate`, and the class
|
|
13
|
+
* carried no implementation at all: both members were abstract, and `instanceof` of it
|
|
14
|
+
* appeared nowhere. Its whole effect was to force the inheritance of XState on every
|
|
15
|
+
* consumer of the contract, including `@xmachines/play-view` and
|
|
16
|
+
* `@xmachines/play-router`, which read `currentView`, `currentRoute` and `send`, and no
|
|
17
|
+
* other member.
|
|
18
|
+
*
|
|
19
|
+
* A capability is optional, and each one lives with the package that holds its shared
|
|
20
|
+
* machinery: `Routable` is in `@xmachines/play-router`, beside the bridge that reads it,
|
|
21
|
+
* and `Viewable` is in `@xmachines/play-view`, beside the view store lifecycle. Neither
|
|
22
|
+
* one names an engine, and neither one implies the other.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
* @see [Play RFC](../../docs/rfc/play.md)
|
|
26
|
+
*/
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=play-actor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"play-actor.js","sourceRoot":"","sources":["../src/play-actor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-actor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "The actor contract of the Play Architecture: state and send. It names no state machine library",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"actor",
|
|
8
8
|
"play-architecture",
|
|
@@ -42,28 +42,26 @@
|
|
|
42
42
|
"test": "vitest",
|
|
43
43
|
"test:coverage": "vitest run --coverage",
|
|
44
44
|
"lint": "oxlint .",
|
|
45
|
+
"lint:security": "node ../../scripts/semgrep-scan.mjs",
|
|
45
46
|
"lint:fix": "oxlint --fix .",
|
|
46
47
|
"format": "oxfmt .",
|
|
47
48
|
"format:check": "oxfmt --check ."
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"@testing-library/jest-dom": "^
|
|
51
|
-
"@types/node": "^26.2
|
|
52
|
-
"@xmachines/
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
51
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
52
|
+
"@types/node": "^26.6.2",
|
|
53
|
+
"@xmachines/play": "4.0.0",
|
|
54
|
+
"@xmachines/play-signals": "4.0.0",
|
|
55
|
+
"oxfmt": "^0.68.0",
|
|
56
|
+
"oxlint": "^1.83.0",
|
|
57
|
+
"vite": "^8.3.0",
|
|
58
|
+
"vitest": "^5.0.1",
|
|
59
|
+
"xstate": "^5.33.0"
|
|
58
60
|
},
|
|
59
61
|
"peerDependencies": {
|
|
60
|
-
"@xmachines/
|
|
61
|
-
"@xmachines/play": "3.0.0",
|
|
62
|
-
"@xmachines/play-signals": "3.0.0",
|
|
63
|
-
"xstate": "^5.31.0"
|
|
62
|
+
"@xmachines/play-signals": "4.0.0"
|
|
64
63
|
},
|
|
65
64
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
67
|
-
}
|
|
68
|
-
"_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."
|
|
65
|
+
"node": ">=24.0.0"
|
|
66
|
+
}
|
|
69
67
|
}
|