@xmachines/play-xstate 2.0.0-alpha.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -114
- package/dist/define-player.d.ts +16 -16
- package/dist/define-player.js +16 -16
- package/dist/errors.d.ts +84 -101
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +108 -108
- package/dist/errors.js.map +1 -1
- package/dist/guards/compose.d.ts +70 -77
- package/dist/guards/compose.d.ts.map +1 -1
- package/dist/guards/compose.js +90 -113
- package/dist/guards/compose.js.map +1 -1
- package/dist/guards/helpers.d.ts +22 -18
- package/dist/guards/helpers.d.ts.map +1 -1
- package/dist/guards/helpers.js +23 -19
- package/dist/guards/helpers.js.map +1 -1
- package/dist/guards/index.d.ts +10 -3
- package/dist/guards/index.d.ts.map +1 -1
- package/dist/guards/index.js +10 -3
- package/dist/guards/index.js.map +1 -1
- package/dist/guards/types.d.ts +9 -9
- package/dist/guards/types.d.ts.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/player-actor.d.ts +197 -113
- package/dist/player-actor.d.ts.map +1 -1
- package/dist/player-actor.js +413 -401
- package/dist/player-actor.js.map +1 -1
- package/dist/routing/build-url.d.ts +19 -21
- package/dist/routing/build-url.d.ts.map +1 -1
- package/dist/routing/build-url.js +70 -71
- package/dist/routing/build-url.js.map +1 -1
- package/dist/routing/derive-current-route.d.ts +51 -13
- package/dist/routing/derive-current-route.d.ts.map +1 -1
- package/dist/routing/derive-current-route.js +69 -60
- package/dist/routing/derive-current-route.js.map +1 -1
- package/dist/routing/derive-initial-route.d.ts +23 -23
- package/dist/routing/derive-initial-route.js +27 -27
- package/dist/routing/derive-initial-route.js.map +1 -1
- package/dist/routing/derive-route.d.ts +38 -37
- package/dist/routing/derive-route.d.ts.map +1 -1
- package/dist/routing/derive-route.js +45 -42
- package/dist/routing/derive-route.js.map +1 -1
- package/dist/routing/format-play-route-transitions.d.ts +34 -71
- package/dist/routing/format-play-route-transitions.d.ts.map +1 -1
- package/dist/routing/format-play-route-transitions.js +74 -130
- package/dist/routing/format-play-route-transitions.js.map +1 -1
- package/dist/routing/index.d.ts +4 -8
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +4 -6
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/types.d.ts +12 -11
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/types.d.ts +97 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/view/derive-current-view.d.ts +51 -0
- package/dist/view/derive-current-view.d.ts.map +1 -0
- package/dist/view/derive-current-view.js +119 -0
- package/dist/view/derive-current-view.js.map +1 -0
- package/package.json +22 -21
- package/dist/define-player.typecheck.d.ts +0 -2
- package/dist/define-player.typecheck.d.ts.map +0 -1
- package/dist/define-player.typecheck.js +0 -48
- package/dist/define-player.typecheck.js.map +0 -1
- package/dist/guards/compose.typecheck.d.ts +0 -2
- package/dist/guards/compose.typecheck.d.ts.map +0 -1
- package/dist/guards/compose.typecheck.js +0 -22
- package/dist/guards/compose.typecheck.js.map +0 -1
- package/dist/player-actor.typecheck.d.ts +0 -2
- package/dist/player-actor.typecheck.d.ts.map +0 -1
- package/dist/player-actor.typecheck.js +0 -30
- package/dist/player-actor.typecheck.js.map +0 -1
- package/dist/routing/create-routed-machine.d.ts +0 -71
- package/dist/routing/create-routed-machine.d.ts.map +0 -1
- package/dist/routing/create-routed-machine.js +0 -71
- package/dist/routing/create-routed-machine.js.map +0 -1
- package/dist/routing/play-route-event.typecheck.d.ts +0 -2
- package/dist/routing/play-route-event.typecheck.d.ts.map +0 -1
- package/dist/routing/play-route-event.typecheck.js +0 -43
- package/dist/routing/play-route-event.typecheck.js.map +0 -1
- package/dist/routing/schemas.d.ts +0 -99
- package/dist/routing/schemas.d.ts.map +0 -1
- package/dist/routing/schemas.js +0 -30
- package/dist/routing/schemas.js.map +0 -1
- package/dist/schemas.d.ts +0 -28
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js +0 -29
- package/dist/schemas.js.map +0 -1
package/dist/guards/compose.d.ts
CHANGED
|
@@ -1,59 +1,44 @@
|
|
|
1
|
+
import type { GuardPredicate } from "xstate";
|
|
1
2
|
import type { Guard, GuardArray } from "./types.js";
|
|
3
|
+
import type { MachineContext, EventObject, ParameterizedObject } from "xstate";
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
5
|
+
* The narrowest public return type of the guard composition helpers.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* composed guard is therefore just another predicate with the same shape as
|
|
8
|
-
* {@link Guard}, callable anywhere transition args are available.
|
|
9
|
-
*
|
|
10
|
-
* String guard names are resolved against `args.guards` (the named guards
|
|
11
|
-
* declared in `setup({ guards })`, which XState v6 passes to every transition
|
|
12
|
-
* function). Resolving a name outside a transition function — or a name that
|
|
13
|
-
* was never declared — throws `UnresolvableGuardNameError` immediately.
|
|
14
|
-
*
|
|
15
|
-
* String names work in `on` transitions, `always`, entry/exit actions (since
|
|
16
|
-
* xstate 6.0.0-alpha.20 populates `guards` there), and function-form route
|
|
17
|
-
* resolvers.
|
|
7
|
+
* `GuardPredicate<MachineContext, EventObject, unknown, ParameterizedObject>` is the
|
|
8
|
+
* concrete XState guard type with the widest compatibility that uses no `any`.
|
|
18
9
|
*
|
|
19
10
|
* @public
|
|
11
|
+
* @deprecated The next major version removes the guard utilities. Use the
|
|
12
|
+
* combinator types of XState directly.
|
|
20
13
|
*/
|
|
21
|
-
export type ComposedGuard =
|
|
14
|
+
export type ComposedGuard = GuardPredicate<MachineContext, EventObject, unknown, ParameterizedObject>;
|
|
22
15
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Combines multiple guard predicates using AND semantics—all guards must pass for
|
|
26
|
-
* the composition to succeed. Evaluation is short-circuiting, left to right.
|
|
27
|
-
*
|
|
28
|
-
* **Architectural Context:** Supports **Actor Authority (INV-01)** by enabling
|
|
29
|
-
* declarative guard composition in state machine transitions. Guards enforce business
|
|
30
|
-
* logic rules that determine whether navigation or actions are valid.
|
|
16
|
+
* Composes the guards with the AND logic, through the and() helper of XState
|
|
31
17
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
18
|
+
* The function joins more than one guard predicate with the AND semantics: every
|
|
19
|
+
* guard must pass, and the composition then succeeds. It uses the built-in `and()`
|
|
20
|
+
* helper of XState. The type inference and the serialization of the machine are
|
|
21
|
+
* therefore correct.
|
|
34
22
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
23
|
+
* **Architectural context:** the function supports **Actor Authority (INV-01)**,
|
|
24
|
+
* because it composes the guards of a state machine transition declaratively. A
|
|
25
|
+
* guard enforces a rule of the business logic, and that rule decides if a
|
|
26
|
+
* navigation or an action is valid.
|
|
38
27
|
*
|
|
39
|
-
* @typeParam TContext -
|
|
40
|
-
*
|
|
41
|
-
* @typeParam TEvent - Event type
|
|
28
|
+
* @typeParam TContext - The context type of the state machine
|
|
29
|
+
* @typeParam TEvent - The event type
|
|
42
30
|
*
|
|
43
|
-
* @param guards -
|
|
44
|
-
*
|
|
45
|
-
* @returns Predicate returning `true` only when every guard passes
|
|
31
|
+
* @param guards - The array of the guard predicates, or of the guard names as strings
|
|
32
|
+
* @returns The and() guard composition of XState
|
|
46
33
|
*
|
|
47
|
-
* @throws {
|
|
34
|
+
* @throws {Error} When the array of the guards is empty
|
|
48
35
|
*
|
|
49
36
|
* @example
|
|
50
|
-
* AND composition with named guards
|
|
37
|
+
* An AND composition with named guards
|
|
51
38
|
* ```typescript
|
|
52
39
|
* import { setup } from "xstate";
|
|
53
40
|
* import { composeGuards } from "@xmachines/play-xstate";
|
|
54
41
|
*
|
|
55
|
-
* const canAccessAdmin = composeGuards(['isLoggedIn', 'hasPermission']);
|
|
56
|
-
*
|
|
57
42
|
* const machine = setup({
|
|
58
43
|
* guards: {
|
|
59
44
|
* isLoggedIn: ({ context }) => !!context.userId,
|
|
@@ -61,49 +46,53 @@ export type ComposedGuard = (args: unknown) => boolean;
|
|
|
61
46
|
* }
|
|
62
47
|
* }).createMachine({
|
|
63
48
|
* on: {
|
|
64
|
-
* accessAdmin:
|
|
65
|
-
*
|
|
66
|
-
*
|
|
49
|
+
* accessAdmin: {
|
|
50
|
+
* // Both guards must pass
|
|
51
|
+
* guard: composeGuards(['isLoggedIn', 'hasPermission']),
|
|
52
|
+
* target: 'adminPanel'
|
|
67
53
|
* }
|
|
68
54
|
* }
|
|
69
55
|
* });
|
|
70
56
|
* ```
|
|
71
57
|
*
|
|
72
58
|
* @example
|
|
73
|
-
* AND composition with inline predicates
|
|
59
|
+
* An AND composition with inline predicates
|
|
74
60
|
* ```typescript
|
|
75
61
|
* import { composeGuards } from "@xmachines/play-xstate";
|
|
76
62
|
*
|
|
77
|
-
*
|
|
63
|
+
* guard: composeGuards([
|
|
78
64
|
* ({ context }) => context.age >= 18,
|
|
79
65
|
* ({ context }) => context.verified
|
|
80
|
-
* ])
|
|
66
|
+
* ])
|
|
81
67
|
* ```
|
|
82
68
|
*
|
|
83
|
-
* @see {@link composeGuardsOr} for OR composition
|
|
84
|
-
* @see {@link negateGuard} for NOT logic
|
|
69
|
+
* @see {@link composeGuardsOr} for the OR composition
|
|
70
|
+
* @see {@link negateGuard} for the NOT logic
|
|
71
|
+
* @deprecated Use the `and()`, `or()`, and `not()` combinators of XState directly. This helper
|
|
72
|
+
* does not compose with a guard slot that `setup()` types, and the next major version removes it.
|
|
85
73
|
*/
|
|
86
74
|
export declare const composeGuards: <TContext = any, TEvent = any>(guards: GuardArray<TContext, TEvent>) => ComposedGuard;
|
|
87
75
|
/**
|
|
88
|
-
*
|
|
76
|
+
* Composes the guards with the OR logic, through the or() helper of XState
|
|
77
|
+
*
|
|
78
|
+
* The function joins more than one guard predicate with the OR semantics: one guard
|
|
79
|
+
* must pass at least, and the composition then succeeds. It uses the built-in `or()`
|
|
80
|
+
* helper of XState, and the type inference is therefore correct.
|
|
89
81
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
82
|
+
* @typeParam TContext - The context type of the state machine
|
|
83
|
+
* @typeParam TEvent - The event type
|
|
92
84
|
*
|
|
93
|
-
* @param guards -
|
|
94
|
-
*
|
|
95
|
-
* @returns Predicate returning `true` when any guard passes
|
|
85
|
+
* @param guards - The array of the guard predicates, or of the guard names
|
|
86
|
+
* @returns The or() guard composition of XState
|
|
96
87
|
*
|
|
97
|
-
* @throws {
|
|
88
|
+
* @throws {Error} When the array of the guards is empty
|
|
98
89
|
*
|
|
99
90
|
* @example
|
|
100
|
-
* OR composition with named guards
|
|
91
|
+
* An OR composition with named guards
|
|
101
92
|
* ```typescript
|
|
102
93
|
* import { setup } from "xstate";
|
|
103
94
|
* import { composeGuardsOr } from "@xmachines/play-xstate";
|
|
104
95
|
*
|
|
105
|
-
* const canDelete = composeGuardsOr(['isOwner', 'isAdmin']);
|
|
106
|
-
*
|
|
107
96
|
* const machine = setup({
|
|
108
97
|
* guards: {
|
|
109
98
|
* isOwner: ({ context }) => context.role === 'owner',
|
|
@@ -111,55 +100,59 @@ export declare const composeGuards: <TContext = any, TEvent = any>(guards: Guard
|
|
|
111
100
|
* }
|
|
112
101
|
* }).createMachine({
|
|
113
102
|
* on: {
|
|
114
|
-
* deleteResource:
|
|
115
|
-
*
|
|
116
|
-
*
|
|
103
|
+
* deleteResource: {
|
|
104
|
+
* // One guard is sufficient
|
|
105
|
+
* guard: composeGuardsOr(['isOwner', 'isAdmin']),
|
|
106
|
+
* actions: 'delete'
|
|
117
107
|
* }
|
|
118
108
|
* }
|
|
119
109
|
* });
|
|
120
110
|
* ```
|
|
121
111
|
*
|
|
122
|
-
* @see {@link composeGuards} for AND composition
|
|
123
|
-
* @see {@link negateGuard} for NOT logic
|
|
112
|
+
* @see {@link composeGuards} for the AND composition
|
|
113
|
+
* @see {@link negateGuard} for the NOT logic
|
|
114
|
+
* @deprecated Use the `and()`, `or()`, and `not()` combinators of XState directly. This helper
|
|
115
|
+
* does not compose with a guard slot that `setup()` types, and the next major version removes it.
|
|
124
116
|
*/
|
|
125
117
|
export declare const composeGuardsOr: <TContext = any, TEvent = any>(guards: GuardArray<TContext, TEvent>) => ComposedGuard;
|
|
126
118
|
/**
|
|
127
|
-
*
|
|
119
|
+
* Negates a guard, through the not() helper of XState
|
|
128
120
|
*
|
|
129
|
-
*
|
|
121
|
+
* The function inverts the result of a guard: the guard passes, and NOT then fails;
|
|
122
|
+
* the guard fails, and NOT then passes. It uses the built-in `not()` helper of
|
|
123
|
+
* XState, and the serialization is therefore correct.
|
|
130
124
|
*
|
|
131
|
-
* @typeParam TContext -
|
|
132
|
-
*
|
|
133
|
-
* @typeParam TEvent - Event type
|
|
125
|
+
* @typeParam TContext - The context type of the state machine
|
|
126
|
+
* @typeParam TEvent - The event type
|
|
134
127
|
*
|
|
135
|
-
* @param guard -
|
|
136
|
-
*
|
|
137
|
-
* @returns Predicate returning the inverted guard result
|
|
128
|
+
* @param guard - The guard predicate to negate, or its name
|
|
129
|
+
* @returns The not() guard negation of XState
|
|
138
130
|
*
|
|
139
131
|
* @example
|
|
140
|
-
* NOT composition with named guard
|
|
132
|
+
* A NOT composition with a named guard
|
|
141
133
|
* ```typescript
|
|
142
134
|
* import { setup } from "xstate";
|
|
143
135
|
* import { negateGuard } from "@xmachines/play-xstate";
|
|
144
136
|
*
|
|
145
|
-
* const isAuthenticated = negateGuard('isGuest');
|
|
146
|
-
*
|
|
147
137
|
* const machine = setup({
|
|
148
138
|
* guards: {
|
|
149
139
|
* isGuest: ({ context }) => !context.userId
|
|
150
140
|
* }
|
|
151
141
|
* }).createMachine({
|
|
152
142
|
* on: {
|
|
153
|
-
* accessDashboard:
|
|
154
|
-
*
|
|
155
|
-
*
|
|
143
|
+
* accessDashboard: {
|
|
144
|
+
* // Permit the transition when the user is NOT a guest, which means an authenticated user
|
|
145
|
+
* guard: negateGuard('isGuest'),
|
|
146
|
+
* target: 'dashboard'
|
|
156
147
|
* }
|
|
157
148
|
* }
|
|
158
149
|
* });
|
|
159
150
|
* ```
|
|
160
151
|
*
|
|
161
|
-
* @see {@link composeGuards} for AND composition
|
|
162
|
-
* @see {@link composeGuardsOr} for OR composition
|
|
152
|
+
* @see {@link composeGuards} for the AND composition
|
|
153
|
+
* @see {@link composeGuardsOr} for the OR composition
|
|
154
|
+
* @deprecated Use the `and()`, `or()`, and `not()` combinators of XState directly. This helper
|
|
155
|
+
* does not compose with a guard slot that `setup()` types, and the next major version removes it.
|
|
163
156
|
*/
|
|
164
157
|
export declare const negateGuard: <TContext = any, TEvent = any>(guard: Guard<TContext, TEvent> | string) => ComposedGuard;
|
|
165
158
|
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/guards/compose.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/guards/compose.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAmB/E;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,CACzC,cAAc,EACd,WAAW,EACX,OAAO,EACP,mBAAmB,CACnB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,eAAO,MAAM,aAAa,GAAI,QAAQ,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EACzD,QAAQ,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,KAClC,aAYF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,eAAO,MAAM,eAAe,GAAI,QAAQ,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EAC3D,QAAQ,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,KAClC,aAYF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,eAAO,MAAM,WAAW,GAAI,QAAQ,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EACvD,OAAO,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,KACrC,aAIF,CAAC"}
|
package/dist/guards/compose.js
CHANGED
|
@@ -1,69 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { and, or, not } from "xstate";
|
|
2
|
+
import { EmptyGuardArrayError } from "../errors.js";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* The one unsound boundary of the PRODUCTION code, and it covers the guard types of
|
|
5
|
+
* XState only. (The test code uses `unsafeCast` from
|
|
6
|
+
* `@xmachines/shared/test-support` instead. The two functions are counterparts, and
|
|
7
|
+
* an audit of the casts with grep must cover both names.)
|
|
8
|
+
*
|
|
9
|
+
* These deprecated helpers close two known gaps in the types of XState 5.28. In the
|
|
10
|
+
* first gap, our `Guard<TContext, TEvent>` type does not match `ComposedGuard`
|
|
11
|
+
* structurally, because the event generic is different. In the second gap, `and()`
|
|
12
|
+
* and `or()` need a `readonly [...tuple]`, and `GuardArray` is a mutable array. The
|
|
13
|
+
* values at run time are identical in each case. The parameter has the type
|
|
14
|
+
* `unknown`. Therefore one `as` cast is here, and no call site needs a double cast.
|
|
15
|
+
* Follow the improvements of the XState types: https://github.com/statelyai/xstate/issues
|
|
11
16
|
*/
|
|
12
|
-
const
|
|
13
|
-
if (typeof guard === "function") {
|
|
14
|
-
return (args) => Boolean(guard(args));
|
|
15
|
-
}
|
|
16
|
-
if (typeof guard === "string") {
|
|
17
|
-
return (args) => {
|
|
18
|
-
const named = args?.guards?.[guard];
|
|
19
|
-
if (typeof named !== "function") {
|
|
20
|
-
throw new UnresolvableGuardNameError(guard, origin);
|
|
21
|
-
}
|
|
22
|
-
return Boolean(named(args));
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
// XState v5 failed loudly when EVALUATING a nullish guard entry — silently
|
|
26
|
-
// treating it as never-passing would turn negateGuard(undefined) into an
|
|
27
|
-
// always-allow. Composition stays lazy; evaluation throws.
|
|
28
|
-
return () => {
|
|
29
|
-
throw new InvalidGuardEntryError(typeof guard, origin);
|
|
30
|
-
};
|
|
31
|
-
};
|
|
17
|
+
const asXStateGuard = (value) => value;
|
|
32
18
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* Combines multiple guard predicates using AND semantics—all guards must pass for
|
|
36
|
-
* the composition to succeed. Evaluation is short-circuiting, left to right.
|
|
19
|
+
* Composes the guards with the AND logic, through the and() helper of XState
|
|
37
20
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
21
|
+
* The function joins more than one guard predicate with the AND semantics: every
|
|
22
|
+
* guard must pass, and the composition then succeeds. It uses the built-in `and()`
|
|
23
|
+
* helper of XState. The type inference and the serialization of the machine are
|
|
24
|
+
* therefore correct.
|
|
41
25
|
*
|
|
42
|
-
* **
|
|
43
|
-
*
|
|
26
|
+
* **Architectural context:** the function supports **Actor Authority (INV-01)**,
|
|
27
|
+
* because it composes the guards of a state machine transition declaratively. A
|
|
28
|
+
* guard enforces a rule of the business logic, and that rule decides if a
|
|
29
|
+
* navigation or an action is valid.
|
|
44
30
|
*
|
|
45
|
-
* @typeParam TContext -
|
|
46
|
-
*
|
|
47
|
-
* @typeParam TEvent - Event type
|
|
31
|
+
* @typeParam TContext - The context type of the state machine
|
|
32
|
+
* @typeParam TEvent - The event type
|
|
48
33
|
*
|
|
49
|
-
* @
|
|
50
|
-
*
|
|
51
|
-
* @typeParam TEvent - Event type
|
|
34
|
+
* @param guards - The array of the guard predicates, or of the guard names as strings
|
|
35
|
+
* @returns The and() guard composition of XState
|
|
52
36
|
*
|
|
53
|
-
* @
|
|
54
|
-
* resolved against `args.guards` from `setup({ guards })`)
|
|
55
|
-
* @returns Predicate returning `true` only when every guard passes
|
|
56
|
-
*
|
|
57
|
-
* @throws {EmptyGuardArrayError} If guards array is empty
|
|
37
|
+
* @throws {Error} When the array of the guards is empty
|
|
58
38
|
*
|
|
59
39
|
* @example
|
|
60
|
-
* AND composition with named guards
|
|
40
|
+
* An AND composition with named guards
|
|
61
41
|
* ```typescript
|
|
62
42
|
* import { setup } from "xstate";
|
|
63
43
|
* import { composeGuards } from "@xmachines/play-xstate";
|
|
64
44
|
*
|
|
65
|
-
* const canAccessAdmin = composeGuards(['isLoggedIn', 'hasPermission']);
|
|
66
|
-
*
|
|
67
45
|
* const machine = setup({
|
|
68
46
|
* guards: {
|
|
69
47
|
* isLoggedIn: ({ context }) => !!context.userId,
|
|
@@ -71,65 +49,64 @@ const toEvaluator = (guard, origin) => {
|
|
|
71
49
|
* }
|
|
72
50
|
* }).createMachine({
|
|
73
51
|
* on: {
|
|
74
|
-
* accessAdmin:
|
|
75
|
-
*
|
|
76
|
-
*
|
|
52
|
+
* accessAdmin: {
|
|
53
|
+
* // Both guards must pass
|
|
54
|
+
* guard: composeGuards(['isLoggedIn', 'hasPermission']),
|
|
55
|
+
* target: 'adminPanel'
|
|
77
56
|
* }
|
|
78
57
|
* }
|
|
79
58
|
* });
|
|
80
59
|
* ```
|
|
81
60
|
*
|
|
82
61
|
* @example
|
|
83
|
-
* AND composition with inline predicates
|
|
62
|
+
* An AND composition with inline predicates
|
|
84
63
|
* ```typescript
|
|
85
64
|
* import { composeGuards } from "@xmachines/play-xstate";
|
|
86
65
|
*
|
|
87
|
-
*
|
|
66
|
+
* guard: composeGuards([
|
|
88
67
|
* ({ context }) => context.age >= 18,
|
|
89
68
|
* ({ context }) => context.verified
|
|
90
|
-
* ])
|
|
69
|
+
* ])
|
|
91
70
|
* ```
|
|
92
71
|
*
|
|
93
|
-
* @see {@link composeGuardsOr} for OR composition
|
|
94
|
-
* @see {@link negateGuard} for NOT logic
|
|
72
|
+
* @see {@link composeGuardsOr} for the OR composition
|
|
73
|
+
* @see {@link negateGuard} for the NOT logic
|
|
74
|
+
* @deprecated Use the `and()`, `or()`, and `not()` combinators of XState directly. This helper
|
|
75
|
+
* does not compose with a guard slot that `setup()` types, and the next major version removes it.
|
|
95
76
|
*/
|
|
96
77
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
78
|
export const composeGuards = (guards) => {
|
|
98
79
|
if (guards.length === 0) {
|
|
99
80
|
throw new EmptyGuardArrayError("and");
|
|
100
81
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
82
|
+
if (guards.length === 1) {
|
|
83
|
+
// One guard passes through. asXStateGuard gives the reason for the boundary.
|
|
84
|
+
return asXStateGuard(guards[0]);
|
|
104
85
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (!evaluate(args))
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
return true;
|
|
111
|
-
};
|
|
86
|
+
// Use the built-in and() of XState, for the type inference and the serialization.
|
|
87
|
+
return and(asXStateGuard(guards));
|
|
112
88
|
};
|
|
113
89
|
/**
|
|
114
|
-
*
|
|
90
|
+
* Composes the guards with the OR logic, through the or() helper of XState
|
|
91
|
+
*
|
|
92
|
+
* The function joins more than one guard predicate with the OR semantics: one guard
|
|
93
|
+
* must pass at least, and the composition then succeeds. It uses the built-in `or()`
|
|
94
|
+
* helper of XState, and the type inference is therefore correct.
|
|
115
95
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
96
|
+
* @typeParam TContext - The context type of the state machine
|
|
97
|
+
* @typeParam TEvent - The event type
|
|
118
98
|
*
|
|
119
|
-
* @param guards -
|
|
120
|
-
*
|
|
121
|
-
* @returns Predicate returning `true` when any guard passes
|
|
99
|
+
* @param guards - The array of the guard predicates, or of the guard names
|
|
100
|
+
* @returns The or() guard composition of XState
|
|
122
101
|
*
|
|
123
|
-
* @throws {
|
|
102
|
+
* @throws {Error} When the array of the guards is empty
|
|
124
103
|
*
|
|
125
104
|
* @example
|
|
126
|
-
* OR composition with named guards
|
|
105
|
+
* An OR composition with named guards
|
|
127
106
|
* ```typescript
|
|
128
107
|
* import { setup } from "xstate";
|
|
129
108
|
* import { composeGuardsOr } from "@xmachines/play-xstate";
|
|
130
109
|
*
|
|
131
|
-
* const canDelete = composeGuardsOr(['isOwner', 'isAdmin']);
|
|
132
|
-
*
|
|
133
110
|
* const machine = setup({
|
|
134
111
|
* guards: {
|
|
135
112
|
* isOwner: ({ context }) => context.role === 'owner',
|
|
@@ -137,75 +114,75 @@ export const composeGuards = (guards) => {
|
|
|
137
114
|
* }
|
|
138
115
|
* }).createMachine({
|
|
139
116
|
* on: {
|
|
140
|
-
* deleteResource:
|
|
141
|
-
*
|
|
142
|
-
*
|
|
117
|
+
* deleteResource: {
|
|
118
|
+
* // One guard is sufficient
|
|
119
|
+
* guard: composeGuardsOr(['isOwner', 'isAdmin']),
|
|
120
|
+
* actions: 'delete'
|
|
143
121
|
* }
|
|
144
122
|
* }
|
|
145
123
|
* });
|
|
146
124
|
* ```
|
|
147
125
|
*
|
|
148
|
-
* @see {@link composeGuards} for AND composition
|
|
149
|
-
* @see {@link negateGuard} for NOT logic
|
|
126
|
+
* @see {@link composeGuards} for the AND composition
|
|
127
|
+
* @see {@link negateGuard} for the NOT logic
|
|
128
|
+
* @deprecated Use the `and()`, `or()`, and `not()` combinators of XState directly. This helper
|
|
129
|
+
* does not compose with a guard slot that `setup()` types, and the next major version removes it.
|
|
150
130
|
*/
|
|
151
131
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
152
132
|
export const composeGuardsOr = (guards) => {
|
|
153
133
|
if (guards.length === 0) {
|
|
154
134
|
throw new EmptyGuardArrayError("or");
|
|
155
135
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
136
|
+
if (guards.length === 1) {
|
|
137
|
+
// One guard passes through. asXStateGuard gives the reason for the boundary.
|
|
138
|
+
return asXStateGuard(guards[0]);
|
|
159
139
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (evaluate(args))
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
return false;
|
|
166
|
-
};
|
|
140
|
+
// Use the built-in or() of XState, for the type inference and the serialization.
|
|
141
|
+
return or(asXStateGuard(guards));
|
|
167
142
|
};
|
|
168
143
|
/**
|
|
169
|
-
*
|
|
144
|
+
* Negates a guard, through the not() helper of XState
|
|
170
145
|
*
|
|
171
|
-
*
|
|
146
|
+
* The function inverts the result of a guard: the guard passes, and NOT then fails;
|
|
147
|
+
* the guard fails, and NOT then passes. It uses the built-in `not()` helper of
|
|
148
|
+
* XState, and the serialization is therefore correct.
|
|
172
149
|
*
|
|
173
|
-
* @typeParam TContext -
|
|
174
|
-
*
|
|
175
|
-
* @typeParam TEvent - Event type
|
|
150
|
+
* @typeParam TContext - The context type of the state machine
|
|
151
|
+
* @typeParam TEvent - The event type
|
|
176
152
|
*
|
|
177
|
-
* @param guard -
|
|
178
|
-
*
|
|
179
|
-
* @returns Predicate returning the inverted guard result
|
|
153
|
+
* @param guard - The guard predicate to negate, or its name
|
|
154
|
+
* @returns The not() guard negation of XState
|
|
180
155
|
*
|
|
181
156
|
* @example
|
|
182
|
-
* NOT composition with named guard
|
|
157
|
+
* A NOT composition with a named guard
|
|
183
158
|
* ```typescript
|
|
184
159
|
* import { setup } from "xstate";
|
|
185
160
|
* import { negateGuard } from "@xmachines/play-xstate";
|
|
186
161
|
*
|
|
187
|
-
* const isAuthenticated = negateGuard('isGuest');
|
|
188
|
-
*
|
|
189
162
|
* const machine = setup({
|
|
190
163
|
* guards: {
|
|
191
164
|
* isGuest: ({ context }) => !context.userId
|
|
192
165
|
* }
|
|
193
166
|
* }).createMachine({
|
|
194
167
|
* on: {
|
|
195
|
-
* accessDashboard:
|
|
196
|
-
*
|
|
197
|
-
*
|
|
168
|
+
* accessDashboard: {
|
|
169
|
+
* // Permit the transition when the user is NOT a guest, which means an authenticated user
|
|
170
|
+
* guard: negateGuard('isGuest'),
|
|
171
|
+
* target: 'dashboard'
|
|
198
172
|
* }
|
|
199
173
|
* }
|
|
200
174
|
* });
|
|
201
175
|
* ```
|
|
202
176
|
*
|
|
203
|
-
* @see {@link composeGuards} for AND composition
|
|
204
|
-
* @see {@link composeGuardsOr} for OR composition
|
|
177
|
+
* @see {@link composeGuards} for the AND composition
|
|
178
|
+
* @see {@link composeGuardsOr} for the OR composition
|
|
179
|
+
* @deprecated Use the `and()`, `or()`, and `not()` combinators of XState directly. This helper
|
|
180
|
+
* does not compose with a guard slot that `setup()` types, and the next major version removes it.
|
|
205
181
|
*/
|
|
206
182
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
207
183
|
export const negateGuard = (guard) => {
|
|
208
|
-
|
|
209
|
-
|
|
184
|
+
// XState 5.28.0: not() requires one SingleGuardArg shape, and not the Guard
|
|
185
|
+
// type of this package. See asXStateGuard.
|
|
186
|
+
return not(asXStateGuard(guard));
|
|
210
187
|
};
|
|
211
188
|
//# sourceMappingURL=compose.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/guards/compose.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/guards/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAItC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,aAAa,GAAG,CAAI,KAAc,EAAK,EAAE,CAAC,KAAU,CAAC;AAmB3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG,CAC5B,MAAoC,EACpB,EAAE;IAClB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,6EAA6E;QAC7E,OAAO,aAAa,CAAgB,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,kFAAkF;IAClF,OAAO,GAAG,CAAC,aAAa,CAA4B,MAAM,CAAC,CAAC,CAAC;AAC9D,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,MAAoC,EACpB,EAAE;IAClB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,6EAA6E;QAC7E,OAAO,aAAa,CAAgB,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,iFAAiF;IACjF,OAAO,EAAE,CAAC,aAAa,CAA2B,MAAM,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAC1B,KAAuC,EACvB,EAAE;IAClB,4EAA4E;IAC5E,2CAA2C;IAC3C,OAAO,GAAG,CAAC,aAAa,CAA4B,KAAK,CAAC,CAAC,CAAC;AAC7D,CAAC,CAAC"}
|
package/dist/guards/helpers.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { Guard } from "./types.js";
|
|
2
2
|
import type { PlayEvent } from "@xmachines/play";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Per CONTEXT.md: Convenience helper for common guard pattern
|
|
4
|
+
* Tells you if the context holds a value at the path, and that the value is truthy
|
|
7
5
|
*
|
|
8
6
|
* @example
|
|
9
7
|
* ```typescript
|
|
@@ -15,12 +13,14 @@ import type { PlayEvent } from "@xmachines/play";
|
|
|
15
13
|
* });
|
|
16
14
|
* ```
|
|
17
15
|
*
|
|
18
|
-
* @param path -
|
|
19
|
-
* @returns
|
|
16
|
+
* @param path - The path to the context property, with a dot between two segments
|
|
17
|
+
* @returns The guard predicate. It tests the property for a truthy value
|
|
18
|
+
* @deprecated This function is part of the guard utilities, and the next major version removes
|
|
19
|
+
* them. Write a plain typed predicate instead.
|
|
20
20
|
*/
|
|
21
21
|
export declare const hasContext: <TContext = Record<string, unknown>>(path: string) => Guard<TContext, PlayEvent>;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Tells you if the type of the event is the expected type
|
|
24
24
|
*
|
|
25
25
|
* @example
|
|
26
26
|
* ```typescript
|
|
@@ -35,24 +35,28 @@ export declare const hasContext: <TContext = Record<string, unknown>>(path: stri
|
|
|
35
35
|
* });
|
|
36
36
|
* ```
|
|
37
37
|
*
|
|
38
|
-
* @param eventType -
|
|
39
|
-
* @returns
|
|
38
|
+
* @param eventType - The expected event type
|
|
39
|
+
* @returns The guard predicate. It tests the event type
|
|
40
|
+
* @deprecated This function is part of the guard utilities, and the next major version removes
|
|
41
|
+
* them. Write a plain typed predicate instead.
|
|
40
42
|
*/
|
|
41
43
|
export declare const eventMatches: <TEvent extends PlayEvent = PlayEvent>(eventType: string) => Guard<unknown, TEvent>;
|
|
42
44
|
/**
|
|
43
|
-
*
|
|
45
|
+
* Tells you if a context field holds the expected value.
|
|
44
46
|
*
|
|
45
|
-
* -
|
|
46
|
-
* -
|
|
47
|
-
* -
|
|
48
|
-
* -
|
|
49
|
-
* -
|
|
47
|
+
* - The function accepts an explicit field path, with a dot between two segments, for example `"user.role"`
|
|
48
|
+
* - It compares a primitive with a strict equality, and an object with a deep structural equality
|
|
49
|
+
* - The order of the keys has no effect on the comparison of an object: `{a:1,b:2}` equals `{b:2,a:1}`
|
|
50
|
+
* - It supports a Date, a RegExp, an instance of a class, a nested object, and an array, through `dequal/lite`
|
|
51
|
+
* - It does NOT match a substring: `"a"` does not match `"active"`
|
|
50
52
|
*
|
|
51
|
-
* For XState state
|
|
53
|
+
* For a match of an XState state node, use the built-in `in:` guard syntax instead.
|
|
52
54
|
*
|
|
53
|
-
* @param fieldPath -
|
|
54
|
-
* @param expectedValue -
|
|
55
|
-
* @returns
|
|
55
|
+
* @param fieldPath - The path to the context property, with a dot between two segments, for example "status" or "user.role"
|
|
56
|
+
* @param expectedValue - The value for the comparison: a string, an object, a Date, and so on
|
|
57
|
+
* @returns The guard predicate. It tests the context field for the value
|
|
58
|
+
* @deprecated This function is part of the guard utilities, and the next major version removes
|
|
59
|
+
* them. Write a plain typed predicate instead.
|
|
56
60
|
*/
|
|
57
61
|
export declare const contextFieldMatches: <TContext = Record<string, unknown>>(fieldPath: string, expectedValue: unknown) => Guard<TContext, PlayEvent>;
|
|
58
62
|
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/guards/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,GACrB,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,MAAM,KAAG,KAAK,CAAC,QAAQ,EAAE,SAAS,CAI5E,CAAC;AAEH
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/guards/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,GACrB,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,MAAM,KAAG,KAAK,CAAC,QAAQ,EAAE,SAAS,CAI5E,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,SAAS,SAAS,GAAG,SAAS,EAAE,WAAW,MAAM,KAAG,KAAK,CAAC,OAAO,EAAE,MAAM,CAG/E,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,WAAW,MAAM,EACjB,eAAe,OAAO,KACpB,KAAK,CAAC,QAAQ,EAAE,SAAS,CAI3B,CAAC"}
|