@aranova/tracking-next 0.14.2 → 0.16.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.
@@ -180,11 +180,17 @@ interface TrackedField {
180
180
  declare function phoneField(name: string, raw: string, country?: CountryCode): TrackedField;
181
181
 
182
182
  /**
183
- * Visitor consent state stored by the SDK.
183
+ * Visitor consent state as reported by `getConsentState()`.
184
184
  *
185
- * - `pending`: the visitor has not accepted or declined yet.
186
- * - `granted`: consent was accepted and Google Consent Mode is updated to granted.
187
- * - `denied`: consent was declined and Google Consent Mode is updated to denied.
185
+ * Consent v2 is an opt-out model: the effective state is `granted` unless the
186
+ * visitor stored an explicit, unexpired decline.
187
+ *
188
+ * - `granted`: default, or an explicit opt-in.
189
+ * - `denied`: explicit opt-out, honored for 90 days.
190
+ * - `pending`: NEVER returned since consent v2 (the opt-in-era "no choice yet"
191
+ * value). Retained in the union only so pre-v2 call sites keep compiling;
192
+ * any `pending` branch is dead code. Use `getConsentChoice().source ===
193
+ * "default"` to detect "no explicit choice".
188
194
  */
189
195
  type ConsentState = "granted" | "denied" | "pending";
190
196
  /**
@@ -296,6 +302,22 @@ interface TrackingSessionUpsertPayload {
296
302
  utm_campaign: string | null;
297
303
  utm_term: string | null;
298
304
  utm_content: string | null;
305
+ /**
306
+ * Landing attribution (ADR-016): params present in THIS session's landing
307
+ * URL, unlike the cookie-backed fields above which carry for 90 days and may
308
+ * be inherited from an earlier session's click. The backend treats key
309
+ * PRESENCE as "landing explicitly known" (nulls mean a param-less landing);
310
+ * when the landing is NOT observable — SSR, or SDKs predating these fields —
311
+ * the keys are omitted and the backend falls back to parsing first_page.
312
+ * Build via `buildLandingPayloadFields()`, which enforces this contract.
313
+ */
314
+ landing_gclid?: string | null;
315
+ landing_fbclid?: string | null;
316
+ landing_utm_source?: string | null;
317
+ landing_utm_medium?: string | null;
318
+ landing_utm_campaign?: string | null;
319
+ landing_utm_term?: string | null;
320
+ landing_utm_content?: string | null;
299
321
  first_page: string | null;
300
322
  consent_state: Record<string, unknown> | null;
301
323
  context: TrackingClientContext;
@@ -344,7 +366,11 @@ interface TrackingInitConfig {
344
366
  environment?: TrackingEnvironment;
345
367
  /** Whether to capture attribution params from `window.location`. Defaults to true. */
346
368
  autoCaptureTrackingParams?: boolean;
347
- /** Whether the browser script should inject the default consent banner. */
369
+ /**
370
+ * @deprecated No-op since consent v2 (opt-out model). The packages no longer
371
+ * ship a consent banner; wire a footer control to
372
+ * `window.AranovaTracking.optOut()` / `optIn()` instead.
373
+ */
348
374
  renderConsentBanner?: boolean;
349
375
  /** Attribution cookie max age in seconds. Defaults to 90 days. */
350
376
  cookieMaxAgeSeconds?: number;
@@ -365,9 +391,104 @@ declare global {
365
391
  getTrackingParams: () => TrackingParams;
366
392
  getConsentState: () => ConsentState;
367
393
  setConsentState: (state: "granted" | "denied") => void;
394
+ getConsentChoice: () => ConsentChoice;
395
+ optIn: () => void;
396
+ optOut: (options?: SetConsentOptions) => void;
397
+ resetConsent: () => void;
368
398
  trackEvent: (eventType: string, metadata: Record<string, unknown>) => void;
369
399
  };
370
400
  }
371
401
  }
372
402
 
