@peektravel/app-utilities 0.9.4 → 1.0.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/README.md CHANGED
@@ -34,8 +34,8 @@ const peek = new PeekAccessService({
34
34
  installId: 'install-123', // JWT subject
35
35
  jwtSecret: process.env.PEEK_INTERNAL_SECRET!, // signs the JWT
36
36
  issuer: process.env.APP_NAME!, // JWT issuer
37
- appId: process.env.PEEK_APP_ID!, // gateway path segment
38
- gatewayKey: process.env.PEEK_GATEWAY_KEY!, // pk-api-key header
37
+ mode: 'v2', // connection generation — required (see below)
38
+ apiUrl: install.apiUrl, // per-install base URL from the install webhook (persisted)
39
39
  });
40
40
 
41
41
  const products: Product[] = await peek.getProductService().getAllProducts();
@@ -44,6 +44,41 @@ const products: Product[] = await peek.getProductService().getAllProducts();
44
44
  The access service is the authenticated root; each `get<Resource>Service()`
45
45
  returns a (memoized) service that owns that resource's calls.
46
46
 
47
+ ### Connection mode (`v1` / `v2`) — **required**
48
+
49
+ `mode` is a **mandatory** config field — you must choose a connection generation
50
+ explicitly. (It used to be optional, defaulting to `v1`; it is now required so
51
+ that updating the package forces a deliberate choice instead of silently keeping
52
+ the old behaviour.)
53
+
54
+ - **`mode: 'v2'`** — **use this going forward.** The app-registry connection. It
55
+ **requires** the install's `apiUrl` — the per-install base URL delivered on the
56
+ install webhook (`api.url`); the constructor throws without it. Persist that URL
57
+ per install (see [Webhooks](#webhooks)) and pass it as `apiUrl`; no `gatewayKey`
58
+ is needed. If you don't yet have a webhook `apiUrl` (e.g. an older install you
59
+ don't want to uninstall/reinstall just to get one), build one by appending your
60
+ **app slug** to the registry prefix and pass that as the `apiUrl` yourself —
61
+ `https://apps.peek.com/installations-api/<app-slug>` (same shape as the
62
+ webhook's `api.url`; the trailing app-slug segment is required — the prefix
63
+ alone is not a valid URL). Treat it as a transitional bridge and move to the
64
+ webhook URL.
65
+ - **`mode: 'v1'`** — **deprecated; will be removed.** The legacy gateway; needs
66
+ `appId` + `gatewayKey`. If your install relied on the previous default, the
67
+ fastest no-behaviour-change update is to pass `mode: 'v1'` — then plan the
68
+ migration to `v2`.
69
+
70
+ ```ts
71
+ // Legacy v1 (deprecated) — fastest update for an install that used the old default:
72
+ const peek = new PeekAccessService({
73
+ installId: 'install-123',
74
+ jwtSecret: process.env.PEEK_INTERNAL_SECRET!,
75
+ issuer: process.env.APP_NAME!,
76
+ appId: process.env.PEEK_APP_ID!, // gateway path segment
77
+ gatewayKey: process.env.PEEK_GATEWAY_KEY!, // pk-api-key header
78
+ mode: 'v1',
79
+ });
80
+ ```
81
+
47
82
  `getAllProducts()` returns a single flat list of activities **and** add-ons
48
83
  (add-ons tagged with `ADD_ON_PRODUCT_TYPE`), gathering all cursor-paginated
49
84
  add-on pages for you.
@@ -102,7 +137,7 @@ With `fullCustomerAccess` **off** (the default):
102
137
 
103
138
  ```ts
104
139
  const peek = new PeekAccessService({
105
- installId, jwtSecret, issuer, appId, gatewayKey,
140
+ installId, jwtSecret, issuer, mode: 'v2', apiUrl,
106
141
  accessOptions: { fullCustomerAccess: true }, // opt into PII + payment operations
107
142
  });
108
143
  ```
package/dist/index.cjs CHANGED
@@ -2496,6 +2496,123 @@ function buildCancellation(items, addonOptionId, quantity) {
2496
2496
  return { bookingQuotes, canceledCount };
2497
2497
  }
2498
2498
 
2499
+ // src/internal/peek/custom-questions/custom-question-converter.ts
2500
+ function fromQuestionConfigurations(configs) {
2501
+ return configs.map(
2502
+ (config) => fromQuestionNode(config.question, config.order, config.isRequired)
2503
+ );
2504
+ }
2505
+ function fromQuestionNodes(questions) {
2506
+ return questions.map((question) => fromQuestionNode(question));
2507
+ }
2508
+ function fromQuestionNode(question, order, isRequired) {
2509
+ return {
2510
+ id: question.id,
2511
+ order: order ?? 0,
2512
+ isRequired: isRequired ?? false,
2513
+ questionText: question.text,
2514
+ hintText: question.hint ?? null,
2515
+ questionType: question.questionType,
2516
+ internalLabel: question.internalLabel ?? null,
2517
+ perGuest: question.perGuest ?? false,
2518
+ defaultValue: question.answerDefaultValue ?? null,
2519
+ options: (question.options ?? []).map(fromQuestionOption)
2520
+ };
2521
+ }
2522
+ function fromQuestionOption(option) {
2523
+ return {
2524
+ id: option.id,
2525
+ order: option.order,
2526
+ value: option.text
2527
+ };
2528
+ }
2529
+
2530
+ // src/internal/peek/custom-questions/custom-question-queries.ts
2531
+ var ALL_CUSTOM_QUESTIONS_QUERY = `
2532
+ query Sales {
2533
+ questions {
2534
+ id
2535
+ questionType
2536
+ text
2537
+ answerDefaultValue
2538
+ hint
2539
+ internalLabel
2540
+ perGuest
2541
+ ... on ChoiceQuestion {
2542
+ options {
2543
+ id
2544
+ text
2545
+ order
2546
+ }
2547
+ }
2548
+ }
2549
+ }
2550
+ `;
2551
+ var CUSTOM_QUESTIONS_QUERY = `
2552
+ query Sales($id: ID!) {
2553
+ activity(id: $id) {
2554
+ id
2555
+ name
2556
+ questionActivityConfigurations {
2557
+ isRequired
2558
+ order
2559
+ question {
2560
+ id
2561
+ questionType
2562
+ text
2563
+ answerDefaultValue
2564
+ hint
2565
+ internalLabel
2566
+ perGuest
2567
+ ... on ChoiceQuestion {
2568
+ options {
2569
+ id
2570
+ text
2571
+ order
2572
+ }
2573
+ }
2574
+ }
2575
+ }
2576
+ }
2577
+ }
2578
+ `;
2579
+
2580
+ // src/internal/peek/custom-questions/custom-question-service.ts
2581
+ var CustomQuestionService = class {
2582
+ constructor(client) {
2583
+ this.client = client;
2584
+ }
2585
+ client;
2586
+ /**
2587
+ * Returns every custom question defined on the account, across all
2588
+ * activities. Choice-style questions (`SELECT_ONE`, `LOCATION`, …) carry their
2589
+ * selectable {@link CustomQuestion.options}; free-text/checkbox questions
2590
+ * return an empty option list.
2591
+ *
2592
+ * These are account-wide question **definitions**, not customer answers — no
2593
+ * PII is involved, so the result is unaffected by `fullCustomerAccess`. Unlike
2594
+ * {@link ProductService.getCustomQuestions}, the catalog carries no
2595
+ * per-activity configuration, so {@link CustomQuestion.order} is `0` and
2596
+ * {@link CustomQuestion.isRequired} is `false` on every entry.
2597
+ *
2598
+ * @returns Every custom question on the account, or an empty list when none
2599
+ * are defined.
2600
+ *
2601
+ * @example
2602
+ * ```ts
2603
+ * const questions = await peek.getCustomQuestionService().getAll();
2604
+ * ```
2605
+ */
2606
+ async getAll() {
2607
+ const body = await this.client.request(
2608
+ SALES_ENDPOINT,
2609
+ ALL_CUSTOM_QUESTIONS_QUERY,
2610
+ {}
2611
+ );
2612
+ return fromQuestionNodes(body.data?.questions ?? []);
2613
+ }
2614
+ };
2615
+
2499
2616
  // src/internal/peek/daily-notes/daily-note-converter.ts
2500
2617
  function toDailyNote(response) {
2501
2618
  const union = response?.dailyNote;
@@ -2674,7 +2791,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
2674
2791
  // src/internal/sdk-headers.ts
2675
2792
  var SDK_HEADER_NAME = "x-peek-sdk";
2676
2793
  var SDK_PLATFORM = "js";
2677
- var SDK_VERSION = "0.9.4";
2794
+ var SDK_VERSION = "1.0.0";
2678
2795
  var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
2679
2796
 
2680
2797
  // src/internal/peek/graphql-client.ts
@@ -3700,33 +3817,6 @@ function fromItemOptionNodes(nodes) {
3700
3817
  return Array.from(grouped.values());
3701
3818
  }
3702
3819
 
3703
- // src/internal/peek/products/custom-question-converter.ts
3704
- function fromQuestionConfigurations(configs) {
3705
- return configs.map(fromQuestionConfiguration);
3706
- }
3707
- function fromQuestionConfiguration(config) {
3708
- const question = config.question;
3709
- return {
3710
- id: question.id,
3711
- order: config.order ?? 0,
3712
- isRequired: config.isRequired ?? false,
3713
- questionText: question.text,
3714
- hintText: question.hint ?? null,
3715
- questionType: question.questionType,
3716
- internalLabel: question.internalLabel ?? null,
3717
- perGuest: question.perGuest ?? false,
3718
- defaultValue: question.answerDefaultValue ?? null,
3719
- options: (question.options ?? []).map(fromQuestionOption)
3720
- };
3721
- }
3722
- function fromQuestionOption(option) {
3723
- return {
3724
- id: option.id,
3725
- order: option.order,
3726
- value: option.text
3727
- };
3728
- }
3729
-
3730
3820
  // src/internal/peek/products/product-queries.ts
3731
3821
  var PRODUCTS_QUERY = `
3732
3822
  query Sales {
@@ -3785,36 +3875,6 @@ var ITEM_OPTIONS_QUERY = `
3785
3875
  }
3786
3876
  `;
3787
3877
 
3788
- // src/internal/peek/products/custom-question-queries.ts
3789
- var CUSTOM_QUESTIONS_QUERY = `
3790
- query Sales($id: ID!) {
3791
- activity(id: $id) {
3792
- id
3793
- name
3794
- questionActivityConfigurations {
3795
- isRequired
3796
- order
3797
- question {
3798
- id
3799
- questionType
3800
- text
3801
- answerDefaultValue
3802
- hint
3803
- internalLabel
3804
- perGuest
3805
- ... on ChoiceQuestion {
3806
- options {
3807
- id
3808
- text
3809
- order
3810
- }
3811
- }
3812
- }
3813
- }
3814
- }
3815
- }
3816
- `;
3817
-
3818
3878
  // src/internal/peek/products/product-service.ts
3819
3879
  var ERROR_PRODUCT_ID_REQUIRED2 = "A non-empty product id is required.";
3820
3880
  var DEFAULT_ITEM_OPTIONS_PAGE_SIZE = 50;
@@ -4374,7 +4434,9 @@ var noopLogger = {
4374
4434
 
4375
4435
  // src/peek-access-service.ts
4376
4436
  var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
4377
- var DEFAULT_V2_BASE_URL = "https://app-registry.peeklabs.com/installations-api";
4437
+ var V2_FALLBACK_URL_PREFIX = "https://apps.peek.com/installations-api";
4438
+ var ERROR_MODE_REQUIRED = `PeekAccessService: "mode" is required and must be "v1" or "v2". Existing installs used the previous default of "v1" \u2014 pass mode: "v1" for the fastest update. Note: "v1" is deprecated and will be removed; migrate to "v2" and pass the install webhook's apiUrl.`;
4439
+ var ERROR_V2_API_URL_REQUIRED = `PeekAccessService: mode "v2" requires an "apiUrl" \u2014 the per-install URL delivered on the install webhook (api.url) and persisted per install. If you do not have one yet, append your app slug to the registry prefix and pass that, e.g. "${V2_FALLBACK_URL_PREFIX}/<app-slug>" (the trailing app-slug segment is required \u2014 the prefix alone is not a valid apiUrl).`;
4378
4440
  var PeekAccessService = class {
4379
4441
  client;
4380
4442
  productServiceOptions;
@@ -4393,21 +4455,27 @@ var PeekAccessService = class {
4393
4455
  membershipService;
4394
4456
  bookingServices = /* @__PURE__ */ new Map();
4395
4457
  reviewService;
4458
+ customQuestionService;
4396
4459
  constructor(config) {
4397
4460
  const hasApiUrl = !!config.apiUrl;
4461
+ if (config.mode !== "v1" && config.mode !== "v2") {
4462
+ throw new Error(ERROR_MODE_REQUIRED);
4463
+ }
4398
4464
  const isV2 = config.mode === "v2";
4399
4465
  requireNonEmpty(config.installId, "installId", "PeekAccessService");
4400
4466
  requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
4401
4467
  requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
4402
- if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
4403
- if (!hasApiUrl && !isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
4468
+ if (isV2 && !hasApiUrl) throw new Error(ERROR_V2_API_URL_REQUIRED);
4469
+ if (!isV2 && !hasApiUrl) {
4470
+ requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
4471
+ requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
4472
+ }
4404
4473
  this.jwtSecret = config.jwtSecret;
4405
4474
  this.issuer = config.issuer;
4406
4475
  const logger = config.logger ?? noopLogger;
4407
4476
  const tokens = createTokenManager(config);
4408
- const useRegistrySlug = hasApiUrl || isV2;
4409
- const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
4410
- const baseApiUrl = hasApiUrl ? resolveBaseApiUrl(config.apiUrl, V2_EXTENDABLE_SLUG, "PeekAccessService") : `${config.baseUrl ?? defaultBaseUrl}/${config.appId}`;
4477
+ const useRegistrySlug = hasApiUrl;
4478
+ const baseApiUrl = hasApiUrl ? resolveBaseApiUrl(config.apiUrl, V2_EXTENDABLE_SLUG, "PeekAccessService") : `${config.baseUrl ?? DEFAULT_BASE_URL}/${config.appId}`;
4411
4479
  this.client = new GraphQLClient({
4412
4480
  baseApiUrl,
4413
4481
  endpoints: peekApiEndpoints(useRegistrySlug),
@@ -4593,6 +4661,16 @@ var PeekAccessService = class {
4593
4661
  }
4594
4662
  return this.reviewService;
4595
4663
  }
4664
+ /**
4665
+ * Returns the {@link CustomQuestionService} for this install, bound to the
4666
+ * shared authenticated transport. The instance is created lazily and reused.
4667
+ */
4668
+ getCustomQuestionService() {
4669
+ if (!this.customQuestionService) {
4670
+ this.customQuestionService = new CustomQuestionService(this.client);
4671
+ }
4672
+ return this.customQuestionService;
4673
+ }
4596
4674
  // ─── Product short-forms ─────────────────────────────────────────────────
4597
4675
  /** All products (activities + add-ons). @deprecated The access service exposes only its resource-service accessors; call the service directly instead. See {@link ProductService.getAllProducts}. */
4598
4676
  getAllProducts() {
@@ -5062,7 +5140,12 @@ var AcmeAccessService = class {
5062
5140
 
5063
5141
  // src/access-service-factory.ts
5064
5142
  function createAccessServiceForInstall(install, config) {
5065
- const shared = { ...config, installId: install.installId, apiUrl: install.apiUrl };
5143
+ const shared = {
5144
+ ...config,
5145
+ installId: install.installId,
5146
+ apiUrl: install.apiUrl,
5147
+ mode: "v2"
5148
+ };
5066
5149
  switch (install.platform) {
5067
5150
  case "peek":
5068
5151
  return new PeekAccessService(shared);
@@ -5242,6 +5325,7 @@ exports.CngAccessService = CngAccessService;
5242
5325
  exports.CngApiError = CngApiError;
5243
5326
  exports.CngPermissionError = CngPermissionError;
5244
5327
  exports.CngProductService = CngProductService;
5328
+ exports.CustomQuestionService = CustomQuestionService;
5245
5329
  exports.DailyNoteService = DailyNoteService;
5246
5330
  exports.INSTALL_STATUSES = INSTALL_STATUSES;
5247
5331
  exports.MembershipService = MembershipService;