Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/internal/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ point (`PeekAccessService`) that hands out per-resource services returning clean
plain-object data models.

Consumers never see GraphQL. They construct one access service per install and
call typed methods like `peek.getProductService().getAllProducts()`.
call typed methods like `peek.getProductService().getAllProducts()` or directly
via the top-level short-forms like `peek.getAllProducts()` and
`peek.getAllActivities()`.

## Layers

Expand Down Expand Up @@ -44,6 +46,8 @@ call typed methods like `peek.getProductService().getAllProducts()`.
- Constructs a single shared `TokenManager` and `GraphQLClient`.
- Exposes one `get<Resource>Service()` accessor per resource. Each is **lazily
created and memoized** — repeated calls return the same instance.
- Exposes **top-level short-form methods** that delegate directly to the
underlying service, e.g. `peek.getAllProducts()` → `peek.getProductService().getAllProducts()`. Every public service method has a named proxy on `PeekAccessService`; the names are prefixed with the resource noun where disambiguation is needed (e.g. `getBookingById`, `getTimeslotById`).
- Exposes `verifyPeekAuthToken(token)` to verify HMAC-signed JWTs issued by
the Peek app registry (`iss: "app_registry_v2"`), returning
a fully typed `PeekAuthTokenClaims` (including the nested `PeekAuthTokenUser`
Expand Down Expand Up @@ -108,6 +112,10 @@ Resources: `products`, `account-users`, `resource-pools`, `timeslots`,
`resellers`, `promo-codes`, `daily-notes`, `availability`, `memberships`,
`bookings`, `reviews`. Clean data shapes live in `src/models/`.

`ProductService` exposes three top-level product filters in addition to the combined `getAllProducts()`:
- `getAllActivities()` — fetches only the `activities` connection (one request, no add-on pagination).
- `getAllAddons()` — fetches only the `itemOptions` connection, paginated.

`waivers` is a **webhook-only resource**: it has no GraphQL reads (so no
queries/service/converter triad), just `src/internal/waivers/waiver-webhook.ts`
and the `src/models/waiver.ts` model. See the webhook notes below.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@peektravel/app-utilities",
"version": "0.2.3",
"version": "0.2.4",
"description": "GraphQL JS mapping utilities extracted from the Peek Pro Autopilot connector.",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export { parseWaiverWebhook } from "./internal/waivers/waiver-webhook.js";

export { ReviewService } from "./internal/reviews/review-service.js";

export { ADD_ON_PRODUCT_TYPE } from "./models/product.js";
export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, RENTAL_PRODUCT_TYPE } from "./models/product.js";
export type { Product, ProductTicket } from "./models/product.js";
export type { AccountUser, AssignedActivity } from "./models/account-user.js";
export type {
Expand Down
24 changes: 23 additions & 1 deletion src/internal/products/product-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
*/
import { SALES_ENDPOINT } from "../gateway-endpoints.js";
import type { GraphQLBody, GraphQLClient } from "../graphql-client.js";
import type { Product } from "../../models/product.js";
import {
ACTIVITY_PRODUCT_TYPE,
RENTAL_PRODUCT_TYPE,
type Product,
} from "../../models/product.js";
import { fromActivities, fromItemOptionNodes } from "./product-converter.js";
import {
ITEM_OPTIONS_QUERY,
Expand Down Expand Up @@ -60,6 +64,24 @@ export class ProductService {
return [...fromActivities(activities), ...fromItemOptionNodes(itemOptionNodes)];
}

/** Returns products with type {@link ACTIVITY_PRODUCT_TYPE}. */
async getAllActivities(): Promise<Product[]> {
const activities = await this.fetchActivities();
return fromActivities(activities).filter((p) => p.type === ACTIVITY_PRODUCT_TYPE);
}

/** Returns products with type {@link RENTAL_PRODUCT_TYPE}. */
async getAllRentals(): Promise<Product[]> {
const activities = await this.fetchActivities();
return fromActivities(activities).filter((p) => p.type === RENTAL_PRODUCT_TYPE);
}

/** Returns only add-on products. */
async getAllAddons(): Promise<Product[]> {
const nodes = await this.fetchAllItemOptionNodes();
return fromItemOptionNodes(nodes);
}

private async fetchActivities(): Promise<ProductsResponse["activities"]> {
const body: GraphQLBody<ProductsResponse> =
await this.client.request<ProductsResponse>(
Expand Down
6 changes: 6 additions & 0 deletions src/models/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export interface ProductTicket {
name: string;
}

/** {@link Product.type} for standard bookable activities. */
export const ACTIVITY_PRODUCT_TYPE = "ACTIVITY";

/** {@link Product.type} for rental products. */
export const RENTAL_PRODUCT_TYPE = "RENTAL";

/**
* The {@link Product.type} value assigned to add-on products.
*
Expand Down
179 changes: 179 additions & 0 deletions src/peek-access-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ import { V2_EXTENDABLE_SLUG } from "./internal/gateway-endpoints.js";
import { TokenManager } from "./internal/token-manager.js";
import { noopLogger, type Logger } from "./logger.js";
import type { PeekAuthTokenClaims } from "./models/auth-token.js";
import type { AvailabilityTimesQuery } from "./models/availability-time.js";
import type {
BookingReadOptions,
BookingTimeRangeSearch,
CreateBookingInput,
NoteMode,
} from "./models/booking.js";
import type { MakePaymentInput, RefundInput } from "./models/booking-payment.js";
import type { MembershipPurchaseInput } from "./models/membership.js";
import type { CreatePromoCodeInput } from "./models/promo-code.js";
import type { ResourcePoolMode } from "./models/resource-pool.js";
import type { GuideAssignment, TimeslotFilter } from "./models/timeslot.js";
import type { AddAddonInput } from "./internal/bookings/booking-service.js";

/** Default backoffice GraphQL gateway base URL (v1). */
const DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
Expand Down Expand Up @@ -331,6 +344,172 @@ export class PeekAccessService {
}
return this.reviewService;
}

// ─── Product short-forms ─────────────────────────────────────────────────

/** All products (activities + add-ons). Delegates to {@link ProductService.getAllProducts}. */
getAllProducts() { return this.getProductService().getAllProducts(); }

/** All activity products (excludes add-ons). Delegates to {@link ProductService.getAllActivities}. */
getAllActivities() { return this.getProductService().getAllActivities(); }

/** All rental products. Delegates to {@link ProductService.getAllRentals}. */
getAllRentals() { return this.getProductService().getAllRentals(); }

/** All add-on products. Delegates to {@link ProductService.getAllAddons}. */
getAllAddons() { return this.getProductService().getAllAddons(); }

// ─── Account-user short-forms ─────────────────────────────────────────────

/** All active account users. Delegates to {@link AccountUserService.getAll}. */
getAllAccountUsers() { return this.getAccountUserService().getAll(); }

/** Account user by id, or null. Delegates to {@link AccountUserService.getById}. */
getAccountUserById(userId: string) { return this.getAccountUserService().getById(userId); }

// ─── Resource-pool short-forms ────────────────────────────────────────────

/** All resource pools. Delegates to {@link ResourcePoolService.getAll}. */
getAllResourcePools(mode?: ResourcePoolMode) { return this.getResourcePoolService().getAll(mode); }

// ─── Timeslot short-forms ─────────────────────────────────────────────────

/** Timeslots for an activity on a given date. Delegates to {@link TimeslotService.getForDay}. */
getTimeslotsForDay(productId: string, date: string, filter?: TimeslotFilter) {
return this.getTimeslotService().getForDay(productId, date, filter);
}

/** Single timeslot by id. Delegates to {@link TimeslotService.getById}. */
getTimeslotById(timeslotId: string) { return this.getTimeslotService().getById(timeslotId); }

/** Set timeslot status. Delegates to {@link TimeslotService.setAvailability}. */
setTimeslotAvailability(timeslotId: string, status: string) {
return this.getTimeslotService().setAvailability(timeslotId, status);
}

/** Set timeslot manifest notes. Delegates to {@link TimeslotService.setNotes}. */
setTimeslotNotes(timeslotId: string, manifestNotes: string) {
return this.getTimeslotService().setNotes(timeslotId, manifestNotes);
}

/** Assign or unassign guides on timeslots. Delegates to {@link TimeslotService.assignGuide}. */
assignTimeslotGuide(assignment: GuideAssignment) {
return this.getTimeslotService().assignGuide(assignment);
}

// ─── Reseller short-forms ─────────────────────────────────────────────────

/** All reseller channels. Delegates to {@link ResellerService.getAllChannels}. */
getAllChannels(agentsPerChannel?: number) {
return this.getResellerService().getAllChannels(agentsPerChannel);
}

// ─── Promo-code short-forms ───────────────────────────────────────────────

/** All promo codes. Delegates to {@link PromoCodeService.getAll}. */
getAllPromoCodes() { return this.getPromoCodeService().getAll(); }

/** Create a promo code. Delegates to {@link PromoCodeService.create}. */
createPromoCode(input: CreatePromoCodeInput) { return this.getPromoCodeService().create(input); }

// ─── Daily-note short-forms ───────────────────────────────────────────────

/** Today's daily note. Delegates to {@link DailyNoteService.getToday}. */
getDailyNoteToday() { return this.getDailyNoteService().getToday(); }

/** Update today's daily note. Delegates to {@link DailyNoteService.update}. */
updateDailyNote(note: string) { return this.getDailyNoteService().update(note); }

// ─── Availability short-forms ─────────────────────────────────────────────

/** Availability times for an activity. Delegates to {@link AvailabilityService.getAvailabilityTimes}. */
getAvailabilityTimes(query: AvailabilityTimesQuery) {
return this.getAvailabilityService().getAvailabilityTimes(query);
}

// ─── Membership short-forms ───────────────────────────────────────────────

/** All memberships. Delegates to {@link MembershipService.getAll}. */
getAllMemberships() { return this.getMembershipService().getAll(); }

/** Purchase a membership. Delegates to {@link MembershipService.purchase}. */
purchaseMembership(input: MembershipPurchaseInput) {
return this.getMembershipService().purchase(input);
}

// ─── Booking short-forms ──────────────────────────────────────────────────

/** Booking by id. Delegates to {@link BookingService.getById}. */
getBookingById(bookingId: string, options?: BookingReadOptions) {
return this.getBookingService().getById(bookingId, options);
}

/** Bookings by time range. Delegates to {@link BookingService.searchByTimeRange}. */
searchBookingsByTimeRange(input: BookingTimeRangeSearch) {
return this.getBookingService().searchByTimeRange(input);
}

/** Bookings on a timeslot. Delegates to {@link BookingService.searchByTimeslot}. */
searchBookingsByTimeslot(timeslotId: string, options?: BookingReadOptions) {
return this.getBookingService().searchByTimeslot(timeslotId, options);
}

/** Guests on a booking. Delegates to {@link BookingService.getGuests}. */
getBookingGuests(bookingId: string) { return this.getBookingService().getGuests(bookingId); }

/** Payments on file for a booking. Delegates to {@link BookingService.getPaymentsOnFile}. */
getBookingPaymentsOnFile(bookingId: string) {
return this.getBookingService().getPaymentsOnFile(bookingId);
}

/** Append or overwrite operator notes. Delegates to {@link BookingService.appendNote}. */
appendBookingNote(bookingId: string, note: string, mode?: NoteMode) {
return this.getBookingService().appendNote(bookingId, note, mode);
}

/** Set booking check-in status. Delegates to {@link BookingService.setCheckinStatus}. */
setBookingCheckinStatus(bookingId: string, checkedIn: boolean) {
return this.getBookingService().setCheckinStatus(bookingId, checkedIn);
}

/** Cancel a booking. Delegates to {@link BookingService.cancel}. */
cancelBooking(bookingId: string, notes?: string) {
return this.getBookingService().cancel(bookingId, notes);
}

/** Charge a booking. Delegates to {@link BookingService.makePayment}. */
makeBookingPayment(input: MakePaymentInput) { return this.getBookingService().makePayment(input); }

/** Refund a booking payment. Delegates to {@link BookingService.refund}. */
refundBooking(input: RefundInput) { return this.getBookingService().refund(input); }

/** Create an invoice link. Delegates to {@link BookingService.createInvoiceLink}. */
createBookingInvoiceLink(bookingId: string) {
return this.getBookingService().createInvoiceLink(bookingId);
}

/** List add-ons on a booking. Delegates to {@link BookingService.listAddons}. */
listBookingAddons(bookingId: string) { return this.getBookingService().listAddons(bookingId); }

/** Add an add-on to a booking. Delegates to {@link BookingService.addAddon}. */
addBookingAddon(bookingId: string, input: AddAddonInput) {
return this.getBookingService().addAddon(bookingId, input);
}

/** Remove an add-on from a booking. Delegates to {@link BookingService.removeAddon}. */
removeBookingAddon(bookingId: string, input: AddAddonInput) {
return this.getBookingService().removeAddon(bookingId, input);
}

/** Create a booking. Delegates to {@link BookingService.create}. */
createBooking(input: CreateBookingInput) { return this.getBookingService().create(input); }

// ─── Review short-forms ───────────────────────────────────────────────────

/** Reviews for an activity. Delegates to {@link ReviewService.getReviews}. */
getReviews(productId: string, reviewCount?: number, reviewOffset?: number) {
return this.getReviewService().getReviews(productId, reviewCount, reviewOffset);
}
}

function requireNonEmpty(value: string, name: string): void {
Expand Down
Loading
Loading