@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.
Files changed (89) hide show
  1. package/README.md +114 -114
  2. package/dist/define-player.d.ts +16 -16
  3. package/dist/define-player.js +16 -16
  4. package/dist/errors.d.ts +84 -101
  5. package/dist/errors.d.ts.map +1 -1
  6. package/dist/errors.js +108 -108
  7. package/dist/errors.js.map +1 -1
  8. package/dist/guards/compose.d.ts +70 -77
  9. package/dist/guards/compose.d.ts.map +1 -1
  10. package/dist/guards/compose.js +90 -113
  11. package/dist/guards/compose.js.map +1 -1
  12. package/dist/guards/helpers.d.ts +22 -18
  13. package/dist/guards/helpers.d.ts.map +1 -1
  14. package/dist/guards/helpers.js +23 -19
  15. package/dist/guards/helpers.js.map +1 -1
  16. package/dist/guards/index.d.ts +10 -3
  17. package/dist/guards/index.d.ts.map +1 -1
  18. package/dist/guards/index.js +10 -3
  19. package/dist/guards/index.js.map +1 -1
  20. package/dist/guards/types.d.ts +9 -9
  21. package/dist/guards/types.d.ts.map +1 -1
  22. package/dist/index.d.ts +8 -8
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -10
  25. package/dist/index.js.map +1 -1
  26. package/dist/player-actor.d.ts +197 -113
  27. package/dist/player-actor.d.ts.map +1 -1
  28. package/dist/player-actor.js +413 -401
  29. package/dist/player-actor.js.map +1 -1
  30. package/dist/routing/build-url.d.ts +19 -21
  31. package/dist/routing/build-url.d.ts.map +1 -1
  32. package/dist/routing/build-url.js +70 -71
  33. package/dist/routing/build-url.js.map +1 -1
  34. package/dist/routing/derive-current-route.d.ts +51 -13
  35. package/dist/routing/derive-current-route.d.ts.map +1 -1
  36. package/dist/routing/derive-current-route.js +69 -60
  37. package/dist/routing/derive-current-route.js.map +1 -1
  38. package/dist/routing/derive-initial-route.d.ts +23 -23
  39. package/dist/routing/derive-initial-route.js +27 -27
  40. package/dist/routing/derive-initial-route.js.map +1 -1
  41. package/dist/routing/derive-route.d.ts +38 -37
  42. package/dist/routing/derive-route.d.ts.map +1 -1
  43. package/dist/routing/derive-route.js +45 -42
  44. package/dist/routing/derive-route.js.map +1 -1
  45. package/dist/routing/format-play-route-transitions.d.ts +34 -71
  46. package/dist/routing/format-play-route-transitions.d.ts.map +1 -1
  47. package/dist/routing/format-play-route-transitions.js +74 -130
  48. package/dist/routing/format-play-route-transitions.js.map +1 -1
  49. package/dist/routing/index.d.ts +4 -8
  50. package/dist/routing/index.d.ts.map +1 -1
  51. package/dist/routing/index.js +4 -6
  52. package/dist/routing/index.js.map +1 -1
  53. package/dist/routing/types.d.ts +12 -11
  54. package/dist/routing/types.d.ts.map +1 -1
  55. package/dist/types.d.ts +97 -20
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/view/derive-current-view.d.ts +51 -0
  58. package/dist/view/derive-current-view.d.ts.map +1 -0
  59. package/dist/view/derive-current-view.js +119 -0
  60. package/dist/view/derive-current-view.js.map +1 -0
  61. package/package.json +22 -21
  62. package/dist/define-player.typecheck.d.ts +0 -2
  63. package/dist/define-player.typecheck.d.ts.map +0 -1
  64. package/dist/define-player.typecheck.js +0 -48
  65. package/dist/define-player.typecheck.js.map +0 -1
  66. package/dist/guards/compose.typecheck.d.ts +0 -2
  67. package/dist/guards/compose.typecheck.d.ts.map +0 -1
  68. package/dist/guards/compose.typecheck.js +0 -22
  69. package/dist/guards/compose.typecheck.js.map +0 -1
  70. package/dist/player-actor.typecheck.d.ts +0 -2
  71. package/dist/player-actor.typecheck.d.ts.map +0 -1
  72. package/dist/player-actor.typecheck.js +0 -30
  73. package/dist/player-actor.typecheck.js.map +0 -1
  74. package/dist/routing/create-routed-machine.d.ts +0 -71
  75. package/dist/routing/create-routed-machine.d.ts.map +0 -1
  76. package/dist/routing/create-routed-machine.js +0 -71
  77. package/dist/routing/create-routed-machine.js.map +0 -1
  78. package/dist/routing/play-route-event.typecheck.d.ts +0 -2
  79. package/dist/routing/play-route-event.typecheck.d.ts.map +0 -1
  80. package/dist/routing/play-route-event.typecheck.js +0 -43
  81. package/dist/routing/play-route-event.typecheck.js.map +0 -1
  82. package/dist/routing/schemas.d.ts +0 -99
  83. package/dist/routing/schemas.d.ts.map +0 -1
  84. package/dist/routing/schemas.js +0 -30
  85. package/dist/routing/schemas.js.map +0 -1
  86. package/dist/schemas.d.ts +0 -28
  87. package/dist/schemas.d.ts.map +0 -1
  88. package/dist/schemas.js +0 -29
  89. package/dist/schemas.js.map +0 -1
