@curless/hotel-storefront 0.25.0 → 0.26.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.
Files changed (73) hide show
  1. package/dist/__tests__/__helpers__/sessions.d.ts +10 -0
  2. package/dist/__tests__/__helpers__/sessions.d.ts.map +1 -0
  3. package/dist/__tests__/__helpers__/sessions.js +37 -0
  4. package/dist/__tests__/__helpers__/sessions.js.map +1 -0
  5. package/dist/__tests__/card-contract.test.js +20 -0
  6. package/dist/__tests__/card-contract.test.js.map +1 -1
  7. package/dist/__tests__/data.test.js +7 -1
  8. package/dist/__tests__/data.test.js.map +1 -1
  9. package/dist/__tests__/dates.test.js +13 -1
  10. package/dist/__tests__/dates.test.js.map +1 -1
  11. package/dist/__tests__/huazhu-audit.test.js +7 -1
  12. package/dist/__tests__/huazhu-audit.test.js.map +1 -1
  13. package/dist/__tests__/huazhu-sandbox.test.js +6 -1
  14. package/dist/__tests__/huazhu-sandbox.test.js.map +1 -1
  15. package/dist/__tests__/server.test.js +111 -2
  16. package/dist/__tests__/server.test.js.map +1 -1
  17. package/dist/__tests__/state.test.d.ts +2 -0
  18. package/dist/__tests__/state.test.d.ts.map +1 -0
  19. package/dist/__tests__/state.test.js +204 -0
  20. package/dist/__tests__/state.test.js.map +1 -0
  21. package/dist/__tests__/tools.test.js +18 -0
  22. package/dist/__tests__/tools.test.js.map +1 -1
  23. package/dist/card.generated.d.ts +1 -1
  24. package/dist/card.generated.js +2 -2
  25. package/dist/card.generated.js.map +1 -1
  26. package/dist/dates.d.ts +9 -0
  27. package/dist/dates.d.ts.map +1 -1
  28. package/dist/dates.js +32 -0
  29. package/dist/dates.js.map +1 -1
  30. package/dist/demo-hotels.d.ts +21 -1
  31. package/dist/demo-hotels.d.ts.map +1 -1
  32. package/dist/demo-hotels.js +83 -11
  33. package/dist/demo-hotels.js.map +1 -1
  34. package/dist/index.js +9 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/legal.d.ts +8 -1
  37. package/dist/legal.d.ts.map +1 -1
  38. package/dist/legal.js +22 -8
  39. package/dist/legal.js.map +1 -1
  40. package/dist/members.d.ts +3 -1
  41. package/dist/members.d.ts.map +1 -1
  42. package/dist/members.js +24 -6
  43. package/dist/members.js.map +1 -1
  44. package/dist/money.d.ts.map +1 -1
  45. package/dist/money.js +8 -1
  46. package/dist/money.js.map +1 -1
  47. package/dist/server.d.ts +12 -8
  48. package/dist/server.d.ts.map +1 -1
  49. package/dist/server.js +101 -36
  50. package/dist/server.js.map +1 -1
  51. package/dist/sessions.d.ts +19 -0
  52. package/dist/sessions.d.ts.map +1 -0
  53. package/dist/sessions.js +96 -0
  54. package/dist/sessions.js.map +1 -0
  55. package/dist/state.d.ts +32 -0
  56. package/dist/state.d.ts.map +1 -0
  57. package/dist/state.js +118 -0
  58. package/dist/state.js.map +1 -0
  59. package/dist/tools.d.ts +11 -0
  60. package/dist/tools.d.ts.map +1 -1
  61. package/dist/tools.js +34 -3
  62. package/dist/tools.js.map +1 -1
  63. package/dist/trip-ids.d.ts +8 -1
  64. package/dist/trip-ids.d.ts.map +1 -1
  65. package/dist/trip-ids.js +24 -1
  66. package/dist/trip-ids.js.map +1 -1
  67. package/dist/trip.d.ts +3 -1
  68. package/dist/trip.d.ts.map +1 -1
  69. package/dist/trip.js +23 -6
  70. package/dist/trip.js.map +1 -1
  71. package/dist/types.d.ts +2 -0
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +1 -1
package/dist/state.js ADDED
@@ -0,0 +1,118 @@
1
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ // What survives a restart: one JSON file per store, in the deployment's state
4
+ // directory (`HOTEL_STATE_DIR`).
5
+ //
6
+ // ⚠️ A DEPLOY USED TO FORGET EVERY GUEST. Bookings, trips, sign-ins and the
7
+ // session ids themselves were in memory, and this connector ships several times
8
+ // a day: a guest told "held until 18:00, SL-7K2M4P" asked to cancel after a
9
+ // deploy and was told no booking matched - and the lookup with their email was
10
+ // then counted as a wrong guess (2026-10-05 audit). Now every store writes
11
+ // itself here, and reads itself back on boot.
12
+ //
13
+ // ⚠️ NO DIRECTORY, NO DISK. Without one the stores stay in memory exactly as
14
+ // before, so a local run or a test writes nothing anywhere. And the directory
15
+ // is never the brand's repository: that is mounted read-only on the box.
16
+ //
17
+ // Writes are coalesced - a trip id touched on every tool call would otherwise
18
+ // rewrite its file on every tool call - so the last half second before a crash
19
+ // can be lost. A clean stop (`flush`, on SIGTERM) loses nothing.
20
+ const WRITE_DELAY_MS = 500;
21
+ export class StateFile {
22
+ path;
23
+ timer;
24
+ snapshot;
25
+ constructor(path) {
26
+ this.path = path;
27
+ }
28
+ /**
29
+ * What was written last time, or undefined when there is nothing usable.
30
+ *
31
+ * ⚠️ A FILE THAT DOES NOT PARSE IS SET ASIDE, NOT OVERWRITTEN. Starting
32
+ * empty and writing over it would destroy the only copy of somebody's
33
+ * booking; renamed, a person can still look at it.
34
+ */
35
+ read() {
36
+ let text;
37
+ try {
38
+ text = readFileSync(this.path, 'utf8');
39
+ }
40
+ catch {
41
+ return undefined; // none yet
42
+ }
43
+ try {
44
+ return JSON.parse(text);
45
+ }
46
+ catch (err) {
47
+ const aside = `${this.path}.corrupt-${Date.now()}`;
48
+ try {
49
+ renameSync(this.path, aside);
50
+ }
51
+ catch {
52
+ // Unreadable and unmovable: starting empty is all that is left.
53
+ }
54
+ console.error(`[state] ${this.path} did not parse (${err.message}); kept as ${aside}`);
55
+ return undefined;
56
+ }
57
+ }
58
+ /** What to write. The store hands over a function, so the write takes the state as it is THEN. */
59
+ bind(snapshot) {
60
+ this.snapshot = snapshot;
61
+ }
62
+ /** Something changed: write it soon. */
63
+ changed() {
64
+ if (this.timer)
65
+ return;
66
+ this.timer = setTimeout(() => this.flush(), WRITE_DELAY_MS);
67
+ this.timer.unref?.();
68
+ }
69
+ /** Write now if anything is waiting. */
70
+ flush() {
71
+ if (!this.timer)
72
+ return;
73
+ clearTimeout(this.timer);
74
+ this.timer = undefined;
75
+ this.write();
76
+ }
77
+ write() {
78
+ if (!this.snapshot)
79
+ return;
80
+ try {
81
+ mkdirSync(dirname(this.path), { recursive: true });
82
+ const tmp = `${this.path}.${process.pid}.tmp`;
83
+ // ⚠️ OWNER-ONLY. These files hold names, emails and phone numbers.
84
+ writeFileSync(tmp, JSON.stringify(this.snapshot()), { mode: 0o600 });
85
+ chmodSync(tmp, 0o600);
86
+ renameSync(tmp, this.path);
87
+ }
88
+ catch (err) {
89
+ // A full disk must not take the conversation down with it: the state is
90
+ // still right in memory, and the next change tries again.
91
+ console.error(`[state] could not write ${this.path}: ${err.message}`);
92
+ }
93
+ }
94
+ }
95
+ /** Where the stores of one deployment keep their files - or nothing, when it has no directory. */
96
+ export class StateDir {
97
+ dir;
98
+ files = [];
99
+ constructor(dir) {
100
+ this.dir = dir;
101
+ }
102
+ get persisted() {
103
+ return this.dir !== undefined;
104
+ }
105
+ file(name) {
106
+ if (!this.dir)
107
+ return undefined;
108
+ const f = new StateFile(join(this.dir, name));
109
+ this.files.push(f);
110
+ return f;
111
+ }
112
+ /** Everything still waiting, written now - on a clean stop. */
113
+ flush() {
114
+ for (const f of this.files)
115
+ f.flush();
116
+ }
117
+ }
118
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/dist/tools.d.ts CHANGED
@@ -787,5 +787,16 @@ export type ToolAnswer = {
787
787
  * read better as hotels, grouped by city.
788
788
  */
789
789
  export declare const CITY_PICKER_MIN = 4;
790
+ /**
791
+ * The answer when the card names a trip this connector no longer knows.
792
+ *
793
+ * ⚠️ SAID, NOT PAPERED OVER. An unknown id used to fall back to the card's own
794
+ * connection - a DIFFERENT trip from the conversation's - and the card took
795
+ * that trip's id as its own. Rooms added on the card then went into a trip the
796
+ * assistant could not see, and "book it" was answered "the trip is empty"
797
+ * (2026-10-05 audit). The way back is the conversation's own trip, which the
798
+ * assistant's next answer hands the card.
799
+ */
800
+ export declare const tripGone: (deps: ToolDeps) => ToolAnswer;
790
801
  export declare const handleTool: (deps: ToolDeps, name: string, rawArgs: unknown, ctx: ToolContext) => Promise<ToolAnswer>;
791
802
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EAOzB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAgBhD,OAAO,EAIL,KAAK,SAAS,EAGf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAYV,eAAe,EAIhB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAkExD,+GAA+G;AAC/G,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,GAAG,SAAS,EACZ,GAAG,UAAU,EACb,OAAM,mBAAuC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IACC,CAAC;AAEjD,mEAAmE;AACnE,eAAO,MAAM,aAAa,GAAI,GAAG,SAAS,EAAE,MAAM,mBAAmB,KAAG,GAAG,CAAC,MAAM,CAM9E,CAAC;AAqqBL,eAAO,MAAM,kBAAkB,GAAI,GAAG,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAoNtE,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAssB/E;;;;GAIG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAyJjC;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,QAAQ,KAAG,UAMzC,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,MAAM,QAAQ,EACd,MAAM,MAAM,EACZ,SAAS,OAAO,EAChB,KAAK,WAAW,KACf,OAAO,CAAC,UAAU,CAMpB,CAAC"}
package/dist/tools.js CHANGED
@@ -5,7 +5,7 @@ import { DESCRIPTION_MAX, POLICY_MAX, hotelText } from './fence.js';
5
5
  import { formatMoney, money, multiplyMoney, toMinor } from './money.js';
6
6
  import { MAX_COMPARED, MAX_CONS, MAX_DAYS, MAX_ITEMS_PER_DAY, MAX_MOMENTS, MAX_PROS, buildItinerary, compareHotels, } from './present.js';
7
7
  import { regionOf } from './regions.js';
8
- import { MAX_EXTRA_QUANTITY, MAX_ROOMS_PER_LINE, clampCount, tripKey, } from './trip.js';
8
+ import { MAX_EXTRA_QUANTITY, MAX_ROOMS_PER_LINE, TRIP_TTL_MS, clampCount, tripKey, } from './trip.js';
9
9
  import { DEMO_NOTE } from './vocab.js';
10
10
  // The eleven things a guest can do: find hotels, look at one on dates, see what
11
11
  // else it offers, build a trip, ask for the rooms, and deal with a booking they
@@ -1007,6 +1007,13 @@ const common = (deps, key) => ({
1007
1007
  ...(deps.vocab.demo ? { demo: true, demoNote: DEMO_NOTE } : { demo: false }),
1008
1008
  ...(key ? memberBadge(deps, key) : {}),
1009
1009
  });
1010
+ /**
1011
+ * When a room stops being free to cancel, the way a guest reads it: the
1012
+ * hotel's own clock when the backend knows it, else the instant in UTC - never
1013
+ * the date alone (see `atHotelTime`).
1014
+ */
1015
+ const freeUntilText = (r) => r.freeCancellationLocal ??
1016
+ `${(r.freeCancellationUntil ?? '').slice(0, 16).replace('T', ' ')} UTC`;
1010
1017
  /** One line of a trip, flattened for the card. Prices are strings: the card never does arithmetic. */
1011
1018
  const quoteForCard = (quote) => ({
1012
1019
  lines: quote.lines.map((l) => ({
@@ -1704,6 +1711,23 @@ const suggestionsFor = async (deps, d) => {
1704
1711
  })
1705
1712
  .slice(0, 3);
1706
1713
  };
1714
+ /**
1715
+ * The answer when the card names a trip this connector no longer knows.
1716
+ *
1717
+ * ⚠️ SAID, NOT PAPERED OVER. An unknown id used to fall back to the card's own
1718
+ * connection - a DIFFERENT trip from the conversation's - and the card took
1719
+ * that trip's id as its own. Rooms added on the card then went into a trip the
1720
+ * assistant could not see, and "book it" was answered "the trip is empty"
1721
+ * (2026-10-05 audit). The way back is the conversation's own trip, which the
1722
+ * assistant's next answer hands the card.
1723
+ */
1724
+ export const tripGone = (deps) => {
1725
+ const detail = `The trip this card was showing is no longer kept - a trip is kept for ${TRIP_TTL_MS / 3_600_000} hours after its last change. Ask the assistant to show the trip again.`;
1726
+ return {
1727
+ structured: { view: 'error', ...common(deps), error: 'trip-gone', detail },
1728
+ text: detail,
1729
+ };
1730
+ };
1707
1731
  export const handleTool = async (deps, name, rawArgs, ctx) => {
1708
1732
  const answer = await answerTool(deps, name, rawArgs, ctx);
1709
1733
  const suggestions = await suggestionsFor(deps, answer.structured).catch(() => []);
@@ -2188,6 +2212,13 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
2188
2212
  // string, not by anyone looking at it.
2189
2213
  ...(r.publicNightly ? { publicNightly: formatMoney(r.publicNightly) } : {}),
2190
2214
  ...(r.publicStayTotal ? { publicStayTotal: formatMoney(r.publicStayTotal) } : {}),
2215
+ // ⚠️ AND STRUCK THROUGH FOR AS MANY ROOMS AS THE PRICE BESIDE IT. One
2216
+ // room's public rate drawn over two rooms' member rate put a "was" price
2217
+ // BELOW the price a member pays (2026-10-05 audit).
2218
+ ...(r.publicStayTotal && roomsWanted > 1
2219
+ ? { publicRoomsTotal: formatMoney(multiplyMoney(r.publicStayTotal, roomsWanted)) }
2220
+ : {}),
2221
+ ...(r.freeCancellationUntil ? { freeCancellationLocal: freeUntilText(r) } : {}),
2191
2222
  }));
2192
2223
  return {
2193
2224
  structured: {
@@ -2200,7 +2231,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
2200
2231
  },
2201
2232
  text: [
2202
2233
  `**${h.name}** · ${enough.length} room ${enough.length === 1 ? 'type' : 'types'} for ${stay.checkIn} to ${stay.checkOut}, ${stay.guests} guests${roomsWanted > 1 ? `, ${roomsWanted} rooms` : ''}.`,
2203
- ...enough.map((r) => `- ${r.name} — ${formatMoney(r.nightly)} a night, ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${roomsWanted > 1 ? ` (${formatMoney(multiplyMoney(r.stayTotal, roomsWanted))} for ${roomsWanted} rooms)` : ''}${r.freeCancellationUntil ? `, free to cancel until ${r.freeCancellationUntil.slice(0, 10)}` : r.refundable ? ', the free cancellation window has closed - cancelling now has a fee' : ', non-refundable'}`),
2234
+ ...enough.map((r) => `- ${r.name} — ${formatMoney(r.nightly)} a night, ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${roomsWanted > 1 ? ` (${formatMoney(multiplyMoney(r.stayTotal, roomsWanted))} for ${roomsWanted} rooms)` : ''}${r.freeCancellationUntil ? `, free to cancel until ${freeUntilText(r)}` : r.refundable ? ', the free cancellation window has closed - cancelling now has a fee' : ', non-refundable'}`),
2204
2235
  flex.text,
2205
2236
  deps.vocab.demo ? DEMO_NOTE : '',
2206
2237
  ]
@@ -2281,7 +2312,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
2281
2312
  : rooms.length
2282
2313
  ? [
2283
2314
  `Rooms for ${stay.checkIn} to ${stay.checkOut}, ${stay.guests} guests${roomsWanted > 1 ? `, ${roomsWanted} rooms` : ''}:`,
2284
- ...rooms.map((r) => `- ${r.name} · sleeps ${r.sleeps}${r.breakfast ? ' · breakfast' : ''} · ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${r.freeCancellationUntil ? `, free to cancel until ${r.freeCancellationUntil.slice(0, 10)}` : r.refundable ? '' : ', non-refundable'}`),
2315
+ ...rooms.map((r) => `- ${r.name} · sleeps ${r.sleeps}${r.breakfast ? ' · breakfast' : ''} · ${formatMoney(r.stayTotal)} for the stay incl. taxes and fees${r.freeCancellationUntil ? `, free to cancel until ${freeUntilText(r)}` : r.refundable ? '' : ', non-refundable'}`),
2285
2316
  ].join('\n')
2286
2317
  : (detail.noRooms ?? `No room type here has ${roomsWanted} left for those nights.`),
2287
2318
  extras.length