@abconvert/headless 0.0.0-stage → 0.1.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.
@@ -0,0 +1,302 @@
1
+ export type AssignmentType = 'price' | 'shipping' | 'theme' | 'template' | 'url_redirect' | 'checkout' | 'offer' | 'visual_editor' | 'combined' | 'personalization';
2
+ export type AssignmentReason = 'random_split' | 'force_assign_group' | 'preview_override' | 'url_force_assign';
3
+ /** Why a visitor was left out of a test, for the exclusions reported as test group -1. */
4
+ export type ReportedExclusion = 'audience_targeting_excluded' | 'force_assign_excluded';
5
+ /** What an assignment row names as its reason: a test group the visitor counts in, or an exclusion. */
6
+ export type ReportedReason = 'random_split' | 'force_assign_group' | ReportedExclusion;
7
+ export interface TestGroup {
8
+ index: number;
9
+ name: string;
10
+ control: boolean;
11
+ split: number;
12
+ /** A feature test's values for this test group; `{}` for every other test type. */
13
+ variables: Record<string, string | number | boolean>;
14
+ }
15
+ export interface Assignment {
16
+ experimentId: string;
17
+ experimentName: string;
18
+ type: AssignmentType;
19
+ status: 'active' | 'preview';
20
+ testGroup: TestGroup;
21
+ reason: AssignmentReason;
22
+ /** Feature tests only. */
23
+ featureKey?: string;
24
+ }
25
+ export interface Price {
26
+ experimentId: string;
27
+ testGroup: TestGroup;
28
+ amount: number;
29
+ compareAtAmount: number | null;
30
+ currency: string;
31
+ country: string;
32
+ productId: string;
33
+ variantId: string;
34
+ }
35
+ export interface PriceOptions {
36
+ country?: string;
37
+ aggregate?: 'min' | 'max' | 'first';
38
+ }
39
+ export interface ShippingRate {
40
+ experimentId: string;
41
+ testGroup: TestGroup;
42
+ zone: string;
43
+ name: string;
44
+ amount: number;
45
+ currency: string;
46
+ condition: {
47
+ type: 'price' | 'weight';
48
+ minimum: number | null;
49
+ maximum: number | null;
50
+ unit: string | null;
51
+ } | null;
52
+ }
53
+ export interface FreeShippingThreshold {
54
+ experimentId: string;
55
+ testGroup: TestGroup;
56
+ zone: string;
57
+ amount: number;
58
+ currency: string;
59
+ }
60
+ export interface ShippingOptions {
61
+ zone?: string;
62
+ }
63
+ export interface Offer {
64
+ experimentId: string;
65
+ testGroup: TestGroup;
66
+ title: string;
67
+ discounts: unknown[];
68
+ }
69
+ export interface CartAttribute {
70
+ key: string;
71
+ value: string;
72
+ }
73
+ export interface CheckoutLine {
74
+ /** A numeric product variant ID, or a Storefront API `gid://shopify/ProductVariant/<id>`. */
75
+ variantId: string | number;
76
+ quantity: number;
77
+ }
78
+ export interface CheckoutUrlOptions {
79
+ lines: CheckoutLine[];
80
+ /** Opens Shop Pay checkout. */
81
+ payment?: 'shop_pay';
82
+ }
83
+ export interface Customer {
84
+ loggedIn?: boolean;
85
+ tags?: string[];
86
+ ordersCount?: number;
87
+ /** In your store currency. */
88
+ totalSpent?: number;
89
+ }
90
+ /** Anything with headers and a URL: a Fetch `Request`, or `{ headers, url }`. */
91
+ export interface RequestLike {
92
+ url: string;
93
+ headers: {
94
+ get(name: string): string | null;
95
+ } | Record<string, string | string[] | undefined>;
96
+ }
97
+ export interface ABConvertOptions {
98
+ /** Your `myshopify.com` domain. */
99
+ shop: string;
100
+ /** A storefront key from Settings, Headless. Safe to ship in storefront code. */
101
+ storefrontKey: string;
102
+ /** The incoming request. Required on the server. */
103
+ request?: RequestLike;
104
+ /** The country your storefront shows prices for. Defaults to the store's default country. */
105
+ country?: string;
106
+ customer?: Customer;
107
+ /** Milliseconds to wait for your tests on a cold start. Defaults to 1000. */
108
+ timeout?: number;
109
+ /** Seconds a loaded copy of your tests stays fresh. Defaults to 60. */
110
+ cacheTtl?: number;
111
+ onError?: (error: Error) => void;
112
+ consent?: {
113
+ analytics: boolean;
114
+ };
115
+ /**
116
+ * Your storefront loads Shopify's Customer Privacy API, which writes the
117
+ * `_tracking_consent` cookie once the visitor answers: until then, track
118
+ * nothing. The Hydrogen entries' `createABConvert` sets it for you.
119
+ */
120
+ expectCustomerPrivacy?: boolean;
121
+ /** Where ABConvert runs. For ABConvert's own testing; leave unset. */
122
+ baseUrl?: string;
123
+ /**
124
+ * Build the client from a page key (`getPageKey()`, or the segment the
125
+ * `/next` middleware rewrites to) instead of a request: what a cached page
126
+ * renders with. It reads prices and assignments for the key's test groups
127
+ * and nothing about the visitor. `''` or `-` is a page no test changes.
128
+ * Rejects when the tests the key names cannot be loaded, so a cache never
129
+ * keeps your original store under a test group's key.
130
+ */
131
+ pageKey?: string;
132
+ /**
133
+ * Your product pages show no price but their own product's (no related or
134
+ * recently viewed products with prices): a product page's key then carries
135
+ * only the price tests that list it, so untested product pages stay
136
+ * cacheable. Leave unset otherwise, or another product's price shows
137
+ * without its test.
138
+ */
139
+ onlyOwnProductPrices?: boolean;
140
+ }
141
+ /** What `getClientState()` hands the browser so it rebuilds the same client without a fetch. */
142
+ export interface ClientState {
143
+ version: 1;
144
+ shop: string;
145
+ visitorId: string;
146
+ sessionId: string;
147
+ country: string;
148
+ consent: {
149
+ analytics: boolean;
150
+ };
151
+ preview: boolean;
152
+ assignments: Assignment[];
153
+ /** The document, cut to each assigned test and its assigned test group's changes. */
154
+ doc: unknown;
155
+ pending: {
156
+ assignments: PendingAssignment[];
157
+ exposures: string[];
158
+ };
159
+ /** The locale the server formatted prices in. The browser formats in it too, so hydration matches. */
160
+ locale: string;
161
+ /** The `abconvert-state` cookie value, which the browser writes when tracking starts. */
162
+ stateCookie: string;
163
+ /** Where the server loaded the tests from, when not ABConvert's default. The browser tracks to the same place. */
164
+ baseUrl?: string;
165
+ }
166
+ /**
167
+ * What a page rendered from a page key hands the browser (`getPageState()`).
168
+ * The same for everyone in the same test groups, so it may be cached: no
169
+ * visitor, session, consent or pending work. The browser reads those from its
170
+ * cookies.
171
+ */
172
+ export interface PageState {
173
+ version: 2;
174
+ shop: string;
175
+ pageKey: string;
176
+ country: string;
177
+ /** The key's assignments: what the page renders for. */
178
+ assignments: Assignment[];
179
+ /** The keyed tests cut to their test group's changes, and every other test without prices or audience rules. */
180
+ doc: unknown;
181
+ locale: string;
182
+ baseUrl?: string;
183
+ /**
184
+ * For the browser to fetch the current tests when its visitor is in one this
185
+ * copy predates. Safe in storefront code. Absent on a page cached
186
+ * before it was added.
187
+ */
188
+ storefrontKey?: string;
189
+ }
190
+ export interface PendingAssignment {
191
+ experimentId: string;
192
+ objectId: string;
193
+ /** -1 for an excluded visitor. */
194
+ groupIndex: number;
195
+ reason: ReportedReason;
196
+ /** The test's assignment version the decision was made under, when it has one. */
197
+ assignmentVersion?: number;
198
+ assignedAt: string;
199
+ snapshot: {
200
+ country?: string;
201
+ device?: string;
202
+ visitorType?: string;
203
+ trafficPlatform?: string;
204
+ trafficChannel?: string;
205
+ referralDomain?: string;
206
+ landingPage?: string;
207
+ browser?: {
208
+ name: string;
209
+ version: string;
210
+ };
211
+ os?: {
212
+ name: string;
213
+ version: string;
214
+ };
215
+ locale?: string;
216
+ };
217
+ }
218
+ export interface ABConvert {
219
+ getAssignment(experimentId: string | number): Assignment | null;
220
+ getAssignments(): Assignment[];
221
+ getFeature(key: string): Assignment | null;
222
+ getPriceByVariantId(variantId: string | number, options?: PriceOptions): Price | null;
223
+ getPriceByProductId(productId: string | number, options?: PriceOptions): Price | null;
224
+ formatPrice(amount: number, currency?: string): string;
225
+ getShippingRates(options?: ShippingOptions): ShippingRate[] | null;
226
+ getFreeShippingThreshold(options?: ShippingOptions): FreeShippingThreshold | null;
227
+ getOffers(): Offer[];
228
+ getVisitorId(): string;
229
+ getCountry(): string;
230
+ forceTestGroup(experimentId: string | number, index: number): void;
231
+ clearForcedTestGroup(experimentId: string | number): void;
232
+ getCartAttributes(): CartAttribute[];
233
+ getCartLineAttributes(): CartAttribute[];
234
+ /** A cart permalink that lands in checkout with the visitor's test groups. Build it per render; never share or cache it. */
235
+ getCheckoutUrl(options: CheckoutUrlOptions): string;
236
+ /** Server: the visitor's URL redirect destination for `url`, as a same-origin path, or null to stay. Null in the browser. */
237
+ getRedirectUrl(url: string): string | null;
238
+ getClientState(): ClientState;
239
+ /** The page key: the test groups this page renders for, the same for everyone in them. `''` when no test changes the page. */
240
+ getPageKey(): string;
241
+ getSetCookieHeaders(): string[];
242
+ refreshTests(): Promise<void>;
243
+ /** Browser: records `page_viewed`, `product_viewed`, `product_added_to_cart`, or a custom event by its name in ABConvert. Does nothing on the server. */
244
+ track(name: string, payload?: Record<string, unknown>): void;
245
+ /** Browser: records that the visitor saw the test, once per test per session. Does nothing on the server. */
246
+ trackExposure(experimentId: string | number): void;
247
+ /** Browser: the visitor's answer to your cookie banner. Does nothing on the server. */
248
+ setConsent(consent: {
249
+ analytics: boolean;
250
+ }): void;
251
+ }
252
+ /** A client made from a page key: `createABConvert({ pageKey })`. */
253
+ export interface PageABConvert extends ABConvert {
254
+ /** What the browser rebuilds the same reads from. Cacheable: nothing about the visitor. */
255
+ getPageState(): PageState;
256
+ }
257
+ /** A client made from a request on the server: `createABConvert({ request })`. */
258
+ export interface ServerABConvert extends ABConvert {
259
+ /**
260
+ * Whether a test can change this page for some visitor, whatever this
261
+ * visitor's groups: then no shared cache may keep one copy of it for everyone.
262
+ * True when a price test prices a product this URL shows or a URL redirect
263
+ * test covers it, when a read on this client returned a test's data (a
264
+ * tested product's price, offers or shipping rates while such a test runs,
265
+ * an assignment), when `getClientState()` was read, and in a preview session.
266
+ */
267
+ isPageTested(): boolean;
268
+ /**
269
+ * What the browser needs from this request, with nothing about the visitor,
270
+ * so a cached page can carry it: the tests this client's reads touched, as
271
+ * the visitor saw them, and every other test light. On Hydrogen, return it
272
+ * from the route loader that read the prices the route renders, and hand it
273
+ * to the provider; the browser reads the visitor from the cookies.
274
+ */
275
+ getPageState(): PageState;
276
+ /**
277
+ * Whether any test runs for your store: an active test, or a preview test in
278
+ * a preview session. On a framework that caches whole responses, any page
279
+ * could then show one visitor's offer, shipping bar or price.
280
+ */
281
+ isTesting(): boolean;
282
+ }
283
+ /** The client in the browser, from `hydrateABConvert` or a browser-only `createABConvert`: the same methods, plus what a binding needs to run it. */
284
+ export interface BrowserABConvert extends ABConvert {
285
+ /** Begin tracking: report what is pending once consent allows. Idempotent. */
286
+ start(): void;
287
+ /** Stop listening for the visitor's consent. What is held stays held. */
288
+ stop(): void;
289
+ /** Called after a force changes what the reads answer. Returns the undo. */
290
+ subscribe(listener: () => void): () => void;
291
+ }
292
+ export interface HydrateOptions {
293
+ /** Runs once per assignment per page load, once the visitor allows analytics. Not for forced or preview assignments. */
294
+ onAssignment?: (assignment: Assignment) => void;
295
+ /**
296
+ * Hold all tracking, cookies included, until Shopify's Customer Privacy API
297
+ * loads and allows analytics, as Hydrogen does. Set it when your storefront
298
+ * loads that API; the Hydrogen entries set it for you. Without it, a page
299
+ * where the API is absent follows `setConsent` or the server's view.
300
+ */
301
+ expectCustomerPrivacy?: boolean;
302
+ }
@@ -0,0 +1,302 @@
1
+ export type AssignmentType = 'price' | 'shipping' | 'theme' | 'template' | 'url_redirect' | 'checkout' | 'offer' | 'visual_editor' | 'combined' | 'personalization';
2
+ export type AssignmentReason = 'random_split' | 'force_assign_group' | 'preview_override' | 'url_force_assign';
3
+ /** Why a visitor was left out of a test, for the exclusions reported as test group -1. */
4
+ export type ReportedExclusion = 'audience_targeting_excluded' | 'force_assign_excluded';
5
+ /** What an assignment row names as its reason: a test group the visitor counts in, or an exclusion. */
6
+ export type ReportedReason = 'random_split' | 'force_assign_group' | ReportedExclusion;
7
+ export interface TestGroup {
8
+ index: number;
9
+ name: string;
10
+ control: boolean;
11
+ split: number;
12
+ /** A feature test's values for this test group; `{}` for every other test type. */
13
+ variables: Record<string, string | number | boolean>;
14
+ }
15
+ export interface Assignment {
16
+ experimentId: string;
17
+ experimentName: string;
18
+ type: AssignmentType;
19
+ status: 'active' | 'preview';
20
+ testGroup: TestGroup;
21
+ reason: AssignmentReason;
22
+ /** Feature tests only. */
23
+ featureKey?: string;
24
+ }
25
+ export interface Price {
26
+ experimentId: string;
27
+ testGroup: TestGroup;
28
+ amount: number;
29
+ compareAtAmount: number | null;
30
+ currency: string;
31
+ country: string;
32
+ productId: string;
33
+ variantId: string;
34
+ }
35
+ export interface PriceOptions {
36
+ country?: string;
37
+ aggregate?: 'min' | 'max' | 'first';
38
+ }
39
+ export interface ShippingRate {
40
+ experimentId: string;
41
+ testGroup: TestGroup;
42
+ zone: string;
43
+ name: string;
44
+ amount: number;
45
+ currency: string;
46
+ condition: {
47
+ type: 'price' | 'weight';
48
+ minimum: number | null;
49
+ maximum: number | null;
50
+ unit: string | null;
51
+ } | null;
52
+ }
53
+ export interface FreeShippingThreshold {
54
+ experimentId: string;
55
+ testGroup: TestGroup;
56
+ zone: string;
57
+ amount: number;
58
+ currency: string;
59
+ }
60
+ export interface ShippingOptions {
61
+ zone?: string;
62
+ }
63
+ export interface Offer {
64
+ experimentId: string;
65
+ testGroup: TestGroup;
66
+ title: string;
67
+ discounts: unknown[];
68
+ }
69
+ export interface CartAttribute {
70
+ key: string;
71
+ value: string;
72
+ }
73
+ export interface CheckoutLine {
74
+ /** A numeric product variant ID, or a Storefront API `gid://shopify/ProductVariant/<id>`. */
75
+ variantId: string | number;
76
+ quantity: number;
77
+ }
78
+ export interface CheckoutUrlOptions {
79
+ lines: CheckoutLine[];
80
+ /** Opens Shop Pay checkout. */
81
+ payment?: 'shop_pay';
82
+ }
83
+ export interface Customer {
84
+ loggedIn?: boolean;
85
+ tags?: string[];
86
+ ordersCount?: number;
87
+ /** In your store currency. */
88
+ totalSpent?: number;
89
+ }
90
+ /** Anything with headers and a URL: a Fetch `Request`, or `{ headers, url }`. */
91
+ export interface RequestLike {
92
+ url: string;
93
+ headers: {
94
+ get(name: string): string | null;
95
+ } | Record<string, string | string[] | undefined>;
96
+ }
97
+ export interface ABConvertOptions {
98
+ /** Your `myshopify.com` domain. */
99
+ shop: string;
100
+ /** A storefront key from Settings, Headless. Safe to ship in storefront code. */
101
+ storefrontKey: string;
102
+ /** The incoming request. Required on the server. */
103
+ request?: RequestLike;
104
+ /** The country your storefront shows prices for. Defaults to the store's default country. */
105
+ country?: string;
106
+ customer?: Customer;
107
+ /** Milliseconds to wait for your tests on a cold start. Defaults to 1000. */
108
+ timeout?: number;
109
+ /** Seconds a loaded copy of your tests stays fresh. Defaults to 60. */
110
+ cacheTtl?: number;
111
+ onError?: (error: Error) => void;
112
+ consent?: {
113
+ analytics: boolean;
114
+ };
115
+ /**
116
+ * Your storefront loads Shopify's Customer Privacy API, which writes the
117
+ * `_tracking_consent` cookie once the visitor answers: until then, track
118
+ * nothing. The Hydrogen entries' `createABConvert` sets it for you.
119
+ */
120
+ expectCustomerPrivacy?: boolean;
121
+ /** Where ABConvert runs. For ABConvert's own testing; leave unset. */
122
+ baseUrl?: string;
123
+ /**
124
+ * Build the client from a page key (`getPageKey()`, or the segment the
125
+ * `/next` middleware rewrites to) instead of a request: what a cached page
126
+ * renders with. It reads prices and assignments for the key's test groups
127
+ * and nothing about the visitor. `''` or `-` is a page no test changes.
128
+ * Rejects when the tests the key names cannot be loaded, so a cache never
129
+ * keeps your original store under a test group's key.
130
+ */
131
+ pageKey?: string;
132
+ /**
133
+ * Your product pages show no price but their own product's (no related or
134
+ * recently viewed products with prices): a product page's key then carries
135
+ * only the price tests that list it, so untested product pages stay
136
+ * cacheable. Leave unset otherwise, or another product's price shows
137
+ * without its test.
138
+ */
139
+ onlyOwnProductPrices?: boolean;
140
+ }
141
+ /** What `getClientState()` hands the browser so it rebuilds the same client without a fetch. */
142
+ export interface ClientState {
143
+ version: 1;
144
+ shop: string;
145
+ visitorId: string;
146
+ sessionId: string;
147
+ country: string;
148
+ consent: {
149
+ analytics: boolean;
150
+ };
151
+ preview: boolean;
152
+ assignments: Assignment[];
153
+ /** The document, cut to each assigned test and its assigned test group's changes. */
154
+ doc: unknown;
155
+ pending: {
156
+ assignments: PendingAssignment[];
157
+ exposures: string[];
158
+ };
159
+ /** The locale the server formatted prices in. The browser formats in it too, so hydration matches. */
160
+ locale: string;
161
+ /** The `abconvert-state` cookie value, which the browser writes when tracking starts. */
162
+ stateCookie: string;
163
+ /** Where the server loaded the tests from, when not ABConvert's default. The browser tracks to the same place. */
164
+ baseUrl?: string;
165
+ }
166
+ /**
167
+ * What a page rendered from a page key hands the browser (`getPageState()`).
168
+ * The same for everyone in the same test groups, so it may be cached: no
169
+ * visitor, session, consent or pending work. The browser reads those from its
170
+ * cookies.
171
+ */
172
+ export interface PageState {
173
+ version: 2;
174
+ shop: string;
175
+ pageKey: string;
176
+ country: string;
177
+ /** The key's assignments: what the page renders for. */
178
+ assignments: Assignment[];
179
+ /** The keyed tests cut to their test group's changes, and every other test without prices or audience rules. */
180
+ doc: unknown;
181
+ locale: string;
182
+ baseUrl?: string;
183
+ /**
184
+ * For the browser to fetch the current tests when its visitor is in one this
185
+ * copy predates. Safe in storefront code. Absent on a page cached
186
+ * before it was added.
187
+ */
188
+ storefrontKey?: string;
189
+ }
190
+ export interface PendingAssignment {
191
+ experimentId: string;
192
+ objectId: string;
193
+ /** -1 for an excluded visitor. */
194
+ groupIndex: number;
195
+ reason: ReportedReason;
196
+ /** The test's assignment version the decision was made under, when it has one. */
197
+ assignmentVersion?: number;
198
+ assignedAt: string;
199
+ snapshot: {
200
+ country?: string;
201
+ device?: string;
202
+ visitorType?: string;
203
+ trafficPlatform?: string;
204
+ trafficChannel?: string;
205
+ referralDomain?: string;
206
+ landingPage?: string;
207
+ browser?: {
208
+ name: string;
209
+ version: string;
210
+ };
211
+ os?: {
212
+ name: string;
213
+ version: string;
214
+ };
215
+ locale?: string;
216
+ };
217
+ }
218
+ export interface ABConvert {
219
+ getAssignment(experimentId: string | number): Assignment | null;
220
+ getAssignments(): Assignment[];
221
+ getFeature(key: string): Assignment | null;
222
+ getPriceByVariantId(variantId: string | number, options?: PriceOptions): Price | null;
223
+ getPriceByProductId(productId: string | number, options?: PriceOptions): Price | null;
224
+ formatPrice(amount: number, currency?: string): string;
225
+ getShippingRates(options?: ShippingOptions): ShippingRate[] | null;
226
+ getFreeShippingThreshold(options?: ShippingOptions): FreeShippingThreshold | null;
227
+ getOffers(): Offer[];
228
+ getVisitorId(): string;
229
+ getCountry(): string;
230
+ forceTestGroup(experimentId: string | number, index: number): void;
231
+ clearForcedTestGroup(experimentId: string | number): void;
232
+ getCartAttributes(): CartAttribute[];
233
+ getCartLineAttributes(): CartAttribute[];
234
+ /** A cart permalink that lands in checkout with the visitor's test groups. Build it per render; never share or cache it. */
235
+ getCheckoutUrl(options: CheckoutUrlOptions): string;
236
+ /** Server: the visitor's URL redirect destination for `url`, as a same-origin path, or null to stay. Null in the browser. */
237
+ getRedirectUrl(url: string): string | null;
238
+ getClientState(): ClientState;
239
+ /** The page key: the test groups this page renders for, the same for everyone in them. `''` when no test changes the page. */
240
+ getPageKey(): string;
241
+ getSetCookieHeaders(): string[];
242
+ refreshTests(): Promise<void>;
243
+ /** Browser: records `page_viewed`, `product_viewed`, `product_added_to_cart`, or a custom event by its name in ABConvert. Does nothing on the server. */
244
+ track(name: string, payload?: Record<string, unknown>): void;
245
+ /** Browser: records that the visitor saw the test, once per test per session. Does nothing on the server. */
246
+ trackExposure(experimentId: string | number): void;
247
+ /** Browser: the visitor's answer to your cookie banner. Does nothing on the server. */
248
+ setConsent(consent: {
249
+ analytics: boolean;
250
+ }): void;
251
+ }
252
+ /** A client made from a page key: `createABConvert({ pageKey })`. */
253
+ export interface PageABConvert extends ABConvert {
254
+ /** What the browser rebuilds the same reads from. Cacheable: nothing about the visitor. */
255
+ getPageState(): PageState;
256
+ }
257
+ /** A client made from a request on the server: `createABConvert({ request })`. */
258
+ export interface ServerABConvert extends ABConvert {
259
+ /**
260
+ * Whether a test can change this page for some visitor, whatever this
261
+ * visitor's groups: then no shared cache may keep one copy of it for everyone.
262
+ * True when a price test prices a product this URL shows or a URL redirect
263
+ * test covers it, when a read on this client returned a test's data (a
264
+ * tested product's price, offers or shipping rates while such a test runs,
265
+ * an assignment), when `getClientState()` was read, and in a preview session.
266
+ */
267
+ isPageTested(): boolean;
268
+ /**
269
+ * What the browser needs from this request, with nothing about the visitor,
270
+ * so a cached page can carry it: the tests this client's reads touched, as
271
+ * the visitor saw them, and every other test light. On Hydrogen, return it
272
+ * from the route loader that read the prices the route renders, and hand it
273
+ * to the provider; the browser reads the visitor from the cookies.
274
+ */
275
+ getPageState(): PageState;
276
+ /**
277
+ * Whether any test runs for your store: an active test, or a preview test in
278
+ * a preview session. On a framework that caches whole responses, any page
279
+ * could then show one visitor's offer, shipping bar or price.
280
+ */
281
+ isTesting(): boolean;
282
+ }
283
+ /** The client in the browser, from `hydrateABConvert` or a browser-only `createABConvert`: the same methods, plus what a binding needs to run it. */
284
+ export interface BrowserABConvert extends ABConvert {
285
+ /** Begin tracking: report what is pending once consent allows. Idempotent. */
286
+ start(): void;
287
+ /** Stop listening for the visitor's consent. What is held stays held. */
288
+ stop(): void;
289
+ /** Called after a force changes what the reads answer. Returns the undo. */
290
+ subscribe(listener: () => void): () => void;
291
+ }
292
+ export interface HydrateOptions {
293
+ /** Runs once per assignment per page load, once the visitor allows analytics. Not for forced or preview assignments. */
294
+ onAssignment?: (assignment: Assignment) => void;
295
+ /**
296
+ * Hold all tracking, cookies included, until Shopify's Customer Privacy API
297
+ * loads and allows analytics, as Hydrogen does. Set it when your storefront
298
+ * loads that API; the Hydrogen entries set it for you. Without it, a page
299
+ * where the API is absent follows `setConsent` or the server's view.
300
+ */
301
+ expectCustomerPrivacy?: boolean;
302
+ }
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@abconvert/headless",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "ABConvert A/B tests on a custom Shopify storefront: assignment, test prices, and cart attributes on the server, edge, or browser.",
5
+ "keywords": ["abconvert", "shopify", "headless", "hydrogen", "nextjs", "ab-testing", "storefront-api"],
6
+ "homepage": "https://docs.abconvert.io/headless-sdk/overview",
7
+ "bugs": { "email": "support@abconvert.io" },
8
+ "repository": { "type": "git", "url": "git+https://github.com/ABConvert/ab-test-app.git", "directory": "packages/headless" },
9
+ "license": "Apache-2.0",
10
+ "type": "module",
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
17
+ "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
18
+ },
19
+ "./react": {
20
+ "import": { "types": "./dist/react.d.ts", "default": "./dist/react.js" },
21
+ "require": { "types": "./dist/react.d.cts", "default": "./dist/react.cjs" }
22
+ },
23
+ "./next": {
24
+ "import": { "types": "./dist/next.d.ts", "default": "./dist/next.js" },
25
+ "require": { "types": "./dist/next.d.cts", "default": "./dist/next.cjs" }
26
+ },
27
+ "./hydrogen": {
28
+ "import": { "types": "./dist/hydrogen.d.ts", "default": "./dist/hydrogen.js" },
29
+ "require": { "types": "./dist/hydrogen.d.cts", "default": "./dist/hydrogen.cjs" }
30
+ },
31
+ "./hydrogen/analytics-provider": {
32
+ "import": { "types": "./dist/hydrogen-analytics-provider.d.ts", "default": "./dist/hydrogen-analytics-provider.js" },
33
+ "require": { "types": "./dist/hydrogen-analytics-provider.d.cts", "default": "./dist/hydrogen-analytics-provider.cjs" }
34
+ }
35
+ },
36
+ "typesVersions": {
37
+ "*": {
38
+ "react": ["./dist/react.d.ts"],
39
+ "next": ["./dist/next.d.ts"],
40
+ "hydrogen": ["./dist/hydrogen.d.ts"],
41
+ "hydrogen/analytics-provider": ["./dist/hydrogen-analytics-provider.d.ts"]
42
+ }
43
+ },
44
+ "files": ["dist", "!dist/*inputs.json", "NOTICE"],
45
+ "sideEffects": false,
46
+ "engines": { "node": ">=20" },
47
+ "publishConfig": { "access": "public" },
48
+ "scripts": {
49
+ "build": "node scripts/build.mjs",
50
+ "test": "vitest run --config vitest.config.ts",
51
+ "test:watch": "vitest --config vitest.config.ts",
52
+ "typecheck": "tsc --noEmit -p tsconfig.json",
53
+ "check:runtime": "node scripts/check-runtime.mjs",
54
+ "check": "npm run typecheck && npm test && npm run build && npm run check:runtime"
55
+ },
56
+ "peerDependencies": {
57
+ "react": ">=17",
58
+ "next": ">=14",
59
+ "@shopify/hydrogen": ">=2024.4"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "react": { "optional": true },
63
+ "next": { "optional": true },
64
+ "@shopify/hydrogen": { "optional": true }
65
+ },
66
+ "devDependencies": {
67
+ "@types/react": "18.3.28",
68
+ "@types/react-dom": "18.3.7",
69
+ "esbuild": "0.27.4",
70
+ "jsdom": "25.0.1",
71
+ "react": "18.3.1",
72
+ "react-dom": "18.3.1",
73
+ "typescript": "4.9.5",
74
+ "vitest": "1.6.1"
75
+ }
76
+ }