@curless/hotel-storefront 0.1.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 (98) hide show
  1. package/README.md +63 -0
  2. package/dist/__tests__/card-contract.test.d.ts +2 -0
  3. package/dist/__tests__/card-contract.test.d.ts.map +1 -0
  4. package/dist/__tests__/card-contract.test.js +64 -0
  5. package/dist/__tests__/card-contract.test.js.map +1 -0
  6. package/dist/__tests__/data.test.d.ts +2 -0
  7. package/dist/__tests__/data.test.d.ts.map +1 -0
  8. package/dist/__tests__/data.test.js +89 -0
  9. package/dist/__tests__/data.test.js.map +1 -0
  10. package/dist/__tests__/dates.test.d.ts +2 -0
  11. package/dist/__tests__/dates.test.d.ts.map +1 -0
  12. package/dist/__tests__/dates.test.js +71 -0
  13. package/dist/__tests__/dates.test.js.map +1 -0
  14. package/dist/__tests__/demo-hotels.test.d.ts +2 -0
  15. package/dist/__tests__/demo-hotels.test.d.ts.map +1 -0
  16. package/dist/__tests__/demo-hotels.test.js +240 -0
  17. package/dist/__tests__/demo-hotels.test.js.map +1 -0
  18. package/dist/__tests__/server.test.d.ts +2 -0
  19. package/dist/__tests__/server.test.d.ts.map +1 -0
  20. package/dist/__tests__/server.test.js +209 -0
  21. package/dist/__tests__/server.test.js.map +1 -0
  22. package/dist/__tests__/tool-descriptions.test.d.ts +2 -0
  23. package/dist/__tests__/tool-descriptions.test.d.ts.map +1 -0
  24. package/dist/__tests__/tool-descriptions.test.js +139 -0
  25. package/dist/__tests__/tool-descriptions.test.js.map +1 -0
  26. package/dist/__tests__/tools.test.d.ts +2 -0
  27. package/dist/__tests__/tools.test.d.ts.map +1 -0
  28. package/dist/__tests__/tools.test.js +226 -0
  29. package/dist/__tests__/tools.test.js.map +1 -0
  30. package/dist/backend.d.ts +43 -0
  31. package/dist/backend.d.ts.map +1 -0
  32. package/dist/backend.js +2 -0
  33. package/dist/backend.js.map +1 -0
  34. package/dist/card.generated.d.ts +4 -0
  35. package/dist/card.generated.d.ts.map +1 -0
  36. package/dist/card.generated.js +8 -0
  37. package/dist/card.generated.js.map +1 -0
  38. package/dist/config.d.ts +122 -0
  39. package/dist/config.d.ts.map +1 -0
  40. package/dist/config.js +42 -0
  41. package/dist/config.js.map +1 -0
  42. package/dist/data.d.ts +5 -0
  43. package/dist/data.d.ts.map +1 -0
  44. package/dist/data.js +183 -0
  45. package/dist/data.js.map +1 -0
  46. package/dist/dates.d.ts +32 -0
  47. package/dist/dates.d.ts.map +1 -0
  48. package/dist/dates.js +75 -0
  49. package/dist/dates.js.map +1 -0
  50. package/dist/demo-hotels.d.ts +96 -0
  51. package/dist/demo-hotels.d.ts.map +1 -0
  52. package/dist/demo-hotels.js +446 -0
  53. package/dist/demo-hotels.js.map +1 -0
  54. package/dist/fence.d.ts +24 -0
  55. package/dist/fence.d.ts.map +1 -0
  56. package/dist/fence.js +0 -0
  57. package/dist/fence.js.map +1 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +23 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/legal.d.ts +5 -0
  63. package/dist/legal.d.ts.map +1 -0
  64. package/dist/legal.js +111 -0
  65. package/dist/legal.js.map +1 -0
  66. package/dist/money.d.ts +13 -0
  67. package/dist/money.d.ts.map +1 -0
  68. package/dist/money.js +46 -0
  69. package/dist/money.js.map +1 -0
  70. package/dist/server.d.ts +85 -0
  71. package/dist/server.d.ts.map +1 -0
  72. package/dist/server.js +275 -0
  73. package/dist/server.js.map +1 -0
  74. package/dist/surface.d.ts +52 -0
  75. package/dist/surface.d.ts.map +1 -0
  76. package/dist/surface.js +99 -0
  77. package/dist/surface.js.map +1 -0
  78. package/dist/tools.d.ts +336 -0
  79. package/dist/tools.d.ts.map +1 -0
  80. package/dist/tools.js +862 -0
  81. package/dist/tools.js.map +1 -0
  82. package/dist/trip-ids.d.ts +16 -0
  83. package/dist/trip-ids.d.ts.map +1 -0
  84. package/dist/trip-ids.js +77 -0
  85. package/dist/trip-ids.js.map +1 -0
  86. package/dist/trip.d.ts +59 -0
  87. package/dist/trip.d.ts.map +1 -0
  88. package/dist/trip.js +149 -0
  89. package/dist/trip.js.map +1 -0
  90. package/dist/types.d.ts +193 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +13 -0
  93. package/dist/types.js.map +1 -0
  94. package/dist/vocab.d.ts +33 -0
  95. package/dist/vocab.d.ts.map +1 -0
  96. package/dist/vocab.js +15 -0
  97. package/dist/vocab.js.map +1 -0
  98. package/package.json +44 -0
