@picked/client 0.0.74-next.1 → 0.0.75-next.1
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/.tsbuildinfo +1 -1
- package/dist/{api-4x_9XPBM.cjs → api-CUlZaK-b.cjs} +1 -1
- package/dist/{api-DSSAUkRz.js → api-ITpKqiED.js} +1 -1
- 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/explanation/feature-flag-allocation.md +43 -1
- package/dist/docs/how-to/read-a-feature-flag.md +22 -10
- package/dist/docs/reference/dashboard/features.mdx +45 -5
- package/dist/docs/reference/delivery-api/workspace-schema.md +12 -1
- package/dist/docs/reference/pickedql/dimensions.md +6 -0
- package/dist/docs/reference/pickedql/virtual-functions.md +16 -5
- package/dist/docs/reference/sdk/features.md +17 -6
- package/dist/docs/reference/sdk/type-generation.md +17 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
- package/dist/esm/server/index.js +1 -1
- package/dist/{plugin-Q121SckS.js → plugin-BNLWb4FA.js} +44 -29
- package/dist/plugin-xFCAKJuO.cjs +1 -0
- package/dist/{provider-DYmW03w9.cjs → provider-DCjzwNWK.cjs} +1 -1
- package/dist/{provider-BlW0FUxy.js → provider-Da1D5BIA.js} +1 -1
- package/dist/types/entities/feature/feature.d.ts +10 -3
- package/dist/types/entities/feature/feature.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/picked.d.ts +1 -1
- package/dist/types/plugins/dynamic-experiences/hash.d.ts +53 -1
- 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/dist/types/schema-registry.d.ts +16 -0
- package/dist/types/schema-registry.d.ts.map +1 -1
- package/dist/{version-D6O3knkR.js → version-BsVuRkHH.js} +1 -1
- package/dist/version-DYoxIopP.cjs +1 -0
- package/package.json +2 -2
- package/dist/plugin-BH-WQNm8.cjs +0 -1
- package/dist/version-De21NPsY.cjs +0 -1
|
@@ -21,6 +21,19 @@ Each feature flag has a unique key. Your application uses this key to check whet
|
|
|
21
21
|
Picked allocates a visitor to the feature flag. You set the key when you create the
|
|
22
22
|
feature flag, and you cannot change it later.
|
|
23
23
|
|
|
24
|
+
## Types
|
|
25
|
+
|
|
26
|
+
Each feature flag has a type. You set the type when you create the feature flag, and you
|
|
27
|
+
cannot change it later.
|
|
28
|
+
|
|
29
|
+
| Type | What your site receives | Groups |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| **Boolean** | `true` or `false` | Two |
|
|
32
|
+
| **Text** | A string that you write, or `false` for the control group | Two or more |
|
|
33
|
+
|
|
34
|
+
Choose **Boolean** for one change against no change. Choose **Text** when you must test
|
|
35
|
+
several changes at the same time, and each change needs its own group.
|
|
36
|
+
|
|
24
37
|
## Set the allocation
|
|
25
38
|
|
|
26
39
|
Set the allocation from the detail page of the feature flag:
|
|
@@ -28,19 +41,46 @@ Set the allocation from the detail page of the feature flag:
|
|
|
28
41
|
| Control | What it does |
|
|
29
42
|
| --- | --- |
|
|
30
43
|
| **Enabled** | Whether Picked allocates a visitor to the feature flag. A disabled feature flag allocates nobody, and it records nothing. See [Disable a feature flag](#disable-a-feature-flag). |
|
|
31
|
-
| **Weight** | The percentage of visitors that Picked allocates to
|
|
44
|
+
| **Weight** | The percentage of visitors that Picked allocates to a Boolean feature flag, from 0 to 100. |
|
|
45
|
+
| **Variants** | The groups of a Text feature flag, and the traffic that each one receives. See [Variants](#variants). |
|
|
32
46
|
| **Description** | An internal note about what the feature flag tests. |
|
|
33
47
|
|
|
34
|
-
A feature flag has one weight. For each visitor it gives one of two outcomes:
|
|
48
|
+
A Boolean feature flag has one weight. For each visitor it gives one of two outcomes:
|
|
35
49
|
allocated, or not allocated. A weight of 30 allocates 30% of your visitors. The other
|
|
36
50
|
70% stay unallocated. A weight of 0 allocates nobody. A weight of 100 allocates
|
|
37
51
|
everybody.
|
|
38
52
|
|
|
53
|
+
## Variants
|
|
54
|
+
|
|
55
|
+
A Text feature flag has a table of variants. Each row is one group of visitors:
|
|
56
|
+
|
|
57
|
+
| Column | What it does |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| **Name** | A label for the variant. It appears in your reports. You can change it at any time. |
|
|
60
|
+
| **Value** | The string that your site receives for a visitor in this variant. |
|
|
61
|
+
| **Weight** | The share of traffic for this variant, against the other rows. |
|
|
62
|
+
|
|
63
|
+
The first row is **Control**. Picked always sends `false` to a visitor in the control
|
|
64
|
+
group, and you cannot remove the row, move it, or change its value. It takes a weight like
|
|
65
|
+
any other row.
|
|
66
|
+
|
|
67
|
+
The weights are shares, and not percentages. Picked reduces them to their smallest whole
|
|
68
|
+
numbers, so `10 : 10 : 20` and `1 : 1 : 2` are the same split. The percentage beside each
|
|
69
|
+
row shows the traffic that the variant receives.
|
|
70
|
+
|
|
71
|
+
Choose **Even split** to give every variant the same weight.
|
|
72
|
+
|
|
73
|
+
:::info
|
|
74
|
+
The order of the rows is the order that you read them in. It has no effect on the
|
|
75
|
+
allocation, so you can move a row and no visitor changes group.
|
|
76
|
+
:::
|
|
77
|
+
|
|
39
78
|
## How Picked allocates a visitor
|
|
40
79
|
|
|
41
|
-
The allocation is deterministic. It derives from the visitor ID
|
|
42
|
-
flag
|
|
43
|
-
|
|
80
|
+
The allocation is deterministic. It derives from the visitor ID and from the ID of the
|
|
81
|
+
feature flag. A Boolean feature flag then compares that value against the weight, and a
|
|
82
|
+
Text feature flag compares it against the range of each variant. Each feature flag gets its
|
|
83
|
+
own value, so tests that run at the same time are independent. See
|
|
44
84
|
[About feature flag allocation](/explanation/feature-flag-allocation).
|
|
45
85
|
|
|
46
86
|
## Disable a feature flag
|
|
@@ -28,6 +28,7 @@ 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-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. |
|
|
31
32
|
| `$defs.contentModels` | JSON Schema definitions for each content model. |
|
|
32
33
|
| `$defs.contentBlocks` | JSON Schema definitions for reusable content blocks. |
|
|
33
34
|
| `$defs.fields` | Object schema for project field values. |
|
|
@@ -41,6 +42,13 @@ On the published surface, the schema reflects your last published workspace. On
|
|
|
41
42
|
|
|
42
43
|
When no published schema exists, the published endpoint returns `404` and tells you to publish the workspace.
|
|
43
44
|
|
|
45
|
+
A Boolean feature flag serves `true` or `false`, and it contributes no entry to
|
|
46
|
+
`x-picked-feature-values`. A workspace of Boolean feature flags therefore omits the field.
|
|
47
|
+
A schema that Picked published before Text feature flags existed also omits it.
|
|
48
|
+
|
|
49
|
+
Picked sorts the values of each Text feature flag by the key of the variant. The document
|
|
50
|
+
therefore does not change when you rename a variant or move a row.
|
|
51
|
+
|
|
44
52
|
## Example
|
|
45
53
|
|
|
46
54
|
```bash
|
|
@@ -57,11 +65,14 @@ curl https://api.picked.app/api/v1/projects/proj_123/schema
|
|
|
57
65
|
"x-picked-step-content-models": {
|
|
58
66
|
"intro": "welcome-page"
|
|
59
67
|
},
|
|
68
|
+
"x-picked-feature-values": {
|
|
69
|
+
"hero": [false, "hero-a", "hero-b"]
|
|
70
|
+
},
|
|
60
71
|
"$defs": {
|
|
61
72
|
"contentModels": {},
|
|
62
73
|
"contentBlocks": {},
|
|
63
74
|
"fields": { "type": "object", "additionalProperties": false, "properties": {} },
|
|
64
|
-
"featureKeys": { "type": "string", "enum": ["default"] },
|
|
75
|
+
"featureKeys": { "type": "string", "enum": ["default", "hero"] },
|
|
65
76
|
"experienceKeys": { "type": "string", "enum": ["welcome-survey"] },
|
|
66
77
|
"stepKeys": { "type": "string", "enum": ["intro"] }
|
|
67
78
|
}
|
|
@@ -22,6 +22,12 @@ point at one. You write no join.
|
|
|
22
22
|
| `experiences` | `experience_title` |
|
|
23
23
|
| `projects` | `project_name`, `project_timezone` |
|
|
24
24
|
|
|
25
|
+
:::warning
|
|
26
|
+
`dim_variant` describes whether the visitor is a known user. It does **not** hold the variants
|
|
27
|
+
of your feature flags. To count the visitors in one variant, use
|
|
28
|
+
[`feature_users`](/reference/pickedql/virtual-functions#feature-functions-on-features-and-on-events).
|
|
29
|
+
:::
|
|
30
|
+
|
|
25
31
|
The `steps`, `experiences`, and `projects` sources are reference tables, and they come
|
|
26
32
|
from the operational database. PickedQL joins them onto `events` and `fact_events`
|
|
27
33
|
when you point at one of their analyst-facing columns. Each one **also** needs its own
|
|
@@ -213,15 +213,26 @@ Parameterized functions that accept the feature name, bucket, and an optional fe
|
|
|
213
213
|
| `feature_sessions('name', 'bucket' [, 'key'])` | Unique sessions in a feature bucket |
|
|
214
214
|
| `feature_events('name', 'bucket' [, 'key'])` | Unique events in a feature bucket |
|
|
215
215
|
|
|
216
|
+
The bucket is what the SDK sent to the site of the visitor. A Boolean feature flag gives
|
|
217
|
+
`'true'` or `'false'`. A Text feature flag gives the value of one variant, or `'false'` for
|
|
218
|
+
the control group. The key is the version of the feature flag, such as `'v1'`.
|
|
219
|
+
|
|
216
220
|
```sql
|
|
217
|
-
-- Compare feature
|
|
221
|
+
-- Compare the two buckets of a Boolean feature flag.
|
|
222
|
+
SELECT
|
|
223
|
+
feature_users('homepage_test', 'false') AS control,
|
|
224
|
+
feature_users('homepage_test', 'true') AS variant
|
|
225
|
+
FROM features
|
|
226
|
+
|
|
227
|
+
-- Compare the variants of a Text feature flag.
|
|
218
228
|
SELECT
|
|
219
|
-
feature_users('
|
|
220
|
-
feature_users('
|
|
229
|
+
feature_users('hero', 'false') AS control,
|
|
230
|
+
feature_users('hero', 'hero-a') AS hero_a,
|
|
231
|
+
feature_users('hero', 'hero-b') AS hero_b
|
|
221
232
|
FROM features
|
|
222
233
|
|
|
223
|
-
--
|
|
224
|
-
SELECT feature_users('homepage_test', '
|
|
234
|
+
-- Count one version of the feature flag alone.
|
|
235
|
+
SELECT feature_users('homepage_test', 'false', 'v1') FROM features
|
|
225
236
|
```
|
|
226
237
|
|
|
227
238
|
## Other (`metrics` only)
|
|
@@ -26,12 +26,22 @@ For a feature flag that you control from the dashboard, the SDK makes the defini
|
|
|
26
26
|
| `enabled` | The dashboard **Enabled** toggle |
|
|
27
27
|
| `weight` | The dashboard **Weight**, 0 to 100 |
|
|
28
28
|
| `storage` | `local`, or `session` in [preview mode](/explanation/feature-flag-allocation#preview-mode) |
|
|
29
|
-
| `decide` | Made by the SDK.
|
|
29
|
+
| `decide` | Made by the SDK. See the two routes below. |
|
|
30
30
|
|
|
31
|
-
The `decide` function that the SDK makes derives from the visitor ID
|
|
32
|
-
feature flag
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
The `decide` function that the SDK makes derives from the visitor ID and from the ID of
|
|
32
|
+
the feature flag. A feature flag that you control from the dashboard therefore cannot use
|
|
33
|
+
the data of your application. Define the feature flag in SDK code when you need that.
|
|
34
|
+
|
|
35
|
+
Which route `decide` takes depends on the type of the feature flag:
|
|
36
|
+
|
|
37
|
+
| Dashboard type | The bucket | How the SDK decides |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Boolean | `true` or `false` | It compares the stable value of the visitor against `weight`. |
|
|
40
|
+
| Text | The value of one variant, or `false` | It compares the stable value of the visitor against the range that Picked gives to each variant. |
|
|
41
|
+
|
|
42
|
+
The SDK sorts the variants by their key before it allocates, so the order of the payload
|
|
43
|
+
has no effect. See
|
|
44
|
+
[More than two groups](/explanation/feature-flag-allocation#more-than-two-groups).
|
|
35
45
|
|
|
36
46
|
## Define a feature flag in SDK code
|
|
37
47
|
|
|
@@ -95,7 +105,8 @@ You can give more than one override value:
|
|
|
95
105
|
https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
The format is `?picked-feature=<feature-id>:<bucket-value>`.
|
|
108
|
+
The format is `?picked-feature=<feature-id>:<bucket-value>`. For a Text feature flag,
|
|
109
|
+
give the value that the variant serves.
|
|
99
110
|
|
|
100
111
|
This is useful for QA, because you can test one bucket value and change no code. The
|
|
101
112
|
SDK keeps the override value in session storage. The browser clears it when you close
|
|
@@ -124,7 +124,7 @@ The generated `picked-env.d.ts` file looks like:
|
|
|
124
124
|
|
|
125
125
|
export interface WelcomePage { /* ... */ }
|
|
126
126
|
export interface FieldValues { /* ... */ }
|
|
127
|
-
export type FeatureKey = 'default' | 'winter-sale'
|
|
127
|
+
export type FeatureKey = 'default' | 'hero' | 'winter-sale'
|
|
128
128
|
|
|
129
129
|
declare module '@picked/client' {
|
|
130
130
|
interface PickedSchemaRegistry {
|
|
@@ -132,12 +132,28 @@ declare module '@picked/client' {
|
|
|
132
132
|
stepContentModels: { intro: 'welcome-page' }
|
|
133
133
|
fieldValues: FieldValues
|
|
134
134
|
featureKeys: FeatureKey
|
|
135
|
+
featureValues: { hero: false | 'hero-a' | 'hero-b' }
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
```
|
|
138
139
|
|
|
139
140
|
Commit this file to the version control system, or regenerate it in CI after you publish a workspace change.
|
|
140
141
|
|
|
142
|
+
### Feature flag values
|
|
143
|
+
|
|
144
|
+
A Text feature flag declares the values that it can serve, so `useFeatureResult` narrows
|
|
145
|
+
the bucket to those values:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const result = useFeatureResult('hero')
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The `bucket` property then has the type `false | 'hero-a' | 'hero-b'`.
|
|
152
|
+
|
|
153
|
+
`featureValues` holds the Text feature flags alone. A Boolean feature flag, and a feature
|
|
154
|
+
flag that your application defines, both keep the general `boolean | string` type. The
|
|
155
|
+
`FeatureValueFor<'hero'>` type gives the same union on its own.
|
|
156
|
+
|
|
141
157
|
## Keys the application defines
|
|
142
158
|
|
|
143
159
|
An application can define a feature, an action, a field, a step, or an experience of its
|
package/dist/esm/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { a as _, i as v, r as ee, s as y, t as b } from "../dom-BqcToNL0.js";
|
|
|
6
6
|
import { l as x, n as S, r as C, u as w } from "../experience-instance-DGk11d8a.js";
|
|
7
7
|
import { i as T, n as E, r as D, t as O } from "../storage-1MW3-_As.js";
|
|
8
8
|
import { t as te } from "../id-Bo4RBeOs.js";
|
|
9
|
-
import { t as ne } from "../version-
|
|
9
|
+
import { t as ne } from "../version-BsVuRkHH.js";
|
|
10
10
|
import { n as re, r as ie, t as ae } from "../inert-script-report-B_bMPRzp.js";
|
|
11
11
|
import { attach as k, combine as A, createApi as j, createDomain as M, createEffect as N, createEvent as P, createStore as F, is as oe, sample as I } from "effector";
|
|
12
12
|
import { createFactory as L, invoke as R } from "@withease/factories";
|
|
@@ -1774,7 +1774,7 @@ var Kt = class {
|
|
|
1774
1774
|
dataProviderFor(e) {
|
|
1775
1775
|
let t = e ?? "", n = this.dataProviders.get(t);
|
|
1776
1776
|
if (n) return n;
|
|
1777
|
-
let r = import("../provider-
|
|
1777
|
+
let r = import("../provider-Da1D5BIA.js").then((t) => t.createPickedDataProvider({
|
|
1778
1778
|
apiUrl: this.apiUrl,
|
|
1779
1779
|
projectId: this.projectId,
|
|
1780
1780
|
...e !== void 0 && { previewToken: e }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as e } from "../../../plugin-
|
|
2
|
-
import { n as t, t as n } from "../../../api-
|
|
1
|
+
import { t as e } from "../../../plugin-BNLWb4FA.js";
|
|
2
|
+
import { n as t, t as n } from "../../../api-ITpKqiED.js";
|
|
3
3
|
import { n as r } from "../../../registry-CXZv0uLg.js";
|
|
4
4
|
import { createClientHeaders as i, fetchSchema as a } from "@picked/delivery-api-client";
|
|
5
5
|
export { i as createClientHeaders, e as dynamicExperiencesPlugin, n as fetchExperienceContent, t as fetchProject, a as fetchSchema, r as registerPickedBundle };
|
package/dist/esm/server/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as e, c as t, i as n, o as r, s as i, t as a, u as o } from "../../experience-instance-DGk11d8a.js";
|
|
2
|
-
import { n as s } from "../../plugin-
|
|
2
|
+
import { n as s } from "../../plugin-BNLWb4FA.js";
|
|
3
3
|
import { attach as c, createEffect as l, createEvent as u, createStore as d, sample as f } from "effector";
|
|
4
4
|
//#region src/server/serialize.ts
|
|
5
5
|
var p = 1e4;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./logger-DMN6TxJJ.js";
|
|
2
|
-
import "./api-
|
|
2
|
+
import "./api-ITpKqiED.js";
|
|
3
3
|
import { attach as e } from "effector";
|
|
4
4
|
//#region src/plugins/dynamic-experiences/hash.ts
|
|
5
5
|
function t(e) {
|
|
@@ -17,12 +17,27 @@ function r(e, r) {
|
|
|
17
17
|
function i(e, t) {
|
|
18
18
|
return t <= 0 ? !1 : t >= 100 || e < t;
|
|
19
19
|
}
|
|
20
|
+
var a = 4294967296;
|
|
21
|
+
function o(e, r, i) {
|
|
22
|
+
return (n(t(`${e}:${r}:${i}`)) + .5) / a;
|
|
23
|
+
}
|
|
24
|
+
function s(e, t, n) {
|
|
25
|
+
return n.weight <= 0 ? 0 : n.weight / -Math.log(o(e, t, n.key));
|
|
26
|
+
}
|
|
27
|
+
function c(e, t, n) {
|
|
28
|
+
let r = null, i = 0;
|
|
29
|
+
for (let a of n) {
|
|
30
|
+
let n = s(e, t, a);
|
|
31
|
+
n <= 0 || (n > i || n === i && r !== null && a.key < r.key) && (i = n, r = a);
|
|
32
|
+
}
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
20
35
|
//#endregion
|
|
21
36
|
//#region src/plugins/dynamic-experiences/plugin.ts
|
|
22
|
-
function
|
|
37
|
+
function l(e, t) {
|
|
23
38
|
if (Array.isArray(t.conditions)) for (let n of t.conditions) e.registerCondition(n);
|
|
24
39
|
}
|
|
25
|
-
function
|
|
40
|
+
function u(e, t) {
|
|
26
41
|
e.registerExperience({
|
|
27
42
|
id: t.id,
|
|
28
43
|
key: t.key,
|
|
@@ -35,13 +50,13 @@ function o(e, t) {
|
|
|
35
50
|
componentName: t.componentName
|
|
36
51
|
});
|
|
37
52
|
}
|
|
38
|
-
function
|
|
53
|
+
function d(e) {
|
|
39
54
|
return typeof e != "object" || !e ? !1 : "id" in e && "key" in e && "type" in e && "name" in e;
|
|
40
55
|
}
|
|
41
|
-
function
|
|
42
|
-
if (Array.isArray(t.fields)) for (let n of t.fields)
|
|
56
|
+
function f(e, t) {
|
|
57
|
+
if (Array.isArray(t.fields)) for (let n of t.fields) d(n) && e.registerField(n);
|
|
43
58
|
}
|
|
44
|
-
function
|
|
59
|
+
function p(e, t) {
|
|
45
60
|
if (!Array.isArray(t.steps)) return;
|
|
46
61
|
let n = new Map(t.fields.map((e) => [e.id, e.key]));
|
|
47
62
|
for (let r of t.steps) {
|
|
@@ -68,17 +83,17 @@ function l(e, t) {
|
|
|
68
83
|
});
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
|
-
function
|
|
72
|
-
if (Array.isArray(t.experiences)) for (let n of t.experiences)
|
|
86
|
+
function m(e, t) {
|
|
87
|
+
if (Array.isArray(t.experiences)) for (let n of t.experiences) u(e, n);
|
|
73
88
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
89
|
+
function h(e, t, n) {
|
|
90
|
+
v(e, t, n.isPreview, n.sessionId), l(e, t), f(e, t), p(e, t), m(e, t);
|
|
76
91
|
}
|
|
77
|
-
function
|
|
92
|
+
function g(e) {
|
|
78
93
|
let t = e.searchParams["preview-token"];
|
|
79
94
|
if (t && t.length > 0) return t[0];
|
|
80
95
|
}
|
|
81
|
-
function
|
|
96
|
+
function _(e) {
|
|
82
97
|
let t = /* @__PURE__ */ new Set(), n = e.searchParams.feature;
|
|
83
98
|
if (!n) return t;
|
|
84
99
|
for (let e of n) {
|
|
@@ -87,29 +102,29 @@ function p(e) {
|
|
|
87
102
|
}
|
|
88
103
|
return t;
|
|
89
104
|
}
|
|
90
|
-
function
|
|
91
|
-
let i =
|
|
92
|
-
for (let a of t.features) i.has(a.id) ||
|
|
105
|
+
function v(e, t, n, r) {
|
|
106
|
+
let i = _(e);
|
|
107
|
+
for (let a of t.features) i.has(a.id) || y(e, a, n, r);
|
|
93
108
|
}
|
|
94
|
-
function
|
|
95
|
-
let o =
|
|
96
|
-
e.registerFeature(
|
|
109
|
+
function y(e, t, n, a) {
|
|
110
|
+
let o = t.id, s = t.variants;
|
|
111
|
+
e.registerFeature(o, {
|
|
97
112
|
key: `v${t.version}`,
|
|
98
113
|
name: t.name,
|
|
99
114
|
enabled: t.enabled,
|
|
100
115
|
weight: t.weight,
|
|
101
116
|
storage: n ? "session" : "local",
|
|
102
|
-
decide: () => i(o, t.weight)
|
|
117
|
+
decide: () => t.valueType === "text" ? !s || s.length === 0 ? (`${o}`, !1) : c(a, o, s)?.value ?? !1 : i(r(a, o), t.weight)
|
|
103
118
|
});
|
|
104
119
|
}
|
|
105
|
-
function
|
|
120
|
+
function b(e, t) {
|
|
106
121
|
for (let n of [...t.references ?? [], ...t.content]) e.registerContent({
|
|
107
122
|
id: n.id,
|
|
108
123
|
contentModelKey: n.contentModelKey,
|
|
109
124
|
data: n.data
|
|
110
125
|
});
|
|
111
126
|
}
|
|
112
|
-
function
|
|
127
|
+
function x(e, t) {
|
|
113
128
|
let n = /* @__PURE__ */ new Map();
|
|
114
129
|
return (r, i) => {
|
|
115
130
|
let a = n.get(r.id);
|
|
@@ -117,7 +132,7 @@ function _(e, t) {
|
|
|
117
132
|
let o = (async () => {
|
|
118
133
|
try {
|
|
119
134
|
let n = await t(r, i);
|
|
120
|
-
return n &&
|
|
135
|
+
return n && b(e, n), n;
|
|
121
136
|
} finally {
|
|
122
137
|
n.delete(r.id);
|
|
123
138
|
}
|
|
@@ -125,18 +140,18 @@ function _(e, t) {
|
|
|
125
140
|
return n.set(r.id, o), o;
|
|
126
141
|
};
|
|
127
142
|
}
|
|
128
|
-
function
|
|
143
|
+
function S(e, t) {
|
|
129
144
|
let n = new Set(t && Array.isArray(t.experiences) ? t.experiences.map((e) => e.id) : []);
|
|
130
145
|
return (t, r) => n.has(t.id) ? e(t, r) : Promise.resolve(null);
|
|
131
146
|
}
|
|
132
|
-
function
|
|
147
|
+
function C() {
|
|
133
148
|
return {
|
|
134
149
|
name: "dynamic-experiences",
|
|
135
150
|
setup: (t) => ({ onStart: e({
|
|
136
151
|
source: t.session.store.map((e) => e.id),
|
|
137
152
|
effect: async (e) => {
|
|
138
|
-
let n =
|
|
139
|
-
if (t.contentLoader =
|
|
153
|
+
let n = g(t), r = n !== void 0, i = n === void 0 ? void 0 : { previewToken: n }, a = await t.getProjectConfig(n === void 0 ? void 0 : { previewToken: n }).catch((e) => null);
|
|
154
|
+
if (t.contentLoader = x(t, S((e) => t.getExperienceContent(e, i), a)), !a) return;
|
|
140
155
|
let o = t.seededVersionId;
|
|
141
156
|
if (o !== null && o !== a.versionId) {
|
|
142
157
|
t.versionSkew = {
|
|
@@ -145,7 +160,7 @@ function y() {
|
|
|
145
160
|
}, `${a.versionId}${o}`;
|
|
146
161
|
return;
|
|
147
162
|
}
|
|
148
|
-
t.previewEmbedderOrigins = a.previewEmbedderOrigins ?? null,
|
|
163
|
+
t.previewEmbedderOrigins = a.previewEmbedderOrigins ?? null, h(t, a, {
|
|
149
164
|
isPreview: r,
|
|
150
165
|
sessionId: e
|
|
151
166
|
});
|
|
@@ -154,4 +169,4 @@ function y() {
|
|
|
154
169
|
};
|
|
155
170
|
}
|
|
156
171
|
//#endregion
|
|
157
|
-
export {
|
|
172
|
+
export { h as n, C as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require("./logger-CSTvAyyB.cjs"),require("./api-CUlZaK-b.cjs");let e=require("effector");function t(e){let t=2166136261;for(let n of e)t^=n.codePointAt(0)??0,t=Math.imul(t,16777619);return t>>>0}function n(e){let t=e;return t^=t>>>16,t=Math.imul(t,2246822507),t^=t>>>13,t=Math.imul(t,3266489909),t^=t>>>16,t>>>0}function r(e,r){return n(t(`${e}:${r}`))%100}function i(e,t){return t<=0?!1:t>=100||e<t}var a=4294967296;function o(e,r,i){return(n(t(`${e}:${r}:${i}`))+.5)/a}function s(e,t,n){return n.weight<=0?0:n.weight/-Math.log(o(e,t,n.key))}function c(e,t,n){let r=null,i=0;for(let a of n){let n=s(e,t,a);n<=0||(n>i||n===i&&r!==null&&a.key<r.key)&&(i=n,r=a)}return r}function l(e,t){if(Array.isArray(t.conditions))for(let n of t.conditions)e.registerCondition(n)}function u(e,t){e.registerExperience({id:t.id,key:t.key,name:t.name,targets:t.targets,nodes:t.nodes,edges:t.edges,condition:t.condition,initializeOpen:t.initializeOpen,componentName:t.componentName})}function d(e){return typeof e!=`object`||!e?!1:`id`in e&&`key`in e&&`type`in e&&`name`in e}function f(e,t){if(Array.isArray(t.fields))for(let n of t.fields)d(n)&&e.registerField(n)}function p(e,t){if(!Array.isArray(t.steps))return;let n=new Map(t.fields.map(e=>[e.id,e.key]));for(let r of t.steps){let t=[];for(let e of r.fieldIds??[]){let i=n.get(e);if(i===void 0){`${r.key}${e}`;continue}t.push(i)}let i=r.layoutContentId?e.contents.getById(r.layoutContentId):null;e.registerStep({id:r.id,key:r.key,name:r.name,fieldKeys:t,layoutName:r.layoutName,...r.layoutContentId!==void 0&&{layoutContentId:r.layoutContentId,layoutContentStore:i}})}}function m(e,t){if(Array.isArray(t.experiences))for(let n of t.experiences)u(e,n)}function h(e,t,n){v(e,t,n.isPreview,n.sessionId),l(e,t),f(e,t),p(e,t),m(e,t)}function g(e){let t=e.searchParams[`preview-token`];if(t&&t.length>0)return t[0]}function _(e){let t=new Set,n=e.searchParams.feature;if(!n)return t;for(let e of n){let n=e.indexOf(`:`);n>0&&t.add(e.substring(0,n))}return t}function v(e,t,n,r){let i=_(e);for(let a of t.features)i.has(a.id)||y(e,a,n,r)}function y(e,t,n,a){let o=t.id,s=t.variants;e.registerFeature(o,{key:`v${t.version}`,name:t.name,enabled:t.enabled,weight:t.weight,storage:n?`session`:`local`,decide:()=>t.valueType===`text`?!s||s.length===0?(`${o}`,!1):c(a,o,s)?.value??!1:i(r(a,o),t.weight)})}function b(e,t){for(let n of[...t.references??[],...t.content])e.registerContent({id:n.id,contentModelKey:n.contentModelKey,data:n.data})}function x(e,t){let n=new Map;return(r,i)=>{let a=n.get(r.id);if(a)return a;let o=(async()=>{try{let n=await t(r,i);return n&&b(e,n),n}finally{n.delete(r.id)}})();return n.set(r.id,o),o}}function S(e,t){let n=new Set(t&&Array.isArray(t.experiences)?t.experiences.map(e=>e.id):[]);return(t,r)=>n.has(t.id)?e(t,r):Promise.resolve(null)}function C(){return{name:`dynamic-experiences`,setup:t=>({onStart:(0,e.attach)({source:t.session.store.map(e=>e.id),effect:async e=>{let n=g(t),r=n!==void 0,i=n===void 0?void 0:{previewToken:n},a=await t.getProjectConfig(n===void 0?void 0:{previewToken:n}).catch(e=>null);if(t.contentLoader=x(t,S(e=>t.getExperienceContent(e,i),a)),!a)return;let o=t.seededVersionId;if(o!==null&&o!==a.versionId){t.versionSkew={seeded:o,resolved:a.versionId},`${a.versionId}${o}`;return}t.previewEmbedderOrigins=a.previewEmbedderOrigins??null,h(t,a,{isPreview:r,sessionId:e})}})})}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return C}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
require("./logger-CSTvAyyB.cjs");const e=require("./api-
|
|
1
|
+
require("./logger-CSTvAyyB.cjs");const e=require("./api-CUlZaK-b.cjs"),t=require("./registry-C1t8l3t9.cjs");var n=`bundle`,r=`api`;async function i(){try{let e=await import(`virtual:picked/data`);return t.r(e)?e:null}catch{return null}}function a(e){if(e===void 0||t.r(e))return e}function o(o){let{apiUrl:s,projectId:c,previewToken:l,signal:u}=o,d=!!l,f=d?r:o.mode,p=a(d?void 0:o.bundle),m=d?void 0:o.projectConfig,h=o.getContent,g={mode:f??r,manifest:null},_,v,y=new Map;function b(){if(f===r)return Promise.resolve(null);if(p)return Promise.resolve(p);let e=t.t();return e?Promise.resolve(e):(_??=i(),_)}function x(e){g.mode=e?n:r,g.manifest=e?e.manifestMetadata:null}async function S(){if(m)return x(await b()),m;let t=await b();return x(t),t?t.projectConfig:f===n?null:e.n(s,c,{...l!==void 0&&{previewToken:l},...u!==void 0&&{signal:u}})}async function C(t,r){let i=l===void 0?void 0:{previewToken:l};if(h)return h(t,i);let a=await b();if(x(a),a)try{return await a.experienceContent(t,i)}catch{return`${t.id}`,null}if(f===n)return null;let o=r??u;return e.t(s,c,t.id,{...l!==void 0&&{previewToken:l},...o!==void 0&&{signal:o}})}return{meta:g,getProjectConfig:()=>(v??=S(),v),getContent:(e,t)=>{let n=y.get(e.id);if(n)return n;let r=C(e,t?.signal).finally(()=>{y.delete(e.id)});return y.set(e.id,r),r}}}exports.createPickedDataProvider=o;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @file Feature entity — Effector stores and registry for A/B test features with persistent bucketing. */
|
|
2
2
|
import { type EventCallable, type Store } from 'effector';
|
|
3
3
|
import type { Picked } from '../../picked';
|
|
4
|
+
import type { FeatureValueFor } from '../../schema-registry';
|
|
4
5
|
export interface Feature {
|
|
5
6
|
key: string;
|
|
6
7
|
name: string;
|
|
@@ -48,9 +49,15 @@ export interface ResolvedFeatureBucket extends FeatureBaseBucket {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
export type FeatureBucket = UnresolvedFeatureBucket | ResolvedFeatureBucket;
|
|
51
|
-
|
|
52
|
+
/**
|
|
53
|
+
* A feature's resolved bucket. `TValue` narrows to a text feature's declared arms when
|
|
54
|
+
* the schema registry is augmented, and stays `string | boolean` otherwise.
|
|
55
|
+
*
|
|
56
|
+
* @param {string | boolean} TValue - The value the feature resolves to.
|
|
57
|
+
*/
|
|
58
|
+
export interface FeatureResult<TValue extends string | boolean = string | boolean> {
|
|
52
59
|
id: string;
|
|
53
|
-
bucket:
|
|
60
|
+
bucket: TValue;
|
|
54
61
|
}
|
|
55
62
|
/** The bucket an evokable feature answers with, or undefined while its feature is unregistered. */
|
|
56
63
|
export type FeatureBucketValue = boolean | string | undefined;
|
|
@@ -81,7 +88,7 @@ declare const createFeatureIndexFactory: ({ client }: {
|
|
|
81
88
|
getByName: (name: string) => Store<FeatureIndexItem | undefined>;
|
|
82
89
|
listStore: Store<FeatureBucket[]>;
|
|
83
90
|
getEvokableFeature: (featureName: string) => Store<() => Promise<FeatureBucketValue>>;
|
|
84
|
-
getFeatureResult: (featureName:
|
|
91
|
+
getFeatureResult: <K extends string>(featureName: K) => Store<FeatureResult<FeatureValueFor<K>> | undefined>;
|
|
85
92
|
};
|
|
86
93
|
/**
|
|
87
94
|
* Creates a feature index with lifecycle stores and registration logic.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../../../src/entities/feature/feature.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,OAAO,EAQL,KAAK,aAAa,EAElB,KAAK,KAAK,EACX,MAAM,UAAU,CAAA;AAEjB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../../../src/entities/feature/feature.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -21,6 +21,6 @@ export type * from './utils/events';
|
|
|
21
21
|
export type * from './utils/search-params';
|
|
22
22
|
export type * from './entities/response/response';
|
|
23
23
|
export { Picked } from './picked';
|
|
24
|
-
export type { ActionKeys, ContentForStep, ExperienceKeys, FeatureKeys, FieldKeys, FieldKeysForStep, FieldValueFor, KnownActionKeys, KnownExperienceKeys, KnownFeatureKeys, KnownFieldKeys, KnownStepKeys, PickedSchemaRegistry, ReferencedContent, StepContentModelsMap, StepKeys, StepLayoutContent, TypedContent, } from './schema-registry';
|
|
24
|
+
export type { ActionKeys, ContentForStep, ExperienceKeys, FeatureKeys, FeatureValueFor, FieldKeys, FieldKeysForStep, FieldValueFor, KnownActionKeys, KnownExperienceKeys, KnownFeatureKeys, KnownFieldKeys, KnownStepKeys, PickedSchemaRegistry, ReferencedContent, StepContentModelsMap, StepKeys, StepLayoutContent, TypedContent, } from './schema-registry';
|
|
25
25
|
export { toTypedContentStore } from './typed-content';
|
|
26
26
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,cAAc,WAAW,CAAA;AACzB,mBAAmB,UAAU,CAAA;AAC7B,mBAAmB,0BAA0B,CAAA;AAC7C,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,8BAA8B,CAAA;AACjD,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,oCAAoC,CAAA;AACvD,mBAAmB,oDAAoD,CAAA;AACvE,mBAAmB,kCAAkC,CAAA;AACrD,mBAAmB,sBAAsB,CAAA;AACzC,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,wBAAwB,CAAA;AAC3C,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,0BAA0B,CAAA;AAC7C,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,gBAAgB,CAAA;AACnC,mBAAmB,uBAAuB,CAAA;AAC1C,mBAAmB,8BAA8B,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,YAAY,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,QAAQ,EACR,iBAAiB,EACjB,YAAY,GACb,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,cAAc,WAAW,CAAA;AACzB,mBAAmB,UAAU,CAAA;AAC7B,mBAAmB,0BAA0B,CAAA;AAC7C,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,8BAA8B,CAAA;AACjD,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,oCAAoC,CAAA;AACvD,mBAAmB,oDAAoD,CAAA;AACvE,mBAAmB,kCAAkC,CAAA;AACrD,mBAAmB,sBAAsB,CAAA;AACzC,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,wBAAwB,CAAA;AAC3C,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,0BAA0B,CAAA;AAC7C,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,gBAAgB,CAAA;AACnC,mBAAmB,uBAAuB,CAAA;AAC1C,mBAAmB,8BAA8B,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,YAAY,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACX,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,QAAQ,EACR,iBAAiB,EACjB,YAAY,GACb,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA"}
|
package/dist/types/picked.d.ts
CHANGED
|
@@ -173,7 +173,7 @@ export declare class Picked {
|
|
|
173
173
|
getByName: (name: string) => Store<import(".").FeatureIndexItem | undefined>;
|
|
174
174
|
listStore: Store<import(".").FeatureBucket[]>;
|
|
175
175
|
getEvokableFeature: (featureName: string) => Store<() => Promise<import(".").FeatureBucketValue>>;
|
|
176
|
-
getFeatureResult: (featureName:
|
|
176
|
+
getFeatureResult: <K extends string>(featureName: K) => Store<import(".").FeatureResult<import("./schema-registry").FeatureValueFor<K>> | undefined>;
|
|
177
177
|
};
|
|
178
178
|
readonly conditions: {
|
|
179
179
|
register: (condition: Condition) => void;
|
|
@@ -24,9 +24,14 @@ export declare function fnv1a32(input: string): number;
|
|
|
24
24
|
* publishing a new version re-runs `decide` against the current weight and an
|
|
25
25
|
* unchanged weight keeps the same audience.
|
|
26
26
|
*
|
|
27
|
+
* This is the boolean path's input. A multi-variant feature does not roll once
|
|
28
|
+
* and look the result up; {@link allocateVariant} scores each arm on its own
|
|
29
|
+
* seed. One roll per feature is still what keeps features independent of one
|
|
30
|
+
* another, per ADR-0018.
|
|
31
|
+
*
|
|
27
32
|
* @param {string} sessionId - Current session id, stable across page loads.
|
|
28
33
|
* @param {string} featureId - Feature the roll is for.
|
|
29
|
-
* @returns {number} A value in the range 0
|
|
34
|
+
* @returns {number} A value in the range 0 to 99.
|
|
30
35
|
* @example
|
|
31
36
|
* ```typescript
|
|
32
37
|
* const diceRoll = featureDiceRoll('sess_1', 'winter-sale')
|
|
@@ -50,4 +55,51 @@ export declare function featureDiceRoll(sessionId: string, featureId: string): n
|
|
|
50
55
|
* ```
|
|
51
56
|
*/
|
|
52
57
|
export declare function isAllocated(diceRoll: number, weight: number): boolean;
|
|
58
|
+
/** The fields {@link allocateVariant} needs from an arm. */
|
|
59
|
+
export interface AllocatableVariant {
|
|
60
|
+
key: string;
|
|
61
|
+
weight: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Picks the arm a session lands in, scoring each arm independently.
|
|
65
|
+
*
|
|
66
|
+
* Each arm is hashed on its own key and scored against its own weight, and the
|
|
67
|
+
* highest score wins. An arm's score therefore depends on nothing but the
|
|
68
|
+
* session, the feature and that arm's own weight, which is what gives the two
|
|
69
|
+
* properties an author is entitled to assume:
|
|
70
|
+
*
|
|
71
|
+
* - Editing one arm's weight moves sessions only into or out of that arm. Two
|
|
72
|
+
* arms the author did not touch never trade sessions with each other.
|
|
73
|
+
* - Widening an arm only admits sessions and never evicts one already inside,
|
|
74
|
+
* the same ratchet {@link isAllocated} gives the boolean path. Narrowing is
|
|
75
|
+
* its mirror.
|
|
76
|
+
*
|
|
77
|
+
* Neither holds for cumulative ranges, where an arm's interval depends on every
|
|
78
|
+
* arm ordered before it, so a boundary shift upstream drags unrelated arms
|
|
79
|
+
* across it.
|
|
80
|
+
*
|
|
81
|
+
* Scores are compared, never summed, so the result is invariant under scaling
|
|
82
|
+
* every weight by the same factor: `1:1:2` and `10:10:20` allocate identically.
|
|
83
|
+
* Ties break on the key so that the arms' arrival order cannot matter — nothing
|
|
84
|
+
* in the delivery pipeline promises one.
|
|
85
|
+
*
|
|
86
|
+
* The chosen arm is returned whole rather than by key, so a caller reading its
|
|
87
|
+
* value needs no second lookup — and cannot resolve a duplicate key to a
|
|
88
|
+
* different arm than the one scored.
|
|
89
|
+
*
|
|
90
|
+
* @template T
|
|
91
|
+
* @param {string} sessionId - Current session id, stable across page loads.
|
|
92
|
+
* @param {string} featureKey - Feature being allocated.
|
|
93
|
+
* @param {readonly T[]} variants - The feature's arms, in any order.
|
|
94
|
+
* @returns {T | null} The chosen arm, or null when the set decides nobody.
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* // 'hero-b' takes three sessions in four; 'false' takes the fourth.
|
|
98
|
+
* allocateVariant('sess_1', 'hero', [
|
|
99
|
+
* { key: 'hero-b', weight: 3 },
|
|
100
|
+
* { key: 'false', weight: 1 },
|
|
101
|
+
* ])
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare function allocateVariant<T extends AllocatableVariant>(sessionId: string, featureKey: string, variants: readonly T[]): T | null;
|
|
53
105
|
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../../src/plugins/dynamic-experiences/hash.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAEtF;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAY7C;AA8BD
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../../src/plugins/dynamic-experiences/hash.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAEtF;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAY7C;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAUrE;AAED,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf;AA4CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,kBAAkB,EAC1D,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAmBV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../src/plugins/dynamic-experiences/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,aAAa,EAGd,MAAM,6BAA6B,CAAA;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAE1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAK1C,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AA+J5E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACjD,IAAI,CAMN;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../src/plugins/dynamic-experiences/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,aAAa,EAGd,MAAM,6BAA6B,CAAA;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAE1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAK1C,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AA+J5E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACjD,IAAI,CAMN;AA+ND;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CA4DjD"}
|