@molecule/api-flights 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +570 -0
  2. package/package.json +9 -8
package/README.md ADDED
@@ -0,0 +1,570 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:48:14.014Z
7
+ -->
8
+
9
+ # @molecule/api-flights
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ Provider-agnostic flights interface for molecule.dev.
16
+
17
+ Defines the {@link FlightsProvider} interface for flight search +
18
+ pricing. Bond packages (Amadeus, Duffel, Sabre, etc.) implement this
19
+ interface. Application code uses the convenience functions
20
+ (`searchFlights`, `getOffer`, `priceOffer`) which delegate to the bonded
21
+ provider.
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { setProvider, searchFlights, priceOffer } from '@molecule/api-flights'
27
+ import { provider as amadeus } from '@molecule/api-flights-amadeus'
28
+
29
+ setProvider(amadeus)
30
+ const offers = await searchFlights({
31
+ origin: 'JFK',
32
+ destination: 'LHR',
33
+ departureDate: '2026-07-15',
34
+ adults: 1,
35
+ })
36
+ const priced = await priceOffer(offers[0].id)
37
+ ```
38
+
39
+ ## Type
40
+
41
+ `core`
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ npm install @molecule/api-flights @molecule/api-bond @molecule/api-i18n
47
+ ```
48
+
49
+ ## API
50
+
51
+ ### Interfaces
52
+
53
+ #### `FlightOffer`
54
+
55
+ Normalized flight offer returned by
56
+ {@link FlightsProvider.searchFlights}.
57
+
58
+ ```typescript
59
+ interface FlightOffer {
60
+ /**
61
+ * Provider-specific opaque offer identifier.
62
+ */
63
+ id: OfferId
64
+
65
+ /**
66
+ * Total grand-total price for ALL passengers in {@link currency}.
67
+ */
68
+ price: number
69
+
70
+ /**
71
+ * Currency the {@link price} is expressed in (e.g. `'USD'`).
72
+ */
73
+ currency: CurrencyCode
74
+
75
+ /**
76
+ * Flight segments, in chronological order. For round-trip itineraries
77
+ * outbound segments precede return segments. For multi-city the order
78
+ * matches the requested itinerary.
79
+ */
80
+ segments: Segment[]
81
+
82
+ /**
83
+ * Total elapsed time across all segments (including layovers).
84
+ */
85
+ duration: IsoDuration
86
+ }
87
+ ```
88
+
89
+ #### `FlightOfferDetail`
90
+
91
+ Detailed flight offer returned by {@link FlightsProvider.getOffer}.
92
+
93
+ Extends {@link FlightOffer} with a per-passenger price breakdown and an
94
+ optional source-data envelope. Providers that do not naturally support a
95
+ "fetch single offer" call MAY return the same offer they previously
96
+ returned from `searchFlights` here (without a fresh upstream round-trip).
97
+
98
+ ```typescript
99
+ interface FlightOfferDetail extends FlightOffer {
100
+ /**
101
+ * Per-passenger price breakdown rows. `null` when the upstream does not
102
+ * supply the breakdown.
103
+ */
104
+ travelerPricings?: TravelerPricing[] | null
105
+ }
106
+ ```
107
+
108
+ #### `FlightsProvider`
109
+
110
+ Flights provider interface.
111
+
112
+ All flight providers (Amadeus, Duffel, Sabre, etc.) implement this
113
+ interface. The interface is deliberately minimal so providers with very
114
+ different upstream APIs can satisfy it identically. Providers that lack
115
+ a "fetch single offer" call typically implement {@link getOffer} by
116
+ round-tripping through their pricing endpoint and returning the priced
117
+ offer.
118
+
119
+ ```typescript
120
+ interface FlightsProvider {
121
+ /**
122
+ * Searches for flight offers matching the supplied itinerary.
123
+ *
124
+ * @param options - Search parameters (origin, destination, dates,
125
+ * passenger counts, cabin, etc.).
126
+ * @returns Array of normalized flight offers.
127
+ */
128
+ searchFlights(options: SearchFlightsOptions): Promise<FlightOffer[]>
129
+
130
+ /**
131
+ * Retrieves a previously-searched offer in detail.
132
+ *
133
+ * @param offerId - Opaque offer identifier returned by
134
+ * {@link searchFlights}.
135
+ * @returns The offer with per-passenger price breakdown when available.
136
+ */
137
+ getOffer(offerId: OfferId): Promise<FlightOfferDetail>
138
+
139
+ /**
140
+ * Confirms the up-to-the-minute price for a previously-searched offer.
141
+ *
142
+ * @param offerId - Opaque offer identifier returned by
143
+ * {@link searchFlights}.
144
+ * @returns Authoritative price snapshot.
145
+ */
146
+ priceOffer(offerId: OfferId): Promise<PricingResult>
147
+ }
148
+ ```
149
+
150
+ #### `PricingResult`
151
+
152
+ Result returned by {@link FlightsProvider.priceOffer}.
153
+
154
+ The `price` and `currency` reflect the upstream's authoritative price as
155
+ of the moment of the call — providers MAY return a different price than
156
+ the original {@link FlightOffer.price} if availability or fare rules
157
+ have shifted between search and pricing.
158
+
159
+ ```typescript
160
+ interface PricingResult {
161
+ /**
162
+ * Original opaque offer identifier the call was made for.
163
+ */
164
+ offerId: OfferId
165
+
166
+ /**
167
+ * Authoritative grand-total price as of the moment of pricing.
168
+ */
169
+ price: number
170
+
171
+ /**
172
+ * Currency the {@link price} is expressed in.
173
+ */
174
+ currency: CurrencyCode
175
+
176
+ /**
177
+ * Per-passenger price breakdown rows. `null` when the upstream does not
178
+ * supply the breakdown.
179
+ */
180
+ travelerPricings?: TravelerPricing[] | null
181
+
182
+ /**
183
+ * Timestamp the pricing snapshot was observed/published.
184
+ */
185
+ pricedAt: Date
186
+ }
187
+ ```
188
+
189
+ #### `SearchFlightsOptions`
190
+
191
+ Options accepted by {@link FlightsProvider.searchFlights}.
192
+
193
+ ```typescript
194
+ interface SearchFlightsOptions {
195
+ /**
196
+ * Origin IATA airport / city code (e.g. `'JFK'`, `'NYC'`).
197
+ */
198
+ origin: AirportCode
199
+
200
+ /**
201
+ * Destination IATA airport / city code.
202
+ */
203
+ destination: AirportCode
204
+
205
+ /**
206
+ * Outbound departure date (ISO 8601 calendar date, e.g. `'2026-07-15'`).
207
+ */
208
+ departureDate: IsoDate
209
+
210
+ /**
211
+ * Return date for round-trip searches. Omit for one-way.
212
+ */
213
+ returnDate?: IsoDate
214
+
215
+ /**
216
+ * Adult passenger count (>=12 years). Defaults to `1` when omitted.
217
+ */
218
+ adults?: number
219
+
220
+ /**
221
+ * Child passenger count (2-11 years). Defaults to `0` when omitted.
222
+ */
223
+ children?: number
224
+
225
+ /**
226
+ * Infant passenger count (<2 years, lap or seated). Defaults to `0`
227
+ * when omitted.
228
+ */
229
+ infants?: number
230
+
231
+ /**
232
+ * Cabin class to search for. Defaults to `'economy'` when omitted.
233
+ */
234
+ cabin?: CabinClass
235
+
236
+ /**
237
+ * Maximum number of offers to return. Implementations MAY clamp this
238
+ * to whatever upper bound their upstream API enforces.
239
+ */
240
+ maxResults?: number
241
+ }
242
+ ```
243
+
244
+ #### `Segment`
245
+
246
+ One leg of a {@link FlightOffer} — a single take-off / landing pair on a
247
+ single operated flight.
248
+
249
+ ```typescript
250
+ interface Segment {
251
+ /**
252
+ * Departure airport / instant.
253
+ */
254
+ departure: SegmentEndpoint
255
+
256
+ /**
257
+ * Arrival airport / instant.
258
+ */
259
+ arrival: SegmentEndpoint
260
+
261
+ /**
262
+ * Marketing carrier IATA code (e.g. `'AA'`, `'BA'`).
263
+ */
264
+ carrier: CarrierCode
265
+
266
+ /**
267
+ * Marketing flight number (e.g. `'100'`, `'1234'`).
268
+ *
269
+ * String rather than number to preserve any leading zeros and to
270
+ * accommodate alphanumeric carriers (e.g. EasyJet's `'EZY'` prefix).
271
+ */
272
+ flightNumber: string
273
+
274
+ /**
275
+ * IATA aircraft code (e.g. `'77W'`, `'320'`). `null` when the upstream
276
+ * does not supply aircraft data for this segment.
277
+ */
278
+ aircraft?: string | null
279
+
280
+ /**
281
+ * Block / total time the segment is in the air. `null` when the upstream
282
+ * does not supply per-segment duration.
283
+ */
284
+ duration?: IsoDuration | null
285
+ }
286
+ ```
287
+
288
+ #### `SegmentEndpoint`
289
+
290
+ Departure or arrival point on a {@link Segment}.
291
+
292
+ ```typescript
293
+ interface SegmentEndpoint {
294
+ /**
295
+ * IATA airport code (e.g. `'JFK'`).
296
+ */
297
+ airport: AirportCode
298
+
299
+ /**
300
+ * Local-time instant including timezone offset.
301
+ */
302
+ at: IsoDateTime
303
+
304
+ /**
305
+ * Terminal designator (e.g. `'4'`, `'2A'`). `null` when not supplied.
306
+ */
307
+ terminal?: string | null
308
+ }
309
+ ```
310
+
311
+ #### `TravelerPricing`
312
+
313
+ Per-passenger price row inside {@link FlightOfferDetail.travelerPricings}.
314
+
315
+ ```typescript
316
+ interface TravelerPricing {
317
+ /**
318
+ * Provider-specific traveler id (typically a 1-indexed string).
319
+ */
320
+ travelerId: string
321
+
322
+ /**
323
+ * Adult, child, or infant. The infant variant is split between
324
+ * lap-infants (`'held-infant'`) and seated infants (`'seated-infant'`)
325
+ * because they are priced differently by most carriers.
326
+ */
327
+ travelerType: 'adult' | 'child' | 'held-infant' | 'seated-infant'
328
+
329
+ /**
330
+ * Total price for this traveler in the offer's currency.
331
+ */
332
+ price: number
333
+
334
+ /**
335
+ * Cabin class booked for this traveler. `null` when mixed across
336
+ * segments or not supplied.
337
+ */
338
+ cabin?: CabinClass | null
339
+ }
340
+ ```
341
+
342
+ ### Types
343
+
344
+ #### `AirportCode`
345
+
346
+ IATA airport or city code (e.g. `'JFK'`, `'LAX'`, `'PAR'`).
347
+
348
+ Kept as a plain `string` alias rather than a string-literal union so
349
+ providers can support whatever set of airports / metropolitan codes their
350
+ upstream exposes.
351
+
352
+ ```typescript
353
+ type AirportCode = string
354
+ ```
355
+
356
+ #### `CabinClass`
357
+
358
+ Cabin class enumeration.
359
+
360
+ Providers map each value onto whatever upstream code they require
361
+ (e.g. Amadeus uses `'ECONOMY' | 'PREMIUM_ECONOMY' | 'BUSINESS' | 'FIRST'`).
362
+
363
+ ```typescript
364
+ type CabinClass = 'economy' | 'premium-economy' | 'business' | 'first'
365
+ ```
366
+
367
+ #### `CarrierCode`
368
+
369
+ IATA two-character airline / marketing carrier code (e.g. `'AA'`,
370
+ `'BA'`, `'AF'`).
371
+
372
+ ```typescript
373
+ type CarrierCode = string
374
+ ```
375
+
376
+ #### `CurrencyCode`
377
+
378
+ ISO 4217 three-letter currency code (e.g. `'USD'`, `'EUR'`, `'JPY'`).
379
+
380
+ Conventionally upper-case to match {@link FlightOffer.currency} and the
381
+ vast majority of upstream travel APIs.
382
+
383
+ ```typescript
384
+ type CurrencyCode = string
385
+ ```
386
+
387
+ #### `IsoDate`
388
+
389
+ ISO 8601 calendar date (e.g. `'2026-07-15'`). Time-of-day MUST NOT be
390
+ included; this is a date for searching availability, not a timestamp.
391
+
392
+ ```typescript
393
+ type IsoDate = string
394
+ ```
395
+
396
+ #### `IsoDateTime`
397
+
398
+ ISO 8601 instant including timezone offset
399
+ (e.g. `'2026-07-15T08:30:00+02:00'`).
400
+
401
+ Providers SHOULD preserve the upstream offset where available; consumers
402
+ that want a `Date` can `new Date(value)` it.
403
+
404
+ ```typescript
405
+ type IsoDateTime = string
406
+ ```
407
+
408
+ #### `IsoDuration`
409
+
410
+ ISO 8601 duration string (e.g. `'PT2H30M'`, `'PT11H45M'`).
411
+
412
+ Plain string for the same reason as {@link IsoDate} — providers differ on
413
+ whether they expose seconds-precision, fractional minutes, etc.
414
+
415
+ ```typescript
416
+ type IsoDuration = string
417
+ ```
418
+
419
+ #### `OfferId`
420
+
421
+ Provider-specific offer identifier.
422
+
423
+ Opaque token returned by {@link FlightsProvider.searchFlights} that can
424
+ be passed back to {@link FlightsProvider.getOffer} or
425
+ {@link FlightsProvider.priceOffer}. Identifier scheme is provider-defined
426
+ and MUST NOT be parsed by consumers.
427
+
428
+ ```typescript
429
+ type OfferId = string
430
+ ```
431
+
432
+ ### Functions
433
+
434
+ #### `getOffer(offerId)`
435
+
436
+ Retrieves a previously-searched offer in detail.
437
+
438
+ ```typescript
439
+ function getOffer(offerId: string): Promise<FlightOfferDetail>
440
+ ```
441
+
442
+ - `offerId` — Opaque offer identifier returned by `searchFlights`.
443
+
444
+ **Returns:** The offer with per-passenger price breakdown when available.
445
+
446
+ #### `getProvider()`
447
+
448
+ Retrieves the bonded flights provider, throwing if none is configured.
449
+
450
+ ```typescript
451
+ function getProvider(): FlightsProvider
452
+ ```
453
+
454
+ **Returns:** The bonded flights provider.
455
+
456
+ #### `hasProvider()`
457
+
458
+ Checks whether a flights provider is currently bonded.
459
+
460
+ ```typescript
461
+ function hasProvider(): boolean
462
+ ```
463
+
464
+ **Returns:** `true` if a flights provider is bonded.
465
+
466
+ #### `priceOffer(offerId)`
467
+
468
+ Confirms the up-to-the-minute price for a previously-searched offer.
469
+
470
+ ```typescript
471
+ function priceOffer(offerId: string): Promise<PricingResult>
472
+ ```
473
+
474
+ - `offerId` — Opaque offer identifier returned by `searchFlights`.
475
+
476
+ **Returns:** Authoritative price snapshot.
477
+
478
+ #### `searchFlights(options)`
479
+
480
+ Searches for flight offers matching the supplied itinerary.
481
+
482
+ ```typescript
483
+ function searchFlights(options: SearchFlightsOptions): Promise<FlightOffer[]>
484
+ ```
485
+
486
+ - `options` — Search parameters (origin, destination, dates, passenger counts, cabin, etc.).
487
+
488
+ **Returns:** Array of normalized flight offers.
489
+
490
+ #### `setProvider(provider)`
491
+
492
+ Registers a flights provider as the active singleton. Called by bond
493
+ packages (e.g. `@molecule/api-flights-amadeus`) during application
494
+ startup.
495
+
496
+ ```typescript
497
+ function setProvider(provider: FlightsProvider): void
498
+ ```
499
+
500
+ - `provider` — The flights provider implementation to bond.
501
+
502
+ ## Available Providers
503
+
504
+ | Provider | Package |
505
+ | -------- | ------------------------------- |
506
+ | Amadeus | `@molecule/api-flights-amadeus` |
507
+
508
+ ## Injection Notes
509
+
510
+ ### Requirements
511
+
512
+ Peer dependencies:
513
+
514
+ - `@molecule/api-bond` ^1.0.1
515
+ - `@molecule/api-i18n` ^1.0.1
516
+
517
+ ### Runtime Dependencies
518
+
519
+ - `@molecule/api-bond`
520
+ - `@molecule/api-i18n`
521
+
522
+ - **Offer ids are opaque, provider-scoped, and short-lived.** Never parse
523
+ them and never persist them as durable references — a stale id fails
524
+ `getOffer`/`priceOffer`; re-run the search instead of replaying old ids.
525
+ - **Re-price before you commit.** `PricingResult.price` is the
526
+ authoritative total at pricing time and MAY differ from the search-time
527
+ `FlightOffer.price` — call `priceOffer()` right before the checkout/
528
+ summary step and display the re-priced figure.
529
+ - **This contract is search + pricing only — there is no booking method.**
530
+ Don't invent a `bookFlight()`; booking needs vendor-specific work outside
531
+ this interface.
532
+ - **Server-side only; cache searches.** Flight-search APIs are slow,
533
+ rate-limited, and often billed per call — cache results per (origin,
534
+ destination, dates, passengers) for a short TTL and never fire a search
535
+ per keystroke.
536
+ - Airports/cities are IATA codes (`'JFK'`, `'NYC'`), dates are ISO calendar
537
+ dates (`'2026-07-15'`), and prices carry an explicit ISO 4217 `currency` —
538
+ format with it, never a hardcoded `$`.
539
+
540
+ ## E2E Tests
541
+
542
+ Integration checklist — drive the real UI (live preview, no mocks), adapt
543
+ each item to this app's actual search / results screens, and check every box
544
+ off one by one. A box you can't check is an integration bug to fix — not a
545
+ skip:
546
+
547
+ - [ ] Searching a real route + future date (e.g. JFK→LHR, a date weeks out)
548
+ returns REAL `searchFlights` offers rendered in the results list — each
549
+ showing an origin, a destination, departure/arrival times, and a price. No
550
+ empty list, `null`, or placeholder row is presented as a successful search.
551
+ - [ ] Results match the query: each offer's first `segment.departure.airport`
552
+ and last `segment.arrival.airport` are the searched cities and the
553
+ `departure.at` falls on the requested date — not random routes or dates.
554
+ - [ ] Every price shows its `currency` (ISO 4217, formatted with it — never a
555
+ hardcoded `$`) and is sane (positive, right order of magnitude). If a
556
+ checkout/summary step re-prices via `priceOffer`/`getOffer`, the displayed
557
+ total is the re-priced figure, not the stale search-time price.
558
+ - [ ] Any exposed filter/sort (price, stops, departure/arrival time, cabin)
559
+ actually narrows or reorders the SAME result set — not a fresh random list.
560
+ - [ ] An impossible or invalid search — no availability, a past date, or a
561
+ bad airport code — shows a clear "no flights" / "invalid" state, not a
562
+ crash and not a blank list presented as a successful search.
563
+ - [ ] A provider outage or rate-limit (HTTP 429) surfaces as a graceful,
564
+ retryable error in the UI — not a hung spinner or a silent empty list.
565
+ - [ ] This contract is search + pricing ONLY (there is no `bookFlight`). If
566
+ the app adds a book/hold step it goes OUT-OF-BAND to the vendor — verify
567
+ the app records the SELECTED offer (its priced total + itinerary), since
568
+ opaque `OfferId`s are short-lived and can't be replayed later.
569
+ - [ ] The provider API key stays server-side: searches run through the API
570
+ bond and no key or upstream credential is ever exposed to the browser.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-flights",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Flight search + offer pricing core interface for molecule.dev",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,8 @@
17
17
  }
