@picked/client 0.0.98-next.1 → 0.0.98-next.2

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 (39) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{api-CONFKApU.js → api-C3VRwEmm.js} +1 -1
  3. package/dist/{api-BgPyneD7.cjs → api-PeY2M79y.cjs} +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  6. package/dist/cjs/server/index.js +1 -1
  7. package/dist/docs/how-to/register-an-action-handler.md +19 -1
  8. package/dist/docs/how-to/wait-for-an-action-response.md +182 -0
  9. package/dist/docs/reference/dashboard/actions.mdx +5 -2
  10. package/dist/docs/reference/delivery-api/workspace-schema.md +3 -0
  11. package/dist/docs/reference/sdk/actions.md +127 -3
  12. package/dist/docs/reference/sdk/type-generation.md +8 -0
  13. package/dist/esm/index.js +514 -328
  14. package/dist/esm/plugins/dynamic-experiences/index.js +4 -4
  15. package/dist/esm/server/index.js +3 -3
  16. package/dist/{experience-instance-DptBuxBJ.js → experience-instance-4KUBH8Gb.js} +278 -220
  17. package/dist/experience-instance-C9WV52iQ.cjs +1 -0
  18. package/dist/{plugin-BZg0v-pk.js → plugin-DCUBvwwg.js} +96 -69
  19. package/dist/plugin-Dbj4RFUo.cjs +1 -0
  20. package/dist/{preview-unavailable-zegsv152.js → preview-unavailable-CRdiC9KU.js} +1 -1
  21. package/dist/{preview-unavailable-BC8sZ_nk.cjs → preview-unavailable-ChKFij_K.cjs} +1 -1
  22. package/dist/{provider-DHsKj_FK.js → provider-B3cWi4ev.js} +1 -1
  23. package/dist/{provider-reK0A8tr.cjs → provider-DtJJrz-_.cjs} +1 -1
  24. package/dist/types/entities/action/action.d.ts +94 -23
  25. package/dist/types/entities/action/action.d.ts.map +1 -1
  26. package/dist/types/entities/experience-instance/experience-instance.d.ts.map +1 -1
  27. package/dist/types/index.d.ts +1 -1
  28. package/dist/types/index.d.ts.map +1 -1
  29. package/dist/types/lifecycle/rules-logic.d.ts.map +1 -1
  30. package/dist/types/picked.d.ts +26 -6
  31. package/dist/types/picked.d.ts.map +1 -1
  32. package/dist/types/plugins/dynamic-experiences/index.d.ts +1 -0
  33. package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
  34. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  35. package/dist/types/schema-registry.d.ts +10 -0
  36. package/dist/types/schema-registry.d.ts.map +1 -1
  37. package/package.json +4 -4
  38. package/dist/experience-instance-CQvQHCNe.cjs +0 -1
  39. package/dist/plugin-CuYWwfVi.cjs +0 -1