373
- export { type ConsentState as C, DEFAULT_PHONE_COUNTRY as D, type FormSubmitConfig as F, type GtagEnvironmentMap as G, type JsonValue as J, type MetaPixelEnvironmentMap as M, type PhoneConfig as P, type TrackingParams as T, type TrackingInstallSurface as a, type TrackingEnvironment as b, type TrackingClientContext as c, type TrackingEventCreatePayload as d, type TrackingSessionUpsertPayload as e, type FormSubmitMetadata as f, type ParsedPhone as g, type PhoneDisplayFormat as h, type TrackedField as i, type TrackingInitConfig as j, formSubmitConfigSchema as k, formSubmitMetadataSchema as l, formatPhone as m, formatPhoneAsTyped as n, jsonValueSchema as o, parsePhone as p, phoneField as q, toE164 as t };
403
+ /**
404
+ * How long an explicit decline is honored before the visitor reverts to the
405
+ * default-granted state. Explicit grants never expire.
406
+ */
407
+ declare const DEFAULT_DECLINE_TTL_DAYS = 90;
408
+ /**
409
+ * Google Consent Mode value sent to `gtag('consent', 'update', ...)`.
410
+ */
411
+ type GtagConsentValue = "granted" | "denied";
412
+ /** Effective consent state — the opt-out model has no "pending". */
413
+ type ConsentChoiceState = "granted" | "denied";
414
+ /**
415
+ * Where the effective state came from: `explicit` when the visitor made a
416
+ * stored, still-valid choice; `default` otherwise (no choice, expired decline,
417
+ * storage blocked, SSR).
418
+ */
419
+ type ConsentSource = "default" | "explicit";
420
+ /**
421
+ * The effective consent decision plus its provenance.
422
+ */
423
+ interface ConsentChoice {
424
+ state: ConsentChoiceState;
425
+ source: ConsentSource;
426
+ /** ISO timestamp of the explicit choice; null for the default state. */
427
+ updatedAt: string | null;
428
+ /** ISO expiry of an unexpired decline; null for grants and the default state. */
429
+ expiresAt: string | null;
430
+ }
431
+ /** Options for explicit consent writes. */
432
+ interface SetConsentOptions {
433
+ /** Days an explicit decline is honored. Defaults to {@link DEFAULT_DECLINE_TTL_DAYS}. */
434
+ declineTtlDays?: number;
435
+ }
436
+ type ConsentChangeListener = (choice: ConsentChoice) => void;
437
+ /**
438
+ * Subscribe to consent changes (opt-in, opt-out, reset) made in THIS tab.
439
+ * Cross-tab changes surface via the browser's `storage` event instead.
440
+ * Returns an unsubscribe function.
441
+ */
442
+ declare function onConsentChange(listener: ConsentChangeListener): () => void;
443
+ /**
444
+ * Resolve the visitor's effective consent (opt-out model).
445
+ *
446
+ * Default is GRANTED. Only a stored, unexpired explicit decline yields
447
+ * `denied`. SSR, blocked storage, and expired declines all resolve to the
448
+ * default. A legacy decline stored by the opt-in-era SDK (no expiry key) stays
449
+ * denied and gets a fresh 90-day expiry backfilled, keeping this reader
450
+ * consistent with the inline scripts (which treat a missing expiry as denied).
451
+ */
452
+ declare function getConsentChoice(): ConsentChoice;
453
+ /**
454
+ * Read the visitor's effective consent state.
455
+ *
456
+ * Opt-out model: returns `granted` unless a stored, unexpired explicit decline
457
+ * exists. Never returns `pending` — that value survives in {@link ConsentState}
458
+ * only so pre-v2 call sites keep compiling.
459
+ */
460
+ declare function getConsentState(): ConsentState;
461
+ /**
462
+ * Persist an explicit visitor consent choice and push it live to Google
463
+ * Consent Mode and the Meta Pixel.
464
+ *
465
+ * Declines expire after {@link DEFAULT_DECLINE_TTL_DAYS} days (override via
466
+ * `options.declineTtlDays`); grants never expire.
467
+ */
468
+ declare function setConsentState(state: GtagConsentValue, options?: SetConsentOptions): void;
469
+ /**
470
+ * Explicitly opt the visitor in to ad tracking (never expires).
471
+ */
472
+ declare function optIn(): void;
473
+ /**
474
+ * Explicitly opt the visitor out of ad tracking for
475
+ * {@link DEFAULT_DECLINE_TTL_DAYS} days (override via `options.declineTtlDays`).
476
+ *
477
+ * This is the primitive a client site's footer "cookie preferences" control
478
+ * should call — the packages ship no consent UI of their own.
479
+ */
480
+ declare function optOut(options?: SetConsentOptions): void;
481
+ /**
482
+ * Clear the stored explicit choice, returning the visitor to the
483
+ * default-granted state, and push that state live to gtag + Meta.
484
+ *
485
+ * Power a "Cookie preferences" reset in a footer:
486
+ *
487
+ * ```tsx
488
+ * const { reset } = useCookiePreferences();
489
+ * <button onClick={reset}>Reset cookie preferences</button>
490
+ * ```
491
+ */
492
+ declare function resetConsent(): void;
493
+
494
+ export { resetConsent as A, setConsentState as B, type ConsentState as C, DEFAULT_DECLINE_TTL_DAYS as D, toE164 as E, type FormSubmitConfig as F, type GtagEnvironmentMap as G, type JsonValue as J, type MetaPixelEnvironmentMap as M, type PhoneConfig as P, type SetConsentOptions as S, type TrackingParams as T, type TrackingInstallSurface as a, type TrackingEnvironment as b, type TrackingClientContext as c, type TrackingEventCreatePayload as d, type TrackingSessionUpsertPayload as e, type FormSubmitMetadata as f, type ConsentChoiceState as g, type ConsentSource as h, type ParsedPhone as i, type PhoneDisplayFormat as j, type ConsentChoice as k, DEFAULT_PHONE_COUNTRY as l, type TrackedField as m, type TrackingInitConfig as n, formSubmitConfigSchema as o, formSubmitMetadataSchema as p, formatPhone as q, formatPhoneAsTyped as r, getConsentChoice as s, getConsentState as t, jsonValueSchema as u, onConsentChange as v, optIn as w, optOut as x, parsePhone as y, phoneField as z };
package/dist/phone.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- export { D as DEFAULT_PHONE_COUNTRY, g as ParsedPhone, P as PhoneConfig, h as PhoneDisplayFormat, i as TrackedField, m as formatPhone, n as formatPhoneAsTyped, p as parsePhone, q as phoneField, t as toE164 } from './phone-utils-CcU5mwzc.mjs';
1
+ export { l as DEFAULT_PHONE_COUNTRY, i as ParsedPhone, P as PhoneConfig, j as PhoneDisplayFormat, m as TrackedField, q as formatPhone, r as formatPhoneAsTyped, y as parsePhone, z as phoneField, E as toE164 } from './phone-utils-DlAQK-gU.mjs';
2
2
  export { CountryCode } from 'libphonenumber-js';
