@avsbhq/snippet-types 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AvsB Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @avsbhq/snippet-types
2
+
3
+ TypeScript types for `window.avsb`, the A vs B web-snippet API.
4
+
5
+ Types only. Nothing is bundled, nothing runs, and your production build is
6
+ unchanged: the snippet itself arrives from the A vs B CDN through the install tag
7
+ you paste into your site.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install --save-dev @avsbhq/snippet-types
13
+ ```
14
+
15
+ ## Use it
16
+
17
+ These are global declarations, so add the package to `types` in your
18
+ `tsconfig.json` rather than importing it:
19
+
20
+ ```json
21
+ {
22
+ "compilerOptions": {
23
+ "types": ["@avsbhq/snippet-types"]
24
+ }
25
+ }
26
+ ```
27
+
28
+ If you would rather not touch `types` (which replaces the default "include every
29
+ installed type package" behaviour), reference it from any `.d.ts` in your project
30
+ instead:
31
+
32
+ ```ts
33
+ // docs-example: not typechecked here, because the A vs B repository does not install @avsbhq/snippet-types as a dependency, so the type reference has nothing to resolve against
34
+
35
+ /// <reference types="@avsbhq/snippet-types" />
36
+ ```
37
+
38
+ Either way, `avsb` and `window.avsb` are typed everywhere, and every shape has a
39
+ name you can use:
40
+
41
+ ```ts
42
+ avsb.ready?.(() => {
43
+ avsb.track.event('signup_completed', { revenue: 49.99 });
44
+
45
+ const variation: string | null = avsb.getVariation('exp_id_from_the_dashboard');
46
+ if (variation) console.log('running', variation, 'on snippet', avsb.version);
47
+ });
48
+
49
+ function reportOrder(order: AvsbPurchaseOrder): void {
50
+ avsb.track.purchase(order);
51
+ }
52
+
53
+ const unsubscribe = avsb.on?.('event', (record: AvsbIntegrationRecord) => {
54
+ if (record.event_type === 'exposure') myAnalytics.track('AB exposure', { ...record });
55
+ });
56
+ unsubscribe?.();
57
+ ```
58
+
59
+ `ready`, `on` and `consent` are optional on `AvsbApi` because the preview bypass
60
+ runtime does not define them, so reach them with `?.` as above.
61
+
62
+ `getVisitorId()` is typed `string | null`, because there is no visitor id before
63
+ `avsb.init()` in consent mode, and none at all for a visitor who denied
64
+ analytics:
65
+
66
+ ```ts
67
+ const visitorId: string | null = avsb.getVisitorId();
68
+ if (visitorId) sendToYourBackend(visitorId);
69
+ ```
70
+
71
+ ## The names
72
+
73
+ `AvsbApi` is the type of `window.avsb`. The shapes it uses are all named too:
74
+ `AvsbTrackFunction`, `AvsbTrackEventProperties`, `AvsbPurchaseOrder`,
75
+ `AvsbPurchaseItem`, `AvsbIntegrationRecord`, `AvsbIntegrationEventType`,
76
+ `AvsbConsentApi`, `AvsbConsentState`, `AvsbDatasetHandle`,
77
+ `AvsbDatasetGetResult`, `AvsbRecsClient`, `AvsbRecsRequest`, `AvsbRecsResult`,
78
+ `AvsbRecProduct`, `AvsbCommerceApi`, `AvsbContractProduct`, `AvsbContractCart`,
79
+ `AvsbContractCartLine`, `AvsbUtils`, `AvsbWaitUntilFn`, `AvsbWaitTarget`,
80
+ `AvsbWaitOptions`, `AvsbGuardedThenable`, `AvsbCookieApi`, `AvsbKVStore`,
81
+ `AvsbElementProps`.
82
+
83
+ Every one of them is global, so no import is needed, and every one is stable:
84
+ names here are only added, never renamed or removed.
85
+
86
+ ## Writing variation, trigger, or project code
87
+
88
+ Code written inside the A vs B editor gets an `options` toolkit and the
89
+ `initVariation` / `initTrigger` / `initProject` entry points. Those are authoring
90
+ globals, so they live behind a subpath and stay out of your site's global scope:
91
+
92
+ ```json
93
+ {
94
+ "compilerOptions": {
95
+ "types": ["@avsbhq/snippet-types", "@avsbhq/snippet-types/variation-code"]
96
+ }
97
+ }
98
+ ```
99
+
100
+ ```ts
101
+ function initVariation(options: AvsbVariationOptions): void {
102
+ options.waitUntil<HTMLElement>('.hero h1').then((el) => {
103
+ options.utils.setText(el, 'A clearer headline');
104
+ options.track.event('hero_seen');
105
+ });
106
+ }
107
+ ```
108
+
109
+ The A vs B CLI (`avsb clone`) already writes this configuration for you when it
110
+ scaffolds a local experiment project.
111
+
112
+ ## Calling the API before the snippet has loaded
113
+
114
+ The loader tag is `async`, so the API can arrive after your own code runs. The
115
+ install tag defines `avsb.ready`, `avsb.on`, `avsb.consent.set` and the four
116
+ `avsb.track.*` methods from the first byte of the page, so those calls are queued
117
+ and replayed. Everything else belongs inside `avsb.ready(...)`.
118
+
119
+ ## Versioning
120
+
121
+ Published in lockstep with the rest of the `@avsbhq/*` family. The declarations
122
+ mirror the snippet runtime's own types, and a test in the A vs B repository fails
123
+ if the two drift apart.
124
+
125
+ ## Links
126
+
127
+ - [Snippet SDK API reference](https://avsb.cloud/docs/developer-reference/snippet-sdk-api)
128
+ - [Tracking events](https://avsb.cloud/docs/developer-reference/tracking-events)
129
+ - [Debug mode](https://avsb.cloud/docs/troubleshooting/debug-mode)
package/avsb.d.ts ADDED
@@ -0,0 +1,413 @@
1
+ // TypeScript declarations for `window.avsb`, the A vs B web-snippet API.
2
+ //
3
+ // SOURCE OF TRUTH for two consumers: this package (`@avsbhq/snippet-types`), and
4
+ // the dashboard code editor's ambient lib at `snippet/types/avsb.d.ts`, which is
5
+ // a generated copy (run `npm run sync:types` here after editing; `sync.test.ts`
6
+ // fails if it drifts).
7
+ //
8
+ // Every shape mirrors the runtime's own types in `snippet/src/types.ts` (plus
9
+ // `utils/types.ts`, `purchase.ts`, `datasets.ts`, `recs.ts`, `commerceTypes.ts`),
10
+ // and `sync.test.ts` asserts that `AvsbApi` and `AvsbTrackFunction` carry exactly
11
+ // the members of `AvsbPublicAPI` and `TrackFunction` there, so an API added to the
12
+ // snippet cannot quietly go missing here.
13
+ //
14
+ // Two rules for editing:
15
+ //
16
+ // - Keep every interface member on ONE line. The sync test reads members by line.
17
+ // - Never add an `import` or `export`. That turns the file into a module, which
18
+ // hides every name below and stops `interface Window` merging with the
19
+ // browser's own, so consumers following the documented setup get nothing.
20
+ // Names are FROZEN at launch: rename nothing, add only.
21
+
22
+ // --- Tracking ---
23
+
24
+ interface AvsbTrackEventProperties {
25
+ /** Monetary amount for this event. Feeds revenue metrics. Plain number, no currency symbol. */
26
+ revenue?: number
27
+ /** Continuous measurement (order value, load time, scroll depth) for percentile and value metrics. */
28
+ value?: number
29
+ }
30
+
31
+ interface AvsbPurchaseItem {
32
+ sku: string
33
+ /** Parent product id when `sku` is a variant. Defaults to `sku` server-side. */
34
+ productKey?: string
35
+ name?: string
36
+ price?: number
37
+ quantity?: number
38
+ category?: string
39
+ }
40
+
41
+ /** An order passed to `avsb.track.purchase`. Money is decimal, in the project currency. */
42
+ interface AvsbPurchaseOrder {
43
+ /** Unique order id. Required. */
44
+ orderId: string
45
+ /** Order total as a decimal, e.g. 99.95. Required. */
46
+ total: number
47
+ /** ISO 4217 code. Defaults to the project currency. */
48
+ currency?: string
49
+ subtotal?: number
50
+ shipping?: number
51
+ tax?: number
52
+ discount?: number
53
+ coupon?: string
54
+ /** Stored but excluded from every calculation. For testing your integration. */
55
+ test?: boolean
56
+ items?: AvsbPurchaseItem[]
57
+ }
58
+
59
+ interface AvsbTrackFunction {
60
+ /** Fire a custom event by its metric event key. Numbers are coerced to strings, so a numeric key works when the metric's key is those digits. An unmatched key warns and lists the keys that do work. */
61
+ event(eventKey: string | number, properties?: AvsbTrackEventProperties): void
62
+ /** Record a visitor attribute you can slice results by. */
63
+ segment(segmentKey: string, segmentValue: string): void
64
+ /** Send an order immediately. Not batched. */
65
+ purchase(order: AvsbPurchaseOrder): void
66
+ /** Tell the snippet the current cart total, for commerce audience conditions. `totalMinor` wins when both are set. */
67
+ cart(cart: { total?: number; totalMinor?: number }): void
68
+ }
69
+
70
+ // --- Event bus record ---
71
+
72
+ type AvsbIntegrationEventType =
73
+ | 'exposure'
74
+ | 'goal'
75
+ | 'purchase'
76
+ | 'rec_impression'
77
+ | 'rec_click'
78
+ | 'custom'
79
+ | 'test'
80
+
81
+ /**
82
+ * The record delivered to `avsb.on('event')` subscribers, one per dispatched
83
+ * exposure, goal, purchase, or rec event. snake_case, shaped for analytics tools.
84
+ */
85
+ interface AvsbIntegrationRecord {
86
+ /** Experiment shortId as a string. */
87
+ experiment_id: string
88
+ /** Display name; falls back to the shortId, never an empty string. */
89
+ experiment_name: string
90
+ /** Variation shortId as a string. */
91
+ variation_id: string
92
+ /** Display name; falls back to the shortId, never an empty string. */
93
+ variation_name: string
94
+ event_type: AvsbIntegrationEventType
95
+ /** Rendered event name (native defaults plus your configured overrides). */
96
+ event_name: string
97
+ /** UUID; matches the AvsB event id for tracker-queued events. */
98
+ event_id: string
99
+ revenue: number | null
100
+ value: number | null
101
+ /** Purchases only. */
102
+ currency?: string
103
+ /** Purchases only: the order id. */
104
+ transaction_id?: string
105
+ /** Goals and custom events: metric `type:shortId` or the developer eventKey. */
106
+ goal_key?: string
107
+ visitor_id: string
108
+ page_url: string
109
+ timestamp: number
110
+ sdk_version: string
111
+ /** Recommendation product data (recipe, items, position, ...). */
112
+ attributes?: Record<string, string | number>
113
+ /** Set to 'preview' or 'internal' on QA traffic; absent for live visitors. */
114
+ source?: 'preview' | 'internal'
115
+ }
116
+
117
+ // --- Waiting ---
118
+
119
+ // A wait target: a CSS selector, a `window.x.y` global path, or a predicate.
120
+ type AvsbWaitTarget<T = unknown> = string | (() => T)
121
+
122
+ interface AvsbWaitOptions {
123
+ /** Milliseconds before the wait rejects. Default 10000. */
124
+ timeout?: number
125
+ /** Resolve a selector with an array of all matches (querySelectorAll). */
126
+ all?: boolean
127
+ }
128
+
129
+ // Promise-like whose `.catch()` is OPTIONAL: a failed wait logs gracefully rather
130
+ // than raising an unhandled rejection. `.stop()` cancels it.
131
+ interface AvsbGuardedThenable<T> extends PromiseLike<T> {
132
+ then<R1 = T, R2 = never>(
133
+ onFulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null,
134
+ onRejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null
135
+ ): AvsbGuardedThenable<R1 | R2>
136
+ catch<R = never>(
137
+ onRejected?: ((reason: unknown) => R | PromiseLike<R>) | null
138
+ ): AvsbGuardedThenable<T | R>
139
+ finally(onFinally?: (() => void) | null): AvsbGuardedThenable<T>
140
+ stop(): void
141
+ }
142
+
143
+ interface AvsbWaitUntilFn {
144
+ /** Wait for a selector / `window.x.y` path / predicate to resolve. */
145
+ <T = unknown>(target: AvsbWaitTarget<T>, opts?: AvsbWaitOptions): AvsbGuardedThenable<T>
146
+ /** Wait for several targets at once; resolves with an array of results. */
147
+ <T extends unknown[]>(targets: AvsbWaitTarget[], opts?: AvsbWaitOptions): AvsbGuardedThenable<T>
148
+ }
149
+
150
+ // --- Consent ---
151
+
152
+ /** Per-category consent flags. An absent key means "not expressed" (allowed). */
153
+ interface AvsbConsentState {
154
+ analytics?: boolean
155
+ marketing?: boolean
156
+ }
157
+
158
+ /**
159
+ * Visitor consent, the hook your cookie banner calls. `set` persists for 1 year in
160
+ * the first-party `avsb_consent` cookie and wins over the inferred Google signal.
161
+ */
162
+ interface AvsbConsentApi {
163
+ set(state: AvsbConsentState): void
164
+ get(): AvsbConsentState
165
+ }
166
+
167
+ // --- Datasets ---
168
+
169
+ interface AvsbDatasetGetResult {
170
+ found: boolean
171
+ /** FEED only: the row's ordered items array. */
172
+ items?: unknown[]
173
+ /** TABLE only: the full row object. */
174
+ row?: unknown
175
+ /** LIST only: whether the key is a member of the list. */
176
+ member?: boolean
177
+ meta: { slug: string; version: number | null }
178
+ }
179
+
180
+ interface AvsbDatasetHandle {
181
+ get(key: string): Promise<AvsbDatasetGetResult>
182
+ has(key: string): Promise<boolean>
183
+ }
184
+
185
+ // --- Recommendations ---
186
+
187
+ interface AvsbRecProduct {
188
+ id: string
189
+ title?: string
190
+ image?: string
191
+ href?: string
192
+ category?: string
193
+ /** MINOR units (ISO-4217). */
194
+ price?: number
195
+ /** MINOR units. */
196
+ compareAtPrice?: number
197
+ currency?: string
198
+ availability?: 'in_stock' | 'out_of_stock' | 'removed'
199
+ }
200
+
201
+ interface AvsbRecsRequest {
202
+ recipe: string
203
+ context?: { seed?: string; seeds?: string[]; recentlyViewed?: number }
204
+ maxItems?: number
205
+ excludeOutOfStock?: boolean
206
+ /** Which rec surface this call serves, for multi-surface pages. */
207
+ surface?: string
208
+ }
209
+
210
+ interface AvsbRecsResult {
211
+ items: AvsbRecProduct[]
212
+ served: boolean
213
+ recipe: string
214
+ /** Slug of the chain step that served, null on a miss. */
215
+ source: string | null
216
+ fallbackStep: number | null
217
+ /** True when the resolve failed (timeout, network, HTTP, parse). */
218
+ degraded: boolean
219
+ }
220
+
221
+ interface AvsbRecsClient {
222
+ /** Resolve items for a recipe. Auto-fires rec:impression on success. Never throws. */
223
+ get(req: AvsbRecsRequest): Promise<AvsbRecsResult>
224
+ /** Fire a rec:click for a product the visitor clicked. */
225
+ trackClick(productId: string, opts?: { position?: number }): void
226
+ /** Record a product view (feeds recently-viewed and commerce audiences). */
227
+ trackView(sku: string, meta?: { category?: string }): void
228
+ }
229
+
230
+ // --- Commerce data contract ---
231
+
232
+ interface AvsbContractProduct {
233
+ /** Required. The merchant's product id. */
234
+ sku: string
235
+ /** Product-grain canonical key. Defaults to `sku`. */
236
+ productKey?: string
237
+ title?: string
238
+ /** Major units as displayed. */
239
+ price?: number
240
+ /** Already-minor integer. Wins over `price`. */
241
+ priceMinor?: number
242
+ compareAtPrice?: number
243
+ compareAtPriceMinor?: number
244
+ currency?: string
245
+ image?: string
246
+ url?: string
247
+ brand?: string
248
+ category?: string
249
+ categories?: string[]
250
+ availability?: 'in_stock' | 'out_of_stock' | 'preorder'
251
+ stock?: number
252
+ variant?: { variantSku: string; options?: Record<string, string>; price?: number; priceMinor?: number; stock?: number }
253
+ customFields?: Record<string, string | number | boolean>
254
+ /** ms-epoch product createdAt. */
255
+ createdAt?: number
256
+ }
257
+
258
+ interface AvsbContractCartLine {
259
+ sku: string
260
+ productKey?: string
261
+ quantity: number
262
+ price?: number
263
+ priceMinor?: number
264
+ variantSku?: string
265
+ }
266
+
267
+ interface AvsbContractCart {
268
+ items: AvsbContractCartLine[]
269
+ total?: number
270
+ totalMinor?: number
271
+ currency?: string
272
+ }
273
+
274
+ interface AvsbCommerceApi {
275
+ productView(p: AvsbContractProduct): void
276
+ /** Category / collection impressions. */
277
+ productList(items: AvsbContractProduct[], meta?: { listId?: string }): void
278
+ addToCart(line: AvsbContractCartLine): void
279
+ removeFromCart(line: AvsbContractCartLine): void
280
+ /** Full cart snapshot. */
281
+ cart(cart: AvsbContractCart): void
282
+ checkoutStep(step: { step: number | string; cart?: AvsbContractCart }): void
283
+ search(q: { query: string; resultCount?: number; skus?: string[] }): void
284
+ categoryView(c: { category: string; skus?: string[] }): void
285
+ wishlist(w: { sku: string; action: 'add' | 'remove' }): void
286
+ }
287
+
288
+ // --- Utils toolkit (avsb.utils.* and options.utils.*) ---
289
+ interface AvsbCookieApi {
290
+ get(name: string): string | null
291
+ set(name: string, value: string, opts?: { days?: number; path?: string; domain?: string; sameSite?: 'Lax' | 'Strict' | 'None'; secure?: boolean }): void
292
+ remove(name: string): void
293
+ }
294
+
295
+ interface AvsbKVStore {
296
+ get<T = unknown>(key: string): T | null
297
+ set(key: string, value: unknown): void
298
+ remove(key: string): void
299
+ }
300
+
301
+ interface AvsbElementProps {
302
+ class?: string
303
+ id?: string
304
+ text?: string
305
+ html?: string
306
+ attrs?: Record<string, string>
307
+ style?: Record<string, string>
308
+ on?: Record<string, EventListener>
309
+ }
310
+
311
+ interface AvsbUtils {
312
+ /** Guarded, promise-based wait. `.catch()` optional; failures log gracefully. */
313
+ waitUntil: AvsbWaitUntilFn
314
+ /** Alias of waitUntil (Qubit `poll` compatibility). */
315
+ poll: AvsbWaitUntilFn
316
+ /** Resolve once a single selector is present. */
317
+ waitForElement(selector: string, opts?: { timeout?: number; root?: ParentNode }): AvsbGuardedThenable<Element>
318
+ /** Run a callback for each matching element, now and on future mutations. Supports shadow roots via `root`. */
319
+ onMutation(selector: string, callback: (el: Element) => void, opts?: { root?: ParentNode; once?: boolean; attributes?: boolean }): { stop(): void }
320
+ once<F extends (...args: never[]) => unknown>(fn: F): F
321
+ throttle<F extends (...args: never[]) => void>(fn: F, ms: number): F & { cancel(): void }
322
+ debounce<F extends (...args: never[]) => void>(fn: F, ms: number): F & { cancel(): void }
323
+ raf(fn: FrameRequestCallback): { cancel(): void }
324
+ idle(fn: () => void, opts?: { timeout?: number }): { cancel(): void }
325
+ domReady(): AvsbGuardedThenable<void>
326
+ retry<T>(fn: () => Promise<T> | T, opts?: { attempts?: number; delay?: number; factor?: number }): Promise<T>
327
+ $(selector: string, root?: ParentNode): Element | null
328
+ $$(selector: string, root?: ParentNode): Element[]
329
+ createElement(tag: string, props?: AvsbElementProps, children?: (Node | string)[]): HTMLElement
330
+ insertAfter(newNode: Node, ref: Node): void
331
+ insertBefore(newNode: Node, ref: Node): void
332
+ wrap(node: Node, wrapper: HTMLElement): void
333
+ remove(node: Node): void
334
+ setText(node: Node, text: string): void
335
+ /** Sets innerHTML; scrubs scripts and handlers unless `{ raw: true }`. */
336
+ setHtml(node: Element, html: string, opts?: { raw?: boolean }): void
337
+ /** Direct listener, or (with a selector string target) a delegated listener that also catches future nodes. */
338
+ on(target: EventTarget | string, type: string, handler: (event: Event, matched?: Element) => void, opts?: AddEventListenerOptions): { off(): void }
339
+ /** Fires on client-side route changes. */
340
+ onUrlChange(callback: (url: string) => void): { off(): void }
341
+ cookie: AvsbCookieApi
342
+ query: { get(name: string): string | null; getAll(): Record<string, string> }
343
+ storage: { local: AvsbKVStore; session: AvsbKVStore }
344
+ /** Share values between triggers.js and variation.js of the same experiment. */
345
+ state: { get<T = unknown>(key: string): T | undefined; set(key: string, value: unknown): void }
346
+ /** Scoped logger: debug/info emit only in debug, preview, or dev mode; warn always. */
347
+ log: { debug(...args: unknown[]): void; info(...args: unknown[]): void; warn(...args: unknown[]): void }
348
+ }
349
+
350
+ // --- Experiment decisions ---
351
+
352
+ /** Why an experiment did or did not run for this visit. `variant` / `control` mean the visitor is IN the test. */
353
+ type AvsbDecisionStatus = 'variant' | 'control' | 'trigger-pending' | 'not-targeted' | 'audience-mismatch' | 'excluded' | 'traffic-holdout' | 'capped' | 'scheduled-off' | 'error'
354
+
355
+ /** One row of `avsb.getExperimentDecisions()`. The variation fields are present only when a variation was assigned. */
356
+ interface AvsbExperimentDecision {
357
+ experimentId: string
358
+ status: AvsbDecisionStatus
359
+ variationId?: string
360
+ variationName?: string
361
+ }
362
+
363
+ // --- The global API ---
364
+
365
+ interface AvsbApi {
366
+ /** Consent mode only: start the snippet once the visitor has agreed. No effect otherwise. */
367
+ init(): void | Promise<void>
368
+ /** Stop everything and forget the visitor: tracking off, timers and listeners cleared, variations reverted, visitor cookie expired. */
369
+ disable(): void
370
+ track: AvsbTrackFunction
371
+ /** The visitor's id, or null when there isn't one (before `init()` in consent mode, or for a visitor who denied analytics). */
372
+ getVisitorId(): string | null
373
+ /** Replace the visitor id to stitch a browser onto a logged-in account. Drops sticky assignments and re-evaluates, so the variation can change. Returns false for an unusable id. */
374
+ setVisitorId(visitorId: string): boolean
375
+ /** The variation this visitor is assigned in the experiment, or null. Pass the numeric **Experiment ID** from the experiment details panel (a number or its string form); the value returned is that variation's numeric **Variation ID**. Internal UUIDs are not accepted. */
376
+ getVariation(experimentId: string | number): string | null
377
+ /** Force a variation, for QA, by the numeric **Experiment ID** and **Variation ID** shown in the experiment details panel. `{ track: false }` applies it without recording an exposure. */
378
+ forceVariation(experimentId: string | number, variationId: string | number, options?: { track?: boolean }): boolean
379
+ waitUntil: AvsbWaitUntilFn
380
+ utils: AvsbUtils
381
+ /** Subscribe to every experiment event. Returns an unsubscribe function. Absent in the preview bypass runtime. */
382
+ on?(event: 'event', callback: (record: AvsbIntegrationRecord) => void): () => void
383
+ /** Run a callback once experiments have been evaluated. Safe to call before the snippet loads. */
384
+ ready?(callback: () => void): void
385
+ /** @deprecated Use `ready`. Same machinery. */
386
+ onReady(callback: () => void): void
387
+ /** Visitor consent: `set` from your banner, `get` for the effective state. Absent in the preview bypass runtime. */
388
+ consent?: AvsbConsentApi
389
+ /** Experiments this visitor is active in on this page, as the same numeric dashboard ids `getVariation` takes, in string form. */
390
+ getActiveExperiments(): Array<{ experimentId: string; variationId: string }>
391
+ /** One row per experiment on the page: what happened to this visit and why (held out, mismatched, pending, running). Rebuilt on every evaluation; empty before evaluation and after `disable()`. Optional: absent on snippet builds that predate it, so read it as `avsb.getExperimentDecisions?.()`. */
392
+ getExperimentDecisions?(): AvsbExperimentDecision[]
393
+ /** Read a server-side dataset. Never throws: unknown slugs resolve `{ found: false }`. */
394
+ dataset(slug: string): AvsbDatasetHandle
395
+ /** Headless recommendations. Inside variation code prefer `options.recs`, which attributes automatically. */
396
+ recs: AvsbRecsClient
397
+ /** Commerce events. Buffered until the commerce chunk loads. */
398
+ commerce: AvsbCommerceApi
399
+ /** Leave a shared preview link: clears the preview token and reloads. */
400
+ exitPreview(): void
401
+ /** Re-run every experiment against the page as it is now. For app-driven navigation the History API never sees. */
402
+ refresh(): void
403
+ /** The snippet build running on this page, e.g. `1.1.0`. */
404
+ version: string
405
+ }
406
+
407
+ /** `window.avsb`. `q` is the install tag's early-call queue, drained by the runtime. */
408
+ interface Window {
409
+ avsb: AvsbApi & { q?: Array<[string, ...unknown[]]> }
410
+ }
411
+
412
+ /** The same object as `window.avsb`, so `avsb.track.event(...)` reads cleanly. */
413
+ declare const avsb: AvsbApi
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@avsbhq/snippet-types",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript declarations for window.avsb, the A vs B web-snippet API",
5
+ "keywords": [
6
+ "avsb",
7
+ "ab-testing",
8
+ "experiments",
9
+ "types",
10
+ "typescript",
11
+ "snippet"
12
+ ],
13
+ "license": "MIT",
14
+ "sideEffects": false,
15
+ "types": "./avsb.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./avsb.d.ts"
19
+ },
20
+ "./variation-code": {
21
+ "types": "./variation-code.d.ts"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "files": [
26
+ "avsb.d.ts",
27
+ "variation-code.d.ts"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/avsbhq/a-vs-b.git",
35
+ "directory": "packages/avsb-snippet-types"
36
+ },
37
+ "homepage": "https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-snippet-types#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/avsbhq/a-vs-b/issues"
40
+ },
41
+ "scripts": {
42
+ "sync:types": "node ../../snippet/scripts/syncSnippetTypes.mjs",
43
+ "check:types": "node ../../snippet/scripts/syncSnippetTypes.mjs --check"
44
+ }
45
+ }
@@ -0,0 +1,72 @@
1
+ // TypeScript declarations for code you write INSIDE A vs B: variation code,
2
+ // trigger code, and project code. Companion to `avsb.d.ts`, which types the
3
+ // `window.avsb` global and must be loaded alongside this file.
4
+ //
5
+ // Kept separate because these are authoring globals (`options`, `activate`,
6
+ // `initVariation`, ...). A site consuming `@avsbhq/snippet-types` for
7
+ // `window.avsb` should not have them in its global scope, so they live behind
8
+ // the `@avsbhq/snippet-types/variation-code` subpath.
9
+ //
10
+ // SOURCE OF TRUTH. The dashboard code editor's copy at
11
+ // `snippet/types/variation-code.d.ts` is generated: run `npm run sync:types` in
12
+ // this package after editing. Mirrors `HarnessOptions` in
13
+ // `snippet/src/userCodeHarness.ts`.
14
+ //
15
+ // Not a module, for the same reason as `avsb.d.ts`: these are global names, and
16
+ // an `export` would hide all of them. Frozen at launch: rename nothing, add only.
17
+
18
+ /** Recommendations client pre-bound to the running experiment and variation. */
19
+ interface AvsbBoundRecsClient extends AvsbRecsClient {
20
+ /** Resolve THIS variation's bound recipe, so one code block serves every variation. */
21
+ getBound(req?: Omit<AvsbRecsRequest, 'recipe'>): Promise<AvsbRecsResult>
22
+ }
23
+
24
+ /** What every user-code surface receives. Self-cleaning helpers are torn down for you. */
25
+ interface AvsbCodeOptions {
26
+ /** Present in variation and trigger code. */
27
+ experimentId?: string
28
+ experimentName?: string
29
+ variationId?: string
30
+ variationName?: string
31
+ variationType?: 'control' | 'variant'
32
+ /** Present in project code only. */
33
+ projectId?: string
34
+ /** Runs when the variation is activated. In variation code it fires as soon as your function returns. */
35
+ onActivation(cb: () => void): void
36
+ /** Runs when the variation is removed (client-side navigation, teardown, forced switch). */
37
+ onRemove(cb: () => void): void
38
+ track: AvsbTrackFunction
39
+ /** Impressions and clicks attribute to this variation automatically. */
40
+ recs: AvsbBoundRecsClient
41
+ getVisitorId(): string | null
42
+ /** The visitor's numeric **Variation ID** in that experiment, or null. Takes the numeric **Experiment ID** from the experiment details panel, NOT the `experimentId` above (that one is the internal id). */
43
+ getVariation(experimentId: string | number): string | null
44
+ /** Experiments running on this page, as the same numeric dashboard ids. */
45
+ getActiveExperiments(): Array<{ experimentId: string; variationId: string }>
46
+ /** Force a variation by the numeric **Experiment ID** and **Variation ID** from the experiment details panel. */
47
+ forceVariation(experimentId: string | number, variationId: string | number, options?: { track?: boolean }): boolean
48
+ /** Cancelled automatically on teardown, unlike `avsb.waitUntil`. */
49
+ waitUntil: AvsbWaitUntilFn
50
+ utils: AvsbUtils
51
+ /** Same numeric handle as the browser's own. Cancelled for you on teardown. */
52
+ setTimeout(handler: () => void, ms?: number): number
53
+ setInterval(handler: () => void, ms?: number): number
54
+ addEventListener(target: EventTarget, type: string, handler: EventListenerOrEventListenerObject, opts?: AddEventListenerOptions | boolean): void
55
+ }
56
+
57
+ type AvsbTriggerOptions = AvsbCodeOptions
58
+ type AvsbVariationOptions = AvsbCodeOptions
59
+ type AvsbProjectOptions = AvsbCodeOptions
60
+
61
+ // Ambient declarations for standalone authoring files. They look unused to
62
+ // ESLint because nothing in this declaration file references them: they are the
63
+ // names your own trigger, variation, and project code calls into, so they must
64
+ // stay literal.
65
+ /* eslint-disable @typescript-eslint/no-unused-vars */
66
+ declare const options: AvsbCodeOptions
67
+ declare function activate(): void
68
+ declare function deactivate(): void
69
+ declare function initVariation(options: AvsbVariationOptions): void
70
+ declare function initTrigger(options: AvsbTriggerOptions, activate: () => void, deactivate: () => void): void
71
+ declare function initProject(options: AvsbProjectOptions): void
72
+ /* eslint-enable @typescript-eslint/no-unused-vars */