@contentful/optimization-api-client 0.1.0-alpha15 → 0.1.0-alpha16

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/dist/index.d.ts CHANGED
@@ -213,7 +213,8 @@ export declare interface BaseFetchMethodOptions {
213
213
  *
214
214
  * @remarks
215
215
  * This is useful for providing polyfills, mocks, or instrumented fetch
216
- * implementations.
216
+ * implementations. It is required in runtimes that do not provide
217
+ * `globalThis.fetch`.
217
218
  */
218
219
  fetchMethod?: FetchMethod;
219
220
  }
@@ -609,14 +610,16 @@ export declare type Feature = 'ip-enrichment' | 'location';
609
610
  *
610
611
  * @example
611
612
  * ```ts
612
- * const method: FetchMethod = async (url, init) => {
613
- * return fetch(url, init)
613
+ * const method: FetchMethod = async (input, init) => {
614
+ * return fetch(input, init)
614
615
  * }
615
616
  * ```
616
617
  *
617
618
  * @public
618
619
  */
619
- export declare type FetchMethod = (url: string | URL, init: RequestInit) => Promise<Response>;
620
+ export declare type FetchInput = string | Request;
621
+
622
+ export declare type FetchMethod = (input: FetchInput, init?: RequestInit) => Promise<Response>;
620
623
 
621
624
  /**
622
625
  * Options passed to callback functions invoked by fetch wrappers.
@@ -664,7 +667,7 @@ export declare const INSIGHTS_BASE_URL = "https://ingest.insights.ninetailed.co/
664
667
  *
665
668
  * @remarks
666
669
  * This client is optimized for sending batched events, optionally using a
667
- * custom beacon-like handler when available.
670
+ * custom beacon-like sender for last-chance lifecycle delivery.
668
671
  *
669
672
  * @example
670
673
  * ```ts
@@ -696,10 +699,6 @@ export declare class InsightsApiClient extends ApiClientBase {
696
699
  * Base URL used for Insights API requests.
697
700
  */
698
701
  protected readonly baseUrl: string;
699
- /**
700
- * Optional handler used to enqueue events via the Beacon API or a similar mechanism.
701
- */
702
- private readonly beaconHandler;
703
702
  /**
704
703
  * Creates a new {@link InsightsApiClient} instance.
705
704
  *
@@ -710,9 +709,6 @@ export declare class InsightsApiClient extends ApiClientBase {
710
709
  * const client = new InsightsApiClient({
711
710
  * clientId: 'org-id',
712
711
  * environment: 'main',
713
- * beaconHandler: (url, data) => {
714
- * return navigator.sendBeacon(url.toString(), JSON.stringify(data))
715
- * },
716
712
  * })
717
713
  * ```
718
714
  */