3
3
  import 'zod';
package/dist/phone.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { D as DEFAULT_PHONE_COUNTRY, g as ParsedPhone, P as PhoneConfig, h as PhoneDisplayFormat, i as TrackedField, m as formatPhone, n as formatPhoneAsTyped, p as parsePhone, q as phoneField, t as toE164 } from './phone-utils-CcU5mwzc.js';
1
+ export { l as DEFAULT_PHONE_COUNTRY, i as ParsedPhone, P as PhoneConfig, j as PhoneDisplayFormat, m as TrackedField, q as formatPhone, r as formatPhoneAsTyped, y as parsePhone, z as phoneField, E as toE164 } from './phone-utils-DlAQK-gU.js';
2
2
  export { CountryCode } from 'libphonenumber-js';
3
3
  import 'zod';
package/dist/phone.js CHANGED
@@ -29,79 +29,6 @@ __export(phone_utils_exports, {
29
29
  });
30
30
  module.exports = __toCommonJS(phone_utils_exports);
31
31
 
32
- // ../tracking-core/src/consent.ts
33
- var CONSENT_STATE_KEY = "consent_state";
34
- var grantedListeners = /* @__PURE__ */ new Set();
35
- function onConsentGranted(listener) {
36
- grantedListeners.add(listener);
37
- return () => {
38
- grantedListeners.delete(listener);
39
- };
40
- }
41
- function getConsentState() {
42
- if (typeof window === "undefined") return "pending";
43
- try {
44
- const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
45
- if (storedState === "granted" || storedState === "denied") return storedState;
46
- } catch {
47
- }
48
- return "pending";
49
- }
50
-
51
- // ../tracking-core/src/gtag.ts
52
- var SEND_TO_RE = /^AW-[A-Za-z0-9]+\/[A-Za-z0-9_-]+$/;
53
- function isValidSendTo(sendTo) {
54
- return SEND_TO_RE.test(sendTo);
55
- }
56
- function fireGtagConversion(input) {
57
- if (typeof window === "undefined" || typeof window.gtag !== "function") return false;
58
- if (!isValidSendTo(input.sendTo)) return false;
59
- const params = { send_to: input.sendTo };
60
- if (input.value != null) params.value = input.value;
61
- if (input.currency) params.currency = input.currency;
62
- if (input.transactionId) params.transaction_id = input.transactionId;
63
- try {
64
- window.gtag("event", "conversion", params);
65
- return true;
66
- } catch {
67
- return false;
68
- }
69
- }
70
-
71
- // ../tracking-core/src/resources/conversion-firing.ts
72
- var DEDUP_PREFIX = "_aranova_conv_";
73
- var pendingQueue = [];
74
- function dedupKey(input) {
75
- return `${DEDUP_PREFIX}${input.transactionId ?? ""}:${input.sendTo}`;
76
- }
77
- function alreadyFired(input) {
78
- if (!input.transactionId || typeof window === "undefined") return false;
79
- try {
80
- return window.sessionStorage.getItem(dedupKey(input)) !== null;
81
- } catch {
82
- return false;
83
- }
84
- }
85
- function markFired(input) {
86
- if (!input.transactionId || typeof window === "undefined") return;
87
- try {
88
- window.sessionStorage.setItem(dedupKey(input), "1");
89
- } catch {
90
- }
91
- }
92
- function fireOnce(input) {
93
- if (alreadyFired(input)) return;
94
- if (fireGtagConversion(input)) markFired(input);
95
- }
96
- function flushPendingConversions() {
97
- if (getConsentState() !== "granted") return;
98
- while (pendingQueue.length > 0) {
99
- const input = pendingQueue.shift();
100
- if (input) fireOnce(input);
101
- }
102
- }
103
- if (typeof window !== "undefined") onConsentGranted(flushPendingConversions);
104
-
105
32
  // ../tracking-core/src/session.ts