@@ -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
- * Composed guard: a plain predicate over transition arguments.
5
+ * The narrowest public return type of the guard composition helpers.
4
6
  *
5
- * XState v6 removed guard objects and the `and()`/`or()`/`not()` creators —
6
- * guards are now plain functions evaluated inside transition functions. A
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 = (args: unknown) => boolean;
14
+ export type ComposedGuard = GuardPredicate<MachineContext, EventObject, unknown, ParameterizedObject>;
22
15
  /**
23
- * Compose guards with AND logic.
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
- * **XState v6:** the composed value is a plain predicate. Call it inside a
33
- * transition function and return early to block the transition:
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
- * @typeParam TContext - State machine context type (defaults to `any` so the
36
- * documented inline-predicate examples compile without annotations)
37
- * @typeParam TEvent - Event type
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 - State machine context type (defaults to `any` so the
40
- * documented inline-predicate examples compile without annotations)
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 - Array of guard predicates or guard names (string references
44
- * resolved against `args.guards` from `setup({ guards })`)
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 {EmptyGuardArrayError} If guards array is empty
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: (args) => {
65
- * if (!canAccessAdmin(args)) return;
66
- * return { target: 'adminPanel' };
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
- * const isEligible = composeGuards([
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
- * Compose guards with OR logic.
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
- * Combines multiple guard predicates using OR semantics—at least one guard must pass
91
- * for the composition to succeed. Evaluation is short-circuiting, left to right.
82
+ * @typeParam TContext - The context type of the state machine
83
+ * @typeParam TEvent - The event type
92
84
  *
93
- * @param guards - Array of guard predicates or guard names (string references
94
- * resolved against `args.guards` from `setup({ guards })`)
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 {EmptyGuardArrayError} If guards array is empty
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: (args, enq) => {
115
- * if (!canDelete(args)) return;
116
- * enq(() => deleteIt());
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
- * Negate a guard.
119
+ * Negates a guard, through the not() helper of XState
128
120
  *
129
- * Inverts a guard's result—if the guard passes, NOT fails; if guard fails, NOT passes.
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 - State machine context type (defaults to `any` so
132
- * inline predicates compile without annotations)
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 - Guard predicate or guard name (resolved against `args.guards`
136
- * from `setup({ guards })`) to negate
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: (args) => {
154
- * if (!isAuthenticated(args)) return;
155
- * return { target: 'dashboard' };
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":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAQpD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;AAqCvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,eAAO,MAAM,aAAa,GAAI,QAAQ,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EACzD,QAAQ,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,KAClC,aAgBF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,eAAO,MAAM,eAAe,GAAI,QAAQ,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EAC3D,QAAQ,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,KAClC,aAgBF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,eAAO,MAAM,WAAW,GAAI,QAAQ,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EACvD,OAAO,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,KACrC,aAGF,CAAC"}
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"}
@@ -1,69 +1,47 @@
1
- import { EmptyGuardArrayError, InvalidGuardEntryError, UnresolvableGuardNameError, } from "../errors.js";
1
+ import { and, or, not } from "xstate";
2
+ import { EmptyGuardArrayError } from "../errors.js";
2
3
  /**
3
- * Resolve a guard entry (predicate or setup guard name) to an evaluator thunk.
4
- *
5
- * The predicate-vs-name discrimination happens once at compose time; only the
6
- * `args.guards` lookup for string names stays at call time, preserving the
7
- * documented late-binding semantics. `origin` names the combinator that built
8
- * the guard, so thrown errors report the correct `scope`.
9
- *
10
- * @internal
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 toEvaluator = (guard, origin) => {
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
- * Compose guards with AND logic.
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
- * **Architectural Context:** Supports **Actor Authority (INV-01)** by enabling
39
- * declarative guard composition in state machine transitions. Guards enforce business
40
- * logic rules that determine whether navigation or actions are valid.
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
- * **XState v6:** the composed value is a plain predicate. Call it inside a
43
- * transition function and return early to block the transition:
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 - State machine context type (defaults to `any` so the
46
- * documented inline-predicate examples compile without annotations)
47
- * @typeParam TEvent - Event type
31
+ * @typeParam TContext - The context type of the state machine
32
+ * @typeParam TEvent - The event type
48
33
  *
49
- * @typeParam TContext - State machine context type (defaults to `any` so the
50
- * documented inline-predicate examples compile without annotations)
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
- * @param guards - Array of guard predicates or guard names (string references
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: (args) => {
75
- * if (!canAccessAdmin(args)) return;
76
- * return { target: 'adminPanel' };
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
- * const isEligible = composeGuards([
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
- const evaluators = guards.map((guard) => toEvaluator(guard, "composeGuards"));
102
- if (evaluators.length === 1) {
103
- return evaluators[0];
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
- return (args) => {
106
- for (const evaluate of evaluators) {
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
- * Compose guards with OR logic.
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
- * Combines multiple guard predicates using OR semantics—at least one guard must pass
117
- * for the composition to succeed. Evaluation is short-circuiting, left to right.
96
+ * @typeParam TContext - The context type of the state machine
97
+ * @typeParam TEvent - The event type
118
98
  *
119
- * @param guards - Array of guard predicates or guard names (string references
120
- * resolved against `args.guards` from `setup({ guards })`)
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 {EmptyGuardArrayError} If guards array is empty
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: (args, enq) => {
141
- * if (!canDelete(args)) return;
142
- * enq(() => deleteIt());
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
- const evaluators = guards.map((guard) => toEvaluator(guard, "composeGuardsOr"));
157
- if (evaluators.length === 1) {
158
- return evaluators[0];
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
- return (args) => {
161
- for (const evaluate of evaluators) {
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
- * Negate a guard.
144
+ * Negates a guard, through the not() helper of XState
170
145
  *
171
- * Inverts a guard's result—if the guard passes, NOT fails; if guard fails, NOT passes.
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 - State machine context type (defaults to `any` so
174
- * inline predicates compile without annotations)
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 - Guard predicate or guard name (resolved against `args.guards`
178
- * from `setup({ guards })`) to negate
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: (args) => {
196
- * if (!isAuthenticated(args)) return;
197
- * return { target: 'dashboard' };
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
- const evaluate = toEvaluator(guard, "negateGuard");
209
- return (args) => !evaluate(args);
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":"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"}
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"}
@@ -1,9 +1,7 @@
1
1
  import type { Guard } from "./types.js";
2
2
  import type { PlayEvent } from "@xmachines/play";
3
3
  /**
4
- * Check if context has a truthy value at path
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 - Dot-separated path to context property
19
- * @returns Guard predicate checking if property is truthy
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
- * Check if event type matches expected type
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 - Expected event type
39
- * @returns Guard predicate checking event type
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
- * Check if a context field matches an expected value.
45
+ * Tells you if a context field holds the expected value.
44
46
  *
45
- * - Accepts an explicit dot-separated field path (e.g. `"user.role"`)
46
- * - Uses strict equality for primitives, deep structural equality for objects
47
- * - Object comparison is key-order-insensitive: `{a:1,b:2}` equals `{b:2,a:1}`
48
- * - Supports Date, RegExp, class instances, nested objects and arrays via `dequal/lite`
49
- * - Does NOT use substring matching — `"a"` will not match `"active"`
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-node matching, use the built-in `in:` guard syntax instead.
53
+ * For a match of an XState state node, use the built-in `in:` guard syntax instead.
52
54
  *
53
- * @param fieldPath - Dot-separated path to context property (e.g., "status", "user.role")
54
- * @param expectedValue - Value to compare against (string, object, Date, etc.)
55
- * @returns Guard predicate checking if context field matches
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;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,SAAS,SAAS,GAAG,SAAS,EAAE,WAAW,MAAM,KAAG,KAAK,CAAC,OAAO,EAAE,MAAM,CAG/E,CAAC;AAEH;;;;;;;;;;;;;;GAcG;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"}
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"}