@curless/hotel-storefront 0.48.1 → 0.49.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.
@@ -1 +1 @@
1
- {"version":3,"file":"card.generated.js","sourceRoot":"","sources":["../src/card.generated.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wEAAwE;AACxE,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAW,MAAM,CAAC,IAAI,CAChD,sl1jCAAsl1jC,EACtl1jC,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEnB,6GAA6G;AAC7G,MAAM,CAAC,MAAM,qBAAqB,GAChC,kEAAkE,CAAC;AAErE,0GAA0G;AAC1G,MAAM,CAAC,MAAM,qBAAqB,GAChC,iLAAiL,CAAC"}
1
+ {"version":3,"file":"card.generated.js","sourceRoot":"","sources":["../src/card.generated.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wEAAwE;AACxE,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAW,MAAM,CAAC,IAAI,CAChD,sxvkCAAsxvkC,EACtxvkC,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEnB,6GAA6G;AAC7G,MAAM,CAAC,MAAM,qBAAqB,GAChC,kEAAkE,CAAC;AAErE,0GAA0G;AAC1G,MAAM,CAAC,MAAM,qBAAqB,GAChC,iLAAiL,CAAC"}
@@ -216,9 +216,16 @@ export type DemoHotelsOptions = {
216
216
  * code AND the email it was made with is added, so it can then change or
217
217
  * cancel it with the code alone - the card's buttons send nothing more. Found
218
218
  * that way it had "No booking here matches that code" on its own Cancel.
219
+ *
220
+ * ⚠️ `askedAs` IS NEVER ANSWERED EITHER. It is what the booking was asked for
221
+ * as - the stay's rooms and nights and a digest of the guest (`askedAsKey`) -
222
+ * so the same stay asked for again within minutes is found and not booked
223
+ * twice (2026-10-27 audit, F15). It holds nobody's name, and a booking whose
224
+ * stay is changed loses it: it is no longer what was asked for.
219
225
  */
220
226
  type StoredBooking = Booking & {
221
227
  owners: Set<string>;
228
+ askedAs?: string;
222
229
  };
223
230
  /**
224
231
  * Live holds one conversation may have at once. A demo's inventory is shared:
@@ -1 +1 @@
1
- {"version":3,"file":"demo-hotels.d.ts","sourceRoot":"","sources":["../src/demo-hotels.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"demo-hotels.d.ts","sourceRoot":"","sources":["../src/demo-hotels.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { FACILITIES, hasFacility } from './facilities.js';
5
5
  import { addMoney, formatMoney, minorFactor, money, multiplyMoney, taxOn, toMinor, wholeUnits, } from './money.js';
6
6
  import { namedExactly, roomNamesOf } from './names.js';
7
7
  import { bedKindsOf } from './room-filters.js';
8
+ import { SAME_BOOKING_MS, guestDigest, singleFlight } from './same-booking.js';
8
9
  import { MAX_EXTRA_QUANTITY, MAX_ROOMS_PER_LINE } from './trip.js';
9
10
  export const demoError = (code, message) => Object.assign(new Error(message), { code });
10
11
  const POLICY_TITLES = {
@@ -103,8 +104,9 @@ const wholeBooking = (b) => {
103
104
  isMoney(x.quote?.total));
104
105
  };
105
106
  const publicBooking = (b) => {
106
- const { owners, ...rest } = b;
107
+ const { owners, askedAs, ...rest } = b;
107
108
  void owners;
109
+ void askedAs;
108
110
  return { ...rest };
109
111
  };
110
112
  /**
@@ -215,6 +217,8 @@ export const createDemoHotels = (opts) => {
215
217
  const rng = opts.rng ?? (() => randomInt(0, 2 ** 32) / 2 ** 32);
216
218
  const holdMs = opts.holdMs ?? HOLD_MS_DEFAULT;
217
219
  const bookings = new Map();
220
+ /** One booking request at a time per conversation and per email - see `singleFlight`. */
221
+ const single = singleFlight();
218
222
  /** Saved room preferences, by membership number - see `preferencesFor`. */
219
223
  const preferences = new Map();
220
224
  const maxHeldRoomNights = opts.maxHeldRoomNights ?? MAX_HELD_ROOM_NIGHTS;
@@ -647,6 +651,12 @@ export const createDemoHotels = (opts) => {
647
651
  }
648
652
  return there;
649
653
  };
654
+ /**
655
+ * The conversation a booking is asked for in: the trip's owner when the card
656
+ * asks on a connection of its own, else the caller - what the same stay asked
657
+ * twice is matched by (a booking made from the card is owned by both).
658
+ */
659
+ const conversationOf = (who) => who.tripOwner ?? who.sessionId;
650
660
  /** Over and done with - nothing held, nothing left to change. */
651
661
  const ended = (b) => (b.status !== 'requested' && b.status !== 'confirmed') || b.stay.checkOut < today(b.hotelId);
652
662
  /** Room for one more, by forgetting the oldest booking that is over. */
@@ -675,6 +685,24 @@ export const createDemoHotels = (opts) => {
675
685
  // reading: the first.
676
686
  return part.length > 1 && part.some((r) => r.rate) ? undefined : part[0];
677
687
  };
688
+ /**
689
+ * What one stay is asked for as: its lines - hotel, room, nights, party,
690
+ * rooms, hours; an add-on's id, quantity and day - and a digest of the guest
691
+ * (`guestDigest`). The hotel and the room are read as the file names them, so
692
+ * a retry that says "Kerry Hotel" for `hong-kong-kerry` is the same asking.
693
+ * The email is not in it: a booking carries its own, and it is matched there.
694
+ */
695
+ const askedAsKey = (lines, digest) => `${lines
696
+ .map((l) => {
697
+ const h = byId(l.hotelId);
698
+ const hotel = h?.id ?? l.hotelId;
699
+ if (l.kind !== 'room')
700
+ return `extra|${hotel}|${l.extraId}|${l.quantity}|${l.day ?? ''}`;
701
+ const room = h ? (roomOf(h, l.roomId)?.id ?? l.roomId) : l.roomId;
702
+ return `room|${hotel}|${room}|${l.stay.checkIn}|${l.stay.checkOut}|${l.stay.guests}|${l.rooms ?? 1}|${l.hourly?.start ?? ''}|${l.hourly?.hours ?? ''}`;
703
+ })
704
+ .sort()
705
+ .join(';')}#${digest}`;
678
706
  const quoteTrip = async (trip, who, ignore) => {
679
707
  sweep();
680
708
  const lines = [];
@@ -1743,85 +1771,142 @@ export const createDemoHotels = (opts) => {
1743
1771
  .map((l) => `${(byId(l.hotelId)?.extras ?? []).find((e) => e.id === l.extraId)?.name ?? l.extraId} at ${byId(l.hotelId)?.name ?? l.hotelId} goes with a stay there: add a room night at that hotel, or put it on a booking already made there.`)
1744
1772
  .join(' ')} The trip is as it was - take that out or change it, then ask again.`);
1745
1773
  }
1746
- // ⚠️ EACH STAY ON ITS OWN, AND WHAT WAS BOOKED IS KEPT (robin,
1747
- // 2026-10-14 - see the seam). A stay that cannot be booked is said, with
1748
- // why, and stays in the trip; the others are booked. It used to be all or
1749
- // none, after a Beijing suite that went while the guest typed their name
1750
- // took the trip's other half with it, unsaid: nothing goes unsaid here.
1751
- const failed = [];
1752
- const failedFor = (g, quote, why) => {
1753
- const room = g.find((l) => l.kind === 'room');
1754
- const h = room ? byId(room.hotelId) : undefined;
1755
- const first = quote?.dropped?.[0];
1756
- return {
1757
- hotelId: room?.hotelId ?? '',
1758
- hotelName: h?.name ?? room?.hotelId ?? '',
1759
- ...(h?.names?.name ? { hotelNames: h.names.name } : {}),
1760
- stay: room?.stay ?? { checkIn: '', checkOut: '', guests: 0 },
1761
- ...(room && h
1762
- ? { roomName: h.rooms.find((r) => r.id === room.roomId)?.name ?? room.roomId }
1763
- : {}),
1764
- why: why ?? first?.why ?? 'It could not be booked.',
1765
- code: why ? 'unavailable' : (first?.code ?? 'unavailable'),
1766
- ...(first?.args && !why ? { args: first.args } : {}),
1767
- };
1768
- };
1769
- const priced = await Promise.all(groups.map(async (g) => ({ lines: g, quote: await quoteTrip({ lines: g }, who) })));
1770
- const bookable = priced.filter(({ lines, quote }) => {
1771
- const ok = !quote.dropped?.length && quote.lines.some((l) => l.kind === 'room');
1772
- if (!ok)
1773
- failed.push(failedFor(lines, quote));
1774
- return ok;
1775
- });
1776
- // The limits are the whole trip's, and count what would be held.
1777
- const live = [...bookings.values()].filter((b) => b.owners.has(who.sessionId) && b.status === 'requested').length;
1778
- if (live + bookable.length > MAX_HOLDS_PER_CONVERSATION) {
1779
- throw demoError('too-many-holds', `Nothing was booked. This conversation already holds ${live} booking${live === 1 ? '' : 's'}, and ${MAX_HOLDS_PER_CONVERSATION} at once is the most - cancel one that is not needed, then ask again.`);
1780
- }
1781
- const over = overHolding(bookable.map((b) => b.quote), who, guest.email, 'booked');
1782
- if (over)
1783
- throw over;
1784
- const t = now();
1785
- const made = [];
1786
- makeRoom(bookable.length);
1787
- // One at a time, in date order: each is held as it is made, so a later
1788
- // stay of this same trip sees the rooms an earlier one took.
1789
- for (const { lines: g } of bookable) {
1790
- const quote = await quoteTrip({ lines: g }, who);
1791
- const [firstRoom] = quote.lines.filter((l) => l.kind === 'room');
1792
- const h = firstRoom ? byId(firstRoom.hotelId) : undefined;
1793
- if (!firstRoom || !h || !quote.stay || quote.dropped?.length) {
1794
- failed.push(failedFor(g, quote));
1795
- continue;
1774
+ // ⚠️ ONE REQUEST AT A TIME PER CONVERSATION AND PER EMAIL (2026-10-27
1775
+ // audit, F15). Pricing is awaited between asking "was this booked
1776
+ // already?" and booking, so two identical requests - a model retrying, two
1777
+ // cards submitting at once - both found nothing and each took the rooms
1778
+ // and counted against the limits. The second waits for the first and
1779
+ // finds its bookings below. The caller and, from the card, the
1780
+ // conversation whose trip it is; the email too, as Huazhu's does.
1781
+ const email = guest.email.trim().toLowerCase();
1782
+ const conversation = conversationOf(who);
1783
+ const locks = [...new Set([who.sessionId, conversation])].map((k) => `s:${k}`);
1784
+ return single([...locks, `e:${email}`], async () => {
1785
+ // ⚠️ THE SAME STAY ASKED FOR AGAIN IS ANSWERED WITH THE FIRST. A stay is
1786
+ // the same when its rooms, nights, party, hours and add-ons are, and the
1787
+ // guest is - name, phone, requests and arrival, NOT the note (free text,
1788
+ // reworded by every retry) - for the same email in the same conversation,
1789
+ // within SAME_BOOKING_MS, and the booking is still live (a cancelled one
1790
+ // is not what was asked for any more). Another NAME on the same email is
1791
+ // another booking: the same room for a friend (robin, 2026-10-23).
1792
+ // Stay by stay, so a trip whose one stay failed is asked again for that
1793
+ // one only, and the ones booked are not booked twice.
1794
+ const digest = guestDigest(guest, requests);
1795
+ const asked = groups.map((lines) => ({ lines, key: askedAsKey(lines, digest) }));
1796
+ const reused = [];
1797
+ const fresh = [];
1798
+ const at = now();
1799
+ for (const ask of asked) {
1800
+ const earlier = [...bookings.values()].find((b) => b.askedAs === ask.key &&
1801
+ !reused.includes(b) &&
1802
+ b.owners.has(conversation) &&
1803
+ b.guest.email.trim().toLowerCase() === email &&
1804
+ at - Date.parse(b.createdAt) < SAME_BOOKING_MS &&
1805
+ !ended(b));
1806
+ if (earlier)
1807
+ reused.push(earlier);
1808
+ else
1809
+ fresh.push(ask);
1796
1810
  }
1797
- const arrival = arrivalInstant(quote.stay.checkIn, h.checkInFrom ?? '15:00', offsetOf(h));
1798
- const holdUntil = new Date(Math.max(t + 3_600_000, Math.min(t + holdMs, arrival - HOLD_STOPS_BEFORE_ARRIVAL_MS))).toISOString();
1799
- const booking = {
1800
- id: `bk_${randomUUID()}`,
1801
- confirmationCode: confirmationCode(new Set(made.map((m) => m.confirmationCode))),
1802
- status: 'requested',
1803
- createdAt: new Date(t).toISOString(),
1804
- holdUntil,
1805
- // On the hotel's clock, as the free-cancellation deadline is (2026-10-05 audit, P2).
1806
- holdUntilLocal: atHotelTime(Date.parse(holdUntil), offsetOf(h)),
1807
- hotelId: h.id,
1808
- hotelName: h.name,
1809
- ...(h.names?.name ? { hotelNames: h.names.name } : {}),
1810
- stay: quote.stay,
1811
- guest,
1812
- quote,
1813
- // The caller, and the conversation whose trip it was - see `Identity.tripOwner`.
1814
- owners: new Set([who.sessionId, ...(who.tripOwner ? [who.tripOwner] : [])]),
1815
- ...(cleanRequests(requests) ? { requests: cleanRequests(requests) } : {}),
1811
+ // ⚠️ SAID AS THE SAME BOOKING, never as a new one (F15): `repeat`.
1812
+ if (reused.length && !fresh.length) {
1813
+ return { booked: reused.map(publicBooking), failed: [], repeat: true };
1814
+ }
1815
+ // ⚠️ EACH STAY ON ITS OWN, AND WHAT WAS BOOKED IS KEPT (robin,
1816
+ // 2026-10-14 - see the seam). A stay that cannot be booked is said, with
1817
+ // why, and stays in the trip; the others are booked. It used to be all or
1818
+ // none, after a Beijing suite that went while the guest typed their name
1819
+ // took the trip's other half with it, unsaid: nothing goes unsaid here.
1820
+ const failed = [];
1821
+ const failedFor = (g, quote, why) => {
1822
+ const room = g.find((l) => l.kind === 'room');
1823
+ const h = room ? byId(room.hotelId) : undefined;
1824
+ const first = quote?.dropped?.[0];
1825
+ return {
1826
+ hotelId: room?.hotelId ?? '',
1827
+ hotelName: h?.name ?? room?.hotelId ?? '',
1828
+ ...(h?.names?.name ? { hotelNames: h.names.name } : {}),
1829
+ stay: room?.stay ?? { checkIn: '', checkOut: '', guests: 0 },
1830
+ ...(room && h
1831
+ ? { roomName: h.rooms.find((r) => r.id === room.roomId)?.name ?? room.roomId }
1832
+ : {}),
1833
+ why: why ?? first?.why ?? 'It could not be booked.',
1834
+ code: why ? 'unavailable' : (first?.code ?? 'unavailable'),
1835
+ ...(first?.args && !why ? { args: first.args } : {}),
1836
+ };
1816
1837
  };
1817
- bookings.set(booking.id, booking);
1818
- made.push(booking);
1819
- }
1820
- if (made.length)
1821
- changed();
1822
- // In the trip's own order, as the guest reads it.
1823
- failed.sort((a, b) => a.stay.checkIn.localeCompare(b.stay.checkIn));
1824
- return { booked: made.map(publicBooking), failed };
1838
+ const priced = await Promise.all(fresh.map(async ({ lines: g, key }) => ({
1839
+ lines: g,
1840
+ key,
1841
+ quote: await quoteTrip({ lines: g }, who),
1842
+ })));
1843
+ const bookable = priced.filter(({ lines, quote }) => {
1844
+ const ok = !quote.dropped?.length && quote.lines.some((l) => l.kind === 'room');
1845
+ if (!ok)
1846
+ failed.push(failedFor(lines, quote));
1847
+ return ok;
1848
+ });
1849
+ // The limits are the whole trip's, and count what would be held.
1850
+ const live = [...bookings.values()].filter((b) => b.owners.has(who.sessionId) && b.status === 'requested').length;
1851
+ if (live + bookable.length > MAX_HOLDS_PER_CONVERSATION) {
1852
+ throw demoError('too-many-holds', `Nothing was booked. This conversation already holds ${live} booking${live === 1 ? '' : 's'}, and ${MAX_HOLDS_PER_CONVERSATION} at once is the most - cancel one that is not needed, then ask again.`);
1853
+ }
1854
+ const over = overHolding(bookable.map((b) => b.quote), who, guest.email, 'booked');
1855
+ if (over)
1856
+ throw over;
1857
+ const t = now();
1858
+ const made = [];
1859
+ makeRoom(bookable.length);
1860
+ // One at a time, in date order: each is held as it is made, so a later
1861
+ // stay of this same trip sees the rooms an earlier one took.
1862
+ for (const { lines: g, key } of bookable) {
1863
+ const quote = await quoteTrip({ lines: g }, who);
1864
+ const [firstRoom] = quote.lines.filter((l) => l.kind === 'room');
1865
+ const h = firstRoom ? byId(firstRoom.hotelId) : undefined;
1866
+ if (!firstRoom || !h || !quote.stay || quote.dropped?.length) {
1867
+ failed.push(failedFor(g, quote));
1868
+ continue;
1869
+ }
1870
+ const arrival = arrivalInstant(quote.stay.checkIn, h.checkInFrom ?? '15:00', offsetOf(h));
1871
+ const holdUntil = new Date(Math.max(t + 3_600_000, Math.min(t + holdMs, arrival - HOLD_STOPS_BEFORE_ARRIVAL_MS))).toISOString();
1872
+ const booking = {
1873
+ id: `bk_${randomUUID()}`,
1874
+ confirmationCode: confirmationCode(new Set(made.map((m) => m.confirmationCode))),
1875
+ status: 'requested',
1876
+ createdAt: new Date(t).toISOString(),
1877
+ holdUntil,
1878
+ // On the hotel's clock, as the free-cancellation deadline is (2026-10-05 audit, P2).
1879
+ holdUntilLocal: atHotelTime(Date.parse(holdUntil), offsetOf(h)),
1880
+ hotelId: h.id,
1881
+ hotelName: h.name,
1882
+ ...(h.names?.name ? { hotelNames: h.names.name } : {}),
1883
+ stay: quote.stay,
1884
+ guest,
1885
+ quote,
1886
+ // The caller, and the conversation whose trip it was - see `Identity.tripOwner`.
1887
+ owners: new Set([who.sessionId, ...(who.tripOwner ? [who.tripOwner] : [])]),
1888
+ ...(cleanRequests(requests) ? { requests: cleanRequests(requests) } : {}),
1889
+ askedAs: key,
1890
+ };
1891
+ bookings.set(booking.id, booking);
1892
+ made.push(booking);
1893
+ }
1894
+ if (made.length)
1895
+ changed();
1896
+ // In the trip's own order, as the guest reads it.
1897
+ failed.sort((a, b) => a.stay.checkIn.localeCompare(b.stay.checkIn));
1898
+ // Stays asked for before are in the answer beside the ones made now. When
1899
+ // none was made now - the rest could not be - it is the first asking again.
1900
+ if (reused.length) {
1901
+ const all = [...reused, ...made].sort((x, y) => x.stay.checkIn.localeCompare(y.stay.checkIn));
1902
+ return {
1903
+ booked: all.map(publicBooking),
1904
+ failed,
1905
+ ...(made.length ? {} : { repeat: true }),
1906
+ };
1907
+ }
1908
+ return { booked: made.map(publicBooking), failed };
1909
+ });
1825
1910
  },
1826
1911
  async listBookings(who, limit = 10) {
1827
1912
  sweep();
@@ -1863,8 +1948,11 @@ export const createDemoHotels = (opts) => {
1863
1948
  const { preview, quote } = await planChange(b, change, who);
1864
1949
  if (!preview.changeable || !preview.to || !quote)
1865
1950
  throw demoError('unavailable', preview.rule);
1951
+ // No longer what was asked for - see `StoredBooking.askedAs`.
1952
+ const { askedAs, ...changing } = b;
1953
+ void askedAs;
1866
1954
  const updated = {
1867
- ...b,
1955
+ ...changing,
1868
1956
  stay: preview.to.stay,
1869
1957
  quote,
1870
1958
  changedAt: new Date(now()).toISOString(),