@peektravel/app-utilities 0.8.0 → 0.8.2

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.cts CHANGED
@@ -896,6 +896,14 @@ interface CreateBookingInput {
896
896
  * invalid checkbox value fails creation before any quote is made.
897
897
  */
898
898
  customQuestionAnswers?: CustomQuestionAnswerInput[];
899
+ /**
900
+ * When `true`, creation fails if any of the activity's required custom
901
+ * questions (per-guest questions excluded) is left unanswered. Because this
902
+ * enforces coverage against the activity's questions, it forces custom-question
903
+ * validation to run even when `customQuestionAnswers` is empty or omitted.
904
+ * Default: `false`.
905
+ */
906
+ requireRequiredQuestions?: boolean;
899
907
  /** Suppress the customer confirmation email. Default: false. */
900
908
  skipCustomerEmail?: boolean;
901
909
  /** Clone the quote from an existing order. */
@@ -1268,8 +1276,9 @@ declare class BookingService {
1268
1276
  *
1269
1277
  * @throws {Error} when `activityId`, `availabilityTimeId`, a ticket
1270
1278
  * `resourceOptionId`/positive `quantity`, or the guest `name` is missing; when
1271
- * `markAsPaid` is set without an `idempotencyKey`; or when the quote/order
1272
- * mutations fail.
1279
+ * `markAsPaid` is set without an `idempotencyKey`; when
1280
+ * `requireRequiredQuestions` is set and a required custom question is left
1281
+ * unanswered; or when the quote/order mutations fail.
1273
1282
  */
1274
1283
  create(input: CreateBookingInput): Promise<CreatedBooking>;
1275
1284
  /**
@@ -2291,6 +2300,7 @@ declare class RestClient$1 {
2291
2300
  *
2292
2301
  * @throws {AdminAccountRequiredError} on HTTP 418
2293
2302
  * @throws {RateLimitError} on HTTP 429 after retries are exhausted
2303
+ * @throws {CngPermissionError} on HTTP 403 (app missing a permission)
2294
2304
  * @throws {CngApiError} on any other non-2xx response
2295
2305
  */
2296
2306
  get<T>(path: string): Promise<T>;
@@ -2312,23 +2322,30 @@ declare class RestClient$1 {
2312
2322
  *
2313
2323
  * `CngAccessService.getAllActivities()` returns these as a flat list.
2314
2324
  *
2315
- * NOTE: field mapping is a best-guess placeholder until the real
2316
- * `commerce-config/products` response shape is confirmed — see
2317
- * `internal/cng/products/product-queries.ts`.
2325
+ * Mapped from the `app-registry/products` REST payload — see
2326
+ * `internal/cng/products/product-queries.ts`. That payload carries no
2327
+ * product-type discriminator and no sub-options, so `type` is always
2328
+ * {@link ACTIVITY_PRODUCT_TYPE} and `tickets` is always empty.
2318
2329
  */
2319
2330
  interface Activity {
2320
2331
  /** Stable unique identifier for the activity. */
2321
2332
  productId: string;
2322
2333
  /** Human-readable display name. */
2323
2334
  name: string;
2324
- /** Product type reported by CNG (e.g. `"ACTIVITY"`). */
2335
+ /**
2336
+ * Product type. Always {@link ACTIVITY_PRODUCT_TYPE} — the CNG products
2337
+ * payload has no type discriminator. Kept for parity with the Peek `Product`.
2338
+ */
2325
2339
  type: string;
2326
2340
  /**
2327
- * Display color as a hex string (e.g. `"#1A2B3C"`). Empty string when no
2328
- * color is set.
2341
+ * Display color as a hex string (e.g. `"#1A2B3C"`), from the product's
2342
+ * access-control color. Empty string when no color is set.
2329
2343
  */
2330
2344
  color: string;
2331
- /** The bookable sub-options (tickets) of this activity. */
2345
+ /**
2346
+ * The bookable sub-options (tickets) of this activity. Always empty — CNG
2347
+ * exposes no tickets on the products endpoint today.
2348
+ */
2332
2349
  tickets: ActivityTicket[];
2333
2350
  }
2334
2351
  /** A single bookable sub-option (ticket) of an {@link Activity}. */
@@ -2855,15 +2872,6 @@ declare function parseInstallWebhook(token: string, body: unknown, secret: strin
2855
2872
  */
2856
2873
  declare function verifyInstallWebhook(token: string, secret: string): InstallWebhookClaims;
2857
2874
 
2858
- /**
2859
- * Typed errors thrown by the package. Each mirrors a failure mode of the Peek
2860
- * GraphQL gateway (or the CNG REST gateway) so callers can branch on the error
2861
- * type rather than parsing messages.
2862
- *
2863
- * `AdminAccountRequiredError` and `RateLimitError` are shared by all gateways.
2864
- * `PeekGraphQLError` is Peek-only; `CngApiError` is CNG-only; `AcmeApiError` is
2865
- * ACME-only.
2866
- */
2867
2875
  /**
2868
2876
  * Thrown when the gateway responds with HTTP 418, indicating the install is not
2869
2877
  * permitted to perform the request because an admin account is required.
@@ -2938,6 +2946,32 @@ declare class CngApiError extends Error {
2938
2946
  readonly body: unknown;
2939
2947
  constructor(statusCode: number, body: unknown, message?: string);
2940
2948
  }
2949
+ /**
2950
+ * Thrown when the CNG REST gateway responds with HTTP 403 because the app lacks
2951
+ * a permission the endpoint requires (e.g. `products:read`). This is an
2952
+ * **expected** failure for a misconfigured install — not a bug — so the
2953
+ * transport logs it at `warn` rather than `error` and leaves presentation to the
2954
+ * caller, which can branch on the type and surface the missing permission
2955
+ * however its UI prefers.
2956
+ *
2957
+ * The permission names the gateway named are on
2958
+ * {@link CngPermissionError.permissions} (empty when the body does not name
2959
+ * any), so a caller can render them without re-parsing the raw body. Thrown in
2960
+ * place of {@link CngApiError} for 403s only; every other non-2xx status still
2961
+ * throws `CngApiError`.
2962
+ */
2963
+ declare class CngPermissionError extends Error {
2964
+ /** The HTTP status that triggered this error. */
2965
+ readonly statusCode = 403;
2966
+ /**
2967
+ * The permissions the gateway reported as missing, e.g. `["products:read"]`.
2968
+ * Empty when the response body did not name any.
2969
+ */
2970
+ readonly permissions: string[];
2971
+ /** The raw response body (parsed JSON when possible, otherwise text). */
2972
+ readonly body: unknown;
2973
+ constructor(permissions: string[], body: unknown, message?: string);
2974
+ }
2941
2975
  /**
2942
2976
  * Thrown when the ACME REST gateway returns a non-2xx response that is not one
2943
2977
  * of the specifically-handled statuses (418/429). The offending status is
@@ -2953,4 +2987,4 @@ declare class AcmeApiError extends Error {
2953
2987
  constructor(statusCode: number, body: unknown, message?: string);
2954
2988
  }
2955
2989
 
2956
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestion, type CustomQuestionAnswer, type CustomQuestionAnswerInput, type CustomQuestionOption, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, INSTALL_STATUSES, type InstallAccessConfig, type InstallAccessService, type InstallAccessTarget, type InstallStatus, type InstallWebhook, type InstallWebhookAccount, type InstallWebhookClaims, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, type ProductMeetingLocation, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, createAccessServiceForInstall, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
2990
+ export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngPermissionError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestion, type CustomQuestionAnswer, type CustomQuestionAnswerInput, type CustomQuestionOption, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, INSTALL_STATUSES, type InstallAccessConfig, type InstallAccessService, type InstallAccessTarget, type InstallStatus, type InstallWebhook, type InstallWebhookAccount, type InstallWebhookClaims, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, type ProductMeetingLocation, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, createAccessServiceForInstall, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
package/dist/index.d.ts CHANGED
@@ -896,6 +896,14 @@ interface CreateBookingInput {
896
896
  * invalid checkbox value fails creation before any quote is made.
897
897
  */
898
898
  customQuestionAnswers?: CustomQuestionAnswerInput[];
899
+ /**
900
+ * When `true`, creation fails if any of the activity's required custom
901
+ * questions (per-guest questions excluded) is left unanswered. Because this
902
+ * enforces coverage against the activity's questions, it forces custom-question
903
+ * validation to run even when `customQuestionAnswers` is empty or omitted.
904
+ * Default: `false`.
905
+ */
906
+ requireRequiredQuestions?: boolean;
899
907
  /** Suppress the customer confirmation email. Default: false. */
900
908
  skipCustomerEmail?: boolean;
901
909
  /** Clone the quote from an existing order. */
@@ -1268,8 +1276,9 @@ declare class BookingService {
1268
1276
  *
1269
1277
  * @throws {Error} when `activityId`, `availabilityTimeId`, a ticket
1270
1278
  * `resourceOptionId`/positive `quantity`, or the guest `name` is missing; when
1271
- * `markAsPaid` is set without an `idempotencyKey`; or when the quote/order
1272
- * mutations fail.
1279
+ * `markAsPaid` is set without an `idempotencyKey`; when
1280
+ * `requireRequiredQuestions` is set and a required custom question is left
1281
+ * unanswered; or when the quote/order mutations fail.
1273
1282
  */
1274
1283
  create(input: CreateBookingInput): Promise<CreatedBooking>;
1275
1284
  /**
@@ -2291,6 +2300,7 @@ declare class RestClient$1 {
2291
2300
  *
2292
2301
  * @throws {AdminAccountRequiredError} on HTTP 418
2293
2302
  * @throws {RateLimitError} on HTTP 429 after retries are exhausted
2303
+ * @throws {CngPermissionError} on HTTP 403 (app missing a permission)
2294
2304
  * @throws {CngApiError} on any other non-2xx response
2295
2305
  */
2296
2306
  get<T>(path: string): Promise<T>;
@@ -2312,23 +2322,30 @@ declare class RestClient$1 {
2312
2322
  *
2313
2323
  * `CngAccessService.getAllActivities()` returns these as a flat list.
2314
2324
  *
2315
- * NOTE: field mapping is a best-guess placeholder until the real
2316
- * `commerce-config/products` response shape is confirmed — see
2317
- * `internal/cng/products/product-queries.ts`.
2325
+ * Mapped from the `app-registry/products` REST payload — see
2326
+ * `internal/cng/products/product-queries.ts`. That payload carries no
2327
+ * product-type discriminator and no sub-options, so `type` is always
2328
+ * {@link ACTIVITY_PRODUCT_TYPE} and `tickets` is always empty.
2318
2329
  */
2319
2330
  interface Activity {
2320
2331
  /** Stable unique identifier for the activity. */
2321
2332
  productId: string;
2322
2333
  /** Human-readable display name. */
2323
2334
  name: string;
2324
- /** Product type reported by CNG (e.g. `"ACTIVITY"`). */
2335
+ /**
2336
+ * Product type. Always {@link ACTIVITY_PRODUCT_TYPE} — the CNG products
2337
+ * payload has no type discriminator. Kept for parity with the Peek `Product`.
2338
+ */
2325
2339
  type: string;
2326
2340
  /**
2327
- * Display color as a hex string (e.g. `"#1A2B3C"`). Empty string when no
2328
- * color is set.
2341
+ * Display color as a hex string (e.g. `"#1A2B3C"`), from the product's
2342
+ * access-control color. Empty string when no color is set.
2329
2343
  */
2330
2344
  color: string;
2331
- /** The bookable sub-options (tickets) of this activity. */
2345
+ /**
2346
+ * The bookable sub-options (tickets) of this activity. Always empty — CNG
2347
+ * exposes no tickets on the products endpoint today.
2348
+ */
2332
2349
  tickets: ActivityTicket[];
2333
2350
  }
2334
2351
  /** A single bookable sub-option (ticket) of an {@link Activity}. */
@@ -2855,15 +2872,6 @@ declare function parseInstallWebhook(token: string, body: unknown, secret: strin
2855
2872
  */
2856
2873
  declare function verifyInstallWebhook(token: string, secret: string): InstallWebhookClaims;
2857
2874
 
2858
- /**
2859
- * Typed errors thrown by the package. Each mirrors a failure mode of the Peek
2860
- * GraphQL gateway (or the CNG REST gateway) so callers can branch on the error
2861
- * type rather than parsing messages.
2862
- *
2863
- * `AdminAccountRequiredError` and `RateLimitError` are shared by all gateways.
2864
- * `PeekGraphQLError` is Peek-only; `CngApiError` is CNG-only; `AcmeApiError` is
2865
- * ACME-only.
2866
- */
2867
2875
  /**
2868
2876
  * Thrown when the gateway responds with HTTP 418, indicating the install is not
2869
2877
  * permitted to perform the request because an admin account is required.
@@ -2938,6 +2946,32 @@ declare class CngApiError extends Error {
2938
2946
  readonly body: unknown;
2939
2947
  constructor(statusCode: number, body: unknown, message?: string);
2940
2948
  }
2949
+ /**
2950
+ * Thrown when the CNG REST gateway responds with HTTP 403 because the app lacks
2951
+ * a permission the endpoint requires (e.g. `products:read`). This is an
2952
+ * **expected** failure for a misconfigured install — not a bug — so the
2953
+ * transport logs it at `warn` rather than `error` and leaves presentation to the
2954
+ * caller, which can branch on the type and surface the missing permission
2955
+ * however its UI prefers.
2956
+ *
2957
+ * The permission names the gateway named are on
2958
+ * {@link CngPermissionError.permissions} (empty when the body does not name
2959
+ * any), so a caller can render them without re-parsing the raw body. Thrown in
2960
+ * place of {@link CngApiError} for 403s only; every other non-2xx status still
2961
+ * throws `CngApiError`.
2962
+ */
2963
+ declare class CngPermissionError extends Error {
2964
+ /** The HTTP status that triggered this error. */
2965
+ readonly statusCode = 403;
2966
+ /**
2967
+ * The permissions the gateway reported as missing, e.g. `["products:read"]`.
2968
+ * Empty when the response body did not name any.
2969
+ */
2970
+ readonly permissions: string[];
2971
+ /** The raw response body (parsed JSON when possible, otherwise text). */
2972
+ readonly body: unknown;
2973
+ constructor(permissions: string[], body: unknown, message?: string);
2974
+ }
2941
2975
  /**
2942
2976
  * Thrown when the ACME REST gateway returns a non-2xx response that is not one
2943
2977
  * of the specifically-handled statuses (418/429). The offending status is
@@ -2953,4 +2987,4 @@ declare class AcmeApiError extends Error {
2953
2987
  constructor(statusCode: number, body: unknown, message?: string);
2954
2988
  }
2955
2989
 
2956
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestion, type CustomQuestionAnswer, type CustomQuestionAnswerInput, type CustomQuestionOption, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, INSTALL_STATUSES, type InstallAccessConfig, type InstallAccessService, type InstallAccessTarget, type InstallStatus, type InstallWebhook, type InstallWebhookAccount, type InstallWebhookClaims, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, type ProductMeetingLocation, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, createAccessServiceForInstall, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
2990
+ export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngPermissionError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestion, type CustomQuestionAnswer, type CustomQuestionAnswerInput, type CustomQuestionOption, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, INSTALL_STATUSES, type InstallAccessConfig, type InstallAccessService, type InstallAccessTarget, type InstallStatus, type InstallWebhook, type InstallWebhookAccount, type InstallWebhookClaims, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, type ProductMeetingLocation, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, createAccessServiceForInstall, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
package/dist/index.js CHANGED
@@ -185,6 +185,13 @@ function resolveAccessOptions(options) {
185
185
  }
186
186
 
187
187
  // src/errors.ts
188
+ var FORBIDDEN_STATUS = 403;
189
+ var PERMISSION_MESSAGE_PREFIX = "CNG request forbidden: the app is missing the required permission";
190
+ var PERMISSION_MESSAGE_UNKNOWN = "CNG request forbidden: the app is missing a required permission";
191
+ function buildPermissionMessage(permissions) {
192
+ if (permissions.length === 0) return PERMISSION_MESSAGE_UNKNOWN;
193
+ return `${PERMISSION_MESSAGE_PREFIX}${permissions.length > 1 ? "s" : ""}: ${permissions.join(", ")}`;
194
+ }
188
195
  var AdminAccountRequiredError = class extends Error {
189
196
  /** The HTTP status that triggered this error. */
190
197
  statusCode = 418;
@@ -248,6 +255,23 @@ var CngApiError = class extends Error {
248
255
  this.body = body;
249
256
  }
250
257
  };
258
+ var CngPermissionError = class extends Error {
259
+ /** The HTTP status that triggered this error. */
260
+ statusCode = FORBIDDEN_STATUS;
261
+ /**
262
+ * The permissions the gateway reported as missing, e.g. `["products:read"]`.
263
+ * Empty when the response body did not name any.
264
+ */
265
+ permissions;
266
+ /** The raw response body (parsed JSON when possible, otherwise text). */
267
+ body;
268
+ constructor(permissions, body, message) {
269
+ super(message ?? buildPermissionMessage(permissions));
270
+ this.name = "CngPermissionError";
271
+ this.permissions = permissions;
272
+ this.body = body;
273
+ }
274
+ };
251
275
  var AcmeApiError = class extends Error {
252
276
  /** The HTTP status that triggered this error. */
253
277
  statusCode;
@@ -1059,6 +1083,7 @@ var errInvalidCheckbox = (v, id) => `Checkbox answer for question "${id}" must b
1059
1083
  var errUnknownOptionId = (v, id) => `No option matches id "${v}" for question "${id}"`;
1060
1084
  var errNoOptionMatch = (v, id) => `No option matches "${v}" for question "${id}"`;
1061
1085
  var errAmbiguousOption = (v, id) => `Option "${v}" matches more than one option for question "${id}"`;
1086
+ var errMissingRequired = (text, id) => `Custom question "${text}" (${id}) is required but was not answered`;
1062
1087
  function normalize(value) {
1063
1088
  return value.toLowerCase().replace(/[^a-z0-9]/g, "");
1064
1089
  }
@@ -1123,8 +1148,20 @@ function resolveOne(answer, questions) {
1123
1148
  throw new Error(errUnsupportedType(question.questionType, question.id));
1124
1149
  }
1125
1150
  }
1126
- function resolveCustomQuestionAnswers(answers, questions) {
1127
- return answers.map((answer) => resolveOne(answer, questions));
1151
+ function resolveCustomQuestionAnswers(answers, questions, options) {
1152
+ const resolved = answers.map((answer) => resolveOne(answer, questions));
1153
+ if (options?.requireRequired) {
1154
+ assertRequiredAnswered(resolved, questions);
1155
+ }
1156
+ return resolved;
1157
+ }
1158
+ function assertRequiredAnswered(resolved, questions) {
1159
+ const answered = new Set(resolved.map((answer) => answer.questionId));
1160
+ for (const question of questions) {
1161
+ if (question.isRequired && !question.perGuest && !answered.has(question.id)) {
1162
+ throw new Error(errMissingRequired(question.questionText, question.id));
1163
+ }
1164
+ }
1128
1165
  }
1129
1166
 
1130
1167
  // src/internal/peek/bookings/booking-service.ts
@@ -1641,8 +1678,9 @@ ${note}`;
1641
1678
  *
1642
1679
  * @throws {Error} when `activityId`, `availabilityTimeId`, a ticket
1643
1680
  * `resourceOptionId`/positive `quantity`, or the guest `name` is missing; when
1644
- * `markAsPaid` is set without an `idempotencyKey`; or when the quote/order
1645
- * mutations fail.
1681
+ * `markAsPaid` is set without an `idempotencyKey`; when
1682
+ * `requireRequiredQuestions` is set and a required custom question is left
1683
+ * unanswered; or when the quote/order mutations fail.
1646
1684
  */
1647
1685
  async create(input) {
1648
1686
  validateCreateInput(input);
@@ -1722,15 +1760,22 @@ ${note}`;
1722
1760
  * against; throws on the first unmatched/ambiguous/invalid answer.
1723
1761
  */
1724
1762
  async buildQuestionAnswers(input) {
1725
- const answers = input.customQuestionAnswers;
1726
- if (!answers || answers.length === 0) {
1763
+ const answers = input.customQuestionAnswers ?? [];
1764
+ const requireRequired = input.requireRequiredQuestions ?? false;
1765
+ if (answers.length === 0 && !requireRequired) {
1727
1766
  return null;
1728
1767
  }
1729
1768
  const questions = await this.deps.productService.getCustomQuestions(
1730
1769
  input.activityId
1731
1770
  );
1732
- return resolveCustomQuestionAnswers(answers, questions).map((resolved) => ({
1733
- ...resolved,
1771
+ const resolved = resolveCustomQuestionAnswers(answers, questions, {
1772
+ requireRequired
1773
+ });
1774
+ if (resolved.length === 0) {
1775
+ return null;
1776
+ }
1777
+ return resolved.map((answer) => ({
1778
+ ...answer,
1734
1779
  refid: randomUUID()
1735
1780
  }));
1736
1781
  }
@@ -4113,7 +4158,7 @@ var PeekAccessService = class {
4113
4158
 
4114
4159
  // src/internal/cng/endpoints.ts
4115
4160
  var CNG_EXTENDABLE_SLUG = "cng_backoffice_api-v1";
4116
- var PRODUCTS_PATH = "api/v2/commerce-config/products";
4161
+ var PRODUCTS_PATH = "api/v2/app-registry/products?active=1";
4117
4162
 
4118
4163
  // src/models/cng/product.ts
4119
4164
  var ACTIVITY_PRODUCT_TYPE2 = "ACTIVITY";
@@ -4124,14 +4169,11 @@ function fromProductNodes(nodes) {
4124
4169
  }
4125
4170
  function fromProductNode(node) {
4126
4171
  return {
4127
- productId: node.id || "",
4172
+ productId: node.id == null ? "" : String(node.id),
4128
4173
  name: node.name || "",
4129
- type: node.product_type || ACTIVITY_PRODUCT_TYPE2,
4130
- color: node.color_hex || "",
4131
- tickets: (node.tickets ?? []).map((ticket) => ({
4132
- id: ticket.id,
4133
- name: ticket.name
4134
- }))
4174
+ type: ACTIVITY_PRODUCT_TYPE2,
4175
+ color: node.access_control_color_hex || "",
4176
+ tickets: []
4135
4177
  };
4136
4178
  }
4137
4179
 
@@ -4150,15 +4192,13 @@ var CngProductService = class {
4150
4192
  * ```
4151
4193
  */
4152
4194
  async getAllActivities() {
4153
- const body = await this.client.get(
4154
- PRODUCTS_PATH
4155
- );
4156
- const nodes = Array.isArray(body) ? body : body?.products ?? [];
4157
- return fromProductNodes(nodes ?? []);
4195
+ const body = await this.client.get(PRODUCTS_PATH);
4196
+ return fromProductNodes(body?.data ?? []);
4158
4197
  }
4159
4198
  };
4160
4199
 
4161
4200
  // src/internal/cng/rest-client.ts
4201
+ var PERMISSION_SEPARATOR = ": ";
4162
4202
  var RestClient = class {
4163
4203
  constructor(options) {
4164
4204
  this.options = options;
@@ -4171,6 +4211,7 @@ var RestClient = class {
4171
4211
  *
4172
4212
  * @throws {AdminAccountRequiredError} on HTTP 418
4173
4213
  * @throws {RateLimitError} on HTTP 429 after retries are exhausted
4214
+ * @throws {CngPermissionError} on HTTP 403 (app missing a permission)
4174
4215
  * @throws {CngApiError} on any other non-2xx response
4175
4216
  */
4176
4217
  async get(path) {
@@ -4184,6 +4225,11 @@ var RestClient = class {
4184
4225
  path,
4185
4226
  async (response) => {
4186
4227
  const body = await parseBody(response);
4228
+ if (response.status === FORBIDDEN_STATUS) {
4229
+ const permissions = extractMissingPermissions(body);
4230
+ logger.warn(`Missing permission for ${path} (HTTP 403)`, { url, permissions });
4231
+ throw new CngPermissionError(permissions, body);
4232
+ }
4187
4233
  if (!response.ok) {
4188
4234
  logger.error(`CNG request failed with HTTP ${response.status}`, { url });
4189
4235
  throw new CngApiError(response.status, body);
@@ -4204,6 +4250,12 @@ var RestClient = class {
4204
4250
  };
4205
4251
  }
4206
4252
  };
4253
+ function extractMissingPermissions(body) {
4254
+ if (typeof body !== "object" || body === null) return [];
4255
+ const raw = body.errors?.permission;
4256
+ if (!Array.isArray(raw)) return [];
4257
+ return raw.filter((entry) => typeof entry === "string").map((entry) => entry.split(PERMISSION_SEPARATOR).pop().trim()).filter((permission) => permission.length > 0);
4258
+ }
4207
4259
 
4208
4260
  // src/cng-access-service.ts
4209
4261
  var DEFAULT_BASE_URL2 = "https://app-registry.peeklabs.com/installations-api";
@@ -4548,6 +4600,6 @@ function verifyInstallWebhook(token, secret) {
4548
4600
  };
4549
4601
  }
4550
4602
 
4551
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AcmeAccessService, AcmeApiError, AcmeProductService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngProductService, DailyNoteService, INSTALL_STATUSES, MembershipService, PeekAccessService, PeekGraphQLError, PeekHttpError, PiiAccessDisabledError, PricingService, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, createAccessServiceForInstall, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
4603
+ export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AcmeAccessService, AcmeApiError, AcmeProductService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngPermissionError, CngProductService, DailyNoteService, INSTALL_STATUSES, MembershipService, PeekAccessService, PeekGraphQLError, PeekHttpError, PiiAccessDisabledError, PricingService, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, createAccessServiceForInstall, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
4552
4604
  //# sourceMappingURL=index.js.map
4553
4605
  //# sourceMappingURL=index.js.map