itamatrix 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 (78) hide show
  1. package/DESIGN.md +247 -0
  2. package/LICENSE +21 -0
  3. package/README.md +101 -0
  4. package/dist/browser/batch.d.ts +8 -0
  5. package/dist/browser/batch.js +87 -0
  6. package/dist/browser/batch.js.map +1 -0
  7. package/dist/browser/batch.test.d.ts +1 -0
  8. package/dist/browser/batch.test.js +38 -0
  9. package/dist/browser/batch.test.js.map +1 -0
  10. package/dist/browser/forms.d.ts +26 -0
  11. package/dist/browser/forms.js +233 -0
  12. package/dist/browser/forms.js.map +1 -0
  13. package/dist/browser/session.d.ts +20 -0
  14. package/dist/browser/session.js +126 -0
  15. package/dist/browser/session.js.map +1 -0
  16. package/dist/cache.d.ts +21 -0
  17. package/dist/cache.js +71 -0
  18. package/dist/cache.js.map +1 -0
  19. package/dist/cache.test.d.ts +1 -0
  20. package/dist/cache.test.js +79 -0
  21. package/dist/cache.test.js.map +1 -0
  22. package/dist/cli.d.ts +3 -0
  23. package/dist/cli.js +154 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/commands/calendar.d.ts +19 -0
  26. package/dist/commands/calendar.js +47 -0
  27. package/dist/commands/calendar.js.map +1 -0
  28. package/dist/commands/calendar.test.d.ts +1 -0
  29. package/dist/commands/calendar.test.js +58 -0
  30. package/dist/commands/calendar.test.js.map +1 -0
  31. package/dist/commands/multicity.d.ts +17 -0
  32. package/dist/commands/multicity.js +50 -0
  33. package/dist/commands/multicity.js.map +1 -0
  34. package/dist/commands/multicity.test.d.ts +1 -0
  35. package/dist/commands/multicity.test.js +54 -0
  36. package/dist/commands/multicity.test.js.map +1 -0
  37. package/dist/commands/search.d.ts +20 -0
  38. package/dist/commands/search.js +43 -0
  39. package/dist/commands/search.js.map +1 -0
  40. package/dist/commands/search.test.d.ts +1 -0
  41. package/dist/commands/search.test.js +124 -0
  42. package/dist/commands/search.test.js.map +1 -0
  43. package/dist/commands/shared.d.ts +44 -0
  44. package/dist/commands/shared.js +77 -0
  45. package/dist/commands/shared.js.map +1 -0
  46. package/dist/model/spec.d.ts +63 -0
  47. package/dist/model/spec.js +29 -0
  48. package/dist/model/spec.js.map +1 -0
  49. package/dist/model/spec.test.d.ts +1 -0
  50. package/dist/model/spec.test.js +55 -0
  51. package/dist/model/spec.test.js.map +1 -0
  52. package/dist/model/types.d.ts +22080 -0
  53. package/dist/model/types.js +100 -0
  54. package/dist/model/types.js.map +1 -0
  55. package/dist/model/types.test.d.ts +1 -0
  56. package/dist/model/types.test.js +35 -0
  57. package/dist/model/types.test.js.map +1 -0
  58. package/dist/render/calendar.d.ts +21 -0
  59. package/dist/render/calendar.js +89 -0
  60. package/dist/render/calendar.js.map +1 -0
  61. package/dist/render/calendar.render.test.d.ts +1 -0
  62. package/dist/render/calendar.render.test.js +66 -0
  63. package/dist/render/calendar.render.test.js.map +1 -0
  64. package/dist/render/json.d.ts +2 -0
  65. package/dist/render/json.js +4 -0
  66. package/dist/render/json.js.map +1 -0
  67. package/dist/render/normalize.d.ts +32 -0
  68. package/dist/render/normalize.js +44 -0
  69. package/dist/render/normalize.js.map +1 -0
  70. package/dist/render/render.test.d.ts +1 -0
  71. package/dist/render/render.test.js +129 -0
  72. package/dist/render/render.test.js.map +1 -0
  73. package/dist/render/table.d.ts +2 -0
  74. package/dist/render/table.js +49 -0
  75. package/dist/render/table.js.map +1 -0
  76. package/docs/ROUTING_CODES.md +137 -0
  77. package/package.json +68 -0
  78. package/skills/itamatrix/SKILL.md +173 -0
