@avsbhq/cli 1.0.1 → 3.8.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/README.md +255 -114
- package/dist/cli.js +7565 -983
- package/dist/types/avsb.d.ts +413 -0
- package/dist/types/variation-code.d.ts +66 -0
- package/package.json +7 -3
|
@@ -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
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
/** What every user-code surface receives. Self-cleaning helpers are torn down for you. */
|
|
19
|
+
interface AvsbCodeOptions {
|
|
20
|
+
/** Present in variation and trigger code. */
|
|
21
|
+
experimentId?: string
|
|
22
|
+
experimentName?: string
|
|
23
|
+
variationId?: string
|
|
24
|
+
variationName?: string
|
|
25
|
+
variationType?: 'control' | 'variant'
|
|
26
|
+
/** Present in project code only. */
|
|
27
|
+
projectId?: string
|
|
28
|
+
/** Runs when the variation is activated. In variation code it fires as soon as your function returns. */
|
|
29
|
+
onActivation(cb: () => void): void
|
|
30
|
+
/** Runs when the variation is removed (client-side navigation, teardown, forced switch). */
|
|
31
|
+
onRemove(cb: () => void): void
|
|
32
|
+
track: AvsbTrackFunction
|
|
33
|
+
/** Impressions and clicks attribute to this variation automatically. */
|
|
34
|
+
recs: AvsbRecsClient
|
|
35
|
+
getVisitorId(): string | null
|
|
36
|
+
/** 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). */
|
|
37
|
+
getVariation(experimentId: string | number): string | null
|
|
38
|
+
/** Experiments running on this page, as the same numeric dashboard ids. */
|
|
39
|
+
getActiveExperiments(): Array<{ experimentId: string; variationId: string }>
|
|
40
|
+
/** Force a variation by the numeric **Experiment ID** and **Variation ID** from the experiment details panel. */
|
|
41
|
+
forceVariation(experimentId: string | number, variationId: string | number, options?: { track?: boolean }): boolean
|
|
42
|
+
/** Cancelled automatically on teardown, unlike `avsb.waitUntil`. */
|
|
43
|
+
waitUntil: AvsbWaitUntilFn
|
|
44
|
+
utils: AvsbUtils
|
|
45
|
+
/** Same numeric handle as the browser's own. Cancelled for you on teardown. */
|
|
46
|
+
setTimeout(handler: () => void, ms?: number): number
|
|
47
|
+
setInterval(handler: () => void, ms?: number): number
|
|
48
|
+
addEventListener(target: EventTarget, type: string, handler: EventListenerOrEventListenerObject, opts?: AddEventListenerOptions | boolean): void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type AvsbTriggerOptions = AvsbCodeOptions
|
|
52
|
+
type AvsbVariationOptions = AvsbCodeOptions
|
|
53
|
+
type AvsbProjectOptions = AvsbCodeOptions
|
|
54
|
+
|
|
55
|
+
// Ambient declarations for standalone authoring files. They look unused to
|
|
56
|
+
// ESLint because nothing in this declaration file references them: they are the
|
|
57
|
+
// names your own trigger, variation, and project code calls into, so they must
|
|
58
|
+
// stay literal.
|
|
59
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
60
|
+
declare const options: AvsbCodeOptions
|
|
61
|
+
declare function activate(): void
|
|
62
|
+
declare function deactivate(): void
|
|
63
|
+
declare function initVariation(options: AvsbVariationOptions): void
|
|
64
|
+
declare function initTrigger(options: AvsbTriggerOptions, activate: () => void, deactivate: () => void): void
|
|
65
|
+
declare function initProject(options: AvsbProjectOptions): void
|
|
66
|
+
/* eslint-enable @typescript-eslint/no-unused-vars */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avsbhq/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.8.1",
|
|
4
4
|
"description": "CLI tool for local A/B experiment development with the A vs B platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,14 +25,18 @@
|
|
|
25
25
|
},
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/
|
|
28
|
+
"url": "git+https://github.com/avsbhq/a-vs-b.git",
|
|
29
29
|
"directory": "cli"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/
|
|
31
|
+
"homepage": "https://github.com/avsbhq/a-vs-b/tree/main/cli#readme",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/avsbhq/a-vs-b/issues"
|
|
34
|
+
},
|
|
32
35
|
"scripts": {
|
|
33
36
|
"build": "node build.mjs",
|
|
34
37
|
"dev": "node --import tsx src/cli.ts",
|
|
35
38
|
"link": "npm link",
|
|
39
|
+
"test": "vitest run",
|
|
36
40
|
"prepublishOnly": "node build.mjs"
|
|
37
41
|
},
|
|
38
42
|
"dependencies": {
|