@xmachines/play-actor 1.1.0 → 2.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 +48 -18
- package/dist/abstract-actor.d.ts +30 -31
- package/dist/abstract-actor.d.ts.map +1 -1
- package/dist/abstract-actor.js +18 -15
- package/dist/abstract-actor.js.map +1 -1
- package/dist/context-projection.d.ts +109 -0
- package/dist/context-projection.d.ts.map +1 -0
- package/dist/context-projection.js +228 -0
- package/dist/context-projection.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/view-store-lifecycle.d.ts +77 -0
- package/dist/view-store-lifecycle.d.ts.map +1 -0
- package/dist/view-store-lifecycle.js +94 -0
- package/dist/view-store-lifecycle.js.map +1 -0
- package/package.json +13 -10
package/README.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Abstract Actor base class for XMachines Play Architecture.
|
|
4
4
|
|
|
5
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[](https://www.npmjs.com/package/@xmachines/play-actor)
|
|
5
|
+
[](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/@xmachines/play-actor)
|
|
7
6
|
|
|
8
7
|
Part of the [xmachines-js monorepo](../../README.md).
|
|
9
8
|
|
|
@@ -16,7 +15,7 @@ pnpm add @xmachines/play-actor
|
|
|
16
15
|
**Peer dependencies** — install alongside the package:
|
|
17
16
|
|
|
18
17
|
```bash
|
|
19
|
-
pnpm add xstate @xmachines/play @xmachines/play-signals
|
|
18
|
+
pnpm add xstate @xmachines/play @xmachines/play-signals @xmachines/json-render-core
|
|
20
19
|
```
|
|
21
20
|
|
|
22
21
|
## Overview
|
|
@@ -87,27 +86,25 @@ class AuthActor extends AbstractActor<AnyActorLogic, AuthEvent> {
|
|
|
87
86
|
}
|
|
88
87
|
```
|
|
89
88
|
|
|
90
|
-
### `typedSpec
|
|
89
|
+
### `typedSpec(spec)`
|
|
91
90
|
|
|
92
|
-
Identity helper that
|
|
91
|
+
Identity helper that types a view-spec literal as `PlaySpec` at the definition site. XState's
|
|
92
|
+
`meta` field is `Record<string, unknown>`, so this is where the spec shape gets compile-time
|
|
93
|
+
validation and IDE autocomplete — without any runtime cost.
|
|
93
94
|
|
|
94
95
|
```ts
|
|
95
96
|
import { typedSpec } from "@xmachines/play-actor";
|
|
96
97
|
|
|
97
|
-
interface DashboardCtx {
|
|
98
|
-
username: string;
|
|
99
|
-
params: Record<string, string>;
|
|
100
|
-
query: Record<string, string>;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
98
|
// In an XState machine meta block:
|
|
104
99
|
meta: {
|
|
105
|
-
view: typedSpec
|
|
100
|
+
view: typedSpec({
|
|
106
101
|
root: "root",
|
|
107
|
-
contextProps: ["username"], // ✓ key of DashboardCtx
|
|
108
|
-
// contextProps: ["usernaem"], // ✗ compile error
|
|
109
102
|
elements: {
|
|
110
|
-
root: {
|
|
103
|
+
root: {
|
|
104
|
+
type: "Dashboard",
|
|
105
|
+
props: { username: { $state: "/context/username" } },
|
|
106
|
+
children: [],
|
|
107
|
+
},
|
|
111
108
|
},
|
|
112
109
|
}),
|
|
113
110
|
}
|
|
@@ -115,20 +112,53 @@ meta: {
|
|
|
115
112
|
|
|
116
113
|
### `PlaySpec`
|
|
117
114
|
|
|
118
|
-
Extends `@xmachines/json-render-core`'s `Spec
|
|
115
|
+
Extends `@xmachines/json-render-core`'s `Spec`. The machine's whole context is projected into
|
|
116
|
+
every derived view's state store under the read-only **`/context` subtree**, so specs read it
|
|
117
|
+
through the ordinary `{ $state: "/context/…" }` grammar — in props, `visible` conditions, and
|
|
118
|
+
`repeat.statePath` alike.
|
|
119
|
+
|
|
120
|
+
`/context` is read-only by design — never writable. Machine context changes only through events;
|
|
121
|
+
a `$bindState` or `setState` write under `/context` throws with an error naming the event to
|
|
122
|
+
send instead. This is the model: bindable ephemeral state lives at the store root (seeded from
|
|
123
|
+
`spec.state`), domain state lives in the machine and changes via meaningful, inspectable events.
|
|
124
|
+
|
|
125
|
+
Provenance is legible in the path: `/context/params/username` is visibly URL-derived, while
|
|
126
|
+
`/context/username` is machine-owned — one can never shadow the other.
|
|
127
|
+
|
|
128
|
+
Two consequences of the everything-is-projected model are worth knowing. First, **exposure**:
|
|
129
|
+
the whole context is client-visible in the view store (debug panels, inspectors, validators) —
|
|
130
|
+
context is a client-side value either way, so keep secrets out of it. Second, **emission
|
|
131
|
+
granularity**: the emit gate compares context per top-level field, so an event that changes any
|
|
132
|
+
field re-emits the view with the same `viewKey`. Providers refresh `/context` in the live store
|
|
133
|
+
rather than reseeding it — no remount, ephemeral view state and focus survive — but a
|
|
134
|
+
re-emission is still a render pass in the framework layer. Keep high-frequency ephemeral data
|
|
135
|
+
(per-keystroke drafts, timers) in the view store (`spec.state` + `$bindState`) or a child actor
|
|
136
|
+
rather than in machine context; domain state belongs in context, keystrokes do not.
|
|
119
137
|
|
|
120
138
|
```ts
|
|
121
139
|
import type { PlaySpec } from "@xmachines/play-actor";
|
|
122
140
|
|
|
123
141
|
const spec: PlaySpec = {
|
|
124
142
|
root: "root",
|
|
125
|
-
contextProps: ["username"], // only these keys are exposed to components
|
|
126
143
|
elements: {
|
|
127
|
-
root: {
|
|
144
|
+
root: {
|
|
145
|
+
type: "Profile",
|
|
146
|
+
props: { username: { $state: "/context/username" } },
|
|
147
|
+
children: [],
|
|
148
|
+
},
|
|
128
149
|
},
|
|
129
150
|
};
|
|
130
151
|
```
|
|
131
152
|
|
|
153
|
+
> Historical note: earlier versions had a `contextProps` field. It first drove an implicit
|
|
154
|
+
> prop-enrichment pass that merged allowlisted context fields and URL params into every
|
|
155
|
+
> element's props (removed — it injected values into components that never asked for them and
|
|
156
|
+
> let user-manipulable URL data silently shadow machine-owned state), and was then briefly a
|
|
157
|
+
> projection filter (removed — filtering what a view may read added machinery without a real
|
|
158
|
+
> problem to solve). Validate the **derived** view (`actor.currentView.get()`), not the raw
|
|
159
|
+
> `meta.view`: the derived spec's `state` carries the projection, so tools like `validateSpec`
|
|
160
|
+
> see a self-consistent spec.
|
|
161
|
+
|
|
132
162
|
### `Routable`
|
|
133
163
|
|
|
134
164
|
Interface for actors that support routing.
|
package/dist/abstract-actor.d.ts
CHANGED
|
@@ -29,56 +29,55 @@ export interface Routable {
|
|
|
29
29
|
/**
|
|
30
30
|
* XMachines extension of `@xmachines/json-render-core` `Spec`.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* Use `typedSpec<TContext>(...)` at the definition site to validate `contextProps`
|
|
38
|
-
* entries against your machine's context type at compile time.
|
|
32
|
+
* The machine's context is projected into every derived view's state store
|
|
33
|
+
* under the read-only `/context` subtree, so specs read it through the
|
|
34
|
+
* ordinary `{ $state: "/context/…" }` grammar — in props, `visible`
|
|
35
|
+
* conditions, and `repeat.statePath` alike. The whole context is always
|
|
36
|
+
* projected; a spec simply reads the paths it needs.
|
|
39
37
|
*/
|
|
40
38
|
export interface PlaySpec extends Spec {
|
|
41
39
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
40
|
+
* Identity of the view this derived spec came from — set by
|
|
41
|
+
* `deriveCurrentView` from the meta entry the derivation actually selected.
|
|
42
|
+
* Providers key their store lifecycle on it: a changed `viewKey` reseeds the
|
|
43
|
+
* store; an unchanged one refreshes `/context` in place, preserving
|
|
44
|
+
* ephemeral view state. Never author this field in `meta.view`.
|
|
47
45
|
*/
|
|
48
|
-
readonly
|
|
46
|
+
readonly viewKey?: string;
|
|
49
47
|
}
|
|
50
48
|
/**
|
|
51
|
-
* Identity helper that
|
|
52
|
-
*
|
|
53
|
-
* autocomplete at the definition site.
|
|
49
|
+
* Identity helper that types a view spec literal as `PlaySpec` at the
|
|
50
|
+
* definition site, giving compile-time validation and IDE autocomplete.
|
|
54
51
|
*
|
|
55
52
|
* XState's `meta` field is typed as `Record<string, unknown>`, so TypeScript
|
|
56
|
-
* cannot infer the
|
|
57
|
-
*
|
|
53
|
+
* cannot infer the spec shape from context. `typedSpec(...)` is the opt-in
|
|
54
|
+
* mechanism that activates checking where the spec is written. `viewKey` is
|
|
55
|
+
* excluded from the parameter — derivation stamps it and would silently
|
|
56
|
+
* overwrite an authored value, so authoring one is rejected at compile time.
|
|
57
|
+
*
|
|
58
|
+
* Excess-property checking only applies to an inline object literal; for a
|
|
59
|
+
* spec built in a variable or through spreads, use `satisfies PlaySpec` at
|
|
60
|
+
* the literal instead.
|
|
58
61
|
*
|
|
59
62
|
* At runtime this is a no-op — the spec object is returned unchanged.
|
|
60
63
|
*
|
|
61
64
|
* @example
|
|
62
65
|
* ```ts
|
|
63
|
-
* interface DashboardCtx {
|
|
64
|
-
* username: string;
|
|
65
|
-
* params: Record<string, string>;
|
|
66
|
-
* query: Record<string, string>;
|
|
67
|
-
* }
|
|
68
|
-
*
|
|
69
66
|
* meta: {
|
|
70
|
-
* view: typedSpec
|
|
67
|
+
* view: typedSpec({
|
|
71
68
|
* root: "root",
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
69
|
+
* elements: {
|
|
70
|
+
* root: {
|
|
71
|
+
* type: "Dashboard",
|
|
72
|
+
* props: { username: { $state: "/context/username" } },
|
|
73
|
+
* children: [],
|
|
74
|
+
* },
|
|
75
|
+
* },
|
|
75
76
|
* }),
|
|
76
77
|
* }
|
|
77
78
|
* ```
|
|
78
79
|
*/
|
|
79
|
-
export declare function typedSpec
|
|
80
|
-
readonly contextProps?: readonly (keyof TContext & string)[];
|
|
81
|
-
}): PlaySpec;
|
|
80
|
+
export declare function typedSpec(spec: Omit<PlaySpec, "viewKey">): PlaySpec;
|
|
82
81
|
/**
|
|
83
82
|
* Actor capability for exposing renderable view state.
|
|
84
83
|
*
|
|
@@ -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,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
|
|
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;;;;;;;;GAQG;AACH,MAAM,WAAW,QAAS,SAAQ,IAAI;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,QAAQ,CAEnE;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,30 +18,33 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { Actor } from "xstate";
|
|
20
20
|
/**
|
|
21
|
-
* Identity helper that
|
|
22
|
-
*
|
|
23
|
-
* autocomplete at the definition site.
|
|
21
|
+
* Identity helper that types a view spec literal as `PlaySpec` at the
|
|
22
|
+
* definition site, giving compile-time validation and IDE autocomplete.
|
|
24
23
|
*
|
|
25
24
|
* XState's `meta` field is typed as `Record<string, unknown>`, so TypeScript
|
|
26
|
-
* cannot infer the
|
|
27
|
-
*
|
|
25
|
+
* cannot infer the spec shape from context. `typedSpec(...)` is the opt-in
|
|
26
|
+
* mechanism that activates checking where the spec is written. `viewKey` is
|
|
27
|
+
* excluded from the parameter — derivation stamps it and would silently
|
|
28
|
+
* overwrite an authored value, so authoring one is rejected at compile time.
|
|
29
|
+
*
|
|
30
|
+
* Excess-property checking only applies to an inline object literal; for a
|
|
31
|
+
* spec built in a variable or through spreads, use `satisfies PlaySpec` at
|
|
32
|
+
* the literal instead.
|
|
28
33
|
*
|
|
29
34
|
* At runtime this is a no-op — the spec object is returned unchanged.
|
|
30
35
|
*
|
|
31
36
|
* @example
|
|
32
37
|
* ```ts
|
|
33
|
-
* interface DashboardCtx {
|
|
34
|
-
* username: string;
|
|
35
|
-
* params: Record<string, string>;
|
|
36
|
-
* query: Record<string, string>;
|
|
37
|
-
* }
|
|
38
|
-
*
|
|
39
38
|
* meta: {
|
|
40
|
-
* view: typedSpec
|
|
39
|
+
* view: typedSpec({
|
|
41
40
|
* root: "root",
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
41
|
+
* elements: {
|
|
42
|
+
* root: {
|
|
43
|
+
* type: "Dashboard",
|
|
44
|
+
* props: { username: { $state: "/context/username" } },
|
|
45
|
+
* children: [],
|
|
46
|
+
* },
|
|
47
|
+
* },
|
|
45
48
|
* }),
|
|
46
49
|
* }
|
|
47
50
|
* ```
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAqCrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,SAAS,CAAC,IAA+B;IACxD,OAAO,IAAI,CAAC;AACb,CAAC;AAiFD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAgB,aAGpB,SAAQ,KAAa;CAuBtB"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context projection — machine context as a read-only `/context` subtree of the
|
|
3
|
+
* view state store.
|
|
4
|
+
*
|
|
5
|
+
* `deriveCurrentView` composes each emitted view's `state` as
|
|
6
|
+
* `{ ...meta.view.state, context: <slice> }`, so the spec is self-consistent:
|
|
7
|
+
* its `state` honestly describes the store contents, and `repeat.statePath`,
|
|
8
|
+
* `visible.$state`, `$state` props and validators can all read machine context
|
|
9
|
+
* through the ordinary `{ $state: "/context/…" }` grammar.
|
|
10
|
+
*
|
|
11
|
+
* The subtree is read-only **to the spec, not to the machinery**: providers
|
|
12
|
+
* hand bindings and action handlers a store wrapped with
|
|
13
|
+
* {@link guardContextWrites}, while refreshing the projection through the
|
|
14
|
+
* unguarded store underneath. Context changes flow exclusively through machine
|
|
15
|
+
* events — a write under `/context` is always a spec bug, and the guard says so.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
import type { StateStore } from "@xmachines/json-render-core";
|
|
20
|
+
import type { PlaySpec } from "./abstract-actor.js";
|
|
21
|
+
/**
|
|
22
|
+
* The reserved top-level state key the projection materializes under.
|
|
23
|
+
*
|
|
24
|
+
* A view's authored `spec.state` must not declare this key — when it does, the
|
|
25
|
+
* projection is skipped for that view (the authored state wins) and a dev
|
|
26
|
+
* warning is emitted. See {@link composePlayState}.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CONTEXT_STATE_KEY = "context";
|
|
29
|
+
/**
|
|
30
|
+
* Own-key shallow equality with `Object.is`, optionally ignoring one key on
|
|
31
|
+
* both sides.
|
|
32
|
+
*
|
|
33
|
+
* The single comparison rule behind every emission-dedup decision: slice
|
|
34
|
+
* equality and the player's emit gate alike (spec-field comparison in
|
|
35
|
+
* `viewSpecsEquivalent`, composed-state reuse in {@link reuseComposedState}).
|
|
36
|
+
*/
|
|
37
|
+
export declare function shallowEqualExcept(a: object, b: object, except?: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Reuse the previous emission's composed `state` — or the whole previous spec
|
|
40
|
+
* — when the projection is value-unchanged.
|
|
41
|
+
*
|
|
42
|
+
* `deriveCurrentView` composes `state: { ...meta.view.state, context: slice }`
|
|
43
|
+
* fresh per call, and XState's `assign` produces a fresh context object on
|
|
44
|
+
* every event — so without reuse, every event would present a new `state`
|
|
45
|
+
* reference and the emit gate's per-field `Object.is` would re-emit (and
|
|
46
|
+
* remount) constantly. Slices are compared per field ({@link shallowEqualExcept} —
|
|
47
|
+
* XState's `assign` produces a fresh context object per event, so whole-object
|
|
48
|
+
* identity would report a change every time); the authored fields are spread
|
|
49
|
+
* from the static `meta.view.state`, so for an unchanged `viewKey` their
|
|
50
|
+
* references are stable and plain `Object.is` holds.
|
|
51
|
+
*
|
|
52
|
+
* Returns, in order of preference:
|
|
53
|
+
* - `prev` itself when nothing observable changed — the emit gate then
|
|
54
|
+
* short-circuits on reference identity with no element walk;
|
|
55
|
+
* - `{ ...next, state: prev.state }` when the state is value-unchanged but
|
|
56
|
+
* some other top-level field differs;
|
|
57
|
+
* - `next` when something actually changed.
|
|
58
|
+
*/
|
|
59
|
+
export declare function reuseComposedState(prev: PlaySpec | null, next: PlaySpec | null): PlaySpec | null;
|
|
60
|
+
/**
|
|
61
|
+
* Wrap a StateStore so writes under `/context` are rejected.
|
|
62
|
+
*
|
|
63
|
+
* Applied by providers to the store they hand to bindings and action handlers
|
|
64
|
+
* (`$bindState`, `setState`, chained `set`). The provider keeps the unwrapped
|
|
65
|
+
* store and refreshes the projection through it — read-only to the spec, not
|
|
66
|
+
* to the machinery.
|
|
67
|
+
*
|
|
68
|
+
* A write whose value is **identical** to the current one passes silently:
|
|
69
|
+
* `setState`-style handlers read the full snapshot, transform it, and write
|
|
70
|
+
* the whole object back — the untouched `context` key flowing through that
|
|
71
|
+
* round-trip is not a mutation attempt. Only a write that would actually
|
|
72
|
+
* change the subtree throws.
|
|
73
|
+
*
|
|
74
|
+
* Reads pass through untouched, and every member is delegated explicitly
|
|
75
|
+
* rather than spread: a consumer-supplied store may be a class instance, whose
|
|
76
|
+
* methods live on the prototype and would not survive `{ ...store }` — the
|
|
77
|
+
* first render would die on `store.getSnapshot is not a function`. The wrapper
|
|
78
|
+
* is built once per resolved store, so the delegating `getSnapshot`/`subscribe`
|
|
79
|
+
* identities stay stable for `useSyncExternalStore`-style consumers.
|
|
80
|
+
*
|
|
81
|
+
* @param store - The underlying store.
|
|
82
|
+
* @returns A store with guarded `set`/`update`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function guardContextWrites(store: StateStore): StateStore;
|
|
85
|
+
/**
|
|
86
|
+
* Refresh a live store's `/context` subtree from a derived view's composed
|
|
87
|
+
* state. A same-`viewKey` emission means only the projection changed — the
|
|
88
|
+
* subtree is replaced wholesale (never merged per-field: `update` cannot
|
|
89
|
+
* delete keys) and every ephemeral root-level value is left untouched.
|
|
90
|
+
* No-op when the view carries no slice or the store already holds it.
|
|
91
|
+
*
|
|
92
|
+
* @param store - The UNGUARDED store (providers refresh through it).
|
|
93
|
+
* @param view - The derived view whose `state.context` carries the slice.
|
|
94
|
+
*/
|
|
95
|
+
export declare function refreshContextSubtree(store: StateStore, view: {
|
|
96
|
+
state?: unknown;
|
|
97
|
+
}): void;
|
|
98
|
+
/**
|
|
99
|
+
* Compose a view's effective state: the authored `spec.state` plus the
|
|
100
|
+
* `/context` slice.
|
|
101
|
+
*
|
|
102
|
+
* When the authored state already declares the reserved key, the projection is
|
|
103
|
+
* skipped (authored state wins) with a dev warning — no existing spec breaks.
|
|
104
|
+
*
|
|
105
|
+
* @param authoredState - The raw `meta.view.state` (may be anything; sanitized by the caller/toAtomState).
|
|
106
|
+
* @param slice - The machine's context, projected wholesale.
|
|
107
|
+
*/
|
|
108
|
+
export declare function composePlayState(authoredState: Record<string, unknown> | undefined, slice: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
109
|
+
//# sourceMappingURL=context-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-projection.d.ts","sourceRoot":"","sources":["../src/context-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAiB3C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAUjF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,CAiBhG;AAgBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CA6ChE;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAKxF;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC/B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAClD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GACxC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAcrC"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context projection — machine context as a read-only `/context` subtree of the
|
|
3
|
+
* view state store.
|
|
4
|
+
*
|
|
5
|
+
* `deriveCurrentView` composes each emitted view's `state` as
|
|
6
|
+
* `{ ...meta.view.state, context: <slice> }`, so the spec is self-consistent:
|
|
7
|
+
* its `state` honestly describes the store contents, and `repeat.statePath`,
|
|
8
|
+
* `visible.$state`, `$state` props and validators can all read machine context
|
|
9
|
+
* through the ordinary `{ $state: "/context/…" }` grammar.
|
|
10
|
+
*
|
|
11
|
+
* The subtree is read-only **to the spec, not to the machinery**: providers
|
|
12
|
+
* hand bindings and action handlers a store wrapped with
|
|
13
|
+
* {@link guardContextWrites}, while refreshing the projection through the
|
|
14
|
+
* unguarded store underneath. Context changes flow exclusively through machine
|
|
15
|
+
* events — a write under `/context` is always a spec bug, and the guard says so.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The reserved top-level state key the projection materializes under.
|
|
21
|
+
*
|
|
22
|
+
* A view's authored `spec.state` must not declare this key — when it does, the
|
|
23
|
+
* projection is skipped for that view (the authored state wins) and a dev
|
|
24
|
+
* warning is emitted. See {@link composePlayState}.
|
|
25
|
+
*/
|
|
26
|
+
export const CONTEXT_STATE_KEY = "context";
|
|
27
|
+
/**
|
|
28
|
+
* Once-per-spec diagnostic dedup. The condition reported here is a static
|
|
29
|
+
* property of the authored meta.view (its `state` object never changes
|
|
30
|
+
* between events), but `deriveCurrentView` runs on every machine snapshot —
|
|
31
|
+
* reporting per derivation would flood the console at event rate. Keyed by
|
|
32
|
+
* the static object's identity, so each spec reports exactly once and tests
|
|
33
|
+
* with fresh literals stay isolated.
|
|
34
|
+
*/
|
|
35
|
+
const reportedDiagnostics = new WeakSet();
|
|
36
|
+
function warnOnce(anchor, message) {
|
|
37
|
+
if (reportedDiagnostics.has(anchor))
|
|
38
|
+
return;
|
|
39
|
+
reportedDiagnostics.add(anchor);
|
|
40
|
+
console.warn(message);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Own-key shallow equality with `Object.is`, optionally ignoring one key on
|
|
44
|
+
* both sides.
|
|
45
|
+
*
|
|
46
|
+
* The single comparison rule behind every emission-dedup decision: slice
|
|
47
|
+
* equality and the player's emit gate alike (spec-field comparison in
|
|
48
|
+
* `viewSpecsEquivalent`, composed-state reuse in {@link reuseComposedState}).
|
|
49
|
+
*/
|
|
50
|
+
export function shallowEqualExcept(a, b, except) {
|
|
51
|
+
const aEntries = Object.entries(a).filter(([key]) => key !== except);
|
|
52
|
+
const bKeyCount = Object.keys(b).filter((key) => key !== except).length;
|
|
53
|
+
if (aEntries.length !== bKeyCount)
|
|
54
|
+
return false;
|
|
55
|
+
const bRecord = b;
|
|
56
|
+
for (const [key, value] of aEntries) {
|
|
57
|
+
// nosemgrep: gitlab.eslint.detect-object-injection
|
|
58
|
+
if (!Object.hasOwn(b, key) || !Object.is(value, bRecord[key]))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reuse the previous emission's composed `state` — or the whole previous spec
|
|
65
|
+
* — when the projection is value-unchanged.
|
|
66
|
+
*
|
|
67
|
+
* `deriveCurrentView` composes `state: { ...meta.view.state, context: slice }`
|
|
68
|
+
* fresh per call, and XState's `assign` produces a fresh context object on
|
|
69
|
+
* every event — so without reuse, every event would present a new `state`
|
|
70
|
+
* reference and the emit gate's per-field `Object.is` would re-emit (and
|
|
71
|
+
* remount) constantly. Slices are compared per field ({@link shallowEqualExcept} —
|
|
72
|
+
* XState's `assign` produces a fresh context object per event, so whole-object
|
|
73
|
+
* identity would report a change every time); the authored fields are spread
|
|
74
|
+
* from the static `meta.view.state`, so for an unchanged `viewKey` their
|
|
75
|
+
* references are stable and plain `Object.is` holds.
|
|
76
|
+
*
|
|
77
|
+
* Returns, in order of preference:
|
|
78
|
+
* - `prev` itself when nothing observable changed — the emit gate then
|
|
79
|
+
* short-circuits on reference identity with no element walk;
|
|
80
|
+
* - `{ ...next, state: prev.state }` when the state is value-unchanged but
|
|
81
|
+
* some other top-level field differs;
|
|
82
|
+
* - `next` when something actually changed.
|
|
83
|
+
*/
|
|
84
|
+
export function reuseComposedState(prev, next) {
|
|
85
|
+
if (!prev || !next || prev.viewKey !== next.viewKey)
|
|
86
|
+
return next;
|
|
87
|
+
const prevState = prev.state;
|
|
88
|
+
const nextState = next.state;
|
|
89
|
+
if (prevState !== nextState) {
|
|
90
|
+
if (prevState === undefined || nextState === undefined)
|
|
91
|
+
return next;
|
|
92
|
+
if (!shallowEqualExcept(prevState, nextState, CONTEXT_STATE_KEY))
|
|
93
|
+
return next;
|
|
94
|
+
const prevSlice = prevState[CONTEXT_STATE_KEY];
|
|
95
|
+
const nextSlice = nextState[CONTEXT_STATE_KEY];
|
|
96
|
+
if (prevSlice !== nextSlice) {
|
|
97
|
+
if (prevSlice === undefined || nextSlice === undefined)
|
|
98
|
+
return next;
|
|
99
|
+
if (!shallowEqualExcept(prevSlice, nextSlice))
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
if (shallowEqualExcept(prev, next, "state"))
|
|
103
|
+
return prev;
|
|
104
|
+
return { ...next, state: prevState };
|
|
105
|
+
}
|
|
106
|
+
return shallowEqualExcept(prev, next, "state") ? prev : next;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Paths that address the reserved subtree: "/context" itself or below it.
|
|
110
|
+
* Store update maps may carry bare top-level keys ("context") as well as
|
|
111
|
+
* JSON Pointers ("/context/…") — both forms address the same subtree.
|
|
112
|
+
*/
|
|
113
|
+
function toPointer(path) {
|
|
114
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
115
|
+
}
|
|
116
|
+
/** Expects a NORMALIZED pointer from {@link toPointer}. */
|
|
117
|
+
function isContextPointer(pointer) {
|
|
118
|
+
return pointer === `/${CONTEXT_STATE_KEY}` || pointer.startsWith(`/${CONTEXT_STATE_KEY}/`);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Wrap a StateStore so writes under `/context` are rejected.
|
|
122
|
+
*
|
|
123
|
+
* Applied by providers to the store they hand to bindings and action handlers
|
|
124
|
+
* (`$bindState`, `setState`, chained `set`). The provider keeps the unwrapped
|
|
125
|
+
* store and refreshes the projection through it — read-only to the spec, not
|
|
126
|
+
* to the machinery.
|
|
127
|
+
*
|
|
128
|
+
* A write whose value is **identical** to the current one passes silently:
|
|
129
|
+
* `setState`-style handlers read the full snapshot, transform it, and write
|
|
130
|
+
* the whole object back — the untouched `context` key flowing through that
|
|
131
|
+
* round-trip is not a mutation attempt. Only a write that would actually
|
|
132
|
+
* change the subtree throws.
|
|
133
|
+
*
|
|
134
|
+
* Reads pass through untouched, and every member is delegated explicitly
|
|
135
|
+
* rather than spread: a consumer-supplied store may be a class instance, whose
|
|
136
|
+
* methods live on the prototype and would not survive `{ ...store }` — the
|
|
137
|
+
* first render would die on `store.getSnapshot is not a function`. The wrapper
|
|
138
|
+
* is built once per resolved store, so the delegating `getSnapshot`/`subscribe`
|
|
139
|
+
* identities stay stable for `useSyncExternalStore`-style consumers.
|
|
140
|
+
*
|
|
141
|
+
* @param store - The underlying store.
|
|
142
|
+
* @returns A store with guarded `set`/`update`.
|
|
143
|
+
*/
|
|
144
|
+
export function guardContextWrites(store) {
|
|
145
|
+
const reject = (path) => {
|
|
146
|
+
throw new Error(`[play-actor] "${path}" is read-only: /${CONTEXT_STATE_KEY} mirrors the machine's context. ` +
|
|
147
|
+
`Send the actor an event to change it — context never changes through the view store.`);
|
|
148
|
+
};
|
|
149
|
+
const checkWrite = (path, value) => {
|
|
150
|
+
const pointer = toPointer(path);
|
|
151
|
+
if (!isContextPointer(pointer))
|
|
152
|
+
return true;
|
|
153
|
+
// Unchanged round-trip (read-modify-write of the whole snapshot) is a
|
|
154
|
+
// no-op for this subtree, not a mutation — drop the key silently.
|
|
155
|
+
if (Object.is(store.get(pointer), value))
|
|
156
|
+
return false;
|
|
157
|
+
return reject(path);
|
|
158
|
+
};
|
|
159
|
+
const guarded = {
|
|
160
|
+
get: (path) => store.get(path),
|
|
161
|
+
getSnapshot: () => store.getSnapshot(),
|
|
162
|
+
subscribe: (listener) => store.subscribe(listener),
|
|
163
|
+
set: (path, value) => {
|
|
164
|
+
if (checkWrite(path, value))
|
|
165
|
+
store.set(path, value);
|
|
166
|
+
},
|
|
167
|
+
update: (updates) => {
|
|
168
|
+
const allowed = {};
|
|
169
|
+
let dropped = false;
|
|
170
|
+
for (const [path, value] of Object.entries(updates)) {
|
|
171
|
+
if (checkWrite(path, value)) {
|
|
172
|
+
allowed[path] = value; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
dropped = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
store.update(dropped ? allowed : updates);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
// Optional in the contract, and its absence is meaningful: json-render-core
|
|
182
|
+
// falls back to `getSnapshot` when the key is missing, so the wrapper must
|
|
183
|
+
// not manufacture one that returns `undefined`.
|
|
184
|
+
const { getServerSnapshot } = store;
|
|
185
|
+
if (getServerSnapshot) {
|
|
186
|
+
guarded.getServerSnapshot = () => getServerSnapshot.call(store);
|
|
187
|
+
}
|
|
188
|
+
return guarded;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Refresh a live store's `/context` subtree from a derived view's composed
|
|
192
|
+
* state. A same-`viewKey` emission means only the projection changed — the
|
|
193
|
+
* subtree is replaced wholesale (never merged per-field: `update` cannot
|
|
194
|
+
* delete keys) and every ephemeral root-level value is left untouched.
|
|
195
|
+
* No-op when the view carries no slice or the store already holds it.
|
|
196
|
+
*
|
|
197
|
+
* @param store - The UNGUARDED store (providers refresh through it).
|
|
198
|
+
* @param view - The derived view whose `state.context` carries the slice.
|
|
199
|
+
*/
|
|
200
|
+
export function refreshContextSubtree(store, view) {
|
|
201
|
+
const slice = view.state?.[CONTEXT_STATE_KEY];
|
|
202
|
+
if (slice !== undefined && !Object.is(store.get(`/${CONTEXT_STATE_KEY}`), slice)) {
|
|
203
|
+
store.update({ [`/${CONTEXT_STATE_KEY}`]: slice });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Compose a view's effective state: the authored `spec.state` plus the
|
|
208
|
+
* `/context` slice.
|
|
209
|
+
*
|
|
210
|
+
* When the authored state already declares the reserved key, the projection is
|
|
211
|
+
* skipped (authored state wins) with a dev warning — no existing spec breaks.
|
|
212
|
+
*
|
|
213
|
+
* @param authoredState - The raw `meta.view.state` (may be anything; sanitized by the caller/toAtomState).
|
|
214
|
+
* @param slice - The machine's context, projected wholesale.
|
|
215
|
+
*/
|
|
216
|
+
export function composePlayState(authoredState, slice) {
|
|
217
|
+
if (authoredState !== undefined && Object.hasOwn(authoredState, CONTEXT_STATE_KEY)) {
|
|
218
|
+
warnOnce(authoredState, `[play-actor] spec.state declares a top-level "${CONTEXT_STATE_KEY}" key; ` +
|
|
219
|
+
`skipping the machine-context projection for this view. ` +
|
|
220
|
+
`Rename the state field to project context at /${CONTEXT_STATE_KEY}.`);
|
|
221
|
+
return authoredState;
|
|
222
|
+
}
|
|
223
|
+
if (slice === undefined) {
|
|
224
|
+
return authoredState;
|
|
225
|
+
}
|
|
226
|
+
return { ...authoredState, [CONTEXT_STATE_KEY]: slice };
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=context-projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-projection.js","sourceRoot":"","sources":["../src/context-projection.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,4 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export { AbstractActor, typedSpec, type Routable, type Viewable, type PlaySpec, type BaseActorProviderProps, type BaseViewContextValue, } from "./abstract-actor.js";
|
|
19
19
|
export { toAtomState, attachRenderErrorHandler } from "./provider-guards.js";
|
|
20
|
+
export { CONTEXT_STATE_KEY, guardContextWrites, refreshContextSubtree, composePlayState, shallowEqualExcept, reuseComposedState, } from "./context-projection.js";
|
|
21
|
+
export { createViewStoreLifecycle } from "./view-store-lifecycle.js";
|
|
22
|
+
export type { ViewStoreLifecycle, ViewStoreResolution, ResolveViewStoreOptions, } from "./view-store-lifecycle.js";
|
|
20
23
|
//# 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,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"}
|
|
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;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,YAAY,EACX,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,GACvB,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -17,4 +17,6 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export { AbstractActor, typedSpec, } from "./abstract-actor.js";
|
|
19
19
|
export { toAtomState, attachRenderErrorHandler } from "./provider-guards.js";
|
|
20
|
+
export { CONTEXT_STATE_KEY, guardContextWrites, refreshContextSubtree, composePlayState, shallowEqualExcept, reuseComposedState, } from "./context-projection.js";
|
|
21
|
+
export { createViewStoreLifecycle } from "./view-store-lifecycle.js";
|
|
20
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;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACN,aAAa,EACb,SAAS,GAMT,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
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;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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View-store lifecycle — the framework-agnostic reseed-vs-refresh decision
|
|
3
|
+
* every provider wires into its own reactivity.
|
|
4
|
+
*
|
|
5
|
+
* The policy in one place (previously hand-copied per renderer, where the
|
|
6
|
+
* copies drifted into real bugs):
|
|
7
|
+
*
|
|
8
|
+
* - `viewKey` changed (or first resolve, or actor swapped) → RESEED: a fresh
|
|
9
|
+
* store from the view's composed state.
|
|
10
|
+
* - `viewKey` unchanged → REFRESH: only the /context projection moved, so the
|
|
11
|
+
* `/context` subtree is replaced in place and ephemeral root-level state
|
|
12
|
+
* (drafts, toggles) survives.
|
|
13
|
+
* - No `viewKey` (a hand-built Viewable that does not stamp one) → reseed per
|
|
14
|
+
* EMISSION, the safe pre-viewKey behavior.
|
|
15
|
+
* - Actor swap → the kept-alive store dies with its actor (lifetime rule).
|
|
16
|
+
* - Controlled mode (caller-supplied store) → the caller owns seeding and
|
|
17
|
+
* lifecycle, but /context is machinery-owned in both modes and is refreshed
|
|
18
|
+
* here — unless the caller defers it ({@link ResolveViewStoreOptions}),
|
|
19
|
+
* which React's render path needs (store subscribers must not be notified
|
|
20
|
+
* mid-render; its effects refresh instead).
|
|
21
|
+
*
|
|
22
|
+
* What stays in each provider is only reactivity wiring: WHEN to resolve, and
|
|
23
|
+
* how the result reaches children.
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
import type { StateStore } from "@xmachines/json-render-core";
|
|
28
|
+
import type { PlaySpec } from "./abstract-actor.js";
|
|
29
|
+
/** What a resolve produced — see {@link ViewStoreLifecycle.resolve}. */
|
|
30
|
+
export interface ViewStoreResolution {
|
|
31
|
+
/** The UNGUARDED store — the machinery's own refresh reference. */
|
|
32
|
+
store: StateStore;
|
|
33
|
+
/**
|
|
34
|
+
* The store to hand children ($bindState, setState, chained set): writes
|
|
35
|
+
* under /context throw. One wrapper per underlying store — the identity is
|
|
36
|
+
* cached so `useSyncExternalStore`-style consumers stay stable.
|
|
37
|
+
*/
|
|
38
|
+
guardedStore: StateStore;
|
|
39
|
+
/**
|
|
40
|
+
* True when this resolve created a fresh store (first resolve, viewKey
|
|
41
|
+
* change, per-emission fallback, actor swap). Providers that remount a
|
|
42
|
+
* subtree per store (Vue's storeKey) key on it.
|
|
43
|
+
*/
|
|
44
|
+
reseeded: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface ResolveViewStoreOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Controlled mode only: refresh the external store's /context during this
|
|
49
|
+
* resolve (default true). Pass false where notifying store subscribers is
|
|
50
|
+
* not allowed at the call site (React's render path) and refresh from an
|
|
51
|
+
* effect instead.
|
|
52
|
+
*/
|
|
53
|
+
refreshExternalStore?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface ViewStoreLifecycle {
|
|
56
|
+
/**
|
|
57
|
+
* Bring the store in line with an emission and return it (with its guard).
|
|
58
|
+
*
|
|
59
|
+
* @param actor - The actor the emission came from — a swap drops the kept store.
|
|
60
|
+
* @param view - The derived view (non-null; a null emission is a GAP, not a
|
|
61
|
+
* new view — callers simply do not resolve on null, keeping the store).
|
|
62
|
+
* @param externalStore - Controlled mode: the caller-owned store.
|
|
63
|
+
* @param options - See {@link ResolveViewStoreOptions}.
|
|
64
|
+
*/
|
|
65
|
+
resolve(actor: unknown, view: PlaySpec, externalStore?: StateStore | undefined, options?: ResolveViewStoreOptions): ViewStoreResolution;
|
|
66
|
+
/** Drop everything (unmount/disconnect) — the next resolve reseeds. */
|
|
67
|
+
reset(): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a lifecycle coordinator.
|
|
71
|
+
*
|
|
72
|
+
* @param createStore - Framework-supplied store factory; receives the
|
|
73
|
+
* proto-safe seed (`toAtomState(view.state)` — the composed state already
|
|
74
|
+
* carries /context).
|
|
75
|
+
*/
|
|
76
|
+
export declare function createViewStoreLifecycle(createStore: (seed: Record<string, unknown>) => StateStore): ViewStoreLifecycle;
|
|
77
|
+
//# sourceMappingURL=view-store-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-store-lifecycle.d.ts","sourceRoot":"","sources":["../src/view-store-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,wEAAwE;AACxE,MAAM,WAAW,mBAAmB;IACnC,mEAAmE;IACnE,KAAK,EAAE,UAAU,CAAC;IAClB;;;;OAIG;IACH,YAAY,EAAE,UAAU,CAAC;IACzB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACvC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IAClC;;;;;;;;OAQG;IACH,OAAO,CACN,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,QAAQ,EACd,aAAa,CAAC,EAAE,UAAU,GAAG,SAAS,EACtC,OAAO,CAAC,EAAE,uBAAuB,GAC/B,mBAAmB,CAAC;IACvB,uEAAuE;IACvE,KAAK,IAAI,IAAI,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACvC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,UAAU,GACxD,kBAAkB,CA4DpB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View-store lifecycle — the framework-agnostic reseed-vs-refresh decision
|
|
3
|
+
* every provider wires into its own reactivity.
|
|
4
|
+
*
|
|
5
|
+
* The policy in one place (previously hand-copied per renderer, where the
|
|
6
|
+
* copies drifted into real bugs):
|
|
7
|
+
*
|
|
8
|
+
* - `viewKey` changed (or first resolve, or actor swapped) → RESEED: a fresh
|
|
9
|
+
* store from the view's composed state.
|
|
10
|
+
* - `viewKey` unchanged → REFRESH: only the /context projection moved, so the
|
|
11
|
+
* `/context` subtree is replaced in place and ephemeral root-level state
|
|
12
|
+
* (drafts, toggles) survives.
|
|
13
|
+
* - No `viewKey` (a hand-built Viewable that does not stamp one) → reseed per
|
|
14
|
+
* EMISSION, the safe pre-viewKey behavior.
|
|
15
|
+
* - Actor swap → the kept-alive store dies with its actor (lifetime rule).
|
|
16
|
+
* - Controlled mode (caller-supplied store) → the caller owns seeding and
|
|
17
|
+
* lifecycle, but /context is machinery-owned in both modes and is refreshed
|
|
18
|
+
* here — unless the caller defers it ({@link ResolveViewStoreOptions}),
|
|
19
|
+
* which React's render path needs (store subscribers must not be notified
|
|
20
|
+
* mid-render; its effects refresh instead).
|
|
21
|
+
*
|
|
22
|
+
* What stays in each provider is only reactivity wiring: WHEN to resolve, and
|
|
23
|
+
* how the result reaches children.
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
import { guardContextWrites, refreshContextSubtree } from "./context-projection.js";
|
|
28
|
+
import { toAtomState } from "./provider-guards.js";
|
|
29
|
+
/**
|
|
30
|
+
* Create a lifecycle coordinator.
|
|
31
|
+
*
|
|
32
|
+
* @param createStore - Framework-supplied store factory; receives the
|
|
33
|
+
* proto-safe seed (`toAtomState(view.state)` — the composed state already
|
|
34
|
+
* carries /context).
|
|
35
|
+
*/
|
|
36
|
+
export function createViewStoreLifecycle(createStore) {
|
|
37
|
+
let internalStore = null;
|
|
38
|
+
let lastViewKey = undefined;
|
|
39
|
+
let lastView = null;
|
|
40
|
+
let lastActor = null;
|
|
41
|
+
// Guard identity cache — one wrapper per underlying store.
|
|
42
|
+
let guardedSource = null;
|
|
43
|
+
let guardedStore = null;
|
|
44
|
+
const reset = () => {
|
|
45
|
+
internalStore = null;
|
|
46
|
+
lastViewKey = undefined;
|
|
47
|
+
lastView = null;
|
|
48
|
+
lastActor = null;
|
|
49
|
+
guardedSource = null;
|
|
50
|
+
guardedStore = null;
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
reset,
|
|
54
|
+
resolve(actor, view, externalStore, options) {
|
|
55
|
+
// Lifetime rule: a kept-alive store must not outlive its actor.
|
|
56
|
+
if (lastActor !== actor) {
|
|
57
|
+
const firstResolve = lastActor === null && internalStore === null;
|
|
58
|
+
if (!firstResolve)
|
|
59
|
+
reset();
|
|
60
|
+
lastActor = actor;
|
|
61
|
+
}
|
|
62
|
+
let resolved;
|
|
63
|
+
let reseeded = false;
|
|
64
|
+
if (externalStore) {
|
|
65
|
+
resolved = externalStore;
|
|
66
|
+
if (options?.refreshExternalStore !== false) {
|
|
67
|
+
refreshContextSubtree(resolved, view);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const viewKey = view.viewKey;
|
|
72
|
+
let nextStore = internalStore;
|
|
73
|
+
if (nextStore === null ||
|
|
74
|
+
(viewKey !== undefined ? lastViewKey !== viewKey : lastView !== view)) {
|
|
75
|
+
nextStore = createStore(toAtomState(view.state));
|
|
76
|
+
internalStore = nextStore;
|
|
77
|
+
lastViewKey = viewKey;
|
|
78
|
+
reseeded = true;
|
|
79
|
+
}
|
|
80
|
+
else if (viewKey !== undefined) {
|
|
81
|
+
refreshContextSubtree(nextStore, view);
|
|
82
|
+
}
|
|
83
|
+
lastView = view;
|
|
84
|
+
resolved = nextStore;
|
|
85
|
+
}
|
|
86
|
+
if (guardedSource !== resolved) {
|
|
87
|
+
guardedSource = resolved;
|
|
88
|
+
guardedStore = guardContextWrites(resolved);
|
|
89
|
+
}
|
|
90
|
+
return { store: resolved, guardedStore: guardedStore, reseeded };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=view-store-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-store-lifecycle.js","sourceRoot":"","sources":["../src/view-store-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAkDnD;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACvC,WAA0D;IAE1D,IAAI,aAAa,GAAsB,IAAI,CAAC;IAC5C,IAAI,WAAW,GAAuB,SAAS,CAAC;IAChD,IAAI,QAAQ,GAAoB,IAAI,CAAC;IACrC,IAAI,SAAS,GAAY,IAAI,CAAC;IAC9B,2DAA2D;IAC3D,IAAI,aAAa,GAAsB,IAAI,CAAC;IAC5C,IAAI,YAAY,GAAsB,IAAI,CAAC;IAE3C,MAAM,KAAK,GAAG,GAAS,EAAE;QACxB,aAAa,GAAG,IAAI,CAAC;QACrB,WAAW,GAAG,SAAS,CAAC;QACxB,QAAQ,GAAG,IAAI,CAAC;QAChB,SAAS,GAAG,IAAI,CAAC;QACjB,aAAa,GAAG,IAAI,CAAC;QACrB,YAAY,GAAG,IAAI,CAAC;IACrB,CAAC,CAAC;IAEF,OAAO;QACN,KAAK;QACL,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO;YAC1C,gEAAgE;YAChE,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;gBACzB,MAAM,YAAY,GAAG,SAAS,KAAK,IAAI,IAAI,aAAa,KAAK,IAAI,CAAC;gBAClE,IAAI,CAAC,YAAY;oBAAE,KAAK,EAAE,CAAC;gBAC3B,SAAS,GAAG,KAAK,CAAC;YACnB,CAAC;YAED,IAAI,QAAoB,CAAC;YACzB,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,aAAa,EAAE,CAAC;gBACnB,QAAQ,GAAG,aAAa,CAAC;gBACzB,IAAI,OAAO,EAAE,oBAAoB,KAAK,KAAK,EAAE,CAAC;oBAC7C,qBAAqB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;gBACvC,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;gBAC7B,IAAI,SAAS,GAAsB,aAAa,CAAC;gBACjD,IACC,SAAS,KAAK,IAAI;oBAClB,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,EACpE,CAAC;oBACF,SAAS,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;oBACjD,aAAa,GAAG,SAAS,CAAC;oBAC1B,WAAW,GAAG,OAAO,CAAC;oBACtB,QAAQ,GAAG,IAAI,CAAC;gBACjB,CAAC;qBAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAClC,qBAAqB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBACxC,CAAC;gBACD,QAAQ,GAAG,IAAI,CAAC;gBAChB,QAAQ,GAAG,SAAS,CAAC;YACtB,CAAC;YAED,IAAI,aAAa,KAAK,QAAQ,EAAE,CAAC;gBAChC,aAAa,GAAG,QAAQ,CAAC;gBACzB,YAAY,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;YAC7C,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,YAA0B,EAAE,QAAQ,EAAE,CAAC;QAChF,CAAC;KACD,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-actor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Abstract Actor base class for XMachines Play Architecture",
|
|
6
6
|
"keywords": [
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"author": "XMachines Contributors",
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+
|
|
17
|
+
"url": "git+https://gitlab.com/xmachin-es/xmachines-js.git",
|
|
18
18
|
"directory": "packages/play-actor"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
@@ -24,17 +24,20 @@
|
|
|
24
24
|
],
|
|
25
25
|
"type": "module",
|
|
26
26
|
"sideEffects": false,
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
27
29
|
"exports": {
|
|
28
30
|
".": {
|
|
29
31
|
"types": "./dist/index.d.ts",
|
|
30
|
-
"
|
|
31
|
-
}
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
32
35
|
},
|
|
33
36
|
"publishConfig": {
|
|
34
37
|
"access": "public"
|
|
35
38
|
},
|
|
36
39
|
"scripts": {
|
|
37
|
-
"build": "tsc --build",
|
|
40
|
+
"build": "vite build && tsc --build",
|
|
38
41
|
"clean": "rm -rf dist *.tsbuildinfo coverage node_modules/.svelte2tsx-* node_modules/.vite*",
|
|
39
42
|
"test": "vitest",
|
|
40
43
|
"lint": "oxlint .",
|
|
@@ -42,20 +45,20 @@
|
|
|
42
45
|
"format": "oxfmt .",
|
|
43
46
|
"format:check": "oxfmt --check ."
|
|
44
47
|
},
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@xmachines/json-render-core": "^0.19.0-xm.2"
|
|
47
|
-
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@testing-library/jest-dom": "^6.9.1",
|
|
50
50
|
"@types/node": "^26.2.0",
|
|
51
|
+
"@xmachines/json-render-core": "^0.20.0-xm.2",
|
|
51
52
|
"oxfmt": "^0.64.0",
|
|
52
53
|
"oxlint": "^1.79.0",
|
|
54
|
+
"vite": "^8.0.10",
|
|
53
55
|
"vitest": "^4.1.11",
|
|
54
56
|
"xstate": "^5.31.0"
|
|
55
57
|
},
|
|
56
58
|
"peerDependencies": {
|
|
57
|
-
"@xmachines/
|
|
58
|
-
"@xmachines/play
|
|
59
|
+
"@xmachines/json-render-core": "^0.20.0-xm.2",
|
|
60
|
+
"@xmachines/play": "2.0.0",
|
|
61
|
+
"@xmachines/play-signals": "2.0.0",
|
|
59
62
|
"xstate": "^5.31.0"
|
|
60
63
|
},
|
|
61
64
|
"engines": {
|