@@ -0,0 +1,182 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to wait for the response of an action
7
+
8
+ This guide shows you how to run an action from your application and show its response. You
9
+ need an action in your project first, with a key and a workflow that the action starts. See
10
+ [Actions](/reference/dashboard/actions).
11
+
12
+ The response is the value that the action returns. For an action that starts a workflow in
13
+ Picked, the response is the payload of the **Action response** node in the workflow.
14
+
15
+ ## 1. Generate the types of your project
16
+
17
+ Run type generation. The generated types name the actions that start a workflow. For those
18
+ keys, the types of `useAction` and `getAction` include `run`. At runtime, the value includes
19
+ `run` for each key. See [Type generation](/reference/sdk/type-generation).
20
+
21
+ ## 2. Bind the action to a component
22
+
23
+ Call `useAction` in React, or `getAction` in Svelte. Neither runs the action. The action runs
24
+ only when you call `run`. The action receives the arguments of `run` in `args`.
25
+
26
+ All the components that use the same key and the same experience share one run state. When
27
+ one of them calls `run`, all of them show that run.
28
+
29
+ :::code-group
30
+
31
+ ```tsx [React]
32
+ import { useAction } from '@picked/react/hooks'
33
+
34
+ function CallbackButton() {
35
+ const callback = useAction('request-callback')
36
+
37
+ return (
38
+ <button onClick={() => void callback.run()} disabled={callback.status === 'running'}>
39
+ Get a call back
40
+ </button>
41
+ )
42
+ }
43
+ ```
44
+
45
+ ```svelte [Svelte]
46
+ <script lang="ts">
47
+ import { getAction } from '@picked/svelte/hooks'
48
+
49
+ const callback = getAction('request-callback')
50
+ </script>
51
+
52
+ <button onclick={() => $callback.run()} disabled={$callback.status === 'running'}>
53
+ Get a call back
54
+ </button>
55
+ ```
56
+
57
+ :::
58
+
59
+ ## 3. Show the response
60
+
61
+ Read `status`, `response` and `error` for the last run. Compare `response` with the values
62
+ that your workflow sends.
63
+
64
+ :::code-group
65
+
66
+ ```tsx [React]
67
+ import { useAction } from '@picked/react/hooks'
68
+
69
+ // Reads the status that the workflow sends in its response.
70
+ function answerStatus(response: unknown): unknown {
71
+ return typeof response === 'object' && response !== null && 'status' in response
72
+ ? response.status
73
+ : undefined
74
+ }
75
+
76
+ function CallbackButton() {
77
+ const callback = useAction('request-callback')
78
+
79
+ if (callback.status === 'failed') return <p>We could not send your request.</p>
80
+ if (callback.status === 'succeeded') {
81
+ if (answerStatus(callback.response) === 'closed') {
82
+ return <p>We call back on weekdays, 8 am to 5 pm.</p>
83
+ }
84
+ return <p>Thank you. We will call you soon.</p>
85
+ }
86
+
87
+ return (
88
+ <button onClick={() => void callback.run()} disabled={callback.status === 'running'}>
89
+ Get a call back
90
+ </button>
91
+ )
92
+ }
93
+ ```
94
+
95
+ ```svelte [Svelte]
96
+ <script lang="ts">
97
+ import { getAction } from '@picked/svelte/hooks'
98
+
99
+ const callback = getAction('request-callback')
100
+
101
+ // Reads the status that the workflow sends in its response.
102
+ function answerStatus(response: unknown): unknown {
103
+ return typeof response === 'object' && response !== null && 'status' in response
104
+ ? response.status
105
+ : undefined
106
+ }
107
+ </script>
108
+
109
+ {#if $callback.status === 'failed'}
110
+ <p>We could not send your request.</p>
111
+ {:else if $callback.status === 'succeeded' && answerStatus($callback.response) === 'closed'}
112
+ <p>We call back on weekdays, 8 am to 5 pm.</p>
113
+ {:else if $callback.status === 'succeeded'}
114
+ <p>Thank you. We will call you soon.</p>
115
+ {:else}
116
+ <button onclick={() => $callback.run()} disabled={$callback.status === 'running'}>
117
+ Get a call back
118
+ </button>
119
+ {/if}
120
+ ```
121
+
122
+ :::
123
+
124
+ `run` does not reject. It resolves to `{ status: 'succeeded', response }` or to
125
+ `{ status: 'failed', error }`.
126
+
127
+ ## 4. Run the action outside a component
128
+
129
+ Call `client.actions.run` with the action key. It resolves to the response, and it rejects
130
+ when the action fails.
131
+
132
+ The client registers the actions of your project when it loads the configuration of your
133
+ project. For this, the client must have `dynamicExperiencesPlugin`, and you must start the
134
+ client. Until the client is ready, `run` rejects with `Action '<key>' is not registered`. A
135
+ handler that your application registers in `createClient({ actions })` is available
136
+ immediately.
137
+
138
+ ```ts
139
+ import { createClient } from 'picked'
140
+ import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
141
+
142
+ const picked = createClient({
143
+ projectId: 'your-project-id',
144
+ plugins: [dynamicExperiencesPlugin()],
145
+ })
146
+
147
+ picked.start()
148
+
149
+ // Waits until the client is ready.
150
+ async function whenReady(): Promise<void> {
151
+ if (picked.readyStore.getState()) return
152
+
153
+ await new Promise<void>((resolve) => {
154
+ const subscription = picked.readyStore.updates.watch((ready) => {
155
+ if (!ready) return
156
+ subscription.unsubscribe()
157
+ resolve()
158
+ })
159
+ })
160
+ }
161
+
162
+ async function requestCallback(): Promise<void> {
163
+ await whenReady()
164
+
165
+ try {
166
+ const response = await picked.actions.run('request-callback', { timeoutMs: 15_000 })
167
+ console.info('Callback response', response)
168
+ } catch (error) {
169
+ console.warn('Callback request failed', error)
170
+ }
171
+ }
172
+ ```
173
+
174
+ Set `timeoutMs` to stop the wait sooner. Set `signal` to stop the wait from your own code.
175
+
176
+ ## Related
177
+
178
+ - [SDK actions](/reference/sdk/actions) — every outcome, error and limit.
179
+ - [How to register an action handler](/how-to/register-an-action-handler) — return a
180
+ response from your own handler.
181
+ - [How to call an external API from a workflow](/how-to/call-an-external-api-from-a-workflow)
182
+ — a workflow that sends a response.
@@ -24,8 +24,11 @@ not see it.
24
24
 