18
18
  },
19
19
  "files": [
20
- "dist"
20
+ "dist",
21
+ "README.md"
21
22
  ],
22
23
  "keywords": [
23
24
  "molecule",
@@ -28,22 +29,22 @@
28
29
  ],
29
30
  "license": "Apache-2.0",
30
31
  "peerDependencies": {
31
- "@molecule/api-bond": "^1.0.0",
32
- "@molecule/api-i18n": "^1.0.0"
32
+ "@molecule/api-bond": "^1.0.1",
33
+ "@molecule/api-i18n": "^1.0.1"
33
34
  },
34
35
  "devDependencies": {
35
- "@molecule/api-bond": "1.0.0",
36
- "@molecule/api-i18n": "1.0.0",
36
+ "@molecule/api-bond": "1.0.2",
37
+ "@molecule/api-i18n": "1.0.3",
37
38
  "@types/node": "26.1.2",
38
39
  "typescript": "6.0.3",
39
- "vitest": "4.1.10"
40
+ "vitest": "4.1.11"
40
41
  },
41
42
  "repository": {
42
43
  "type": "git",
43
44
  "url": "https://github.com/molecule-dev/molecule.git",
44
45
  "directory": "packages/api/core/flights"
45
46
  },
46
- "homepage": "https://github.com/molecule-dev/molecule/tree/main/packages/api/core/flights",
47
+ "homepage": "https://www.molecule.dev/packages/api-flights",
47
48
  "bugs": "https://github.com/molecule-dev/molecule/issues",
48
49
  "publishConfig": {
49
50
  "access": "public"