@@ -0,0 +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,0ykTAA0ykT,EAC1ykT,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEnB,2FAA2F;AAC3F,MAAM,CAAC,MAAM,qBAAqB,GAChC,kEAAkE,CAAC"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * One hotel group's identity, vocabulary and wiring.
3
+ *
4
+ * Same bargain as the shopping connector's `StorefrontConfig`: a second brand
5
+ * is a JSON file, never a fork. What differs is the seam - a shop config points
6
+ * at a Shopify domain, this one points at a `HotelBackend`, because the group's
7
+ * own booking API is coming and the demo data has to step aside without the
8
+ * config shape changing.
9
+ *
10
+ * Secrets never live here. They arrive through the environment.
11
+ */
12
+ export type HotelConfig = {
13
+ /** Deployment id, and the path segment it is served under: hotels.curless.ai/<id>. */
14
+ id: string;
15
+ /** The group whose rooms these are - printed on the card and in the legal pages. */
16
+ brand: string;
17
+ /** MCP `serverInfo`, passed to the host verbatim. */
18
+ server: {
19
+ name: string;
20
+ title: string;
21
+ websiteUrl?: string;
22
+ /**
23
+ * The group's own artwork, as files beside the config. Files rather than
24
+ * bytes in the package: the shopping connector once served the first
25
+ * merchant's logo to a merchant who had supplied none, which named the
26
+ * wrong party on someone else's connector. Resolved against the config
27
+ * file's directory; absent, the routes 404 and no icon is claimed.
28
+ */
29
+ iconFile?: string;
30
+ logoFile?: string;
31
+ };
32
+ /**
33
+ * How the tools are named: `list_shangrila_hotels` rather than `list_hotels`.
34
+ *
35
+ * A model choosing between a Shangri-La connector and a Marriott one picks by
36
+ * name, and a specific name is harder to pick wrong. Leave `prefix` unset and
37
+ * the tools are generic - right for a single listed app, wrong for a machine
38
+ * that serves several groups.
39
+ */
40
+ naming?: {
41
+ prefix?: string;
42
+ /** 'hotel' / 'hotels'. A group that calls them resorts or residences says so here. */
43
+ noun?: string;
44
+ nounPlural?: string;
45
+ };
46
+ /** Where the rooms come from. */
47
+ data: {
48
+ /**
49
+ * `demo` - read `file` and price it ourselves. Every rate it answers with is
50
+ * marked as a demonstration value, in the tool output and on the card, so
51
+ * nobody can mistake it for a rate they could book.
52
+ * `api` - the group's own booking API. Not implemented yet; named here so
53
+ * the config does not have to change when it lands.
54
+ */
55
+ source: 'demo' | 'api';
56
+ /** Demo only: the hotels file, resolved against the config file's directory. */
57
+ file?: string;
58
+ /** api only: where it answers. The key comes from the environment. */
59
+ baseUrl?: string;
60
+ };
61
+ /**
62
+ * Taking money. Absent - and it IS absent today, by decision - the connector
63
+ * goes as far as a booking REQUEST: the rooms are held, the guest gets a
64
+ * confirmation code, and the card says in as many words that nothing has been
65
+ * charged. The wallet step slots in here, between request and confirmed.
66
+ */
67
+ payment?: {
68
+ mode: 'none' | 'wallet';
69
+ };
70
+ /** Where this deployment answers. `publicUrl` differs when the group fronts it with their own hostname. */
71
+ urls: {
72
+ appUrl: string;
73
+ publicUrl?: string;
74
+ };
75
+ /**
76
+ * Group-specific facts, appended verbatim to the tool they belong to.
77
+ *
78
+ * Only facts a generic connector cannot know: that a Horizon Club rate
79
+ * includes the lounge, that one city has four properties under three brands.
80
+ * Behavioural guidance does NOT go here - it goes in the server instructions,
81
+ * which is the line the shopping connector had to redraw on 2026-09-17 to
82
+ * satisfy the directory's attestation about tool descriptions.
83
+ */
84
+ hints?: {
85
+ search?: string;
86
+ hotel?: string;
87
+ booking?: string;
88
+ /** One real hotel name, shown in an argument's description as an example. */
89
+ example?: string;
90
+ };
91
+ legal: {
92
+ operator?: string;
93
+ operatorContact?: string;
94
+ /** The address the group publishes in their own privacy policy. */
95
+ brandContact?: string;
96
+ region?: string;
97
+ updated?: string;
98
+ };
99
+ };
100
+ /**
101
+ * The tool names this config produces.
102
+ *
103
+ * Built in one place because the names are a contract: the card addresses
104
+ * tools by name, the server instructions name them in prose, and the OpenAI
105
+ * directory freezes them at submission. Two lists that drift are two lists that
106
+ * disagree in front of a reviewer.
107
+ */
108
+ export declare const toolNames: (config: Pick<HotelConfig, "naming">) => {
109
+ readonly listHotels: `list_${string}${string}`;
110
+ readonly getHotel: `get_${string}${string}`;
111
+ readonly listExtras: `list_${string}extras`;
112
+ readonly addToTrip: `add_to_${string}trip`;
113
+ readonly viewTrip: `view_${string}trip`;
114
+ readonly clearTrip: `clear_${string}trip`;
115
+ readonly requestBooking: `request_${string}booking`;
116
+ readonly listBookings: `list_my_${string}bookings`;
117
+ readonly previewCancellation: `preview_${string}cancellation`;
118
+ readonly cancelBooking: `cancel_${string}booking`;
119
+ readonly policies: `get_${string}${string}_policies`;
120
+ };
121
+ export type ToolNames = ReturnType<typeof toolNames>;
122
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,WAAW,GAAG;IACxB,sFAAsF;IACtF,EAAE,EAAE,MAAM,CAAC;IAEX,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IAEd,qDAAqD;IACrD,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IAEF;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,sFAAsF;QACtF,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IAEF,iCAAiC;IACjC,IAAI,EAAE;QACJ;;;;;;WAMG;QACH,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;QACvB,gFAAgF;QAChF,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,sEAAsE;QACtE,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IAEF;;;;;OAKG;IACH,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;KACzB,CAAC;IAEF,2GAA2G;IAC3G,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,6EAA6E;QAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IAEF,KAAK,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,mEAAmE;QACnE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH,CAAC;AAQF;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,GAAI,QAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;;;;;;;;;;;;CAiB5D,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * One hotel group's identity, vocabulary and wiring.
3
+ *
4
+ * Same bargain as the shopping connector's `StorefrontConfig`: a second brand
5
+ * is a JSON file, never a fork. What differs is the seam - a shop config points
6
+ * at a Shopify domain, this one points at a `HotelBackend`, because the group's
7
+ * own booking API is coming and the demo data has to step aside without the
8
+ * config shape changing.
9
+ *
10
+ * Secrets never live here. They arrive through the environment.
11
+ */
12
+ const slug = (s) => s
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '_')
15
+ .replace(/^_|_$/g, '');
16
+ /**
17
+ * The tool names this config produces.
18
+ *
19
+ * Built in one place because the names are a contract: the card addresses
20
+ * tools by name, the server instructions name them in prose, and the OpenAI
21
+ * directory freezes them at submission. Two lists that drift are two lists that
22
+ * disagree in front of a reviewer.
23
+ */
24
+ export const toolNames = (config) => {
25
+ const prefix = config.naming?.prefix ? `${slug(config.naming.prefix)}_` : '';
26
+ const noun = slug(config.naming?.noun ?? 'hotel');
27
+ const plural = slug(config.naming?.nounPlural ?? `${noun}s`);
28
+ return {
29
+ listHotels: `list_${prefix}${plural}`,
30
+ getHotel: `get_${prefix}${noun}`,
31
+ listExtras: `list_${prefix}extras`,
32
+ addToTrip: `add_to_${prefix}trip`,
33
+ viewTrip: `view_${prefix}trip`,
34
+ clearTrip: `clear_${prefix}trip`,
35
+ requestBooking: `request_${prefix}booking`,
36
+ listBookings: `list_my_${prefix}bookings`,
37
+ previewCancellation: `preview_${prefix}cancellation`,
38
+ cancelBooking: `cancel_${prefix}booking`,
39
+ policies: `get_${prefix}${noun}_policies`,
40
+ };
41
+ };
42
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAmGH,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CACjC,CAAC;KACE,WAAW,EAAE;KACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;KAC3B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAmC,EAAE,EAAE;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,OAAO,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;IAC7D,OAAO;QACL,UAAU,EAAE,QAAQ,MAAM,GAAG,MAAM,EAAE;QACrC,QAAQ,EAAE,OAAO,MAAM,GAAG,IAAI,EAAE;QAChC,UAAU,EAAE,QAAQ,MAAM,QAAQ;QAClC,SAAS,EAAE,UAAU,MAAM,MAAM;QACjC,QAAQ,EAAE,QAAQ,MAAM,MAAM;QAC9B,SAAS,EAAE,SAAS,MAAM,MAAM;QAChC,cAAc,EAAE,WAAW,MAAM,SAAS;QAC1C,YAAY,EAAE,WAAW,MAAM,UAAU;QACzC,mBAAmB,EAAE,WAAW,MAAM,cAAc;QACpD,aAAa,EAAE,UAAU,MAAM,SAAS;QACxC,QAAQ,EAAE,OAAO,MAAM,GAAG,IAAI,WAAW;KACjC,CAAC;AACb,CAAC,CAAC"}
package/dist/data.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { RawHotelData } from './demo-hotels.js';
2
+ export declare const parseHotelData: (raw: unknown) => RawHotelData;
3
+ /** Read a brand's hotels file. `from` is the config file it was named in. */
4
+ export declare const loadHotelData: (file: string, from?: string) => RawHotelData;
5
+ //# sourceMappingURL=data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,YAAY,EAAW,MAAM,kBAAkB,CAAC;AA0KlF,eAAO,MAAM,cAAc,GAAI,KAAK,OAAO,KAAG,YAU7C,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,YAS3D,CAAC"}
package/dist/data.js ADDED
@@ -0,0 +1,183 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ /**
4
+ * Reading a brand's hotels file, and refusing a bad one loudly.
5
+ *
6
+ * The file is hand-written per brand and nothing else checks it, so a missing
7
+ * `currency` or a `taxRate` someone wrote as 16 instead of 0.16 would otherwise
8
+ * surface as a wrong price on a card - which is the one failure mode a booking
9
+ * connector cannot have. Every check below exists because the wrong version of
10
+ * it prints a plausible number rather than an error.
11
+ */
12
+ const KINDS = ['transfer', 'dining', 'experience', 'family', 'wellness'];
13
+ const TOPICS = [
14
+ 'cancellation',
15
+ 'check-in',
16
+ 'children',
17
+ 'pets',
18
+ 'deposit',
19
+ 'accessibility',
20
+ ];
21
+ class DataError extends Error {
22
+ constructor(where, what) {
23
+ super(`${where}: ${what}`);
24
+ this.name = 'HotelDataError';
25
+ }
26
+ }
27
+ const str = (v, where, what) => {
28
+ if (typeof v !== 'string' || v.trim() === '')
29
+ throw new DataError(where, `${what} must be a non-empty string`);
30
+ return v;
31
+ };
32
+ const num = (v, where, what, min, max) => {
33
+ if (typeof v !== 'number' || !Number.isFinite(v))
34
+ throw new DataError(where, `${what} must be a number`);
35
+ if (v < min || v > max)
36
+ throw new DataError(where, `${what} is ${v}, outside ${min}..${max}`);
37
+ return v;
38
+ };
39
+ const time = (v, where, what) => {
40
+ if (v === undefined)
41
+ return undefined;
42
+ const s = str(v, where, what);
43
+ if (!/^\d{2}:\d{2}$/.test(s))
44
+ throw new DataError(where, `${what} must look like 15:00`);
45
+ return s;
46
+ };
47
+ const parseRoom = (raw, where) => {
48
+ const r = raw;
49
+ const id = str(r.id, where, 'room id');
50
+ const at = `${where} room ${id}`;
51
+ const refundable = r.refundable === true;
52
+ if (refundable && r.freeCancelHours === undefined)
53
+ throw new DataError(at, 'a refundable rate must say freeCancelHours - "free to cancel" with no deadline is not a policy');
54
+ if (!refundable && r.freeCancelHours !== undefined)
55
+ throw new DataError(at, 'freeCancelHours on a non-refundable rate would print a free window that does not exist');
56
+ return {
57
+ id,
58
+ name: str(r.name, at, 'name'),
59
+ sleeps: num(r.sleeps, at, 'sleeps', 1, 8),
60
+ sizeSqm: r.sizeSqm === undefined ? undefined : num(r.sizeSqm, at, 'sizeSqm', 5, 500),
61
+ beds: r.beds === undefined ? undefined : str(r.beds, at, 'beds'),
62
+ view: r.view === undefined ? undefined : str(r.view, at, 'view'),
63
+ breakfast: r.breakfast === true,
64
+ // A rate is in whole currency units, the way the hotel writes it. 380000
65
+ // would be a rate typed in minor units by mistake, and it would look like a
66
+ // real - if eye-watering - price on the card.
67
+ nightly: num(r.nightly, at, 'nightly', 1, 200_000),
68
+ roomsLeft: num(r.roomsLeft, at, 'roomsLeft', 0, 500),
69
+ refundable,
70
+ freeCancelHours: refundable ? num(r.freeCancelHours, at, 'freeCancelHours', 0, 720) : undefined,
71
+ };
72
+ };
73
+ const parseExtra = (raw, where) => {
74
+ const e = raw;
75
+ const id = str(e.id, where, 'extra id');
76
+ const at = `${where} extra ${id}`;
77
+ const kind = str(e.kind, at, 'kind');
78
+ if (!KINDS.includes(kind))
79
+ throw new DataError(at, `kind ${kind} is not one of ${KINDS.join(', ')}`);
80
+ const per = str(e.per, at, 'per');
81
+ if (per !== 'stay' && per !== 'person' && per !== 'night')
82
+ throw new DataError(at, 'per must be stay, person or night');
83
+ const when = e.when === undefined ? undefined : str(e.when, at, 'when');
84
+ if (when !== undefined && when !== 'arrival' && when !== 'departure' && when !== 'any')
85
+ throw new DataError(at, 'when must be arrival, departure or any');
86
+ return {
87
+ id,
88
+ name: str(e.name, at, 'name'),
89
+ kind,
90
+ description: str(e.description, at, 'description'),
91
+ price: num(e.price, at, 'price', 1, 200_000),
92
+ per,
93
+ when,
94
+ };
95
+ };
96
+ const parseHotel = (raw, index) => {
97
+ const h = raw;
98
+ const where = `hotel[${index}]`;
99
+ const id = str(h.id, where, 'id');
100
+ const at = `hotel ${id}`;
101
+ const rooms = Array.isArray(h.rooms) ? h.rooms.map((r) => parseRoom(r, at)) : [];
102
+ if (rooms.length === 0)
103
+ throw new DataError(at, 'a hotel with no room types cannot be booked; leave it out instead');
104
+ const seen = new Set();
105
+ for (const r of rooms) {
106
+ if (seen.has(r.id))
107
+ throw new DataError(at, `two room types share the id ${r.id}`);
108
+ seen.add(r.id);
109
+ }
110
+ const policies = {};
111
+ const rawPolicies = (h.policies ?? {});
112
+ for (const [topic, text] of Object.entries(rawPolicies)) {
113
+ if (!TOPICS.includes(topic))
114
+ throw new DataError(at, `policy topic ${topic} is not one we show`);
115
+ policies[topic] = str(text, at, `policy ${topic}`);
116
+ }
117
+ const currency = str(h.currency, at, 'currency');
118
+ if (!/^[A-Z]{3}$/.test(currency))
119
+ throw new DataError(at, `currency ${currency} must be a 3-letter code like HKD`);
120
+ return {
121
+ id,
122
+ name: str(h.name, at, 'name'),
123
+ brand: str(h.brand, at, 'brand'),
124
+ city: str(h.city, at, 'city'),
125
+ cityId: str(h.cityId, at, 'cityId'),
126
+ area: h.area === undefined ? undefined : str(h.area, at, 'area'),
127
+ summary: str(h.summary, at, 'summary'),
128
+ amenities: Array.isArray(h.amenities)
129
+ ? h.amenities.map((a, i) => str(a, at, `amenity ${i}`))
130
+ : [],
131
+ address: h.address === undefined ? undefined : str(h.address, at, 'address'),
132
+ url: h.url === undefined ? undefined : str(h.url, at, 'url'),
133
+ imageUrl: h.imageUrl === undefined ? undefined : str(h.imageUrl, at, 'imageUrl'),
134
+ checkInFrom: time(h.checkInFrom, at, 'checkInFrom'),
135
+ checkOutBy: time(h.checkOutBy, at, 'checkOutBy'),
136
+ currency,
137
+ // Fractions, not percentages. 16 instead of 0.16 is a 1600% tax that still
138
+ // renders as a number, so it is rejected rather than trusted.
139
+ taxRate: num(h.taxRate, at, 'taxRate', 0, 0.5),
140
+ weekendUplift: num(h.weekendUplift, at, 'weekendUplift', 0, 2),
141
+ utcOffset: h.utcOffset === undefined ? undefined : str(h.utcOffset, at, 'utcOffset'),
142
+ rating: h.rating === undefined ? undefined : parseRating(h.rating, at),
143
+ rooms,
144
+ extras: Array.isArray(h.extras) ? h.extras.map((e) => parseExtra(e, at)) : [],
145
+ policies,
146
+ };
147
+ };
148
+ const parseRating = (raw, where) => {
149
+ const r = raw;
150
+ const scaleMax = num(r.scaleMax, where, 'rating.scaleMax', 1, 100);
151
+ return {
152
+ value: num(r.value, where, 'rating.value', 0, scaleMax),
153
+ scaleMax,
154
+ // A rating with no count behind it is a number nobody can check.
155
+ count: num(r.count, where, 'rating.count', 1, 10_000_000),
156
+ };
157
+ };
158
+ export const parseHotelData = (raw) => {
159
+ const d = raw;
160
+ const hotels = Array.isArray(d.hotels) ? d.hotels.map(parseHotel) : [];
161
+ if (hotels.length === 0)
162
+ throw new DataError('hotels file', 'has no hotels');
163
+ const ids = new Set();
164
+ for (const h of hotels) {
165
+ if (ids.has(h.id))
166
+ throw new DataError('hotels file', `two hotels share the id ${h.id}`);
167
+ ids.add(h.id);
168
+ }
169
+ return { brand: str(d.brand, 'hotels file', 'brand'), hotels };
170
+ };
171
+ /** Read a brand's hotels file. `from` is the config file it was named in. */
172
+ export const loadHotelData = (file, from) => {
173
+ const path = from ? resolve(dirname(from), file) : resolve(file);
174
+ let text;
175
+ try {
176
+ text = readFileSync(path, 'utf8');
177
+ }
178
+ catch (err) {
179
+ throw new DataError('hotels file', `cannot read ${path} - ${err.message}`);
180
+ }
181
+ return parseHotelData(JSON.parse(text));
182
+ };
183
+ //# sourceMappingURL=data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.js","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export declare const DAY_MS = 86400000;
2
+ export declare const isDay: (s: unknown) => s is string;
3
+ export declare const toDay: (ms: number) => string;
4
+ export declare const addDays: (day: string, n: number) => string;
5
+ /** Check-out exclusive: `nights('2026-10-12','2026-10-14')` is 2. */
6
+ export declare const nights: (checkIn: string, checkOut: string) => number;
7
+ /** Every night of the stay, as its own date. The check-out day is not a night. */
8
+ export declare const eachNight: (checkIn: string, checkOut: string) => string[];
9
+ /** Friday and Saturday nights - the ones hotels charge more for. */
10
+ export declare const isWeekendNight: (day: string) => boolean;
11
+ /** The hotel's own today, in its own timezone offset (`+08:00`). */
12
+ export declare const todayAt: (now: number, utcOffset: string) => string;
13
+ /** The moment a guest can walk in, as an instant. */
14
+ export declare const arrivalInstant: (checkIn: string, checkInFrom: string, utcOffset: string) => number;
15
+ export declare const MAX_NIGHTS = 30;
16
+ export declare const MAX_GUESTS = 8;
17
+ /** How far ahead the demo data is willing to quote. A real API decides this itself. */
18
+ export declare const MAX_DAYS_AHEAD = 365;
19
+ /**
20
+ * Why these dates cannot be used, in words a guest can act on - or null.
21
+ *
22
+ * Returned rather than thrown because "you asked for last Tuesday" is an answer
23
+ * to give the guest, not a crash. Never guesses a fix: a connector that
24
+ * silently rolls a past date forward to today books someone a room on the
25
+ * wrong night.
26
+ */
27
+ export declare const stayProblem: (stay: {
28
+ checkIn?: unknown;
29
+ checkOut?: unknown;
30
+ guests?: unknown;
31
+ }, today: string) => string | null;
32
+ //# sourceMappingURL=dates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dates.d.ts","sourceRoot":"","sources":["../src/dates.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,MAAM,WAAa,CAAC;AAEjC,eAAO,MAAM,KAAK,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,MAIpB,CAAC;AAErB,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,MAAiD,CAAC;AAErF,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,GAAG,MAAM,KAAG,MAAqC,CAAC;AAEvF,qEAAqE;AACrE,eAAO,MAAM,MAAM,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MACR,CAAC;AAEpD,kFAAkF;AAClF,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MAAM,EAKnE,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAG5C,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,WAAW,MAAM,KAAG,MAMxD,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,aAAa,MAAM,EAAE,WAAW,MAAM,KAAG,MACI,CAAC;AAE9F,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,uFAAuF;AACvF,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EACjE,OAAO,MAAM,KACZ,MAAM,GAAG,IAiBX,CAAC"}
package/dist/dates.js ADDED
@@ -0,0 +1,75 @@
1
+ // Dates, the way a hotel means them.
2
+ //
3
+ // A night is a calendar day, not 24 hours, and check-out is exclusive: 12th to
4
+ // the 14th is two nights. Everything here works on `YYYY-MM-DD` strings in the
5
+ // HOTEL's calendar, never on the machine's local time - a server in Frankfurt
6
+ // must not decide that a Hong Kong guest's "tonight" is yesterday.
7
+ /** Parsed as UTC noon, so a timezone can never tip the date over a day boundary. */
8
+ const at = (day) => Date.parse(`${day}T12:00:00Z`);
9
+ export const DAY_MS = 86_400_000;
10
+ export const isDay = (s) => typeof s === 'string' &&
11
+ /^\d{4}-\d{2}-\d{2}$/.test(s) &&
12
+ !Number.isNaN(at(s)) &&
13
+ toDay(at(s)) === s;
14
+ export const toDay = (ms) => new Date(ms).toISOString().slice(0, 10);
15
+ export const addDays = (day, n) => toDay(at(day) + n * DAY_MS);
16
+ /** Check-out exclusive: `nights('2026-10-12','2026-10-14')` is 2. */
17
+ export const nights = (checkIn, checkOut) => Math.round((at(checkOut) - at(checkIn)) / DAY_MS);
18
+ /** Every night of the stay, as its own date. The check-out day is not a night. */
19
+ export const eachNight = (checkIn, checkOut) => {
20
+ const out = [];
21
+ if (nights(checkIn, checkOut) <= 0)
22
+ return out;
23
+ for (let d = checkIn; d !== checkOut; d = addDays(d, 1))
24
+ out.push(d);
25
+ return out;
26
+ };
27
+ /** Friday and Saturday nights - the ones hotels charge more for. */
28
+ export const isWeekendNight = (day) => {
29
+ const dow = new Date(at(day)).getUTCDay();
30
+ return dow === 5 || dow === 6;
31
+ };
32
+ /** The hotel's own today, in its own timezone offset (`+08:00`). */
33
+ export const todayAt = (now, utcOffset) => {
34
+ const m = /^([+-])(\d{2}):(\d{2})$/.exec(utcOffset);
35
+ const shift = m
36
+ ? (m[1] === '-' ? -1 : 1) * (Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0)) * 60_000
37
+ : 0;
38
+ return new Date(now + shift).toISOString().slice(0, 10);
39
+ };
40
+ /** The moment a guest can walk in, as an instant. */
41
+ export const arrivalInstant = (checkIn, checkInFrom, utcOffset) => Date.parse(`${checkIn}T${checkInFrom.length === 5 ? checkInFrom : '15:00'}:00${utcOffset}`);
42
+ export const MAX_NIGHTS = 30;
43
+ export const MAX_GUESTS = 8;
44
+ /** How far ahead the demo data is willing to quote. A real API decides this itself. */
45
+ export const MAX_DAYS_AHEAD = 365;
46
+ /**
47
+ * Why these dates cannot be used, in words a guest can act on - or null.
48
+ *
49
+ * Returned rather than thrown because "you asked for last Tuesday" is an answer
50
+ * to give the guest, not a crash. Never guesses a fix: a connector that
51
+ * silently rolls a past date forward to today books someone a room on the
52
+ * wrong night.
53
+ */
54
+ export const stayProblem = (stay, today) => {
55
+ if (!isDay(stay.checkIn))
56
+ return 'Check-in must be a date like 2026-10-12.';
57
+ if (!isDay(stay.checkOut))
58
+ return 'Check-out must be a date like 2026-10-14.';
59
+ const n = nights(stay.checkIn, stay.checkOut);
60
+ if (n <= 0)
61
+ return 'Check-out has to be after check-in. One night means checking out the next day.';
62
+ if (n > MAX_NIGHTS)
63
+ return `This connector quotes up to ${MAX_NIGHTS} nights at a time; that stay is ${n}.`;
64
+ if (stay.checkIn < today)
65
+ return `Check-in ${stay.checkIn} is in the past - today is ${today}.`;
66
+ if (nights(today, stay.checkIn) > MAX_DAYS_AHEAD)
67
+ return `Rooms are only quoted ${MAX_DAYS_AHEAD} days ahead; ${stay.checkIn} is further out.`;
68
+ const guests = stay.guests;
69
+ if (typeof guests !== 'number' || !Number.isInteger(guests) || guests < 1)
70
+ return 'Say how many guests are in the room, as a whole number.';
71
+ if (guests > MAX_GUESTS)
72
+ return `More than ${MAX_GUESTS} guests in one room needs the hotel directly.`;
73
+ return null;
74
+ };
75
+ //# sourceMappingURL=dates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dates.js","sourceRoot":"","sources":["../src/dates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import type { HotelBackend } from './backend.js';
2
+ import type { Booking, ExtraKind, HotelPolicyTopic } from './types.js';
3
+ /**
4
+ * The hotels, out of a JSON file, priced by us.
5
+ *
6
+ * THIS IS THE DEMONSTRATION BACKEND. The group's own booking API is coming
7
+ * (robin, 2026-09-18); when it does it arrives as a second `HotelBackend` and
8
+ * nothing above this file changes. Until then every number this answers with is
9
+ * invented, and the honest handling of that is not a disclaimer in the docs -
10
+ * it is `demo: true` on every price-bearing answer, which the tools and the
11
+ * card are required to show.
12
+ *
13
+ * Three things it refuses to fake, because faking them is how a demo turns into
14
+ * a lie a guest acts on:
15
+ * - availability it does not have: a held room comes off the count,
16
+ * - a date nobody gave: no stay, no price,
17
+ * - a cancellation fee: previewed with its rule before anything is cancelled.
18
+ */
19
+ export type RawRoom = {
20
+ id: string;
21
+ name: string;
22
+ sleeps: number;
23
+ sizeSqm?: number;
24
+ beds?: string;
25
+ view?: string;
26
+ breakfast: boolean;
27
+ /** Written the way a human writes it - 3800, not 380000. */
28
+ nightly: number;
29
+ roomsLeft: number;
30
+ refundable: boolean;
31
+ /** Hours before arrival that cancelling is still free. Absent on a non-refundable rate. */
32
+ freeCancelHours?: number;
33
+ };
34
+ export type RawExtra = {
35
+ id: string;
36
+ name: string;
37
+ kind: ExtraKind;
38
+ description: string;
39
+ price: number;
40
+ per: 'stay' | 'person' | 'night';
41
+ when?: 'arrival' | 'departure' | 'any';
42
+ };
43
+ export type RawHotel = {
44
+ id: string;
45
+ name: string;
46
+ brand: string;
47
+ city: string;
48
+ cityId: string;
49
+ area?: string;
50
+ summary: string;
51
+ amenities: string[];
52
+ address?: string;
53
+ url?: string;
54
+ imageUrl?: string;
55
+ checkInFrom?: string;
56
+ checkOutBy?: string;
57
+ currency: string;
58
+ /** Taxes and service charge, as a fraction. Applied to rooms and add-ons alike. */
59
+ taxRate: number;
60
+ /** How much more a Friday or Saturday night costs, as a fraction. */
61
+ weekendUplift: number;
62
+ /** The hotel's own clock. Everything in this demo is +08:00. */
63
+ utcOffset?: string;
64
+ rating?: {
65
+ value: number;
66
+ scaleMax: number;
67
+ count: number;
68
+ };
69
+ rooms: RawRoom[];
70
+ extras?: RawExtra[];
71
+ policies?: Partial<Record<HotelPolicyTopic, string>>;
72
+ };
73
+ export type RawHotelData = {
74
+ brand: string;
75
+ hotels: RawHotel[];
76
+ };
77
+ export type DemoError = Error & {
78
+ code: 'not-found' | 'bad-stay' | 'unavailable' | 'mixed-currency' | 'empty-trip';
79
+ };
80
+ export declare const demoError: (code: DemoError["code"], message: string) => DemoError;
81
+ export type DemoHotelsOptions = {
82
+ data: RawHotelData;
83
+ /** Injected so tests do not depend on the day they run. */
84
+ now?: () => number;
85
+ rng?: () => number;
86
+ };
87
+ type StoredBooking = Booking & {
88
+ sessionId: string;
89
+ };
90
+ export declare const createDemoHotels: (opts: DemoHotelsOptions) => HotelBackend & {
91
+ /** For the tools: the same clock the prices were computed against. */
92
+ today(hotelId?: string): string;
93
+ bookingsForTest(): StoredBooking[];
94
+ };
95
+ export {};
96
+ //# sourceMappingURL=demo-hotels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo-hotels.d.ts","sourceRoot":"","sources":["../src/demo-hotels.ts"],"names":[],"mappings":"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"}