@peektravel/app-utilities 0.2.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -25,11 +25,11 @@ var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
25
25
 
26
26
  // src/peek-access-service.ts
27
27
 
28
- // src/internal/gateway-endpoints.ts
28
+ // src/internal/peek/gateway-endpoints.ts
29
29
  var SALES_ENDPOINT = "sales";
30
30
  var V2_EXTENDABLE_SLUG = "peek_backoffice_api-v1";
31
31
 
32
- // src/internal/account-users/account-user-converter.ts
32
+ // src/internal/peek/account-users/account-user-converter.ts
33
33
  var ACTIVE_STATUS = "ACTIVE";
34
34
  function fromAccountUserNode(node) {
35
35
  if (!node || node.status !== ACTIVE_STATUS) {
@@ -57,7 +57,7 @@ function fromAccountUserNodes(nodes) {
57
57
  return users;
58
58
  }
59
59
 
60
- // src/internal/account-users/account-user-queries.ts
60
+ // src/internal/peek/account-users/account-user-queries.ts
61
61
  var USER_QUERY = `
62
62
  query Sales($first: Int, $after: String) {
63
63
  accountUsers(first: $first, after: $after) {
@@ -103,7 +103,7 @@ var USER_BY_FILTER_QUERY = `
103
103
  }
104
104
  `;
105
105
 
106
- // src/internal/account-users/account-user-service.ts
106
+ // src/internal/peek/account-users/account-user-service.ts
107
107
  var DEFAULT_PAGE_SIZE = 50;
108
108
  var AccountUserService = class {
109
109
  constructor(client, options = {}) {
@@ -157,7 +157,7 @@ var AccountUserService = class {
157
157
  }
158
158
  };
159
159
 
160
- // src/internal/availability/availability-queries.ts
160
+ // src/internal/peek/availability/availability-queries.ts
161
161
  var AVAILABILITY_TIMES_QUERY = `
162
162
  query Sales($activityId: ID!, $resourceOptionQuantities: [ResourceOptionQuantityData!]!, $date: Date!) {
163
163
  availabilityTimes(activityId: $activityId, resourceOptionQuantities: $resourceOptionQuantities, date: $date) {
@@ -182,7 +182,7 @@ var AVAILABILITY_TIMES_QUERY = `
182
182
  }
183
183
  `;
184
184
 
185
- // src/internal/availability/availability-service.ts
185
+ // src/internal/peek/availability/availability-service.ts
186
186
  var AvailabilityService = class {
187
187
  constructor(client) {
188
188
  this.client = client;
@@ -203,12 +203,12 @@ var AvailabilityService = class {
203
203
  }
204
204
  };
205
205
 
206
- // src/models/product.ts
206
+ // src/models/peek/product.ts
207
207
  var ACTIVITY_PRODUCT_TYPE = "ACTIVITY";
208
208
  var RENTAL_PRODUCT_TYPE = "RENTAL";
209
209
  var ADD_ON_PRODUCT_TYPE = "ADD-ON";
210
210
 
211
- // src/internal/bookings/booking-queries.ts
211
+ // src/internal/peek/bookings/booking-queries.ts
212
212
  var SEARCH_BY_PURCHASE_DATE = "purchaseDate";
213
213
  var SEARCH_BY_ACTIVITY_DATE = "activityDate";
214
214
  function normalizeBookingId(bookingId) {
@@ -569,7 +569,7 @@ function buildBookingsVariables(params) {
569
569
  };
570
570
  }
571
571
 
572
- // src/internal/bookings/booking-converter.ts
572
+ // src/internal/peek/bookings/booking-converter.ts
573
573
  var UNKNOWN = "unknown";
574
574
  var PEEK_PRO_DEEP_LINK_BASE = "http://pro-app.peek.com/-/order";
575
575
  var SOURCE_SOURCE_MAP = {
@@ -800,7 +800,7 @@ function mapPrice(priceData) {
800
800
  };
801
801
  }
802
802
 
803
- // src/internal/bookings/booking-guest-converter.ts
803
+ // src/internal/peek/bookings/booking-guest-converter.ts
804
804
  function fromBookingGuestsResponse(response) {
805
805
  const firstEdge = (response?.sales?.edges ?? [])[0];
806
806
  if (!firstEdge) {
@@ -821,7 +821,7 @@ function fromBookingGuestsResponse(response) {
821
821
  return guests;
822
822
  }
823
823
 
824
- // src/internal/bookings/payments-on-file-converter.ts
824
+ // src/internal/peek/bookings/payments-on-file-converter.ts
825
825
  function fromPaymentsOnFileResponse(response, bookingId) {
826
826
  const firstEdge = (response?.sales?.edges ?? [])[0];
827
827
  if (!firstEdge) {
@@ -863,7 +863,7 @@ function dateOnly(iso) {
863
863
  return iso.split("T")[0] ?? iso;
864
864
  }
865
865
 
866
- // src/internal/bookings/addon-queries.ts
866
+ // src/internal/peek/bookings/addon-queries.ts
867
867
  var RESERVATION_STATUS_CONFIRMED = "CONFIRMED";
868
868
  var ADDON_OPTION_STATUS_CANCELED = "CANCELED";
869
869
  var SALES_ADDONS_PAGE_SIZE = 100;
@@ -907,7 +907,7 @@ function buildSalesAddonsVariables(searchString) {
907
907
  };
908
908
  }
909
909
 
910
- // src/internal/bookings/addon-converter.ts
910
+ // src/internal/peek/bookings/addon-converter.ts
911
911
  var ERROR_INCONSISTENT_ADDON_ITEM_ID = "Add-on group contains options with mismatched item IDs";
912
912
  function parseSaleNode(node) {
913
913
  const bookingId = node.id || "";
@@ -969,7 +969,7 @@ function toBookingAddon(item) {
969
969
  return { addonId, addonName, total: item.total, addonOptions };
970
970
  }
971
971
 
972
- // src/internal/bookings/booking-service.ts
972
+ // src/internal/peek/bookings/booking-service.ts
973
973
  var DEFAULT_PAGE_SIZE2 = 50;
974
974
  var DEFAULT_CANCEL_NOTE = "Canceled";
975
975
  var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
@@ -1719,7 +1719,7 @@ function buildCancellation(items, addonOptionId, quantity) {
1719
1719
  return { bookingQuotes, canceledCount };
1720
1720
  }
1721
1721
 
1722
- // src/internal/daily-notes/daily-note-converter.ts
1722
+ // src/internal/peek/daily-notes/daily-note-converter.ts
1723
1723
  function toDailyNote(response) {
1724
1724
  const union = response?.dailyNote;
1725
1725
  if (!union) {
@@ -1740,7 +1740,7 @@ function cleanNote(note) {
1740
1740
  return { note: note.note };
1741
1741
  }
1742
1742
 
1743
- // src/internal/daily-notes/daily-note-queries.ts
1743
+ // src/internal/peek/daily-notes/daily-note-queries.ts
1744
1744
  var GLOBAL_NOTE_TYPE = "DASHBOARD";
1745
1745
  var DAILY_NOTE_TODAY_QUERY = `
1746
1746
  query dailyNote($type: GlobalNoteType!) {
@@ -1771,7 +1771,7 @@ function buildUpsertDailyNoteVariables(note) {
1771
1771
  return { input: { type: GLOBAL_NOTE_TYPE, note } };
1772
1772
  }
1773
1773
 
1774
- // src/internal/daily-notes/daily-note-service.ts
1774
+ // src/internal/peek/daily-notes/daily-note-service.ts
1775
1775
  var DailyNoteService = class {
1776
1776
  constructor(client) {
1777
1777
  this.client = client;
@@ -1794,6 +1794,53 @@ var DailyNoteService = class {
1794
1794
  return fromUpsertResponse(body.data);
1795
1795
  }
1796
1796
  };
1797
+ var TokenManager = class {
1798
+ constructor(options) {
1799
+ this.options = options;
1800
+ }
1801
+ options;
1802
+ cached;
1803
+ /**
1804
+ * Returns a valid bearer token, reusing the cached one until it is within
1805
+ * `leewaySeconds` of expiring, at which point a fresh token is minted.
1806
+ */
1807
+ getToken() {
1808
+ const now = Date.now();
1809
+ if (this.cached && now < this.cached.expiresAtMs) {
1810
+ return this.cached.token;
1811
+ }
1812
+ const { secret, issuer, installId, ttlSeconds, leewaySeconds } = this.options;
1813
+ const token = jwt__namespace.sign({}, secret, {
1814
+ expiresIn: ttlSeconds,
1815
+ issuer,
1816
+ subject: installId
1817
+ });
1818
+ this.cached = {
1819
+ token,
1820
+ expiresAtMs: now + (ttlSeconds - leewaySeconds) * 1e3
1821
+ };
1822
+ return token;
1823
+ }
1824
+ };
1825
+
1826
+ // src/access-service-config.ts
1827
+ var DEFAULT_TOKEN_TTL_SECONDS = 3600;
1828
+ var DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS = 60;
1829
+ var DEFAULT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
1830
+ function requireNonEmpty(value, name, serviceName) {
1831
+ if (!value) {
1832
+ throw new Error(`${serviceName}: "${name}" is required`);
1833
+ }
1834
+ }
1835
+ function createTokenManager(config) {
1836
+ return new TokenManager({
1837
+ secret: config.jwtSecret,
1838
+ issuer: config.issuer,
1839
+ installId: config.installId,
1840
+ ttlSeconds: config.tokenTtlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS,
1841
+ leewaySeconds: config.tokenRefreshLeewaySeconds ?? DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS
1842
+ });
1843
+ }
1797
1844
 
1798
1845
  // src/errors.ts
1799
1846
  var AdminAccountRequiredError = class extends Error {
@@ -1821,11 +1868,49 @@ var PeekGraphQLError = class extends Error {
1821
1868
  this.graphqlErrors = graphqlErrors;
1822
1869
  }
1823
1870
  };
1871
+ var CngApiError = class extends Error {
1872
+ /** The HTTP status that triggered this error. */
1873
+ statusCode;
1874
+ /** The raw response body (parsed JSON when possible, otherwise text). */
1875
+ body;
1876
+ constructor(statusCode, body, message) {
1877
+ super(message ?? `CNG request failed with HTTP ${statusCode}`);
1878
+ this.name = "CngApiError";
1879
+ this.statusCode = statusCode;
1880
+ this.body = body;
1881
+ }
1882
+ };
1824
1883
 
1825
- // src/internal/graphql-client.ts
1884
+ // src/internal/http-transport.ts
1826
1885
  var RATE_LIMIT_STATUS = 429;
1827
1886
  var ADMIN_ACCOUNT_REQUIRED_STATUS = 418;
1828
1887
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1888
+ async function requestWithRetry(http, url, init, label, onResponse) {
1889
+ const { retryDelaysMs, logger, fetchFn } = http;
1890
+ for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
1891
+ const response = await fetchFn(url, init);
1892
+ if (response.status === ADMIN_ACCOUNT_REQUIRED_STATUS) {
1893
+ logger.warn(`Admin account required for ${label} (HTTP 418)`, { url });
1894
+ throw new AdminAccountRequiredError();
1895
+ }
1896
+ if (response.status === RATE_LIMIT_STATUS) {
1897
+ const delay = retryDelaysMs[attempt];
1898
+ if (delay !== void 0) {
1899
+ logger.warn(
1900
+ `Rate limited on ${label}, retrying in ${delay}ms (attempt ${attempt + 1}/${retryDelaysMs.length})`
1901
+ );
1902
+ await sleep(delay);
1903
+ continue;
1904
+ }
1905
+ logger.error(`Rate limit exceeded for ${label}`, { url });
1906
+ throw new RateLimitError();
1907
+ }
1908
+ return onResponse(response);
1909
+ }
1910
+ throw new RateLimitError();
1911
+ }
1912
+
1913
+ // src/internal/peek/graphql-client.ts
1829
1914
  var GraphQLClient = class {
1830
1915
  constructor(options) {
1831
1916
  this.options = options;
@@ -1833,50 +1918,39 @@ var GraphQLClient = class {
1833
1918
  options;
1834
1919
  /**
1835
1920
  * Executes a GraphQL query against the named endpoint and returns the raw
1836
- * response body. Retries on HTTP 429 per the configured backoff.
1921
+ * response body. Retries on HTTP 429 per the configured backoff (via the
1922
+ * shared {@link requestWithRetry} loop).
1837
1923
  */
1838
1924
  async request(endpointName, query, variables) {
1839
- const { retryDelaysMs, logger, fetchFn } = this.options;
1925
+ const { logger } = this.options;
1840
1926
  const url = this.endpoint(endpointName);
1841
1927
  const collapsedQuery = query.replace(/\s+/g, " ").trim();
1842
1928
  logger.info("Making GraphQL request", { url, endpointName });
1843
- for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
1844
- const response = await fetchFn(url, {
1929
+ return requestWithRetry(
1930
+ this.options,
1931
+ url,
1932
+ {
1845
1933
  method: "POST",
1846
1934
  headers: this.buildHeaders(),
1847
1935
  body: JSON.stringify({ query: collapsedQuery, variables })
1848
- });
1849
- if (response.status === ADMIN_ACCOUNT_REQUIRED_STATUS) {
1850
- logger.warn(`Admin account required for ${endpointName} (HTTP 418)`, { url });
1851
- throw new AdminAccountRequiredError();
1852
- }
1853
- if (response.status === RATE_LIMIT_STATUS) {
1854
- const delay = retryDelaysMs[attempt];
1855
- if (delay !== void 0) {
1856
- logger.warn(
1857
- `Rate limited on ${endpointName}, retrying in ${delay}ms (attempt ${attempt + 1}/${retryDelaysMs.length})`
1858
- );
1859
- await sleep(delay);
1860
- continue;
1936
+ },
1937
+ endpointName,
1938
+ async (response) => {
1939
+ const body = await response.json();
1940
+ if (body.errors) {
1941
+ logger.error(`GraphQL errors for ${endpointName}`, {
1942
+ url,
1943
+ graphqlErrors: JSON.stringify(body.errors)
1944
+ });
1945
+ throw new PeekGraphQLError(body.errors);
1861
1946
  }
1862
- logger.error(`Rate limit exceeded for ${endpointName}`, { url });
1863
- throw new RateLimitError();
1864
- }
1865
- const body = await response.json();
1866
- if (body.errors) {
1867
- logger.error(`GraphQL errors for ${endpointName}`, {
1868
- url,
1869
- graphqlErrors: JSON.stringify(body.errors)
1870
- });
1871
- throw new PeekGraphQLError(body.errors);
1872
- }
1873
- if (!response.ok) {
1874
- logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
1875
- throw new Error(`GraphQL request failed with HTTP ${response.status}`);
1947
+ if (!response.ok) {
1948
+ logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
1949
+ throw new Error(`GraphQL request failed with HTTP ${response.status}`);
1950
+ }
1951
+ return body;
1876
1952
  }
1877
- return body;
1878
- }
1879
- throw new RateLimitError();
1953
+ );
1880
1954
  }
1881
1955
  endpoint(endpointName) {
1882
1956
  const { baseUrl, appId, endpointPathPrefix } = this.options;
@@ -1895,7 +1969,7 @@ var GraphQLClient = class {
1895
1969
  }
1896
1970
  };
1897
1971
 
1898
- // src/internal/memberships/membership-converter.ts
1972
+ // src/internal/peek/memberships/membership-converter.ts
1899
1973
  function fromMembershipsResponse(response) {
1900
1974
  return (response?.memberships ?? []).flatMap(
1901
1975
  (membership) => (membership.membershipVariants ?? []).map((variant) => ({
@@ -1910,7 +1984,7 @@ function fromMembershipsResponse(response) {
1910
1984
  );
1911
1985
  }
1912
1986
 
1913
- // src/internal/memberships/membership-queries.ts
1987
+ // src/internal/peek/memberships/membership-queries.ts
1914
1988
  var MEMBERSHIPS_QUERY = `
1915
1989
  query Sales {
1916
1990
  memberships {
@@ -1955,7 +2029,7 @@ var CREATE_MEMBERSHIP_ORDER_FROM_QUOTE_MUTATION = `
1955
2029
  }
1956
2030
  `;
1957
2031
 
1958
- // src/internal/memberships/membership-service.ts
2032
+ // src/internal/peek/memberships/membership-service.ts
1959
2033
  var QUOTE_STATUS_CONFIRMED = "CONFIRMED";
1960
2034
  var DEFAULT_BALANCE_AMOUNT = "0.00";
1961
2035
  var DEFAULT_BALANCE_CURRENCY = "USD";
@@ -2061,7 +2135,7 @@ function validatePurchaseInput(input) {
2061
2135
  }
2062
2136
  }
2063
2137
 
2064
- // src/internal/resellers/channel-converter.ts
2138
+ // src/internal/peek/resellers/channel-converter.ts
2065
2139
  function fromChannelsResponse(response) {
2066
2140
  return (response?.channels ?? []).map(fromChannelNode);
2067
2141
  }
@@ -2082,7 +2156,7 @@ function fromChannelNode(node) {
2082
2156
  };
2083
2157
  }
2084
2158
 
2085
- // src/internal/resellers/channel-queries.ts
2159
+ // src/internal/peek/resellers/channel-queries.ts
2086
2160
  var CHANNELS_QUERY = `
2087
2161
  query Sales($first: Int) {
2088
2162
  channels {
@@ -2106,7 +2180,7 @@ var CHANNELS_QUERY = `
2106
2180
  }
2107
2181
  `;
2108
2182
 
2109
- // src/internal/resellers/reseller-service.ts
2183
+ // src/internal/peek/resellers/reseller-service.ts
2110
2184
  var DEFAULT_AGENTS_PER_CHANNEL = 10;
2111
2185
  var ResellerService = class {
2112
2186
  constructor(client) {
@@ -2125,7 +2199,7 @@ var ResellerService = class {
2125
2199
  }
2126
2200
  };
2127
2201
 
2128
- // src/internal/resource-pools/resource-pool-converter.ts
2202
+ // src/internal/peek/resource-pools/resource-pool-converter.ts
2129
2203
  function fromResourcePoolsResponse(response) {
2130
2204
  return (response?.resourcePools ?? []).map(fromResourcePoolNode);
2131
2205
  }
@@ -2153,7 +2227,7 @@ function fromAccountUser(accountUser) {
2153
2227
  };
2154
2228
  }
2155
2229
 
2156
- // src/internal/resource-pools/resource-pool-queries.ts
2230
+ // src/internal/peek/resource-pools/resource-pool-queries.ts
2157
2231
  var RESOURCE_POOLS_QUERY = `
2158
2232
  query Sales($filter: ResourcePoolsFilter) {
2159
2233
  resourcePools(filter: $filter) {
@@ -2174,7 +2248,7 @@ var RESOURCE_POOLS_QUERY = `
2174
2248
  }
2175
2249
  `;
2176
2250
 
2177
- // src/internal/resource-pools/resource-pool-service.ts
2251
+ // src/internal/peek/resource-pools/resource-pool-service.ts
2178
2252
  var DEFAULT_MODE = "ALL";
2179
2253
  var ResourcePoolService = class {
2180
2254
  constructor(client) {
@@ -2194,7 +2268,7 @@ var ResourcePoolService = class {
2194
2268
  }
2195
2269
  };
2196
2270
 
2197
- // src/internal/reviews/review-converter.ts
2271
+ // src/internal/peek/reviews/review-converter.ts
2198
2272
  var ISO_DATE_LENGTH = 10;
2199
2273
  function toDateOnly(isoDateTime) {
2200
2274
  return isoDateTime.slice(0, ISO_DATE_LENGTH);
@@ -2218,13 +2292,13 @@ function fromReviewNode(node) {
2218
2292
  };
2219
2293
  }
2220
2294
 
2221
- // src/internal/reviews/review-cursor.ts
2295
+ // src/internal/peek/reviews/review-cursor.ts
2222
2296
  function encodeCursor(offset, pageSize) {
2223
2297
  const start = Math.max(0, offset - pageSize + 1);
2224
2298
  return Buffer.from(`range:${start}..${offset},${offset}`).toString("base64");
2225
2299
  }
2226
2300
 
2227
- // src/internal/reviews/review-queries.ts
2301
+ // src/internal/peek/reviews/review-queries.ts
2228
2302
  var REVIEWS_QUERY = `
2229
2303
  query Reviews($first: Int, $filter: ReviewFilter, $after: String) {
2230
2304
  reviews(first: $first, filter: $filter, after: $after) {
@@ -2259,7 +2333,7 @@ function buildReviewsVariables(params) {
2259
2333
  };
2260
2334
  }
2261
2335
 
2262
- // src/internal/reviews/review-service.ts
2336
+ // src/internal/peek/reviews/review-service.ts
2263
2337
  var DEFAULT_REVIEW_COUNT = 50;
2264
2338
  var MIN_REVIEW_COUNT = 1;
2265
2339
  var MAX_REVIEW_COUNT = 50;
@@ -2320,7 +2394,7 @@ var ReviewService = class {
2320
2394
  }
2321
2395
  };
2322
2396
 
2323
- // src/internal/timeslots/timeslot-converter.ts
2397
+ // src/internal/peek/timeslots/timeslot-converter.ts
2324
2398
  function fromTimeslotNodes(nodes, productId) {
2325
2399
  if (!Array.isArray(nodes) || nodes.length === 0) {
2326
2400
  return [];
@@ -2378,7 +2452,7 @@ function mapAssignedResources(allocations) {
2378
2452
  });
2379
2453
  }
2380
2454
 
2381
- // src/internal/timeslots/guide-matcher.ts
2455
+ // src/internal/peek/timeslots/guide-matcher.ts
2382
2456
  function matchGuideToResourcePool(guideId, guideResourcePools, accountUsers) {
2383
2457
  const directIdMatch = guideResourcePools.find((pool) => pool.id === guideId);
2384
2458
  if (directIdMatch) {
@@ -2412,7 +2486,7 @@ function matchGuideToResourcePool(guideId, guideResourcePools, accountUsers) {
2412
2486
  return null;
2413
2487
  }
2414
2488
 
2415
- // src/internal/timeslots/resource-allocation-queries.ts
2489
+ // src/internal/peek/timeslots/resource-allocation-queries.ts
2416
2490
  var RESOURCE_ALLOCATION_BULK_REQUEST_MUTATION = `
2417
2491
  mutation ResourceAllocationBulkRequest($input: ResourceAllocationBulkRequestInput!) {
2418
2492
  resourceAllocationBulkRequest(input: $input) {
@@ -2431,7 +2505,7 @@ function buildResourceAllocationVariables(timeslotIds, resourcePoolIds, status)
2431
2505
  return { input: { timeslotIds, resourcePoolIds, status } };
2432
2506
  }
2433
2507
 
2434
- // src/internal/timeslots/timeslot-queries.ts
2508
+ // src/internal/peek/timeslots/timeslot-queries.ts
2435
2509
  var TIMESLOTS_QUERY = `
2436
2510
  query Sales($params: TimeslotsFilter!) {
2437
2511
  timeslots(filter: $params) {
@@ -2534,7 +2608,7 @@ function buildTimeslotVariables(productId, date, filter) {
2534
2608
  return { params };
2535
2609
  }
2536
2610
 
2537
- // src/internal/timeslots/timeslot-service.ts
2611
+ // src/internal/peek/timeslots/timeslot-service.ts
2538
2612
  var GUIDE_CATEGORY = "guide";
2539
2613
  var ERROR_MISSING_TIMESLOTS_OR_GUIDES = "At least one timeslot and one guide are required";
2540
2614
  var ERROR_INVALID_ACTION = "Invalid action. Must be either assign or unassign";
@@ -2651,7 +2725,7 @@ function normalizeDate(date) {
2651
2725
  return date.split("T")[0] ?? date;
2652
2726
  }
2653
2727
 
2654
- // src/internal/products/product-converter.ts
2728
+ // src/internal/peek/products/product-converter.ts
2655
2729
  var ADD_ON_COLOR = "#FFFFFF";
2656
2730
  function fromActivities(activities) {
2657
2731
  return activities.map(fromActivity);
@@ -2690,7 +2764,7 @@ function fromItemOptionNodes(nodes) {
2690
2764
  return Array.from(grouped.values());
2691
2765
  }
2692
2766
 
2693
- // src/internal/products/product-queries.ts
2767
+ // src/internal/peek/products/product-queries.ts
2694
2768
  var PRODUCTS_QUERY = `
2695
2769
  query Sales {
2696
2770
  activities {
@@ -2730,7 +2804,7 @@ var ITEM_OPTIONS_QUERY = `
2730
2804
  }
2731
2805
  `;
2732
2806
 
2733
- // src/internal/products/product-service.ts
2807
+ // src/internal/peek/products/product-service.ts
2734
2808
  var DEFAULT_ITEM_OPTIONS_PAGE_SIZE = 50;
2735
2809
  var ProductService = class {
2736
2810
  constructor(client, options = {}) {
@@ -2807,7 +2881,7 @@ var ProductService = class {
2807
2881
  }
2808
2882
  };
2809
2883
 
2810
- // src/internal/promo-codes/promo-code-queries.ts
2884
+ // src/internal/peek/promo-codes/promo-code-queries.ts
2811
2885
  var PROMO_CODES_QUERY = `
2812
2886
  query Sales($first: Int, $after: String) {
2813
2887
  promoCodes(first: $first, after: $after) {
@@ -2846,7 +2920,7 @@ var CREATE_PROMO_CODE_MUTATION = `
2846
2920
  }
2847
2921
  `;
2848
2922
 
2849
- // src/internal/promo-codes/promo-code-service.ts
2923
+ // src/internal/peek/promo-codes/promo-code-service.ts
2850
2924
  var DEFAULT_PAGE_SIZE3 = 50;
2851
2925
  var DEFAULT_CURRENCY = "USD";
2852
2926
  var CURRENCY_PATTERN = /^[A-Z]{3}$/;
@@ -2925,34 +2999,6 @@ var PromoCodeService = class {
2925
2999
  return { id: result.id, name: result.name };
2926
3000
  }
2927
3001
  };
2928
- var TokenManager = class {
2929
- constructor(options) {
2930
- this.options = options;
2931
- }
2932
- options;
2933
- cached;
2934
- /**
2935
- * Returns a valid bearer token, reusing the cached one until it is within
2936
- * `leewaySeconds` of expiring, at which point a fresh token is minted.
2937
- */
2938
- getToken() {
2939
- const now = Date.now();
2940
- if (this.cached && now < this.cached.expiresAtMs) {
2941
- return this.cached.token;
2942
- }
2943
- const { secret, issuer, installId, ttlSeconds, leewaySeconds } = this.options;
2944
- const token = jwt__namespace.sign({}, secret, {
2945
- expiresIn: ttlSeconds,
2946
- issuer,
2947
- subject: installId
2948
- });
2949
- this.cached = {
2950
- token,
2951
- expiresAtMs: now + (ttlSeconds - leewaySeconds) * 1e3
2952
- };
2953
- return token;
2954
- }
2955
- };
2956
3002
 
2957
3003
  // src/logger.ts
2958
3004
  var noopLogger = {
@@ -2967,9 +3013,6 @@ var noopLogger = {
2967
3013
  // src/peek-access-service.ts
2968
3014
  var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
2969
3015
  var DEFAULT_V2_BASE_URL = "https://app-registry.peeklabs.com/installations-api";
2970
- var DEFAULT_TOKEN_TTL_SECONDS = 3600;
2971
- var DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS = 60;
2972
- var DEFAULT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
2973
3016
  var PEEK_TOKEN_ISSUER = "app_registry_v2";
2974
3017
  var PeekAccessService = class {
2975
3018
  client;
@@ -2988,20 +3031,14 @@ var PeekAccessService = class {
2988
3031
  reviewService;
2989
3032
  constructor(config) {
2990
3033
  const isV2 = config.mode === "v2";
2991
- requireNonEmpty(config.installId, "installId");
2992
- requireNonEmpty(config.jwtSecret, "jwtSecret");
2993
- requireNonEmpty(config.issuer, "issuer");
2994
- requireNonEmpty(config.appId, "appId");
2995
- if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey");
3034
+ requireNonEmpty(config.installId, "installId", "PeekAccessService");
3035
+ requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
3036
+ requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
3037
+ requireNonEmpty(config.appId, "appId", "PeekAccessService");
3038
+ if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
2996
3039
  this.jwtSecret = config.jwtSecret;
2997
3040
  const logger = config.logger ?? noopLogger;
2998
- const tokens = new TokenManager({
2999
- secret: config.jwtSecret,
3000
- issuer: config.issuer,
3001
- installId: config.installId,
3002
- ttlSeconds: config.tokenTtlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS,
3003
- leewaySeconds: config.tokenRefreshLeewaySeconds ?? DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS
3004
- });
3041
+ const tokens = createTokenManager(config);
3005
3042
  const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
3006
3043
  this.client = new GraphQLClient({
3007
3044
  baseUrl: config.baseUrl ?? defaultBaseUrl,
@@ -3327,13 +3364,148 @@ var PeekAccessService = class {
3327
3364
  return this.getReviewService().getReviews(productId, reviewCount, reviewOffset);
3328
3365
  }
3329
3366
  };
3330
- function requireNonEmpty(value, name) {
3331
- if (!value) {
3332
- throw new Error(`PeekAccessService: "${name}" is required`);
3333
- }
3367
+
3368
+ // src/internal/cng/endpoints.ts
3369
+ var CNG_EXTENDABLE_SLUG = "cng_backoffice_api-v1";
3370
+ var PRODUCTS_PATH = "api/v2/commerce-config/products";
3371
+
3372
+ // src/models/cng/product.ts
3373
+ var ACTIVITY_PRODUCT_TYPE2 = "ACTIVITY";
3374
+
3375
+ // src/internal/cng/products/product-converter.ts
3376
+ function fromProductNodes(nodes) {
3377
+ return nodes.map(fromProductNode);
3378
+ }
3379
+ function fromProductNode(node) {
3380
+ return {
3381
+ productId: node.id || "",
3382
+ name: node.name || "",
3383
+ type: node.product_type || ACTIVITY_PRODUCT_TYPE2,
3384
+ color: node.color_hex || "",
3385
+ tickets: (node.tickets ?? []).map((ticket) => ({
3386
+ id: ticket.id,
3387
+ name: ticket.name
3388
+ }))
3389
+ };
3334
3390
  }
3335
3391
 
3336
- // src/internal/bookings/booking-webhook.ts
3392
+ // src/internal/cng/products/product-service.ts
3393
+ var CngProductService = class {
3394
+ constructor(client) {
3395
+ this.client = client;
3396
+ }
3397
+ client;
3398
+ /**
3399
+ * Returns every activity as a single flat list.
3400
+ *
3401
+ * @example
3402
+ * ```ts
3403
+ * const activities = await cng.getProductService().getAllActivities();
3404
+ * ```
3405
+ */
3406
+ async getAllActivities() {
3407
+ const body = await this.client.get(
3408
+ PRODUCTS_PATH
3409
+ );
3410
+ const nodes = Array.isArray(body) ? body : body?.products ?? [];
3411
+ return fromProductNodes(nodes ?? []);
3412
+ }
3413
+ };
3414
+
3415
+ // src/internal/cng/rest-client.ts
3416
+ var RestClient = class {
3417
+ constructor(options) {
3418
+ this.options = options;
3419
+ }
3420
+ options;
3421
+ /**
3422
+ * Issues a GET against the named REST path and returns the parsed JSON body.
3423
+ * Retries on HTTP 429 per the configured backoff (via the shared
3424
+ * {@link requestWithRetry} loop).
3425
+ *
3426
+ * @throws {AdminAccountRequiredError} on HTTP 418
3427
+ * @throws {RateLimitError} on HTTP 429 after retries are exhausted
3428
+ * @throws {CngApiError} on any other non-2xx response
3429
+ */
3430
+ async get(path) {
3431
+ const { logger } = this.options;
3432
+ const url = this.endpoint(path);
3433
+ logger.info("Making CNG request", { url, path });
3434
+ return requestWithRetry(
3435
+ this.options,
3436
+ url,
3437
+ { method: "GET", headers: this.buildHeaders() },
3438
+ path,
3439
+ async (response) => {
3440
+ const body = await this.parseBody(response);
3441
+ if (!response.ok) {
3442
+ logger.error(`CNG request failed with HTTP ${response.status}`, { url });
3443
+ throw new CngApiError(response.status, body);
3444
+ }
3445
+ return body;
3446
+ }
3447
+ );
3448
+ }
3449
+ /** Reads the response body as JSON, falling back to raw text when unparseable. */
3450
+ async parseBody(response) {
3451
+ const text = await response.text();
3452
+ try {
3453
+ return JSON.parse(text);
3454
+ } catch {
3455
+ return text;
3456
+ }
3457
+ }
3458
+ endpoint(path) {
3459
+ const { baseUrl, appId, extendableSlug } = this.options;
3460
+ return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
3461
+ }
3462
+ buildHeaders() {
3463
+ return {
3464
+ "X-Peek-Auth": `Bearer ${this.options.getToken()}`,
3465
+ "Content-Type": "application/json"
3466
+ };
3467
+ }
3468
+ };
3469
+
3470
+ // src/cng-access-service.ts
3471
+ var DEFAULT_BASE_URL2 = "https://app-registry.peeklabs.com/installations-api";
3472
+ var CngAccessService = class {
3473
+ client;
3474
+ productService;
3475
+ constructor(config) {
3476
+ requireNonEmpty(config.installId, "installId", "CngAccessService");
3477
+ requireNonEmpty(config.jwtSecret, "jwtSecret", "CngAccessService");
3478
+ requireNonEmpty(config.issuer, "issuer", "CngAccessService");
3479
+ requireNonEmpty(config.appId, "appId", "CngAccessService");
3480
+ const logger = config.logger ?? noopLogger;
3481
+ const tokens = createTokenManager(config);
3482
+ this.client = new RestClient({
3483
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL2,
3484
+ appId: config.appId,
3485
+ extendableSlug: CNG_EXTENDABLE_SLUG,
3486
+ getToken: () => tokens.getToken(),
3487
+ retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
3488
+ logger,
3489
+ fetchFn: config.fetch ?? globalThis.fetch
3490
+ });
3491
+ }
3492
+ /**
3493
+ * Returns the {@link CngProductService} for this install, bound to the shared
3494
+ * authenticated transport. The instance is created lazily and reused.
3495
+ */
3496
+ getProductService() {
3497
+ if (!this.productService) {
3498
+ this.productService = new CngProductService(this.client);
3499
+ }
3500
+ return this.productService;
3501
+ }
3502
+ /** All activities. Delegates to {@link CngProductService.getAllActivities}. */
3503
+ getAllActivities() {
3504
+ return this.getProductService().getAllActivities();
3505
+ }
3506
+ };
3507
+
3508
+ // src/internal/peek/bookings/booking-webhook.ts
3337
3509
  var PAYLOAD_BOOKING_KEY = "booking";
3338
3510
  var VALUE_OPEN_TOKEN = "value {";
3339
3511
  var PRICE_BREAKDOWN_KEYS = [
@@ -3383,7 +3555,7 @@ function hasPriceBreakdown(node) {
3383
3555
  return PRICE_BREAKDOWN_KEYS.some((key) => key in value);
3384
3556
  }
3385
3557
 
3386
- // src/internal/waivers/waiver-webhook.ts
3558
+ // src/internal/peek/waivers/waiver-webhook.ts
3387
3559
  var PAYLOAD_WAIVER_KEY = "waiver";
3388
3560
  function parseWaiverWebhook(payload) {
3389
3561
  return fromWaiverNode(extractWaiverNode(payload));
@@ -3424,6 +3596,9 @@ exports.AccountUserService = AccountUserService;
3424
3596
  exports.AdminAccountRequiredError = AdminAccountRequiredError;
3425
3597
  exports.AvailabilityService = AvailabilityService;
3426
3598
  exports.BookingService = BookingService;
3599
+ exports.CngAccessService = CngAccessService;
3600
+ exports.CngApiError = CngApiError;
3601
+ exports.CngProductService = CngProductService;
3427
3602
  exports.DailyNoteService = DailyNoteService;
3428
3603
  exports.MembershipService = MembershipService;
3429
3604
  exports.PeekAccessService = PeekAccessService;