@xmachines/play-xstate 6.1.0 → 7.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.
Files changed (42) hide show
  1. package/README.md +64 -12
  2. package/dist/capabilities.js +6 -2
  3. package/dist/capabilities.js.map +1 -1
  4. package/dist/define-player.js +8 -8
  5. package/dist/define-player.js.map +1 -1
  6. package/dist/errors.js +262 -259
  7. package/dist/errors.js.map +1 -1
  8. package/dist/index.js +4 -25
  9. package/dist/player-actor.js +318 -448
  10. package/dist/player-actor.js.map +1 -1
  11. package/dist/routing/build-url.js +74 -108
  12. package/dist/routing/build-url.js.map +1 -1
  13. package/dist/routing/derive-current-route.d.ts.map +1 -1
  14. package/dist/routing/derive-current-route.js +46 -67
  15. package/dist/routing/derive-current-route.js.map +1 -1
  16. package/dist/routing/derive-initial-route.js +53 -64
  17. package/dist/routing/derive-initial-route.js.map +1 -1
  18. package/dist/routing/derive-route.js +225 -242
  19. package/dist/routing/derive-route.js.map +1 -1
  20. package/dist/routing/format-play-route-transitions.js +192 -258
  21. package/dist/routing/format-play-route-transitions.js.map +1 -1
  22. package/dist/state-meta.d.ts +56 -25
  23. package/dist/state-meta.d.ts.map +1 -1
  24. package/dist/state-meta.js +164 -105
  25. package/dist/state-meta.js.map +1 -1
  26. package/dist/view/derive-current-view.d.ts +18 -10
  27. package/dist/view/derive-current-view.d.ts.map +1 -1
  28. package/dist/view/derive-current-view.js +243 -221
  29. package/dist/view/derive-current-view.js.map +1 -1
  30. package/dist/with-routing.js +94 -136
  31. package/dist/with-routing.js.map +1 -1
  32. package/dist/with-view.d.ts.map +1 -1
  33. package/dist/with-view.js +82 -152
  34. package/dist/with-view.js.map +1 -1
  35. package/package.json +19 -19
  36. package/dist/index.js.map +0 -1
  37. package/dist/routing/index.js +0 -14
  38. package/dist/routing/index.js.map +0 -1
  39. package/dist/routing/types.js +0 -2
  40. package/dist/routing/types.js.map +0 -1
  41. package/dist/types.js +0 -2
  42. package/dist/types.js.map +0 -1
