@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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @xmachines/play-actor
2
2
 
3
- Abstract Actor base class for XMachines Play Architecture.
3
+ The actor contract of the XMachines Play Architecture. It names no state machine library.
4
4
 
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-3.0.0-blue)](https://www.npmjs.com/package/@xmachines/play-actor)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-4.0.0-blue)](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 xstate @xmachines/play @xmachines/play-signals @xmachines/json-render-core
16
+ pnpm add @xmachines/play-signals
17
17
  ```
18
18
 
19
19
  ## Overview
20
20
 
21
- `@xmachines/play-actor` gives you `AbstractActor`, a minimal base class. The class extends the XState `Actor` class, and it enforces the **signal protocol** of the Play Architecture (RFC section 5.3). It exposes reactive TC39 Signals for the infrastructure layer. It also keeps the complete compatibility with the XState ecosystem, which includes the devtools and the inspection.
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
- The core protocol is small on purpose:
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
- | Property | Type | Description |
26
- | -------- | ------------------------- | ---------------------------------------- |
27
- | `state` | `Signal.State<unknown>` | Reactive snapshot of current actor state |
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
- Separate interfaces declare the optional capabilities. A concrete actor implements only the interfaces that it needs:
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
- | Interface | Property | Description |
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
- An adapter, such as [`@xmachines/play-xstate`](../play-xstate/README.md), makes the concrete implementations.
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
- ## API Summary
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
- ### `AbstractActor<TLogic, TEvent>`
50
+ ## API Summary
43
51
 
44
- The abstract base class extends the XState `Actor<TLogic>` class.
52
+ ### `PlayActor<TSnapshot, TEvent>`
45
53
 
46
- A subclass **is** the actor. Give the logic and its options to `super()`, so that one
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 { AbstractActor } from "@xmachines/play-actor";
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
- constructor(logic: AnyActorLogic, options?: ActorOptions<AnyActorLogic>) {
61
- super(logic, options);
62
- this.state = new Signal.State(this.getSnapshot());
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 extends AbstractActor<AnyActorLogic, AuthEvent> {
80
- state = new Signal.State({ isAuthenticated: false, username: null });
64
+ class AuthActor implements PlayActor<Snapshot, AuthEvent> {
65
+ readonly state = new Signal.State<Snapshot>({ value: "guest" });
81
66
 
82
- override send(event: AuthEvent): void {
83
- Actor.prototype.send.call(this, event);
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
- ```ts
96
- import { typedSpec } from "@xmachines/play-actor";
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
- ### `PlaySpec`
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
- import type { PlaySpec } from "@xmachines/play-actor";
145
-
146
- const spec: PlaySpec = {
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
- > Historical note: an earlier version had a `contextProps` field. At first the field drove an
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
- ### `Routable`
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
- Interface for actors that support routing.
92
+ ### An actor with a capability
171
93
 
172
- ```ts
173
- import { AbstractActor, type Routable } from "@xmachines/play-actor";
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 { Viewable } from "@xmachines/play-actor";
194
- import type { PlaySpec } from "@xmachines/play-actor";
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
- // currentView carries PlaySpec | null
198
- const signal = new Signal.State<PlaySpec | null>(null);
199
- const viewable: Viewable = { currentView: signal };
200
- ```
201
-
202
- ### `BaseActorProviderProps<TRegistry>`
203
-
204
- The framework-agnostic base props. Every `ActorProvider` implementation shares them: React, Vue, Solid, and Svelte. Each framework renderer package extends this interface.
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
- ### `BaseViewContextValue<TRegistry>`
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** `>=22.0.0`
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 abstract Actor base class of the Play Architecture
2
+ * @xmachines/play-actor - the actor contract of the Play Architecture
3
3
  *
4
- * This package gives you AbstractActor, a minimal base class. It extends the XState
5
- * Actor class, and it enforces the signal protocol of the Play Architecture (RFC
6
- * section 5.3).
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
- * The core protocol is minimal: state and send. Two interfaces give the optional
9
- * capabilities:
10
- * - Routable: for an actor with a routing support
11
- * - Viewable: for an actor with a view rendering
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
- * The class keeps the compatibility with the XState ecosystem, such as the
14
- * inspection and the devtools. It also exposes the reactive signals of the
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 { AbstractActor, typedSpec, type Routable, type Viewable, type PlaySpec, type BaseActorProviderProps, type BaseViewContextValue, } from "./abstract-actor.js";
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;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;AAE7E,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC3F,YAAY,EACX,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,UAAU,GACV,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACX,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,GACvB,MAAM,2BAA2B,CAAC"}
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 abstract Actor base class of the Play Architecture
2
+ * @xmachines/play-actor - the actor contract of the Play Architecture
3
3
  *
4
- * This package gives you AbstractActor, a minimal base class. It extends the XState
5
- * Actor class, and it enforces the signal protocol of the Play Architecture (RFC
6
- * section 5.3).
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
- * The core protocol is minimal: state and send. Two interfaces give the optional
9
- * capabilities:
10
- * - Routable: for an actor with a routing support
11
- * - Viewable: for an actor with a view rendering
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
- * The class keeps the compatibility with the XState ecosystem, such as the
14
- * inspection and the devtools. It also exposes the reactive signals of the
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 { AbstractActor, typedSpec, } from "./abstract-actor.js";
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;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACN,aAAa,EACb,SAAS,GAMT,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAE7E,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC"}
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.0.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
- "description": "Abstract Actor base class for XMachines Play Architecture",
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": "^6.9.1",
51
- "@types/node": "^26.2.0",
52
- "@xmachines/json-render-core": "^0.20.0-xm.4",
53
- "oxfmt": "^0.64.0",
54
- "oxlint": "^1.79.0",
55
- "vite": "^8.0.10",
56
- "vitest": "^4.1.11",
57
- "xstate": "^5.31.0"
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/json-render-core": "^0.20.0-xm.4",
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": ">=22.0.0"
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
  }