@picked/client 0.0.70-next.5 → 0.0.70
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/dist/{api-DQ1DqG_F.js → api-CPy9G49C.js} +1 -1
- package/dist/api-Ce6xYI--.cjs +1 -0
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
- package/dist/cjs/server/index.js +1 -1
- package/dist/docs/actions.mdx +38 -18
- package/dist/docs/api/authentication.md +75 -48
- package/dist/docs/api/authorization-and-scopes.md +63 -47
- package/dist/docs/api/error-handling.md +70 -54
- package/dist/docs/api/index.md +41 -22
- package/dist/docs/api/pagination.md +30 -20
- package/dist/docs/api-keys.mdx +11 -6
- package/dist/docs/chrome-extension.md +22 -5
- package/dist/docs/conditions.mdx +32 -19
- package/dist/docs/content/asset-library.mdx +65 -34
- package/dist/docs/content/content-entries.mdx +8 -2
- package/dist/docs/content/layouts.mdx +45 -29
- package/dist/docs/content/pages.mdx +29 -14
- package/dist/docs/content/sections.mdx +19 -8
- package/dist/docs/delivery-api/experience-content.md +10 -5
- package/dist/docs/delivery-api/index.md +19 -12
- package/dist/docs/delivery-api/preview-caching-crawling.md +27 -14
- package/dist/docs/delivery-api/project-config.md +6 -4
- package/dist/docs/experiences.mdx +26 -10
- package/dist/docs/features.mdx +75 -20
- package/dist/docs/fields.mdx +42 -27
- package/dist/docs/flow-builder/building-flows.md +38 -20
- package/dist/docs/flow-builder/condition-builder.md +32 -21
- package/dist/docs/flow-builder/keyboard-shortcuts.md +18 -18
- package/dist/docs/flow-builder/nodes.md +26 -13
- package/dist/docs/flow-builder/overview.mdx +28 -15
- package/dist/docs/gtm-template.md +35 -26
- package/dist/docs/guides/build-a-personalized-page.mdx +36 -18
- package/dist/docs/image-optimisation-service.md +33 -17
- package/dist/docs/index.md +28 -17
- package/dist/docs/mcp.md +46 -38
- package/dist/docs/pickedql/data-sources.md +65 -43
- package/dist/docs/pickedql/dimensions.md +27 -12
- package/dist/docs/pickedql/examples.md +38 -26
- package/dist/docs/pickedql/filtering.md +10 -6
- package/dist/docs/pickedql/index.md +32 -14
- package/dist/docs/pickedql/schema.md +85 -76
- package/dist/docs/pickedql/virtual-functions.md +62 -36
- package/dist/docs/projects.mdx +14 -7
- package/dist/docs/reports.mdx +5 -3
- package/dist/docs/sdk/actions.md +33 -17
- package/dist/docs/sdk/conditions.md +8 -5
- package/dist/docs/sdk/content-security-policy.md +5 -4
- package/dist/docs/sdk/experiences.md +29 -11
- package/dist/docs/sdk/feature-allocation.md +79 -47
- package/dist/docs/sdk/features.md +130 -76
- package/dist/docs/sdk/fields.md +66 -42
- package/dist/docs/sdk/index.md +22 -13
- package/dist/docs/sdk/plugins/create-experience.md +18 -15
- package/dist/docs/sdk/plugins/dynamic-experiences.md +42 -16
- package/dist/docs/sdk/predicates.md +53 -37
- package/dist/docs/sdk/ssr/index.md +25 -8
- package/dist/docs/sdk/ssr/rendering-and-hydration.md +37 -16
- package/dist/docs/sdk/ssr/serialize.md +38 -20
- package/dist/docs/sdk/ssr/slots-and-placements.md +28 -10
- package/dist/docs/sdk/steps.md +100 -55
- package/dist/docs/steps.mdx +20 -11
- package/dist/docs/team-management.md +17 -11
- package/dist/docs/vite-plugin.md +26 -19
- package/dist/docs/workspace-versioning.mdx +14 -8
- package/dist/docs/workspaces.mdx +55 -24
- package/dist/esm/index.js +257 -238
- package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
- package/dist/esm/server/index.js +1 -1
- package/dist/{plugin-EPx6sE_l.js → plugin-ChPkTj__.js} +58 -53
- package/dist/plugin-DHn11gn6.cjs +1 -0
- package/dist/types/entities/feature/feature.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/hash.d.ts +40 -5
- package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/api-B9koex39.cjs +0 -1
- package/dist/plugin-YbSSGrM7.cjs +0 -1
package/dist/docs/sdk/fields.md
CHANGED
|
@@ -4,16 +4,19 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Fields
|
|
6
6
|
|
|
7
|
-
Define fields in code with `createField()
|
|
7
|
+
Define your fields in code with `createField()`. Check the input with JSON Logic.
|
|
8
|
+
Read the values that a user sends through the step context.
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
+
## Define a field
|
|
10
11
|
|
|
11
|
-
Define
|
|
12
|
+
Define a field with `createField()` from the
|
|
13
|
+
`@picked/client/plugins/create-experience` package. Each field needs a unique
|
|
14
|
+
identifier, a name, a type, a label, and a count of the values that it takes:
|
|
12
15
|
|
|
13
16
|
```ts
|
|
14
17
|
import { createField } from '@picked/client/plugins/create-experience'
|
|
15
18
|
|
|
16
|
-
//
|
|
19
|
+
// A text input that the user must fill in.
|
|
17
20
|
export const emailField = createField({
|
|
18
21
|
id: 'email-address',
|
|
19
22
|
name: 'email-address',
|
|
@@ -24,7 +27,7 @@ export const emailField = createField({
|
|
|
24
27
|
placeholder: 'you@example.com',
|
|
25
28
|
})
|
|
26
29
|
|
|
27
|
-
//
|
|
30
|
+
// A dropdown that takes more than one choice, with fixed options.
|
|
28
31
|
export const communicationMethods = createField({
|
|
29
32
|
id: 'communication-methods',
|
|
30
33
|
name: 'communication-methods',
|
|
@@ -54,21 +57,28 @@ export const communicationMethods = createField({
|
|
|
54
57
|
|
|
55
58
|
## How fields work
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
1. **Registration**:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
After you register a field and an experience uses it:
|
|
61
|
+
|
|
62
|
+
1. **Registration**: you register a field on the experience while you create the
|
|
63
|
+
step, with `.addField()`.
|
|
64
|
+
2. **Condition evaluation**: when the field has a `condition`, the SDK evaluates it
|
|
65
|
+
against the current context. The result decides whether the field is visible.
|
|
66
|
+
3. **Rendering**: the SDK renders each visible field in the experience UI, with its
|
|
67
|
+
label, its placeholder, and its options.
|
|
68
|
+
4. **User input**: the user enters or chooses a value. Validation then runs in the
|
|
69
|
+
browser, when you set a rule.
|
|
70
|
+
5. **Storage**: the SDK stores a valid response in the experience state, under
|
|
71
|
+
`responses[field.name]`.
|
|
72
|
+
6. **Reference**: a later condition can point at the value with
|
|
73
|
+
`{ var: 'responses.field-name' }`. This works in a field condition, in a step
|
|
74
|
+
condition, and in the logic of an experience.
|
|
65
75
|
|
|
66
76
|
## Field types
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
A field has one of four basic types: `text`, `number`, `boolean`, or `object`.
|
|
69
79
|
|
|
70
80
|
```ts
|
|
71
|
-
//
|
|
81
|
+
// One text input.
|
|
72
82
|
const nameField = createField({
|
|
73
83
|
type: 'text',
|
|
74
84
|
multiple: false,
|
|
@@ -76,7 +86,7 @@ const nameField = createField({
|
|
|
76
86
|
})
|
|
77
87
|
```
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
Give an `options` array to let the user choose more than one value:
|
|
80
90
|
|
|
81
91
|
```ts
|
|
82
92
|
const nameField = createField({
|
|
@@ -104,7 +114,7 @@ const nameField = createField({
|
|
|
104
114
|
|
|
105
115
|
## Conditions
|
|
106
116
|
|
|
107
|
-
|
|
117
|
+
A field and a field option both work with condition logic:
|
|
108
118
|
|
|
109
119
|
```ts
|
|
110
120
|
const conditionalField = createField({
|
|
@@ -131,7 +141,8 @@ const conditionalField = createField({
|
|
|
131
141
|
|
|
132
142
|
## Field validation
|
|
133
143
|
|
|
134
|
-
|
|
144
|
+
Check the input of a user with a JSON Logic rule. The `$input` variable holds the
|
|
145
|
+
current value of the field:
|
|
135
146
|
|
|
136
147
|
```ts
|
|
137
148
|
const quantityField = createField({
|
|
@@ -147,7 +158,7 @@ const quantityField = createField({
|
|
|
147
158
|
{
|
|
148
159
|
and: [
|
|
149
160
|
{
|
|
150
|
-
//
|
|
161
|
+
// The input value is above 0.
|
|
151
162
|
'>': [
|
|
152
163
|
{
|
|
153
164
|
var: '$input',
|
|
@@ -156,7 +167,7 @@ const quantityField = createField({
|
|
|
156
167
|
],
|
|
157
168
|
},
|
|
158
169
|
{
|
|
159
|
-
//
|
|
170
|
+
// The input value is 999 or below.
|
|
160
171
|
'<=': [
|
|
161
172
|
{
|
|
162
173
|
var: '$input',
|
|
@@ -176,12 +187,13 @@ const quantityField = createField({
|
|
|
176
187
|
```
|
|
177
188
|
|
|
178
189
|
:::tip
|
|
179
|
-
The `catch
|
|
190
|
+
The `catch` and `throw` pattern in JSON Logic checks that the condition gives `true`.
|
|
191
|
+
When it does not, the SDK shows the `throw` message to the user.
|
|
180
192
|
:::
|
|
181
193
|
|
|
182
194
|
## Field policies
|
|
183
195
|
|
|
184
|
-
|
|
196
|
+
Use a privacy policy to control how Picked tracks and stores the data of a field:
|
|
185
197
|
|
|
186
198
|
```ts
|
|
187
199
|
const sensitiveField = createField({
|
|
@@ -192,22 +204,25 @@ const sensitiveField = createField({
|
|
|
192
204
|
multiple: false,
|
|
193
205
|
required: true,
|
|
194
206
|
policies: {
|
|
195
|
-
doNotTrackValue: true, //
|
|
196
|
-
doNotStoreValue: true, //
|
|
207
|
+
doNotTrackValue: true, // Do not send the value to analytics.
|
|
208
|
+
doNotStoreValue: true, // Do not keep the value in browser storage.
|
|
197
209
|
},
|
|
198
210
|
})
|
|
199
211
|
```
|
|
200
212
|
|
|
201
213
|
**Privacy policies**:
|
|
202
214
|
|
|
203
|
-
- `doNotTrackValue`:
|
|
204
|
-
|
|
215
|
+
- `doNotTrackValue`: keeps the value of the field out of your analytics events. The
|
|
216
|
+
events carry the field ID only.
|
|
217
|
+
- `doNotStoreValue`: stops the SDK, so that it does not put the value in browser
|
|
218
|
+
storage.
|
|
205
219
|
|
|
206
|
-
Use these policies for sensitive data
|
|
220
|
+
Use these policies for sensitive data such as a tax number, a credit card number, or
|
|
221
|
+
a password. That data must stay in the browser.
|
|
207
222
|
|
|
208
223
|
## Field metadata
|
|
209
224
|
|
|
210
|
-
|
|
225
|
+
Add more text to help the user:
|
|
211
226
|
|
|
212
227
|
```ts
|
|
213
228
|
const passwordField = createField({
|
|
@@ -225,18 +240,19 @@ const passwordField = createField({
|
|
|
225
240
|
|
|
226
241
|
**Metadata fields**:
|
|
227
242
|
|
|
228
|
-
- `placeholder`:
|
|
229
|
-
- `description`:
|
|
230
|
-
- `helpText`:
|
|
243
|
+
- `placeholder`: the hint text that an empty input shows.
|
|
244
|
+
- `description`: a short explanation below the label.
|
|
245
|
+
- `helpText`: more guidance, or the requirements, next to the field.
|
|
231
246
|
|
|
232
|
-
##
|
|
247
|
+
## Read a field value
|
|
233
248
|
|
|
234
|
-
|
|
249
|
+
The SDK stores each field response in the experience state. Read a response through
|
|
250
|
+
the `responses` object:
|
|
235
251
|
|
|
236
252
|
:::code-group
|
|
237
253
|
|
|
238
254
|
```ts [In Conditions]
|
|
239
|
-
//
|
|
255
|
+
// Point at the value from a later field condition.
|
|
240
256
|
const sensitiveField = createField({
|
|
241
257
|
condition: {
|
|
242
258
|
'==': [
|
|
@@ -253,11 +269,19 @@ const sensitiveField = createField({
|
|
|
253
269
|
|
|
254
270
|
## Best practices
|
|
255
271
|
|
|
256
|
-
- **
|
|
257
|
-
|
|
258
|
-
- **
|
|
259
|
-
|
|
260
|
-
- **
|
|
261
|
-
|
|
262
|
-
- **
|
|
263
|
-
|
|
272
|
+
- **Give each field a name that says what it holds**: use `email-address` and not
|
|
273
|
+
`field1`. It is then easier to debug the flow and to read your analytics.
|
|
274
|
+
- **Match the type to the data**: use `number` for a numeric value, and not `text`.
|
|
275
|
+
The SDK can then check and compare the value correctly.
|
|
276
|
+
- **Mark a field as required only when it is**: fewer required fields means fewer
|
|
277
|
+
users who leave the form.
|
|
278
|
+
- **Keep each validation message clear**: an error message must tell the user exactly
|
|
279
|
+
how to correct the input.
|
|
280
|
+
- **Show a field only when it is relevant**: use a condition on the field, and decide
|
|
281
|
+
it from the earlier responses.
|
|
282
|
+
- **Apply a privacy policy to sensitive data**: use `doNotTrackValue` and
|
|
283
|
+
`doNotStoreValue` for personal information, a password, or financial data.
|
|
284
|
+
- **Give helpful metadata**: add a `placeholder` and a `description` to help the
|
|
285
|
+
user, above all for an input with a format.
|
|
286
|
+
- **Keep an option list easy to read**: for more than 5 options, add a search or
|
|
287
|
+
break the field into more than one step.
|
package/dist/docs/sdk/index.md
CHANGED
|
@@ -4,13 +4,15 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Picked SDK
|
|
6
6
|
|
|
7
|
-
The Picked SDK
|
|
7
|
+
The Picked SDK lets you add a dynamic, personalized experience to your web
|
|
8
|
+
application with very little code.
|
|
8
9
|
|
|
9
10
|
:::tip
|
|
10
|
-
The SDK is framework-agnostic
|
|
11
|
+
The SDK is framework-agnostic. It works with React, Svelte, Vue, and plain
|
|
12
|
+
JavaScript.
|
|
11
13
|
:::
|
|
12
14
|
|
|
13
|
-
## Quick
|
|
15
|
+
## Quick start
|
|
14
16
|
|
|
15
17
|
:::code-group
|
|
16
18
|
|
|
@@ -41,18 +43,20 @@ const snapshot = await serialize(client, {
|
|
|
41
43
|
|
|
42
44
|
:::
|
|
43
45
|
|
|
44
|
-
The `@picked/client/server` entry holds the
|
|
46
|
+
The `@picked/client/server` entry holds the helpers that run only on a server, such
|
|
47
|
+
as `serialize()`. Those helpers make an SSR snapshot. It is a separate entry point,
|
|
48
|
+
so a browser bundle never holds it. See [Server-Side Rendering](/sdk/ssr).
|
|
45
49
|
|
|
46
50
|
:::tip
|
|
47
|
-
|
|
51
|
+
In a browser, you can reach the Picked client from the console with `window.Picked`.
|
|
48
52
|
:::
|
|
49
53
|
|
|
50
|
-
## Precondition
|
|
54
|
+
## Precondition and condition
|
|
51
55
|
|
|
52
56
|
| Category | Question | Description |
|
|
53
57
|
| ------------ | ---------------------- | --------------------------------------------------------------------------------------------- |
|
|
54
|
-
| Precondition |
|
|
55
|
-
| Condition | Is this user eligible? | Use
|
|
58
|
+
| Precondition | Must Picked start? | Use it to check the environment, to filter by page, or for a feature flag that turns the SDK off completely. |
|
|
59
|
+
| Condition | Is this user eligible? | Use it to divide your users into groups, to enter a user into an A/B test, or to reach one group of users. |
|
|
56
60
|
|
|
57
61
|
```ts
|
|
58
62
|
const picked = createClient({
|
|
@@ -76,14 +80,19 @@ const picked = createClient({
|
|
|
76
80
|
var: 'features.convincely-enabled',
|
|
77
81
|
},
|
|
78
82
|
},
|
|
79
|
-
// rest of
|
|
83
|
+
// The rest of the Picked options go here.
|
|
80
84
|
})
|
|
81
85
|
```
|
|
82
86
|
|
|
83
87
|
:::warning
|
|
84
|
-
A precondition or condition that
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
A precondition or a condition that points at a flag reads the bucket of that flag,
|
|
89
|
+
and that allocates the visitor at that moment. A precondition runs at startup.
|
|
90
|
+
Picked therefore allocates every visitor to a flag that a precondition points at,
|
|
91
|
+
before anything else happens. This includes a visitor who never reaches the
|
|
92
|
+
experience that the flag controls.
|
|
93
|
+
|
|
94
|
+
See [Allocation and exposure](/sdk/feature-allocation) before you point a
|
|
95
|
+
precondition at a flag.
|
|
87
96
|
:::
|
|
88
97
|
|
|
89
|
-
See [conditions](./conditions.md) for more detail
|
|
98
|
+
See [conditions](./conditions.md) for more detail.
|
|
@@ -4,9 +4,9 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Create Experience Plugin
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This plugin registers an array of experiences on the Picked client.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Set the plugin up
|
|
10
10
|
|
|
11
11
|
:::code-group
|
|
12
12
|
|
|
@@ -26,14 +26,15 @@ const client = createClient({
|
|
|
26
26
|
|
|
27
27
|
:::
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Define a step
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Define a step with `createStep()`. Add it directly to an experience with
|
|
32
|
+
`experience.addStep()`.
|
|
32
33
|
|
|
33
34
|
```ts
|
|
34
35
|
import { createExperience, createStep } from '@picked/client/plugins/create-experience'
|
|
35
36
|
|
|
36
|
-
// Define
|
|
37
|
+
// Define the steps on their own.
|
|
37
38
|
const welcomeStep = createStep({
|
|
38
39
|
id: 'welcome',
|
|
39
40
|
name: 'Welcome',
|
|
@@ -44,7 +45,7 @@ const contactStep = createStep({
|
|
|
44
45
|
name: 'Contact Details',
|
|
45
46
|
})
|
|
46
47
|
|
|
47
|
-
// Define the experience
|
|
48
|
+
// Define the experience, then add the steps to build the flow.
|
|
48
49
|
const onboardingExperience = createExperience({
|
|
49
50
|
id: 'onboarding',
|
|
50
51
|
name: 'User Onboarding',
|
|
@@ -55,13 +56,14 @@ onboardingExperience.addStep(welcomeStep)
|
|
|
55
56
|
onboardingExperience.addStep(contactStep)
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
Each step
|
|
59
|
+
Each step needs a `name`, and your analytics events use that name to identify the
|
|
60
|
+
step. An `id` is optional. Picked makes one for you when you give none.
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
You can use the same step on more than one experience.
|
|
61
63
|
|
|
62
|
-
###
|
|
64
|
+
### Add a field to a step
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
Define a field with `createField()`.
|
|
65
67
|
|
|
66
68
|
```ts
|
|
67
69
|
import { createStep, createField } from '@picked/client/plugins/create-experience'
|
|
@@ -77,7 +79,8 @@ const emailField = createField({
|
|
|
77
79
|
})
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
Add a field to a step with `step.addField()`. You can also pass an array of
|
|
83
|
+
`fieldIds` when you create the step:
|
|
81
84
|
|
|
82
85
|
```ts
|
|
83
86
|
const contactStep = createStep({
|
|
@@ -87,7 +90,7 @@ const contactStep = createStep({
|
|
|
87
90
|
|
|
88
91
|
contactStep.addField(emailField)
|
|
89
92
|
|
|
90
|
-
//Or
|
|
93
|
+
// Or:
|
|
91
94
|
|
|
92
95
|
const contactStep = createStep({
|
|
93
96
|
id: 'contact',
|
|
@@ -96,9 +99,9 @@ const contactStep = createStep({
|
|
|
96
99
|
})
|
|
97
100
|
```
|
|
98
101
|
|
|
99
|
-
##
|
|
102
|
+
## Use a condition in an experience
|
|
100
103
|
|
|
101
|
-
[
|
|
104
|
+
A [condition](../conditions) defines when an experience becomes active for the user.
|
|
102
105
|
|
|
103
106
|
```ts
|
|
104
107
|
import { createExperience, createStep } from '@picked/client/plugins/create-experience'
|
|
@@ -125,7 +128,7 @@ const productPageFunnel = createExperience({
|
|
|
125
128
|
})
|
|
126
129
|
```
|
|
127
130
|
|
|
128
|
-
|
|
131
|
+
A [condition](../conditions) also controls the move from one step to the next.
|
|
129
132
|
|
|
130
133
|
```ts
|
|
131
134
|
import { createExperience, createStep } from '@picked/client/plugins/create-experience'
|
|
@@ -4,9 +4,11 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Dynamic Experiences Plugin
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This plugin reads the options of your project from the Picked API at runtime. You can
|
|
8
|
+
therefore control your experiences, feature flags, steps, fields, and conditions from
|
|
9
|
+
the [dashboard](/experiences) alone.
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Set the plugin up
|
|
10
12
|
|
|
11
13
|
:::code-group
|
|
12
14
|
|
|
@@ -25,34 +27,58 @@ client.start()
|
|
|
25
27
|
:::
|
|
26
28
|
|
|
27
29
|
:::tip
|
|
28
|
-
|
|
30
|
+
With [`createExperiencePlugin`](./create-experience.md) you define an experience, a
|
|
31
|
+
step, and a field in code. With this plugin you do not. You control every option from
|
|
32
|
+
the [Picked dashboard](/experiences).
|
|
29
33
|
:::
|
|
30
34
|
|
|
31
|
-
## How
|
|
35
|
+
## How it works
|
|
32
36
|
|
|
33
|
-
When the client starts, the plugin resolves
|
|
37
|
+
When the client starts, the plugin resolves the options of your project. It tries
|
|
38
|
+
three sources, in this order:
|
|
34
39
|
|
|
35
|
-
1. **Preview mode** —
|
|
40
|
+
1. **Preview mode** — after you [enter preview mode from the dashboard](/workspaces#preview),
|
|
41
|
+
the plugin reads fresh options from the API. A feature flag decision then uses
|
|
42
|
+
session storage, so it does not stay after you close the tab.
|
|
36
43
|
|
|
37
|
-
2. **Build-time
|
|
44
|
+
2. **Build-time options** — the plugin uses the options that it has already, and
|
|
45
|
+
makes no network request. This applies in two cases. You pass `projectConfig` as a
|
|
46
|
+
plugin option, or the [Vite Plugin](/vite-plugin) injects the options at build
|
|
47
|
+
time.
|
|
38
48
|
|
|
39
|
-
3. **Runtime
|
|
49
|
+
3. **Runtime read** — in every other case, the plugin reads the options from the API
|
|
50
|
+
when the client starts.
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
After the options resolve, the plugin registers every feature flag, condition, field,
|
|
53
|
+
step, and experience on the client. Each experience then starts, and its
|
|
54
|
+
[conditions](/sdk/conditions) and [target selectors](/sdk/experiences) decide
|
|
55
|
+
whether it does.
|
|
42
56
|
|
|
43
57
|
:::note
|
|
44
|
-
The [Vite Plugin](/vite-plugin)
|
|
58
|
+
The [Vite Plugin](/vite-plugin) embeds the options of your project at build time. In
|
|
59
|
+
production there is therefore no API request at runtime. This is the recommended
|
|
60
|
+
setup for an application that uses Vite.
|
|
45
61
|
:::
|
|
46
62
|
|
|
47
63
|
## Feature flag allocation
|
|
48
64
|
|
|
49
|
-
Each [feature flag](/sdk/features) in your project has an `enabled` state and a
|
|
65
|
+
Each [feature flag](/sdk/features) in your project has an `enabled` state and a
|
|
66
|
+
`weight` from 0 to 100. The weight controls what percentage of your traffic Picked
|
|
67
|
+
allocates to the flag.
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
The plugin makes a deterministic hash from the session ID of the user, and that gives
|
|
70
|
+
a stable allocation value. Picked allocates a user whose value is below the weight of
|
|
71
|
+
the flag. The same user always gets the same result.
|
|
52
72
|
|
|
53
|
-
|
|
73
|
+
The plugin caches a feature flag decision in `localStorage` by default. In
|
|
74
|
+
[preview mode](#preview-mode) it uses `sessionStorage`.
|
|
54
75
|
|
|
55
|
-
|
|
76
|
+
## Preview mode
|
|
56
77
|
|
|
57
|
-
|
|
58
|
-
|
|
78
|
+
After you [enter preview mode from the dashboard](/workspaces#preview), the SDK
|
|
79
|
+
receives a preview token and starts preview mode:
|
|
80
|
+
|
|
81
|
+
- The plugin reads fresh options from the API with that token. It bypasses the
|
|
82
|
+
build-time options and the cached options.
|
|
83
|
+
- A feature flag decision uses session storage. The decision resets when you close
|
|
84
|
+
the tab.
|
|
@@ -4,11 +4,15 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Predicates
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A predicate holds reusable condition logic that reads the current state of your
|
|
8
|
+
application. Each predicate is a pure function. It takes a state snapshot, and that
|
|
9
|
+
snapshot holds the responses, the fields, the browser context, and optional
|
|
10
|
+
arguments.
|
|
8
11
|
|
|
9
|
-
##
|
|
12
|
+
## Define a predicate
|
|
10
13
|
|
|
11
|
-
Define predicates when
|
|
14
|
+
Define your predicates when you create the Picked client. Pass a `predicates` object.
|
|
15
|
+
Each predicate needs a unique identifier and a function that takes a state snapshot:
|
|
12
16
|
|
|
13
17
|
```ts
|
|
14
18
|
import { createClient } from '@picked/client'
|
|
@@ -21,24 +25,28 @@ const client = createClient({
|
|
|
21
25
|
},
|
|
22
26
|
'get-account-tier': async () => {
|
|
23
27
|
const data = await fetch('/api/get-account-tier').then((r) => r.json())
|
|
24
|
-
return data.result //
|
|
28
|
+
return data.result // Returns "trial".
|
|
25
29
|
},
|
|
26
30
|
},
|
|
27
31
|
})
|
|
28
32
|
```
|
|
29
33
|
|
|
30
|
-
## How
|
|
34
|
+
## How predicates work
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
After you register a predicate and a condition uses it, the SDK follows this flow:
|
|
33
37
|
|
|
34
|
-
1. **Registration**:
|
|
35
|
-
2. **State
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
1. **Registration**: you register each predicate on the Picked client at startup.
|
|
39
|
+
2. **State snapshot**: the SDK gives the predicate a frozen state snapshot. The
|
|
40
|
+
snapshot holds the current responses, the fields, and the browser context.
|
|
41
|
+
3. **Condition evaluation**: an experience condition invokes the predicate with a
|
|
42
|
+
fresh snapshot on each call.
|
|
43
|
+
4. **Return types**: a predicate can return a boolean, a string, an array of
|
|
44
|
+
strings, or `undefined`. It can also return a promise that resolves to one of
|
|
45
|
+
those.
|
|
38
46
|
|
|
39
|
-
##
|
|
47
|
+
## Use a predicate in a condition
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
Most predicates are used inside condition logic, through the `invoke` operator:
|
|
42
50
|
|
|
43
51
|
```ts
|
|
44
52
|
const myExperience = createExperience({
|
|
@@ -49,10 +57,10 @@ const myExperience = createExperience({
|
|
|
49
57
|
'==': [
|
|
50
58
|
{
|
|
51
59
|
invoke: {
|
|
52
|
-
var: 'predicates.is-example-page', //
|
|
60
|
+
var: 'predicates.is-example-page', // Points at a predicate that calls your endpoint.
|
|
53
61
|
},
|
|
54
62
|
},
|
|
55
|
-
true, // Check
|
|
63
|
+
true, // Check that the return value is true.
|
|
56
64
|
],
|
|
57
65
|
},
|
|
58
66
|
{
|
|
@@ -62,7 +70,7 @@ const myExperience = createExperience({
|
|
|
62
70
|
var: 'predicates.account-tier',
|
|
63
71
|
},
|
|
64
72
|
},
|
|
65
|
-
'/trial', //
|
|
73
|
+
'/trial', // The condition is true when the string matches.
|
|
66
74
|
],
|
|
67
75
|
},
|
|
68
76
|
],
|
|
@@ -71,42 +79,50 @@ const myExperience = createExperience({
|
|
|
71
79
|
```
|
|
72
80
|
|
|
73
81
|
:::tip
|
|
74
|
-
|
|
82
|
+
You can also use a predicate in `validation` logic.
|
|
75
83
|
:::
|
|
76
84
|
|
|
77
|
-
##
|
|
85
|
+
## Use the state snapshot
|
|
78
86
|
|
|
79
|
-
Each predicate
|
|
87
|
+
Each predicate takes a `StateSnapshot` object with these properties:
|
|
80
88
|
|
|
81
89
|
```ts
|
|
82
90
|
interface StateSnapshot {
|
|
83
|
-
client: Picked // The Picked client
|
|
91
|
+
client: Picked // The Picked client.
|
|
84
92
|
responses?: Array<{
|
|
85
|
-
//
|
|
93
|
+
// Every response from the user.
|
|
86
94
|
fieldId: string
|
|
87
95
|
value: FieldValue | undefined
|
|
88
96
|
}>
|
|
89
97
|
args?: unknown[]
|
|
90
|
-
fields?: Field[] //
|
|
91
|
-
location?: Location //
|
|
92
|
-
document?: Document //
|
|
93
|
-
sessionStorage?: Storage //
|
|
94
|
-
localStorage?: Storage //
|
|
98
|
+
fields?: Field[] // Every registered field.
|
|
99
|
+
location?: Location // The browser location: URL, pathname, search, and more.
|
|
100
|
+
document?: Document // The browser document object.
|
|
101
|
+
sessionStorage?: Storage // The browser sessionStorage API.
|
|
102
|
+
localStorage?: Storage // The browser localStorage API.
|
|
95
103
|
}
|
|
96
104
|
```
|
|
97
105
|
|
|
98
106
|
:::warning
|
|
99
|
-
In server-side rendering
|
|
100
|
-
and `localStorage`
|
|
107
|
+
In server-side rendering, and in any environment that is not a browser, `location`,
|
|
108
|
+
`document`, `sessionStorage`, and `localStorage` are `undefined`.
|
|
101
109
|
:::
|
|
102
110
|
|
|
103
|
-
## Best
|
|
104
|
-
|
|
105
|
-
- **Keep
|
|
106
|
-
|
|
107
|
-
- **
|
|
108
|
-
|
|
109
|
-
- **
|
|
110
|
-
|
|
111
|
-
- **
|
|
112
|
-
|
|
111
|
+
## Best practices
|
|
112
|
+
|
|
113
|
+
- **Keep each predicate pure**: a predicate must be deterministic and free of side
|
|
114
|
+
effects. The same snapshot must always give the same result.
|
|
115
|
+
- **Handle missing data**: check that a response or a field is there before you read
|
|
116
|
+
its value. Return `undefined` when the data is not available. Do not throw.
|
|
117
|
+
- **Do not do heavy work**: the SDK can invoke a predicate many times. Keep the logic
|
|
118
|
+
small and fast.
|
|
119
|
+
- **Use an async predicate only when you must**: the SDK supports one, but it can
|
|
120
|
+
make the experience slow to appear. Use synchronous logic where you can.
|
|
121
|
+
- **Return one type**: do not return a boolean from a predicate one time and a string
|
|
122
|
+
the next.
|
|
123
|
+
- **Use a meaningful name**: a predicate ID must say what the predicate evaluates,
|
|
124
|
+
such as `is-premium-user` or `has-items-in-cart`.
|
|
125
|
+
- **Keep the result of expensive work**: when a predicate must do expensive work, put
|
|
126
|
+
the result in `localStorage` or `sessionStorage` inside the predicate.
|
|
127
|
+
- **Do not change the snapshot**: the state snapshot is frozen. A change to it throws
|
|
128
|
+
an error in strict mode.
|