@peektravel/app-utilities 0.7.4 → 0.8.1

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
@@ -421,6 +421,64 @@ declare const RENTAL_PRODUCT_TYPE = "RENTAL";
421
421
  */
422
422
  declare const ADD_ON_PRODUCT_TYPE = "ADD-ON";
423
423
 
424
+ /**
425
+ * The clean, transport-agnostic data model for an activity's custom questions.
426
+ *
427
+ * Custom questions are the operator-configured questions attached to a bookable
428
+ * activity (per-guest waiver-style prompts, free-text inputs, multiple-choice
429
+ * pickers, etc.). This is the shape consumers work with — it is decoupled from
430
+ * the underlying Peek GraphQL `Question`/`ChoiceQuestion` schema, which stays
431
+ * internal.
432
+ *
433
+ * Note: this describes the **question definitions** on an activity, not any
434
+ * customer's answers — so there is no PII here and nothing gated behind
435
+ * `fullCustomerAccess`.
436
+ */
437
+ /**
438
+ * A single custom question configured on an activity.
439
+ *
440
+ * The ordering and requiredness come from the activity's question
441
+ * configuration; the remaining fields describe the reusable question itself.
442
+ */
443
+ interface CustomQuestion {
444
+ /** Unique identifier of the question (e.g. `"cq_anq67"`). */
445
+ id: string;
446
+ /** 1-based position of the question within the activity's question list. */
447
+ order: number;
448
+ /** Whether the activity requires an answer to this question. */
449
+ isRequired: boolean;
450
+ /** The question prompt shown to the guest (Peek's `text`). */
451
+ questionText: string;
452
+ /** Helper/hint text shown alongside the prompt, or `null` when none. */
453
+ hintText: string | null;
454
+ /**
455
+ * The question's input type as reported by Peek — e.g. `"CHECK_BOX"`,
456
+ * `"TEXT"`, `"LOCATION"`, `"SELECT_ONE"`. Left as a string so new Peek
457
+ * question types flow through without a package change.
458
+ */
459
+ questionType: string;
460
+ /** Operator-only label for the question, or `null` when none. */
461
+ internalLabel: string | null;
462
+ /** `true` when the question is answered once per guest rather than per booking. */
463
+ perGuest: boolean;
464
+ /** Pre-filled default answer, or `null` when none. */
465
+ defaultValue: string | null;
466
+ /**
467
+ * Selectable options for choice-style questions (`SELECT_ONE`, `LOCATION`,
468
+ * …). Empty for free-text/checkbox questions that carry no options.
469
+ */
470
+ options: CustomQuestionOption[];
471
+ }
472
+ /** A single selectable option on a choice-style {@link CustomQuestion}. */
473
+ interface CustomQuestionOption {
474
+ /** Unique identifier of the option (e.g. `"cqao_mwdgvw"`). */
475
+ id: string;
476
+ /** 1-based position of the option within the question's option list. */
477
+ order: number;
478
+ /** The option's display label (Peek's `text`). */
479
+ value: string;
480
+ }
481
+
424
482
  /** Tuning options for a {@link ProductService}. */