106
33
  var SESSION_IDLE_MS = 30 * 60 * 1e3;
107
34
 
@@ -133,9 +60,17 @@ var ctaClickMetadataSchema = import_zod2.z.object({
133
60
  path: import_zod2.z.string()
134
61
  }).strict(),
135
62
  section: import_zod2.z.string().nullable().optional(),
136
- destination_url: import_zod2.z.string().nullable().optional()
63
+ destination_url: import_zod2.z.string().nullable().optional(),
64
+ // Set by auto-capture (and available to manual callers): the link target
65
+ // and a short element descriptor (tag#id) for tying clicks to specific UI.
66
+ href: import_zod2.z.string().nullable().optional(),
67
+ element: import_zod2.z.string().nullable().optional()
68
+ }).strict();
69
+ var ctaClickConfigSchema = import_zod2.z.object({
70
+ autoCapture: import_zod2.z.object({
71
+ selector: import_zod2.z.string().optional()
72
+ }).strict().optional()
137
73
  }).strict();
138
- var ctaClickConfigSchema = import_zod2.z.object({}).strict();
139
74
 
140
75
  // ../tracking-core/src/events/sdk-heartbeat.ts
141
76
  var import_zod3 = require("zod");
@@ -211,31 +146,44 @@ var multiPageSessionConfigSchema = import_zod6.z.object({
211
146
  pageThreshold: import_zod6.z.number().int().min(2)
212
147
  }).strict();
213
148
 
214
- // ../tracking-core/src/events/phone-click.ts
149
+ // ../tracking-core/src/events/page-exit.ts
215
150
  var import_zod7 = require("zod");
216
- var phoneClickMetadataSchema = import_zod7.z.object({
217
- phone_number: import_zod7.z.string(),
151
+ var pageExitMetadataSchema = import_zod7.z.object({
152
+ dwell_ms: import_zod7.z.number().int().min(0),
153
+ // null = left without any scroll signal; floor is 0 so a valid 0% is never
154
+ // rejected (a single bad field 422s the whole keepalive beacon batch).
155
+ max_scroll_percent: import_zod7.z.number().int().min(0).max(100).nullable(),
218
156
  page: import_zod7.z.object({
219
157
  path: import_zod7.z.string()
220
- }).strict(),
221
- section: import_zod7.z.string().nullable().optional()
158
+ }).strict()
222
159
  }).strict();