@@ -721,18 +717,18 @@ export declare class InsightsApiClient extends ApiClientBase {
721
717
  * Sends batches of insights events to the Ninetailed Insights API.
722
718
  *
723
719
  * @param batches - Array of event batches to send.
724
- * @param options - Optional request options, including a per-call `beaconHandler`.
720
+ * @param options - Optional request options, including a per-call `beacon` sender.
725
721
  * @returns `true` when the event batch is successfully queued by the beacon
726
- * handler or a direct request is successfully sent, `false` otherwise.
722
+ * sender or a direct request is successfully sent, `false` otherwise.
727
723
  *
728
724
  * @remarks
729
- * If a `beaconHandler` is provided (either in the method call or in the
730
- * client configuration) it will be invoked first. When the handler returns
731
- * `true`, the events are considered successfully queued and no network
732
- * request is made by this method.
725
+ * If a `beacon` sender is provided, it will be invoked first with the
726
+ * serialized request body. When the sender returns `true`, the events are
727
+ * considered successfully queued and no network request is made by this
728
+ * method.
733
729
  *
734
- * If the handler is missing or returns `false`, the events are emitted
735
- * immediately via `fetch`.
730
+ * If the sender is missing, returns `false`, or throws, the events are
731
+ * emitted immediately via `fetch`.
736
732
  *
737
733
  * @example
738
734
  * ```ts
@@ -741,11 +737,9 @@ export declare class InsightsApiClient extends ApiClientBase {
741
737
  *
742
738
  * @example
743
739
  * ```ts
744
- * // Override beaconHandler for a single call
740
+ * // Use Beacon for a last-chance lifecycle flush
745
741
  * const success = await insightsClient.sendBatchEvents(batches, {
746
- * beaconHandler: (url, data) => {
747
- * return navigator.sendBeacon(url.toString(), JSON.stringify(data))
748
- * },
742
+ * beacon: (url, body) => navigator.sendBeacon(url, body),
749
743
  * })
750
744
  * ```
751
745
  */
@@ -757,7 +751,7 @@ export declare class InsightsApiClient extends ApiClientBase {
757
751
  *
758
752
  * @public
759
753
  */
760
- export declare interface InsightsApiClientConfig extends ApiConfig, InsightsApiClientRequestOptions {
754
+ export declare interface InsightsApiClientConfig extends ApiConfig {
761
755
  }
762
756
 
763
757
  /**
@@ -767,18 +761,18 @@ export declare interface InsightsApiClientConfig extends ApiConfig, InsightsApiC
767
761
  */
768
762
  export declare interface InsightsApiClientRequestOptions {
769
763
  /**
770
- * Handler used to enqueue events via the Beacon API or a similar mechanism.
764
+ * Sender used to enqueue serialized events via the Beacon API or a similar mechanism.
771
765
  *
772
766
  * @param url - Target URL for the batched events.
773
- * @param data - Array of batched insights events to be sent.
767
+ * @param body - Serialized batched insights events to be sent.
774
768
  * @returns `true` if the events were successfully queued, `false` otherwise.
775
769
  *
776
770
  * @remarks
777
- * When provided, this handler is preferred over direct `fetch` calls. If it
778
- * returns `false`, the client falls back to emitting events immediately via
779
- * `fetch`.
771
+ * This option is intended for last-chance lifecycle delivery. If it returns
772
+ * `false` or throws, the client falls back to emitting events immediately via
773
+ * `fetch` with `keepalive`.
780
774
  */
781
- beaconHandler?: (url: string | URL, data: BatchInsightsEventArray) => boolean;
775
+ beacon?: (url: string, body: string) => boolean;
782
776
  }
783
777
 
784
778
  /**
package/dist/index.mjs CHANGED
@@ -1,5 +1,15 @@
1
1
  import { createScopedLogger } from "./649.mjs";
2
2
  import { __rspack_external__contentful_optimization_api_schemas_66dcf407 } from "./199.mjs";
3
+ const fetchInputToString = (input)=>{
4
+ if ('string' == typeof input) return input;
5
+ return input.url;
6
+ };
7
+ const MISSING_FETCH_METHOD_MESSAGE = 'No fetch implementation available. Provide a fetchMethod.';
8
+ function resolveFetchMethod(fetchMethod) {
9
+ if (fetchMethod) return fetchMethod;
10
+ if ('function' == typeof globalThis.fetch) return globalThis.fetch.bind(globalThis);
11
+ throw new Error(MISSING_FETCH_METHOD_MESSAGE);
12
+ }
3
13
  const logger = createScopedLogger('ApiClient:Retry');
4
14
  const DEFAULT_INTERVAL_TIMEOUT = 0;
5
15
  const DEFAULT_RETRY_COUNT = 1;
@@ -13,22 +23,23 @@ class HttpError extends Error {
13
23
  this.status = status;
14
24
  }
15
25
  }
16
- function createRetryFetchCallback({ apiName = 'Optimization', controller, fetchMethod = fetch, init, url }) {
26
+ function createRetryFetchCallback({ apiName = 'Optimization', controller, fetchMethod, init, url }) {
17
27
  return async ()=>{
28
+ const urlLabel = fetchInputToString(url);
18
29
  try {
19
30
  const response = await fetchMethod(url, init);
20
- if (response.status === RETRY_RESPONSE_STATUS) throw new HttpError(`${apiName} API request to "${url.toString()}" failed with status: "[${response.status}] ${response.statusText}".`, RETRY_RESPONSE_STATUS);
31
+ if (response.status === RETRY_RESPONSE_STATUS) throw new HttpError(`${apiName} API request to "${urlLabel}" failed with status: "[${response.status}] ${response.statusText}".`, RETRY_RESPONSE_STATUS);
21
32
  if (!response.ok) {
22
- const httpError = new Error(`Request to "${url.toString()}" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`);
33
+ const httpError = new Error(`Request to "${urlLabel}" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`);
23
34
  logger.error('Request failed with non-OK status:', httpError);
24
35
  controller.abort();
25
36
  return;
26
37
  }
27
- logger.debug(`Response from "${url.toString()}":`, response);
38
+ logger.debug(`Response from "${urlLabel}":`, response);
28
39
  return response;
29
40
  } catch (error) {
30
41
  if (error instanceof HttpError && error.status === RETRY_RESPONSE_STATUS) throw error;
31
- logger.error(`Request to "${url.toString()}" failed:`, error);
42
+ logger.error(`Request to "${urlLabel}" failed:`, error);
32
43
  controller.abort();
33
44
  }
34
45
  };
@@ -41,14 +52,15 @@ async function delayRetry(intervalTimeout) {
41
52
  }, intervalTimeout);
42
53
  await promise;
43
54
  }
44
- function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod = fetch, intervalTimeout = DEFAULT_INTERVAL_TIMEOUT, onFailedAttempt, retries = DEFAULT_RETRY_COUNT } = {}) {
55
+ function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod, intervalTimeout = DEFAULT_INTERVAL_TIMEOUT, onFailedAttempt, retries = DEFAULT_RETRY_COUNT } = {}) {
56
+ const resolvedFetchMethod = resolveFetchMethod(fetchMethod);
45
57
  return async (url, init)=>{
46
58
  const controller = new AbortController();
47
59
  const maxAttempts = retries + 1;
48
60
  const attemptFetch = createRetryFetchCallback({
49
61
  apiName,
50
62
  controller,
51
- fetchMethod,
63
+ fetchMethod: resolvedFetchMethod,
52
64
  init,
53
65
  url
54
66
  });
@@ -68,28 +80,32 @@ function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod = fetch,
68
80
  if (0 === retriesLeft) throw error;
69
81
  await delayRetry(intervalTimeout);
70
82
  }
71
- throw new Error(`${apiName} API request to "${url.toString()}" may not be retried.`);
83
+ throw new Error(`${apiName} API request to "${fetchInputToString(url)}" may not be retried.`);
72
84
  };
73
85
  }
74
86
  const createTimeoutFetchMethod_logger = createScopedLogger('ApiClient:Timeout');
75
87
  const DEFAULT_REQUEST_TIMEOUT = 3000;
76
- function createTimeoutFetchMethod({ apiName = 'Optimization', fetchMethod = fetch, onRequestTimeout, requestTimeout = DEFAULT_REQUEST_TIMEOUT } = {}) {
88
+ function isFetchAbortSignal(signal) {
89
+ return 'object' == typeof signal && null !== signal;
90
+ }
91
+ function createTimeoutFetchMethod({ apiName = 'Optimization', fetchMethod, onRequestTimeout, requestTimeout = DEFAULT_REQUEST_TIMEOUT } = {}) {
92
+ const resolvedFetchMethod = resolveFetchMethod(fetchMethod);
77
93
  return async (url, init)=>{
78
94
  const controller = new AbortController();
79
95
  const id = setTimeout(()=>{
80
96
  if ('function' == typeof onRequestTimeout) onRequestTimeout({
81
97
  apiName
82
98
  });
83
- else createTimeoutFetchMethod_logger.error(`Request to "${url.toString()}" timed out`, new Error('Request timeout'));
99
+ else createTimeoutFetchMethod_logger.error(`Request to "${fetchInputToString(url)}" timed out`, new Error('Request timeout'));
84
100
  controller.abort();
85
101
  }, requestTimeout);
102
+ const { signal } = controller;
103
+ if (!isFetchAbortSignal(signal)) throw new Error('AbortController signal is not compatible with fetch.');
86
104
  const requestInit = {
87
- ...init
105
+ ...init,
106
+ signal
88
107
  };
89
- Object.assign(requestInit, {
90
- signal: controller.signal
91
- });
92
- const response = await fetchMethod(url, requestInit);
108
+ const response = await resolvedFetchMethod(url, requestInit);
93
109
  clearTimeout(id);
94
110
  return response;
95
111
  };
@@ -113,7 +129,7 @@ const Fetch = {
113
129
  create: createProtectedFetchMethod
114
130
  };
115
131
  const fetch_Fetch = Fetch;
116
- const src_fetch = fetch_Fetch;
132
+ const fetch = fetch_Fetch;
117
133
  const ApiClientBase_logger = createScopedLogger('ApiClient');
118
134
  const DEFAULT_ENVIRONMENT = 'main';
119
135
  class ApiClientBase {
@@ -125,7 +141,7 @@ class ApiClientBase {
125
141
  this.clientId = clientId;
126
142
  this.environment = environment ?? DEFAULT_ENVIRONMENT;
127
143
  this.name = name;
128
- this.fetch = src_fetch.create({
144
+ this.fetch = fetch.create({
129
145
  ...fetchOptions ?? {},
130
146
  apiName: name
131
147
  });
@@ -310,37 +326,42 @@ class ExperienceApiClient extends src_ApiClientBase {
310
326
  }
311
327
  }
312
328
  const InsightsApiClient_logger = createScopedLogger('ApiClient:Insights');
329
+ const BEACON_FALLBACK_MESSAGE = 'beacon failed; falling back to fetch';
313
330
  const INSIGHTS_BASE_URL = 'https://ingest.insights.ninetailed.co/';
314
331
  class InsightsApiClient extends src_ApiClientBase {
315
332
  baseUrl;
316
- beaconHandler;
317
333
  constructor(config){
318
334
  super('Insights', config);
319
- const { baseUrl, beaconHandler } = config;
335
+ const { baseUrl } = config;
320
336
  this.baseUrl = baseUrl || INSIGHTS_BASE_URL;
321
- this.beaconHandler = beaconHandler;
322
337
  }
323
338
  async sendBatchEvents(batches, options = {}) {
324
- const { beaconHandler = this.beaconHandler } = options;
339
+ const { beacon } = options;
325
340
  const url = new URL(`v1/organizations/${this.clientId}/environments/${this.environment}/events`, this.baseUrl);
341
+ const urlString = url.toString();
326
342
  const body = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(__rspack_external__contentful_optimization_api_schemas_66dcf407.BatchInsightsEventArray, batches);
327
- if ('function' == typeof beaconHandler) {
328
- InsightsApiClient_logger.debug('Queueing events via beaconHandler');
329
- const beaconSuccessfullyQueued = beaconHandler(url, body);
330
- if (beaconSuccessfullyQueued) return true;
331
- InsightsApiClient_logger.warn('beaconHandler failed to queue events; events will be emitted immediately via fetch');
343
+ const serializedBody = JSON.stringify(body);
344
+ if (beacon) {
345
+ InsightsApiClient_logger.debug('Queueing events via beacon');
346
+ try {
347
+ const beaconSuccessfullyQueued = beacon(urlString, serializedBody);
348
+ if (beaconSuccessfullyQueued) return true;
349
+ InsightsApiClient_logger.warn(BEACON_FALLBACK_MESSAGE);
350
+ } catch (error) {
351
+ InsightsApiClient_logger.warn(BEACON_FALLBACK_MESSAGE, error);
352
+ }
332
353
  }
333
354
  const requestName = 'Event Batches';
334
355
  InsightsApiClient_logger.info(`Sending "${requestName}" request`);
335
356
  InsightsApiClient_logger.debug(`"${requestName}" request body:`, body);
336
357
  try {
337
- await this.fetch(url, {
358
+ await this.fetch(urlString, {
338
359
  method: 'POST',
339
360
  headers: {
340
361
  'Content-Type': 'application/json'
341
362
  },
342
- body: JSON.stringify(body),
343
- keepalive: true
363
+ body: serializedBody,
364
+ keepalive: !!beacon
344
365
  });
345
366
  InsightsApiClient_logger.debug(`"${requestName}" request successfully completed`);
346
367
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/fetch/createRetryFetchMethod.ts","../src/fetch/createTimeoutFetchMethod.ts","../src/fetch/createProtectedFetchMethod.ts","../src/fetch/Fetch.ts","../src/fetch/index.ts","../src/ApiClientBase.ts","../src/experience/ExperienceApiClient.ts","../src/insights/InsightsApiClient.ts","../src/ApiClient.ts"],"sourcesContent":["import { createScopedLogger } from '../logger'\nimport type { BaseFetchMethodOptions, FetchMethod, FetchMethodCallbackOptions } from './Fetch'\n\nconst logger = createScopedLogger('ApiClient:Retry')\n\n/**\n * Default interval (in milliseconds) between retry attempts.\n *\n * @internal\n */\nconst DEFAULT_INTERVAL_TIMEOUT = 0\n\n/**\n * Default number of retry attempts.\n *\n * @internal\n */\nconst DEFAULT_RETRY_COUNT = 1\n\n/**\n * HTTP status code that triggers a retry.\n *\n * @remarks\n * This value is fixed to `503 Service Unavailable`.\n *\n * @internal\n */\nconst RETRY_RESPONSE_STATUS = 503\n\n/**\n * Default HTTP status code used for {@link HttpError}.\n *\n * @internal\n */\nconst HTTP_ERROR_RESPONSE_STATUS = 500\n\n/**\n * Error type representing HTTP failures with an associated status code.\n *\n * @internal\n */\nclass HttpError extends Error {\n /**\n * The HTTP status code associated with the error.\n */\n public status: number\n\n /**\n * Creates a new {@link HttpError}.\n *\n * @param message - Description of the error.\n * @param status - HTTP status code associated with the error.\n */\n constructor(message: string, status: number = HTTP_ERROR_RESPONSE_STATUS) {\n super(message)\n Object.setPrototypeOf(this, HttpError.prototype)\n this.status = status\n }\n}\n\n/**\n * Configuration options for {@link createRetryFetchMethod}.\n *\n * @public\n */\nexport interface RetryFetchMethodOptions extends BaseFetchMethodOptions {\n /**\n * Delay (in milliseconds) between retry attempts.\n *\n * @defaultValue `0`\n */\n intervalTimeout?: number\n\n /**\n * Callback invoked whenever a retry attempt fails.\n *\n * @param options - Information about the failed attempt.\n *\n * @remarks\n * This callback is invoked with additional metadata such as the attempt\n * number and the number of retries left.\n */\n onFailedAttempt?: (options: FetchMethodCallbackOptions) => void\n\n /**\n * Maximum number of retry attempts.\n *\n * @defaultValue `1`\n */\n retries?: number\n}\n\n/**\n * Internal configuration passed to the retry callback.\n *\n * @internal\n */\ninterface RetryFetchCallbackOptions extends RetryFetchMethodOptions {\n /**\n * Abort controller used to cancel the underlying fetch requests.\n */\n controller: AbortController\n\n /**\n * Initialization options passed to the `fetch` implementation.\n */\n init: RequestInit\n\n /**\n * Request URL.\n */\n url: string | URL\n}\n\n/**\n * Creates a callback function used by the retry loop to perform a protected fetch attempt.\n *\n * @param options - Internal options controlling request execution and retry classification.\n * @returns A function that performs one fetch attempt and either:\n * - returns a successful {@link Response},\n * - throws {@link HttpError} for retriable `503` responses, or\n * - aborts and returns `undefined` for non-retriable failures.\n *\n * @internal\n */\nfunction createRetryFetchCallback({\n apiName = 'Optimization',\n controller,\n fetchMethod = fetch,\n init,\n url,\n}: RetryFetchCallbackOptions) {\n return async () => {\n try {\n const response = await fetchMethod(url, init)\n\n if (response.status === RETRY_RESPONSE_STATUS) {\n throw new HttpError(\n `${apiName} API request to \"${url.toString()}\" failed with status: \"[${response.status}] ${response.statusText}\".`,\n RETRY_RESPONSE_STATUS,\n )\n }\n\n if (!response.ok) {\n const httpError = new Error(\n `Request to \"${url.toString()}\" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`,\n )\n logger.error('Request failed with non-OK status:', httpError)\n\n controller.abort()\n\n return\n }\n\n logger.debug(`Response from \"${url.toString()}\":`, response)\n\n return response\n } catch (error) {\n if (error instanceof HttpError && error.status === RETRY_RESPONSE_STATUS) {\n throw error\n }\n\n logger.error(`Request to \"${url.toString()}\" failed:`, error)\n\n controller.abort()\n }\n }\n}\n\n/**\n * Waits for the configured retry delay between attempts.\n *\n * @param intervalTimeout - Delay in milliseconds before the next retry attempt.\n * @returns A promise that resolves immediately when delay is non-positive,\n * otherwise after the timeout elapses.\n *\n * @internal\n */\nasync function delayRetry(intervalTimeout: number): Promise<void> {\n if (intervalTimeout <= 0) {\n return\n }\n\n const { promise, resolve } = Promise.withResolvers<undefined>()\n setTimeout(() => {\n resolve(undefined)\n }, intervalTimeout)\n await promise\n}\n\n/**\n * Creates a {@link FetchMethod} that retries failed requests according to the\n * provided configuration.\n *\n * @param options - Configuration options that control retry behavior.\n * @returns A {@link FetchMethod} that automatically retries qualifying failures.\n *\n * @throws Error\n * Thrown when the request cannot be retried and no successful response is obtained.\n *\n * @remarks\n * This wrapper uses a lightweight internal retry loop and an {@link AbortController}\n * to cancel pending requests when a non-retriable error occurs.\n *\n * @example\n * ```ts\n * const fetchWithRetry = createRetryFetchMethod({\n * apiName: 'Optimization',\n * retries: 3,\n * intervalTimeout: 200,\n * onFailedAttempt: ({ attemptNumber, retriesLeft }) => {\n * console.warn(`Attempt ${attemptNumber} failed. Retries left: ${retriesLeft}`)\n * },\n * })\n *\n * const response = await fetchWithRetry('https://example.com', { method: 'GET' })\n * ```\n *\n * @public\n */\nexport function createRetryFetchMethod({\n apiName = 'Optimization',\n fetchMethod = fetch,\n intervalTimeout = DEFAULT_INTERVAL_TIMEOUT,\n onFailedAttempt,\n retries = DEFAULT_RETRY_COUNT,\n}: RetryFetchMethodOptions = {}): FetchMethod {\n return async (url: string | URL, init: RequestInit) => {\n const controller = new AbortController()\n const maxAttempts = retries + 1\n const attemptFetch = createRetryFetchCallback({\n apiName,\n controller,\n fetchMethod,\n init,\n url,\n })\n\n for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber++) {\n try {\n const response = await attemptFetch()\n\n if (response) {\n return response\n }\n\n break\n } catch (error) {\n if (!(error instanceof HttpError) || error.status !== RETRY_RESPONSE_STATUS) {\n throw error\n }\n\n const retriesLeft = maxAttempts - attemptNumber\n\n onFailedAttempt?.({\n apiName,\n error,\n attemptNumber,\n retriesLeft,\n } satisfies FetchMethodCallbackOptions)\n\n if (retriesLeft === 0) {\n throw error\n }\n\n await delayRetry(intervalTimeout)\n }\n }\n\n throw new Error(`${apiName} API request to \"${url.toString()}\" may not be retried.`)\n }\n}\n","import { createScopedLogger } from '../logger'\nimport type { BaseFetchMethodOptions, FetchMethod, FetchMethodCallbackOptions } from './Fetch'\n\nconst logger = createScopedLogger('ApiClient:Timeout')\n\n/**\n * Default timeout (in milliseconds) for outgoing requests.\n *\n * @internal\n */\nconst DEFAULT_REQUEST_TIMEOUT = 3000\n\n/**\n * Configuration options for {@link createTimeoutFetchMethod}.\n *\n * @public\n */\nexport interface TimeoutFetchMethodOptions extends BaseFetchMethodOptions {\n /**\n * Callback invoked when a request exceeds the configured timeout.\n *\n * @param options - Information about the timed-out request.\n *\n * @remarks\n * If this callback is not provided, a default error is logged.\n *\n * @see {@link FetchMethodCallbackOptions}\n */\n onRequestTimeout?: (options: FetchMethodCallbackOptions) => void\n\n /**\n * Maximum time (in milliseconds) to wait for a response before aborting the request.\n *\n * @defaultValue `3000`\n */\n requestTimeout?: number\n}\n\n/**\n * Creates a {@link FetchMethod} that aborts requests after a configurable timeout.\n *\n * @param options - Configuration options controlling timeout behavior.\n * @returns A {@link FetchMethod} that enforces a timeout for each request.\n *\n * @remarks\n * When a timeout occurs, the request is aborted using an {@link AbortController}.\n * If `onRequestTimeout` is not provided, an error is logged by the package logger.\n *\n * @example\n * ```ts\n * const fetchWithTimeout = createTimeoutFetchMethod({\n * apiName: 'Optimization',\n * requestTimeout: 5000,\n * onRequestTimeout: ({ apiName }) => {\n * console.warn(`${apiName} request timed out`)\n * },\n * })\n *\n * const response = await fetchWithTimeout('https://example.com', { method: 'GET' })\n * ```\n *\n * @see {@link TimeoutFetchMethodOptions}\n *\n * @public\n */\nexport function createTimeoutFetchMethod({\n apiName = 'Optimization',\n fetchMethod = fetch,\n onRequestTimeout,\n requestTimeout = DEFAULT_REQUEST_TIMEOUT,\n}: TimeoutFetchMethodOptions = {}): FetchMethod {\n return async (url: string | URL, init: RequestInit) => {\n const controller = new AbortController()\n\n const id = setTimeout(() => {\n if (typeof onRequestTimeout === 'function') {\n onRequestTimeout({ apiName })\n } else {\n logger.error(`Request to \"${url.toString()}\" timed out`, new Error('Request timeout'))\n }\n\n controller.abort()\n }, requestTimeout)\n\n const requestInit: RequestInit = { ...init }\n Object.assign(requestInit, { signal: controller.signal })\n\n const response = await fetchMethod(url, requestInit)\n\n clearTimeout(id)\n\n return response\n }\n}\n","import { createScopedLogger } from '../logger'\nimport { createRetryFetchMethod, type RetryFetchMethodOptions } from './createRetryFetchMethod'\nimport {\n createTimeoutFetchMethod,\n type TimeoutFetchMethodOptions,\n} from './createTimeoutFetchMethod'\nimport type { FetchMethod } from './Fetch'\n\nconst logger = createScopedLogger('ApiClient:Fetch')\n\n/**\n * Options for {@link createProtectedFetchMethod}, combining timeout and retry behavior.\n *\n * @see {@link RetryFetchMethodOptions}\n * @see {@link TimeoutFetchMethodOptions}\n *\n * @public\n */\nexport interface ProtectedFetchMethodOptions\n extends RetryFetchMethodOptions, TimeoutFetchMethodOptions {}\n\n/**\n * Creates a {@link FetchMethod} that combines timeout and retry protection.\n *\n * @param options - Configuration options for both timeout and retry behavior.\n * @returns A {@link FetchMethod} that applies timeout and retry logic to requests.\n *\n * @throws Error\n * Rethrows the original error after logging, including abort errors.\n *\n * @remarks\n * The resulting method first wraps the base fetch with a timeout (via\n * {@link createTimeoutFetchMethod}), then applies retry behavior (via\n * {@link createRetryFetchMethod}).\n *\n * If an error is thrown during configuration or request execution, it is logged.\n *\n * @example\n * ```ts\n * const fetchProtected = createProtectedFetchMethod({\n * apiName: 'Optimization',\n * requestTimeout: 4000,\n * retries: 2,\n * })\n *\n * const response = await fetchProtected('https://example.com/experiences', {\n * method: 'GET',\n * })\n * ```\n *\n * @public\n */\nexport function createProtectedFetchMethod(options: ProtectedFetchMethodOptions): FetchMethod {\n try {\n const timeoutFetchMethod = createTimeoutFetchMethod(options)\n const retryFetchMethod = createRetryFetchMethod({ ...options, fetchMethod: timeoutFetchMethod })\n\n return retryFetchMethod\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n logger.warn('Request aborted due to network issues. This request may not be retried.')\n } else {\n logger.error('Request failed:', error)\n }\n }\n throw error\n }\n}\n","import { createProtectedFetchMethod } from './createProtectedFetchMethod'\n\n/**\n * Signature of a fetch method used by the API clients.\n *\n * @param url - The request URL.\n * @param init - Initialization options passed to `fetch`.\n * @returns A promise that resolves with the {@link Response}.\n *\n * @remarks\n * This abstraction allows the underlying implementation to be replaced,\n * for example in tests or different runtime environments.\n *\n * @example\n * ```ts\n * const method: FetchMethod = async (url, init) => {\n * return fetch(url, init)\n * }\n * ```\n *\n * @public\n */\nexport type FetchMethod = (url: string | URL, init: RequestInit) => Promise<Response>\n\n/**\n * Base options shared across fetch method factories.\n *\n * @public\n */\nexport interface BaseFetchMethodOptions {\n /**\n * Human-readable name of the API being called.\n *\n * @remarks\n * Used primarily for logging and error messages.\n */\n apiName?: string\n\n /**\n * Custom fetch implementation to use instead of the global `fetch`.\n *\n * @remarks\n * This is useful for providing polyfills, mocks, or instrumented fetch\n * implementations.\n */\n fetchMethod?: FetchMethod\n}\n\n/**\n * Options passed to callback functions invoked by fetch wrappers.\n *\n * @remarks\n * Not all fields are guaranteed to be present in all callback scenarios.\n *\n * @public\n */\nexport interface FetchMethodCallbackOptions {\n /**\n * Name of the API associated with the request.\n */\n apiName?: string\n\n /**\n * Error that caused the callback to be invoked, if available.\n */\n error?: Error\n\n /**\n * The current attempt number (for retry callbacks).\n */\n attemptNumber?: number\n\n /**\n * Number of retry attempts remaining (for retry callbacks).\n */\n retriesLeft?: number\n}\n\n/**\n * Namespace-like object providing factory methods for protected fetch functions.\n *\n * @public\n */\nconst Fetch = {\n /**\n * Creates a fully protected fetch method with timeout and retry behavior.\n *\n * @example\n * ```ts\n * const fetchMethod = Fetch.create({\n * apiName: 'Optimization',\n * requestTimeout: 3000,\n * retries: 2,\n * })\n *\n * const response = await fetchMethod('https://example.com', { method: 'GET' })\n * ```\n *\n * @see {@link createProtectedFetchMethod}\n */\n create: createProtectedFetchMethod,\n}\n\nexport default Fetch\n","import Fetch from './Fetch'\n\nexport * from './createProtectedFetchMethod'\nexport * from './createRetryFetchMethod'\nexport * from './createTimeoutFetchMethod'\nexport * from './Fetch'\n\nexport default Fetch\n","import Fetch, { type FetchMethod, type ProtectedFetchMethodOptions } from './fetch'\nimport { createScopedLogger } from './logger'\n\nconst logger = createScopedLogger('ApiClient')\n\n/**\n * Default Contentful environment used when none is explicitly provided.\n *\n * @internal\n */\nconst DEFAULT_ENVIRONMENT = 'main'\n\n/**\n * Configuration options for API clients extending `ApiClientBase`.\n *\n * @public\n */\nexport interface ApiConfig {\n /**\n * Base URL for the API.\n *\n * @remarks\n * When omitted, the concrete client is expected to construct full URLs\n * internally.\n */\n baseUrl?: string\n\n /**\n * Contentful environment identifier.\n *\n * @defaultValue `'main'`\n */\n environment?: string\n\n /**\n * Options used to configure the underlying protected fetch method.\n *\n * @remarks\n * `apiName` is derived from the client name and must not be provided here.\n */\n fetchOptions?: Omit<ProtectedFetchMethodOptions, 'apiName'>\n\n /**\n * Client identifier used for authentication or tracking.\n */\n clientId: string\n}\n\n/**\n * Properties that can be shared between global and per-client API configuration.\n *\n * @public\n */\nexport type GlobalApiConfigProperties = 'environment' | 'fetchOptions' | 'clientId'\n\n/**\n * Base class for API clients that provides shared configuration and error logging.\n *\n * @remarks\n * This abstract class is intended for internal use within the package and\n * must not be treated as part of the public API surface.\n *\n * Concrete API clients must extend this class to inherit consistent logging\n * behavior and fetch configuration.\n *\n * @example\n * ```ts\n * interface MyClientConfig extends ApiConfig {\n * // additional config\n * }\n *\n * class MyClient extends ApiClientBase {\n * constructor(config: MyClientConfig) {\n * super('MyClient', config)\n * }\n *\n * async getSomething() {\n * const response = await this.fetch('https://example.com', { method: 'GET' })\n * return response.json()\n * }\n * }\n * ```\n *\n * @internal\n */\nabstract class ApiClientBase {\n /**\n * Name of the API client, used in log messages and as the `apiName` for fetch.\n */\n protected readonly name: string\n\n /**\n * Client identifier used for authentication or tracking.\n */\n protected readonly clientId: string\n\n /**\n * Contentful environment associated with this client.\n */\n protected readonly environment: string\n\n /**\n * Protected fetch method used by the client to perform HTTP requests.\n */\n protected readonly fetch: FetchMethod\n\n /**\n * Creates a new API client base instance.\n *\n * @param name - Human-readable name of the client (used for logging and `apiName`).\n * @param config - Configuration options for the client.\n */\n constructor(name: string, { fetchOptions, clientId, environment }: ApiConfig) {\n this.clientId = clientId\n this.environment = environment ?? DEFAULT_ENVIRONMENT\n this.name = name\n\n this.fetch = Fetch.create({ ...(fetchOptions ?? {}), apiName: name })\n }\n\n /**\n * Logs errors that occur during API requests with standardized messages.\n *\n * @param error - The error thrown by the underlying operation.\n * @param options - Additional metadata about the request.\n * @param options.requestName - Human-readable name of the request operation.\n *\n * @remarks\n * Abort errors are logged at `warn` level and other errors at `error` level.\n * The log message includes the client name for better debugging context.\n */\n protected logRequestError(error: unknown, { requestName }: { requestName: string }): void {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n logger.warn(\n `[${this.name}] \"${requestName}\" request aborted due to network issues. This request may not be retried.`,\n )\n } else {\n logger.error(`[${this.name}] \"${requestName}\" request failed:`, error)\n }\n }\n }\n}\n\nexport default ApiClientBase\n","import {\n BatchExperienceRequestData,\n BatchExperienceResponse,\n ExperienceEventArray,\n ExperienceRequestData,\n ExperienceResponse,\n parseWithFriendlyError,\n type BatchExperienceEventArray,\n type BatchExperienceResponseData,\n type ExperienceRequestOptions as ExperienceRequestBodyOptions,\n type OptimizationData,\n} from '@contentful/optimization-api-schemas'\nimport ApiClientBase, { type ApiConfig } from '../ApiClientBase'\nimport { createScopedLogger } from '../logger'\n\nconst logger = createScopedLogger('ApiClient:Experience')\n\n/**\n * Default base URL for the Experience API.\n *\n * @public\n */\nexport const EXPERIENCE_BASE_URL = 'https://experience.ninetailed.co/'\n\n/**\n * Feature flags supported by the Experience API.\n *\n * @internal\n */\nexport type Feature = 'ip-enrichment' | 'location'\n\n/**\n * Options that control how requests to the Experience API are handled.\n *\n * @public\n */\nexport interface ExperienceApiClientRequestOptions {\n /**\n * Enabled features (for example, `\"ip-enrichment\"`) for the API to use for this request.\n *\n * @remarks\n * When omitted, the API can apply a default set of features.\n */\n enabledFeatures?: Feature[]\n\n /**\n * IP address to override the API behavior for IP analysis.\n *\n * @remarks\n * Commonly used in ESR or SSR environments, as the API otherwise uses\n * the server IP.\n */\n ip?: string\n\n /**\n * Experience API `locale` query parameter used to translate profile location fields such as\n * `location.city` and `location.country`.\n *\n * @remarks\n * When omitted, the API can use a server-side default.\n */\n locale?: string\n\n /**\n * When `true`, sends performance-critical endpoints in plain text.\n *\n * @remarks\n * The Ninetailed API accepts certain endpoints in plain text to avoid CORS\n * preflight requests, which can improve performance in browser environments.\n */\n plainText?: boolean\n\n /**\n * When `true`, instructs the API to aggregate a new profile state but not store it.\n *\n * @remarks\n * This is commonly used in ESR or SSR environments where you want to\n * preview the result without persisting changes.\n */\n preflight?: boolean\n}\n\n/**\n * Internal options for profile mutation requests.\n *\n * @internal\n */\ninterface ProfileMutationRequestOptions {\n url: string\n body: unknown\n options: ExperienceApiClientRequestOptions\n}\n\n/**\n * Parameters used when creating a profile.\n *\n * @public\n */\nexport interface CreateProfileParams {\n /**\n * Events used to aggregate the profile state.\n */\n events: ExperienceEventArray\n}\n\n/**\n * Parameters used when updating an existing profile.\n *\n * @public\n */\nexport interface UpdateProfileParams extends CreateProfileParams {\n /**\n * ID of the profile to update.\n */\n profileId: string\n}\n\n/**\n * Parameters used when creating or updating a profile.\n *\n * @public\n */\nexport interface UpsertProfileParams extends CreateProfileParams {\n /**\n * Optional ID of the profile; when omitted, a new profile is created.\n */\n profileId?: string\n}\n\n/**\n * Parameters used when performing a batch profile update.\n *\n * @public\n */\nexport interface BatchUpdateProfileParams {\n /**\n * Batch of events to process.\n */\n events: BatchExperienceEventArray\n}\n\n/**\n * Configuration for {@link ExperienceApiClient}.\n *\n * @public\n */\nexport interface ExperienceApiClientConfig extends ApiConfig, ExperienceApiClientRequestOptions {}\n\n/**\n * Client for interacting with the Experience API.\n *\n * @remarks\n * This client is responsible for reading and mutating Ninetailed profiles\n * using the Experience API.\n *\n * @example\n * ```ts\n * const client = new ExperienceApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * })\n *\n * const profile = await client.getProfile('profile-id')\n * ```\n *\n * Extends `ApiClientBase`.\n *\n * @public\n */\nexport default class ExperienceApiClient extends ApiClientBase {\n /**\n * Base URL used for Experience API requests.\n */\n protected readonly baseUrl: string\n\n private readonly enabledFeatures?: ExperienceApiClientRequestOptions['enabledFeatures']\n private readonly ip?: ExperienceApiClientRequestOptions['ip']\n private locale?: ExperienceApiClientRequestOptions['locale']\n private readonly plainText?: ExperienceApiClientRequestOptions['plainText']\n private readonly preflight?: ExperienceApiClientRequestOptions['preflight']\n\n /**\n * Creates a new {@link ExperienceApiClient} instance.\n *\n * @param config - Configuration for the Experience API client.\n */\n constructor(config: ExperienceApiClientConfig) {\n super('Experience', config)\n\n const { baseUrl, enabledFeatures, ip, locale, plainText, preflight } = config\n\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Set default for anything falsey\n this.baseUrl = baseUrl || EXPERIENCE_BASE_URL\n this.enabledFeatures = enabledFeatures\n this.ip = ip\n this.locale = locale\n this.plainText = plainText\n this.preflight = preflight\n }\n\n /**\n * Update the default Experience API locale used when request options omit `locale`.\n *\n * @param locale - Next default Experience API locale.\n *\n * @public\n */\n public setLocale(locale: ExperienceApiClientRequestOptions['locale']): void {\n this.locale = locale\n }\n\n /**\n * Retrieves a profile by ID.\n *\n * @param id - The profile ID to retrieve.\n * @param options - Optional request options. `preflight` and `plainText` are not allowed here.\n * @returns The current optimization data for the profile.\n *\n * @throws Error\n * Thrown if `id` is missing or the underlying request fails.\n *\n * @example\n * ```ts\n * const profile = await client.getProfile('profile-id', {\n * locale: 'en-US',\n * })\n * ```\n */\n public async getProfile(\n id: string,\n options: Omit<ExperienceApiClientRequestOptions, 'preflight' | 'plainText'> = {},\n ): Promise<OptimizationData> {\n if (!id) throw new Error('Valid profile ID required.')\n\n const requestName = 'Get Profile'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n try {\n const response = await this.fetch(\n this.constructUrl(\n `v2/organizations/${this.clientId}/environments/${this.environment}/profiles/${id}`,\n options,\n ),\n {\n method: 'GET',\n },\n )\n\n const {\n data: { changes, experiences, profile },\n } = parseWithFriendlyError(ExperienceResponse, await response.json())\n\n const data = { changes, selectedOptimizations: experiences, profile }\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return data\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Sends a POST request to mutate a profile or profiles.\n *\n * @param request - Mutation request options including URL, body, and request options.\n * @returns The raw {@link Response} from the underlying fetch.\n *\n * @internal\n */\n private async makeProfileMutationRequest({\n url,\n body,\n options,\n }: ProfileMutationRequestOptions): Promise<Response> {\n return await this.fetch(this.constructUrl(url, options), {\n method: 'POST',\n headers: this.constructHeaders(options),\n body: JSON.stringify(body),\n keepalive: true,\n })\n }\n\n /**\n * Creates a profile and returns the resulting optimization data.\n *\n * @param params - Parameters containing the events to aggregate into the profile.\n * @param options - Optional request options.\n * @returns The optimization data for the newly created profile.\n *\n * @remarks\n * The returned profile ID can be used for subsequent update requests.\n *\n * @example\n * ```ts\n * const data = await client.createProfile({\n * events: [{ type: 'identify', userId: 'user-123' }],\n * })\n * ```\n */\n public async createProfile(\n { events }: CreateProfileParams,\n options: ExperienceApiClientRequestOptions = {},\n ): Promise<OptimizationData> {\n const requestName = 'Create Profile'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n const body = this.constructExperienceRequestBody(events, options)\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n const response = await this.makeProfileMutationRequest({\n url: `v2/organizations/${this.clientId}/environments/${this.environment}/profiles`,\n body,\n options,\n })\n\n const {\n data: { changes, experiences, profile },\n } = parseWithFriendlyError(ExperienceResponse, await response.json())\n\n const data = { changes, selectedOptimizations: experiences, profile }\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return data\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Updates an existing profile with the given profile ID.\n *\n * @param params - Parameters including the profile ID and events.\n * @param options - Optional request options.\n * @returns The updated optimization data for the profile.\n *\n * @throws Error\n * Thrown if `profileId` is missing or the underlying request fails.\n *\n * @example\n * ```ts\n * const data = await client.updateProfile({\n * profileId: 'profile-id',\n * events: [{ type: 'track', event: 'viewed_video' }],\n * })\n * ```\n */\n public async updateProfile(\n { profileId, events }: UpdateProfileParams,\n options: ExperienceApiClientRequestOptions = {},\n ): Promise<OptimizationData> {\n if (!profileId) throw new Error('Valid profile ID required.')\n\n const requestName = 'Update Profile'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n const body = this.constructExperienceRequestBody(events, options)\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n const response = await this.makeProfileMutationRequest({\n url: `v2/organizations/${this.clientId}/environments/${this.environment}/profiles/${profileId}`,\n body,\n options,\n })\n\n const {\n data: { changes, experiences, profile },\n } = parseWithFriendlyError(ExperienceResponse, await response.json())\n\n const data = { changes, selectedOptimizations: experiences, profile }\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return data\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Creates or updates a profile depending on whether a `profileId` is provided.\n *\n * @param params - Parameters including optional profile ID and events.\n * @param options - Optional request options.\n * @returns The resulting optimization data.\n *\n * @example\n * ```ts\n * // Create\n * await client.upsertProfile({ events })\n *\n * // Update\n * await client.upsertProfile({ profileId: 'profile-id', events })\n * ```\n */\n public async upsertProfile(\n { profileId, events }: UpsertProfileParams,\n options?: ExperienceApiClientRequestOptions,\n ): Promise<OptimizationData> {\n if (!profileId) {\n return await this.createProfile({ events }, options)\n } else {\n return await this.updateProfile({ profileId, events }, options)\n }\n }\n\n /**\n * Sends multiple events to the Ninetailed Experience API to upsert many profiles.\n *\n * @param params - Parameters containing the batch of events.\n * @param options - Optional request options.\n * @returns The list of profiles affected by the batch operation.\n *\n * @remarks\n * Batch requests must contain at least one event. Every event must contain\n * an anonymous ID. Profiles will be created or updated according to the\n * anonymous ID.\n *\n * This method is intended to be used from server environments.\n *\n * @example\n * ```ts\n * const profiles = await client.upsertManyProfiles({\n * events: [\n * { anonymousId: 'anon-1', type: 'identify', ... },\n * { anonymousId: 'anon-2', type: 'identify', ... },\n * ],\n * })\n * ```\n */\n public async upsertManyProfiles(\n { events }: BatchUpdateProfileParams,\n options: ExperienceApiClientRequestOptions = {},\n ): Promise<BatchExperienceResponseData['profiles']> {\n const requestName = 'Upsert Many Profiles'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n const body = parseWithFriendlyError(BatchExperienceRequestData, {\n events,\n options: this.constructBodyOptions(options),\n })\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n const response = await this.makeProfileMutationRequest({\n url: `v2/organizations/${this.clientId}/environments/${this.environment}/events`,\n body,\n options: { plainText: false, ...options },\n })\n\n const {\n data: { profiles },\n } = parseWithFriendlyError(BatchExperienceResponse, await response.json())\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return profiles\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Constructs a request URL with query parameters derived from request options.\n *\n * @param path - Path relative to the Experience API base URL.\n * @param options - Request options that can influence query parameters.\n * @returns The fully constructed URL as a string.\n *\n * @internal\n */\n private constructUrl(path: string, options: ExperienceApiClientRequestOptions): string {\n const url = new URL(path, this.baseUrl)\n const locale = options.locale ?? this.locale\n const preflight = options.preflight ?? this.preflight\n\n if (locale) {\n url.searchParams.set('locale', locale)\n }\n\n if (preflight) {\n url.searchParams.set('type', 'preflight')\n }\n\n return url.toString()\n }\n\n /**\n * Constructs request headers based on request options and default configuration.\n *\n * @param options - Request options that can influence headers.\n * @returns A record of HTTP headers to send with the request.\n *\n * @internal\n */\n private constructHeaders({\n ip = this.ip,\n plainText = this.plainText,\n }: ExperienceApiClientRequestOptions): Record<string, string> {\n const headers = new Map<string, string>()\n\n if (ip) {\n headers.set('X-Force-IP', ip)\n }\n\n if (plainText ?? this.plainText ?? true) {\n headers.set('Content-Type', 'text/plain')\n } else {\n headers.set('Content-Type', 'application/json')\n }\n\n return Object.fromEntries(headers)\n }\n\n /**\n * Constructs the `options` section of the request body for profile mutations.\n *\n * @param options - Request options that can specify enabled features.\n * @returns Experience API body options including feature flags.\n *\n * @internal\n */\n private readonly constructBodyOptions = ({\n enabledFeatures = this.enabledFeatures,\n }: ExperienceApiClientRequestOptions): ExperienceRequestBodyOptions => {\n const bodyOptions: ExperienceRequestBodyOptions = {}\n\n if (enabledFeatures && Array.isArray(enabledFeatures) && enabledFeatures.length > 0) {\n bodyOptions.features = enabledFeatures\n } else {\n bodyOptions.features = ['ip-enrichment', 'location']\n }\n\n return bodyOptions\n }\n\n /**\n * Construct and validate the singular Experience mutation request payload.\n *\n * @param events - Events for the Experience API to evaluate.\n * @param options - Request options that can contribute body options.\n * @returns Validated singular Experience request body.\n *\n * @internal\n */\n private constructExperienceRequestBody(\n events: ExperienceEventArray,\n options: ExperienceApiClientRequestOptions,\n ): ExperienceRequestData {\n return ExperienceRequestData.parse({\n events: parseWithFriendlyError(ExperienceEventArray, events),\n options: this.constructBodyOptions(options),\n })\n }\n}\n","import {\n BatchInsightsEventArray,\n parseWithFriendlyError,\n} from '@contentful/optimization-api-schemas'\nimport ApiClientBase, { type ApiConfig } from '../ApiClientBase'\nimport { createScopedLogger } from '../logger'\n\nconst logger = createScopedLogger('ApiClient:Insights')\n\n/**\n * Default base URL for the Insights ingest API.\n *\n * @public\n */\nexport const INSIGHTS_BASE_URL = 'https://ingest.insights.ninetailed.co/'\n\n/**\n * Options that control how Insights events are sent.\n *\n * @public\n */\nexport interface InsightsApiClientRequestOptions {\n /**\n * Handler used to enqueue events via the Beacon API or a similar mechanism.\n *\n * @param url - Target URL for the batched events.\n * @param data - Array of batched insights events to be sent.\n * @returns `true` if the events were successfully queued, `false` otherwise.\n *\n * @remarks\n * When provided, this handler is preferred over direct `fetch` calls. If it\n * returns `false`, the client falls back to emitting events immediately via\n * `fetch`.\n */\n beaconHandler?: (url: string | URL, data: BatchInsightsEventArray) => boolean\n}\n\n/**\n * Configuration for {@link InsightsApiClient}.\n *\n * @public\n */\nexport interface InsightsApiClientConfig extends ApiConfig, InsightsApiClientRequestOptions {}\n\n/**\n * Client for sending analytics and insights events to the Ninetailed Insights API.\n *\n * @remarks\n * This client is optimized for sending batched events, optionally using a\n * custom beacon-like handler when available.\n *\n * @example\n * ```ts\n * const insightsClient = new InsightsApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * })\n *\n * await insightsClient.sendBatchEvents([\n * {\n * profile: { id: 'profile-123', ... },\n * events: [\n * {\n * type: 'track',\n * event: 'button_clicked',\n * properties: { id: 'primary-cta' },\n * },\n * ],\n * }\n * ])\n * ```\n *\n * Extends `ApiClientBase`.\n *\n * @public\n */\nexport default class InsightsApiClient extends ApiClientBase {\n /**\n * Base URL used for Insights API requests.\n */\n protected readonly baseUrl: string\n\n /**\n * Optional handler used to enqueue events via the Beacon API or a similar mechanism.\n */\n private readonly beaconHandler: InsightsApiClientRequestOptions['beaconHandler']\n\n /**\n * Creates a new {@link InsightsApiClient} instance.\n *\n * @param config - Configuration for the Insights API client.\n *\n * @example\n * ```ts\n * const client = new InsightsApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * beaconHandler: (url, data) => {\n * return navigator.sendBeacon(url.toString(), JSON.stringify(data))\n * },\n * })\n * ```\n */\n constructor(config: InsightsApiClientConfig) {\n super('Insights', config)\n\n const { baseUrl, beaconHandler } = config\n\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Set default for anything falsey\n this.baseUrl = baseUrl || INSIGHTS_BASE_URL\n this.beaconHandler = beaconHandler\n }\n\n /**\n * Sends batches of insights events to the Ninetailed Insights API.\n *\n * @param batches - Array of event batches to send.\n * @param options - Optional request options, including a per-call `beaconHandler`.\n * @returns `true` when the event batch is successfully queued by the beacon\n * handler or a direct request is successfully sent, `false` otherwise.\n *\n * @remarks\n * If a `beaconHandler` is provided (either in the method call or in the\n * client configuration) it will be invoked first. When the handler returns\n * `true`, the events are considered successfully queued and no network\n * request is made by this method.\n *\n * If the handler is missing or returns `false`, the events are emitted\n * immediately via `fetch`.\n *\n * @example\n * ```ts\n * const success = await insightsClient.sendBatchEvents(batches)\n * ```\n *\n * @example\n * ```ts\n * // Override beaconHandler for a single call\n * const success = await insightsClient.sendBatchEvents(batches, {\n * beaconHandler: (url, data) => {\n * return navigator.sendBeacon(url.toString(), JSON.stringify(data))\n * },\n * })\n * ```\n */\n public async sendBatchEvents(\n batches: BatchInsightsEventArray,\n options: InsightsApiClientRequestOptions = {},\n ): Promise<boolean> {\n const { beaconHandler = this.beaconHandler } = options\n\n const url = new URL(\n `v1/organizations/${this.clientId}/environments/${this.environment}/events`,\n this.baseUrl,\n )\n\n const body = parseWithFriendlyError(BatchInsightsEventArray, batches)\n\n if (typeof beaconHandler === 'function') {\n logger.debug('Queueing events via beaconHandler')\n\n const beaconSuccessfullyQueued = beaconHandler(url, body)\n\n if (beaconSuccessfullyQueued) {\n return true\n } else {\n logger.warn(\n 'beaconHandler failed to queue events; events will be emitted immediately via fetch',\n )\n }\n }\n\n const requestName = 'Event Batches'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n await this.fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n keepalive: true,\n })\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return true\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n return false\n }\n }\n}\n","import type { ApiConfig, GlobalApiConfigProperties } from './ApiClientBase'\nimport { ExperienceApiClient, type ExperienceApiClientConfig } from './experience'\nimport { InsightsApiClient, type InsightsApiClientConfig } from './insights'\n\n/**\n * Configuration for the high-level {@link ApiClient}.\n *\n * @public\n */\nexport interface ApiClientConfig extends Pick<ApiConfig, GlobalApiConfigProperties> {\n /**\n * Configuration for the Experience API client.\n *\n * @remarks\n * Shared fields (`clientId`, `environment`, `fetchOptions`) are inherited\n * from top-level config; this object is for Experience-specific options.\n */\n experience?: Omit<ExperienceApiClientConfig, GlobalApiConfigProperties>\n\n /**\n * Configuration for the Insights API client.\n *\n * @remarks\n * Shared fields (`clientId`, `environment`, `fetchOptions`) are inherited\n * from top-level config; this object is for Insights-specific options.\n */\n insights?: Omit<InsightsApiClientConfig, GlobalApiConfigProperties>\n}\n\n/**\n * Aggregated API client providing access to Experience and Insights APIs.\n *\n * @remarks\n * This client encapsulates shared configuration and exposes dedicated\n * sub-clients for experience and insights use cases.\n *\n * @example\n * ```ts\n * const client = new ApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * experience: {\n * // experience-specific overrides\n * },\n * insights: {\n * // insights-specific overrides\n * },\n * })\n *\n * const profile = await client.experience.getProfile('profile-id')\n * await client.insights.sendBatchEvents(batches)\n * ```\n *\n * @see {@link ExperienceApiClient}\n * @see {@link InsightsApiClient}\n *\n * @public\n */\nexport default class ApiClient {\n /**\n * Shared configuration applied to both Experience and Insights clients.\n *\n * @see {@link ApiConfig}\n */\n readonly config: ApiConfig\n\n /**\n * Client for experience-related operations.\n *\n * @see {@link ExperienceApiClient}\n */\n readonly experience: ExperienceApiClient\n\n /**\n * Client for insights-related operations.\n *\n * @see {@link InsightsApiClient}\n */\n readonly insights: InsightsApiClient\n\n /**\n * Creates a new aggregated {@link ApiClient} instance.\n *\n * @param config - Global API client configuration with optional per-client overrides.\n */\n constructor(config: ApiClientConfig) {\n const { experience, insights, clientId, environment, fetchOptions } = config\n const apiConfig: ApiConfig = {\n clientId,\n environment,\n fetchOptions,\n }\n\n this.config = apiConfig\n\n this.experience = new ExperienceApiClient({\n ...apiConfig,\n ...experience,\n })\n\n this.insights = new InsightsApiClient({\n ...apiConfig,\n ...insights,\n })\n }\n}\n"],"names":["logger","createScopedLogger","DEFAULT_INTERVAL_TIMEOUT","DEFAULT_RETRY_COUNT","RETRY_RESPONSE_STATUS","HTTP_ERROR_RESPONSE_STATUS","HttpError","Error","message","status","Object","createRetryFetchCallback","apiName","controller","fetchMethod","fetch","init","url","response","httpError","error","delayRetry","intervalTimeout","promise","resolve","Promise","setTimeout","undefined","createRetryFetchMethod","onFailedAttempt","retries","AbortController","maxAttempts","attemptFetch","attemptNumber","retriesLeft","DEFAULT_REQUEST_TIMEOUT","createTimeoutFetchMethod","onRequestTimeout","requestTimeout","id","requestInit","clearTimeout","createProtectedFetchMethod","options","timeoutFetchMethod","retryFetchMethod","Fetch","DEFAULT_ENVIRONMENT","ApiClientBase","name","fetchOptions","clientId","environment","requestName","EXPERIENCE_BASE_URL","ExperienceApiClient","config","baseUrl","enabledFeatures","ip","locale","plainText","preflight","changes","experiences","profile","parseWithFriendlyError","ExperienceResponse","data","body","JSON","events","profileId","BatchExperienceRequestData","profiles","BatchExperienceResponse","path","URL","headers","Map","bodyOptions","Array","ExperienceRequestData","ExperienceEventArray","INSIGHTS_BASE_URL","InsightsApiClient","beaconHandler","batches","BatchInsightsEventArray","beaconSuccessfullyQueued","ApiClient","experience","insights","apiConfig"],"mappings":";;AAGA,MAAMA,SAASC,mBAAmB;AAOlC,MAAMC,2BAA2B;AAOjC,MAAMC,sBAAsB;AAU5B,MAAMC,wBAAwB;AAO9B,MAAMC,6BAA6B;AAOnC,MAAMC,kBAAkBC;IAIf,OAAc;IAQrB,YAAYC,OAAe,EAAEC,SAAiBJ,0BAA0B,CAAE;QACxE,KAAK,CAACG;QACNE,OAAO,cAAc,CAAC,IAAI,EAAEJ,UAAU,SAAS;QAC/C,IAAI,CAAC,MAAM,GAAGG;IAChB;AACF;AAmEA,SAASE,yBAAyB,EAChCC,UAAU,cAAc,EACxBC,UAAU,EACVC,cAAcC,KAAK,EACnBC,IAAI,EACJC,GAAG,EACuB;IAC1B,OAAO;QACL,IAAI;YACF,MAAMC,WAAW,MAAMJ,YAAYG,KAAKD;YAExC,IAAIE,SAAS,MAAM,KAAKd,uBACtB,MAAM,IAAIE,UACR,GAAGM,QAAQ,iBAAiB,EAAEK,IAAI,QAAQ,GAAG,wBAAwB,EAAEC,SAAS,MAAM,CAAC,EAAE,EAAEA,SAAS,UAAU,CAAC,EAAE,CAAC,EAClHd;YAIJ,IAAI,CAACc,SAAS,EAAE,EAAE;gBAChB,MAAMC,YAAY,IAAIZ,MACpB,CAAC,YAAY,EAAEU,IAAI,QAAQ,GAAG,uBAAuB,EAAEC,SAAS,MAAM,CAAC,EAAE,EAAEA,SAAS,UAAU,CAAC,gBAAgB,EAAEA,SAAS,OAAO,CAAC,GAAG,CAAC,gBAAgB;gBAExJlB,OAAO,KAAK,CAAC,sCAAsCmB;gBAEnDN,WAAW,KAAK;gBAEhB;YACF;YAEAb,OAAO,KAAK,CAAC,CAAC,eAAe,EAAEiB,IAAI,QAAQ,GAAG,EAAE,CAAC,EAAEC;YAEnD,OAAOA;QACT,EAAE,OAAOE,OAAO;YACd,IAAIA,iBAAiBd,aAAac,MAAM,MAAM,KAAKhB,uBACjD,MAAMgB;YAGRpB,OAAO,KAAK,CAAC,CAAC,YAAY,EAAEiB,IAAI,QAAQ,GAAG,SAAS,CAAC,EAAEG;YAEvDP,WAAW,KAAK;QAClB;IACF;AACF;AAWA,eAAeQ,WAAWC,eAAuB;IAC/C,IAAIA,mBAAmB,GACrB;IAGF,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAE,GAAGC,QAAQ,aAAa;IAClDC,WAAW;QACTF,QAAQG;IACV,GAAGL;IACH,MAAMC;AACR;AAgCO,SAASK,uBAAuB,EACrChB,UAAU,cAAc,EACxBE,cAAcC,KAAK,EACnBO,kBAAkBpB,wBAAwB,EAC1C2B,eAAe,EACfC,UAAU3B,mBAAmB,EACL,GAAG,CAAC,CAAC;IAC7B,OAAO,OAAOc,KAAmBD;QAC/B,MAAMH,aAAa,IAAIkB;QACvB,MAAMC,cAAcF,UAAU;QAC9B,MAAMG,eAAetB,yBAAyB;YAC5CC;YACAC;YACAC;YACAE;YACAC;QACF;QAEA,IAAK,IAAIiB,gBAAgB,GAAGA,iBAAiBF,aAAaE,gBACxD,IAAI;YACF,MAAMhB,WAAW,MAAMe;YAEvB,IAAIf,UACF,OAAOA;YAGT;QACF,EAAE,OAAOE,OAAO;YACd,IAAI,CAAEA,CAAAA,iBAAiBd,SAAQ,KAAMc,MAAM,MAAM,KAAKhB,uBACpD,MAAMgB;YAGR,MAAMe,cAAcH,cAAcE;YAElCL,kBAAkB;gBAChBjB;gBACAQ;gBACAc;gBACAC;YACF;YAEA,IAAIA,AAAgB,MAAhBA,aACF,MAAMf;YAGR,MAAMC,WAAWC;QACnB;QAGF,MAAM,IAAIf,MAAM,GAAGK,QAAQ,iBAAiB,EAAEK,IAAI,QAAQ,GAAG,qBAAqB,CAAC;IACrF;AACF;AC5QA,MAAMjB,kCAASC,mBAAmB;AAOlC,MAAMmC,0BAA0B;AAuDzB,SAASC,yBAAyB,EACvCzB,UAAU,cAAc,EACxBE,cAAcC,KAAK,EACnBuB,gBAAgB,EAChBC,iBAAiBH,uBAAuB,EACd,GAAG,CAAC,CAAC;IAC/B,OAAO,OAAOnB,KAAmBD;QAC/B,MAAMH,aAAa,IAAIkB;QAEvB,MAAMS,KAAKd,WAAW;YACpB,IAAI,AAA4B,cAA5B,OAAOY,kBACTA,iBAAiB;gBAAE1B;YAAQ;iBAE3BZ,gCAAO,KAAK,CAAC,CAAC,YAAY,EAAEiB,IAAI,QAAQ,GAAG,WAAW,CAAC,EAAE,IAAIV,MAAM;YAGrEM,WAAW,KAAK;QAClB,GAAG0B;QAEH,MAAME,cAA2B;YAAE,GAAGzB,IAAI;QAAC;QAC3CN,OAAO,MAAM,CAAC+B,aAAa;YAAE,QAAQ5B,WAAW,MAAM;QAAC;QAEvD,MAAMK,WAAW,MAAMJ,YAAYG,KAAKwB;QAExCC,aAAaF;QAEb,OAAOtB;IACT;AACF;ACrFA,MAAMlB,oCAASC,mBAAmB;AA4C3B,SAAS0C,2BAA2BC,OAAoC;IAC7E,IAAI;QACF,MAAMC,qBAAqBR,yBAAyBO;QACpD,MAAME,mBAAmBlB,uBAAuB;YAAE,GAAGgB,OAAO;YAAE,aAAaC;QAAmB;QAE9F,OAAOC;IACT,EAAE,OAAO1B,OAAO;QACd,IAAIA,iBAAiBb,OACnB,IAAIa,AAAe,iBAAfA,MAAM,IAAI,EACZpB,kCAAO,IAAI,CAAC;aAEZA,kCAAO,KAAK,CAAC,mBAAmBoB;QAGpC,MAAMA;IACR;AACF;ACeA,MAAM2B,QAAQ;IAiBZ,QAAQJ;AACV;AAEA,oBAAeI;AChGf,kBAAeA;ACJf,MAAM/C,uBAASC,mBAAmB;AAOlC,MAAM+C,sBAAsB;AA2E5B,MAAeC;IAIM,KAAY;IAKZ,SAAgB;IAKhB,YAAmB;IAKnB,MAAkB;IAQrC,YAAYC,IAAY,EAAE,EAAEC,YAAY,EAAEC,QAAQ,EAAEC,WAAW,EAAa,CAAE;QAC5E,IAAI,CAAC,QAAQ,GAAGD;QAChB,IAAI,CAAC,WAAW,GAAGC,eAAeL;QAClC,IAAI,CAAC,IAAI,GAAGE;QAEZ,IAAI,CAAC,KAAK,GAAGH,UAAAA,MAAY,CAAC;YAAE,GAAII,gBAAgB,CAAC,CAAC;YAAG,SAASD;QAAK;IACrE;IAaU,gBAAgB9B,KAAc,EAAE,EAAEkC,WAAW,EAA2B,EAAQ;QACxF,IAAIlC,iBAAiBb,OACnB,IAAIa,AAAe,iBAAfA,MAAM,IAAI,EACZpB,qBAAO,IAAI,CACT,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAEsD,YAAY,yEAAyE,CAAC;aAG3GtD,qBAAO,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAEsD,YAAY,iBAAiB,CAAC,EAAElC;IAGtE;AACF;AAEA,0BAAe6B;ACjIf,MAAMjD,6BAASC,mBAAmB;AAO3B,MAAMsD,sBAAsB;AAmJpB,MAAMC,4BAA4BP;IAI5B,QAAe;IAEjB,gBAAsE;IACtE,GAA4C;IACrD,OAAoD;IAC3C,UAA0D;IAC1D,UAA0D;IAO3E,YAAYQ,MAAiC,CAAE;QAC7C,KAAK,CAAC,cAAcA;QAEpB,MAAM,EAAEC,OAAO,EAAEC,eAAe,EAAEC,EAAE,EAAEC,MAAM,EAAEC,SAAS,EAAEC,SAAS,EAAE,GAAGN;QAGvE,IAAI,CAAC,OAAO,GAAGC,WAAWH;QAC1B,IAAI,CAAC,eAAe,GAAGI;QACvB,IAAI,CAAC,EAAE,GAAGC;QACV,IAAI,CAAC,MAAM,GAAGC;QACd,IAAI,CAAC,SAAS,GAAGC;QACjB,IAAI,CAAC,SAAS,GAAGC;IACnB;IASO,UAAUF,MAAmD,EAAQ;QAC1E,IAAI,CAAC,MAAM,GAAGA;IAChB;IAmBA,MAAa,WACXrB,EAAU,EACVI,UAA8E,CAAC,CAAC,EACrD;QAC3B,IAAI,CAACJ,IAAI,MAAM,IAAIjC,MAAM;QAEzB,MAAM+C,cAAc;QAEpBtD,2BAAO,IAAI,CAAC,CAAC,SAAS,EAAEsD,YAAY,SAAS,CAAC;QAE9C,IAAI;YACF,MAAMpC,WAAW,MAAM,IAAI,CAAC,KAAK,CAC/B,IAAI,CAAC,YAAY,CACf,CAAC,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,UAAU,EAAEsB,IAAI,EACnFI,UAEF;gBACE,QAAQ;YACV;YAGF,MAAM,EACJ,MAAM,EAAEoB,OAAO,EAAEC,WAAW,EAAEC,OAAO,EAAE,EACxC,GAAGC,AAAAA,IAAAA,gEAAAA,sBAAAA,AAAAA,EAAuBC,gEAAAA,kBAAkBA,EAAE,MAAMlD,SAAS,IAAI;YAElE,MAAMmD,OAAO;gBAAEL;gBAAS,uBAAuBC;gBAAaC;YAAQ;YAEpElE,2BAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,gCAAgC,CAAC;YAE9D,OAAOe;QACT,EAAE,OAAOjD,OAAO;YACd,IAAI,CAAC,eAAe,CAACA,OAAO;gBAAEkC;YAAY;YAE1C,MAAMlC;QACR;IACF;IAUA,MAAc,2BAA2B,EACvCH,GAAG,EACHqD,IAAI,EACJ1B,OAAO,EACuB,EAAqB;QACnD,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC3B,KAAK2B,UAAU;YACvD,QAAQ;YACR,SAAS,IAAI,CAAC,gBAAgB,CAACA;YAC/B,MAAM2B,KAAK,SAAS,CAACD;YACrB,WAAW;QACb;IACF;IAmBA,MAAa,cACX,EAAEE,MAAM,EAAuB,EAC/B5B,UAA6C,CAAC,CAAC,EACpB;QAC3B,MAAMU,cAAc;QAEpBtD,2BAAO,IAAI,CAAC,CAAC,SAAS,EAAEsD,YAAY,SAAS,CAAC;QAE9C,MAAMgB,OAAO,IAAI,CAAC,8BAA8B,CAACE,QAAQ5B;QAEzD5C,2BAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,eAAe,CAAC,EAAEgB;QAE/C,IAAI;YACF,MAAMpD,WAAW,MAAM,IAAI,CAAC,0BAA0B,CAAC;gBACrD,KAAK,CAAC,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC;gBAClFoD;gBACA1B;YACF;YAEA,MAAM,EACJ,MAAM,EAAEoB,OAAO,EAAEC,WAAW,EAAEC,OAAO,EAAE,EACxC,GAAGC,AAAAA,IAAAA,gEAAAA,sBAAAA,AAAAA,EAAuBC,gEAAAA,kBAAkBA,EAAE,MAAMlD,SAAS,IAAI;YAElE,MAAMmD,OAAO;gBAAEL;gBAAS,uBAAuBC;gBAAaC;YAAQ;YAEpElE,2BAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,gCAAgC,CAAC;YAE9D,OAAOe;QACT,EAAE,OAAOjD,OAAO;YACd,IAAI,CAAC,eAAe,CAACA,OAAO;gBAAEkC;YAAY;YAE1C,MAAMlC;QACR;IACF;IAoBA,MAAa,cACX,EAAEqD,SAAS,EAAED,MAAM,EAAuB,EAC1C5B,UAA6C,CAAC,CAAC,EACpB;QAC3B,IAAI,CAAC6B,WAAW,MAAM,IAAIlE,MAAM;QAEhC,MAAM+C,cAAc;QAEpBtD,2BAAO,IAAI,CAAC,CAAC,SAAS,EAAEsD,YAAY,SAAS,CAAC;QAE9C,MAAMgB,OAAO,IAAI,CAAC,8BAA8B,CAACE,QAAQ5B;QAEzD5C,2BAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,eAAe,CAAC,EAAEgB;QAE/C,IAAI;YACF,MAAMpD,WAAW,MAAM,IAAI,CAAC,0BAA0B,CAAC;gBACrD,KAAK,CAAC,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,UAAU,EAAEuD,WAAW;gBAC/FH;gBACA1B;YACF;YAEA,MAAM,EACJ,MAAM,EAAEoB,OAAO,EAAEC,WAAW,EAAEC,OAAO,EAAE,EACxC,GAAGC,AAAAA,IAAAA,gEAAAA,sBAAAA,AAAAA,EAAuBC,gEAAAA,kBAAkBA,EAAE,MAAMlD,SAAS,IAAI;YAElE,MAAMmD,OAAO;gBAAEL;gBAAS,uBAAuBC;gBAAaC;YAAQ;YAEpElE,2BAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,gCAAgC,CAAC;YAE9D,OAAOe;QACT,EAAE,OAAOjD,OAAO;YACd,IAAI,CAAC,eAAe,CAACA,OAAO;gBAAEkC;YAAY;YAE1C,MAAMlC;QACR;IACF;IAkBA,MAAa,cACX,EAAEqD,SAAS,EAAED,MAAM,EAAuB,EAC1C5B,OAA2C,EAChB;QAC3B,IAAI,CAAC6B,WACH,OAAO,MAAM,IAAI,CAAC,aAAa,CAAC;YAAED;QAAO,GAAG5B;QAE5C,OAAO,MAAM,IAAI,CAAC,aAAa,CAAC;YAAE6B;YAAWD;QAAO,GAAG5B;IAE3D;IA0BA,MAAa,mBACX,EAAE4B,MAAM,EAA4B,EACpC5B,UAA6C,CAAC,CAAC,EACG;QAClD,MAAMU,cAAc;QAEpBtD,2BAAO,IAAI,CAAC,CAAC,SAAS,EAAEsD,YAAY,SAAS,CAAC;QAE9C,MAAMgB,OAAOH,AAAAA,IAAAA,gEAAAA,sBAAAA,AAAAA,EAAuBO,gEAAAA,0BAA0BA,EAAE;YAC9DF;YACA,SAAS,IAAI,CAAC,oBAAoB,CAAC5B;QACrC;QAEA5C,2BAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,eAAe,CAAC,EAAEgB;QAE/C,IAAI;YACF,MAAMpD,WAAW,MAAM,IAAI,CAAC,0BAA0B,CAAC;gBACrD,KAAK,CAAC,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;gBAChFoD;gBACA,SAAS;oBAAE,WAAW;oBAAO,GAAG1B,OAAO;gBAAC;YAC1C;YAEA,MAAM,EACJ,MAAM,EAAE+B,QAAQ,EAAE,EACnB,GAAGR,AAAAA,IAAAA,gEAAAA,sBAAAA,AAAAA,EAAuBS,gEAAAA,uBAAuBA,EAAE,MAAM1D,SAAS,IAAI;YAEvElB,2BAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,gCAAgC,CAAC;YAE9D,OAAOqB;QACT,EAAE,OAAOvD,OAAO;YACd,IAAI,CAAC,eAAe,CAACA,OAAO;gBAAEkC;YAAY;YAE1C,MAAMlC;QACR;IACF;IAWQ,aAAayD,IAAY,EAAEjC,OAA0C,EAAU;QACrF,MAAM3B,MAAM,IAAI6D,IAAID,MAAM,IAAI,CAAC,OAAO;QACtC,MAAMhB,SAASjB,QAAQ,MAAM,IAAI,IAAI,CAAC,MAAM;QAC5C,MAAMmB,YAAYnB,QAAQ,SAAS,IAAI,IAAI,CAAC,SAAS;QAErD,IAAIiB,QACF5C,IAAI,YAAY,CAAC,GAAG,CAAC,UAAU4C;QAGjC,IAAIE,WACF9C,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ;QAG/B,OAAOA,IAAI,QAAQ;IACrB;IAUQ,iBAAiB,EACvB2C,KAAK,IAAI,CAAC,EAAE,EACZE,YAAY,IAAI,CAAC,SAAS,EACQ,EAA0B;QAC5D,MAAMiB,UAAU,IAAIC;QAEpB,IAAIpB,IACFmB,QAAQ,GAAG,CAAC,cAAcnB;QAG5B,IAAIE,aAAa,IAAI,CAAC,SAAS,IAAI,MACjCiB,QAAQ,GAAG,CAAC,gBAAgB;aAE5BA,QAAQ,GAAG,CAAC,gBAAgB;QAG9B,OAAOrE,OAAO,WAAW,CAACqE;IAC5B;IAUiB,uBAAuB,CAAC,EACvCpB,kBAAkB,IAAI,CAAC,eAAe,EACJ;QAClC,MAAMsB,cAA4C,CAAC;QAEnD,IAAItB,mBAAmBuB,MAAM,OAAO,CAACvB,oBAAoBA,gBAAgB,MAAM,GAAG,GAChFsB,YAAY,QAAQ,GAAGtB;aAEvBsB,YAAY,QAAQ,GAAG;YAAC;YAAiB;SAAW;QAGtD,OAAOA;IACT,EAAC;IAWO,+BACNT,MAA4B,EAC5B5B,OAA0C,EACnB;QACvB,OAAOuC,gEAAAA,qBAAAA,CAAAA,KAA2B,CAAC;YACjC,QAAQhB,AAAAA,IAAAA,gEAAAA,sBAAAA,AAAAA,EAAuBiB,gEAAAA,oBAAoBA,EAAEZ;YACrD,SAAS,IAAI,CAAC,oBAAoB,CAAC5B;QACrC;IACF;AACF;ACrjBA,MAAM5C,2BAASC,mBAAmB;AAO3B,MAAMoF,oBAAoB;AA8DlB,MAAMC,0BAA0BrC;IAI1B,QAAe;IAKjB,cAA+D;IAkBhF,YAAYQ,MAA+B,CAAE;QAC3C,KAAK,CAAC,YAAYA;QAElB,MAAM,EAAEC,OAAO,EAAE6B,aAAa,EAAE,GAAG9B;QAGnC,IAAI,CAAC,OAAO,GAAGC,WAAW2B;QAC1B,IAAI,CAAC,aAAa,GAAGE;IACvB;IAkCA,MAAa,gBACXC,OAAgC,EAChC5C,UAA2C,CAAC,CAAC,EAC3B;QAClB,MAAM,EAAE2C,gBAAgB,IAAI,CAAC,aAAa,EAAE,GAAG3C;QAE/C,MAAM3B,MAAM,IAAI6D,IACd,CAAC,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAC3E,IAAI,CAAC,OAAO;QAGd,MAAMR,OAAOH,AAAAA,IAAAA,gEAAAA,sBAAAA,AAAAA,EAAuBsB,gEAAAA,uBAAuBA,EAAED;QAE7D,IAAI,AAAyB,cAAzB,OAAOD,eAA8B;YACvCvF,yBAAO,KAAK,CAAC;YAEb,MAAM0F,2BAA2BH,cAActE,KAAKqD;YAEpD,IAAIoB,0BACF,OAAO;YAEP1F,yBAAO,IAAI,CACT;QAGN;QAEA,MAAMsD,cAAc;QAEpBtD,yBAAO,IAAI,CAAC,CAAC,SAAS,EAAEsD,YAAY,SAAS,CAAC;QAE9CtD,yBAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,eAAe,CAAC,EAAEgB;QAE/C,IAAI;YACF,MAAM,IAAI,CAAC,KAAK,CAACrD,KAAK;gBACpB,QAAQ;gBACR,SAAS;oBACP,gBAAgB;gBAClB;gBACA,MAAMsD,KAAK,SAAS,CAACD;gBACrB,WAAW;YACb;YAEAtE,yBAAO,KAAK,CAAC,CAAC,CAAC,EAAEsD,YAAY,gCAAgC,CAAC;YAE9D,OAAO;QACT,EAAE,OAAOlC,OAAO;YACd,IAAI,CAAC,eAAe,CAACA,OAAO;gBAAEkC;YAAY;YAE1C,OAAO;QACT;IACF;AACF;AC3Ie,MAAMqC;IAMV,OAAiB;IAOjB,WAA+B;IAO/B,SAA2B;IAOpC,YAAYlC,MAAuB,CAAE;QACnC,MAAM,EAAEmC,UAAU,EAAEC,QAAQ,EAAEzC,QAAQ,EAAEC,WAAW,EAAEF,YAAY,EAAE,GAAGM;QACtE,MAAMqC,YAAuB;YAC3B1C;YACAC;YACAF;QACF;QAEA,IAAI,CAAC,MAAM,GAAG2C;QAEd,IAAI,CAAC,UAAU,GAAG,IAAItC,oBAAoB;YACxC,GAAGsC,SAAS;YACZ,GAAGF,UAAU;QACf;QAEA,IAAI,CAAC,QAAQ,GAAG,IAAIN,kBAAkB;YACpC,GAAGQ,SAAS;YACZ,GAAGD,QAAQ;QACb;IACF;AACF"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/fetch/fetchInputToString.ts","../src/fetch/resolveFetchMethod.ts","../src/fetch/createRetryFetchMethod.ts","../src/fetch/createTimeoutFetchMethod.ts","../src/fetch/createProtectedFetchMethod.ts","../src/fetch/Fetch.ts","../src/fetch/index.ts","../src/ApiClientBase.ts","../src/experience/ExperienceApiClient.ts","../src/insights/InsightsApiClient.ts","../src/ApiClient.ts"],"sourcesContent":["import type { FetchMethod } from './Fetch'\n\nexport const fetchInputToString = (input: Parameters<FetchMethod>[0]): string => {\n if (typeof input === 'string') return input\n\n return input.url\n}\n","import type { FetchMethod } from './Fetch'\n\nconst MISSING_FETCH_METHOD_MESSAGE = 'No fetch implementation available. Provide a fetchMethod.'\n\nexport function resolveFetchMethod(fetchMethod?: FetchMethod): FetchMethod {\n if (fetchMethod) return fetchMethod\n\n if (typeof globalThis.fetch === 'function') {\n return globalThis.fetch.bind(globalThis)\n }\n\n throw new Error(MISSING_FETCH_METHOD_MESSAGE)\n}\n","import { createScopedLogger } from '../logger'\nimport type { BaseFetchMethodOptions, FetchMethod, FetchMethodCallbackOptions } from './Fetch'\nimport { fetchInputToString } from './fetchInputToString'\nimport { resolveFetchMethod } from './resolveFetchMethod'\n\nconst logger = createScopedLogger('ApiClient:Retry')\n\n/**\n * Default interval (in milliseconds) between retry attempts.\n *\n * @internal\n */\nconst DEFAULT_INTERVAL_TIMEOUT = 0\n\n/**\n * Default number of retry attempts.\n *\n * @internal\n */\nconst DEFAULT_RETRY_COUNT = 1\n\n/**\n * HTTP status code that triggers a retry.\n *\n * @remarks\n * This value is fixed to `503 Service Unavailable`.\n *\n * @internal\n */\nconst RETRY_RESPONSE_STATUS = 503\n\n/**\n * Default HTTP status code used for {@link HttpError}.\n *\n * @internal\n */\nconst HTTP_ERROR_RESPONSE_STATUS = 500\n\n/**\n * Error type representing HTTP failures with an associated status code.\n *\n * @internal\n */\nclass HttpError extends Error {\n /**\n * The HTTP status code associated with the error.\n */\n public status: number\n\n /**\n * Creates a new {@link HttpError}.\n *\n * @param message - Description of the error.\n * @param status - HTTP status code associated with the error.\n */\n constructor(message: string, status: number = HTTP_ERROR_RESPONSE_STATUS) {\n super(message)\n Object.setPrototypeOf(this, HttpError.prototype)\n this.status = status\n }\n}\n\n/**\n * Configuration options for {@link createRetryFetchMethod}.\n *\n * @public\n */\nexport interface RetryFetchMethodOptions extends BaseFetchMethodOptions {\n /**\n * Delay (in milliseconds) between retry attempts.\n *\n * @defaultValue `0`\n */\n intervalTimeout?: number\n\n /**\n * Callback invoked whenever a retry attempt fails.\n *\n * @param options - Information about the failed attempt.\n *\n * @remarks\n * This callback is invoked with additional metadata such as the attempt\n * number and the number of retries left.\n */\n onFailedAttempt?: (options: FetchMethodCallbackOptions) => void\n\n /**\n * Maximum number of retry attempts.\n *\n * @defaultValue `1`\n */\n retries?: number\n}\n\n/**\n * Internal configuration passed to the retry callback.\n *\n * @internal\n */\ninterface RetryFetchCallbackOptions extends RetryFetchMethodOptions {\n fetchMethod: FetchMethod\n\n /**\n * Abort controller used to cancel the underlying fetch requests.\n */\n controller: AbortController\n\n /**\n * Initialization options passed to the `fetch` implementation.\n */\n init: Parameters<FetchMethod>[1]\n\n /**\n * Request URL.\n */\n url: Parameters<FetchMethod>[0]\n}\n\n/**\n * Creates a callback function used by the retry loop to perform a protected fetch attempt.\n *\n * @param options - Internal options controlling request execution and retry classification.\n * @returns A function that performs one fetch attempt and either:\n * - returns a successful {@link Response},\n * - throws {@link HttpError} for retriable `503` responses, or\n * - aborts and returns `undefined` for non-retriable failures.\n *\n * @internal\n */\nfunction createRetryFetchCallback({\n apiName = 'Optimization',\n controller,\n fetchMethod,\n init,\n url,\n}: RetryFetchCallbackOptions) {\n return async () => {\n const urlLabel = fetchInputToString(url)\n\n try {\n const response = await fetchMethod(url, init)\n\n if (response.status === RETRY_RESPONSE_STATUS) {\n throw new HttpError(\n `${apiName} API request to \"${urlLabel}\" failed with status: \"[${response.status}] ${response.statusText}\".`,\n RETRY_RESPONSE_STATUS,\n )\n }\n\n if (!response.ok) {\n const httpError = new Error(\n `Request to \"${urlLabel}\" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`,\n )\n logger.error('Request failed with non-OK status:', httpError)\n\n controller.abort()\n\n return\n }\n\n logger.debug(`Response from \"${urlLabel}\":`, response)\n\n return response\n } catch (error) {\n if (error instanceof HttpError && error.status === RETRY_RESPONSE_STATUS) {\n throw error\n }\n\n logger.error(`Request to \"${urlLabel}\" failed:`, error)\n\n controller.abort()\n }\n }\n}\n\n/**\n * Waits for the configured retry delay between attempts.\n *\n * @param intervalTimeout - Delay in milliseconds before the next retry attempt.\n * @returns A promise that resolves immediately when delay is non-positive,\n * otherwise after the timeout elapses.\n *\n * @internal\n */\nasync function delayRetry(intervalTimeout: number): Promise<void> {\n if (intervalTimeout <= 0) {\n return\n }\n\n const { promise, resolve } = Promise.withResolvers<undefined>()\n setTimeout(() => {\n resolve(undefined)\n }, intervalTimeout)\n await promise\n}\n\n/**\n * Creates a {@link FetchMethod} that retries failed requests according to the\n * provided configuration.\n *\n * @param options - Configuration options that control retry behavior.\n * @returns A {@link FetchMethod} that automatically retries qualifying failures.\n *\n * @throws Error\n * Thrown when the request cannot be retried and no successful response is obtained.\n *\n * @remarks\n * This wrapper uses a lightweight internal retry loop and an {@link AbortController}\n * to cancel pending requests when a non-retriable error occurs.\n *\n * @example\n * ```ts\n * const fetchWithRetry = createRetryFetchMethod({\n * apiName: 'Optimization',\n * retries: 3,\n * intervalTimeout: 200,\n * onFailedAttempt: ({ attemptNumber, retriesLeft }) => {\n * console.warn(`Attempt ${attemptNumber} failed. Retries left: ${retriesLeft}`)\n * },\n * })\n *\n * const response = await fetchWithRetry('https://example.com', { method: 'GET' })\n * ```\n *\n * @public\n */\nexport function createRetryFetchMethod({\n apiName = 'Optimization',\n fetchMethod,\n intervalTimeout = DEFAULT_INTERVAL_TIMEOUT,\n onFailedAttempt,\n retries = DEFAULT_RETRY_COUNT,\n}: RetryFetchMethodOptions = {}): FetchMethod {\n const resolvedFetchMethod = resolveFetchMethod(fetchMethod)\n\n return async (url, init) => {\n const controller = new AbortController()\n const maxAttempts = retries + 1\n const attemptFetch = createRetryFetchCallback({\n apiName,\n controller,\n fetchMethod: resolvedFetchMethod,\n init,\n url,\n })\n\n for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber++) {\n try {\n const response = await attemptFetch()\n\n if (response) {\n return response\n }\n\n break\n } catch (error) {\n if (!(error instanceof HttpError) || error.status !== RETRY_RESPONSE_STATUS) {\n throw error\n }\n\n const retriesLeft = maxAttempts - attemptNumber\n\n onFailedAttempt?.({\n apiName,\n error,\n attemptNumber,\n retriesLeft,\n } satisfies FetchMethodCallbackOptions)\n\n if (retriesLeft === 0) {\n throw error\n }\n\n await delayRetry(intervalTimeout)\n }\n }\n\n throw new Error(`${apiName} API request to \"${fetchInputToString(url)}\" may not be retried.`)\n }\n}\n","import { createScopedLogger } from '../logger'\nimport type { BaseFetchMethodOptions, FetchMethod, FetchMethodCallbackOptions } from './Fetch'\nimport { fetchInputToString } from './fetchInputToString'\nimport { resolveFetchMethod } from './resolveFetchMethod'\n\nconst logger = createScopedLogger('ApiClient:Timeout')\n\n/**\n * Default timeout (in milliseconds) for outgoing requests.\n *\n * @internal\n */\nconst DEFAULT_REQUEST_TIMEOUT = 3000\n\n/** @internal */\nfunction isFetchAbortSignal(signal: unknown): signal is NonNullable<RequestInit['signal']> {\n return typeof signal === 'object' && signal !== null\n}\n\n/**\n * Configuration options for {@link createTimeoutFetchMethod}.\n *\n * @public\n */\nexport interface TimeoutFetchMethodOptions extends BaseFetchMethodOptions {\n /**\n * Callback invoked when a request exceeds the configured timeout.\n *\n * @param options - Information about the timed-out request.\n *\n * @remarks\n * If this callback is not provided, a default error is logged.\n *\n * @see {@link FetchMethodCallbackOptions}\n */\n onRequestTimeout?: (options: FetchMethodCallbackOptions) => void\n\n /**\n * Maximum time (in milliseconds) to wait for a response before aborting the request.\n *\n * @defaultValue `3000`\n */\n requestTimeout?: number\n}\n\n/**\n * Creates a {@link FetchMethod} that aborts requests after a configurable timeout.\n *\n * @param options - Configuration options controlling timeout behavior.\n * @returns A {@link FetchMethod} that enforces a timeout for each request.\n *\n * @remarks\n * When a timeout occurs, the request is aborted using an {@link AbortController}.\n * If `onRequestTimeout` is not provided, an error is logged by the package logger.\n *\n * @example\n * ```ts\n * const fetchWithTimeout = createTimeoutFetchMethod({\n * apiName: 'Optimization',\n * requestTimeout: 5000,\n * onRequestTimeout: ({ apiName }) => {\n * console.warn(`${apiName} request timed out`)\n * },\n * })\n *\n * const response = await fetchWithTimeout('https://example.com', { method: 'GET' })\n * ```\n *\n * @see {@link TimeoutFetchMethodOptions}\n *\n * @public\n */\nexport function createTimeoutFetchMethod({\n apiName = 'Optimization',\n fetchMethod,\n onRequestTimeout,\n requestTimeout = DEFAULT_REQUEST_TIMEOUT,\n}: TimeoutFetchMethodOptions = {}): FetchMethod {\n const resolvedFetchMethod = resolveFetchMethod(fetchMethod)\n\n return async (url, init) => {\n const controller = new AbortController()\n\n const id = setTimeout(() => {\n if (typeof onRequestTimeout === 'function') {\n onRequestTimeout({ apiName })\n } else {\n logger.error(\n `Request to \"${fetchInputToString(url)}\" timed out`,\n new Error('Request timeout'),\n )\n }\n\n controller.abort()\n }, requestTimeout)\n\n const { signal } = controller\n\n if (!isFetchAbortSignal(signal)) {\n throw new Error('AbortController signal is not compatible with fetch.')\n }\n\n const requestInit = { ...init, signal }\n\n const response = await resolvedFetchMethod(url, requestInit)\n\n clearTimeout(id)\n\n return response\n }\n}\n","import { createScopedLogger } from '../logger'\nimport { createRetryFetchMethod, type RetryFetchMethodOptions } from './createRetryFetchMethod'\nimport {\n createTimeoutFetchMethod,\n type TimeoutFetchMethodOptions,\n} from './createTimeoutFetchMethod'\nimport type { FetchMethod } from './Fetch'\n\nconst logger = createScopedLogger('ApiClient:Fetch')\n\n/**\n * Options for {@link createProtectedFetchMethod}, combining timeout and retry behavior.\n *\n * @see {@link RetryFetchMethodOptions}\n * @see {@link TimeoutFetchMethodOptions}\n *\n * @public\n */\nexport interface ProtectedFetchMethodOptions\n extends RetryFetchMethodOptions, TimeoutFetchMethodOptions {}\n\n/**\n * Creates a {@link FetchMethod} that combines timeout and retry protection.\n *\n * @param options - Configuration options for both timeout and retry behavior.\n * @returns A {@link FetchMethod} that applies timeout and retry logic to requests.\n *\n * @throws Error\n * Rethrows the original error after logging, including abort errors.\n *\n * @remarks\n * The resulting method first wraps the base fetch with a timeout (via\n * {@link createTimeoutFetchMethod}), then applies retry behavior (via\n * {@link createRetryFetchMethod}).\n *\n * If an error is thrown during configuration or request execution, it is logged.\n *\n * @example\n * ```ts\n * const fetchProtected = createProtectedFetchMethod({\n * apiName: 'Optimization',\n * requestTimeout: 4000,\n * retries: 2,\n * })\n *\n * const response = await fetchProtected('https://example.com/experiences', {\n * method: 'GET',\n * })\n * ```\n *\n * @public\n */\nexport function createProtectedFetchMethod(options: ProtectedFetchMethodOptions): FetchMethod {\n try {\n const timeoutFetchMethod = createTimeoutFetchMethod(options)\n const retryFetchMethod = createRetryFetchMethod({ ...options, fetchMethod: timeoutFetchMethod })\n\n return retryFetchMethod\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n logger.warn('Request aborted due to network issues. This request may not be retried.')\n } else {\n logger.error('Request failed:', error)\n }\n }\n throw error\n }\n}\n","import { createProtectedFetchMethod } from './createProtectedFetchMethod'\n\n/**\n * Signature of a fetch method used by the API clients.\n *\n * @param url - The request URL.\n * @param init - Initialization options passed to `fetch`.\n * @returns A promise that resolves with the {@link Response}.\n *\n * @remarks\n * This abstraction allows the underlying implementation to be replaced,\n * for example in tests or different runtime environments.\n *\n * @example\n * ```ts\n * const method: FetchMethod = async (input, init) => {\n * return fetch(input, init)\n * }\n * ```\n *\n * @public\n */\nexport type FetchInput = string | Request\nexport type FetchMethod = (input: FetchInput, init?: RequestInit) => Promise<Response>\n\n/**\n * Base options shared across fetch method factories.\n *\n * @public\n */\nexport interface BaseFetchMethodOptions {\n /**\n * Human-readable name of the API being called.\n *\n * @remarks\n * Used primarily for logging and error messages.\n */\n apiName?: string\n\n /**\n * Custom fetch implementation to use instead of the global `fetch`.\n *\n * @remarks\n * This is useful for providing polyfills, mocks, or instrumented fetch\n * implementations. It is required in runtimes that do not provide\n * `globalThis.fetch`.\n */\n fetchMethod?: FetchMethod\n}\n\n/**\n * Options passed to callback functions invoked by fetch wrappers.\n *\n * @remarks\n * Not all fields are guaranteed to be present in all callback scenarios.\n *\n * @public\n */\nexport interface FetchMethodCallbackOptions {\n /**\n * Name of the API associated with the request.\n */\n apiName?: string\n\n /**\n * Error that caused the callback to be invoked, if available.\n */\n error?: Error\n\n /**\n * The current attempt number (for retry callbacks).\n */\n attemptNumber?: number\n\n /**\n * Number of retry attempts remaining (for retry callbacks).\n */\n retriesLeft?: number\n}\n\n/**\n * Namespace-like object providing factory methods for protected fetch functions.\n *\n * @public\n */\nconst Fetch = {\n /**\n * Creates a fully protected fetch method with timeout and retry behavior.\n *\n * @example\n * ```ts\n * const fetchMethod = Fetch.create({\n * apiName: 'Optimization',\n * requestTimeout: 3000,\n * retries: 2,\n * })\n *\n * const response = await fetchMethod('https://example.com', { method: 'GET' })\n * ```\n *\n * @see {@link createProtectedFetchMethod}\n */\n create: createProtectedFetchMethod,\n}\n\nexport default Fetch\n","import Fetch from './Fetch'\n\nexport * from './createProtectedFetchMethod'\nexport * from './createRetryFetchMethod'\nexport * from './createTimeoutFetchMethod'\nexport * from './Fetch'\n\nexport default Fetch\n","import Fetch, { type FetchMethod, type ProtectedFetchMethodOptions } from './fetch'\nimport { createScopedLogger } from './logger'\n\nconst logger = createScopedLogger('ApiClient')\n\n/**\n * Default Contentful environment used when none is explicitly provided.\n *\n * @internal\n */\nconst DEFAULT_ENVIRONMENT = 'main'\n\n/**\n * Configuration options for API clients extending `ApiClientBase`.\n *\n * @public\n */\nexport interface ApiConfig {\n /**\n * Base URL for the API.\n *\n * @remarks\n * When omitted, the concrete client is expected to construct full URLs\n * internally.\n */\n baseUrl?: string\n\n /**\n * Contentful environment identifier.\n *\n * @defaultValue `'main'`\n */\n environment?: string\n\n /**\n * Options used to configure the underlying protected fetch method.\n *\n * @remarks\n * `apiName` is derived from the client name and must not be provided here.\n */\n fetchOptions?: Omit<ProtectedFetchMethodOptions, 'apiName'>\n\n /**\n * Client identifier used for authentication or tracking.\n */\n clientId: string\n}\n\n/**\n * Properties that can be shared between global and per-client API configuration.\n *\n * @public\n */\nexport type GlobalApiConfigProperties = 'environment' | 'fetchOptions' | 'clientId'\n\n/**\n * Base class for API clients that provides shared configuration and error logging.\n *\n * @remarks\n * This abstract class is intended for internal use within the package and\n * must not be treated as part of the public API surface.\n *\n * Concrete API clients must extend this class to inherit consistent logging\n * behavior and fetch configuration.\n *\n * @example\n * ```ts\n * interface MyClientConfig extends ApiConfig {\n * // additional config\n * }\n *\n * class MyClient extends ApiClientBase {\n * constructor(config: MyClientConfig) {\n * super('MyClient', config)\n * }\n *\n * async getSomething() {\n * const response = await this.fetch('https://example.com', { method: 'GET' })\n * return response.json()\n * }\n * }\n * ```\n *\n * @internal\n */\nabstract class ApiClientBase {\n /**\n * Name of the API client, used in log messages and as the `apiName` for fetch.\n */\n protected readonly name: string\n\n /**\n * Client identifier used for authentication or tracking.\n */\n protected readonly clientId: string\n\n /**\n * Contentful environment associated with this client.\n */\n protected readonly environment: string\n\n /**\n * Protected fetch method used by the client to perform HTTP requests.\n */\n protected readonly fetch: FetchMethod\n\n /**\n * Creates a new API client base instance.\n *\n * @param name - Human-readable name of the client (used for logging and `apiName`).\n * @param config - Configuration options for the client.\n */\n constructor(name: string, { fetchOptions, clientId, environment }: ApiConfig) {\n this.clientId = clientId\n this.environment = environment ?? DEFAULT_ENVIRONMENT\n this.name = name\n\n this.fetch = Fetch.create({ ...(fetchOptions ?? {}), apiName: name })\n }\n\n /**\n * Logs errors that occur during API requests with standardized messages.\n *\n * @param error - The error thrown by the underlying operation.\n * @param options - Additional metadata about the request.\n * @param options.requestName - Human-readable name of the request operation.\n *\n * @remarks\n * Abort errors are logged at `warn` level and other errors at `error` level.\n * The log message includes the client name for better debugging context.\n */\n protected logRequestError(error: unknown, { requestName }: { requestName: string }): void {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n logger.warn(\n `[${this.name}] \"${requestName}\" request aborted due to network issues. This request may not be retried.`,\n )\n } else {\n logger.error(`[${this.name}] \"${requestName}\" request failed:`, error)\n }\n }\n }\n}\n\nexport default ApiClientBase\n","import {\n BatchExperienceRequestData,\n BatchExperienceResponse,\n ExperienceEventArray,\n ExperienceRequestData,\n ExperienceResponse,\n parseWithFriendlyError,\n type BatchExperienceEventArray,\n type BatchExperienceResponseData,\n type ExperienceRequestOptions as ExperienceRequestBodyOptions,\n type OptimizationData,\n} from '@contentful/optimization-api-schemas'\nimport ApiClientBase, { type ApiConfig } from '../ApiClientBase'\nimport { createScopedLogger } from '../logger'\n\nconst logger = createScopedLogger('ApiClient:Experience')\n\n/**\n * Default base URL for the Experience API.\n *\n * @public\n */\nexport const EXPERIENCE_BASE_URL = 'https://experience.ninetailed.co/'\n\n/**\n * Feature flags supported by the Experience API.\n *\n * @internal\n */\nexport type Feature = 'ip-enrichment' | 'location'\n\n/**\n * Options that control how requests to the Experience API are handled.\n *\n * @public\n */\nexport interface ExperienceApiClientRequestOptions {\n /**\n * Enabled features (for example, `\"ip-enrichment\"`) for the API to use for this request.\n *\n * @remarks\n * When omitted, the API can apply a default set of features.\n */\n enabledFeatures?: Feature[]\n\n /**\n * IP address to override the API behavior for IP analysis.\n *\n * @remarks\n * Commonly used in ESR or SSR environments, as the API otherwise uses\n * the server IP.\n */\n ip?: string\n\n /**\n * Experience API `locale` query parameter used to translate profile location fields such as\n * `location.city` and `location.country`.\n *\n * @remarks\n * When omitted, the API can use a server-side default.\n */\n locale?: string\n\n /**\n * When `true`, sends performance-critical endpoints in plain text.\n *\n * @remarks\n * The Ninetailed API accepts certain endpoints in plain text to avoid CORS\n * preflight requests, which can improve performance in browser environments.\n */\n plainText?: boolean\n\n /**\n * When `true`, instructs the API to aggregate a new profile state but not store it.\n *\n * @remarks\n * This is commonly used in ESR or SSR environments where you want to\n * preview the result without persisting changes.\n */\n preflight?: boolean\n}\n\n/**\n * Internal options for profile mutation requests.\n *\n * @internal\n */\ninterface ProfileMutationRequestOptions {\n url: string\n body: unknown\n options: ExperienceApiClientRequestOptions\n}\n\n/**\n * Parameters used when creating a profile.\n *\n * @public\n */\nexport interface CreateProfileParams {\n /**\n * Events used to aggregate the profile state.\n */\n events: ExperienceEventArray\n}\n\n/**\n * Parameters used when updating an existing profile.\n *\n * @public\n */\nexport interface UpdateProfileParams extends CreateProfileParams {\n /**\n * ID of the profile to update.\n */\n profileId: string\n}\n\n/**\n * Parameters used when creating or updating a profile.\n *\n * @public\n */\nexport interface UpsertProfileParams extends CreateProfileParams {\n /**\n * Optional ID of the profile; when omitted, a new profile is created.\n */\n profileId?: string\n}\n\n/**\n * Parameters used when performing a batch profile update.\n *\n * @public\n */\nexport interface BatchUpdateProfileParams {\n /**\n * Batch of events to process.\n */\n events: BatchExperienceEventArray\n}\n\n/**\n * Configuration for {@link ExperienceApiClient}.\n *\n * @public\n */\nexport interface ExperienceApiClientConfig extends ApiConfig, ExperienceApiClientRequestOptions {}\n\n/**\n * Client for interacting with the Experience API.\n *\n * @remarks\n * This client is responsible for reading and mutating Ninetailed profiles\n * using the Experience API.\n *\n * @example\n * ```ts\n * const client = new ExperienceApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * })\n *\n * const profile = await client.getProfile('profile-id')\n * ```\n *\n * Extends `ApiClientBase`.\n *\n * @public\n */\nexport default class ExperienceApiClient extends ApiClientBase {\n /**\n * Base URL used for Experience API requests.\n */\n protected readonly baseUrl: string\n\n private readonly enabledFeatures?: ExperienceApiClientRequestOptions['enabledFeatures']\n private readonly ip?: ExperienceApiClientRequestOptions['ip']\n private locale?: ExperienceApiClientRequestOptions['locale']\n private readonly plainText?: ExperienceApiClientRequestOptions['plainText']\n private readonly preflight?: ExperienceApiClientRequestOptions['preflight']\n\n /**\n * Creates a new {@link ExperienceApiClient} instance.\n *\n * @param config - Configuration for the Experience API client.\n */\n constructor(config: ExperienceApiClientConfig) {\n super('Experience', config)\n\n const { baseUrl, enabledFeatures, ip, locale, plainText, preflight } = config\n\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Set default for anything falsey\n this.baseUrl = baseUrl || EXPERIENCE_BASE_URL\n this.enabledFeatures = enabledFeatures\n this.ip = ip\n this.locale = locale\n this.plainText = plainText\n this.preflight = preflight\n }\n\n /**\n * Update the default Experience API locale used when request options omit `locale`.\n *\n * @param locale - Next default Experience API locale.\n *\n * @public\n */\n public setLocale(locale: ExperienceApiClientRequestOptions['locale']): void {\n this.locale = locale\n }\n\n /**\n * Retrieves a profile by ID.\n *\n * @param id - The profile ID to retrieve.\n * @param options - Optional request options. `preflight` and `plainText` are not allowed here.\n * @returns The current optimization data for the profile.\n *\n * @throws Error\n * Thrown if `id` is missing or the underlying request fails.\n *\n * @example\n * ```ts\n * const profile = await client.getProfile('profile-id', {\n * locale: 'en-US',\n * })\n * ```\n */\n public async getProfile(\n id: string,\n options: Omit<ExperienceApiClientRequestOptions, 'preflight' | 'plainText'> = {},\n ): Promise<OptimizationData> {\n if (!id) throw new Error('Valid profile ID required.')\n\n const requestName = 'Get Profile'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n try {\n const response = await this.fetch(\n this.constructUrl(\n `v2/organizations/${this.clientId}/environments/${this.environment}/profiles/${id}`,\n options,\n ),\n {\n method: 'GET',\n },\n )\n\n const {\n data: { changes, experiences, profile },\n } = parseWithFriendlyError(ExperienceResponse, await response.json())\n\n const data = { changes, selectedOptimizations: experiences, profile }\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return data\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Sends a POST request to mutate a profile or profiles.\n *\n * @param request - Mutation request options including URL, body, and request options.\n * @returns The raw {@link Response} from the underlying fetch.\n *\n * @internal\n */\n private async makeProfileMutationRequest({\n url,\n body,\n options,\n }: ProfileMutationRequestOptions): Promise<Response> {\n return await this.fetch(this.constructUrl(url, options), {\n method: 'POST',\n headers: this.constructHeaders(options),\n body: JSON.stringify(body),\n keepalive: true,\n })\n }\n\n /**\n * Creates a profile and returns the resulting optimization data.\n *\n * @param params - Parameters containing the events to aggregate into the profile.\n * @param options - Optional request options.\n * @returns The optimization data for the newly created profile.\n *\n * @remarks\n * The returned profile ID can be used for subsequent update requests.\n *\n * @example\n * ```ts\n * const data = await client.createProfile({\n * events: [{ type: 'identify', userId: 'user-123' }],\n * })\n * ```\n */\n public async createProfile(\n { events }: CreateProfileParams,\n options: ExperienceApiClientRequestOptions = {},\n ): Promise<OptimizationData> {\n const requestName = 'Create Profile'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n const body = this.constructExperienceRequestBody(events, options)\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n const response = await this.makeProfileMutationRequest({\n url: `v2/organizations/${this.clientId}/environments/${this.environment}/profiles`,\n body,\n options,\n })\n\n const {\n data: { changes, experiences, profile },\n } = parseWithFriendlyError(ExperienceResponse, await response.json())\n\n const data = { changes, selectedOptimizations: experiences, profile }\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return data\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Updates an existing profile with the given profile ID.\n *\n * @param params - Parameters including the profile ID and events.\n * @param options - Optional request options.\n * @returns The updated optimization data for the profile.\n *\n * @throws Error\n * Thrown if `profileId` is missing or the underlying request fails.\n *\n * @example\n * ```ts\n * const data = await client.updateProfile({\n * profileId: 'profile-id',\n * events: [{ type: 'track', event: 'viewed_video' }],\n * })\n * ```\n */\n public async updateProfile(\n { profileId, events }: UpdateProfileParams,\n options: ExperienceApiClientRequestOptions = {},\n ): Promise<OptimizationData> {\n if (!profileId) throw new Error('Valid profile ID required.')\n\n const requestName = 'Update Profile'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n const body = this.constructExperienceRequestBody(events, options)\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n const response = await this.makeProfileMutationRequest({\n url: `v2/organizations/${this.clientId}/environments/${this.environment}/profiles/${profileId}`,\n body,\n options,\n })\n\n const {\n data: { changes, experiences, profile },\n } = parseWithFriendlyError(ExperienceResponse, await response.json())\n\n const data = { changes, selectedOptimizations: experiences, profile }\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return data\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Creates or updates a profile depending on whether a `profileId` is provided.\n *\n * @param params - Parameters including optional profile ID and events.\n * @param options - Optional request options.\n * @returns The resulting optimization data.\n *\n * @example\n * ```ts\n * // Create\n * await client.upsertProfile({ events })\n *\n * // Update\n * await client.upsertProfile({ profileId: 'profile-id', events })\n * ```\n */\n public async upsertProfile(\n { profileId, events }: UpsertProfileParams,\n options?: ExperienceApiClientRequestOptions,\n ): Promise<OptimizationData> {\n if (!profileId) {\n return await this.createProfile({ events }, options)\n } else {\n return await this.updateProfile({ profileId, events }, options)\n }\n }\n\n /**\n * Sends multiple events to the Ninetailed Experience API to upsert many profiles.\n *\n * @param params - Parameters containing the batch of events.\n * @param options - Optional request options.\n * @returns The list of profiles affected by the batch operation.\n *\n * @remarks\n * Batch requests must contain at least one event. Every event must contain\n * an anonymous ID. Profiles will be created or updated according to the\n * anonymous ID.\n *\n * This method is intended to be used from server environments.\n *\n * @example\n * ```ts\n * const profiles = await client.upsertManyProfiles({\n * events: [\n * { anonymousId: 'anon-1', type: 'identify', ... },\n * { anonymousId: 'anon-2', type: 'identify', ... },\n * ],\n * })\n * ```\n */\n public async upsertManyProfiles(\n { events }: BatchUpdateProfileParams,\n options: ExperienceApiClientRequestOptions = {},\n ): Promise<BatchExperienceResponseData['profiles']> {\n const requestName = 'Upsert Many Profiles'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n const body = parseWithFriendlyError(BatchExperienceRequestData, {\n events,\n options: this.constructBodyOptions(options),\n })\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n const response = await this.makeProfileMutationRequest({\n url: `v2/organizations/${this.clientId}/environments/${this.environment}/events`,\n body,\n options: { plainText: false, ...options },\n })\n\n const {\n data: { profiles },\n } = parseWithFriendlyError(BatchExperienceResponse, await response.json())\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return profiles\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n throw error\n }\n }\n\n /**\n * Constructs a request URL with query parameters derived from request options.\n *\n * @param path - Path relative to the Experience API base URL.\n * @param options - Request options that can influence query parameters.\n * @returns The fully constructed URL as a string.\n *\n * @internal\n */\n private constructUrl(path: string, options: ExperienceApiClientRequestOptions): string {\n const url = new URL(path, this.baseUrl)\n const locale = options.locale ?? this.locale\n const preflight = options.preflight ?? this.preflight\n\n if (locale) {\n url.searchParams.set('locale', locale)\n }\n\n if (preflight) {\n url.searchParams.set('type', 'preflight')\n }\n\n return url.toString()\n }\n\n /**\n * Constructs request headers based on request options and default configuration.\n *\n * @param options - Request options that can influence headers.\n * @returns A record of HTTP headers to send with the request.\n *\n * @internal\n */\n private constructHeaders({\n ip = this.ip,\n plainText = this.plainText,\n }: ExperienceApiClientRequestOptions): Record<string, string> {\n const headers = new Map<string, string>()\n\n if (ip) {\n headers.set('X-Force-IP', ip)\n }\n\n if (plainText ?? this.plainText ?? true) {\n headers.set('Content-Type', 'text/plain')\n } else {\n headers.set('Content-Type', 'application/json')\n }\n\n return Object.fromEntries(headers)\n }\n\n /**\n * Constructs the `options` section of the request body for profile mutations.\n *\n * @param options - Request options that can specify enabled features.\n * @returns Experience API body options including feature flags.\n *\n * @internal\n */\n private readonly constructBodyOptions = ({\n enabledFeatures = this.enabledFeatures,\n }: ExperienceApiClientRequestOptions): ExperienceRequestBodyOptions => {\n const bodyOptions: ExperienceRequestBodyOptions = {}\n\n if (enabledFeatures && Array.isArray(enabledFeatures) && enabledFeatures.length > 0) {\n bodyOptions.features = enabledFeatures\n } else {\n bodyOptions.features = ['ip-enrichment', 'location']\n }\n\n return bodyOptions\n }\n\n /**\n * Construct and validate the singular Experience mutation request payload.\n *\n * @param events - Events for the Experience API to evaluate.\n * @param options - Request options that can contribute body options.\n * @returns Validated singular Experience request body.\n *\n * @internal\n */\n private constructExperienceRequestBody(\n events: ExperienceEventArray,\n options: ExperienceApiClientRequestOptions,\n ): ExperienceRequestData {\n return ExperienceRequestData.parse({\n events: parseWithFriendlyError(ExperienceEventArray, events),\n options: this.constructBodyOptions(options),\n })\n }\n}\n","import {\n BatchInsightsEventArray,\n parseWithFriendlyError,\n} from '@contentful/optimization-api-schemas'\nimport ApiClientBase, { type ApiConfig } from '../ApiClientBase'\nimport { createScopedLogger } from '../logger'\n\nconst logger = createScopedLogger('ApiClient:Insights')\nconst BEACON_FALLBACK_MESSAGE = 'beacon failed; falling back to fetch'\n\n/**\n * Default base URL for the Insights ingest API.\n *\n * @public\n */\nexport const INSIGHTS_BASE_URL = 'https://ingest.insights.ninetailed.co/'\n\n/**\n * Options that control how Insights events are sent.\n *\n * @public\n */\nexport interface InsightsApiClientRequestOptions {\n /**\n * Sender used to enqueue serialized events via the Beacon API or a similar mechanism.\n *\n * @param url - Target URL for the batched events.\n * @param body - Serialized batched insights events to be sent.\n * @returns `true` if the events were successfully queued, `false` otherwise.\n *\n * @remarks\n * This option is intended for last-chance lifecycle delivery. If it returns\n * `false` or throws, the client falls back to emitting events immediately via\n * `fetch` with `keepalive`.\n */\n beacon?: (url: string, body: string) => boolean\n}\n\n/**\n * Configuration for {@link InsightsApiClient}.\n *\n * @public\n */\nexport interface InsightsApiClientConfig extends ApiConfig {}\n\n/**\n * Client for sending analytics and insights events to the Ninetailed Insights API.\n *\n * @remarks\n * This client is optimized for sending batched events, optionally using a\n * custom beacon-like sender for last-chance lifecycle delivery.\n *\n * @example\n * ```ts\n * const insightsClient = new InsightsApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * })\n *\n * await insightsClient.sendBatchEvents([\n * {\n * profile: { id: 'profile-123', ... },\n * events: [\n * {\n * type: 'track',\n * event: 'button_clicked',\n * properties: { id: 'primary-cta' },\n * },\n * ],\n * }\n * ])\n * ```\n *\n * Extends `ApiClientBase`.\n *\n * @public\n */\nexport default class InsightsApiClient extends ApiClientBase {\n /**\n * Base URL used for Insights API requests.\n */\n protected readonly baseUrl: string\n\n /**\n * Creates a new {@link InsightsApiClient} instance.\n *\n * @param config - Configuration for the Insights API client.\n *\n * @example\n * ```ts\n * const client = new InsightsApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * })\n * ```\n */\n constructor(config: InsightsApiClientConfig) {\n super('Insights', config)\n\n const { baseUrl } = config\n\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Set default for anything falsey\n this.baseUrl = baseUrl || INSIGHTS_BASE_URL\n }\n\n /**\n * Sends batches of insights events to the Ninetailed Insights API.\n *\n * @param batches - Array of event batches to send.\n * @param options - Optional request options, including a per-call `beacon` sender.\n * @returns `true` when the event batch is successfully queued by the beacon\n * sender or a direct request is successfully sent, `false` otherwise.\n *\n * @remarks\n * If a `beacon` sender is provided, it will be invoked first with the\n * serialized request body. When the sender returns `true`, the events are\n * considered successfully queued and no network request is made by this\n * method.\n *\n * If the sender is missing, returns `false`, or throws, the events are\n * emitted immediately via `fetch`.\n *\n * @example\n * ```ts\n * const success = await insightsClient.sendBatchEvents(batches)\n * ```\n *\n * @example\n * ```ts\n * // Use Beacon for a last-chance lifecycle flush\n * const success = await insightsClient.sendBatchEvents(batches, {\n * beacon: (url, body) => navigator.sendBeacon(url, body),\n * })\n * ```\n */\n public async sendBatchEvents(\n batches: BatchInsightsEventArray,\n options: InsightsApiClientRequestOptions = {},\n ): Promise<boolean> {\n const { beacon } = options\n\n const url = new URL(\n `v1/organizations/${this.clientId}/environments/${this.environment}/events`,\n this.baseUrl,\n )\n const urlString = url.toString()\n\n const body = parseWithFriendlyError(BatchInsightsEventArray, batches)\n const serializedBody = JSON.stringify(body)\n\n if (beacon) {\n logger.debug('Queueing events via beacon')\n\n try {\n const beaconSuccessfullyQueued = beacon(urlString, serializedBody)\n\n if (beaconSuccessfullyQueued) {\n return true\n }\n\n logger.warn(BEACON_FALLBACK_MESSAGE)\n } catch (error) {\n logger.warn(BEACON_FALLBACK_MESSAGE, error)\n }\n }\n\n const requestName = 'Event Batches'\n\n logger.info(`Sending \"${requestName}\" request`)\n\n logger.debug(`\"${requestName}\" request body:`, body)\n\n try {\n await this.fetch(urlString, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: serializedBody,\n keepalive: !!beacon,\n })\n\n logger.debug(`\"${requestName}\" request successfully completed`)\n\n return true\n } catch (error) {\n this.logRequestError(error, { requestName })\n\n return false\n }\n }\n}\n","import type { ApiConfig, GlobalApiConfigProperties } from './ApiClientBase'\nimport { ExperienceApiClient, type ExperienceApiClientConfig } from './experience'\nimport { InsightsApiClient, type InsightsApiClientConfig } from './insights'\n\n/**\n * Configuration for the high-level {@link ApiClient}.\n *\n * @public\n */\nexport interface ApiClientConfig extends Pick<ApiConfig, GlobalApiConfigProperties> {\n /**\n * Configuration for the Experience API client.\n *\n * @remarks\n * Shared fields (`clientId`, `environment`, `fetchOptions`) are inherited\n * from top-level config; this object is for Experience-specific options.\n */\n experience?: Omit<ExperienceApiClientConfig, GlobalApiConfigProperties>\n\n /**\n * Configuration for the Insights API client.\n *\n * @remarks\n * Shared fields (`clientId`, `environment`, `fetchOptions`) are inherited\n * from top-level config; this object is for Insights-specific options.\n */\n insights?: Omit<InsightsApiClientConfig, GlobalApiConfigProperties>\n}\n\n/**\n * Aggregated API client providing access to Experience and Insights APIs.\n *\n * @remarks\n * This client encapsulates shared configuration and exposes dedicated\n * sub-clients for experience and insights use cases.\n *\n * @example\n * ```ts\n * const client = new ApiClient({\n * clientId: 'org-id',\n * environment: 'main',\n * experience: {\n * // experience-specific overrides\n * },\n * insights: {\n * // insights-specific overrides\n * },\n * })\n *\n * const profile = await client.experience.getProfile('profile-id')\n * await client.insights.sendBatchEvents(batches)\n * ```\n *\n * @see {@link ExperienceApiClient}\n * @see {@link InsightsApiClient}\n *\n * @public\n */\nexport default class ApiClient {\n /**\n * Shared configuration applied to both Experience and Insights clients.\n *\n * @see {@link ApiConfig}\n */\n readonly config: ApiConfig\n\n /**\n * Client for experience-related operations.\n *\n * @see {@link ExperienceApiClient}\n */\n readonly experience: ExperienceApiClient\n\n /**\n * Client for insights-related operations.\n *\n * @see {@link InsightsApiClient}\n */\n readonly insights: InsightsApiClient\n\n /**\n * Creates a new aggregated {@link ApiClient} instance.\n *\n * @param config - Global API client configuration with optional per-client overrides.\n */\n constructor(config: ApiClientConfig) {\n const { experience, insights, clientId, environment, fetchOptions } = config\n const apiConfig: ApiConfig = {\n clientId,\n environment,\n fetchOptions,\n }\n\n this.config = apiConfig\n\n this.experience = new ExperienceApiClient({\n ...apiConfig,\n ...experience,\n })\n\n this.insights = new InsightsApiClient({\n ...apiConfig,\n ...insights,\n })\n }\n}\n"],"names":["fetchInputToString","input","MISSING_FETCH_METHOD_MESSAGE","resolveFetchMethod","fetchMethod","globalThis","Error","logger","createScopedLogger","DEFAULT_INTERVAL_TIMEOUT","DEFAULT_RETRY_COUNT","RETRY_RESPONSE_STATUS","HTTP_ERROR_RESPONSE_STATUS","HttpError","message","status","Object","createRetryFetchCallback","apiName","controller","init","url","urlLabel","response","httpError","error","delayRetry","intervalTimeout","promise","resolve","Promise","setTimeout","undefined","createRetryFetchMethod","onFailedAttempt","retries","resolvedFetchMethod","AbortController","maxAttempts","attemptFetch","attemptNumber","retriesLeft","DEFAULT_REQUEST_TIMEOUT","isFetchAbortSignal","signal","createTimeoutFetchMethod","onRequestTimeout","requestTimeout","id","requestInit","clearTimeout","createProtectedFetchMethod","options","timeoutFetchMethod","retryFetchMethod","Fetch","DEFAULT_ENVIRONMENT","ApiClientBase","name","fetchOptions","clientId","environment","requestName","EXPERIENCE_BASE_URL","ExperienceApiClient","config","baseUrl","enabledFeatures","ip","locale","plainText","preflight","changes","experiences","profile","parseWithFriendlyError","ExperienceResponse","data","body","JSON","events","profileId","BatchExperienceRequestData","profiles","BatchExperienceResponse","path","URL","headers","Map","bodyOptions","Array","ExperienceRequestData","ExperienceEventArray","BEACON_FALLBACK_MESSAGE","INSIGHTS_BASE_URL","InsightsApiClient","batches","beacon","urlString","BatchInsightsEventArray","serializedBody","beaconSuccessfullyQueued","ApiClient","experience","insights","apiConfig"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/optimization-api-client",
3
- "version": "0.1.0-alpha15",
3
+ "version": "0.1.0-alpha16",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -57,8 +57,6 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "es-toolkit": "^1.45.1",
61
- "zod": "^4.3.6",
62
- "@contentful/optimization-api-schemas": "0.1.0-alpha15"
60
+ "@contentful/optimization-api-schemas": "0.1.0-alpha16"
63
61
  }
64
62
  }