package/README.md CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  > XState v5 adapter for the XMachines Play Architecture. It binds a state machine to the actor base, with atom-driven reactivity and a router integration.
4
4
 
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-6.1.0-blue)](https://www.npmjs.com/package/@xmachines/play-xstate)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-7.0.0-blue)](https://www.npmjs.com/package/@xmachines/play-xstate)
6
6
 
7
7
  ---
8
8
 
9
- > **Browser floor: Chrome 110, Firefox 115, Safari 16.4.** This package calls the ES2023
10
- > change-by-copy array methods, so a browser below that floor throws
11
- > `TypeError: ... is not a function`. The root README carries the table.
9
+ > **Browser floor:** the table of the root README covers this package. Its own code calls
10
+ > no ES2023 change-by-copy array method, and its routing capability imports only types from
11
+ > [`@xmachines/play-router`](../play-router/README.md). A router bridge of play-router sets
12
+ > the floor of the table.
12
13
 
13
14
  ## Installation
14
15
 
@@ -115,21 +116,36 @@ machine cannot be `undefined`, the first argument of the factory is necessary. A
115
116
  input is then a compile error, not an actor that stops in an error status.
116
117
 
117
118
  ```typescript
118
- type PlayerFactory<TMachine> =
119
+ import type { AnyStateMachine, InputFrom } from "xstate";
120
+ import type { PlayerActor, PlayerFactoryResumeOptions } from "@xmachines/play-xstate";
121
+
122
+ type PlayerFactory<TMachine extends AnyStateMachine, TCapabilities extends object = object> =
119
123
  undefined extends InputFrom<TMachine>
120
124
  ? (
121
125
  input?: InputFrom<TMachine>,
122
126
  options?: PlayerFactoryResumeOptions<TMachine>,
123
- ) => PlayerActor<TMachine>
127
+ ) => PlayerActor<TMachine> & TCapabilities
124
128
  : (
125
129
  input: InputFrom<TMachine>,
126
130
  options?: PlayerFactoryResumeOptions<TMachine>,
127
- ) => PlayerActor<TMachine>;
131
+ ) => PlayerActor<TMachine> & TCapabilities;
128
132
  ```
129
133
 
134
+ `TCapabilities` is what `compose` adds, for example `Routable` of [`@xmachines/play-router`](../play-router/README.md).
135
+
130
136
  #### Restoring from a snapshot
131
137
 
132
138
  ```typescript
139
+ import { definePlayer, compose, PlayerActor } from "@xmachines/play-xstate";
140
+ import { withRouting } from "@xmachines/play-xstate/routing";
141
+ import type { AnyStateMachine } from "xstate";
142
+
143
+ declare const machine: AnyStateMachine; // your machine, with an input and routed states
144
+
145
+ const createPlayer = definePlayer({ machine, actor: compose(PlayerActor, withRouting) });
146
+ const actor = createPlayer({ userId: "alice" });
147
+ actor.start();
148
+
133
149
  const snapshot = actor.getPersistedSnapshot();
134
150
  actor.stop();
135
151
 
@@ -182,6 +198,9 @@ carries the route utilities — `deriveRoute`, `isAbsoluteRoute`, `buildRouteUrl
182
198
  import { definePlayer, PlayerActor, compose } from "@xmachines/play-xstate";
183
199
  import { withRouting } from "@xmachines/play-xstate/routing";
184
200
  import { withView } from "@xmachines/play-xstate/view";
201
+ import type { AnyStateMachine } from "xstate";
202
+
203
+ declare const machine: AnyStateMachine; // your machine
185
204
 
186
205
  // Both capabilities
187
206
  const createPlayer = definePlayer({
@@ -201,6 +220,15 @@ const createBare = definePlayer({ machine });
201
220
  **A lifecycle hook receives the bare actor.** `PlayerOptions` is typed before the class is composed, so it cannot name the capabilities, and a narrower hook parameter is refused under `strictFunctionTypes`. A hook that reads `currentRoute` or `currentView` reads them through a binding of the composed type:
202
221
 
203
222
  ```typescript
223
+ import { definePlayer, PlayerActor, compose } from "@xmachines/play-xstate";
224
+ import { withRouting } from "@xmachines/play-xstate/routing";
225
+ import { withView } from "@xmachines/play-xstate/view";
226
+ import type { Routable } from "@xmachines/play-router";
227
+ import type { Viewable } from "@xmachines/play-view";
228
+ import type { AnyStateMachine } from "xstate";
229
+
230
+ declare const machine: AnyStateMachine; // your machine
231
+
204
232
  let composed: PlayerActor<typeof machine> & Routable & Viewable;
205
233
 
206
234
  const actor = definePlayer({
@@ -254,6 +282,11 @@ the path yourself, when you run work that belongs to a change of the route alone
254
282
 
255
283
  ```typescript
256
284
  import { watchAtom } from "@xmachines/play-atom";
285
+ import type { PlayerActor } from "@xmachines/play-xstate";
286
+ import type { Routable } from "@xmachines/play-router";
287
+ import type { AnyStateMachine } from "xstate";
288
+
289
+ declare const actor: PlayerActor<AnyStateMachine> & Routable; // composed with withRouting
257
290
 
258
291
  const stop = watchAtom(actor.currentRoute, (route) => {
259
292
  console.log("Route changed:", route);
@@ -275,22 +308,32 @@ This package gives no guard utility. Compose a guard with the `and()`, `or()` an
275
308
  import { and, not, setup } from "xstate";
276
309
 
277
310
  const machine = setup({
311
+ types: {
312
+ context: {} as { userId: string; role: string },
313
+ events: {} as { type: "accessAdmin" } | { type: "accessLogin" },
314
+ },
278
315
  guards: {
279
316
  isLoggedIn: ({ context }) => context.userId !== "",
280
317
  hasAdminRole: ({ context }) => context.role === "admin",
281
318
  },
282
319
  }).createMachine({
320
+ context: { userId: "", role: "guest" },
321
+ initial: "home",
283
322
  on: {
284
323
  accessAdmin: {
285
324
  guard: and(["isLoggedIn", "hasAdminRole"]),
286
- target: "adminPanel",
325
+ target: ".adminPanel",
287
326
  },
288
327
  accessLogin: {
289
328
  guard: not("isLoggedIn"),
290
- target: "login",
329
+ target: ".login",
291
330
  },
292
331
  },
293
- // ...
332
+ states: {
333
+ home: {},
334
+ adminPanel: {},
335
+ login: {},
336
+ },
294
337
  });
295
338
  ```
296
339
 
@@ -314,6 +357,7 @@ import { formatPlayRouteTransitions } from "@xmachines/play-xstate/routing";
314
357
 
315
358
  const config = formatPlayRouteTransitions({
316
359
  id: "app",
360
+ initial: "home",
317
361
  states: {
318
362
  home: {
319
363
  id: "home",
@@ -348,6 +392,10 @@ with `asDisposable` of [`@xmachines/core/utils`](../core/README.md):
348
392
 
349
393
  ```typescript
350
394
  import { asDisposable } from "@xmachines/core/utils";
395
+ import { definePlayer } from "@xmachines/play-xstate";
396
+ import type { AnyStateMachine } from "xstate";
397
+
398
+ declare const machine: AnyStateMachine; // your machine
351
399
 
352
400
  {
353
401
  const actor = definePlayer({ machine })();
@@ -368,6 +416,10 @@ includes [`@statelyai/inspect`](https://stately.ai/docs/inspector):
368
416
 
369
417
  ```typescript
370
418
  import { createBrowserInspector } from "@statelyai/inspect";
419
+ import { definePlayer } from "@xmachines/play-xstate";
420
+ import type { AnyStateMachine } from "xstate";
421
+
422
+ declare const machine: AnyStateMachine; // your machine
371
423
 
372
424
  const { inspect } = createBrowserInspector();
373
425
  const createPlayer = definePlayer({ machine, options: { inspect } });
@@ -429,7 +481,7 @@ every consumer, including one that composes no routing.
429
481
  The string form declares the path only. The object form declares the path and the
430
482
  behaviour of the generated `play.route` transition:
431
483
 
432
- ```typescript
484
+ ```typescript fragment
433
485
  meta: {
434
486
  route: {
435
487
  path: "/doc/:docId",
@@ -467,7 +519,7 @@ An unknown `handler` value throws `InvalidRouteHandlerError` at the format time.
467
519
  The `data` field follows the `WithDynamicParams` shape of XState, so the function form
468
520
  reads the context and the event:
469
521
 
470
- ```typescript
522
+ ```typescript fragment
471
523
  data: ({ context, event }) => ({ title: `Document ${event.params?.docId}` });
472
524
  ```
473
525
 
@@ -1,4 +1,8 @@
1
- export function compose(Base, ...capabilities) {
2
- return capabilities.reduce((actor, capability) => capability(actor), Base);
1
+ //#region packages/play-xstate/src/capabilities.ts
2
+ function compose(Base, ...capabilities) {
3
+ return capabilities.reduce((actor, capability) => capability(actor), Base);
3
4
  }
5
+ //#endregion
6
+ export { compose };
7
+
4
8
  //# sourceMappingURL=capabilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AA6HA,MAAM,UAAU,OAAO,CACtB,IAAiC,EACjC,GAAG,YAEF;IAED,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;AAC5E,CAAC"}
1
+ {"version":3,"file":"capabilities.js","names":[],"sources":["../src/capabilities.ts"],"sourcesContent":["/**\n * The composition of the optional capabilities of an actor.\n *\n * `PlayerActor` gives the protocol of `PlayActor` and nothing else: `state` and\n * `send`. Routing and the view are capabilities, each one a MIXIN in a module that the\n * core imports never. An application that renders no view therefore loads no view code,\n * and an application that declares no route loads no routing code.\n *\n * {@link compose} applies each capability from left to right, so the LAST one is the\n * outermost class. The ORDER decides no value: a capability that publishes one derives it\n * with a computed atom over `state`, and the engine evaluates every derivation from the\n * one write of a transition, in topological order. A capability that must run an EFFECT\n * overrides `onSnapshot`, and the override calls `super.onSnapshot()` first.\n *\n * ## Why the types read this way\n *\n * A capability is typed by the INSTANCE that it extends, and not by a type parameter for\n * the class. That one decision removes three problems at once:\n *\n * - A class that extends a TYPE PARAMETER must constrain it to `new (...args: any[])`, and\n * TypeScript reports TS2545 for every other spelling. A concrete constructor type needs\n * no `any`.\n * - A mixin that INFERS its return type emits no declaration: `Actor` of XState carries\n * private members such as `_actorScope`, and an anonymous class that holds one is not\n * nameable (TS4094).\n * - A conditional return type cannot be checked against the class that a mixin builds, so\n * it forces `as unknown as` at the return. Nothing here asserts anything.\n *\n * `TCapabilities` carries what the base already added, so a composed constructor names\n * every capability on its instance. {@link compose} is what accumulates them:\n * `compose(PlayerActor, withRouting, withView)` answers an actor that is `Routable` AND\n * `Viewable`. Each capability declares a FIXED return type, so the nested form\n * `withView(withRouting(PlayerActor))` answers `Viewable` alone.\n */\nimport type { ActorOptions, AnyStateMachine, InputFrom } from \"xstate\";\n\nimport type { PlayerActor } from \"./player-actor.js\";\nimport type { PlayerOptions } from \"./types.js\";\n\n/**\n * The constructor of a `PlayerActor`, with every capability that it carries already.\n *\n * The argument list is the one that `PlayerActor` declares, so `definePlayer` builds an\n * actor with the arguments checked, whatever the composition added to the instance.\n *\n * @typeParam TMachine - The machine of the actor.\n * @typeParam TCapabilities - What the capabilities added, for example `Routable & Viewable`.\n */\nexport type PlayerConstructor<\n\tTMachine extends AnyStateMachine,\n\tTCapabilities extends object = object,\n> = new (\n\tmachine: TMachine,\n\toptions: PlayerOptions<TMachine>,\n\tinput?: InputFrom<TMachine>,\n\trestoredSnapshot?: ActorOptions<TMachine>[\"snapshot\"],\n) => PlayerActor<TMachine> & TCapabilities;\n\n/**\n * One capability of an actor: a function that takes an actor class and answers the same\n * class with `TAdded` on the instance.\n *\n * `withRouting` of `@xmachines/play-xstate/routing` and `withView` of\n * `@xmachines/play-xstate/view` are the two that this package ships. Write your own with\n * the same shape, and {@link compose} takes it beside them.\n *\n * @typeParam TMachine - The machine of the actor.\n * @typeParam TAdded - What the capability adds to the instance, for example `Routable`.\n */\nexport type Capability<TMachine extends AnyStateMachine, TAdded extends object> = (\n\tBase: PlayerConstructor<TMachine>,\n) => PlayerConstructor<TMachine, TAdded>;\n\n/**\n * Builds an actor class from a base and a list of capabilities.\n *\n * It applies each capability from left to right, so the LAST one is the outermost class,\n * and the return type accumulates what each one adds:\n * `compose(PlayerActor, withRouting, withView)` is `Routable` AND `Viewable`.\n *\n * Use this form, and not the nested one. Each capability declares a FIXED return type, so\n * `withView(withRouting(PlayerActor))` builds the same class at run time and answers\n * `Viewable` alone at the type level — a router bridge then refuses the actor with\n * TS2739. The overloads below are what carries `Routable` through.\n *\n * @example\n * ```typescript\n * import { definePlayer, PlayerActor, compose } from \"@xmachines/play-xstate\";\n * import { withRouting } from \"@xmachines/play-xstate/routing\";\n * import { withView } from \"@xmachines/play-xstate/view\";\n *\n * const createPlayer = definePlayer({\n * machine,\n * actor: compose(PlayerActor, withRouting, withView),\n * });\n * ```\n *\n * Each overload takes the capabilities as separate arguments, `c1` to `c3`, and it\n * applies them from left to right.\n *\n * @param Base - The actor class to build on, normally `PlayerActor`.\n */\nexport function compose<TMachine extends AnyStateMachine>(\n\tBase: PlayerConstructor<TMachine>,\n): PlayerConstructor<TMachine>;\nexport function compose<TMachine extends AnyStateMachine, TC1 extends object>(\n\tBase: PlayerConstructor<TMachine>,\n\tc1: Capability<TMachine, TC1>,\n): PlayerConstructor<TMachine, TC1>;\nexport function compose<TMachine extends AnyStateMachine, TC1 extends object, TC2 extends object>(\n\tBase: PlayerConstructor<TMachine>,\n\tc1: Capability<TMachine, TC1>,\n\tc2: Capability<TMachine, TC2>,\n): PlayerConstructor<TMachine, TC1 & TC2>;\nexport function compose<\n\tTMachine extends AnyStateMachine,\n\tTC1 extends object,\n\tTC2 extends object,\n\tTC3 extends object,\n>(\n\tBase: PlayerConstructor<TMachine>,\n\tc1: Capability<TMachine, TC1>,\n\tc2: Capability<TMachine, TC2>,\n\tc3: Capability<TMachine, TC3>,\n): PlayerConstructor<TMachine, TC1 & TC2 & TC3>;\nexport function compose<TMachine extends AnyStateMachine>(\n\tBase: PlayerConstructor<TMachine>,\n\t...capabilities: ReadonlyArray<\n\t\t(Base: PlayerConstructor<TMachine>) => PlayerConstructor<TMachine>\n\t>\n): PlayerConstructor<TMachine> {\n\treturn capabilities.reduce((actor, capability) => capability(actor), Base);\n}\n"],"mappings":";AA6HA,SAAgB,QACf,MACA,GAAG,cAG2B;CAC9B,OAAO,aAAa,QAAQ,OAAO,eAAe,WAAW,KAAK,GAAG,IAAI;AAC1E"}
@@ -1,11 +1,11 @@
1
1
  import { PlayerActor } from "./player-actor.js";
2
- export function definePlayer(config) {
3
- const { machine, options, actor } = config;
4
- // `PlayerActor` is the default, and it carries no capability. A consumer that routes or
5
- // renders passes a composed class, and this module imports neither capability. The two
6
- // overloads above are what keeps the answer exact: the default one answers a bare
7
- // player, and the other answers the player with everything that the composition added.
8
- const ActorClass = actor ?? PlayerActor;
9
- return (input, restore) => new ActorClass(machine, options ?? {}, input, restore?.snapshot);
2
+ //#region packages/play-xstate/src/define-player.ts
3
+ function definePlayer(config) {
4
+ const { machine, options, actor } = config;
5
+ const ActorClass = actor ?? PlayerActor;
6
+ return (input, restore) => new ActorClass(machine, options ?? {}, input, restore?.snapshot);
10
7
  }
8
+ //#endregion
9
+ export { definePlayer };
10
+
11
11
  //# sourceMappingURL=define-player.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-player.js","sourceRoot":"","sources":["../src/define-player.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkEhD,MAAM,UAAU,YAAY,CAC3B,MAA6C;IAE7C,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAC3C,wFAAwF;IACxF,uFAAuF;IACvF,kFAAkF;IAClF,uFAAuF;IACvF,MAAM,UAAU,GAAG,KAAK,IAAI,WAAW,CAAC;IAExC,OAAO,CAAC,KAA2B,EAAE,OAA8C,EAAE,EAAE,CACtF,IAAI,UAAU,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AACnE,CAAC"}
1
+ {"version":3,"file":"define-player.js","names":[],"sources":["../src/define-player.ts"],"sourcesContent":["import type { AnyStateMachine, InputFrom } from \"xstate\";\nimport type { PlayerConfig, PlayerFactory, PlayerFactoryResumeOptions } from \"./types.js\";\nimport type { PlayerConstructor } from \"./capabilities.js\";\nimport { PlayerActor } from \"./player-actor.js\";\n\n/**\n * Creates a player factory from an XState machine\n *\n * This factory function accepts an XState v5 machine. It returns a function that\n * makes {@link PlayerActor} instances. One configuration can therefore make more\n * than one actor instance, and this helps with a test, with an application of\n * several instances, and with a render on the server.\n *\n * @typeParam TMachine - The type of the XState v5 state machine\n *\n * @param config - The configuration object of the player\n * @param config.machine - The XState v5 state machine\n * @param config.options - The optional lifecycle hooks: onStart, onTransition, and the others\n *\n * @returns The factory function. It makes an actor instance, with an optional input context\n *\n * @example\n * A basic player factory\n * ```typescript\n * import { setup } from \"xstate\";\n * import { definePlayer } from \"@xmachines/play-xstate\";\n *\n * const machine = setup({}).createMachine({\n * initial: 'idle',\n * states: {\n * idle: { meta: { route: '/' } },\n * active: { meta: { route: '/active' } }\n * }\n * });\n *\n * const createPlayer = definePlayer({ machine });\n * const actor = createPlayer();\n * actor.start();\n * ```\n *\n * @example\n * More than one actor instance from one factory\n * ```typescript\n * const createPlayer = definePlayer({ machine });\n *\n * // Create an actor for each user\n * const alice = createPlayer({ userId: 'alice' });\n * const bob = createPlayer({ userId: 'bob' });\n *\n * alice.start();\n * bob.start();\n *\n * // The two state machines are independent\n * console.log(alice.state.get() !== bob.state.get());\n * ```\n *\n * @see [Play RFC](../../docs/rfc/play.md)\n * @see {@link PlayerActor} for the concrete actor implementation\n * @see {@link PlayerConfig} for the configuration options\n * @see {@link PlayerFactory} for the signature of the factory function\n */\nexport function definePlayer<TMachine extends AnyStateMachine>(\n\tconfig: PlayerConfig<TMachine> & { actor?: undefined },\n): PlayerFactory<TMachine>;\nexport function definePlayer<TMachine extends AnyStateMachine, TCapabilities extends object>(\n\tconfig: PlayerConfig<TMachine, TCapabilities> & {\n\t\tactor: PlayerConstructor<TMachine, TCapabilities>;\n\t},\n): PlayerFactory<TMachine, TCapabilities>;\nexport function definePlayer<TMachine extends AnyStateMachine, TCapabilities extends object>(\n\tconfig: PlayerConfig<TMachine, TCapabilities>,\n): PlayerFactory<TMachine, TCapabilities> | PlayerFactory<TMachine> {\n\tconst { machine, options, actor } = config;\n\t// `PlayerActor` is the default, and it carries no capability. A consumer that routes or\n\t// renders passes a composed class, and this module imports neither capability. The two\n\t// overloads above are what keeps the answer exact: the default one answers a bare\n\t// player, and the other answers the player with everything that the composition added.\n\tconst ActorClass = actor ?? PlayerActor;\n\n\treturn (input?: InputFrom<TMachine>, restore?: PlayerFactoryResumeOptions<TMachine>) =>\n\t\tnew ActorClass(machine, options ?? {}, input, restore?.snapshot);\n}\n"],"mappings":";;AAqEA,SAAgB,aACf,QACmE;CACnE,MAAM,EAAE,SAAS,SAAS,UAAU;CAKpC,MAAM,aAAa,SAAS;CAE5B,QAAQ,OAA6B,YACpC,IAAI,WAAW,SAAS,WAAW,CAAC,GAAG,OAAO,SAAS,QAAQ;AACjE"}