25
25
  ## Register in your application
26
26
 
27
- Each action key must match a key in the `actions` object that you pass to
28
- `createClient()`. See
27
+ A published action that starts a workflow needs no code in your application. The SDK
28
+ sends the request to Picked, and the workflow runs there.
29
+
30
+ To run your own code, pass a handler under the action key in `createClient()`. That handler
31
+ replaces the workflow for this key. See
29
32
  [How to register an action handler](/how-to/register-an-action-handler) for the code, and
30
33
  [SDK actions](/reference/sdk/actions) for what the handler receives and returns.
31
34
 
@@ -28,13 +28,16 @@ The response is a [JSON Schema draft 2020-12](https://json-schema.org/draft/2020
28
28
  | `x-picked-source` | `"published"` or `"preview"` — mirrors the surface you requested. |
29
29
  | `x-picked-version-id` | Published workspace version id, or `null` on preview. |
30
30
  | `x-picked-step-content-models` | Map of step key → content model key, which gives each layout its type. |
31
+ | `x-picked-step-fields` | Map of step key → the keys of the fields on that step, in order. |
31
32
  | `x-picked-feature-values` | Map of feature key → the values that the feature can serve. Picked adds this field only when the workspace has a Text feature flag. |
33
+ | `x-picked-workflow-action-keys` | Keys of the actions that start a workflow, sorted. Picked adds this field only when the workspace has such an action. |
32
34
  | `$defs.contentModels` | JSON Schema definitions for each content model. |
33
35
  | `$defs.contentBlocks` | JSON Schema definitions for reusable content blocks. |
34
36
  | `$defs.fields` | Object schema for project field values. |
35
37
  | `$defs.featureKeys` | String enum of feature keys. |
36
38
  | `$defs.experienceKeys` | String enum of experience keys. |
37
39
  | `$defs.stepKeys` | String enum of step keys. |
40
+ | `$defs.actionKeys` | String enum of action keys. A schema from a version that Picked published before this field existed has none. |
38
41
 
39
42
  ## Field values
40
43
 
@@ -5,8 +5,23 @@ layout: docs
5
5
 
6
6
  # Actions
7
7
 
8
- Register an action handler on the Picked client. The SDK then invokes the handler
9
- when an experience flow reaches an action node.
8
+ An action is a function that the SDK runs by its key. An action comes from one of two
9
+ places:
10
+
11
+ - **Your application** registers a handler in `createClient({ actions })`.
12
+ - **Your project** publishes the action. Its handler sends the visitor's answers to Picked,
13
+ and the workflow of the action runs there.
14
+
15
+ A handler from your application replaces a published action that has the same key. The
16
+ workflow then does not start.
17
+
18
+ ## Response
19
+
20
+ The value that a handler returns is the response of the action. A handler can return any
21
+ value, or a promise of any value.
22
+
23
+ The response of a published action is the payload of the **Action response** node in its
24
+ workflow. A workflow that has no **Action response** node gives `undefined`.
10
25
 
11
26
  ## Outcome and branch
12
27
 
@@ -19,10 +34,119 @@ An action node can also carry one plain connection, with no success or failure b
19
34
  flow then continues through it on either outcome.
20
35
 
21
36
  An action does not interrupt your visitor. The current step stays on the screen while an
22
- async action runs.
37
+ async action runs. While the action runs, the flow ignores a request to go to the next step
38
+ or to a specified step. The outcome of the action then chooses the branch. If your visitor
39
+ goes back before the action completes, the flow stays on that step. The signal that the
40
+ handler receives then stops.
41
+
42
+ ## Published actions
43
+
44
+ A published action waits for the response of its workflow. It stops after 30 seconds.
45
+
46
+ | Cause | The action rejects with |
47
+ | --- | --- |
48
+ | Picked refuses the request | An `Error` that names the HTTP status |
49
+ | Picked does not accept the request in 25 seconds | A `TimeoutError` |
50
+ | Picked accepts the request, but gives no run to wait for | An `Error` that says that no run is available |
51
+ | The workflow fails before it sends a response | `ActionRunFailedError` |
52
+ | Picked refuses a request for the response with a `4xx` status | An `Error` that names the HTTP status |
53
+ | No response in 30 seconds | `ActionResponseTimeoutError` |
54
+ | Your code stops the wait with a signal | The reason of the signal |
55
+
56
+ A request for the response can fail because of a network error, a timeout, a `5xx` status or
57
+ a body that the SDK cannot read. The SDK then sends the request again. It waits 1 second after
58
+ the first failure, and two times longer after each failure that follows. If the requests
59
+ continue to fail, the 30-second limit stops the wait with `ActionResponseTimeoutError`.
60
+
61
+ To identify `ActionRunFailedError` and `ActionResponseTimeoutError` with `instanceof`, import
62
+ them from `@picked/client/plugins/dynamic-experiences`. The `name` of each error is the same
63
+ as the name that you import.
64
+
65
+ ## Run an action from code
66
+
67
+ | API | Returns | Rejects |
68
+ | --- | --- | --- |
69
+ | `client.actions.run(key, options)` | A promise of the response | Yes, with the error of the action |
70
+ | `useAction(key, options)` (React) | For an action that starts a workflow: `{ run, status, response, error }`. For other actions: the action as a function. | `run`: No. The function: it returns or throws what the action returns or throws. |
71
+ | `getAction(key, options)` (Svelte) | A store of the same value | The same as `useAction` |
72
+ | `useActionResult(key, options)` (React) | The last result of your own action | No |
73
+ | `getActionResult(key, options)` (Svelte) | A store of the last result of your own action | No |
74
+ | `client.actions.getActionBinding(key, options)` | A store of the value that `useAction` and `getAction` give | The same as `useAction` |
75
+ | `client.actions.getActionResult(key, options)` | A store of the last result of your own action, which `useActionResult` and `getActionResult` read | No |
76
+
77
+ Type generation names the actions that start a workflow (`WorkflowActionKeys`). Without generated
78
+ types, `useAction` and `getAction` give the action as a function. For each key, the value also
79
+ includes `run`, `status`, `response` and `error`.
80
+
81
+ The handler receives the arguments of a call to the function in `args`. It also receives the
82
+ arguments of `run(...args)` in `args`.
83
+
84
+ `status` is `idle`, `running`, `succeeded` or `failed`, for the last run. When a run starts,
85
+ `response` and `error` are `undefined`. `run(...args)` resolves to
86
+ `{ status: 'succeeded', response }` or to `{ status: 'failed', error }`.
87
+
88
+ The SDK keeps one run state for each key and experience. All the components that use the
89
+ same key and the same experience share that run state, and they show the same last run. When
90
+ one of them calls `run`, all of them show that run.
91
+
92
+ `useActionResult` and `getActionResult` run your own action again when a response, a field or
93
+ the handler of the action changes. They also run it when a component that reads the result
94
+ starts. In React, the action runs when the component mounts, and when an `<Activity>` shows the
95
+ component again. In Svelte, it runs each time a component calls `getActionResult`. A change that
96
+ occurs outside Picked, for example a login, then shows in a component that starts after the
97
+ change.
98
+
99
+ The components that read the same key and the same experience share one result. The action
100
+ runs one time for each change. It also runs one time for the components that React renders
101
+ together. React can stop while it renders and continue later, for example in a transition. Then
102
+ the components that React renders after it continues run the action again.
103
+
104
+ The result is always from the run that started last. If that run throws, the result is
105
+ `undefined`. For a published action they give `undefined` and do not send a request. The types
106
+ refuse the key of an action that starts a workflow.
107
+
108
+ A result for an experience follows the changes only while `client.experienceInstances`
109
+ contains that experience. When `client.experienceInstances` adds the experience, the action
110
+ runs if a response, a field or the handler changed after the last run.
111
+
112
+ The options of `client.actions.run`:
113
+
114
+ | Option | Type | Description |
115
+ | --- | --- | --- |
116
+ | `args` | `unknown[]` | The values that the handler receives in `args`. |
117
+ | `experience` | `ExperienceInstance` | The experience that the handler receives as `context.experience`. |
118
+ | `signal` | `AbortSignal` | Stops the wait. The handler receives a signal that stops too. |
119
+ | `timeoutMs` | `number` | Stops the wait after this many milliseconds, with a `TimeoutError`. |
120
+
121
+ `useAction` and `getAction` take `experience` only.
122
+
123
+ ## Other methods of `client.actions`
124
+
125
+ | Method | Description |
126
+ | --- | --- |
127
+ | `refreshActionResult(key, options)` | Runs your own action again for the result that `getActionResult` gives. It does nothing until a component or your code reads that result. The calls in one task start one run. |
128
+ | `isPublished(key)` | Returns `true` when the handler of the key is a published action. |
129
+ | `unregister(key)` | Removes the handler of the key. If the key has no handler, nothing changes. |
130
+
131
+ `refreshActionResult` and `getActionResult` take `experience` only.
132
+
133
+ ## Rule logic
134
+
135
+ A rule reads the response of an action with `{ "invoke": [{ "var": "actions.<key>" }] }`. A
136
+ response that is a string, a number, a Boolean, `null`, or an array or object of these
137
+ reaches the rule. A different response reaches the rule as `undefined`.
138
+
139
+ A rule runs the action each time the SDK evaluates the rule.
140
+
141
+ A condition node can have a rule that runs an action. While the condition node waits for that
142
+ rule, the flow ignores a request to go to the next step or to a specified step. The result of
143
+ the rule then chooses the branch. If your visitor goes back before the rule has a result, the
144
+ flow ignores the result.
23
145
 
24
146
  ## Related
25
147
 
148
+ - [How to wait for the response of an action](/how-to/wait-for-an-action-response) — show
149
+ the response in a component.
26
150
  - [How to register an action handler](/how-to/register-an-action-handler) — the handler,
27
151
  and when to throw.
28
152
  - [About how Picked works](/explanation/how-picked-works#how-an-action-decides-which-way-to-go)
@@ -244,6 +244,14 @@ Each key type has a `Known` form that holds the workspace keys alone —
244
244
  `KnownFeatureKeys`, `KnownActionKeys`, `KnownFieldKeys`, `KnownStepKeys`, and
245
245
  `KnownExperienceKeys`. Use one where you want the workspace set and nothing else.
246
246
 
247
+ ## Actions that start a workflow
248
+
249
+ `WorkflowActionKeys` holds the keys of the actions that start a workflow in the workspace. For one
250
+ of these keys, the types of `useAction` and `getAction` give `run`, `status`, `response` and
251
+ `error`. For every other key, the types give the action as a function. Without generated types,
252
+ and with a schema that has no such actions, `WorkflowActionKeys` is `never`. See
253
+ [Actions](/reference/sdk/actions).
254
+
247
255
  ## Cache
248
256
 
249
257
  Type generation writes its artefacts under `.picked/`: the schema JSON and a manifest. The first run adds this directory to `.gitignore` for you.