@picked/client 0.0.74 → 0.0.75

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 (36) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{api-DrR9JbZX.cjs → api-DnuqBb19.cjs} +1 -1
  3. package/dist/{api-DGFHHioX.js → api-qyDGH_LT.js} +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/explanation/feature-flag-allocation.md +43 -1
  8. package/dist/docs/how-to/read-a-feature-flag.md +22 -10
  9. package/dist/docs/reference/dashboard/features.mdx +45 -5
  10. package/dist/docs/reference/delivery-api/workspace-schema.md +12 -1
  11. package/dist/docs/reference/pickedql/dimensions.md +6 -0
  12. package/dist/docs/reference/pickedql/virtual-functions.md +16 -5
  13. package/dist/docs/reference/sdk/features.md +17 -6
  14. package/dist/docs/reference/sdk/type-generation.md +17 -1
  15. package/dist/esm/index.js +2 -2
  16. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  17. package/dist/esm/server/index.js +1 -1
  18. package/dist/plugin-BlE2cb8o.cjs +1 -0
  19. package/dist/{plugin-B-c2VAl2.js → plugin-_IjsFtkt.js} +44 -29
  20. package/dist/{provider-HpWMlxa0.js → provider-BPkok9kt.js} +1 -1
  21. package/dist/{provider-hQPGEy73.cjs → provider-b4PZ4JDf.cjs} +1 -1
  22. package/dist/types/entities/feature/feature.d.ts +10 -3
  23. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  24. package/dist/types/index.d.ts +1 -1
  25. package/dist/types/index.d.ts.map +1 -1
  26. package/dist/types/picked.d.ts +1 -1
  27. package/dist/types/plugins/dynamic-experiences/hash.d.ts +53 -1
  28. package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
  29. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  30. package/dist/types/schema-registry.d.ts +16 -0
  31. package/dist/types/schema-registry.d.ts.map +1 -1
  32. package/dist/{version-BJFZwX5G.js → version-BDPumbPI.js} +1 -1
  33. package/dist/version-D7F_nhve.cjs +1 -0
  34. package/package.json +2 -2
  35. package/dist/plugin-B8YrVbNq.cjs +0 -1
  36. package/dist/version-DzA5_Ptz.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 the feature flag, from 0 to 100. |
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, the ID of the feature
42
- flag, and the weight. Each feature flag gets its own value, so tests that run at the
43
- same time are independent. See
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 buckets
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('homepage_test', 'control') AS control,
220
- feature_users('homepage_test', 'variant') AS variant
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
- -- With feature key filter
224
- SELECT feature_users('homepage_test', 'control', 'v1') FROM features
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. It compares the stable value of the visitor for this feature flag against `weight`. |
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, the ID of the
32
- feature flag, and the weight. A feature flag that you control from the dashboard
33
- therefore cannot use the data of your application. Define the feature flag in SDK code
34
- when you need that.
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-BJFZwX5G.js";
9
+ import { t as ne } from "../version-BDPumbPI.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-HpWMlxa0.js").then((t) => t.createPickedDataProvider({
1777
+ let r = import("../provider-BPkok9kt.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-B-c2VAl2.js";
2
- import { n as t, t as n } from "../../../api-DGFHHioX.js";
1
+ import { t as e } from "../../../plugin-_IjsFtkt.js";
2
+ import { n as t, t as n } from "../../../api-qyDGH_LT.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 };
@@ -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-B-c2VAl2.js";
2
+ import { n as s } from "../../plugin-_IjsFtkt.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;
@@ -0,0 +1 @@
1
+ require("./logger-CSTvAyyB.cjs"),require("./api-DnuqBb19.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,5 +1,5 @@
1
1
  import "./logger-DMN6TxJJ.js";
2
- import "./api-DGFHHioX.js";
2
+ import "./api-qyDGH_LT.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 a(e, t) {
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 o(e, t) {
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 s(e) {
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 c(e, t) {
42
- if (Array.isArray(t.fields)) for (let n of t.fields) s(n) && e.registerField(n);
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 l(e, t) {
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 u(e, t) {
72
- if (Array.isArray(t.experiences)) for (let n of t.experiences) o(e, n);
86
+ function m(e, t) {
87
+ if (Array.isArray(t.experiences)) for (let n of t.experiences) u(e, n);
73
88
  }
74
- function d(e, t, n) {
75
- m(e, t, n.isPreview, n.sessionId), a(e, t), c(e, t), l(e, t), u(e, t);
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 f(e) {
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 p(e) {
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 m(e, t, n, r) {
91
- let i = p(e);
92
- for (let a of t.features) i.has(a.id) || h(e, a, n, r);
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 h(e, t, n, a) {
95
- let o = r(a, t.id);
96
- e.registerFeature(t.id, {
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 g(e, t) {
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 _(e, t) {
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 && g(e, n), 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 v(e, t) {
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 y() {
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 = f(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);
139
- if (t.contentLoader = _(t, v((e) => t.getExperienceContent(e, i), a)), !a) return;
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, d(t, a, {
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 { d as n, y as t };
172
+ export { h as n, C as t };
@@ -1,5 +1,5 @@
1
1
  import "./logger-DMN6TxJJ.js";
2
- import { n as e, t } from "./api-DGFHHioX.js";
2
+ import { n as e, t } from "./api-qyDGH_LT.js";
3
3
  import { r as n, t as r } from "./registry-CXZv0uLg.js";
4
4
  //#region src/data-source/provider.ts
5
5
  var i = "bundle", a = "api";
@@ -1 +1 @@
1
- require("./logger-CSTvAyyB.cjs");const e=require("./api-DrR9JbZX.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
+ require("./logger-CSTvAyyB.cjs");const e=require("./api-DnuqBb19.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
- export interface FeatureResult {
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: string | boolean;
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: string) => Store<FeatureResult | undefined>;
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":"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"}
1
+ {"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../../../src/entities/feature/feature.ts"],"names":[],"mappings":"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"}
@@ -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"}
@@ -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: string) => Store<import(".").FeatureResult | undefined>;
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–99.
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;;;;;;;;;;;;;;;;;;;;GAoBG;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"}
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;AAwMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CA4DjD"}
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"}