425
483
  interface ProductServiceOptions {
426
484
  /** Page size for cursor-paginated item options. Default: 50. */
@@ -451,6 +509,21 @@ declare class ProductService {
451
509
  getAllRentals(): Promise<Product[]>;
452
510
  /** Returns only add-on products. */
453
511
  getAllAddons(): Promise<Product[]>;
512
+ /**
513
+ * Returns every custom question configured on a single activity, in the order
514
+ * Peek reports them. Choice-style questions (`SELECT_ONE`, `LOCATION`, …)
515
+ * carry their selectable {@link CustomQuestion.options}; free-text/checkbox
516
+ * questions return an empty option list.
517
+ *
518
+ * These are question **definitions**, not customer answers — no PII is
519
+ * involved, so the result is unaffected by `fullCustomerAccess`.
520
+ *
521
+ * @param productId The activity's id.
522
+ * @returns The activity's custom questions, or an empty list when the activity
523
+ * has none or is not found.
524
+ * @throws Error when `productId` is missing or blank.
525
+ */
526
+ getCustomQuestions(productId: string): Promise<CustomQuestion[]>;
454
527
  private fetchActivities;
455
528
  private fetchAllItemOptionNodes;
456
529
  }
@@ -773,6 +846,34 @@ interface CreateBookingGuest {
773
846
  optinMarketing?: boolean;
774
847
  optinSms?: boolean;
775
848
  }
849
+ /**
850
+ * An answer to one of an activity's custom questions, supplied when creating a
851
+ * booking. The question is identified by id **or** text, and the value is
852
+ * resolved against the question's type at create time (see
853
+ * {@link CreateBookingInput.customQuestionAnswers}).
854
+ */
855
+ interface CustomQuestionAnswerInput {
856
+ /**
857
+ * The question to answer — either its id (`cq_…`) or its question text. Text
858
+ * is matched leniently (lowercased, non-alphanumerics stripped); it must match
859
+ * exactly one question or creation fails.
860
+ */
861
+ questionIdOrText: string;
862
+ /**
863
+ * The answer. How it is interpreted depends on the question's type:
864
+ * - **checkbox** — `yes`/`no`/`true`/`false` (case-insensitive).
865
+ * - **text** — used verbatim.
866
+ * - **select-one / location** — an option id (`cqao_…`) or the option's label,
867
+ * matched leniently the same way as `questionIdOrText`.
868
+ *
869
+ * For **location** questions you only select the option — Peek holds each
870
+ * option's configured lat/long and applies it, so no coordinates are (or can
871
+ * be) supplied here.
872
+ *
873
+ * Per-guest questions are **not yet supported**: answering one fails creation.
874
+ */
875
+ value: string;
876
+ }
776
877
  /**
777
878
  * Input for creating a booking. IDs must already be resolved — the package does
778
879
  * not do free-text product/ticket/time matching (that stays in the caller).
@@ -788,6 +889,21 @@ interface CreateBookingInput {
788
889
  guest: CreateBookingGuest;
789
890
  /** Operator notes to attach. */
790
891
  operatorNotes?: string;
892
+ /**
893
+ * Answers to the activity's custom questions. When non-empty, the activity's
894
+ * custom questions are fetched and each answer is validated/resolved before
895
+ * the booking is created — an unmatched question, ambiguous text/option, or
896
+ * invalid checkbox value fails creation before any quote is made.
897
+ */
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;
791
907
  /** Suppress the customer confirmation email. Default: false. */
792
908
  skipCustomerEmail?: boolean;
793
909
  /** Clone the quote from an existing order. */
@@ -1160,10 +1276,18 @@ declare class BookingService {
1160
1276
  *
1161
1277
  * @throws {Error} when `activityId`, `availabilityTimeId`, a ticket
1162
1278
  * `resourceOptionId`/positive `quantity`, or the guest `name` is missing; when
1163
- * `markAsPaid` is set without an `idempotencyKey`; or when the quote/order
1164
- * 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.
1165
1282
  */
1166
1283
  create(input: CreateBookingInput): Promise<CreatedBooking>;
1284
+ /**
1285
+ * Resolves the input's custom-question answers into the quote's
1286
+ * `questionAnswers` payload (each tagged with a fresh `refid`), or `null` when
1287
+ * none were supplied. Fetches the activity's custom questions to validate
1288
+ * against; throws on the first unmatched/ambiguous/invalid answer.
1289
+ */
1290
+ private buildQuestionAnswers;
1167
1291
  private markCreatedBookingPaid;
1168
1292
  /** Finds the parent item id of an add-on by matching its option id. */
1169
1293
  private resolveParentItemId;
@@ -2062,6 +2186,8 @@ declare class PeekAccessService {
2062
2186
  getAllRentals(): Promise<Product[]>;
2063
2187
  /** All add-on products. Delegates to {@link ProductService.getAllAddons}. */
2064
2188
  getAllAddons(): Promise<Product[]>;
2189
+ /** An activity's custom questions. Delegates to {@link ProductService.getCustomQuestions}. */
2190
+ getCustomQuestions(productId: string): Promise<CustomQuestion[]>;
2065
2191
  /** All active account users. Delegates to {@link AccountUserService.getAll}. */
2066
2192
  getAllAccountUsers(): Promise<AccountUser[]>;
2067
2193
  /** Account user by id, or null. Delegates to {@link AccountUserService.getById}. */
@@ -2836,4 +2962,4 @@ declare class AcmeApiError extends Error {
2836
2962
  constructor(statusCode: number, body: unknown, message?: string);
2837
2963
  }
2838
2964
 
2839
- 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 CustomQuestionAnswer, 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 };
2965
+ 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 };
package/dist/index.d.ts CHANGED
@@ -421,6 +421,64 @@ declare const RENTAL_PRODUCT_TYPE = "RENTAL";
421
421
  */
422
422
  declare const ADD_ON_PRODUCT_TYPE = "ADD-ON";
423
423
 
424
+ /**
425
+ * The clean, transport-agnostic data model for an activity's custom questions.
426
+ *
427
+ * Custom questions are the operator-configured questions attached to a bookable
428
+ * activity (per-guest waiver-style prompts, free-text inputs, multiple-choice
429
+ * pickers, etc.). This is the shape consumers work with — it is decoupled from
430
+ * the underlying Peek GraphQL `Question`/`ChoiceQuestion` schema, which stays
431
+ * internal.
432
+ *
433
+ * Note: this describes the **question definitions** on an activity, not any
434
+ * customer's answers — so there is no PII here and nothing gated behind
435
+ * `fullCustomerAccess`.
436
+ */
437
+ /**
438
+ * A single custom question configured on an activity.
439
+ *
440
+ * The ordering and requiredness come from the activity's question
441
+ * configuration; the remaining fields describe the reusable question itself.
442
+ */
443
+ interface CustomQuestion {
444
+ /** Unique identifier of the question (e.g. `"cq_anq67"`). */
445
+ id: string;
446
+ /** 1-based position of the question within the activity's question list. */
447
+ order: number;
448
+ /** Whether the activity requires an answer to this question. */
449
+ isRequired: boolean;
450
+ /** The question prompt shown to the guest (Peek's `text`). */
451
+ questionText: string;
452
+ /** Helper/hint text shown alongside the prompt, or `null` when none. */
453
+ hintText: string | null;
454
+ /**
455
+ * The question's input type as reported by Peek — e.g. `"CHECK_BOX"`,
456
+ * `"TEXT"`, `"LOCATION"`, `"SELECT_ONE"`. Left as a string so new Peek
457
+ * question types flow through without a package change.
458
+ */
459
+ questionType: string;
460
+ /** Operator-only label for the question, or `null` when none. */
461
+ internalLabel: string | null;
462
+ /** `true` when the question is answered once per guest rather than per booking. */
463
+ perGuest: boolean;
464
+ /** Pre-filled default answer, or `null` when none. */
465
+ defaultValue: string | null;
466
+ /**
467
+ * Selectable options for choice-style questions (`SELECT_ONE`, `LOCATION`,
468
+ * …). Empty for free-text/checkbox questions that carry no options.
469
+ */
470
+ options: CustomQuestionOption[];
471
+ }
472
+ /** A single selectable option on a choice-style {@link CustomQuestion}. */
473
+ interface CustomQuestionOption {
474
+ /** Unique identifier of the option (e.g. `"cqao_mwdgvw"`). */
475
+ id: string;
476
+ /** 1-based position of the option within the question's option list. */
477
+ order: number;
478
+ /** The option's display label (Peek's `text`). */
479
+ value: string;
480
+ }
481
+
424
482
  /** Tuning options for a {@link ProductService}. */
425
483
  interface ProductServiceOptions {
426
484
  /** Page size for cursor-paginated item options. Default: 50. */
@@ -451,6 +509,21 @@ declare class ProductService {
451
509
  getAllRentals(): Promise<Product[]>;
452
510
  /** Returns only add-on products. */
453
511
  getAllAddons(): Promise<Product[]>;
512
+ /**
513
+ * Returns every custom question configured on a single activity, in the order
514
+ * Peek reports them. Choice-style questions (`SELECT_ONE`, `LOCATION`, …)
515
+ * carry their selectable {@link CustomQuestion.options}; free-text/checkbox
516
+ * questions return an empty option list.
517
+ *
518
+ * These are question **definitions**, not customer answers — no PII is
519
+ * involved, so the result is unaffected by `fullCustomerAccess`.
520
+ *
521
+ * @param productId The activity's id.
522
+ * @returns The activity's custom questions, or an empty list when the activity
523
+ * has none or is not found.
524
+ * @throws Error when `productId` is missing or blank.
525
+ */
526
+ getCustomQuestions(productId: string): Promise<CustomQuestion[]>;
454
527
  private fetchActivities;
455
528
  private fetchAllItemOptionNodes;
456
529
  }
@@ -773,6 +846,34 @@ interface CreateBookingGuest {
773
846
  optinMarketing?: boolean;
774
847
  optinSms?: boolean;
775
848
  }
849
+ /**
850
+ * An answer to one of an activity's custom questions, supplied when creating a
851
+ * booking. The question is identified by id **or** text, and the value is
852
+ * resolved against the question's type at create time (see
853
+ * {@link CreateBookingInput.customQuestionAnswers}).
854
+ */
855
+ interface CustomQuestionAnswerInput {
856
+ /**
857
+ * The question to answer — either its id (`cq_…`) or its question text. Text
858
+ * is matched leniently (lowercased, non-alphanumerics stripped); it must match
859
+ * exactly one question or creation fails.
860
+ */
861
+ questionIdOrText: string;
862
+ /**
863
+ * The answer. How it is interpreted depends on the question's type:
864
+ * - **checkbox** — `yes`/`no`/`true`/`false` (case-insensitive).
865
+ * - **text** — used verbatim.
866
+ * - **select-one / location** — an option id (`cqao_…`) or the option's label,
867
+ * matched leniently the same way as `questionIdOrText`.
868
+ *
869
+ * For **location** questions you only select the option — Peek holds each
870
+ * option's configured lat/long and applies it, so no coordinates are (or can
871
+ * be) supplied here.
872
+ *
873
+ * Per-guest questions are **not yet supported**: answering one fails creation.
874
+ */
875
+ value: string;
876
+ }
776
877
  /**
777
878
  * Input for creating a booking. IDs must already be resolved — the package does
778
879
  * not do free-text product/ticket/time matching (that stays in the caller).
@@ -788,6 +889,21 @@ interface CreateBookingInput {
788
889
  guest: CreateBookingGuest;
789
890
  /** Operator notes to attach. */
790
891
  operatorNotes?: string;
892
+ /**
893
+ * Answers to the activity's custom questions. When non-empty, the activity's
894
+ * custom questions are fetched and each answer is validated/resolved before
895
+ * the booking is created — an unmatched question, ambiguous text/option, or
896
+ * invalid checkbox value fails creation before any quote is made.
897
+ */
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;
791
907
  /** Suppress the customer confirmation email. Default: false. */
792
908
  skipCustomerEmail?: boolean;
793
909
  /** Clone the quote from an existing order. */
@@ -1160,10 +1276,18 @@ declare class BookingService {
1160
1276
  *
1161
1277
  * @throws {Error} when `activityId`, `availabilityTimeId`, a ticket
1162
1278
  * `resourceOptionId`/positive `quantity`, or the guest `name` is missing; when
1163
- * `markAsPaid` is set without an `idempotencyKey`; or when the quote/order
1164
- * 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.
1165
1282
  */
1166
1283
  create(input: CreateBookingInput): Promise<CreatedBooking>;
1284
+ /**
1285
+ * Resolves the input's custom-question answers into the quote's
1286
+ * `questionAnswers` payload (each tagged with a fresh `refid`), or `null` when
1287
+ * none were supplied. Fetches the activity's custom questions to validate
1288
+ * against; throws on the first unmatched/ambiguous/invalid answer.
1289
+ */
1290
+ private buildQuestionAnswers;
1167
1291
  private markCreatedBookingPaid;
1168
1292
  /** Finds the parent item id of an add-on by matching its option id. */
1169
1293
  private resolveParentItemId;
@@ -2062,6 +2186,8 @@ declare class PeekAccessService {
2062
2186
  getAllRentals(): Promise<Product[]>;
2063
2187
  /** All add-on products. Delegates to {@link ProductService.getAllAddons}. */
2064
2188
  getAllAddons(): Promise<Product[]>;
2189
+ /** An activity's custom questions. Delegates to {@link ProductService.getCustomQuestions}. */
2190
+ getCustomQuestions(productId: string): Promise<CustomQuestion[]>;
2065
2191
  /** All active account users. Delegates to {@link AccountUserService.getAll}. */
2066
2192
  getAllAccountUsers(): Promise<AccountUser[]>;
2067
2193
  /** Account user by id, or null. Delegates to {@link AccountUserService.getById}. */
@@ -2836,4 +2962,4 @@ declare class AcmeApiError extends Error {
2836
2962
  constructor(statusCode: number, body: unknown, message?: string);
2837
2963
  }
2838
2964
 
2839
- 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 CustomQuestionAnswer, 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 };
2965
+ 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 };
package/dist/index.js CHANGED
@@ -1039,6 +1039,107 @@ function toBookingAddon(item) {
1039
1039
  return { addonId, addonName, total: item.total, addonOptions };
1040
1040
  }
1041
1041
 
1042
+ // src/internal/peek/bookings/custom-question-answer.ts
1043
+ var QUESTION_TYPE_CHECK_BOX = "CHECK_BOX";
1044
+ var QUESTION_TYPE_TEXT = "TEXT";
1045
+ var QUESTION_TYPE_SELECT_ONE = "SELECT_ONE";
1046
+ var QUESTION_TYPE_LOCATION = "LOCATION";
1047
+ var QUESTION_ID_REGEX = /^cq_[a-z0-9]+$/;
1048
+ var OPTION_ID_REGEX = /^cqao_[a-z0-9]+$/;
1049
+ var CHECKBOX_TRUE = /* @__PURE__ */ new Set(["yes", "true"]);
1050
+ var CHECKBOX_VALID = /* @__PURE__ */ new Set(["yes", "no", "true", "false"]);
1051
+ var CHECKBOX_TEXT_TRUE = "Yes";
1052
+ var CHECKBOX_TEXT_FALSE = "No";
1053
+ var errUnknownQuestionId = (v) => `No custom question matches id "${v}" for this activity`;
1054
+ var errNoTextMatch = (v) => `No custom question matches text "${v}" for this activity`;
1055
+ var errAmbiguousText = (v) => `Custom question text "${v}" matches more than one question`;
1056
+ var errPerGuestUnsupported = (id) => `Per-guest custom question "${id}" is not yet supported`;
1057
+ var errUnsupportedType = (type, id) => `Custom question "${id}" has unsupported type "${type}"`;
1058
+ var errInvalidCheckbox = (v, id) => `Checkbox answer for question "${id}" must be yes/no/true/false, got "${v}"`;
1059
+ var errUnknownOptionId = (v, id) => `No option matches id "${v}" for question "${id}"`;
1060
+ var errNoOptionMatch = (v, id) => `No option matches "${v}" for question "${id}"`;
1061
+ var errAmbiguousOption = (v, id) => `Option "${v}" matches more than one option for question "${id}"`;
1062
+ var errMissingRequired = (text, id) => `Custom question "${text}" (${id}) is required but was not answered`;
1063
+ function normalize(value) {
1064
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
1065
+ }
1066
+ function resolveQuestion(idOrText, questions) {
1067
+ if (QUESTION_ID_REGEX.test(idOrText)) {
1068
+ const byId = questions.find((question) => question.id === idOrText);
1069
+ if (!byId) throw new Error(errUnknownQuestionId(idOrText));
1070
+ return byId;
1071
+ }
1072
+ const target = normalize(idOrText);
1073
+ const matches = questions.filter(
1074
+ (question) => normalize(question.questionText) === target
1075
+ );
1076
+ if (matches.length === 0) throw new Error(errNoTextMatch(idOrText));
1077
+ if (matches.length > 1) throw new Error(errAmbiguousText(idOrText));
1078
+ return matches[0];
1079
+ }
1080
+ function resolveOption(value, question) {
1081
+ if (OPTION_ID_REGEX.test(value)) {
1082
+ const byId = question.options.find((option) => option.id === value);
1083
+ if (!byId) throw new Error(errUnknownOptionId(value, question.id));
1084
+ return byId;
1085
+ }
1086
+ const target = normalize(value);
1087
+ const matches = question.options.filter(
1088
+ (option) => normalize(option.value) === target
1089
+ );
1090
+ if (matches.length === 0) throw new Error(errNoOptionMatch(value, question.id));
1091
+ if (matches.length > 1) throw new Error(errAmbiguousOption(value, question.id));
1092
+ return matches[0];
1093
+ }
1094
+ function resolveOne(answer, questions) {
1095
+ const question = resolveQuestion(answer.questionIdOrText, questions);
1096
+ if (question.perGuest) {
1097
+ throw new Error(errPerGuestUnsupported(question.id));
1098
+ }
1099
+ switch (question.questionType) {
1100
+ case QUESTION_TYPE_CHECK_BOX: {
1101
+ const normalized = answer.value.trim().toLowerCase();
1102
+ if (!CHECKBOX_VALID.has(normalized)) {
1103
+ throw new Error(errInvalidCheckbox(answer.value, question.id));
1104
+ }
1105
+ const isChecked = CHECKBOX_TRUE.has(normalized);
1106
+ return {
1107
+ questionId: question.id,
1108
+ isChecked,
1109
+ questionAnswerText: isChecked ? CHECKBOX_TEXT_TRUE : CHECKBOX_TEXT_FALSE
1110
+ };
1111
+ }
1112
+ case QUESTION_TYPE_TEXT:
1113
+ return { questionId: question.id, questionAnswerText: answer.value };
1114
+ case QUESTION_TYPE_SELECT_ONE:
1115
+ case QUESTION_TYPE_LOCATION: {
1116
+ const option = resolveOption(answer.value, question);
1117
+ return {
1118
+ questionId: question.id,
1119
+ questionAnswerOptionId: option.id,
1120
+ questionAnswerText: option.value
1121
+ };
1122
+ }
1123
+ default:
1124
+ throw new Error(errUnsupportedType(question.questionType, question.id));
1125
+ }
1126
+ }
1127
+ function resolveCustomQuestionAnswers(answers, questions, options) {
1128
+ const resolved = answers.map((answer) => resolveOne(answer, questions));
1129
+ if (options?.requireRequired) {
1130
+ assertRequiredAnswered(resolved, questions);
1131
+ }
1132
+ return resolved;
1133
+ }
1134
+ function assertRequiredAnswered(resolved, questions) {
1135
+ const answered = new Set(resolved.map((answer) => answer.questionId));
1136
+ for (const question of questions) {
1137
+ if (question.isRequired && !question.perGuest && !answered.has(question.id)) {
1138
+ throw new Error(errMissingRequired(question.questionText, question.id));
1139
+ }
1140
+ }
1141
+ }
1142
+
1042
1143
  // src/internal/peek/bookings/booking-service.ts
1043
1144
  var DEFAULT_PAGE_SIZE2 = 50;
1044
1145
  var DEFAULT_CANCEL_NOTE = "Canceled";
@@ -1553,11 +1654,13 @@ ${note}`;
1553
1654
  *
1554
1655
  * @throws {Error} when `activityId`, `availabilityTimeId`, a ticket
1555
1656
  * `resourceOptionId`/positive `quantity`, or the guest `name` is missing; when
1556
- * `markAsPaid` is set without an `idempotencyKey`; or when the quote/order
1557
- * mutations fail.
1657
+ * `markAsPaid` is set without an `idempotencyKey`; when
1658
+ * `requireRequiredQuestions` is set and a required custom question is left
1659
+ * unanswered; or when the quote/order mutations fail.
1558
1660
  */
1559
1661
  async create(input) {
1560
1662
  validateCreateInput(input);
1663
+ const questionAnswers = await this.buildQuestionAnswers(input);
1561
1664
  const tickets = input.tickets.flatMap(
1562
1665
  (ticket) => Array.from({ length: ticket.quantity }, () => ({
1563
1666
  resourceOptionId: ticket.resourceOptionId,
@@ -1584,6 +1687,9 @@ ${note}`;
1584
1687
  if (input.operatorNotes) {
1585
1688
  bookingQuote.operatorNotes = input.operatorNotes;
1586
1689
  }
1690
+ if (questionAnswers) {
1691
+ bookingQuote.questionAnswers = questionAnswers;
1692
+ }
1587
1693
  const quoteInput = { bookingQuotes: [bookingQuote] };
1588
1694
  if (input.parentOrderId) {
1589
1695
  quoteInput.source = { clonedFromId: input.parentOrderId };
@@ -1623,6 +1729,32 @@ ${note}`;
1623
1729
  }
1624
1730
  return created;
1625
1731
  }
1732
+ /**
1733
+ * Resolves the input's custom-question answers into the quote's
1734
+ * `questionAnswers` payload (each tagged with a fresh `refid`), or `null` when
1735
+ * none were supplied. Fetches the activity's custom questions to validate
1736
+ * against; throws on the first unmatched/ambiguous/invalid answer.
1737
+ */
1738
+ async buildQuestionAnswers(input) {
1739
+ const answers = input.customQuestionAnswers ?? [];
1740
+ const requireRequired = input.requireRequiredQuestions ?? false;
1741
+ if (answers.length === 0 && !requireRequired) {
1742
+ return null;
1743
+ }
1744
+ const questions = await this.deps.productService.getCustomQuestions(
1745
+ input.activityId
1746
+ );
1747
+ const resolved = resolveCustomQuestionAnswers(answers, questions, {
1748
+ requireRequired
1749
+ });
1750
+ if (resolved.length === 0) {
1751
+ return null;
1752
+ }
1753
+ return resolved.map((answer) => ({
1754
+ ...answer,
1755
+ refid: randomUUID()
1756
+ }));
1757
+ }
1626
1758
  async markCreatedBookingPaid(booking, input) {
1627
1759
  const parsedPartial = input.markAsPaidAmount ? parseFloat(input.markAsPaidAmount) : NaN;
1628
1760
  const amount = !Number.isNaN(parsedPartial) && parsedPartial > 0 ? parsedPartial.toFixed(2) : booking.balanceAmount;
@@ -2935,6 +3067,33 @@ function fromItemOptionNodes(nodes) {
2935
3067
  return Array.from(grouped.values());
2936
3068
  }
2937
3069
 
3070
+ // src/internal/peek/products/custom-question-converter.ts
3071
+ function fromQuestionConfigurations(configs) {
3072
+ return configs.map(fromQuestionConfiguration);
3073
+ }
3074
+ function fromQuestionConfiguration(config) {
3075
+ const question = config.question;
3076
+ return {
3077
+ id: question.id,
3078
+ order: config.order ?? 0,
3079
+ isRequired: config.isRequired ?? false,
3080
+ questionText: question.text,
3081
+ hintText: question.hint ?? null,
3082
+ questionType: question.questionType,
3083
+ internalLabel: question.internalLabel ?? null,
3084
+ perGuest: question.perGuest ?? false,
3085
+ defaultValue: question.answerDefaultValue ?? null,
3086
+ options: (question.options ?? []).map(fromQuestionOption)
3087
+ };
3088
+ }
3089
+ function fromQuestionOption(option) {
3090
+ return {
3091
+ id: option.id,
3092
+ order: option.order,
3093
+ value: option.text
3094
+ };
3095
+ }
3096
+
2938
3097
  // src/internal/peek/products/product-queries.ts
2939
3098
  var PRODUCTS_QUERY = `
2940
3099
  query Sales {
@@ -2993,7 +3152,38 @@ var ITEM_OPTIONS_QUERY = `
2993
3152
  }
2994
3153
  `;
2995
3154
 
3155
+ // src/internal/peek/products/custom-question-queries.ts
3156
+ var CUSTOM_QUESTIONS_QUERY = `
3157
+ query Sales($id: ID!) {
3158
+ activity(id: $id) {
3159
+ id
3160
+ name
3161
+ questionActivityConfigurations {
3162
+ isRequired
3163
+ order
3164
+ question {
3165
+ id
3166
+ questionType
3167
+ text
3168
+ answerDefaultValue
3169
+ hint
3170
+ internalLabel
3171
+ perGuest
3172
+ ... on ChoiceQuestion {
3173
+ options {
3174
+ id
3175
+ text
3176
+ order
3177
+ }
3178
+ }
3179
+ }
3180
+ }
3181
+ }
3182
+ }
3183
+ `;
3184
+
2996
3185
  // src/internal/peek/products/product-service.ts
3186
+ var ERROR_PRODUCT_ID_REQUIRED2 = "A non-empty product id is required.";
2997
3187
  var DEFAULT_ITEM_OPTIONS_PAGE_SIZE = 50;
2998
3188
  var ProductService = class {
2999
3189
  constructor(client, options = {}) {
@@ -3038,6 +3228,32 @@ var ProductService = class {
3038
3228
  const nodes = await this.fetchAllItemOptionNodes();
3039
3229
  return fromItemOptionNodes(nodes);
3040
3230
  }
3231
+ /**
3232
+ * Returns every custom question configured on a single activity, in the order
3233
+ * Peek reports them. Choice-style questions (`SELECT_ONE`, `LOCATION`, …)
3234
+ * carry their selectable {@link CustomQuestion.options}; free-text/checkbox
3235
+ * questions return an empty option list.
3236
+ *
3237
+ * These are question **definitions**, not customer answers — no PII is
3238
+ * involved, so the result is unaffected by `fullCustomerAccess`.
3239
+ *
3240
+ * @param productId The activity's id.
3241
+ * @returns The activity's custom questions, or an empty list when the activity
3242
+ * has none or is not found.
3243
+ * @throws Error when `productId` is missing or blank.
3244
+ */
3245
+ async getCustomQuestions(productId) {
3246
+ if (!productId?.trim()) {
3247
+ throw new Error(ERROR_PRODUCT_ID_REQUIRED2);
3248
+ }
3249
+ const body = await this.client.request(
3250
+ SALES_ENDPOINT,
3251
+ CUSTOM_QUESTIONS_QUERY,
3252
+ { id: productId }
3253
+ );
3254
+ const configs = body.data?.activity?.questionActivityConfigurations ?? [];
3255
+ return fromQuestionConfigurations(configs);
3256
+ }
3041
3257
  async fetchActivities() {
3042
3258
  const body = await this.client.request(
3043
3259
  SALES_ENDPOINT,
@@ -3751,6 +3967,10 @@ var PeekAccessService = class {
3751
3967
  getAllAddons() {
3752
3968
  return this.getProductService().getAllAddons();
3753
3969
  }
3970
+ /** An activity's custom questions. Delegates to {@link ProductService.getCustomQuestions}. */
3971
+ getCustomQuestions(productId) {
3972
+ return this.getProductService().getCustomQuestions(productId);
3973
+ }
3754
3974
  // ─── Account-user short-forms ─────────────────────────────────────────────
3755
3975
  /** All active account users. Delegates to {@link AccountUserService.getAll}. */
3756
3976
  getAllAccountUsers() {