From 629595dd9a7a56e38ac1b58aad9039b05f7418b9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 16 Oct 2025 21:10:14 +0000 Subject: [PATCH] feat: Add SeatsAero client validation mode and optimize pagination Co-authored-by: punitsai36 --- src/lib/fli/seats-aero/client.ts | 29 +++++++++++++++++++ .../process-seats-aero-search-pagination.ts | 8 ++++- .../workflows/process-seats-aero-search.ts | 2 ++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/lib/fli/seats-aero/client.ts b/src/lib/fli/seats-aero/client.ts index 6a0ebe9..ca36fb7 100644 --- a/src/lib/fli/seats-aero/client.ts +++ b/src/lib/fli/seats-aero/client.ts @@ -7,12 +7,20 @@ import type { SearchRequestParams, SearchResponse } from "../models/seats-aero"; import { SearchRequestParamsSchema, SearchResponseSchema, + AvailabilityTripSchema, } from "../models/seats-aero"; +import { z } from "zod"; export type SeatsAeroClientConfig = { apiKey: string; baseUrl?: string; fetchImpl?: typeof fetch; + /** + * Controls how strictly responses are validated. + * - "full" (default): validate entire response with deep schemas + * - "light": validate only top-level fields and AvailabilityTrips to reduce CPU + */ + validationMode?: "full" | "light"; }; /** @@ -41,11 +49,13 @@ export class SeatsAeroClient { private readonly apiKey: string; private readonly baseUrl: string; private readonly fetchImpl: typeof fetch; + private readonly validationMode: "full" | "light"; constructor(config: SeatsAeroClientConfig) { this.apiKey = config.apiKey; this.baseUrl = config.baseUrl ?? "https://seats.aero/partnerapi"; this.fetchImpl = config.fetchImpl ?? fetch; + this.validationMode = config.validationMode ?? "full"; } /** @@ -79,6 +89,25 @@ export class SeatsAeroClient { } const data = await response.json(); + + if (this.validationMode === "light") { + // Validate only the fields we actually consume downstream to reduce CPU cost + const LightAvailabilitySchema = z + .object({ + AvailabilityTrips: z.array(AvailabilityTripSchema).nullable(), + }) + .passthrough(); + + const LightSearchResponseSchema = z.object({ + data: z.array(LightAvailabilitySchema), + count: z.number(), + hasMore: z.boolean(), + cursor: z.number(), + }); + + return LightSearchResponseSchema.parse(data) as SearchResponse; + } + return SearchResponseSchema.parse(data); } diff --git a/src/workers/workflows/process-seats-aero-search-pagination.ts b/src/workers/workflows/process-seats-aero-search-pagination.ts index 9ddd6ec..bacad5b 100644 --- a/src/workers/workflows/process-seats-aero-search-pagination.ts +++ b/src/workers/workflows/process-seats-aero-search-pagination.ts @@ -28,6 +28,7 @@ export interface SeatsAeroClientLike { take: number; only_direct_flights: boolean; include_filtered: boolean; + minify_trips?: boolean; cursor?: number; }) => Promise; } @@ -73,6 +74,8 @@ export async function paginateSeatsAeroSearch({ delay: "30 seconds" as const, backoff: "constant" as const, }, + // Reduce risk of step-level timeouts; API responses are fast, + // but JSON parsing + DB upserts can be heavy. Keep ample time. timeout: "10 minutes" as const, }; @@ -90,9 +93,12 @@ export async function paginateSeatsAeroSearch({ start_date: params.searchStartDate, end_date: params.searchEndDate, include_trips: true, - take: 1000, + // Use smaller page size to cut payload and parsing time + take: 500, only_direct_flights: false, include_filtered: false, + // Ask API to reduce per-trip fields when supported + minify_trips: true, cursor: currentCursor, }); diff --git a/src/workers/workflows/process-seats-aero-search.ts b/src/workers/workflows/process-seats-aero-search.ts index 9a6e43b..e575374 100644 --- a/src/workers/workflows/process-seats-aero-search.ts +++ b/src/workers/workflows/process-seats-aero-search.ts @@ -95,6 +95,8 @@ class ProcessSeatsAeroSearchWorkflowBase extends WorkflowEntrypoint< const client = createSeatsAeroClient({ apiKey: this.env.SEATS_AERO_API_KEY, + // Use lighter response validation to avoid CPU-bound stalls + validationMode: "light", }); const { totalProcessed } = await paginateSeatsAeroSearch({