223
- var phoneClickConfigSchema = import_zod7.z.object({}).strict();
160
+ var pageExitConfigSchema = import_zod7.z.object({}).strict();
224
161
 
225
- // ../tracking-core/src/events/scroll-depth.ts
162
+ // ../tracking-core/src/events/phone-click.ts
226
163
  var import_zod8 = require("zod");
227
- var scrollDepthMetadataSchema = import_zod8.z.object({
228
- depth_percent: import_zod8.z.number().int().min(1).max(100),
164
+ var phoneClickMetadataSchema = import_zod8.z.object({
165
+ phone_number: import_zod8.z.string(),
229
166
  page: import_zod8.z.object({
230
167
  path: import_zod8.z.string()
168
+ }).strict(),
169
+ section: import_zod8.z.string().nullable().optional()
170
+ }).strict();
171
+ var phoneClickConfigSchema = import_zod8.z.object({}).strict();
172
+
173
+ // ../tracking-core/src/events/scroll-depth.ts
174
+ var import_zod9 = require("zod");
175
+ var scrollDepthMetadataSchema = import_zod9.z.object({
176
+ depth_percent: import_zod9.z.number().int().min(1).max(100),
177
+ page: import_zod9.z.object({
178
+ path: import_zod9.z.string()
231
179
  }).strict()
232
180
  }).strict();
233
- var scrollDepthConfigSchema = import_zod8.z.object({
234
- thresholds: import_zod8.z.array(import_zod8.z.number().int().min(1).max(100)).min(1)
181
+ var scrollDepthConfigSchema = import_zod9.z.object({
182
+ thresholds: import_zod9.z.array(import_zod9.z.number().int().min(1).max(100)).min(1)
235
183
  }).strict();
236
184
 
237
185
  // ../tracking-core/src/events/specific-page-visit.ts
238
- var import_zod9 = require("zod");
186
+ var import_zod10 = require("zod");
239
187
  var SPECIFIC_PAGE_NAMES = [
240
188
  "contact_page",
241
189
  "about_page",
@@ -246,18 +194,18 @@ var SPECIFIC_PAGE_NAMES = [
246
194
  "faq_page",
247
195
  "testimonials_page"
248
196
  ];
249
- var specificPageNameSchema = import_zod9.z.enum(SPECIFIC_PAGE_NAMES);
250
- var specificPageVisitMetadataSchema = import_zod9.z.object({
197
+ var specificPageNameSchema = import_zod10.z.enum(SPECIFIC_PAGE_NAMES);
198
+ var specificPageVisitMetadataSchema = import_zod10.z.object({
251
199
  page_name: specificPageNameSchema,
252
- page: import_zod9.z.object({
253
- path: import_zod9.z.string()
200
+ page: import_zod10.z.object({
201
+ path: import_zod10.z.string()
254
202
  }).strict()
255
203
  }).strict();
256
- var specificPageVisitConfigSchema = import_zod9.z.object({
257
- pages: import_zod9.z.array(
258
- import_zod9.z.object({
204
+ var specificPageVisitConfigSchema = import_zod10.z.object({
205
+ pages: import_zod10.z.array(
206
+ import_zod10.z.object({
259
207
  name: specificPageNameSchema,
260
- pathPattern: import_zod9.z.custom((value) => value instanceof RegExp, {
208
+ pathPattern: import_zod10.z.custom((value) => value instanceof RegExp, {
261
209
  message: "pathPattern must be a RegExp"
262
210
  })
263
211
  }).strict()
@@ -265,15 +213,15 @@ var specificPageVisitConfigSchema = import_zod9.z.object({
265
213
  }).strict();
266
214
 
267
215
  // ../tracking-core/src/events/time-on-site.ts
268
- var import_zod10 = require("zod");
269
- var timeOnSiteMetadataSchema = import_zod10.z.object({
270
- duration_ms: import_zod10.z.number().int().nonnegative(),
271
- page: import_zod10.z.object({
272
- path: import_zod10.z.string()
216
+ var import_zod11 = require("zod");
217
+ var timeOnSiteMetadataSchema = import_zod11.z.object({
218
+ duration_ms: import_zod11.z.number().int().nonnegative(),
219
+ page: import_zod11.z.object({
220
+ path: import_zod11.z.string()
273
221
  }).strict()
274
222
  }).strict();
275
- var timeOnSiteConfigSchema = import_zod10.z.object({
276
- thresholdSeconds: import_zod10.z.number().int().positive()
223
+ var timeOnSiteConfigSchema = import_zod11.z.object({
224
+ thresholdSeconds: import_zod11.z.number().int().positive()
277
225
  }).strict();
278
226
 
279
227
  // ../tracking-core/src/events/registry.ts
@@ -315,6 +263,11 @@ var EVENT_REGISTRY = {
315
263
  metadataSchema: sdkHeartbeatMetadataSchema,
316
264
  configSchema: sdkHeartbeatConfigSchema
317
265
  },
266
+ page_exit: {
267
+ kind: "automatic",
268
+ metadataSchema: pageExitMetadataSchema,
269
+ configSchema: pageExitConfigSchema
270
+ },
318
271
  // --- manual triggers ---
319
272
  form_submit: {
320
273
  kind: "manual",
@@ -336,42 +289,42 @@ var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def])
336
289
  var ALL_MANUAL_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "manual").map(([name]) => name);
337
290
 
338
291
  // ../tracking-core/src/resources/sales/schema.ts
339
- var import_zod11 = require("zod");
292
+ var import_zod12 = require("zod");
340
293
  var SUPPORTED_CURRENCIES = ["USD", "CAD"];
341
294
  var TRACKING_ENVIRONMENTS = ["production", "development"];
342
- var currencySchema = import_zod11.z.enum(SUPPORTED_CURRENCIES);
343
- var centsSchema = import_zod11.z.number().int().nonnegative();
344
- var quantitySchema = import_zod11.z.string().regex(/^\d+(\.\d{1,3})?$/);
345
- var metadataSchema = import_zod11.z.record(import_zod11.z.unknown());
346
- var saleItemSchema = import_zod11.z.object({
347
- external_item_id: import_zod11.z.string().nullable().optional(),
348
- name: import_zod11.z.string().nullable().optional(),
349
- category: import_zod11.z.string().nullable().optional(),
295
+ var currencySchema = import_zod12.z.enum(SUPPORTED_CURRENCIES);
296
+ var centsSchema = import_zod12.z.number().int().nonnegative();
297
+ var quantitySchema = import_zod12.z.string().regex(/^\d+(\.\d{1,3})?$/);
298
+ var metadataSchema = import_zod12.z.record(import_zod12.z.unknown());
299
+ var saleItemSchema = import_zod12.z.object({
300
+ external_item_id: import_zod12.z.string().nullable().optional(),
301
+ name: import_zod12.z.string().nullable().optional(),
302
+ category: import_zod12.z.string().nullable().optional(),
350
303
  quantity: quantitySchema,
351
304
  unit_price_cents: centsSchema,
352
305
  // Non-negativity validated on the wire — same contract as the other cents
353
306
  // fields — and backstopped by the DB CHECK.
354
307
  unit_cost_cents: centsSchema.nullable().optional()
355
308
  }).strict();
356
- var saleServiceSchema = import_zod11.z.object({
357
- service: import_zod11.z.string(),
309
+ var saleServiceSchema = import_zod12.z.object({
310
+ service: import_zod12.z.string(),
358
311
  amount_cents: centsSchema
359
312
  }).strict();
360
- var customerNameSchema = import_zod11.z.string().max(200);
361
- var customerPhoneSchema = import_zod11.z.string().max(64);
362
- var customerEmailSchema = import_zod11.z.string().max(320).email();
313
+ var customerNameSchema = import_zod12.z.string().max(200);
314
+ var customerPhoneSchema = import_zod12.z.string().max(64);
315
+ var customerEmailSchema = import_zod12.z.string().max(320).email();
363
316
  function refineServiceXor(val, ctx, { requireAmount }) {
364
317
  if (val.services != null) {
365
318
  if (val.service != null) {
366
319
  ctx.addIssue({
367
- code: import_zod11.z.ZodIssueCode.custom,
320
+ code: import_zod12.z.ZodIssueCode.custom,
368
321
  message: "pass either `service` or `services`, not both",
369
322
  path: ["services"]
370
323
  });
371
324
  }
372
325
  if (val.services.length === 0) {
373
326
  ctx.addIssue({
374
- code: import_zod11.z.ZodIssueCode.custom,
327
+ code: import_zod12.z.ZodIssueCode.custom,
375
328
  message: "`services` must not be empty",
376
329
  path: ["services"]
377
330
  });
@@ -379,7 +332,7 @@ function refineServiceXor(val, ctx, { requireAmount }) {
379
332
  const keys = val.services.map((s) => s.service);
380
333
  if (new Set(keys).size !== keys.length) {
381
334
  ctx.addIssue({
382
- code: import_zod11.z.ZodIssueCode.custom,
335
+ code: import_zod12.z.ZodIssueCode.custom,
383
336
  message: "`services` must not list the same service more than once",
384
337
  path: ["services"]
385
338
  });
@@ -388,7 +341,7 @@ function refineServiceXor(val, ctx, { requireAmount }) {
388
341
  const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
389
342
  if (val.amount_total_cents !== sum) {
390
343
  ctx.addIssue({
391
- code: import_zod11.z.ZodIssueCode.custom,
344
+ code: import_zod12.z.ZodIssueCode.custom,
392
345
  message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
393
346
  path: ["amount_total_cents"]
394
347
  });
@@ -396,37 +349,37 @@ function refineServiceXor(val, ctx, { requireAmount }) {
396
349
  }
397
350
  } else if (requireAmount && val.amount_total_cents == null) {
398
351
  ctx.addIssue({
399
- code: import_zod11.z.ZodIssueCode.custom,
352
+ code: import_zod12.z.ZodIssueCode.custom,
400
353
  message: "amount_total_cents is required unless `services` is provided",
401
354
  path: ["amount_total_cents"]
402
355
  });
403
356
  }
404
357
  }
405
- var saleCreateSchema = import_zod11.z.object({
406
- external_id: import_zod11.z.string().nullable().optional(),
407
- description: import_zod11.z.string().nullable().optional(),
408
- service: import_zod11.z.string().nullable().optional(),
409
- services: import_zod11.z.array(saleServiceSchema).nullable().optional(),
358
+ var saleCreateSchema = import_zod12.z.object({
359
+ external_id: import_zod12.z.string().nullable().optional(),
360
+ description: import_zod12.z.string().nullable().optional(),
361
+ service: import_zod12.z.string().nullable().optional(),
362
+ services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
410
363
  currency: currencySchema,
411
364
  // Optional only because the plural `services` form derives it from the sum
412
365
  // (see refineServiceXor); the singular/serviceless path still requires it.
413
366
  amount_total_cents: centsSchema.nullable().optional(),
414
- occurred_at: import_zod11.z.string().datetime(),
415
- environment: import_zod11.z.enum(TRACKING_ENVIRONMENTS).default("production"),
416
- items: import_zod11.z.array(saleItemSchema).default([]),
367
+ occurred_at: import_zod12.z.string().datetime(),
368
+ environment: import_zod12.z.enum(TRACKING_ENVIRONMENTS).default("production"),
369
+ items: import_zod12.z.array(saleItemSchema).default([]),
417
370
  metadata: metadataSchema.nullable().optional(),
418
371
  customer_name: customerNameSchema.nullable().optional(),
419
372
  customer_phone: customerPhoneSchema.nullable().optional(),
420
373
  customer_email: customerEmailSchema.nullable().optional()
421
374
  }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
422
- var saleUpdateSchema = import_zod11.z.object({
423
- description: import_zod11.z.string().nullable().optional(),
424
- service: import_zod11.z.string().nullable().optional(),
425
- services: import_zod11.z.array(saleServiceSchema).nullable().optional(),
375
+ var saleUpdateSchema = import_zod12.z.object({
376
+ description: import_zod12.z.string().nullable().optional(),
377
+ service: import_zod12.z.string().nullable().optional(),
378
+ services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
426
379
  currency: currencySchema.optional(),
427
380
  amount_total_cents: centsSchema.optional(),
428
- occurred_at: import_zod11.z.string().datetime().optional(),
429
- items: import_zod11.z.array(saleItemSchema).optional(),
381
+ occurred_at: import_zod12.z.string().datetime().optional(),
382
+ items: import_zod12.z.array(saleItemSchema).optional(),
430
383
  metadata: metadataSchema.nullable().optional(),
431
384
  customer_name: customerNameSchema.nullable().optional(),
432
385
  customer_phone: customerPhoneSchema.nullable().optional(),