@surfiran/public-api-client 0.1.0 → 0.11.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
@@ -5,9 +5,14 @@ TypeScript types and a fetch-based client for SURFIRAN CRM Public API V1 (`/api/
5
5
  ## Contract Source
6
6
 
7
7
  ```text
8
- server/openapi/public-api-v1.yaml (v0.6.0)
8
+ contract/public-api-v1.yaml (v0.9.0 package snapshot)
9
9
  ```
10
10
 
11
+ The backend API remains the canonical contract owner at
12
+ `server/openapi/public-api-v1.yaml`. This package contains an explicit,
13
+ checked-in snapshot with provenance linking it to the canonical source commit
14
+ and checksum. The package never reads backend source during generation.
15
+
11
16
  ## Current Runtime Scope
12
17
 
13
18
  Active read endpoints:
@@ -117,9 +122,74 @@ This package must never:
117
122
 
118
123
  ## Next.js Usage
119
124
 
120
- This client is already consumed from Next.js in production: `apps/web/lib/public*.ts` (tours, destinations, sitemap, search suggestions, site navigation) and multiple `apps/web/components/*` build on `createPublicApiClient` via the `@surfiran/public-api-client` path alias. There is no remaining gate blocking this consumption.
125
+ The public Next.js website consumes this client as the exact published dependency `@surfiran/public-api-client@0.1.0`. Its TypeScript, Vitest, and build configuration must resolve the package from `node_modules`, never from the monorepo `packages/` source tree.
126
+
127
+ ## Code Generation
128
+
129
+ From this package directory, install dependencies and generate the committed
130
+ artifact from the checked-in contract snapshot:
131
+
132
+ ```text
133
+ npm ci --include=dev
134
+ npm run generate
135
+ npm run verify:contract
136
+ npm run verify:generated
137
+ ```
138
+
139
+ Generation has stable ordering, formatting, LF newlines, no random identifiers,
140
+ no machine-specific paths, and no uncontrolled timestamps. To synchronize the
141
+ snapshot from the canonical backend source during monorepo development, use the
142
+ explicit root command with a recorded source commit:
143
+
144
+ ```text
145
+ npm run sync:public-api-client-contract -- --source-commit <canonical-source-commit>
146
+ ```
147
+
148
+ The synchronization command is not required by a package-only clone and must
149
+ not be used to create a competing contract.
150
+
151
+ ## Publication and Rollback
152
+
153
+ Publication preflight is validation-only and must run from the package root:
154
+
155
+ ```text
156
+ npm run verify:contract
157
+ npm run generate
158
+ npm run verify:generated
159
+ npm run verify:package
160
+ npm run typecheck
161
+ npm test
162
+ npm run build
163
+ npm pack --dry-run --json
164
+ npm run verify:pack
165
+ npm run verify:publication-preflight -- <previous-exact-version>
166
+ ```
167
+
168
+ Create and verify release provenance with an explicit UTC timestamp, source
169
+ commit, rollback version, and packed tarball:
170
+
171
+ ```text
172
+ npm pack --pack-destination <evidence-directory>
173
+ npm run create:provenance -- \
174
+ --source-commit <full-source-commit> \
175
+ --generated-at <UTC-ISO-8601-timestamp> \
176
+ --tarball <packed-tarball> \
177
+ --rollback-version <previous-exact-version> \
178
+ --output <evidence-directory>/public-api-client-provenance.json
179
+ npm run verify:provenance -- \
180
+ --file <evidence-directory>/public-api-client-provenance.json \
181
+ --tarball <packed-tarball>
182
+ ```
183
+
184
+ The exact package allowlist is enforced before packing. Provenance links the
185
+ package name and version, canonical source commit, canonical and snapshot
186
+ checksums, generated artifact checksum, allowlist checksum, generation tool,
187
+ explicit generation timestamp policy, rollback version, and tarball checksum.
188
+ The package is immutable on npm. Consumers roll back by pinning the previous
189
+ verified exact version; an existing version is never overwritten.
121
190
 
122
191
  ## Next Steps
123
192
 
124
- - Keep this package aligned with `server/openapi/public-api-v1.yaml`
125
- - Wire code generation only after an owner-approved codegen plan
193
+ - Keep `server/openapi/public-api-v1.yaml` as the API-owned contract source
194
+ - Synchronize the explicit snapshot only from the API-owned canonical source
195
+ - Run package-local generation, validation, provenance, and clean-clone checks
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GetPublicSearchSuggestionsParams, GetPublicSitemapParams, GetPublicTravelKnowledgeParams, ListPublicDeparturesParams, ListPublicDestinationsParams, ListPublicTourAccommodationsParams, ListPublicTourAvailabilityParams, ListPublicTourCategoriesParams, ListPublicTourExperiencesParams, ListPublicTravellerStoriesParams, ListPublicTourQuestionsParams, ListPublicTourReviewsParams, ListPublicTourItineraryParams, ListPublicTourRouteParams, ListPublicToursParams, PublicApiResult, PublicAvailabilityListResponse, PublicBookingIntentRequest, PublicBookingIntentResponse, PublicBrochureLeadRequest, PublicBrochureLeadResponse, PublicDepartureListResponse, PublicDestinationDetailResponse, PublicDestinationListResponse, PublicHomepageResponse, PublicInquiryRequest, PublicInquiryResponse, PublicSearchSuggestionsResponse, PublicSitemapResponse, PublicSiteChromeContentResponse, PublicSiteHomeContentResponse, PublicSiteNavigationResponse, PublicSiteTrustPageContentResponse, PublicTourAccommodationListResponse, PublicTourCategoryListResponse, PublicTourDetailResponse, PublicTourExperienceListResponse, PublicTravellerStoryListResponse, PublicTourListResponse, PublicTourQuestionListResponse, PublicTourReviewsSummaryResponse, PublicTourReviewListResponse, PublicTourItineraryResponse, PublicTourRouteResponse, PublicTravelKnowledgeResponse, PublicTravelStatusResponse } from './types.js';
1
+ import type { GetPublicSearchSuggestionsParams, GetPublicSitemapParams, GetPublicTourPreviewParams, GetPublicTravelKnowledgeParams, ListPublicDeparturesParams, ListPublicDestinationsParams, ListPublicTourAccommodationsParams, ListPublicTourAvailabilityParams, ListPublicTourCategoriesParams, ListPublicTourExperiencesParams, ListPublicTravellerStoriesParams, ListPublicTourQuestionsParams, ListPublicTourReviewsParams, ListPublicTourItineraryParams, ListPublicTourRouteParams, ListPublicToursParams, PublicApiResult, PublicAvailabilityListResponse, PublicBookingIntentRequest, PublicBookingIntentResponse, PublicBrochureLeadRequest, PublicBrochureLeadResponse, PublicDepartureListResponse, PublicDestinationDetailResponse, PublicDestinationListResponse, PublicHomepageResponse, PublicInquiryRequest, PublicInquiryResponse, PublicSearchSuggestionsResponse, PublicSitemapResponse, PublicSiteChromeContentResponse, PublicSiteFeaturesResponse, PublicSiteHomeContentResponse, PublicSiteNavigationResponse, PublicSiteTrustPageContentResponse, PublicTourAccommodationListResponse, PublicTourCategoryListResponse, PublicTourDetailResponse, PublicTourExperienceListResponse, PublicTravellerStoryListResponse, PublicTourListResponse, PublicTourQuestionListResponse, PublicTourReviewsSummaryResponse, PublicTourReviewListResponse, PublicTourItineraryResponse, PublicTourRouteResponse, PublicTravelKnowledgeResponse, PublicTravelStatusResponse } from './types.js';
2
2
  export type PublicApiClientOptions = {
3
3
  baseUrl: string;
4
4
  fetchImpl?: typeof fetch;
@@ -13,6 +13,15 @@ export type PublicApiClientOptions = {
13
13
  export declare const createPublicApiClient: (options: PublicApiClientOptions) => {
14
14
  listPublicTours(params?: ListPublicToursParams): Promise<PublicApiResult<PublicTourListResponse>>;
15
15
  getPublicTourBySlug(slug: string): Promise<PublicApiResult<PublicTourDetailResponse>>;
16
+ /**
17
+ * ADR-0022 Tour Product/Edition MVP candidate preview: renders the same
18
+ * public-safe DTO as getPublicTourBySlug, with the Edition-sourced
19
+ * fields overlaid from an unpublished candidate instead of the currently
20
+ * live manifest. `token` is a short-lived signed token minted by the
21
+ * CRM, required (not optional, unlike this client's other params
22
+ * objects) — there is no unauthenticated variant of this call.
23
+ */
24
+ getPublicTourPreviewBySlug(slug: string, params: GetPublicTourPreviewParams): Promise<PublicApiResult<PublicTourDetailResponse>>;
16
25
  listPublicTourDepartures(slug: string, params?: ListPublicDeparturesParams): Promise<PublicApiResult<PublicDepartureListResponse>>;
17
26
  listPublicTourQuestions(slug: string, params?: ListPublicTourQuestionsParams): Promise<PublicApiResult<PublicTourQuestionListResponse>>;
18
27
  listPublicTourExperiences(slug: string, params?: ListPublicTourExperiencesParams): Promise<PublicApiResult<PublicTourExperienceListResponse>>;
@@ -31,6 +40,7 @@ export declare const createPublicApiClient: (options: PublicApiClientOptions) =>
31
40
  createPublicBrochureLead(body: PublicBrochureLeadRequest, idempotencyKey?: string): Promise<PublicApiResult<PublicBrochureLeadResponse>>;
32
41
  getPublicHomepage(): Promise<PublicApiResult<PublicHomepageResponse>>;
33
42
  getPublicSiteNavigation(): Promise<PublicApiResult<PublicSiteNavigationResponse>>;
43
+ getPublicSiteFeatures(): Promise<PublicApiResult<PublicSiteFeaturesResponse>>;
34
44
  getPublicSitemap(params?: GetPublicSitemapParams): Promise<PublicApiResult<PublicSitemapResponse>>;
35
45
  getPublicSearchSuggestions(params: GetPublicSearchSuggestionsParams): Promise<PublicApiResult<PublicSearchSuggestionsResponse>>;
36
46
  listPublicTourCategories(params?: ListPublicTourCategoriesParams): Promise<PublicApiResult<PublicTourCategoryListResponse>>;
package/dist/client.js CHANGED
@@ -161,6 +161,17 @@ export const createPublicApiClient = (options) => {
161
161
  getPublicTourBySlug(slug) {
162
162
  return requestJson(`${baseUrl}/api/v1/tours/${encodeURIComponent(slug)}`, options);
163
163
  },
164
+ /**
165
+ * ADR-0022 Tour Product/Edition MVP candidate preview: renders the same
166
+ * public-safe DTO as getPublicTourBySlug, with the Edition-sourced
167
+ * fields overlaid from an unpublished candidate instead of the currently
168
+ * live manifest. `token` is a short-lived signed token minted by the
169
+ * CRM, required (not optional, unlike this client's other params
170
+ * objects) — there is no unauthenticated variant of this call.
171
+ */
172
+ getPublicTourPreviewBySlug(slug, params) {
173
+ return requestJson(`${baseUrl}/api/v1/tours/${encodeURIComponent(slug)}/preview?token=${encodeURIComponent(params.token)}`, options);
174
+ },
164
175
  listPublicTourDepartures(slug, params) {
165
176
  return requestJson(`${baseUrl}/api/v1/tours/${encodeURIComponent(slug)}/departures${toPaginationQueryString(params)}`, options);
166
177
  },
@@ -216,6 +227,11 @@ export const createPublicApiClient = (options) => {
216
227
  getPublicSiteNavigation() {
217
228
  return requestJson(`${baseUrl}/api/v1/site-navigation`, options);
218
229
  },
230
+ // Effective public website feature state. One call returns every known
231
+ // feature, so a consumer never needs a per-feature request.
232
+ getPublicSiteFeatures() {
233
+ return requestJson(`${baseUrl}/api/v1/site-features`, options);
234
+ },
219
235
  getPublicSitemap(params) {
220
236
  return requestJson(`${baseUrl}/api/v1/sitemap${toSitemapQueryString(params)}`, options);
221
237
  },
@@ -1,7 +1,7 @@
1
- export type GeneratedPublicApiVersion = '0.8.0';
2
- export type GeneratedPublicApiPath = '/homepage' | '/tours' | '/tours/{slug}' | '/tours/{slug}/availability' | '/tours/{slug}/departures' | '/tours/{slug}/questions' | '/tours/{slug}/experiences' | '/traveller-stories' | '/tours/{slug}/accommodations' | '/tours/{slug}/reviews-summary' | '/tours/{slug}/reviews' | '/tours/{slug}/route' | '/tours/{slug}/itinerary' | '/destinations' | '/destinations/{slug}' | '/sitemap' | '/tour-categories' | '/site-navigation' | '/search-suggestions' | '/travel-status' | '/travel-knowledge' | '/site-content/home' | '/site-content/header' | '/site-content/footer' | '/site-content/about' | '/site-content/contact' | '/site-content/faqs' | '/site-content/privacy' | '/site-content/terms' | '/site-content/responsible-travel' | '/site-content/iran-travel-status' | '/site-content/iran-visa-information' | '/site-content/money-in-iran' | '/site-content/travel-insurance' | '/site-content/health-safety-support' | '/site-content/booking-cancellation-policy' | '/site-content/cookie-policy' | '/site-content/accessibility-statement' | '/inquiries' | '/booking-intents' | '/brochure-leads' | '/booking-intents/{id}';
3
- export type GeneratedPublicApiOperation = 'GET /homepage' | 'GET /tours' | 'GET /tours/{slug}' | 'GET /tours/{slug}/availability' | 'GET /tours/{slug}/departures' | 'GET /tours/{slug}/questions' | 'GET /tours/{slug}/experiences' | 'GET /traveller-stories' | 'GET /tours/{slug}/accommodations' | 'GET /tours/{slug}/reviews-summary' | 'GET /tours/{slug}/reviews' | 'GET /tours/{slug}/route' | 'GET /tours/{slug}/itinerary' | 'GET /destinations' | 'GET /destinations/{slug}' | 'GET /sitemap' | 'GET /tour-categories' | 'GET /site-navigation' | 'GET /search-suggestions' | 'GET /travel-status' | 'GET /travel-knowledge' | 'GET /site-content/home' | 'GET /site-content/header' | 'GET /site-content/footer' | 'GET /site-content/about' | 'GET /site-content/contact' | 'GET /site-content/faqs' | 'GET /site-content/privacy' | 'GET /site-content/terms' | 'GET /site-content/responsible-travel' | 'GET /site-content/iran-travel-status' | 'GET /site-content/iran-visa-information' | 'GET /site-content/money-in-iran' | 'GET /site-content/travel-insurance' | 'GET /site-content/health-safety-support' | 'GET /site-content/booking-cancellation-policy' | 'GET /site-content/cookie-policy' | 'GET /site-content/accessibility-statement' | 'POST /inquiries' | 'POST /booking-intents' | 'POST /brochure-leads' | 'GET /booking-intents/{id}';
4
- export type GeneratedPublicApiActiveOperation = 'GET /homepage' | 'GET /tours' | 'GET /tours/{slug}' | 'GET /tours/{slug}/availability' | 'GET /tours/{slug}/departures' | 'GET /tours/{slug}/questions' | 'GET /tours/{slug}/experiences' | 'GET /traveller-stories' | 'GET /tours/{slug}/accommodations' | 'GET /tours/{slug}/reviews-summary' | 'GET /tours/{slug}/reviews' | 'GET /tours/{slug}/route' | 'GET /tours/{slug}/itinerary' | 'GET /destinations' | 'GET /destinations/{slug}' | 'GET /sitemap' | 'GET /tour-categories' | 'GET /site-navigation' | 'GET /search-suggestions' | 'GET /travel-status' | 'GET /travel-knowledge' | 'GET /site-content/home' | 'GET /site-content/header' | 'GET /site-content/footer' | 'GET /site-content/about' | 'GET /site-content/contact' | 'GET /site-content/faqs' | 'GET /site-content/privacy' | 'GET /site-content/terms' | 'GET /site-content/responsible-travel' | 'GET /site-content/iran-travel-status' | 'GET /site-content/iran-visa-information' | 'GET /site-content/money-in-iran' | 'GET /site-content/travel-insurance' | 'GET /site-content/health-safety-support' | 'GET /site-content/booking-cancellation-policy' | 'GET /site-content/cookie-policy' | 'GET /site-content/accessibility-statement' | 'POST /inquiries' | 'POST /booking-intents' | 'POST /brochure-leads';
1
+ export type GeneratedPublicApiVersion = '0.9.0';
2
+ export type GeneratedPublicApiPath = '/booking-intents' | '/booking-intents/{id}' | '/brochure-leads' | '/destinations' | '/destinations/{slug}' | '/homepage' | '/inquiries' | '/search-suggestions' | '/site-content/about' | '/site-content/accessibility-statement' | '/site-content/booking-cancellation-policy' | '/site-content/contact' | '/site-content/cookie-policy' | '/site-content/faqs' | '/site-content/footer' | '/site-content/header' | '/site-content/health-safety-support' | '/site-content/home' | '/site-content/iran-travel-status' | '/site-content/iran-visa-information' | '/site-content/money-in-iran' | '/site-content/privacy' | '/site-content/responsible-travel' | '/site-content/terms' | '/site-content/travel-insurance' | '/site-features' | '/site-navigation' | '/sitemap' | '/tour-categories' | '/tours' | '/tours/{slug}' | '/tours/{slug}/accommodations' | '/tours/{slug}/availability' | '/tours/{slug}/departures' | '/tours/{slug}/experiences' | '/tours/{slug}/itinerary' | '/tours/{slug}/preview' | '/tours/{slug}/questions' | '/tours/{slug}/reviews' | '/tours/{slug}/reviews-summary' | '/tours/{slug}/route' | '/travel-knowledge' | '/travel-status' | '/traveller-stories';
3
+ export type GeneratedPublicApiOperation = 'GET /booking-intents/{id}' | 'GET /destinations' | 'GET /destinations/{slug}' | 'GET /homepage' | 'GET /search-suggestions' | 'GET /site-content/about' | 'GET /site-content/accessibility-statement' | 'GET /site-content/booking-cancellation-policy' | 'GET /site-content/contact' | 'GET /site-content/cookie-policy' | 'GET /site-content/faqs' | 'GET /site-content/footer' | 'GET /site-content/header' | 'GET /site-content/health-safety-support' | 'GET /site-content/home' | 'GET /site-content/iran-travel-status' | 'GET /site-content/iran-visa-information' | 'GET /site-content/money-in-iran' | 'GET /site-content/privacy' | 'GET /site-content/responsible-travel' | 'GET /site-content/terms' | 'GET /site-content/travel-insurance' | 'GET /site-features' | 'GET /site-navigation' | 'GET /sitemap' | 'GET /tour-categories' | 'GET /tours' | 'GET /tours/{slug}' | 'GET /tours/{slug}/accommodations' | 'GET /tours/{slug}/availability' | 'GET /tours/{slug}/departures' | 'GET /tours/{slug}/experiences' | 'GET /tours/{slug}/itinerary' | 'GET /tours/{slug}/preview' | 'GET /tours/{slug}/questions' | 'GET /tours/{slug}/reviews' | 'GET /tours/{slug}/reviews-summary' | 'GET /tours/{slug}/route' | 'GET /travel-knowledge' | 'GET /travel-status' | 'GET /traveller-stories' | 'POST /booking-intents' | 'POST /brochure-leads' | 'POST /inquiries';
4
+ export type GeneratedPublicApiActiveOperation = 'GET /destinations' | 'GET /destinations/{slug}' | 'GET /homepage' | 'GET /search-suggestions' | 'GET /site-content/about' | 'GET /site-content/accessibility-statement' | 'GET /site-content/booking-cancellation-policy' | 'GET /site-content/contact' | 'GET /site-content/cookie-policy' | 'GET /site-content/faqs' | 'GET /site-content/footer' | 'GET /site-content/header' | 'GET /site-content/health-safety-support' | 'GET /site-content/home' | 'GET /site-content/iran-travel-status' | 'GET /site-content/iran-visa-information' | 'GET /site-content/money-in-iran' | 'GET /site-content/privacy' | 'GET /site-content/responsible-travel' | 'GET /site-content/terms' | 'GET /site-content/travel-insurance' | 'GET /site-features' | 'GET /site-navigation' | 'GET /sitemap' | 'GET /tour-categories' | 'GET /tours' | 'GET /tours/{slug}' | 'GET /tours/{slug}/accommodations' | 'GET /tours/{slug}/availability' | 'GET /tours/{slug}/departures' | 'GET /tours/{slug}/experiences' | 'GET /tours/{slug}/itinerary' | 'GET /tours/{slug}/preview' | 'GET /tours/{slug}/questions' | 'GET /tours/{slug}/reviews' | 'GET /tours/{slug}/reviews-summary' | 'GET /tours/{slug}/route' | 'GET /travel-knowledge' | 'GET /travel-status' | 'GET /traveller-stories' | 'POST /booking-intents' | 'POST /brochure-leads' | 'POST /inquiries';
5
5
  export type GeneratedPublicApiBlockedOperation = 'GET /booking-intents/{id}';
6
- export type GeneratedPublicApiOperationId = 'getPublicHomepage' | 'listPublicTours' | 'getPublicTourBySlug' | 'listPublicTourAvailability' | 'listPublicTourDepartures' | 'listPublicTourQuestions' | 'listPublicTourExperiences' | 'listPublicTravellerStories' | 'listPublicTourAccommodations' | 'getPublicTourReviewsSummary' | 'listPublicTourReviews' | 'listPublicTourRoute' | 'listPublicTourItinerary' | 'listPublicDestinations' | 'getPublicDestinationBySlug' | 'getPublicSitemap' | 'listPublicTourCategories' | 'getPublicSiteNavigation' | 'getPublicSearchSuggestions' | 'getPublicTravelStatus' | 'listPublicTravelKnowledge' | 'getPublicSiteContentHome' | 'getPublicSiteContentHeader' | 'getPublicSiteContentFooter' | 'getPublicSiteContentAbout' | 'getPublicSiteContentContact' | 'getPublicSiteContentFaqs' | 'getPublicSiteContentPrivacy' | 'getPublicSiteContentTerms' | 'getPublicSiteContentResponsibleTravel' | 'getPublicSiteContentIranTravelStatus' | 'getPublicSiteContentIranVisaInformation' | 'getPublicSiteContentMoneyInIran' | 'getPublicSiteContentTravelInsurance' | 'getPublicSiteContentHealthSafetySupport' | 'getPublicSiteContentBookingCancellationPolicy' | 'getPublicSiteContentCookiePolicy' | 'getPublicSiteContentAccessibilityStatement' | 'createPublicInquiry' | 'createPublicBookingIntent' | 'createPublicBrochureLead' | 'getBookingIntent';
7
- export type GeneratedPublicApiSchemaName = 'PublicHomepageResponse' | 'PublicHomepageStats' | 'PublicHomepageSeo' | 'PublicTourListResponse' | 'PublicTourDetailResponse' | 'PublicAvailabilityListResponse' | 'PublicDepartureListResponse' | 'PublicTourQuestionListResponse' | 'PublicTourExperienceListResponse' | 'PublicTravellerStoryListResponse' | 'PublicTourAccommodationListResponse' | 'PublicTourReviewsSummaryResponse' | 'PublicTourReviewListResponse' | 'PublicTourRouteResponse' | 'PublicDestinationListResponse' | 'PublicDestinationDetailResponse' | 'PublicSitemapResponse' | 'PublicSitemapEntry' | 'PublicTourCategoryListResponse' | 'PublicSearchSuggestionsResponse' | 'PublicTourCategoryListItem' | 'PublicSeo' | 'PublicSearchSuggestion' | 'PublicTourListItem' | 'PublicTourDetail' | 'PublicAvailabilityListItem' | 'PublicDepartureListItem' | 'PublicTourQuestionListItem' | 'PublicTourExperienceFrame' | 'PublicTourExperienceListItem' | 'PublicTravellerStoryListItem' | 'PublicTourAccommodationListItem' | 'PublicTourReviewsAggregate' | 'PublicTourRatingBreakdownItem' | 'PublicTourReviewsSummary' | 'PublicTourReviewListItem' | 'PublicTourRouteStop' | 'PublicTourItineraryDay' | 'PublicTourItineraryResponse' | 'PublicDestinationListItem' | 'PublicDestinationDetail' | 'PublicNavLink' | 'PublicStaticLink' | 'PublicSiteNavigationResponse' | 'PublicSiteRouteKey' | 'PublicSiteCta' | 'PublicSiteHomeSection' | 'PublicSiteHomeContent' | 'PublicSiteHomeContentResponse' | 'PublicSiteNavItem' | 'PublicSiteSocialLink' | 'PublicSiteChromeContent' | 'PublicSiteChromeContentResponse' | 'PublicSiteTrustTitledItem' | 'PublicSiteTrustSection' | 'PublicSiteTrustPageSeo' | 'PublicSiteTrustPageContent' | 'PublicSiteTrustPageContentResponse' | 'CreateInquiryRequest' | 'CreateInquiryResponse' | 'CreateBrochureLeadRequest' | 'CreateBrochureLeadResponse' | 'CreateBookingIntentRequest' | 'PublicBookingIntentStatus' | 'PublicBookingIntentPublicStatus' | 'PublicBookingIntentLinks' | 'CreateBookingIntentSuccessResponse' | 'GetBookingIntentStatusResponse' | 'BookingIntentConflictErrorResponse' | 'BookingIntentResponse' | 'PublicError' | 'PublicApiError' | 'PublicApiNotImplementedError' | 'PublicApiValidationError' | 'PublicApiRateLimitError' | 'PublicApiAbuseBlockedError' | 'PublicApiInternalError' | 'PublicApiNotFoundError' | 'PublicApiTravelStatusUnavailableError' | 'PublicErrorEnvelope' | 'PaginationMeta' | 'PublicTravelStatusState' | 'PublicTravelStatusReviewerLabel' | 'PublicTravelStatusFreshness' | 'PublicOfficialSourceCard' | 'PublicSurfiranOperationalUpdate' | 'PublicTravelStatusCorrectionNotice' | 'PublicTravelStatusHandoff' | 'PublicTravelStatusSection' | 'PublicTravelStatusResponse' | 'PublicTravelKnowledgeItem' | 'PublicTravelKnowledgeResponse';
6
+ export type GeneratedPublicApiOperationId = 'getBookingIntent' | 'listPublicDestinations' | 'getPublicDestinationBySlug' | 'getPublicHomepage' | 'getPublicSearchSuggestions' | 'getPublicSiteContentAbout' | 'getPublicSiteContentAccessibilityStatement' | 'getPublicSiteContentBookingCancellationPolicy' | 'getPublicSiteContentContact' | 'getPublicSiteContentCookiePolicy' | 'getPublicSiteContentFaqs' | 'getPublicSiteContentFooter' | 'getPublicSiteContentHeader' | 'getPublicSiteContentHealthSafetySupport' | 'getPublicSiteContentHome' | 'getPublicSiteContentIranTravelStatus' | 'getPublicSiteContentIranVisaInformation' | 'getPublicSiteContentMoneyInIran' | 'getPublicSiteContentPrivacy' | 'getPublicSiteContentResponsibleTravel' | 'getPublicSiteContentTerms' | 'getPublicSiteContentTravelInsurance' | 'getPublicSiteFeatures' | 'getPublicSiteNavigation' | 'getPublicSitemap' | 'listPublicTourCategories' | 'listPublicTours' | 'getPublicTourBySlug' | 'listPublicTourAccommodations' | 'listPublicTourAvailability' | 'listPublicTourDepartures' | 'listPublicTourExperiences' | 'listPublicTourItinerary' | 'getPublicTourPreviewBySlug' | 'listPublicTourQuestions' | 'listPublicTourReviews' | 'getPublicTourReviewsSummary' | 'listPublicTourRoute' | 'listPublicTravelKnowledge' | 'getPublicTravelStatus' | 'listPublicTravellerStories' | 'createPublicBookingIntent' | 'createPublicBrochureLead' | 'createPublicInquiry';
7
+ export type GeneratedPublicApiSchemaName = 'BookingIntentConflictErrorResponse' | 'BookingIntentResponse' | 'CanonicalIntakeAttribution' | 'CanonicalIntakeConsent' | 'CreateBookingIntentRequest' | 'CreateBookingIntentSuccessResponse' | 'CreateBrochureLeadRequest' | 'CreateBrochureLeadResponse' | 'CreateInquiryRequest' | 'CreateInquiryResponse' | 'GetBookingIntentStatusResponse' | 'PaginationMeta' | 'PublicApiAbuseBlockedError' | 'PublicApiError' | 'PublicApiInternalError' | 'PublicApiNotFoundError' | 'PublicApiNotImplementedError' | 'PublicApiRateLimitError' | 'PublicApiTravelStatusUnavailableError' | 'PublicApiValidationError' | 'PublicAvailabilityListItem' | 'PublicAvailabilityListResponse' | 'PublicBookingIntentLinks' | 'PublicBookingIntentPublicStatus' | 'PublicBookingIntentStatus' | 'PublicDepartureListItem' | 'PublicDepartureListResponse' | 'PublicDestinationDetail' | 'PublicDestinationDetailResponse' | 'PublicDestinationListItem' | 'PublicDestinationListResponse' | 'PublicError' | 'PublicErrorEnvelope' | 'PublicHomepageResponse' | 'PublicHomepageSeo' | 'PublicHomepageStats' | 'PublicNavLink' | 'PublicOfficialSourceCard' | 'PublicSearchSuggestion' | 'PublicSearchSuggestionsResponse' | 'PublicSeo' | 'PublicSiteChromeContent' | 'PublicSiteChromeContentResponse' | 'PublicSiteCta' | 'PublicSiteFeaturesResponse' | 'PublicSiteFeatureStates' | 'PublicSiteHomeContent' | 'PublicSiteHomeContentResponse' | 'PublicSiteHomeSection' | 'PublicSitemapEntry' | 'PublicSitemapResponse' | 'PublicSiteNavigationResponse' | 'PublicSiteNavItem' | 'PublicSiteRouteKey' | 'PublicSiteSocialLink' | 'PublicSiteTrustPageContent' | 'PublicSiteTrustPageContentResponse' | 'PublicSiteTrustPageSeo' | 'PublicSiteTrustSection' | 'PublicSiteTrustTitledItem' | 'PublicStaticLink' | 'PublicSurfiranOperationalUpdate' | 'PublicTourAccommodationListItem' | 'PublicTourAccommodationListResponse' | 'PublicTourCategoryListItem' | 'PublicTourCategoryListResponse' | 'PublicTourDetail' | 'PublicTourDetailEditorial' | 'PublicTourDetailResponse' | 'PublicTourExperienceFrame' | 'PublicTourExperienceListItem' | 'PublicTourExperienceListResponse' | 'PublicTourItineraryDay' | 'PublicTourItineraryResponse' | 'PublicTourListItem' | 'PublicTourListResponse' | 'PublicTourQuestionListItem' | 'PublicTourQuestionListResponse' | 'PublicTourRatingBreakdownItem' | 'PublicTourReviewListItem' | 'PublicTourReviewListResponse' | 'PublicTourReviewsAggregate' | 'PublicTourReviewsSummary' | 'PublicTourReviewsSummaryResponse' | 'PublicTourRouteResponse' | 'PublicTourRouteStop' | 'PublicTourTailorJourney' | 'PublicTravelKnowledgeItem' | 'PublicTravelKnowledgeResponse' | 'PublicTravellerStoryListItem' | 'PublicTravellerStoryListResponse' | 'PublicTravelStatusCorrectionNotice' | 'PublicTravelStatusFreshness' | 'PublicTravelStatusHandoff' | 'PublicTravelStatusResponse' | 'PublicTravelStatusReviewerLabel' | 'PublicTravelStatusSection' | 'PublicTravelStatusState';
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED FILE. DO NOT EDIT.
2
- // Source: server/openapi/public-api-v1.yaml
3
- // Generator: scripts/generate-public-api-v1-types.cjs
2
+ // Contract snapshot: contract/public-api-v1.yaml
3
+ // Generator: scripts/generate-public-api-v1.cjs v1.0.0
4
4
  export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { createPublicApiClient } from './client.js';
2
- export type { PublicApiErrorCode, PublicApiV1Path, PublicApiV1RouteMap, PublicError, PublicErrorEnvelope, PublicApiError, PublicApiNotImplementedError, PublicApiValidationError, PublicApiRateLimitError, PublicApiAbuseBlockedError, PublicTourListItem, PublicTourDetail, PublicDepartureListItem, PublicAvailabilityStatus, PublicAvailabilityListItem, PublicAvailabilityListResponse, PublicDestinationListItem, PublicDestinationDetail, PublicTourListResponse, PublicTourDetailResponse, PublicDepartureListResponse, PublicDestinationListResponse, PublicDestinationDetailResponse, PublicApiResult, ListPublicToursParams, ListPublicDeparturesParams, ListPublicTourAvailabilityParams, ListPublicDestinationsParams, PublicInquiryRequest, PublicInquiryResponse, PublicBookingIntentRequest, PublicBookingIntentData, PublicBookingIntentResponse, PublicHomepageStats, PublicHomepageSeo, PublicHomepageResponse, PublicSitemapEntry, PublicSitemapResponse, PublicNavLink, PublicStaticLink, PublicSiteNavigationResponse, PublicTourCategoryListItem, PublicTourCategoryListResponse, PublicSearchSuggestion, PublicSearchSuggestionsResponse, PublicSiteRouteKey, PublicSiteCta, PublicSiteHomeSectionType, PublicSiteHomeSection, PublicSiteHomeContent, PublicSiteHomeContentResponse, PublicSiteNavItem, PublicSiteSocialPlatform, PublicSiteSocialLink, PublicSiteChromeContent, PublicSiteChromeContentResponse, PublicSiteTrustSectionType, PublicSiteTrustTitledItem, PublicSiteTrustParagraphGroup, PublicSiteTrustTitledItemList, PublicSiteTrustBulletList, PublicSiteTrustSection, PublicSiteTrustPageSeo, PublicSiteTrustPageContent, PublicSiteTrustPageContentResponse, PublicTravelStatusState, PublicTravelStatusFreshness, PublicSurfiranOperationalUpdate, PublicTravelStatusResponse, PublicTravelKnowledgeType, PublicTravelKnowledgeItem, PublicTravelKnowledgeResponse, GetPublicSitemapParams, GetPublicSearchSuggestionsParams, GetPublicTravelKnowledgeParams, ListPublicTourCategoriesParams, PublicTravellerStoryListItem, PublicTravellerStoryListResponse, ListPublicTravellerStoriesParams, PublicTourQuestionListItem, PublicTourQuestionListResponse, ListPublicTourQuestionsParams, PublicTourExperienceFrame, PublicTourExperienceListItem, PublicTourExperienceListResponse, ListPublicTourExperiencesParams, PublicTourAccommodationListItem, PublicTourAccommodationListResponse, ListPublicTourAccommodationsParams, PublicTourReviewsAggregate, PublicTourRatingBreakdownItem, PublicTourReviewsSummary, PublicTourReviewsSummaryResponse, PublicTourReviewListItem, PublicTourReviewListResponse, PublicTourReviewsLimit, ListPublicTourReviewsParams, PublicTourRouteStop, PublicTourRouteResponse, ListPublicTourRouteParams, PublicItineraryMealCode, PublicTourItineraryDay, PublicTourItineraryResponse, ListPublicTourItineraryParams, PublicBrochureLeadRequest, PublicBrochureLeadResponse, } from './types.js';
2
+ export type { PublicApiErrorCode, PublicApiV1Path, PublicApiV1RouteMap, PublicError, PublicErrorEnvelope, PublicApiError, PublicApiNotImplementedError, PublicApiValidationError, PublicApiRateLimitError, PublicApiAbuseBlockedError, PublicTourListItem, PublicTourDetail, PublicTourDetailEditorial, PublicDepartureListItem, PublicAvailabilityStatus, PublicAvailabilityListItem, PublicAvailabilityListResponse, PublicDestinationListItem, PublicDestinationDetail, PublicTourListResponse, PublicTourDetailResponse, PublicDepartureListResponse, PublicDestinationListResponse, PublicDestinationDetailResponse, PublicApiResult, ListPublicToursParams, GetPublicTourPreviewParams, ListPublicDeparturesParams, ListPublicTourAvailabilityParams, ListPublicDestinationsParams, PublicInquiryRequest, PublicTourTailorJourney, PublicInquiryResponse, PublicBookingIntentRequest, PublicBookingIntentData, PublicBookingIntentResponse, PublicHomepageStats, PublicHomepageSeo, PublicHomepageResponse, PublicSitemapEntry, PublicSitemapResponse, PublicNavLink, PublicStaticLink, PublicSiteNavigationResponse, PublicTourCategoryListItem, PublicTourCategoryListResponse, PublicSearchSuggestion, PublicSearchSuggestionsResponse, PublicSiteRouteKey, PublicSiteCta, PublicSiteHomeSectionType, PublicSiteHomeSection, PublicSiteHomeContent, PublicSiteHomeContentResponse, PublicSiteNavItem, PublicSiteSocialPlatform, PublicSiteSocialLink, PublicSiteChromeContent, PublicSiteChromeContentResponse, PublicSiteFeatureStates, PublicSiteFeaturesResponse, PublicSiteTrustSectionType, PublicSiteTrustTitledItem, PublicSiteTrustParagraphGroup, PublicSiteTrustTitledItemList, PublicSiteTrustBulletList, PublicSiteTrustSection, PublicSiteTrustPageSeo, PublicSiteTrustPageContent, PublicSiteTrustPageContentResponse, PublicTravelStatusState, PublicTravelStatusFreshness, PublicSurfiranOperationalUpdate, PublicTravelStatusResponse, PublicTravelKnowledgeType, PublicTravelKnowledgeItem, PublicTravelKnowledgeResponse, GetPublicSitemapParams, GetPublicSearchSuggestionsParams, GetPublicTravelKnowledgeParams, ListPublicTourCategoriesParams, PublicTravellerStoryListItem, PublicTravellerStoryListResponse, ListPublicTravellerStoriesParams, PublicTourQuestionListItem, PublicTourQuestionListResponse, ListPublicTourQuestionsParams, PublicTourExperienceFrame, PublicTourExperienceListItem, PublicTourExperienceListResponse, ListPublicTourExperiencesParams, PublicTourAccommodationListItem, PublicTourAccommodationListResponse, ListPublicTourAccommodationsParams, PublicTourReviewsAggregate, PublicTourRatingBreakdownItem, PublicTourReviewsSummary, PublicTourReviewsSummaryResponse, PublicTourReviewListItem, PublicTourReviewListResponse, PublicTourReviewsLimit, ListPublicTourReviewsParams, PublicTourRouteStop, PublicTourRouteResponse, ListPublicTourRouteParams, PublicItineraryMealCode, PublicTourItineraryDay, PublicTourItineraryResponse, ListPublicTourItineraryParams, PublicBrochureLeadRequest, PublicBrochureLeadResponse, } from './types.js';
3
3
  export type { PublicApiClientOptions } from './client.js';
4
4
  export type { GeneratedPublicApiVersion, GeneratedPublicApiPath, GeneratedPublicApiOperation, GeneratedPublicApiActiveOperation, GeneratedPublicApiBlockedOperation, GeneratedPublicApiOperationId, GeneratedPublicApiSchemaName, } from './generated/public-api-v1.js';
package/dist/types.d.ts CHANGED
@@ -1,10 +1,17 @@
1
1
  export type PublicApiErrorCode = 'PUBLIC_API_ROUTE_NOT_IMPLEMENTED' | 'PUBLIC_API_INTERNAL_ERROR' | 'PUBLIC_API_TOUR_NOT_FOUND' | 'PUBLIC_API_DESTINATION_NOT_FOUND' | 'PUBLIC_API_INVALID_QUERY' | 'PUBLIC_API_BOOKING_INTENT_VALIDATION_ERROR' | 'PUBLIC_API_IDEMPOTENCY_CONFLICT' | 'PUBLIC_API_RATE_LIMITED' | 'PUBLIC_API_ABUSE_BLOCKED' | 'PUBLIC_API_TRAVEL_STATUS_UNAVAILABLE' | 'PUBLIC_API_SITE_CONTENT_UNAVAILABLE' | 'PUBLIC_API_CLIENT_TIMEOUT';
2
- export type PublicApiV1Path = '/homepage' | '/site-navigation' | '/sitemap' | '/search-suggestions' | '/tour-categories' | '/tours' | '/tours/{slug}' | '/tours/{slug}/departures' | '/tours/{slug}/availability' | '/tours/{slug}/reviews' | '/destinations' | '/destinations/{slug}' | '/inquiries' | '/booking-intents' | '/travel-status' | '/travel-knowledge' | '/site-content/home' | '/site-content/header' | '/site-content/footer' | '/site-content/about' | '/site-content/contact' | '/site-content/faqs' | '/site-content/privacy' | '/site-content/terms' | '/site-content/responsible-travel' | '/site-content/iran-travel-status' | '/site-content/iran-visa-information' | '/site-content/money-in-iran' | '/site-content/travel-insurance' | '/site-content/health-safety-support' | '/site-content/booking-cancellation-policy' | '/site-content/cookie-policy' | '/site-content/accessibility-statement';
2
+ export type PublicApiV1Path = '/homepage' | '/site-navigation' | '/site-features' | '/sitemap' | '/search-suggestions' | '/tour-categories' | '/tours' | '/tours/{slug}' | '/tours/{slug}/preview' | '/tours/{slug}/departures' | '/tours/{slug}/availability' | '/tours/{slug}/reviews' | '/destinations' | '/destinations/{slug}' | '/inquiries' | '/booking-intents' | '/travel-status' | '/travel-knowledge' | '/site-content/home' | '/site-content/header' | '/site-content/footer' | '/site-content/about' | '/site-content/contact' | '/site-content/faqs' | '/site-content/privacy' | '/site-content/terms' | '/site-content/responsible-travel' | '/site-content/iran-travel-status' | '/site-content/iran-visa-information' | '/site-content/money-in-iran' | '/site-content/travel-insurance' | '/site-content/health-safety-support' | '/site-content/booking-cancellation-policy' | '/site-content/cookie-policy' | '/site-content/accessibility-statement';
3
3
  export type PublicError = {
4
4
  code: string;
5
5
  message: string;
6
6
  status: number;
7
7
  requestId: string | null;
8
+ /**
9
+ * Optional public-safe diagnostic object (contract schema PublicError.details).
10
+ * Shape varies by error code — e.g. the destination-not-found error may
11
+ * carry `{ redirectSlug: string }` when the requested slug was renamed or
12
+ * merged into another place. Absent when there is nothing safe to expose.
13
+ */
14
+ details?: Record<string, unknown>;
8
15
  };
9
16
  export type PublicErrorEnvelope = {
10
17
  error: PublicError;
@@ -93,6 +100,16 @@ export type PublicTourListItem = {
93
100
  * here — it has no fixed tour to attach to; it is the `/inquire` flow.
94
101
  */
95
102
  travelFormat?: string | null;
103
+ /**
104
+ * True when staff have flagged this tour as a "Signature Trip" from
105
+ * `tour_packages.is_signature` (draft migration
106
+ * `20260820_tour_packages_is_signature.sql`, not yet executed in
107
+ * production, so this is `false` for every tour today). Independent of the
108
+ * homepage's positional "Signature Iran Tours" grid, which does not read
109
+ * this column. Optional so a response projected before this field existed
110
+ * still typechecks; absent means false.
111
+ */
112
+ isSignature?: boolean;
96
113
  /**
97
114
  * Owner directive 2026-08-07 (full Home design application): staff-authored
98
115
  * "was" price display for a current deal
@@ -121,6 +138,20 @@ export type PublicTourListItem = {
121
138
  * published.
122
139
  */
123
140
  maxGroupSize?: number | null;
141
+ /**
142
+ * Staff-authored city the tour begins in (`tour_packages.start_city`).
143
+ * Published so a curated value can override the endpoints the card otherwise
144
+ * derives from itinerary day objects, whose fallback reaches a day title when
145
+ * the day itself names no city. Null when unpublished — never backfilled and
146
+ * never guessed, so a consumer should fall back to its own derivation rather
147
+ * than treat null as "this tour has no start city".
148
+ */
149
+ startCity?: string | null;
150
+ /**
151
+ * Staff-authored city the tour ends in (`tour_packages.finish_city`). Same
152
+ * provenance and null semantics as `startCity`.
153
+ */
154
+ finishCity?: string | null;
124
155
  status: string;
125
156
  updatedAt: string | null;
126
157
  };
@@ -133,6 +164,92 @@ export type PublicTourDetail = PublicTourListItem & {
133
164
  gallery: string[];
134
165
  seoTitle: string | null;
135
166
  seoDescription: string | null;
167
+ editorial?: PublicTourDetailEditorial | null;
168
+ /**
169
+ * Operator-configured "Tailor This Journey" call to action.
170
+ *
171
+ * Absent when no operator has configured the module for this tour, so a
172
+ * consumer keeps whatever default tailor-made hand-off it already renders.
173
+ * Present with `enabled: false` and no other field when an operator switched
174
+ * it off — that is the signal to render nothing for this tour.
175
+ *
176
+ * The call to action leads to a personalised-proposal inquiry only. It never
177
+ * represents a booking, reservation, inventory hold, price guarantee,
178
+ * payment, or confirmed departure.
179
+ */
180
+ tailorThisJourney?: PublicTourTailorJourney;
181
+ /** Operator-curated related tours (PR-13 / OA-9), in the pinned order. Empty when none are pinned/published. */
182
+ relatedJourneys?: PublicTourListItem[];
183
+ };
184
+ export type PublicTourTailorJourney = {
185
+ enabled: boolean;
186
+ /** Canonical public slug of the source tour, for inquiry attribution. Present only when enabled. */
187
+ tourSlug?: string;
188
+ /** Operator override for the headline. Null means the consumer uses its own default copy. */
189
+ ctaTitle?: string | null;
190
+ /** Operator override for the supporting text. Null means the consumer uses its own default copy. */
191
+ ctaText?: string | null;
192
+ /** Published customisation prompts, in the operator's authored order. */
193
+ suggestedCustomizationOptions?: string[];
194
+ };
195
+ export type PublicTourDetailEditorial = {
196
+ tourType: string | null;
197
+ tourCode: string | null;
198
+ breadcrumbTitle: string | null;
199
+ authorName: string | null;
200
+ authorUrl: string | null;
201
+ authorRole: string | null;
202
+ lastReviewedAt: string | null;
203
+ guideLabel: string | null;
204
+ groupSizeNote: string | null;
205
+ paceLabel: string | null;
206
+ paceNote: string | null;
207
+ comfortLabel: string | null;
208
+ comfortNote: string | null;
209
+ guideNote: string | null;
210
+ startLabel: string | null;
211
+ endLabel: string | null;
212
+ startEndNote: string | null;
213
+ overviewParagraphs: string[];
214
+ heroAltText: string | null;
215
+ heroObjectPosition: string | null;
216
+ galleryAltTexts: string[];
217
+ galleryObjectPositions: string[];
218
+ heroVideo: {
219
+ url: string;
220
+ posterUrl: string | null;
221
+ durationLabel: string | null;
222
+ } | null;
223
+ heroQuote: {
224
+ text: string;
225
+ authorName: string | null;
226
+ travelDate: string | null;
227
+ sourceUrl: string | null;
228
+ } | null;
229
+ payment?: {
230
+ depositSummary: string | null;
231
+ balanceSummary: string | null;
232
+ cancellationSummary: string | null;
233
+ paymentSummary: string | null;
234
+ };
235
+ operator?: {
236
+ body: string | null;
237
+ imageUrl: string | null;
238
+ aboutUrl: string | null;
239
+ licenceLabel: string | null;
240
+ officeLabel: string | null;
241
+ metrics: Array<{
242
+ value: string;
243
+ label: string;
244
+ }>;
245
+ };
246
+ goodToKnow: Array<{
247
+ title: string;
248
+ body: string;
249
+ ctaLabel: string | null;
250
+ href: string | null;
251
+ icon: 'info' | 'card' | 'shield' | 'globe' | 'people' | 'route' | null;
252
+ }>;
136
253
  };
137
254
  export type PublicDepartureListItem = {
138
255
  id: number;
@@ -190,6 +307,10 @@ export type PublicTourReviewsAggregate = {
190
307
  averageRating: number | null;
191
308
  reviewCount: number;
192
309
  source: string | null;
310
+ /** Public URL for the source aggregate, when the CRM has verified one. */
311
+ sourceUrl?: string | null;
312
+ /** Source platform's operator-wide review count, when published. */
313
+ operatorReviewCount?: number | null;
193
314
  /** Most recent staff entry among included reviews — the "as of" honesty marker. */
194
315
  lastVerifiedAt: string | null;
195
316
  };
@@ -207,9 +328,13 @@ export type PublicTourReviewListItem = {
207
328
  authorName: string | null;
208
329
  partyType: string | null;
209
330
  reviewDate: string | null;
331
+ /** Source-backed month/year label when the original review has no known day. */
332
+ reviewDateLabel?: string | null;
210
333
  rating: number;
211
334
  title: string;
212
335
  body: string;
336
+ /** Public attribution for the operator reply, without the reply body. */
337
+ replyAuthor?: string | null;
213
338
  deepLink: string | null;
214
339
  };
215
340
  /** Meal codes a day can include. Always returned in B,L,D order. */
@@ -239,6 +364,13 @@ export type PublicTourItineraryDay = {
239
364
  includedActivities: string[];
240
365
  optionalActivities: string[];
241
366
  landmarks: string[];
367
+ transportNote: string | null;
368
+ extraNightsNote?: string | null;
369
+ /** Alt text aligned by index with mediaUrls. Older runtime responses may omit it. */
370
+ mediaAltTexts?: string[];
371
+ /** @deprecated Use mediaAltTexts. Retained for older consumers. */
372
+ mediaAltText?: string | null;
373
+ mediaUrls: string[];
242
374
  };
243
375
  export type PublicTourRouteStop = {
244
376
  id: number;
@@ -265,7 +397,14 @@ export type PublicAvailabilityListItem = {
265
397
  currency: string | null;
266
398
  priceDisplay: string | null;
267
399
  promoLabel?: string | null;
400
+ publicNote?: string | null;
268
401
  roomBasis?: 'twin_share' | 'double_share' | 'single_room' | 'triple_share' | 'family_room' | null;
402
+ roomOptions?: Array<{
403
+ basis: 'twin_share' | 'double_share' | 'single_room' | 'private_departure';
404
+ label: string;
405
+ priceDisplay: string;
406
+ minimumTravelers?: number;
407
+ }>;
269
408
  remainingCapacityBucket: 'available' | 'limited' | 'full' | 'unconstrained' | 'unknown';
270
409
  /**
271
410
  * Owner directive 2026-08-07: full Home design application — limited-state
@@ -278,6 +417,26 @@ export type PublicAvailabilityListItem = {
278
417
  ctaUrl?: string;
279
418
  updatedAt: string | null;
280
419
  };
420
+ export type PublicSeoRobots = {
421
+ index: boolean;
422
+ follow: boolean;
423
+ };
424
+ /**
425
+ * Public-safe SEO projection (contract schema PublicSeo). The og fields
426
+ * default to mirroring title/description; destinations with a published place
427
+ * profile may carry operator-authored og overrides, a canonical override, and
428
+ * a robots directive. robots is null when no published source declares one.
429
+ */
430
+ export type PublicSeo = {
431
+ title: string | null;
432
+ description: string | null;
433
+ canonicalPath: string;
434
+ ogTitle: string | null;
435
+ ogDescription: string | null;
436
+ ogImage: string | null;
437
+ schemaType: string | null;
438
+ robots?: PublicSeoRobots | null;
439
+ };
281
440
  export type PublicDestinationListItem = {
282
441
  id: number;
283
442
  slug: string;
@@ -285,11 +444,15 @@ export type PublicDestinationListItem = {
285
444
  summary: string | null;
286
445
  heroImage: string | null;
287
446
  updatedAt: string | null;
447
+ /** Optional so consumers stay compatible with servers predating the seo projection. */
448
+ seo?: PublicSeo;
288
449
  };
289
450
  export type PublicDestinationDetail = PublicDestinationListItem & {
290
451
  description: string | null;
291
452
  seoTitle: string | null;
292
453
  seoDescription: string | null;
454
+ /** Operator-authored JSON-LD overrides from the published place profile. */
455
+ structuredDataOverrides?: Record<string, unknown> | null;
293
456
  };
294
457
  export type PublicTourListResponse = {
295
458
  data: PublicTourListItem[];
@@ -361,18 +524,58 @@ export type PublicInquiryRequest = {
361
524
  name: string;
362
525
  email: string;
363
526
  message: string;
527
+ /**
528
+ * Stable event identifier shared by the structured API submission and any
529
+ * transport fallback. The server creates one when an older client omits it.
530
+ */
531
+ leadEventId?: string | null;
532
+ phone?: string | null;
533
+ travelersCount?: number | null;
534
+ locale?: string | null;
535
+ formId?: string | null;
536
+ attribution?: {
537
+ landingPage?: string | null;
538
+ referrer?: string | null;
539
+ utmSource?: string | null;
540
+ utmMedium?: string | null;
541
+ utmCampaign?: string | null;
542
+ utmContent?: string | null;
543
+ utmTerm?: string | null;
544
+ sessionId?: string | null;
545
+ clickId?: string | null;
546
+ } | null;
547
+ consent?: {
548
+ purpose: string;
549
+ policyVersion: string;
550
+ /** ISO-8601 timestamp with an explicit timezone. */
551
+ capturedAt: string;
552
+ } | null;
364
553
  tourSlug?: string | null;
554
+ /**
555
+ * Closed public intent used by the CRM sales queue for inquiry routing.
556
+ * `tailor_made` requires a `tourSlug` whose tour is publicly eligible and
557
+ * whose Tailor This Journey call to action is enabled.
558
+ */
559
+ requestType?: 'tour_inquiry' | 'waiting_list' | 'specialist_question' | 'tailor_made' | null;
365
560
  /**
366
561
  * Optional requested departure date (YYYY-MM-DD) when the inquiry was
367
562
  * submitted from a specific public departure card. No departure identifier or
368
563
  * commercial data is sent.
369
564
  */
370
565
  departureDate?: string | null;
566
+ /**
567
+ * Customisation options the traveller selected from the ones published for
568
+ * the named tour. Requires `tourSlug` and `requestType: 'tailor_made'`, and
569
+ * every entry must match one of that tour's published
570
+ * `suggestedCustomizationOptions`. Free-text preferences belong in `message`.
571
+ */
572
+ customizationOptions?: string[] | null;
371
573
  };
372
574
  export type PublicInquiryResponse = {
373
575
  data: {
374
576
  accepted: boolean;
375
577
  inquiryId: string;
578
+ leadEventId: string;
376
579
  };
377
580
  };
378
581
  export type PublicBookingIntentRequest = {
@@ -383,10 +586,16 @@ export type PublicBookingIntentRequest = {
383
586
  leadTravelerEmail?: string | null;
384
587
  leadTravelerPhone?: string | null;
385
588
  notes?: string | null;
589
+ leadEventId?: string | null;
590
+ locale?: string | null;
591
+ formId?: string | null;
592
+ attribution?: PublicInquiryRequest['attribution'];
593
+ consent?: PublicInquiryRequest['consent'];
386
594
  };
387
595
  export type PublicBookingIntentStatus = 'received' | 'needs_review' | 'expired' | 'cancelled' | 'converted_internal';
388
596
  export type PublicBookingIntentData = {
389
597
  publicId: string;
598
+ leadEventId: string;
390
599
  status: PublicBookingIntentStatus;
391
600
  tourSlug: string;
392
601
  departureId: number | null;
@@ -411,11 +620,17 @@ export type PublicBrochureLeadRequest = {
411
620
  utmSource?: string | null;
412
621
  utmMedium?: string | null;
413
622
  utmCampaign?: string | null;
623
+ leadEventId?: string | null;
624
+ locale?: string | null;
625
+ formId?: string | null;
626
+ attribution?: PublicInquiryRequest['attribution'];
627
+ consent?: PublicInquiryRequest['consent'];
414
628
  };
415
629
  export type PublicBrochureLeadResponse = {
416
630
  data: {
417
631
  accepted: boolean;
418
632
  leadId: string;
633
+ leadEventId: string;
419
634
  };
420
635
  };
421
636
  export type PublicHomepageStats = {
@@ -550,7 +765,7 @@ export type PublicTravelStatusResponse = {
550
765
  };
551
766
  };
552
767
  /** Closed public route vocabulary. Routing is resolved by the rendering layer. */
553
- export type PublicSiteRouteKey = 'home' | 'tours' | 'tour-categories' | 'destinations' | 'about' | 'contact' | 'inquire' | 'faqs' | 'privacy' | 'terms' | 'responsible-travel' | 'iran-travel-status' | 'iran-visa-information' | 'money-in-iran' | 'travel-insurance' | 'health-safety-support' | 'booking-cancellation-policy' | 'cookie-policy' | 'accessibility-statement';
768
+ export type PublicSiteRouteKey = 'home' | 'tours' | 'tour-categories' | 'destinations' | 'about' | 'contact' | 'inquire' | 'faqs' | 'privacy' | 'terms' | 'responsible-travel' | 'iran-travel-status' | 'iran-visa-information' | 'money-in-iran' | 'travel-insurance' | 'health-safety-support' | 'booking-cancellation-policy' | 'cookie-policy' | 'accessibility-statement' | 'reviews' | 'travel-trade';
554
769
  export type PublicSiteCta = {
555
770
  label: string;
556
771
  routeKey: PublicSiteRouteKey;
@@ -637,14 +852,87 @@ export type PublicSiteTrustPageContent = {
637
852
  export type PublicSiteTrustPageContentResponse = {
638
853
  data: PublicSiteTrustPageContent | null;
639
854
  };
855
+ /**
856
+ * Effective on/off state for every known public website feature.
857
+ *
858
+ * Closed by construction: the key set is owned by the API's feature registry,
859
+ * so a consumer can only branch on a feature the contract actually declares.
860
+ * The payload is booleans only — it never carries who changed a setting, when,
861
+ * or whether a master control is involved.
862
+ *
863
+ * These configure presentation. Booking, payment, inventory, guaranteed
864
+ * departure, availability, price validity, cancellation, and visa state are
865
+ * authoritative operational facts and never appear here.
866
+ */
867
+ export type PublicSiteFeatureStates = {
868
+ /**
869
+ * True when the website may offer tour comparison. When false, every Compare
870
+ * control is removed and the comparison page is unavailable. Comparison
871
+ * selections already stored in a visitor's browser are not affected.
872
+ */
873
+ compareTours: boolean;
874
+ /**
875
+ * True when the website may show traveller reviews and ratings. When false,
876
+ * review sections are removed from tour pages, the homepage, the tours
877
+ * catalog, and the tailor-made enquiry page, the dedicated reviews page is
878
+ * unavailable, and review ratings are omitted from structured data.
879
+ */
880
+ tourReviews: boolean;
881
+ /**
882
+ * True when the website may show deal ribbons, previous prices, and savings
883
+ * notes on tour cards. When false, those promotional marks are hidden while
884
+ * current prices and availability stay visible.
885
+ */
886
+ dealBadges: boolean;
887
+ /**
888
+ * True when the website may offer the email-gated PDF brochure download on
889
+ * tour pages. When false, every brochure control and the download dialog are
890
+ * removed.
891
+ */
892
+ brochureDownload: boolean;
893
+ /**
894
+ * True when the website may show the published traveller questions section
895
+ * on tour pages. When false, the section is removed and the questions are
896
+ * omitted from structured data.
897
+ */
898
+ tourQuestions: boolean;
899
+ /**
900
+ * True when the website may show the related-tours rail on tour pages. When
901
+ * false, the rail is removed.
902
+ */
903
+ relatedTours: boolean;
904
+ /**
905
+ * True when the website may promote the tailor-made service on tour pages
906
+ * and the tours catalog. When false, those teaser sections are removed;
907
+ * tailor-made pages stay reachable directly.
908
+ */
909
+ tailorMadeTeaser: boolean;
910
+ /**
911
+ * True when the website may show the guided trip-finder widget on the
912
+ * homepage. When false, the widget is removed.
913
+ */
914
+ tripFinder: boolean;
915
+ /**
916
+ * True when the website may show the traveller moments and stories section
917
+ * on the homepage. When false, the section is removed.
918
+ */
919
+ travellerStories: boolean;
920
+ };
921
+ export type PublicSiteFeaturesResponse = {
922
+ data: {
923
+ features: PublicSiteFeatureStates;
924
+ };
925
+ };
640
926
  export type PublicApiV1RouteMap = {
641
927
  'GET /homepage': PublicHomepageResponse;
642
928
  'GET /site-navigation': PublicSiteNavigationResponse;
929
+ 'GET /site-features': PublicSiteFeaturesResponse;
643
930
  'GET /sitemap': PublicSitemapResponse;
644
931
  'GET /search-suggestions': PublicSearchSuggestionsResponse;
645
932
  'GET /tour-categories': PublicTourCategoryListResponse;
646
933
  'GET /tours': PublicTourListResponse;
647
934
  'GET /tours/{slug}': PublicTourDetailResponse;
935
+ 'GET /tours/{slug}/preview': PublicTourDetailResponse;
648
936
  'GET /tours/{slug}/departures': PublicDepartureListResponse;
649
937
  'GET /tours/{slug}/availability': PublicAvailabilityListResponse;
650
938
  'GET /tours/{slug}/reviews': PublicTourReviewListResponse;
@@ -698,6 +986,10 @@ export type ListPublicDeparturesParams = {
698
986
  limit?: number;
699
987
  cursor?: string;
700
988
  };
989
+ export type GetPublicTourPreviewParams = {
990
+ /** Short-lived signed candidate-preview token minted by the CRM (ADR-0022). Required. */
991
+ token: string;
992
+ };
701
993
  export type ListPublicTourQuestionsParams = {
702
994
  limit?: number;
703
995
  };
package/dist/types.js CHANGED
@@ -1,3 +1,3 @@
1
- // TypeScript interfaces aligned with server/openapi/public-api-v1.yaml v0.8.0
1
+ // TypeScript interfaces aligned with server/openapi/public-api-v1.yaml v0.9.0
2
2
  // Zero runtime code. Types only.
3
3
  export {};
package/package.json CHANGED
@@ -1,8 +1,25 @@
1
1
  {
2
2
  "name": "@surfiran/public-api-client",
3
- "version": "0.1.0",
3
+ "version": "0.11.0",
4
4
  "description": "TypeScript types and client for SURFIRAN CRM Public API V1",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/mehdi360/surfiran-public-api-client.git",
12
+ "directory": "."
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/mehdi360/surfiran-public-api-client/issues"
16
+ },
17
+ "homepage": "https://github.com/mehdi360/surfiran-public-api-client",
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "registry": "https://registry.npmjs.org"
21
+ },
22
+ "sideEffects": false,
6
23
  "main": "dist/index.js",
7
24
  "types": "dist/index.d.ts",
8
25
  "exports": {
@@ -16,16 +33,29 @@
16
33
  }
17
34
  },
18
35
  "files": [
19
- "dist",
20
- "README.md"
36
+ "README.md",
37
+ "dist/client.d.ts",
38
+ "dist/client.js",
39
+ "dist/generated/public-api-v1.d.ts",
40
+ "dist/generated/public-api-v1.js",
41
+ "dist/index.d.ts",
42
+ "dist/index.js",
43
+ "dist/types.d.ts",
44
+ "dist/types.js"
21
45
  ],
22
46
  "scripts": {
23
- "generate": "node ../../scripts/generate-public-api-v1-types.cjs",
24
47
  "build": "rm -rf dist && tsc -p tsconfig.json",
25
- "prepack": "npm run typecheck && npm test && npm run build",
48
+ "generate": "node scripts/generate-public-api-v1.cjs",
49
+ "prepack": "npm run verify:contract && npm run verify:package && npm run verify:generated && npm run typecheck && npm test && npm run build",
26
50
  "typecheck": "tsc --noEmit",
27
51
  "test": "vitest run",
28
- "verify:generated": "npm run generate && git diff --exit-code -- src/generated/public-api-v1.ts"
52
+ "verify:contract": "node scripts/verify-contract.cjs",
53
+ "verify:generated": "node scripts/verify-generated.cjs",
54
+ "verify:package": "node scripts/verify-package.cjs",
55
+ "verify:pack": "node scripts/verify-pack.cjs",
56
+ "verify:publication-preflight": "node scripts/verify-publication-preflight.cjs",
57
+ "create:provenance": "node scripts/create-provenance.cjs",
58
+ "verify:provenance": "node scripts/verify-provenance.cjs"
29
59
  },
30
60
  "dependencies": {},
31
61
  "devDependencies": {