@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.js CHANGED
@@ -3,11 +3,11 @@ import { randomUUID } from 'crypto';
3
3
 
4
4
  // src/peek-access-service.ts
5
5
 
6
- // src/internal/gateway-endpoints.ts
6
+ // src/internal/peek/gateway-endpoints.ts
7
7
  var SALES_ENDPOINT = "sales";
8
8
  var V2_EXTENDABLE_SLUG = "peek_backoffice_api-v1";
9
9
 
10
- // src/internal/account-users/account-user-converter.ts
10
+ // src/internal/peek/account-users/account-user-converter.ts
11
11
  var ACTIVE_STATUS = "ACTIVE";
12
12
  function fromAccountUserNode(node) {
13
13
  if (!node || node.status !== ACTIVE_STATUS) {
@@ -35,7 +35,7 @@ function fromAccountUserNodes(nodes) {
35
35
  return users;
36
36
  }
37
37
 
38
- // src/internal/account-users/account-user-queries.ts
38
+ // src/internal/peek/account-users/account-user-queries.ts
39
39
  var USER_QUERY = `
40
40
  query Sales($first: Int, $after: String) {
41
41
  accountUsers(first: $first, after: $after) {
@@ -81,7 +81,7 @@ var USER_BY_FILTER_QUERY = `
81
81
  }
82
82
  `;
83
83
 
84
- // src/internal/account-users/account-user-service.ts
84
+ // src/internal/peek/account-users/account-user-service.ts
85
85
  var DEFAULT_PAGE_SIZE = 50;
86
86
  var AccountUserService = class {
87
87
  constructor(client, options = {}) {
@@ -135,7 +135,7 @@ var AccountUserService = class {
135
135
  }
136
136
  };
137
137
 
138
- // src/internal/availability/availability-queries.ts
138
+ // src/internal/peek/availability/availability-queries.ts
139
139
  var AVAILABILITY_TIMES_QUERY = `
140
140
  query Sales($activityId: ID!, $resourceOptionQuantities: [ResourceOptionQuantityData!]!, $date: Date!) {
141
141
  availabilityTimes(activityId: $activityId, resourceOptionQuantities: $resourceOptionQuantities, date: $date) {
@@ -160,7 +160,7 @@ var AVAILABILITY_TIMES_QUERY = `
160
160
  }
161
161
  `;
162
162
 
163
- // src/internal/availability/availability-service.ts
163
+ // src/internal/peek/availability/availability-service.ts
164
164
  var AvailabilityService = class {
165
165
  constructor(client) {
166
166
  this.client = client;
@@ -181,12 +181,12 @@ var AvailabilityService = class {
181
181
  }
182
182
  };
183
183
 
184
- // src/models/product.ts
184
+ // src/models/peek/product.ts
185
185
  var ACTIVITY_PRODUCT_TYPE = "ACTIVITY";
186
186
  var RENTAL_PRODUCT_TYPE = "RENTAL";
187
187
  var ADD_ON_PRODUCT_TYPE = "ADD-ON";
188
188
 
189
- // src/internal/bookings/booking-queries.ts
189
+ // src/internal/peek/bookings/booking-queries.ts
190
190
  var SEARCH_BY_PURCHASE_DATE = "purchaseDate";
191
191
  var SEARCH_BY_ACTIVITY_DATE = "activityDate";
192
192
  function normalizeBookingId(bookingId) {
@@ -547,7 +547,7 @@ function buildBookingsVariables(params) {
547
547
  };
548
548
  }
549
549
 
550
- // src/internal/bookings/booking-converter.ts
550
+ // src/internal/peek/bookings/booking-converter.ts
551
551
  var UNKNOWN = "unknown";
552
552
  var PEEK_PRO_DEEP_LINK_BASE = "http://pro-app.peek.com/-/order";
553
553
  var SOURCE_SOURCE_MAP = {
@@ -778,7 +778,7 @@ function mapPrice(priceData) {
778
778
  };
779
779
  }
780
780
 
781
- // src/internal/bookings/booking-guest-converter.ts
781
+ // src/internal/peek/bookings/booking-guest-converter.ts
782
782
  function fromBookingGuestsResponse(response) {
783
783
  const firstEdge = (response?.sales?.edges ?? [])[0];
784
784
  if (!firstEdge) {
@@ -799,7 +799,7 @@ function fromBookingGuestsResponse(response) {
799
799
  return guests;
800
800
  }
801
801
 
802
- // src/internal/bookings/payments-on-file-converter.ts
802
+ // src/internal/peek/bookings/payments-on-file-converter.ts
803
803
  function fromPaymentsOnFileResponse(response, bookingId) {
804
804
  const firstEdge = (response?.sales?.edges ?? [])[0];
805
805
  if (!firstEdge) {
@@ -841,7 +841,7 @@ function dateOnly(iso) {
841
841
  return iso.split("T")[0] ?? iso;
842
842
  }
843
843
 
844
- // src/internal/bookings/addon-queries.ts
844
+ // src/internal/peek/bookings/addon-queries.ts
845
845
  var RESERVATION_STATUS_CONFIRMED = "CONFIRMED";
846
846
  var ADDON_OPTION_STATUS_CANCELED = "CANCELED";
847
847
  var SALES_ADDONS_PAGE_SIZE = 100;
@@ -885,7 +885,7 @@ function buildSalesAddonsVariables(searchString) {
885
885
  };
886
886
  }
887
887
 
888
- // src/internal/bookings/addon-converter.ts
888
+ // src/internal/peek/bookings/addon-converter.ts
889
889
  var ERROR_INCONSISTENT_ADDON_ITEM_ID = "Add-on group contains options with mismatched item IDs";
890
890
  function parseSaleNode(node) {
891
891
  const bookingId = node.id || "";
@@ -947,7 +947,7 @@ function toBookingAddon(item) {
947
947
  return { addonId, addonName, total: item.total, addonOptions };
948
948
  }
949
949
 
950
- // src/internal/bookings/booking-service.ts
950
+ // src/internal/peek/bookings/booking-service.ts
951
951
  var DEFAULT_PAGE_SIZE2 = 50;
952
952
  var DEFAULT_CANCEL_NOTE = "Canceled";
953
953
  var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
@@ -1697,7 +1697,7 @@ function buildCancellation(items, addonOptionId, quantity) {
1697
1697
  return { bookingQuotes, canceledCount };
1698
1698
  }
1699
1699
 
1700
- // src/internal/daily-notes/daily-note-converter.ts
1700
+ // src/internal/peek/daily-notes/daily-note-converter.ts
1701
1701
  function toDailyNote(response) {
1702
1702
  const union = response?.dailyNote;
1703
1703
  if (!union) {
@@ -1718,7 +1718,7 @@ function cleanNote(note) {
1718
1718
  return { note: note.note };
1719
1719
  }
1720
1720
 
1721
- // src/internal/daily-notes/daily-note-queries.ts
1721
+ // src/internal/peek/daily-notes/daily-note-queries.ts
1722
1722
  var GLOBAL_NOTE_TYPE = "DASHBOARD";
1723
1723
  var DAILY_NOTE_TODAY_QUERY = `
1724
1724
  query dailyNote($type: GlobalNoteType!) {
@@ -1749,7 +1749,7 @@ function buildUpsertDailyNoteVariables(note) {
1749
1749
  return { input: { type: GLOBAL_NOTE_TYPE, note } };
1750
1750
  }
1751
1751
 
1752
- // src/internal/daily-notes/daily-note-service.ts
1752
+ // src/internal/peek/daily-notes/daily-note-service.ts
1753
1753
  var DailyNoteService = class {
1754
1754
  constructor(client) {
1755
1755
  this.client = client;
@@ -1772,6 +1772,53 @@ var DailyNoteService = class {
1772
1772
  return fromUpsertResponse(body.data);
1773
1773
  }
1774
1774
  };
1775
+ var TokenManager = class {
1776
+ constructor(options) {
1777
+ this.options = options;
1778
+ }
1779
+ options;
1780
+ cached;
1781
+ /**
1782
+ * Returns a valid bearer token, reusing the cached one until it is within
1783
+ * `leewaySeconds` of expiring, at which point a fresh token is minted.
1784
+ */
1785
+ getToken() {
1786
+ const now = Date.now();
1787
+ if (this.cached && now < this.cached.expiresAtMs) {
1788
+ return this.cached.token;
1789
+ }
1790
+ const { secret, issuer, installId, ttlSeconds, leewaySeconds } = this.options;
1791
+ const token = jwt.sign({}, secret, {
1792
+ expiresIn: ttlSeconds,
1793
+ issuer,
1794
+ subject: installId
1795
+ });
1796
+ this.cached = {
1797
+ token,
1798
+ expiresAtMs: now + (ttlSeconds - leewaySeconds) * 1e3
1799
+ };
1800
+ return token;
1801
+ }
1802
+ };
1803
+
1804
+ // src/access-service-config.ts
1805
+ var DEFAULT_TOKEN_TTL_SECONDS = 3600;
1806
+ var DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS = 60;
1807
+ var DEFAULT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
1808
+ function requireNonEmpty(value, name, serviceName) {
1809
+ if (!value) {
1810
+ throw new Error(`${serviceName}: "${name}" is required`);
1811
+ }
1812
+ }
1813
+ function createTokenManager(config) {
1814
+ return new TokenManager({
1815
+ secret: config.jwtSecret,
1816
+ issuer: config.issuer,
1817
+ installId: config.installId,
1818
+ ttlSeconds: config.tokenTtlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS,
1819
+ leewaySeconds: config.tokenRefreshLeewaySeconds ?? DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS
1820
+ });
1821
+ }
1775
1822
 
1776
1823
  // src/errors.ts
1777
1824
  var AdminAccountRequiredError = class extends Error {
@@ -1799,11 +1846,49 @@ var PeekGraphQLError = class extends Error {
1799
1846
  this.graphqlErrors = graphqlErrors;
1800
1847
  }
1801
1848
  };
1849
+ var CngApiError = class extends Error {
1850
+ /** The HTTP status that triggered this error. */
1851
+ statusCode;
1852
+ /** The raw response body (parsed JSON when possible, otherwise text). */
1853
+ body;
1854
+ constructor(statusCode, body, message) {
1855
+ super(message ?? `CNG request failed with HTTP ${statusCode}`);
1856
+ this.name = "CngApiError";
1857
+ this.statusCode = statusCode;
1858
+ this.body = body;
1859
+ }
1860
+ };
1802
1861
 
1803
- // src/internal/graphql-client.ts
1862
+ // src/internal/http-transport.ts
1804
1863
  var RATE_LIMIT_STATUS = 429;
1805
1864
  var ADMIN_ACCOUNT_REQUIRED_STATUS = 418;
1806
1865
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1866
+ async function requestWithRetry(http, url, init, label, onResponse) {
1867
+ const { retryDelaysMs, logger, fetchFn } = http;
1868
+ for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
1869
+ const response = await fetchFn(url, init);
1870
+ if (response.status === ADMIN_ACCOUNT_REQUIRED_STATUS) {
1871
+ logger.warn(`Admin account required for ${label} (HTTP 418)`, { url });
1872
+ throw new AdminAccountRequiredError();
1873
+ }
1874
+ if (response.status === RATE_LIMIT_STATUS) {
1875
+ const delay = retryDelaysMs[attempt];
1876
+ if (delay !== void 0) {
1877
+ logger.warn(
1878
+ `Rate limited on ${label}, retrying in ${delay}ms (attempt ${attempt + 1}/${retryDelaysMs.length})`
1879
+ );
1880
+ await sleep(delay);
1881
+ continue;
1882
+ }
1883
+ logger.error(`Rate limit exceeded for ${label}`, { url });
1884
+ throw new RateLimitError();
1885
+ }
1886
+ return onResponse(response);
1887
+ }
1888
+ throw new RateLimitError();
1889
+ }
1890
+
1891
+ // src/internal/peek/graphql-client.ts
1807
1892
  var GraphQLClient = class {
1808
1893
  constructor(options) {
1809
1894
  this.options = options;
@@ -1811,50 +1896,39 @@ var GraphQLClient = class {
1811
1896
  options;
1812
1897
  /**
1813
1898
  * Executes a GraphQL query against the named endpoint and returns the raw
1814
- * response body. Retries on HTTP 429 per the configured backoff.
1899
+ * response body. Retries on HTTP 429 per the configured backoff (via the
1900
+ * shared {@link requestWithRetry} loop).
1815
1901
  */
1816
1902
  async request(endpointName, query, variables) {
1817
- const { retryDelaysMs, logger, fetchFn } = this.options;
1903
+ const { logger } = this.options;
1818
1904
  const url = this.endpoint(endpointName);
1819
1905
  const collapsedQuery = query.replace(/\s+/g, " ").trim();
1820
1906
  logger.info("Making GraphQL request", { url, endpointName });
1821
- for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
1822
- const response = await fetchFn(url, {
1907
+ return requestWithRetry(
1908
+ this.options,
1909
+ url,
1910
+ {
1823
1911
  method: "POST",
1824
1912
  headers: this.buildHeaders(),
1825
1913
  body: JSON.stringify({ query: collapsedQuery, variables })
1826
- });
1827
- if (response.status === ADMIN_ACCOUNT_REQUIRED_STATUS) {
1828
- logger.warn(`Admin account required for ${endpointName} (HTTP 418)`, { url });
1829
- throw new AdminAccountRequiredError();
1830
- }
1831
- if (response.status === RATE_LIMIT_STATUS) {
1832
- const delay = retryDelaysMs[attempt];
1833
- if (delay !== void 0) {
1834
- logger.warn(
1835
- `Rate limited on ${endpointName}, retrying in ${delay}ms (attempt ${attempt + 1}/${retryDelaysMs.length})`
1836
- );
1837
- await sleep(delay);
1838
- continue;
1914
+ },
1915
+ endpointName,
1916
+ async (response) => {
1917
+ const body = await response.json();
1918
+ if (body.errors) {
1919
+ logger.error(`GraphQL errors for ${endpointName}`, {
1920
+ url,
1921
+ graphqlErrors: JSON.stringify(body.errors)
1922
+ });
1923
+ throw new PeekGraphQLError(body.errors);
1839
1924
  }
1840
- logger.error(`Rate limit exceeded for ${endpointName}`, { url });
1841
- throw new RateLimitError();
1842
- }
1843
- const body = await response.json();
1844
- if (body.errors) {
1845
- logger.error(`GraphQL errors for ${endpointName}`, {
1846
- url,
1847
- graphqlErrors: JSON.stringify(body.errors)
1848
- });
1849
- throw new PeekGraphQLError(body.errors);
1850
- }
1851
- if (!response.ok) {
1852
- logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
1853
- throw new Error(`GraphQL request failed with HTTP ${response.status}`);
1925
+ if (!response.ok) {
1926
+ logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
1927
+ throw new Error(`GraphQL request failed with HTTP ${response.status}`);
1928
+ }
1929
+ return body;
1854
1930
  }
1855
- return body;
1856
- }
1857
- throw new RateLimitError();
1931
+ );
1858
1932
  }
1859
1933
  endpoint(endpointName) {
1860
1934
  const { baseUrl, appId, endpointPathPrefix } = this.options;
@@ -1873,7 +1947,7 @@ var GraphQLClient = class {
1873
1947
  }
1874
1948
  };
1875
1949
 
1876
- // src/internal/memberships/membership-converter.ts
1950
+ // src/internal/peek/memberships/membership-converter.ts
1877
1951
  function fromMembershipsResponse(response) {
1878
1952
  return (response?.memberships ?? []).flatMap(
1879
1953
  (membership) => (membership.membershipVariants ?? []).map((variant) => ({
@@ -1888,7 +1962,7 @@ function fromMembershipsResponse(response) {
1888
1962
  );
1889
1963
  }
1890
1964
 
1891
- // src/internal/memberships/membership-queries.ts
1965
+ // src/internal/peek/memberships/membership-queries.ts
1892
1966
  var MEMBERSHIPS_QUERY = `
1893
1967
  query Sales {
1894
1968
  memberships {
@@ -1933,7 +2007,7 @@ var CREATE_MEMBERSHIP_ORDER_FROM_QUOTE_MUTATION = `
1933
2007
  }
1934
2008
  `;
1935
2009
 
1936
- // src/internal/memberships/membership-service.ts
2010
+ // src/internal/peek/memberships/membership-service.ts
1937
2011
  var QUOTE_STATUS_CONFIRMED = "CONFIRMED";
1938
2012
  var DEFAULT_BALANCE_AMOUNT = "0.00";
1939
2013
  var DEFAULT_BALANCE_CURRENCY = "USD";
@@ -2039,7 +2113,7 @@ function validatePurchaseInput(input) {
2039
2113
  }
2040
2114
  }
2041
2115
 
2042
- // src/internal/resellers/channel-converter.ts
2116
+ // src/internal/peek/resellers/channel-converter.ts
2043
2117
  function fromChannelsResponse(response) {
2044
2118
  return (response?.channels ?? []).map(fromChannelNode);
2045
2119
  }
@@ -2060,7 +2134,7 @@ function fromChannelNode(node) {
2060
2134
  };
2061
2135
  }
2062
2136
 
2063
- // src/internal/resellers/channel-queries.ts
2137
+ // src/internal/peek/resellers/channel-queries.ts
2064
2138
  var CHANNELS_QUERY = `
2065
2139
  query Sales($first: Int) {
2066
2140
  channels {
@@ -2084,7 +2158,7 @@ var CHANNELS_QUERY = `
2084
2158
  }
2085
2159
  `;
2086
2160
 
2087
- // src/internal/resellers/reseller-service.ts
2161
+ // src/internal/peek/resellers/reseller-service.ts
2088
2162
  var DEFAULT_AGENTS_PER_CHANNEL = 10;
2089
2163
  var ResellerService = class {
2090
2164
  constructor(client) {
@@ -2103,7 +2177,7 @@ var ResellerService = class {
2103
2177
  }
2104
2178
  };
2105
2179
 
2106
- // src/internal/resource-pools/resource-pool-converter.ts
2180
+ // src/internal/peek/resource-pools/resource-pool-converter.ts
2107
2181
  function fromResourcePoolsResponse(response) {
2108
2182
  return (response?.resourcePools ?? []).map(fromResourcePoolNode);
2109
2183
  }
@@ -2131,7 +2205,7 @@ function fromAccountUser(accountUser) {
2131
2205
  };
2132
2206
  }
2133
2207
 
2134
- // src/internal/resource-pools/resource-pool-queries.ts
2208
+ // src/internal/peek/resource-pools/resource-pool-queries.ts
2135
2209
  var RESOURCE_POOLS_QUERY = `
2136
2210
  query Sales($filter: ResourcePoolsFilter) {
2137
2211
  resourcePools(filter: $filter) {
@@ -2152,7 +2226,7 @@ var RESOURCE_POOLS_QUERY = `
2152
2226
  }
2153
2227
  `;
2154
2228
 
2155
- // src/internal/resource-pools/resource-pool-service.ts
2229
+ // src/internal/peek/resource-pools/resource-pool-service.ts
2156
2230
  var DEFAULT_MODE = "ALL";
2157
2231
  var ResourcePoolService = class {
2158
2232
  constructor(client) {
@@ -2172,7 +2246,7 @@ var ResourcePoolService = class {
2172
2246
  }
2173
2247
  };
2174
2248
 
2175
- // src/internal/reviews/review-converter.ts
2249
+ // src/internal/peek/reviews/review-converter.ts
2176
2250
  var ISO_DATE_LENGTH = 10;
2177
2251
  function toDateOnly(isoDateTime) {
2178
2252
  return isoDateTime.slice(0, ISO_DATE_LENGTH);
@@ -2196,13 +2270,13 @@ function fromReviewNode(node) {
2196
2270
  };
2197
2271
  }
2198
2272
 
2199
- // src/internal/reviews/review-cursor.ts
2273
+ // src/internal/peek/reviews/review-cursor.ts
2200
2274
  function encodeCursor(offset, pageSize) {
2201
2275
  const start = Math.max(0, offset - pageSize + 1);
2202
2276
  return Buffer.from(`range:${start}..${offset},${offset}`).toString("base64");
2203
2277
  }
2204
2278
 
2205
- // src/internal/reviews/review-queries.ts
2279
+ // src/internal/peek/reviews/review-queries.ts
2206
2280
  var REVIEWS_QUERY = `
2207
2281
  query Reviews($first: Int, $filter: ReviewFilter, $after: String) {
2208
2282
  reviews(first: $first, filter: $filter, after: $after) {
@@ -2237,7 +2311,7 @@ function buildReviewsVariables(params) {
2237
2311
  };
2238
2312
  }
2239
2313
 
2240
- // src/internal/reviews/review-service.ts
2314
+ // src/internal/peek/reviews/review-service.ts
2241
2315
  var DEFAULT_REVIEW_COUNT = 50;
2242
2316
  var MIN_REVIEW_COUNT = 1;
2243
2317
  var MAX_REVIEW_COUNT = 50;
@@ -2298,7 +2372,7 @@ var ReviewService = class {
2298
2372
  }
2299
2373
  };
2300
2374
 
2301
- // src/internal/timeslots/timeslot-converter.ts
2375
+ // src/internal/peek/timeslots/timeslot-converter.ts
2302
2376
  function fromTimeslotNodes(nodes, productId) {
2303
2377
  if (!Array.isArray(nodes) || nodes.length === 0) {
2304
2378
  return [];
@@ -2356,7 +2430,7 @@ function mapAssignedResources(allocations) {
2356
2430
  });
2357
2431
  }
2358
2432
 
2359
- // src/internal/timeslots/guide-matcher.ts
2433
+ // src/internal/peek/timeslots/guide-matcher.ts
2360
2434
  function matchGuideToResourcePool(guideId, guideResourcePools, accountUsers) {
2361
2435
  const directIdMatch = guideResourcePools.find((pool) => pool.id === guideId);
2362
2436
  if (directIdMatch) {
@@ -2390,7 +2464,7 @@ function matchGuideToResourcePool(guideId, guideResourcePools, accountUsers) {
2390
2464
  return null;
2391
2465
  }
2392
2466
 
2393
- // src/internal/timeslots/resource-allocation-queries.ts
2467
+ // src/internal/peek/timeslots/resource-allocation-queries.ts
2394
2468
  var RESOURCE_ALLOCATION_BULK_REQUEST_MUTATION = `
2395
2469
  mutation ResourceAllocationBulkRequest($input: ResourceAllocationBulkRequestInput!) {
2396
2470
  resourceAllocationBulkRequest(input: $input) {
@@ -2409,7 +2483,7 @@ function buildResourceAllocationVariables(timeslotIds, resourcePoolIds, status)
2409
2483
  return { input: { timeslotIds, resourcePoolIds, status } };
2410
2484
  }
2411
2485
 
2412
- // src/internal/timeslots/timeslot-queries.ts
2486
+ // src/internal/peek/timeslots/timeslot-queries.ts
2413
2487
  var TIMESLOTS_QUERY = `
2414
2488
  query Sales($params: TimeslotsFilter!) {
2415
2489
  timeslots(filter: $params) {
@@ -2512,7 +2586,7 @@ function buildTimeslotVariables(productId, date, filter) {
2512
2586
  return { params };
2513
2587
  }
2514
2588
 
2515
- // src/internal/timeslots/timeslot-service.ts
2589
+ // src/internal/peek/timeslots/timeslot-service.ts
2516
2590
  var GUIDE_CATEGORY = "guide";
2517
2591
  var ERROR_MISSING_TIMESLOTS_OR_GUIDES = "At least one timeslot and one guide are required";
2518
2592
  var ERROR_INVALID_ACTION = "Invalid action. Must be either assign or unassign";
@@ -2629,7 +2703,7 @@ function normalizeDate(date) {
2629
2703
  return date.split("T")[0] ?? date;
2630
2704
  }
2631
2705
 
2632
- // src/internal/products/product-converter.ts
2706
+ // src/internal/peek/products/product-converter.ts
2633
2707
  var ADD_ON_COLOR = "#FFFFFF";
2634
2708
  function fromActivities(activities) {
2635
2709
  return activities.map(fromActivity);
@@ -2668,7 +2742,7 @@ function fromItemOptionNodes(nodes) {
2668
2742
  return Array.from(grouped.values());
2669
2743
  }
2670
2744
 
2671
- // src/internal/products/product-queries.ts
2745
+ // src/internal/peek/products/product-queries.ts
2672
2746
  var PRODUCTS_QUERY = `
2673
2747
  query Sales {
2674
2748
  activities {
@@ -2708,7 +2782,7 @@ var ITEM_OPTIONS_QUERY = `
2708
2782
  }
2709
2783
  `;
2710
2784
 
2711
- // src/internal/products/product-service.ts
2785
+ // src/internal/peek/products/product-service.ts
2712
2786
  var DEFAULT_ITEM_OPTIONS_PAGE_SIZE = 50;
2713
2787
  var ProductService = class {
2714
2788
  constructor(client, options = {}) {
@@ -2785,7 +2859,7 @@ var ProductService = class {
2785
2859
  }
2786
2860
  };
2787
2861
 
2788
- // src/internal/promo-codes/promo-code-queries.ts
2862
+ // src/internal/peek/promo-codes/promo-code-queries.ts
2789
2863
  var PROMO_CODES_QUERY = `
2790
2864
  query Sales($first: Int, $after: String) {
2791
2865
  promoCodes(first: $first, after: $after) {
@@ -2824,7 +2898,7 @@ var CREATE_PROMO_CODE_MUTATION = `
2824
2898
  }
2825
2899
  `;
2826
2900
 
2827
- // src/internal/promo-codes/promo-code-service.ts
2901
+ // src/internal/peek/promo-codes/promo-code-service.ts
2828
2902
  var DEFAULT_PAGE_SIZE3 = 50;
2829
2903
  var DEFAULT_CURRENCY = "USD";
2830
2904
  var CURRENCY_PATTERN = /^[A-Z]{3}$/;
@@ -2903,34 +2977,6 @@ var PromoCodeService = class {
2903
2977
  return { id: result.id, name: result.name };
2904
2978
  }
2905
2979
  };
2906
- var TokenManager = class {
2907
- constructor(options) {
2908
- this.options = options;
2909
- }
2910
- options;
2911
- cached;
2912
- /**
2913
- * Returns a valid bearer token, reusing the cached one until it is within
2914
- * `leewaySeconds` of expiring, at which point a fresh token is minted.
2915
- */
2916
- getToken() {
2917
- const now = Date.now();
2918
- if (this.cached && now < this.cached.expiresAtMs) {
2919
- return this.cached.token;
2920
- }
2921
- const { secret, issuer, installId, ttlSeconds, leewaySeconds } = this.options;
2922
- const token = jwt.sign({}, secret, {
2923
- expiresIn: ttlSeconds,
2924
- issuer,
2925
- subject: installId
2926
- });
2927
- this.cached = {
2928
- token,
2929
- expiresAtMs: now + (ttlSeconds - leewaySeconds) * 1e3
2930
- };
2931
- return token;
2932
- }
2933
- };
2934
2980
 
2935
2981
  // src/logger.ts
2936
2982
  var noopLogger = {
@@ -2945,9 +2991,6 @@ var noopLogger = {
2945
2991
  // src/peek-access-service.ts
2946
2992
  var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
2947
2993
  var DEFAULT_V2_BASE_URL = "https://app-registry.peeklabs.com/installations-api";
2948
- var DEFAULT_TOKEN_TTL_SECONDS = 3600;
2949
- var DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS = 60;
2950
- var DEFAULT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
2951
2994
  var PEEK_TOKEN_ISSUER = "app_registry_v2";
2952
2995
  var PeekAccessService = class {
2953
2996
  client;
@@ -2966,20 +3009,14 @@ var PeekAccessService = class {
2966
3009
  reviewService;
2967
3010
  constructor(config) {
2968
3011
  const isV2 = config.mode === "v2";
2969
- requireNonEmpty(config.installId, "installId");
2970
- requireNonEmpty(config.jwtSecret, "jwtSecret");
2971
- requireNonEmpty(config.issuer, "issuer");
2972
- requireNonEmpty(config.appId, "appId");
2973
- if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey");
3012
+ requireNonEmpty(config.installId, "installId", "PeekAccessService");
3013
+ requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
3014
+ requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
3015
+ requireNonEmpty(config.appId, "appId", "PeekAccessService");
3016
+ if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
2974
3017
  this.jwtSecret = config.jwtSecret;
2975
3018
  const logger = config.logger ?? noopLogger;
2976
- const tokens = new TokenManager({
2977
- secret: config.jwtSecret,
2978
- issuer: config.issuer,
2979
- installId: config.installId,
2980
- ttlSeconds: config.tokenTtlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS,
2981
- leewaySeconds: config.tokenRefreshLeewaySeconds ?? DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS
2982
- });
3019
+ const tokens = createTokenManager(config);
2983
3020
  const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
2984
3021
  this.client = new GraphQLClient({
2985
3022
  baseUrl: config.baseUrl ?? defaultBaseUrl,
@@ -3305,13 +3342,148 @@ var PeekAccessService = class {
3305
3342
  return this.getReviewService().getReviews(productId, reviewCount, reviewOffset);
3306
3343
  }
3307
3344
  };
3308
- function requireNonEmpty(value, name) {
3309
- if (!value) {
3310
- throw new Error(`PeekAccessService: "${name}" is required`);
3311
- }
3345
+
3346
+ // src/internal/cng/endpoints.ts
3347
+ var CNG_EXTENDABLE_SLUG = "cng_backoffice_api-v1";
3348
+ var PRODUCTS_PATH = "api/v2/commerce-config/products";
3349
+
3350
+ // src/models/cng/product.ts
3351
+ var ACTIVITY_PRODUCT_TYPE2 = "ACTIVITY";
3352
+
3353
+ // src/internal/cng/products/product-converter.ts
3354
+ function fromProductNodes(nodes) {
3355
+ return nodes.map(fromProductNode);
3356
+ }
3357
+ function fromProductNode(node) {
3358
+ return {
3359
+ productId: node.id || "",
3360
+ name: node.name || "",
3361
+ type: node.product_type || ACTIVITY_PRODUCT_TYPE2,
3362
+ color: node.color_hex || "",
3363
+ tickets: (node.tickets ?? []).map((ticket) => ({
3364
+ id: ticket.id,
3365
+ name: ticket.name
3366
+ }))
3367
+ };
3312
3368
  }
3313
3369
 
3314
- // src/internal/bookings/booking-webhook.ts
3370
+ // src/internal/cng/products/product-service.ts
3371
+ var CngProductService = class {
3372
+ constructor(client) {
3373
+ this.client = client;
3374
+ }
3375
+ client;
3376
+ /**
3377
+ * Returns every activity as a single flat list.
3378
+ *
3379
+ * @example
3380
+ * ```ts
3381
+ * const activities = await cng.getProductService().getAllActivities();
3382
+ * ```
3383
+ */
3384
+ async getAllActivities() {
3385
+ const body = await this.client.get(
3386
+ PRODUCTS_PATH
3387
+ );
3388
+ const nodes = Array.isArray(body) ? body : body?.products ?? [];
3389
+ return fromProductNodes(nodes ?? []);
3390
+ }
3391
+ };
3392
+
3393
+ // src/internal/cng/rest-client.ts
3394
+ var RestClient = class {
3395
+ constructor(options) {
3396
+ this.options = options;
3397
+ }
3398
+ options;
3399
+ /**
3400
+ * Issues a GET against the named REST path and returns the parsed JSON body.
3401
+ * Retries on HTTP 429 per the configured backoff (via the shared
3402
+ * {@link requestWithRetry} loop).
3403
+ *
3404
+ * @throws {AdminAccountRequiredError} on HTTP 418
3405
+ * @throws {RateLimitError} on HTTP 429 after retries are exhausted
3406
+ * @throws {CngApiError} on any other non-2xx response
3407
+ */
3408
+ async get(path) {
3409
+ const { logger } = this.options;
3410
+ const url = this.endpoint(path);
3411
+ logger.info("Making CNG request", { url, path });
3412
+ return requestWithRetry(
3413
+ this.options,
3414
+ url,
3415
+ { method: "GET", headers: this.buildHeaders() },
3416
+ path,
3417
+ async (response) => {
3418
+ const body = await this.parseBody(response);
3419
+ if (!response.ok) {
3420
+ logger.error(`CNG request failed with HTTP ${response.status}`, { url });
3421
+ throw new CngApiError(response.status, body);
3422
+ }
3423
+ return body;
3424
+ }
3425
+ );
3426
+ }
3427
+ /** Reads the response body as JSON, falling back to raw text when unparseable. */
3428
+ async parseBody(response) {
3429
+ const text = await response.text();
3430
+ try {
3431
+ return JSON.parse(text);
3432
+ } catch {
3433
+ return text;
3434
+ }
3435
+ }
3436
+ endpoint(path) {
3437
+ const { baseUrl, appId, extendableSlug } = this.options;
3438
+ return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
3439
+ }
3440
+ buildHeaders() {
3441
+ return {
3442
+ "X-Peek-Auth": `Bearer ${this.options.getToken()}`,
3443
+ "Content-Type": "application/json"
3444
+ };
3445
+ }
3446
+ };
3447
+
3448
+ // src/cng-access-service.ts
3449
+ var DEFAULT_BASE_URL2 = "https://app-registry.peeklabs.com/installations-api";
3450
+ var CngAccessService = class {
3451
+ client;
3452
+ productService;
3453
+ constructor(config) {
3454
+ requireNonEmpty(config.installId, "installId", "CngAccessService");
3455
+ requireNonEmpty(config.jwtSecret, "jwtSecret", "CngAccessService");
3456
+ requireNonEmpty(config.issuer, "issuer", "CngAccessService");
3457
+ requireNonEmpty(config.appId, "appId", "CngAccessService");
3458
+ const logger = config.logger ?? noopLogger;
3459
+ const tokens = createTokenManager(config);
3460
+ this.client = new RestClient({
3461
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL2,
3462
+ appId: config.appId,
3463
+ extendableSlug: CNG_EXTENDABLE_SLUG,
3464
+ getToken: () => tokens.getToken(),
3465
+ retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
3466
+ logger,
3467
+ fetchFn: config.fetch ?? globalThis.fetch
3468
+ });
3469
+ }
3470
+ /**
3471
+ * Returns the {@link CngProductService} for this install, bound to the shared
3472
+ * authenticated transport. The instance is created lazily and reused.
3473
+ */
3474
+ getProductService() {
3475
+ if (!this.productService) {
3476
+ this.productService = new CngProductService(this.client);
3477
+ }
3478
+ return this.productService;
3479
+ }
3480
+ /** All activities. Delegates to {@link CngProductService.getAllActivities}. */
3481
+ getAllActivities() {
3482
+ return this.getProductService().getAllActivities();
3483
+ }
3484
+ };
3485
+
3486
+ // src/internal/peek/bookings/booking-webhook.ts
3315
3487
  var PAYLOAD_BOOKING_KEY = "booking";
3316
3488
  var VALUE_OPEN_TOKEN = "value {";
3317
3489
  var PRICE_BREAKDOWN_KEYS = [
@@ -3361,7 +3533,7 @@ function hasPriceBreakdown(node) {
3361
3533
  return PRICE_BREAKDOWN_KEYS.some((key) => key in value);
3362
3534
  }
3363
3535
 
3364
- // src/internal/waivers/waiver-webhook.ts
3536
+ // src/internal/peek/waivers/waiver-webhook.ts
3365
3537
  var PAYLOAD_WAIVER_KEY = "waiver";
3366
3538
  function parseWaiverWebhook(payload) {
3367
3539
  return fromWaiverNode(extractWaiverNode(payload));
@@ -3396,6 +3568,6 @@ function extractWaiverNode(payload) {
3396
3568
  return record;
3397
3569
  }
3398
3570
 
3399
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AdminAccountRequiredError, AvailabilityService, BookingService, DailyNoteService, MembershipService, PeekAccessService, PeekGraphQLError, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, noopLogger, parseBookingWebhook, parseWaiverWebhook };
3571
+ export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngProductService, DailyNoteService, MembershipService, PeekAccessService, PeekGraphQLError, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, noopLogger, parseBookingWebhook, parseWaiverWebhook };
3400
3572
  //# sourceMappingURL=index.js.map
3401
3573
  //# sourceMappingURL=index.js.map