@@ -0,0 +1,233 @@
1
+ import { CABIN_LABELS, EXTRA_STOPS_LABELS, STOPS_LABELS, hasAdvancedControls, isRoundTrip, } from "../model/spec.js";
2
+ /**
3
+ * Drives the Matrix search form and clicks Search.
4
+ *
5
+ * NOTE: this layer is intentionally thin and is the part most coupled to the
6
+ * live DOM (DESIGN.md "Trade-offs"). Selectors target the Angular Material
7
+ * markup observed on matrix.itasoftware.com/search. If Matrix restructures the
8
+ * form, failures surface here with a clear message.
9
+ */
10
+ export async function driveSearchForm(page, spec) {
11
+ const roundTrip = isRoundTrip(spec);
12
+ await selectTripType(page, roundTrip);
13
+ await fillAirport(page, 0, spec.origin);
14
+ await fillAirport(page, 1, spec.dest);
15
+ await fillDates(page, spec.departDate, roundTrip ? spec.returnDate : undefined);
16
+ await setAdults(page, spec.adults);
17
+ await setAdvancedControls(page, spec, roundTrip);
18
+ await clickSearch(page);
19
+ }
20
+ /**
21
+ * Drives the multi-city form: selects the Multi-City tab, materialises one row
22
+ * per leg, fills each leg's origin/destination/date, then applies the shared
23
+ * cabin/stops controls and clicks Search. The response is the same
24
+ * `solutionList` shape as a normal search (N slices instead of 1–2).
25
+ */
26
+ export async function driveMultiCityForm(page, spec) {
27
+ await page.getByRole("tab", { name: "Multi-City", exact: true }).click();
28
+ await ensureLegRows(page, spec.slices.length);
29
+ for (const [i, leg] of spec.slices.entries()) {
30
+ await fillAirport(page, 2 * i, leg.origin);
31
+ await fillAirport(page, 2 * i + 1, leg.dest);
32
+ await commitDate(page.locator("input.mat-datepicker-input").nth(i), leg.departDate);
33
+ }
34
+ await dismissOverlay(page);
35
+ await setAdults(page, spec.adults);
36
+ await setMultiCityAdvanced(page, spec);
37
+ await clickSearch(page);
38
+ }
39
+ /**
40
+ * Drives the price-calendar form: "See calendar of lowest fares" over a
41
+ * departure-date range. With `tripLength` it's a round-trip calendar; without,
42
+ * one-way. Selectors here are provisional — the calendar UI/response could not
43
+ * be captured for P3 (DESIGN), so this is the thinnest, most likely-to-drift
44
+ * layer; failures surface with a clear message.
45
+ */
46
+ export async function driveCalendarForm(page, spec) {
47
+ const roundTrip = spec.tripLength != null;
48
+ await selectTripType(page, roundTrip);
49
+ await fillAirport(page, 0, spec.origin);
50
+ await fillAirport(page, 1, spec.dest);
51
+ await page
52
+ .getByText(/see calendar of lowest fares/i)
53
+ .first()
54
+ .click();
55
+ await commitDate(page.locator("input.mat-start-date"), spec.departFrom);
56
+ await commitDate(page.locator("input.mat-end-date"), spec.departTo);
57
+ if (spec.tripLength != null) {
58
+ const len = page.locator('input[formcontrolname="tripLength"]');
59
+ await len.fill(String(spec.tripLength));
60
+ await len.dispatchEvent("change");
61
+ }
62
+ await dismissOverlay(page);
63
+ await setAdults(page, spec.adults);
64
+ await setGlobalControls(page, spec, spec.routing, spec.ext, roundTrip);
65
+ await clickSearch(page);
66
+ }
67
+ /**
68
+ * Expands "Show Advanced Controls" and applies cabin/stops/routing/extension.
69
+ * All controls live behind this panel; we open it only when a control is set.
70
+ * Routing/extension are per-slice: Matrix mirrors them on the return slice
71
+ * (formcontrolname `routingRet`/`extRet`) for symmetric round-trips.
72
+ */
73
+ async function setAdvancedControls(page, spec, roundTrip) {
74
+ if (!hasAdvancedControls(spec))
75
+ return;
76
+ await openAdvancedPanel(page);
77
+ await applyCabinStops(page, spec);
78
+ if (spec.routing) {
79
+ await fillFormControl(page, "routing", spec.routing);
80
+ if (roundTrip)
81
+ await fillFormControl(page, "routingRet", spec.routing);
82
+ }
83
+ if (spec.ext) {
84
+ await fillFormControl(page, "ext", spec.ext);
85
+ if (roundTrip)
86
+ await fillFormControl(page, "extRet", spec.ext);
87
+ }
88
+ }
89
+ /**
90
+ * Adds "Add another flight" rows until the form has `count` legs. Matrix shows
91
+ * two legs by default, so we only click for the extras.
92
+ */
93
+ async function ensureLegRows(page, count) {
94
+ const addLeg = page.getByRole("button", { name: /add (another )?flight/i });
95
+ for (let existing = 2; existing < count; existing++) {
96
+ await addLeg.click();
97
+ }
98
+ }
99
+ /** Multi-city: shared cabin/stops globally, routing/ext per leg by row index. */
100
+ async function setMultiCityAdvanced(page, spec) {
101
+ const perLeg = spec.slices.some((s) => s.routing || s.ext);
102
+ if (!hasGlobalControls(spec) && !perLeg)
103
+ return;
104
+ await openAdvancedPanel(page);
105
+ await applyCabinStops(page, spec);
106
+ for (const [i, leg] of spec.slices.entries()) {
107
+ if (leg.routing)
108
+ await fillIndexedControl(page, "routing", i, leg.routing);
109
+ if (leg.ext)
110
+ await fillIndexedControl(page, "ext", i, leg.ext);
111
+ }
112
+ }
113
+ /**
114
+ * Calendar: shared cabin/stops plus optional single routing/ext. Routing/ext are
115
+ * per-slice, so for a round-trip calendar they are mirrored onto the return slice
116
+ * (`routingRet`/`extRet`), matching `setAdvancedControls`.
117
+ */
118
+ async function setGlobalControls(page, opts, routing, ext, roundTrip = false) {
119
+ if (!hasGlobalControls(opts) && !routing && !ext)
120
+ return;
121
+ await openAdvancedPanel(page);
122
+ await applyCabinStops(page, opts);
123
+ if (routing) {
124
+ await fillFormControl(page, "routing", routing);
125
+ if (roundTrip)
126
+ await fillFormControl(page, "routingRet", routing);
127
+ }
128
+ if (ext) {
129
+ await fillFormControl(page, "ext", ext);
130
+ if (roundTrip)
131
+ await fillFormControl(page, "extRet", ext);
132
+ }
133
+ }
134
+ function hasGlobalControls(opts) {
135
+ return Boolean(opts.cabin || opts.stops || opts.extraStops);
136
+ }
137
+ async function openAdvancedPanel(page) {
138
+ await page.getByRole("button", { name: /show advanced controls/i }).click();
139
+ }
140
+ async function applyCabinStops(page, opts) {
141
+ if (opts.cabin)
142
+ await selectOption(page, "cabin", CABIN_LABELS[opts.cabin]);
143
+ if (opts.stops)
144
+ await selectOption(page, "stops", STOPS_LABELS[opts.stops]);
145
+ if (opts.extraStops) {
146
+ await selectOption(page, "extraStops", EXTRA_STOPS_LABELS[opts.extraStops]);
147
+ }
148
+ }
149
+ /** Per-leg routing/ext share a formcontrolname across rows; target by index. */
150
+ async function fillIndexedControl(page, control, index, value) {
151
+ const input = page.locator(`input[formcontrolname="${control}"]`).nth(index);
152
+ await input.fill(value);
153
+ await input.dispatchEvent("change");
154
+ await input.evaluate((el) => el.blur());
155
+ }
156
+ /** Open a Material <mat-select> by formcontrolname and click the labelled option. */
157
+ async function selectOption(page, control, label) {
158
+ await page.locator(`mat-select[formcontrolname="${control}"]`).click();
159
+ await page.getByRole("option", { name: label, exact: true }).click();
160
+ }
161
+ async function fillFormControl(page, control, value) {
162
+ const input = page.locator(`input[formcontrolname="${control}"]`);
163
+ await input.fill(value);
164
+ await input.dispatchEvent("change");
165
+ await input.evaluate((el) => el.blur());
166
+ }
167
+ async function selectTripType(page, roundTrip) {
168
+ const name = roundTrip ? "Round Trip" : "One Way";
169
+ await page.getByRole("tab", { name, exact: true }).click();
170
+ }
171
+ /**
172
+ * Fills the nth "Add airport" combobox. Search uses 0/1 (origin/dest);
173
+ * multi-city uses 2·leg / 2·leg+1.
174
+ */
175
+ async function fillAirport(page, index, code) {
176
+ const input = page.getByRole("combobox", { name: "Add airport" }).nth(index);
177
+ await input.click();
178
+ await input.fill("");
179
+ await input.type(code, { delay: 60 });
180
+ const option = page.getByRole("option").first();
181
+ await option.waitFor({ state: "visible", timeout: 15_000 });
182
+ await option.click();
183
+ }
184
+ /**
185
+ * The start/end inputs share one Material date-range picker (a single calendar
186
+ * overlay). `fill()` writes the value without a pointer click, so the overlay
187
+ * backdrop can't intercept it; clicking the backdrop afterwards commits the
188
+ * typed values (blur) and closes the calendar — Escape would cancel them.
189
+ * Values are M/D/YYYY (en-US locale).
190
+ */
191
+ async function fillDates(page, departIso, returnIso) {
192
+ if (returnIso) {
193
+ // Round-trip: a date-range picker with start/end inner inputs.
194
+ await commitDate(page.locator("input.mat-start-date"), departIso);
195
+ await commitDate(page.locator("input.mat-end-date"), returnIso);
196
+ }
197
+ else {
198
+ // One-way: a single datepicker input.
199
+ await commitDate(page.locator("input.mat-datepicker-input"), departIso);
200
+ }
201
+ await dismissOverlay(page);
202
+ }
203
+ /** Clicks the calendar backdrop (if open) to commit typed dates and close it. */
204
+ async function dismissOverlay(page) {
205
+ const backdrop = page.locator(".cdk-overlay-backdrop").last();
206
+ if (await backdrop.count())
207
+ await backdrop.click({ force: true });
208
+ }
209
+ /** fill() sets the value; blur fires Material's date parse/validation. */
210
+ async function commitDate(input, isoDate) {
211
+ await input.fill(toUsDate(isoDate));
212
+ await input.dispatchEvent("change");
213
+ await input.evaluate((el) => el.blur());
214
+ }
215
+ function toUsDate(iso) {
216
+ const [y, m, d] = iso.split("-");
217
+ return `${Number(m)}/${Number(d)}/${y}`;
218
+ }
219
+ async function setAdults(page, adults) {
220
+ if (adults === 1)
221
+ return; // Matrix default.
222
+ // Exclude the calendar trip-length field, which is also a numeric input and
223
+ // would otherwise be matched first, swapping the nights and adults values.
224
+ const input = page
225
+ .locator('input[type="number"]:not([formcontrolname="tripLength"])')
226
+ .first();
227
+ await input.fill(String(adults));
228
+ await input.dispatchEvent("change");
229
+ }
230
+ async function clickSearch(page) {
231
+ await page.getByRole("button", { name: /search/i }).last().click();
232
+ }
233
+ //# sourceMappingURL=forms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forms.js","sourceRoot":"","sources":["../../src/browser/forms.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAU,EAAE,IAAgB;IAChE,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAChF,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IACjD,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAU,EACV,IAAmB;IAEnB,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACzE,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAE9C,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7C,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,oBAAoB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAU,EACV,IAAkB;IAElB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IAC1C,MAAM,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEtC,MAAM,IAAI;SACP,SAAS,CAAC,+BAA+B,CAAC;SAC1C,KAAK,EAAE;SACP,KAAK,EAAE,CAAC;IAEX,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACxE,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpE,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC;QAChE,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACxC,MAAM,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IACD,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACvE,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,mBAAmB,CAChC,IAAU,EACV,IAAgB,EAChB,SAAkB;IAElB,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC;QAAE,OAAO;IAEvC,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,SAAS;YAAE,MAAM,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,MAAM,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,SAAS;YAAE,MAAM,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,aAAa,CAAC,IAAU,EAAE,KAAa;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC,CAAC;IAC5E,KAAK,IAAI,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC;QACpD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,KAAK,UAAU,oBAAoB,CAAC,IAAU,EAAE,IAAmB;IACjE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO;IAEhD,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7C,IAAI,GAAG,CAAC,OAAO;YAAE,MAAM,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3E,IAAI,GAAG,CAAC,GAAG;YAAE,MAAM,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,iBAAiB,CAC9B,IAAU,EACV,IAAiB,EACjB,OAAgB,EAChB,GAAY,EACZ,SAAS,GAAG,KAAK;IAEjB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG;QAAE,OAAO;IAEzD,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAChD,IAAI,SAAS;YAAE,MAAM,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,SAAS;YAAE,MAAM,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAiB;IAC1C,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;AAC9D,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,IAAU;IACzC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AAC9E,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,IAAU,EAAE,IAAiB;IAC1D,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5E,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5E,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,KAAK,UAAU,kBAAkB,CAC/B,IAAU,EACV,OAAe,EACf,KAAa,EACb,KAAa;IAEb,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,0BAA0B,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAE,EAAuB,CAAC,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,qFAAqF;AACrF,KAAK,UAAU,YAAY,CAAC,IAAU,EAAE,OAAe,EAAE,KAAa;IACpE,MAAM,IAAI,CAAC,OAAO,CAAC,+BAA+B,OAAO,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;IACvE,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,IAAU,EAAE,OAAe,EAAE,KAAa;IACvE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,0BAA0B,OAAO,IAAI,CAAC,CAAC;IAClE,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAE,EAAuB,CAAC,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,IAAU,EAAE,SAAkB;IAC1D,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,WAAW,CAAC,IAAU,EAAE,KAAa,EAAE,IAAY;IAChE,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACpB,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrB,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;IAChD,MAAM,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5D,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,SAAS,CAAC,IAAU,EAAE,SAAiB,EAAE,SAAkB;IACxE,IAAI,SAAS,EAAE,CAAC;QACd,+DAA+D;QAC/D,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC,CAAC;QAClE,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,sCAAsC;QACtC,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,4BAA4B,CAAC,EAAE,SAAS,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,iFAAiF;AACjF,KAAK,UAAU,cAAc,CAAC,IAAU;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9D,IAAI,MAAM,QAAQ,CAAC,KAAK,EAAE;QAAE,MAAM,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACpE,CAAC;AAED,0EAA0E;AAC1E,KAAK,UAAU,UAAU,CACvB,KAA2C,EAC3C,OAAe;IAEf,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACpC,MAAM,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAE,EAAuB,CAAC,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAU,EAAE,MAAc;IACjD,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,kBAAkB;IAC5C,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,KAAK,GAAG,IAAI;SACf,OAAO,CAAC,0DAA0D,CAAC;SACnE,KAAK,EAAE,CAAC;IACX,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,MAAM,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;AACrE,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { CalendarSpec, MultiCitySpec, SearchSpec } from "../model/spec.js";
2
+ import { type CalendarResponse, type SearchResponse } from "../model/types.js";
3
+ export interface SessionOptions {
4
+ /** Show the browser window (debugging). Default headless. */
5
+ headful?: boolean;
6
+ /** Max time to wait for the /v1/search response. Matrix is slow (40–60s). */
7
+ timeoutMs?: number;
8
+ }
9
+ /**
10
+ * One-shot Matrix search: launch Chromium, drive the form, intercept the
11
+ * `/v1/search` response, and return the parsed payload.
12
+ *
13
+ * Bundled headless Chromium works with light stealth (real Chrome UA, hide
14
+ * navigator.webdriver) — Google's BotGuard JS still runs and mints the token.
15
+ */
16
+ export declare function runSearch(spec: SearchSpec, opts?: SessionOptions): Promise<SearchResponse>;
17
+ /** Multi-city search (DESIGN P3): same `solutionList` response as `runSearch`. */
18
+ export declare function runMultiCity(spec: MultiCitySpec, opts?: SessionOptions): Promise<SearchResponse>;
19
+ /** Price-calendar search (DESIGN P3): lowest fare per departure date. */
20
+ export declare function runCalendar(spec: CalendarSpec, opts?: SessionOptions): Promise<CalendarResponse>;
@@ -0,0 +1,126 @@
1
+ import { chromium } from "playwright";
2
+ import { driveCalendarForm, driveMultiCityForm, driveSearchForm } from "./forms.js";
3
+ import { extractCalendarPayload, extractSearchPayload } from "./batch.js";
4
+ import { parseCalendarResponse, parseSearchResponse, } from "../model/types.js";
5
+ const SEARCH_URL = "https://matrix.itasoftware.com/search";
6
+ // Results arrive in a multipart `/batch` response (not the documented
7
+ // `/v1/search`); the relevant part carries a `solutionList` JSON body.
8
+ const SEARCH_API_RE = /content-alkalimatrix-pa\.googleapis\.com\/batch/;
9
+ const DEFAULT_TIMEOUT_MS = 120_000;
10
+ // A real desktop-Chrome UA (no "Headless"); BotGuard rejects the headless UA.
11
+ const STEALTH_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
12
+ "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
13
+ /**
14
+ * One-shot Matrix search: launch Chromium, drive the form, intercept the
15
+ * `/v1/search` response, and return the parsed payload.
16
+ *
17
+ * Bundled headless Chromium works with light stealth (real Chrome UA, hide
18
+ * navigator.webdriver) — Google's BotGuard JS still runs and mints the token.
19
+ */
20
+ export async function runSearch(spec, opts = {}) {
21
+ return runDriver((page) => driveSearchForm(page, spec), extractSearchPayload, parseSearchResponse, opts);
22
+ }
23
+ /** Multi-city search (DESIGN P3): same `solutionList` response as `runSearch`. */
24
+ export async function runMultiCity(spec, opts = {}) {
25
+ return runDriver((page) => driveMultiCityForm(page, spec), extractSearchPayload, parseSearchResponse, opts);
26
+ }
27
+ /** Price-calendar search (DESIGN P3): lowest fare per departure date. */
28
+ export async function runCalendar(spec, opts = {}) {
29
+ return runDriver((page) => driveCalendarForm(page, spec), extractCalendarPayload, parseCalendarResponse, opts);
30
+ }
31
+ /**
32
+ * Shared driver: launch Chromium, drive the form, intercept the first `/batch`
33
+ * response that `match` accepts, and parse it. All P1–P3 commands share this;
34
+ * they differ only in how the form is driven and which payload is extracted.
35
+ */
36
+ async function runDriver(drive, match, parse, opts) {
37
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
38
+ const browser = await launch(opts.headful ?? false);
39
+ const page = await newStealthPage(browser);
40
+ const waiter = waitForResponse(page, match, timeoutMs);
41
+ // Never let an unobserved rejection (e.g. browser closed on form failure)
42
+ // crash the process; the real error is surfaced below.
43
+ waiter.promise.catch(() => { });
44
+ try {
45
+ try {
46
+ await page.goto(SEARCH_URL, { waitUntil: "domcontentloaded", timeout: 60_000 });
47
+ await drive(page);
48
+ }
49
+ catch {
50
+ // Navigation/form-driving failures are Playwright errors that leak
51
+ // selectors and local paths; surface a concise, user-safe message.
52
+ throw new Error("Failed to drive the Matrix search form (the site may have changed). Run with --headful to debug.");
53
+ }
54
+ const raw = await waiter.promise;
55
+ return parse(raw);
56
+ }
57
+ finally {
58
+ // Cancel first: a form/nav failure must clear the pending timeout so the
59
+ // process can exit immediately instead of waiting out `timeoutMs`.
60
+ waiter.cancel();
61
+ await browser.close();
62
+ }
63
+ }
64
+ async function launch(headful) {
65
+ try {
66
+ return await chromium.launch({
67
+ headless: !headful,
68
+ args: ["--disable-blink-features=AutomationControlled"],
69
+ });
70
+ }
71
+ catch (err) {
72
+ const msg = err instanceof Error ? err.message : String(err);
73
+ if (/Executable doesn't exist|playwright install/i.test(msg)) {
74
+ throw new Error("Chromium is not installed. Run: npx playwright install chromium");
75
+ }
76
+ // Raw Playwright launch errors leak the browser command and local paths;
77
+ // surface a concise, user-safe message instead.
78
+ throw new Error("Failed to launch the browser. Run with --headful to debug, or reinstall Chromium: npx playwright install chromium");
79
+ }
80
+ }
81
+ async function newStealthPage(browser) {
82
+ const context = await browser.newContext({
83
+ userAgent: STEALTH_UA,
84
+ viewport: { width: 1280, height: 900 },
85
+ });
86
+ await context.addInitScript(() => {
87
+ Object.defineProperty(navigator, "webdriver", { get: () => undefined });
88
+ });
89
+ return context.newPage();
90
+ }
91
+ /**
92
+ * Resolves with the payload from the first `/batch` response whose body `match`
93
+ * accepts. Many `/batch` calls fire (autocomplete, facets), so we inspect
94
+ * bodies rather than match on URL alone. The returned `cancel` must be called
95
+ * once the caller is done (success or failure) to clear the pending timeout.
96
+ */
97
+ function waitForResponse(page, match, timeoutMs) {
98
+ let cancel = () => { };
99
+ const promise = new Promise((resolve, reject) => {
100
+ const onResponse = (res) => {
101
+ if (!SEARCH_API_RE.test(res.url()) || res.status() !== 200)
102
+ return;
103
+ res
104
+ .text()
105
+ .then((body) => {
106
+ const payload = match(body);
107
+ if (!payload)
108
+ return;
109
+ cancel();
110
+ resolve(payload);
111
+ })
112
+ .catch(() => { });
113
+ };
114
+ const timer = setTimeout(() => {
115
+ cancel();
116
+ reject(new Error(`Timed out after ${timeoutMs}ms waiting for search results`));
117
+ }, timeoutMs);
118
+ cancel = () => {
119
+ clearTimeout(timer);
120
+ page.off("response", onResponse);
121
+ };
122
+ page.on("response", onResponse);
123
+ });
124
+ return { promise, cancel: () => cancel() };
125
+ }
126
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/browser/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * File-based result cache. Matrix queries take 30–60 s and are driven by a
3
+ * browser, so repeat queries for the same spec are expensive; caching the raw
4
+ * parsed response makes them instant (DESIGN P4). The cache key is the spec, so
5
+ * output format is irrelevant — render happens after the cache layer.
6
+ */
7
+ export interface CacheOptions {
8
+ /** When false, `withCache` always calls the producer and skips read/write. */
9
+ enabled: boolean;
10
+ /** Entries older than this are treated as misses. */
11
+ ttlMs: number;
12
+ }
13
+ export declare const DEFAULT_CACHE_TTL_MINUTES = 60;
14
+ /** `$XDG_CACHE_HOME/itamatrix` → `~/.cache/itamatrix` → `$TMPDIR/itamatrix`. */
15
+ export declare function cacheDir(): string;
16
+ /**
17
+ * Return a cached response for `keyParts` if present and fresh, otherwise call
18
+ * `produce`, cache its result, and return it. Caching is best-effort: any I/O
19
+ * error degrades to calling `produce` directly.
20
+ */
21
+ export declare function withCache<T>(namespace: string, keyParts: unknown, opts: CacheOptions, produce: () => Promise<T>): Promise<T>;
package/dist/cache.js ADDED
@@ -0,0 +1,71 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
3
+ import { homedir, tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ export const DEFAULT_CACHE_TTL_MINUTES = 60;
6
+ /** `$XDG_CACHE_HOME/itamatrix` → `~/.cache/itamatrix` → `$TMPDIR/itamatrix`. */
7
+ export function cacheDir() {
8
+ const xdg = process.env.XDG_CACHE_HOME;
9
+ if (xdg)
10
+ return join(xdg, "itamatrix");
11
+ const home = homedir();
12
+ if (home)
13
+ return join(home, ".cache", "itamatrix");
14
+ return join(tmpdir(), "itamatrix");
15
+ }
16
+ function hashKey(namespace, keyParts) {
17
+ const digest = createHash("sha256")
18
+ .update(namespace)
19
+ .update("\0")
20
+ .update(JSON.stringify(keyParts))
21
+ .digest("hex");
22
+ return `${namespace}-${digest}`;
23
+ }
24
+ function entryPath(key) {
25
+ return join(cacheDir(), `${key}.json`);
26
+ }
27
+ function readEntry(key, ttlMs, now) {
28
+ const path = entryPath(key);
29
+ if (!existsSync(path))
30
+ return null;
31
+ try {
32
+ const entry = JSON.parse(readFileSync(path, "utf8"));
33
+ if (typeof entry?.savedAt !== "number" || !("value" in entry))
34
+ return null;
35
+ if (now - entry.savedAt > ttlMs)
36
+ return null;
37
+ return entry.value;
38
+ }
39
+ catch {
40
+ // Corrupt/unreadable cache file: treat as a miss, never fail the query.
41
+ return null;
42
+ }
43
+ }
44
+ function writeEntry(key, value, now) {
45
+ try {
46
+ mkdirSync(cacheDir(), { recursive: true });
47
+ const entry = { savedAt: now, value };
48
+ writeFileSync(entryPath(key), JSON.stringify(entry));
49
+ }
50
+ catch {
51
+ // Cache is best-effort; a write failure must not break the command.
52
+ }
53
+ }
54
+ /**
55
+ * Return a cached response for `keyParts` if present and fresh, otherwise call
56
+ * `produce`, cache its result, and return it. Caching is best-effort: any I/O
57
+ * error degrades to calling `produce` directly.
58
+ */
59
+ export async function withCache(namespace, keyParts, opts, produce) {
60
+ if (!opts.enabled)
61
+ return produce();
62
+ const key = hashKey(namespace, keyParts);
63
+ const now = Date.now();
64
+ const hit = readEntry(key, opts.ttlMs, now);
65
+ if (hit !== null)
66
+ return hit;
67
+ const value = await produce();
68
+ writeEntry(key, value, now);
69
+ return value;
70
+ }
71
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { withCache, cacheDir, DEFAULT_CACHE_TTL_MINUTES } from "./cache.js";
7
+ let dir;
8
+ let prevXdg;
9
+ beforeEach(() => {
10
+ dir = mkdtempSync(join(tmpdir(), "itamatrix-cache-"));
11
+ prevXdg = process.env.XDG_CACHE_HOME;
12
+ process.env.XDG_CACHE_HOME = dir;
13
+ });
14
+ afterEach(() => {
15
+ if (prevXdg === undefined)
16
+ delete process.env.XDG_CACHE_HOME;
17
+ else
18
+ process.env.XDG_CACHE_HOME = prevXdg;
19
+ rmSync(dir, { recursive: true, force: true });
20
+ });
21
+ const opts = { enabled: true, ttlMs: DEFAULT_CACHE_TTL_MINUTES * 60_000 };
22
+ describe("withCache", () => {
23
+ it("returns the cache dir under XDG_CACHE_HOME", () => {
24
+ expect(cacheDir()).toBe(join(dir, "itamatrix"));
25
+ });
26
+ it("calls the producer once and serves the second call from cache", async () => {
27
+ const produce = vi.fn().mockResolvedValue({ price: 100 });
28
+ const first = await withCache("search", { o: "BOS" }, opts, produce);
29
+ const second = await withCache("search", { o: "BOS" }, opts, produce);
30
+ expect(first).toEqual({ price: 100 });
31
+ expect(second).toEqual({ price: 100 });
32
+ expect(produce).toHaveBeenCalledTimes(1);
33
+ });
34
+ it("treats a different key as a miss", async () => {
35
+ const produce = vi.fn().mockResolvedValue({ price: 100 });
36
+ await withCache("search", { o: "BOS" }, opts, produce);
37
+ await withCache("search", { o: "JFK" }, opts, produce);
38
+ expect(produce).toHaveBeenCalledTimes(2);
39
+ });
40
+ it("separates entries by namespace", async () => {
41
+ const produce = vi.fn().mockResolvedValue({ price: 100 });
42
+ await withCache("search", { x: 1 }, opts, produce);
43
+ await withCache("calendar", { x: 1 }, opts, produce);
44
+ expect(produce).toHaveBeenCalledTimes(2);
45
+ });
46
+ it("bypasses cache entirely when disabled", async () => {
47
+ const produce = vi.fn().mockResolvedValue({ price: 100 });
48
+ const off = { enabled: false, ttlMs: 60_000 };
49
+ await withCache("search", { o: "BOS" }, off, produce);
50
+ await withCache("search", { o: "BOS" }, off, produce);
51
+ expect(produce).toHaveBeenCalledTimes(2);
52
+ });
53
+ it("treats a JSON file of the wrong shape as a miss", async () => {
54
+ const produce = vi.fn().mockResolvedValue({ price: 100 });
55
+ mkdirSync(join(dir, "itamatrix"), { recursive: true });
56
+ // Mirror the key derivation: namespace + sha256(namespace \0 JSON(parts)).
57
+ const parts = { o: "BOS" };
58
+ const digest = createHash("sha256")
59
+ .update("search")
60
+ .update("\0")
61
+ .update(JSON.stringify(parts))
62
+ .digest("hex");
63
+ writeFileSync(join(dir, "itamatrix", `search-${digest}.json`), "{}");
64
+ const result = await withCache("search", parts, opts, produce);
65
+ expect(result).toEqual({ price: 100 });
66
+ expect(produce).toHaveBeenCalledTimes(1);
67
+ });
68
+ it("re-fetches once the entry is older than the TTL", async () => {
69
+ const produce = vi.fn().mockResolvedValue({ price: 100 });
70
+ const base = 1_000_000;
71
+ vi.spyOn(Date, "now").mockReturnValue(base);
72
+ await withCache("search", { o: "BOS" }, { enabled: true, ttlMs: 1000 }, produce);
73
+ vi.spyOn(Date, "now").mockReturnValue(base + 2000);
74
+ await withCache("search", { o: "BOS" }, { enabled: true, ttlMs: 1000 }, produce);
75
+ expect(produce).toHaveBeenCalledTimes(2);
76
+ vi.restoreAllMocks();
77
+ });
78
+ });
79
+ //# sourceMappingURL=cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.test.js","sourceRoot":"","sources":["../src/cache.test.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ export declare const program: Command;