@happyvertical/weather 0.79.0 → 0.80.1

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.
package/dist/index.js CHANGED
@@ -1,1377 +1,912 @@
1
- import { loadEnvConfig as M } from "@happyvertical/utils";
2
- class u extends Error {
3
- constructor(e, r, t, i) {
4
- super(e), this.provider = r, this.code = t, this.details = i, this.name = "WeatherError";
5
- }
6
- }
7
- class C extends u {
8
- constructor(e, r) {
9
- super(
10
- `Rate limit exceeded for ${e}${r ? `. Retry after ${r}s` : ""}`,
11
- e,
12
- "RATE_LIMIT"
13
- ), this.name = "RateLimitError";
14
- }
15
- }
16
- class g extends u {
17
- constructor(e, r) {
18
- super(
19
- r || `Authentication failed for ${e}`,
20
- e,
21
- "AUTH_ERROR"
22
- ), this.name = "AuthenticationError";
23
- }
24
- }
25
- class D extends u {
26
- constructor(e, r, t, i) {
27
- super(
28
- i || `Location (${r}, ${t}) is invalid or unsupported`,
29
- e,
30
- "INVALID_LOCATION",
31
- { latitude: r, longitude: t }
32
- ), this.name = "InvalidLocationError";
33
- }
34
- }
35
- class w extends u {
36
- constructor(e, r, t) {
37
- super(
38
- `No forecast data available for location (${r}, ${t})`,
39
- e,
40
- "NO_RESULTS",
41
- { latitude: r, longitude: t }
42
- ), this.name = "NoResultsError";
43
- }
44
- }
45
- class P extends u {
46
- constructor(e, r, t) {
47
- super(
48
- t || `${e} does not support weather capability: ${r}`,
49
- e,
50
- "UNSUPPORTED_CAPABILITY",
51
- { capability: r }
52
- ), this.name = "UnsupportedWeatherCapabilityError";
53
- }
54
- }
55
- const k = 3600 * 1e3;
56
- function S(o, e) {
57
- const r = e.interval || "hourly";
58
- if (r !== "hourly")
59
- throw new u(
60
- `Unsupported historical weather interval: ${r}`,
61
- o,
62
- "UNSUPPORTED_INTERVAL",
63
- { interval: r }
64
- );
65
- const t = O(o, e.start, "start"), i = O(
66
- o,
67
- e.end || e.start,
68
- "end"
69
- );
70
- if (i.getTime() < t.getTime())
71
- throw new u(
72
- "Historical weather end time must be after start time",
73
- o,
74
- "INVALID_DATE_RANGE",
75
- {
76
- start: t.toISOString(),
77
- end: i.toISOString()
78
- }
79
- );
80
- return { start: t, end: i };
81
- }
82
- function R(o, e, r) {
83
- const t = o.filter(
84
- (i) => i.timestamp.getTime() >= e.start.getTime() && i.timestamp.getTime() <= e.end.getTime()
85
- ).sort((i, n) => i.timestamp.getTime() - n.timestamp.getTime());
86
- return r && r > 0 ? t.slice(0, r) : t;
87
- }
88
- function F(o, e) {
89
- return Math.max(
90
- 1,
91
- // +1 ensures the requested window is fully covered before filtering.
92
- Math.ceil((e.getTime() - o.getTime()) / k) + 1
93
- );
94
- }
95
- function _(o) {
96
- return o.toISOString().slice(0, 10);
97
- }
98
- function O(o, e, r) {
99
- const t = e instanceof Date ? e : new Date(e);
100
- if (Number.isNaN(t.getTime()))
101
- throw new u(
102
- `Invalid historical weather ${r} time`,
103
- o,
104
- "INVALID_DATE_RANGE",
105
- { [r]: e }
106
- );
107
- return t;
108
- }
109
- function K(o, e) {
110
- return typeof o != "number" || typeof e != "number" ? { valid: !1, error: "Coordinates must be numbers" } : Number.isNaN(o) || Number.isNaN(e) ? { valid: !1, error: "Coordinates cannot be NaN" } : o < -90 || o > 90 ? {
111
- valid: !1,
112
- error: `Invalid latitude: ${o} (must be between -90 and 90)`
113
- } : e < -180 || e > 180 ? {
114
- valid: !1,
115
- error: `Invalid longitude: ${e} (must be between -180 and 180)`
116
- } : { valid: !0 };
117
- }
118
- function p(o, e, r) {
119
- const t = K(e, r);
120
- if (!t.valid)
121
- throw new D(
122
- o,
123
- e,
124
- r,
125
- t.error
126
- );
127
- }
128
- function te(o) {
129
- return o - 273.15;
130
- }
131
- function re(o) {
132
- return (o - 32) * 5 / 9;
133
- }
134
- function d(o) {
135
- return o * 3.6;
136
- }
137
- function ie(o) {
138
- return o * 1.60934;
139
- }
140
- function v(o) {
141
- return o / 1e3;
142
- }
143
- function ne(o) {
144
- return o * 1.60934;
145
- }
146
- function m(o) {
147
- return Math.round(o);
148
- }
149
- function N(o, e) {
150
- return o >= 41.7 && o <= 83.1 && e >= -141 && e <= -52.6;
151
- }
152
- function x(o, e, r, t) {
153
- const n = A(r - o), a = A(t - e), s = Math.sin(n / 2) * Math.sin(n / 2) + Math.cos(A(o)) * Math.cos(A(r)) * Math.sin(a / 2) * Math.sin(a / 2);
154
- return 6371 * (2 * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s)));
155
- }
156
- function A(o) {
157
- return o * Math.PI / 180;
158
- }
159
- class W {
160
- name = "Environment Canada";
161
- providerType = "government";
162
- apiBase = "https://api.weather.gc.ca/collections/citypageweather-realtime/items";
163
- climateHourlyApiBase = "https://api.weather.gc.ca/collections/climate-hourly/items";
164
- timeout;
165
- constructor(e = {}) {
166
- this.timeout = e.timeout || 1e4;
167
- }
168
- /**
169
- * Fetch weather forecasts for a location
170
- */
171
- async fetchForLocation(e, r, t) {
172
- if (p(this.name, e, r), !N(e, r))
173
- throw new D(
174
- this.name,
175
- e,
176
- r,
177
- "Environment Canada only supports Canadian locations"
178
- );
179
- try {
180
- const i = this.buildApiUrl(e, r), n = new AbortController(), a = setTimeout(
181
- () => n.abort(),
182
- t?.timeout || this.timeout
183
- ), s = await fetch(i, { signal: n.signal });
184
- if (clearTimeout(a), !s.ok)
185
- throw new u(
186
- `API request failed: ${s.statusText}`,
187
- this.name,
188
- `HTTP_${s.status}`
189
- );
190
- const c = await s.json(), h = this.transformForecasts(c, e, r);
191
- return t?.limit && t.limit > 0 ? h.slice(0, t.limit) : h;
192
- } catch (i) {
193
- throw i instanceof u ? i : i.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
194
- `Failed to fetch weather data: ${i.message}`,
195
- this.name,
196
- "FETCH_ERROR"
197
- );
198
- }
199
- }
200
- async fetchHistoricalForLocation(e, r, t) {
201
- if (p(this.name, e, r), !N(e, r))
202
- throw new D(
203
- this.name,
204
- e,
205
- r,
206
- "Environment Canada historical climate observations only support Canadian locations"
207
- );
208
- const i = S(this.name, t);
209
- try {
210
- const n = this.buildClimateHourlyUrl(e, r, i), a = await this.fetchClimateHourlyPages(
211
- n,
212
- i,
213
- t.timeout
214
- ), s = this.transformClimateHourly(
215
- a,
216
- e,
217
- r,
218
- i
219
- ), c = t.limit && t.limit > 0 ? s.slice(0, t.limit) : s;
220
- if (c.length === 0)
221
- throw new w(this.name, e, r);
222
- return c;
223
- } catch (n) {
224
- throw n instanceof u ? n : n.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
225
- `Failed to fetch historical weather data: ${n.message}`,
226
- this.name,
227
- "FETCH_ERROR"
228
- );
229
- }
230
- }
231
- /**
232
- * Build Environment Canada API URL for given coordinates
233
- */
234
- buildApiUrl(e, r) {
235
- const t = new URLSearchParams({
236
- f: "json",
237
- bbox: `${r - 1},${e - 1},${r + 1},${e + 1}`,
238
- limit: "1"
239
- // Get closest city only
240
- });
241
- return `${this.apiBase}?${t.toString()}`;
242
- }
243
- buildClimateHourlyUrl(e, r, t) {
244
- const i = new Date(t.start.getTime() - 864e5), n = new Date(t.end.getTime() + 1440 * 60 * 1e3), a = 1, s = new URLSearchParams({
245
- f: "json",
246
- bbox: `${r - a},${e - a},${r + a},${e + a}`,
247
- datetime: `${_(i)}/${_(n)}`,
248
- limit: "2000"
249
- });
250
- return `${this.climateHourlyApiBase}?${s.toString()}`;
251
- }
252
- async fetchClimateHourlyPages(e, r, t) {
253
- const i = [], n = /* @__PURE__ */ new Set();
254
- let a = e, s = 0;
255
- for (; a; ) {
256
- if (n.has(a))
257
- throw new u(
258
- "Environment Canada climate pagination loop detected",
259
- this.name,
260
- "PAGINATION_LOOP"
261
- );
262
- if (n.add(a), s += 1, s > 50)
263
- throw new u(
264
- "Environment Canada climate pagination exceeded safety limit",
265
- this.name,
266
- "PAGINATION_LIMIT"
267
- );
268
- const c = await this.fetchClimateHourlyPage(a, t);
269
- i.push(
270
- ...(c.features || []).filter(
271
- (h) => this.isClimateFeatureInWindow(h, r)
272
- )
273
- ), a = c.links?.find(
274
- (h) => h.rel === "next" && h.href
275
- )?.href;
276
- }
277
- return {
278
- type: "FeatureCollection",
279
- features: i
280
- };
281
- }
282
- isClimateFeatureInWindow(e, r) {
283
- const t = e.properties.UTC_DATE;
284
- if (!t)
285
- return !1;
286
- const i = U(t);
287
- return Number.isNaN(i.getTime()) ? !1 : i.getTime() >= r.start.getTime() && i.getTime() <= r.end.getTime();
288
- }
289
- async fetchClimateHourlyPage(e, r) {
290
- const t = new AbortController(), i = setTimeout(
291
- () => t.abort(),
292
- r || this.timeout
293
- );
294
- try {
295
- const n = await fetch(e, { signal: t.signal });
296
- if (!n.ok)
297
- throw new u(
298
- `API request failed: ${n.statusText}`,
299
- this.name,
300
- `HTTP_${n.status}`
301
- );
302
- return await n.json();
303
- } finally {
304
- clearTimeout(i);
305
- }
306
- }
307
- /**
308
- * Transform Environment Canada data into WeatherForecast objects
309
- */
310
- transformForecasts(e, r, t) {
311
- if (!e.features || e.features.length === 0)
312
- throw new w(this.name, r, t);
313
- const i = [], a = e.features[0].properties, s = a.currentConditions;
314
- s && i.push({
315
- timestamp: new Date(s.timestamp?.en || /* @__PURE__ */ new Date()),
316
- temperature: s.temperature?.value?.en || 0,
317
- humidity: s.relativeHumidity?.value?.en || 0,
318
- windSpeed: Number(s.wind?.speed?.value?.en) || 0,
319
- windDirection: s.wind?.bearing?.value?.en,
320
- pressure: (s.pressure?.value?.en || 0) * 10,
321
- // Convert kPa to hPa
322
- conditions: s.condition?.en || "Unknown",
323
- confidence: 100,
324
- // Current conditions are observed
325
- raw: {
326
- source: "environment-canada-citypage",
327
- currentConditions: s
328
- }
329
- });
330
- const c = a.forecastGroup;
331
- if (c && c.forecasts) {
332
- let h = /* @__PURE__ */ new Date();
333
- for (const l of c.forecasts) {
334
- const f = l.period, b = l.temperatures?.temperature || [], y = b.find((T) => T.class?.en === "high"), I = b.find((T) => T.class?.en === "low");
335
- f?.textForecastName?.en?.toLowerCase() === "tonight" || f?.value?.en?.toLowerCase().includes("night") ? h.setHours(18, 0, 0, 0) : h.setHours(6, 0, 0, 0), i.push({
336
- timestamp: new Date(h),
337
- temperature: y?.value?.en || I?.value?.en || 0,
338
- temperatureMax: y?.value?.en,
339
- temperatureMin: I?.value?.en,
340
- humidity: l.relativeHumidity?.value?.en || 0,
341
- windSpeed: Number(l.winds?.periods?.[0]?.speed?.value?.en) || 0,
342
- windDirection: l.winds?.periods?.[0]?.bearing?.value?.en,
343
- conditions: l.cloudPrecip?.en || l.textSummary?.en || "Unknown",
344
- precipAmount: l.precipitation?.accumulation?.amount?.value?.en,
345
- confidence: 90,
346
- raw: {
347
- source: "environment-canada-citypage",
348
- periodName: f?.textForecastName?.en || f?.value?.en,
349
- period: l
350
- }
351
- }), f?.textForecastName?.en?.toLowerCase().includes("night") ? h = new Date(h.getTime() + 720 * 60 * 1e3) : h = new Date(h.getTime() + 1440 * 60 * 1e3);
352
- }
353
- }
354
- return i;
355
- }
356
- transformClimateHourly(e, r, t, i) {
357
- if (!e.features || e.features.length === 0)
358
- throw new w(this.name, r, t);
359
- const n = /* @__PURE__ */ new Map();
360
- for (const s of e.features) {
361
- const c = s.properties, h = c.UTC_DATE ? U(c.UTC_DATE) : null;
362
- if (!h || Number.isNaN(h.getTime()))
363
- continue;
364
- const l = c.LATITUDE_DECIMAL_DEGREES || s.geometry?.coordinates[1], f = c.LONGITUDE_DECIMAL_DEGREES || s.geometry?.coordinates[0];
365
- if (typeof l != "number" || typeof f != "number")
366
- continue;
367
- const b = typeof c.TEMP == "number" ? c.TEMP : void 0;
368
- if (b === void 0)
369
- continue;
370
- const y = String(
371
- c.CLIMATE_IDENTIFIER || c.STN_ID || c.STATION_NAME || "nearby"
372
- ), I = x(
373
- r,
374
- t,
375
- l,
376
- f
377
- ), T = c.WEATHER_ENG_DESC || "Unknown", $ = G(
378
- c.WIND_DIRECTION
379
- ), L = {
380
- timestamp: h,
381
- temperature: m(b),
382
- feelsLike: typeof c.WINDCHILL == "number" ? m(c.WINDCHILL) : typeof c.HUMIDEX == "number" ? m(c.HUMIDEX) : void 0,
383
- humidity: m(c.RELATIVE_HUMIDITY || 0),
384
- windSpeed: m(c.WIND_SPEED || 0),
385
- windDirection: $,
386
- pressure: typeof c.STATION_PRESSURE == "number" ? m(c.STATION_PRESSURE * 10) : void 0,
387
- conditions: T.toUpperCase() === "NA" ? "Unknown" : T,
388
- visibility: typeof c.VISIBILITY == "number" ? c.VISIBILITY : void 0,
389
- precipAmount: typeof c.PRECIP_AMOUNT == "number" ? c.PRECIP_AMOUNT : void 0,
390
- confidence: 100,
391
- raw: {
392
- source: "environment-canada-climate-hourly",
393
- stationId: y,
394
- stationName: c.STATION_NAME,
395
- stationLatitude: l,
396
- stationLongitude: f,
397
- stationDistanceKm: I,
398
- observation: c
399
- }
400
- }, H = n.get(y);
401
- H ? H.forecasts.push(L) : n.set(y, { distance: I, forecasts: [L] });
402
- }
403
- const a = [...n.values()].map((s) => ({
404
- distance: s.distance,
405
- forecasts: i ? R(s.forecasts, i) : s.forecasts.sort(
406
- (c, h) => c.timestamp.getTime() - h.timestamp.getTime()
407
- )
408
- })).filter((s) => s.forecasts.length > 0).sort((s, c) => s.distance - c.distance)[0];
409
- if (!a)
410
- throw new w(this.name, r, t);
411
- return a.forecasts.sort(
412
- (s, c) => s.timestamp.getTime() - c.timestamp.getTime()
413
- );
414
- }
415
- /**
416
- * Test connection to Environment Canada API
417
- */
418
- async testConnection() {
419
- try {
420
- const e = this.buildApiUrl(51.0447, -114.0719), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
421
- return clearTimeout(t), i.ok;
422
- } catch {
423
- return !1;
424
- }
425
- }
426
- /**
427
- * Check if location is in Canada (Environment Canada coverage area)
428
- */
429
- async supportsLocation(e, r) {
430
- return {
431
- valid: !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180
432
- }.valid ? N(e, r) : !1;
433
- }
434
- }
435
- function U(o) {
436
- const e = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(o);
437
- return new Date(e ? o : `${o}Z`);
438
- }
439
- function G(o) {
440
- if (typeof o == "number")
441
- return o <= 36 ? o * 10 : o;
442
- }
443
- class q {
444
- name = "Google Weather";
445
- providerType = "commercial";
446
- apiBase = "https://weather.googleapis.com/v1";
447
- apiKey;
448
- timeout;
449
- constructor(e) {
450
- if (!e.apiKey)
451
- throw new g("Google Weather", "API key is required");
452
- this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
453
- }
454
- /**
455
- * Fetch weather forecasts for a location
456
- * Returns combined hourly (240h) and daily (10 days) forecasts
457
- */
458
- async fetchForLocation(e, r, t) {
459
- p(this.name, e, r);
460
- try {
461
- const [i, n] = await Promise.all([
462
- this.fetchHourlyForecast(e, r, t),
463
- this.fetchDailyForecast(e, r, t)
464
- ]), a = [...i, ...n];
465
- return t?.limit && t.limit > 0 ? a.slice(0, t.limit) : a;
466
- } catch (i) {
467
- throw i instanceof u ? i : new u(
468
- `Failed to fetch weather data: ${i.message}`,
469
- this.name,
470
- "FETCH_ERROR"
471
- );
472
- }
473
- }
474
- /**
475
- * Fetch current weather conditions
476
- */
477
- async fetchCurrentConditions(e, r, t) {
478
- p(this.name, e, r);
479
- const i = this.buildUrl("/currentConditions:lookup", e, r), n = await this.fetchWithTimeout(
480
- i,
481
- t?.timeout
482
- );
483
- return this.transformCurrentConditions(n);
484
- }
485
- /**
486
- * Fetch hourly forecast (up to 240 hours)
487
- *
488
- * Google Weather API paginates hourly results (24 hours per page).
489
- * This method automatically fetches all pages to get the full forecast.
490
- */
491
- async fetchHourlyForecast(e, r, t) {
492
- p(this.name, e, r);
493
- const i = t?.hours || 240, n = [];
494
- let a;
495
- do {
496
- let s = this.buildUrl(
497
- "/forecast/hours:lookup",
498
- e,
499
- r,
500
- `&hours=${i}`
501
- );
502
- a && (s += `&pageToken=${encodeURIComponent(a)}`);
503
- const c = await this.fetchWithTimeout(
504
- s,
505
- t?.timeout
506
- );
507
- c.forecastHours && n.push(...c.forecastHours), a = c.nextPageToken;
508
- } while (a && n.length < i);
509
- if (n.length === 0)
510
- throw new w(this.name, e, r);
511
- return n.slice(0, i).map((s) => this.transformHourlyForecast(s, "hourly"));
512
- }
513
- /**
514
- * Fetch daily forecast (up to 10 days)
515
- *
516
- * Google Weather API paginates daily results (5 days per page).
517
- * This method automatically fetches all pages to get the full forecast.
518
- */
519
- async fetchDailyForecast(e, r, t) {
520
- p(this.name, e, r);
521
- const i = t?.days || 10, n = [];
522
- let a;
523
- do {
524
- let s = this.buildUrl(
525
- "/forecast/days:lookup",
526
- e,
527
- r,
528
- `&days=${i}`
529
- );
530
- a && (s += `&pageToken=${encodeURIComponent(a)}`);
531
- const c = await this.fetchWithTimeout(
532
- s,
533
- t?.timeout
534
- );
535
- c.forecastDays && n.push(...c.forecastDays), a = c.nextPageToken;
536
- } while (a && n.length < i);
537
- if (n.length === 0)
538
- throw new w(this.name, e, r);
539
- return n.slice(0, i).map((s) => this.transformDailyForecast(s));
540
- }
541
- /**
542
- * Fetch hourly history (up to 24 hours)
543
- */
544
- async fetchHourlyHistory(e, r, t) {
545
- p(this.name, e, r);
546
- const i = t?.hours || 24, n = this.buildUrl(
547
- "/history/hours:lookup",
548
- e,
549
- r,
550
- `&hours=${i}`
551
- ), a = await this.fetchWithTimeout(
552
- n,
553
- t?.timeout
554
- );
555
- if (!a.historyHours || a.historyHours.length === 0)
556
- throw new w(this.name, e, r);
557
- return a.historyHours.map(
558
- (s) => this.transformHourlyForecast(s, "history")
559
- );
560
- }
561
- async fetchHistoricalForLocation(e, r, t) {
562
- p(this.name, e, r);
563
- const i = S(this.name, t), n = /* @__PURE__ */ new Date(), a = new Date(n.getTime() - 1440 * 60 * 1e3);
564
- if (i.end.getTime() > n.getTime())
565
- throw new u(
566
- "Google Weather historical lookup cannot end in the future",
567
- this.name,
568
- "INVALID_DATE_RANGE"
569
- );
570
- if (i.start.getTime() < a.getTime())
571
- throw new P(
572
- this.name,
573
- "historical-weather",
574
- "Google Weather hourly history is limited to the previous 24 hours."
575
- );
576
- const s = Math.min(24, F(i.start, n)), c = await this.fetchHourlyHistory(e, r, {
577
- ...t,
578
- hours: s
579
- }), h = R(c, i, t.limit);
580
- if (h.length === 0)
581
- throw new w(this.name, e, r);
582
- return h;
583
- }
584
- /**
585
- * Fetch weather alerts
586
- */
587
- async fetchAlerts(e, r, t) {
588
- p(this.name, e, r);
589
- const i = this.buildUrl("/publicAlerts:lookup", e, r), n = await this.fetchWithTimeout(
590
- i,
591
- t?.timeout
592
- );
593
- return !n.alerts || n.alerts.length === 0 ? [] : n.alerts.map((a) => this.transformAlert(a));
594
- }
595
- /**
596
- * Test connection to Google Weather API
597
- */
598
- async testConnection() {
599
- try {
600
- const e = this.buildUrl("/currentConditions:lookup", 37.422, -122.084), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
601
- return clearTimeout(t), i.ok;
602
- } catch {
603
- return !1;
604
- }
605
- }
606
- /**
607
- * Check if location is supported (Google Weather is global)
608
- */
609
- async supportsLocation(e, r) {
610
- return !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180;
611
- }
612
- /**
613
- * Build API URL with location and API key
614
- */
615
- buildUrl(e, r, t, i) {
616
- const n = new URLSearchParams({
617
- key: this.apiKey,
618
- "location.latitude": r.toString(),
619
- "location.longitude": t.toString()
620
- });
621
- let a = `${this.apiBase}${e}?${n.toString()}`;
622
- return i && (a += i), a;
623
- }
624
- /**
625
- * Fetch with timeout and error handling
626
- */
627
- async fetchWithTimeout(e, r) {
628
- const t = new AbortController(), i = setTimeout(
629
- () => t.abort(),
630
- r || this.timeout
631
- );
632
- try {
633
- const n = await fetch(e, { signal: t.signal });
634
- return clearTimeout(i), n.ok || await this.handleHttpError(n), await n.json();
635
- } catch (n) {
636
- throw clearTimeout(i), n instanceof u ? n : n.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
637
- `Failed to fetch weather data: ${n.message}`,
638
- this.name,
639
- "FETCH_ERROR"
640
- );
641
- }
642
- }
643
- /**
644
- * Handle HTTP error responses
645
- */
646
- async handleHttpError(e) {
647
- if (e.status === 401 || e.status === 403)
648
- throw new g(this.name, "Invalid API key");
649
- if (e.status === 429)
650
- throw new C(this.name);
651
- const r = await e.text();
652
- throw new u(
653
- `API request failed: ${e.statusText} - ${r}`,
654
- this.name,
655
- `HTTP_${e.status}`
656
- );
657
- }
658
- /**
659
- * Transform current conditions to WeatherForecast
660
- */
661
- transformCurrentConditions(e) {
662
- return {
663
- timestamp: new Date(e.currentTime),
664
- temperature: m(e.temperature.degrees),
665
- feelsLike: e.feelsLikeTemperature ? m(e.feelsLikeTemperature.degrees) : void 0,
666
- conditions: e.weatherCondition.description.text,
667
- humidity: m(e.relativeHumidity),
668
- windSpeed: m(d(e.wind.speed.value)),
669
- windDirection: e.wind.direction.degrees,
670
- windGust: e.wind.gust ? m(d(e.wind.gust.value)) : void 0,
671
- pressure: e.airPressure ? m(e.airPressure.meanSeaLevelMillibars) : void 0,
672
- cloudCover: e.cloudCover,
673
- visibility: e.visibility ? m(v(e.visibility.distance)) : void 0,
674
- precipProbability: e.precipitation?.probability?.percent,
675
- precipAmount: e.precipitation?.qpf?.quantity,
676
- confidence: 95,
677
- raw: {
678
- source: "google-weather-current",
679
- isDaytime: e.isDaytime,
680
- uvIndex: e.uvIndex,
681
- thunderstormProbability: e.thunderstormProbability,
682
- timeZone: e.timeZone.id
683
- }
684
- };
685
- }
686
- /**
687
- * Transform hourly forecast to WeatherForecast
688
- */
689
- transformHourlyForecast(e, r) {
690
- return {
691
- timestamp: new Date(e.interval.startTime),
692
- temperature: m(e.temperature.degrees),
693
- feelsLike: e.feelsLikeTemperature ? m(e.feelsLikeTemperature.degrees) : void 0,
694
- conditions: e.weatherCondition.description.text,
695
- humidity: m(e.relativeHumidity),
696
- windSpeed: m(d(e.wind.speed.value)),
697
- windDirection: e.wind.direction.degrees,
698
- windGust: e.wind.gust ? m(d(e.wind.gust.value)) : void 0,
699
- pressure: e.airPressure ? m(e.airPressure.meanSeaLevelMillibars) : void 0,
700
- cloudCover: e.cloudCover,
701
- visibility: e.visibility ? m(v(e.visibility.distance)) : void 0,
702
- precipProbability: e.precipitation?.probability?.percent,
703
- precipAmount: e.precipitation?.qpf?.quantity,
704
- confidence: r === "history" ? 100 : 95,
705
- raw: {
706
- source: `google-weather-${r}`,
707
- isDaytime: e.isDaytime,
708
- uvIndex: e.uvIndex,
709
- thunderstormProbability: e.thunderstormProbability,
710
- displayDateTime: e.displayDateTime
711
- }
712
- };
713
- }
714
- /**
715
- * Transform daily forecast to WeatherForecast
716
- */
717
- transformDailyForecast(e) {
718
- const r = e.daytimeForecast || e.nighttimeForecast;
719
- if (!r)
720
- throw new u(
721
- "Daily forecast missing both daytime and nighttime data",
722
- this.name,
723
- "INVALID_DATA"
724
- );
725
- return {
726
- timestamp: new Date(e.interval.startTime),
727
- temperature: e.maxTemperature ? m(e.maxTemperature.degrees) : m(r.temperature.degrees),
728
- temperatureMax: e.maxTemperature ? m(e.maxTemperature.degrees) : void 0,
729
- temperatureMin: e.minTemperature ? m(e.minTemperature.degrees) : void 0,
730
- feelsLike: e.feelsLikeMaxTemperature ? m(e.feelsLikeMaxTemperature.degrees) : r.feelsLikeTemperature ? m(r.feelsLikeTemperature.degrees) : void 0,
731
- conditions: r.weatherCondition.description.text,
732
- humidity: m(r.relativeHumidity),
733
- windSpeed: m(
734
- d(r.wind.speed.value)
735
- ),
736
- windDirection: r.wind.direction.degrees,
737
- windGust: r.wind.gust ? m(
738
- d(r.wind.gust.value)
739
- ) : void 0,
740
- pressure: r.airPressure ? m(r.airPressure.meanSeaLevelMillibars) : void 0,
741
- cloudCover: r.cloudCover,
742
- visibility: r.visibility ? m(v(r.visibility.distance)) : void 0,
743
- precipProbability: r.precipitation?.probability?.percent,
744
- precipAmount: r.precipitation?.qpf?.quantity,
745
- confidence: 90,
746
- raw: {
747
- source: "google-weather-daily",
748
- displayDate: e.displayDate,
749
- daytimeForecast: e.daytimeForecast,
750
- nighttimeForecast: e.nighttimeForecast
751
- }
752
- };
753
- }
754
- /**
755
- * Transform alert to WeatherAlert
756
- */
757
- transformAlert(e) {
758
- return {
759
- id: e.alertId,
760
- headline: e.headline || "Weather Alert",
761
- description: e.description || "",
762
- severity: e.severity || "unknown",
763
- startTime: e.effective ? new Date(e.effective) : /* @__PURE__ */ new Date(),
764
- endTime: e.expires ? new Date(e.expires) : /* @__PURE__ */ new Date(),
765
- raw: e
766
- };
767
- }
768
- }
769
- const B = 7200 * 60 * 1e3;
770
- class V {
771
- name = "Open-Meteo";
772
- providerType = "community";
773
- forecastApiBase = "https://api.open-meteo.com/v1/forecast";
774
- archiveApiBase = "https://archive-api.open-meteo.com/v1/archive";
775
- timeout;
776
- constructor(e = {}) {
777
- this.timeout = e.timeout || 1e4;
778
- }
779
- async fetchForLocation(e, r, t) {
780
- p(this.name, e, r);
781
- const i = this.buildBaseParams(e, r);
782
- i.set("forecast_days", "7");
783
- const n = await this.fetchJson(
784
- `${this.forecastApiBase}?${i.toString()}`,
785
- t?.timeout
786
- ), a = this.transformHourlyData(n, "open-meteo-forecast");
787
- if (a.length === 0)
788
- throw new w(this.name, e, r);
789
- return t?.limit && t.limit > 0 ? a.slice(0, t.limit) : a;
790
- }
791
- async fetchHistoricalForLocation(e, r, t) {
792
- p(this.name, e, r);
793
- const i = S(this.name, t), n = new Date(Date.now() - B);
794
- if (i.end.getTime() > n.getTime())
795
- throw new u(
796
- "Open-Meteo archive data is published with a 5-7 day lag",
797
- this.name,
798
- "INVALID_DATE_RANGE",
799
- {
800
- requestedEnd: i.end.toISOString(),
801
- latestSupportedEnd: n.toISOString()
802
- }
803
- );
804
- const a = this.buildBaseParams(e, r);
805
- a.set("start_date", _(i.start)), a.set("end_date", _(i.end));
806
- const s = await this.fetchJson(
807
- `${this.archiveApiBase}?${a.toString()}`,
808
- t.timeout
809
- ), c = R(
810
- this.transformHourlyData(s, "open-meteo-archive"),
811
- i,
812
- t.limit
813
- );
814
- if (c.length === 0)
815
- throw new w(this.name, e, r);
816
- return c;
817
- }
818
- async testConnection() {
819
- try {
820
- return (await fetch(
821
- `${this.forecastApiBase}?latitude=51.0447&longitude=-114.0719&hourly=temperature_2m&forecast_days=1&timezone=UTC`
822
- )).ok;
823
- } catch {
824
- return !1;
825
- }
826
- }
827
- async supportsLocation(e, r) {
828
- return !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180;
829
- }
830
- buildBaseParams(e, r) {
831
- return new URLSearchParams({
832
- latitude: e.toString(),
833
- longitude: r.toString(),
834
- hourly: [
835
- "temperature_2m",
836
- "relative_humidity_2m",
837
- "precipitation",
838
- "weather_code",
839
- "cloud_cover",
840
- "wind_speed_10m",
841
- "wind_direction_10m",
842
- "wind_gusts_10m",
843
- "visibility"
844
- ].join(","),
845
- temperature_unit: "celsius",
846
- wind_speed_unit: "kmh",
847
- precipitation_unit: "mm",
848
- timezone: "UTC"
849
- });
850
- }
851
- async fetchJson(e, r) {
852
- const t = new AbortController(), i = setTimeout(
853
- () => t.abort(),
854
- r || this.timeout
855
- );
856
- try {
857
- const n = await fetch(e, { signal: t.signal });
858
- if (n.status === 429)
859
- throw new C(this.name);
860
- if (!n.ok)
861
- throw new u(
862
- `API request failed: ${n.statusText}`,
863
- this.name,
864
- `HTTP_${n.status}`
865
- );
866
- const a = await n.json();
867
- if (a.error)
868
- throw new u(
869
- a.reason || "Open-Meteo API returned an error",
870
- this.name,
871
- "API_ERROR",
872
- a
873
- );
874
- return a;
875
- } catch (n) {
876
- throw n instanceof u ? n : n.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
877
- `Failed to fetch weather data: ${n.message}`,
878
- this.name,
879
- "FETCH_ERROR"
880
- );
881
- } finally {
882
- clearTimeout(i);
883
- }
884
- }
885
- transformHourlyData(e, r) {
886
- const t = e.hourly;
887
- if (!t?.time?.length)
888
- return [];
889
- const i = [];
890
- for (let n = 0; n < t.time.length; n++) {
891
- const a = z(t.time[n]), s = t.temperature_2m?.[n], c = E(t.relative_humidity_2m?.[n]), h = E(t.wind_speed_10m?.[n]);
892
- if (Number.isNaN(a.getTime()) || typeof s != "number" || c === void 0 || h === void 0)
893
- continue;
894
- const l = t.weather_code?.[n];
895
- i.push({
896
- timestamp: a,
897
- temperature: m(s),
898
- humidity: m(c),
899
- windSpeed: m(h),
900
- windDirection: E(t.wind_direction_10m?.[n]),
901
- windGust: typeof t.wind_gusts_10m?.[n] == "number" ? m(t.wind_gusts_10m[n]) : void 0,
902
- conditions: Y(l),
903
- cloudCover: E(t.cloud_cover?.[n]),
904
- visibility: typeof t.visibility?.[n] == "number" ? m(v(t.visibility[n])) : void 0,
905
- precipAmount: E(t.precipitation?.[n]),
906
- confidence: r === "open-meteo-archive" ? 100 : 90,
907
- raw: {
908
- source: r,
909
- weatherCode: l,
910
- latitude: e.latitude,
911
- longitude: e.longitude,
912
- timezone: e.timezone,
913
- units: e.hourly_units
914
- }
915
- });
916
- }
917
- return i.sort(
918
- (n, a) => n.timestamp.getTime() - a.timestamp.getTime()
919
- );
920
- }
921
- }
922
- function E(o) {
923
- return typeof o == "number" ? o : void 0;
924
- }
925
- function z(o) {
926
- const e = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(o);
927
- return new Date(e ? o : `${o}Z`);
928
- }
929
- function Y(o) {
930
- switch (o) {
931
- case 0:
932
- return "Clear sky";
933
- case 1:
934
- return "Mainly clear";
935
- case 2:
936
- return "Partly cloudy";
937
- case 3:
938
- return "Overcast";
939
- case 45:
940
- case 48:
941
- return "Fog";
942
- case 51:
943
- case 53:
944
- case 55:
945
- return "Drizzle";
946
- case 56:
947
- case 57:
948
- return "Freezing drizzle";
949
- case 61:
950
- case 63:
951
- case 65:
952
- return "Rain";
953
- case 66:
954
- case 67:
955
- return "Freezing rain";
956
- case 71:
957
- case 73:
958
- case 75:
959
- return "Snow";
960
- case 77:
961
- return "Snow grains";
962
- case 80:
963
- case 81:
964
- case 82:
965
- return "Rain showers";
966
- case 85:
967
- case 86:
968
- return "Snow showers";
969
- case 95:
970
- return "Thunderstorm";
971
- case 96:
972
- case 99:
973
- return "Thunderstorm with hail";
974
- default:
975
- return "Unknown";
976
- }
977
- }
978
- class j {
979
- name = "OpenWeatherMap";
980
- providerType = "commercial";
981
- apiBase = "https://api.openweathermap.org/data/2.5/forecast";
982
- apiKey;
983
- timeout;
984
- constructor(e) {
985
- if (!e.apiKey)
986
- throw new g("OpenWeatherMap", "API key is required");
987
- this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
988
- }
989
- /**
990
- * Fetch weather forecasts for a location
991
- */
992
- async fetchForLocation(e, r, t) {
993
- p(this.name, e, r);
994
- try {
995
- const i = this.buildApiUrl(e, r), n = new AbortController(), a = setTimeout(
996
- () => n.abort(),
997
- t?.timeout || this.timeout
998
- ), s = await fetch(i, { signal: n.signal });
999
- if (clearTimeout(a), !s.ok)
1000
- throw s.status === 401 || s.status === 403 ? new g(this.name, "Invalid API key") : s.status === 429 ? new C(this.name) : new u(
1001
- `API request failed: ${s.statusText}`,
1002
- this.name,
1003
- `HTTP_${s.status}`
1004
- );
1005
- const c = await s.json(), h = this.transformForecasts(c);
1006
- return t?.limit && t.limit > 0 ? h.slice(0, t.limit) : h;
1007
- } catch (i) {
1008
- throw i instanceof u ? i : i.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
1009
- `Failed to fetch weather data: ${i.message}`,
1010
- this.name,
1011
- "FETCH_ERROR"
1012
- );
1013
- }
1014
- }
1015
- async fetchHistoricalForLocation(e, r, t) {
1016
- throw p(this.name, e, r), new P(
1017
- this.name,
1018
- "historical-weather",
1019
- "The OpenWeatherMap forecast adapter does not expose OpenWeatherMap historical products yet."
1020
- );
1021
- }
1022
- /**
1023
- * Build the API URL with coordinates and API key
1024
- */
1025
- buildApiUrl(e, r) {
1026
- const t = new URLSearchParams({
1027
- lat: e.toString(),
1028
- lon: r.toString(),
1029
- appid: this.apiKey,
1030
- units: "metric",
1031
- // Celsius
1032
- cnt: "40"
1033
- // Get all 40 forecast periods (5 days)
1034
- });
1035
- return `${this.apiBase}?${t.toString()}`;
1036
- }
1037
- /**
1038
- * Transform OpenWeatherMap API data to WeatherForecast objects
1039
- */
1040
- transformForecasts(e) {
1041
- const r = [];
1042
- for (const t of e.list) {
1043
- const i = new Date(t.dt * 1e3);
1044
- r.push({
1045
- timestamp: i,
1046
- temperature: m(t.main.temp),
1047
- feelsLike: m(t.main.feels_like),
1048
- temperatureMin: m(t.main.temp_min),
1049
- temperatureMax: m(t.main.temp_max),
1050
- pressure: m(t.main.pressure),
1051
- humidity: m(t.main.humidity),
1052
- windSpeed: m(d(t.wind.speed)),
1053
- windDirection: t.wind.deg,
1054
- windGust: t.wind.gust ? m(d(t.wind.gust)) : void 0,
1055
- conditions: t.weather[0]?.description || "Unknown",
1056
- cloudCover: t.clouds.all,
1057
- visibility: t.visibility ? m(v(t.visibility)) : void 0,
1058
- precipProbability: m(t.pop * 100),
1059
- precipAmount: t.rain?.["3h"] || t.snow?.["3h"] || 0,
1060
- confidence: 95,
1061
- raw: {
1062
- source: "openweathermap-5day-3hour",
1063
- timestamp: t.dt,
1064
- weather: t.weather,
1065
- partOfDay: t.sys.pod,
1066
- cityData: {
1067
- name: e.city.name,
1068
- country: e.city.country,
1069
- timezone: e.city.timezone
1070
- }
1071
- }
1072
- });
1073
- }
1074
- return r;
1075
- }
1076
- /**
1077
- * Test connection to OpenWeatherMap API
1078
- */
1079
- async testConnection() {
1080
- try {
1081
- const e = this.buildApiUrl(51.0447, -114.0719), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
1082
- return clearTimeout(t), i.ok;
1083
- } catch {
1084
- return !1;
1085
- }
1086
- }
1087
- /**
1088
- * Check if location is supported (OpenWeatherMap is global)
1089
- */
1090
- async supportsLocation(e, r) {
1091
- return {
1092
- valid: !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180
1093
- }.valid;
1094
- }
1095
- }
1096
- class Z {
1097
- name = "OpenWeatherMap One Call";
1098
- providerType = "commercial";
1099
- apiBase = "https://api.openweathermap.org/data/3.0/onecall";
1100
- apiKey;
1101
- timeout;
1102
- constructor(e) {
1103
- if (!e.apiKey)
1104
- throw new g(
1105
- "OpenWeatherMap One Call",
1106
- "API key is required"
1107
- );
1108
- this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
1109
- }
1110
- /**
1111
- * Fetch weather forecasts for a location
1112
- */
1113
- async fetchForLocation(e, r, t) {
1114
- p(this.name, e, r);
1115
- try {
1116
- const i = this.buildApiUrl(e, r), n = new AbortController(), a = setTimeout(
1117
- () => n.abort(),
1118
- t?.timeout || this.timeout
1119
- ), s = await fetch(i, { signal: n.signal });
1120
- if (clearTimeout(a), !s.ok) {
1121
- if (s.status === 401 || s.status === 403)
1122
- throw new g(this.name, "Invalid API key");
1123
- if (s.status === 429)
1124
- throw new C(this.name);
1125
- const l = await s.text();
1126
- throw new u(
1127
- `API request failed: ${s.statusText} - ${l}`,
1128
- this.name,
1129
- `HTTP_${s.status}`
1130
- );
1131
- }
1132
- const c = await s.json(), h = this.transformForecasts(c);
1133
- return t?.limit && t.limit > 0 ? h.slice(0, t.limit) : h;
1134
- } catch (i) {
1135
- throw i instanceof u ? i : i.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
1136
- `Failed to fetch weather data: ${i.message}`,
1137
- this.name,
1138
- "FETCH_ERROR"
1139
- );
1140
- }
1141
- }
1142
- async fetchHistoricalForLocation(e, r, t) {
1143
- throw p(this.name, e, r), new P(
1144
- this.name,
1145
- "historical-weather",
1146
- "OpenWeatherMap One Call historical data is not exposed through the standardized adapter yet."
1147
- );
1148
- }
1149
- /**
1150
- * Build the API URL with coordinates and API key
1151
- */
1152
- buildApiUrl(e, r) {
1153
- const t = new URLSearchParams({
1154
- lat: e.toString(),
1155
- lon: r.toString(),
1156
- appid: this.apiKey,
1157
- units: "metric",
1158
- // Celsius
1159
- exclude: "minutely,alerts"
1160
- // We only want current, hourly, and daily
1161
- });
1162
- return `${this.apiBase}?${t.toString()}`;
1163
- }
1164
- /**
1165
- * Transform OpenWeatherMap One Call API data to WeatherForecast objects
1166
- */
1167
- transformForecasts(e) {
1168
- const r = [];
1169
- if (e.hourly)
1170
- for (const t of e.hourly) {
1171
- const i = new Date(t.dt * 1e3);
1172
- r.push({
1173
- timestamp: i,
1174
- temperature: m(t.temp),
1175
- feelsLike: m(t.feels_like),
1176
- pressure: m(t.pressure),
1177
- humidity: m(t.humidity),
1178
- windSpeed: m(d(t.wind_speed)),
1179
- windDirection: t.wind_deg,
1180
- windGust: t.wind_gust ? m(d(t.wind_gust)) : void 0,
1181
- conditions: t.weather[0]?.description || "Unknown",
1182
- cloudCover: t.clouds,
1183
- visibility: t.visibility ? m(v(t.visibility)) : void 0,
1184
- precipProbability: m(t.pop * 100),
1185
- precipAmount: t.rain?.["1h"] || t.snow?.["1h"] || 0,
1186
- confidence: 95,
1187
- raw: {
1188
- source: "openweathermap-onecall-hourly",
1189
- timestamp: t.dt,
1190
- weather: t.weather
1191
- }
1192
- });
1193
- }
1194
- if (e.daily)
1195
- for (const t of e.daily) {
1196
- const i = new Date(t.dt * 1e3);
1197
- r.push({
1198
- timestamp: i,
1199
- temperature: m(t.temp.day),
1200
- temperatureMax: m(t.temp.max),
1201
- temperatureMin: m(t.temp.min),
1202
- feelsLike: m(t.feels_like.day),
1203
- pressure: m(t.pressure),
1204
- humidity: m(t.humidity),
1205
- windSpeed: m(d(t.wind_speed)),
1206
- windDirection: t.wind_deg,
1207
- windGust: t.wind_gust ? m(d(t.wind_gust)) : void 0,
1208
- conditions: t.weather[0]?.description || "Unknown",
1209
- cloudCover: t.clouds,
1210
- precipProbability: m(t.pop * 100),
1211
- precipAmount: t.rain || t.snow || 0,
1212
- confidence: 95,
1213
- raw: {
1214
- source: "openweathermap-onecall-daily",
1215
- timestamp: t.dt,
1216
- weather: t.weather,
1217
- sunrise: t.sunrise,
1218
- sunset: t.sunset,
1219
- uvi: t.uvi
1220
- }
1221
- });
1222
- }
1223
- return r;
1224
- }
1225
- /**
1226
- * Test connection to OpenWeatherMap One Call API
1227
- */
1228
- async testConnection() {
1229
- try {
1230
- const e = this.buildApiUrl(51.0447, -114.0719), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
1231
- return clearTimeout(t), i.ok;
1232
- } catch {
1233
- return !1;
1234
- }
1235
- }
1236
- /**
1237
- * Check if location is supported (One Call API is global)
1238
- */
1239
- async supportsLocation(e, r) {
1240
- return {
1241
- valid: !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180
1242
- }.valid;
1243
- }
1244
- }
1245
- const J = {
1246
- // Provider selection
1247
- provider: "HAVE_WEATHER_PROVIDER",
1248
- // OpenWeatherMap API key
1249
- apiKey: "OPENWEATHER_API_KEY",
1250
- // Google API key
1251
- googleApiKey: "GOOGLE_API_KEY",
1252
- // Timeouts
1253
- timeout: "HAVE_WEATHER_TIMEOUT"
1
+ import { loadEnvConfig as e } from "@happyvertical/utils";
2
+ //#region src/shared/types.ts
3
+ var t = class extends Error {
4
+ provider;
5
+ code;
6
+ details;
7
+ constructor(e, t, n, r) {
8
+ super(e), this.provider = t, this.code = n, this.details = r, this.name = "WeatherError";
9
+ }
10
+ }, n = class extends t {
11
+ constructor(e, t) {
12
+ super(`Rate limit exceeded for ${e}${t ? `. Retry after ${t}s` : ""}`, e, "RATE_LIMIT"), this.name = "RateLimitError";
13
+ }
14
+ }, r = class extends t {
15
+ constructor(e, t) {
16
+ super(t || `Authentication failed for ${e}`, e, "AUTH_ERROR"), this.name = "AuthenticationError";
17
+ }
18
+ }, i = class extends t {
19
+ constructor(e, t, n, r) {
20
+ super(r || `Location (${t}, ${n}) is invalid or unsupported`, e, "INVALID_LOCATION", {
21
+ latitude: t,
22
+ longitude: n
23
+ }), this.name = "InvalidLocationError";
24
+ }
25
+ }, a = class extends t {
26
+ constructor(e, t, n) {
27
+ super(`No forecast data available for location (${t}, ${n})`, e, "NO_RESULTS", {
28
+ latitude: t,
29
+ longitude: n
30
+ }), this.name = "NoResultsError";
31
+ }
32
+ }, o = class extends t {
33
+ constructor(e, t, n) {
34
+ super(n || `${e} does not support weather capability: ${t}`, e, "UNSUPPORTED_CAPABILITY", { capability: t }), this.name = "UnsupportedWeatherCapabilityError";
35
+ }
36
+ }, s = 3600 * 1e3;
37
+ function c(e, n) {
38
+ let r = n.interval || "hourly";
39
+ if (r !== "hourly") throw new t(`Unsupported historical weather interval: ${r}`, e, "UNSUPPORTED_INTERVAL", { interval: r });
40
+ let i = f(e, n.start, "start"), a = f(e, n.end || n.start, "end");
41
+ if (a.getTime() < i.getTime()) throw new t("Historical weather end time must be after start time", e, "INVALID_DATE_RANGE", {
42
+ start: i.toISOString(),
43
+ end: a.toISOString()
44
+ });
45
+ return {
46
+ start: i,
47
+ end: a
48
+ };
49
+ }
50
+ function l(e, t, n) {
51
+ let r = e.filter((e) => e.timestamp.getTime() >= t.start.getTime() && e.timestamp.getTime() <= t.end.getTime()).sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime());
52
+ return n && n > 0 ? r.slice(0, n) : r;
53
+ }
54
+ function u(e, t) {
55
+ return Math.max(1, Math.ceil((t.getTime() - e.getTime()) / s) + 1);
56
+ }
57
+ function d(e) {
58
+ return e.toISOString().slice(0, 10);
59
+ }
60
+ function f(e, n, r) {
61
+ let i = n instanceof Date ? n : new Date(n);
62
+ if (Number.isNaN(i.getTime())) throw new t(`Invalid historical weather ${r} time`, e, "INVALID_DATE_RANGE", { [r]: n });
63
+ return i;
64
+ }
65
+ //#endregion
66
+ //#region src/shared/utils.ts
67
+ function p(e, t) {
68
+ return typeof e != "number" || typeof t != "number" ? {
69
+ valid: !1,
70
+ error: "Coordinates must be numbers"
71
+ } : Number.isNaN(e) || Number.isNaN(t) ? {
72
+ valid: !1,
73
+ error: "Coordinates cannot be NaN"
74
+ } : e < -90 || e > 90 ? {
75
+ valid: !1,
76
+ error: `Invalid latitude: ${e} (must be between -90 and 90)`
77
+ } : t < -180 || t > 180 ? {
78
+ valid: !1,
79
+ error: `Invalid longitude: ${t} (must be between -180 and 180)`
80
+ } : { valid: !0 };
81
+ }
82
+ function m(e, t, n) {
83
+ let r = p(t, n);
84
+ if (!r.valid) throw new i(e, t, n, r.error);
85
+ }
86
+ function h(e) {
87
+ return e - 273.15;
88
+ }
89
+ function g(e) {
90
+ return (e - 32) * 5 / 9;
91
+ }
92
+ function _(e) {
93
+ return e * 3.6;
94
+ }
95
+ function v(e) {
96
+ return e * 1.60934;
97
+ }
98
+ function y(e) {
99
+ return e / 1e3;
100
+ }
101
+ function b(e) {
102
+ return e * 1.60934;
103
+ }
104
+ function x(e) {
105
+ return Math.round(e);
106
+ }
107
+ function S(e, t) {
108
+ return e >= 41.7 && e <= 83.1 && t >= -141 && t <= -52.6;
109
+ }
110
+ function C(e, t, n, r) {
111
+ let i = w(n - e), a = w(r - t), o = Math.sin(i / 2) * Math.sin(i / 2) + Math.cos(w(e)) * Math.cos(w(n)) * Math.sin(a / 2) * Math.sin(a / 2);
112
+ return 6371 * (2 * Math.atan2(Math.sqrt(o), Math.sqrt(1 - o)));
113
+ }
114
+ function w(e) {
115
+ return e * Math.PI / 180;
116
+ }
117
+ //#endregion
118
+ //#region src/providers/environment-canada.ts
119
+ var T = class {
120
+ name = "Environment Canada";
121
+ providerType = "government";
122
+ apiBase = "https://api.weather.gc.ca/collections/citypageweather-realtime/items";
123
+ climateHourlyApiBase = "https://api.weather.gc.ca/collections/climate-hourly/items";
124
+ timeout;
125
+ constructor(e = {}) {
126
+ this.timeout = e.timeout || 1e4;
127
+ }
128
+ async fetchForLocation(e, n, r) {
129
+ if (m(this.name, e, n), !S(e, n)) throw new i(this.name, e, n, "Environment Canada only supports Canadian locations");
130
+ try {
131
+ let i = this.buildApiUrl(e, n), a = new AbortController(), o = setTimeout(() => a.abort(), r?.timeout || this.timeout), s = await fetch(i, { signal: a.signal });
132
+ if (clearTimeout(o), !s.ok) throw new t(`API request failed: ${s.statusText}`, this.name, `HTTP_${s.status}`);
133
+ let c = await s.json(), l = this.transformForecasts(c, e, n);
134
+ return r?.limit && r.limit > 0 ? l.slice(0, r.limit) : l;
135
+ } catch (e) {
136
+ throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
137
+ }
138
+ }
139
+ async fetchHistoricalForLocation(e, n, r) {
140
+ if (m(this.name, e, n), !S(e, n)) throw new i(this.name, e, n, "Environment Canada historical climate observations only support Canadian locations");
141
+ let o = c(this.name, r);
142
+ try {
143
+ let t = this.buildClimateHourlyUrl(e, n, o), i = await this.fetchClimateHourlyPages(t, o, r.timeout), s = this.transformClimateHourly(i, e, n, o), c = r.limit && r.limit > 0 ? s.slice(0, r.limit) : s;
144
+ if (c.length === 0) throw new a(this.name, e, n);
145
+ return c;
146
+ } catch (e) {
147
+ throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch historical weather data: ${e.message}`, this.name, "FETCH_ERROR");
148
+ }
149
+ }
150
+ buildApiUrl(e, t) {
151
+ let n = new URLSearchParams({
152
+ f: "json",
153
+ bbox: `${t - 1},${e - 1},${t + 1},${e + 1}`,
154
+ limit: "1"
155
+ });
156
+ return `${this.apiBase}?${n.toString()}`;
157
+ }
158
+ buildClimateHourlyUrl(e, t, n) {
159
+ let r = /* @__PURE__ */ new Date(n.start.getTime() - 1440 * 60 * 1e3), i = new Date(n.end.getTime() + 1440 * 60 * 1e3), a = new URLSearchParams({
160
+ f: "json",
161
+ bbox: `${t - 1},${e - 1},${t + 1},${e + 1}`,
162
+ datetime: `${d(r)}/${d(i)}`,
163
+ limit: "2000"
164
+ });
165
+ return `${this.climateHourlyApiBase}?${a.toString()}`;
166
+ }
167
+ async fetchClimateHourlyPages(e, n, r) {
168
+ let i = [], a = /* @__PURE__ */ new Set(), o = e, s = 0;
169
+ for (; o;) {
170
+ if (a.has(o)) throw new t("Environment Canada climate pagination loop detected", this.name, "PAGINATION_LOOP");
171
+ if (a.add(o), s += 1, s > 50) throw new t("Environment Canada climate pagination exceeded safety limit", this.name, "PAGINATION_LIMIT");
172
+ let e = await this.fetchClimateHourlyPage(o, r);
173
+ i.push(...(e.features || []).filter((e) => this.isClimateFeatureInWindow(e, n))), o = e.links?.find((e) => e.rel === "next" && e.href)?.href;
174
+ }
175
+ return {
176
+ type: "FeatureCollection",
177
+ features: i
178
+ };
179
+ }
180
+ isClimateFeatureInWindow(e, t) {
181
+ let n = e.properties.UTC_DATE;
182
+ if (!n) return !1;
183
+ let r = E(n);
184
+ return !Number.isNaN(r.getTime()) && r.getTime() >= t.start.getTime() && r.getTime() <= t.end.getTime();
185
+ }
186
+ async fetchClimateHourlyPage(e, n) {
187
+ let r = new AbortController(), i = setTimeout(() => r.abort(), n || this.timeout);
188
+ try {
189
+ let n = await fetch(e, { signal: r.signal });
190
+ if (!n.ok) throw new t(`API request failed: ${n.statusText}`, this.name, `HTTP_${n.status}`);
191
+ return await n.json();
192
+ } finally {
193
+ clearTimeout(i);
194
+ }
195
+ }
196
+ transformForecasts(e, t, n) {
197
+ if (!e.features || e.features.length === 0) throw new a(this.name, t, n);
198
+ let r = [], i = e.features[0].properties, o = i.currentConditions;
199
+ o && r.push({
200
+ timestamp: new Date(o.timestamp?.en || /* @__PURE__ */ new Date()),
201
+ temperature: o.temperature?.value?.en || 0,
202
+ humidity: o.relativeHumidity?.value?.en || 0,
203
+ windSpeed: Number(o.wind?.speed?.value?.en) || 0,
204
+ windDirection: o.wind?.bearing?.value?.en,
205
+ pressure: (o.pressure?.value?.en || 0) * 10,
206
+ conditions: o.condition?.en || "Unknown",
207
+ confidence: 100,
208
+ raw: {
209
+ source: "environment-canada-citypage",
210
+ currentConditions: o
211
+ }
212
+ });
213
+ let s = i.forecastGroup;
214
+ if (s && s.forecasts) {
215
+ let e = /* @__PURE__ */ new Date();
216
+ for (let t of s.forecasts) {
217
+ let n = t.period, i = t.temperatures?.temperature || [], a = i.find((e) => e.class?.en === "high"), o = i.find((e) => e.class?.en === "low");
218
+ n?.textForecastName?.en?.toLowerCase() === "tonight" || n?.value?.en?.toLowerCase().includes("night") ? e.setHours(18, 0, 0, 0) : e.setHours(6, 0, 0, 0), r.push({
219
+ timestamp: new Date(e),
220
+ temperature: a?.value?.en || o?.value?.en || 0,
221
+ temperatureMax: a?.value?.en,
222
+ temperatureMin: o?.value?.en,
223
+ humidity: t.relativeHumidity?.value?.en || 0,
224
+ windSpeed: Number(t.winds?.periods?.[0]?.speed?.value?.en) || 0,
225
+ windDirection: t.winds?.periods?.[0]?.bearing?.value?.en,
226
+ conditions: t.cloudPrecip?.en || t.textSummary?.en || "Unknown",
227
+ precipAmount: t.precipitation?.accumulation?.amount?.value?.en,
228
+ confidence: 90,
229
+ raw: {
230
+ source: "environment-canada-citypage",
231
+ periodName: n?.textForecastName?.en || n?.value?.en,
232
+ period: t
233
+ }
234
+ }), e = n?.textForecastName?.en?.toLowerCase().includes("night") ? new Date(e.getTime() + 720 * 60 * 1e3) : new Date(e.getTime() + 1440 * 60 * 1e3);
235
+ }
236
+ }
237
+ return r;
238
+ }
239
+ transformClimateHourly(e, t, n, r) {
240
+ if (!e.features || e.features.length === 0) throw new a(this.name, t, n);
241
+ let i = /* @__PURE__ */ new Map();
242
+ for (let r of e.features) {
243
+ let e = r.properties, a = e.UTC_DATE ? E(e.UTC_DATE) : null;
244
+ if (!a || Number.isNaN(a.getTime())) continue;
245
+ let o = e.LATITUDE_DECIMAL_DEGREES || r.geometry?.coordinates[1], s = e.LONGITUDE_DECIMAL_DEGREES || r.geometry?.coordinates[0];
246
+ if (typeof o != "number" || typeof s != "number") continue;
247
+ let c = typeof e.TEMP == "number" ? e.TEMP : void 0;
248
+ if (c === void 0) continue;
249
+ let l = String(e.CLIMATE_IDENTIFIER || e.STN_ID || e.STATION_NAME || "nearby"), u = C(t, n, o, s), d = e.WEATHER_ENG_DESC || "Unknown", f = D(e.WIND_DIRECTION), p = {
250
+ timestamp: a,
251
+ temperature: x(c),
252
+ feelsLike: typeof e.WINDCHILL == "number" ? x(e.WINDCHILL) : typeof e.HUMIDEX == "number" ? x(e.HUMIDEX) : void 0,
253
+ humidity: x(e.RELATIVE_HUMIDITY || 0),
254
+ windSpeed: x(e.WIND_SPEED || 0),
255
+ windDirection: f,
256
+ pressure: typeof e.STATION_PRESSURE == "number" ? x(e.STATION_PRESSURE * 10) : void 0,
257
+ conditions: d.toUpperCase() === "NA" ? "Unknown" : d,
258
+ visibility: typeof e.VISIBILITY == "number" ? e.VISIBILITY : void 0,
259
+ precipAmount: typeof e.PRECIP_AMOUNT == "number" ? e.PRECIP_AMOUNT : void 0,
260
+ confidence: 100,
261
+ raw: {
262
+ source: "environment-canada-climate-hourly",
263
+ stationId: l,
264
+ stationName: e.STATION_NAME,
265
+ stationLatitude: o,
266
+ stationLongitude: s,
267
+ stationDistanceKm: u,
268
+ observation: e
269
+ }
270
+ }, m = i.get(l);
271
+ m ? m.forecasts.push(p) : i.set(l, {
272
+ distance: u,
273
+ forecasts: [p]
274
+ });
275
+ }
276
+ let o = [...i.values()].map((e) => ({
277
+ distance: e.distance,
278
+ forecasts: r ? l(e.forecasts, r) : e.forecasts.sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime())
279
+ })).filter((e) => e.forecasts.length > 0).sort((e, t) => e.distance - t.distance)[0];
280
+ if (!o) throw new a(this.name, t, n);
281
+ return o.forecasts.sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime());
282
+ }
283
+ async testConnection() {
284
+ try {
285
+ let e = this.buildApiUrl(51.0447, -114.0719), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
286
+ return clearTimeout(n), r.ok;
287
+ } catch {
288
+ return !1;
289
+ }
290
+ }
291
+ async supportsLocation(e, t) {
292
+ return { valid: !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180 }.valid ? S(e, t) : !1;
293
+ }
1254
294
  };
1255
- function X() {
1256
- const o = M(J), e = {};
1257
- if (o.provider && (e.provider = o.provider), o.apiKey && (e.apiKey = o.apiKey), o.googleApiKey && (e.googleApiKey = o.googleApiKey), o.timeout) {
1258
- const r = Number.parseInt(o.timeout, 10);
1259
- Number.isNaN(r) || (e.timeout = r);
1260
- }
1261
- return e;
1262
- }
1263
- function Q(o) {
1264
- const e = X(), r = { ...e, ...o }, t = r.provider || e.provider || "environment-canada";
1265
- switch (t) {
1266
- case "environment-canada":
1267
- return {
1268
- provider: "environment-canada",
1269
- timeout: r.timeout
1270
- };
1271
- case "openweathermap":
1272
- if (!r.apiKey)
1273
- throw new u(
1274
- "OpenWeatherMap requires an API key. Set OPENWEATHER_API_KEY environment variable or pass apiKey option.",
1275
- "openweathermap",
1276
- "MISSING_API_KEY"
1277
- );
1278
- return {
1279
- provider: "openweathermap",
1280
- apiKey: r.apiKey,
1281
- timeout: r.timeout
1282
- };
1283
- case "openweathermap-onecall":
1284
- if (!r.apiKey)
1285
- throw new u(
1286
- "OpenWeatherMap One Call requires an API key. Set OPENWEATHER_API_KEY environment variable or pass apiKey option.",
1287
- "openweathermap-onecall",
1288
- "MISSING_API_KEY"
1289
- );
1290
- return {
1291
- provider: "openweathermap-onecall",
1292
- apiKey: r.apiKey,
1293
- timeout: r.timeout
1294
- };
1295
- case "google-weather": {
1296
- const i = r.apiKey !== void 0 ? r.apiKey : e.googleApiKey;
1297
- if (!i)
1298
- throw new u(
1299
- "Google Weather requires an API key. Set GOOGLE_API_KEY environment variable or pass apiKey option.",
1300
- "google-weather",
1301
- "MISSING_API_KEY"
1302
- );
1303
- return {
1304
- provider: "google-weather",
1305
- apiKey: i,
1306
- timeout: r.timeout
1307
- };
1308
- }
1309
- case "open-meteo":
1310
- return {
1311
- provider: "open-meteo",
1312
- timeout: r.timeout
1313
- };
1314
- default:
1315
- throw new u(
1316
- `Unknown provider: ${t}. Supported providers: environment-canada, openweathermap, openweathermap-onecall, google-weather, open-meteo`,
1317
- "unknown",
1318
- "INVALID_PROVIDER"
1319
- );
1320
- }
1321
- }
1322
- async function oe(o) {
1323
- const e = Q(o);
1324
- switch (e.provider) {
1325
- case "environment-canada":
1326
- return new W({
1327
- timeout: e.timeout
1328
- });
1329
- case "openweathermap":
1330
- return new j({
1331
- apiKey: e.apiKey,
1332
- timeout: e.timeout
1333
- });
1334
- case "openweathermap-onecall":
1335
- return new Z({
1336
- apiKey: e.apiKey,
1337
- timeout: e.timeout
1338
- });
1339
- case "google-weather":
1340
- return new q({
1341
- apiKey: e.apiKey,
1342
- timeout: e.timeout
1343
- });
1344
- case "open-meteo":
1345
- return new V({
1346
- timeout: e.timeout
1347
- });
1348
- default:
1349
- throw new u(
1350
- `Unsupported provider: ${e.provider}`,
1351
- "unknown",
1352
- "INVALID_PROVIDER"
1353
- );
1354
- }
1355
- }
1356
- const se = !0;
1357
- export {
1358
- g as AuthenticationError,
1359
- D as InvalidLocationError,
1360
- w as NoResultsError,
1361
- se as PACKAGE_VERSION_INITIALIZED,
1362
- C as RateLimitError,
1363
- P as UnsupportedWeatherCapabilityError,
1364
- u as WeatherError,
1365
- x as calculateDistance,
1366
- oe as default,
1367
- p as ensureValidCoordinates,
1368
- re as fahrenheitToCelsius,
1369
- oe as getWeatherAdapter,
1370
- N as isInCanada,
1371
- te as kelvinToCelsius,
1372
- d as metersPerSecondToKmPerHour,
1373
- v as metersToKilometers,
1374
- ie as milesPerHourToKmPerHour,
1375
- ne as milesToKilometers,
1376
- K as validateCoordinates
295
+ function E(e) {
296
+ let t = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(e);
297
+ return new Date(t ? e : `${e}Z`);
298
+ }
299
+ function D(e) {
300
+ if (typeof e == "number") return e <= 36 ? e * 10 : e;
301
+ }
302
+ //#endregion
303
+ //#region src/providers/google-weather.ts
304
+ var O = class {
305
+ name = "Google Weather";
306
+ providerType = "commercial";
307
+ apiBase = "https://weather.googleapis.com/v1";
308
+ apiKey;
309
+ timeout;
310
+ constructor(e) {
311
+ if (!e.apiKey) throw new r("Google Weather", "API key is required");
312
+ this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
313
+ }
314
+ async fetchForLocation(e, n, r) {
315
+ m(this.name, e, n);
316
+ try {
317
+ let [t, i] = await Promise.all([this.fetchHourlyForecast(e, n, r), this.fetchDailyForecast(e, n, r)]), a = [...t, ...i];
318
+ return r?.limit && r.limit > 0 ? a.slice(0, r.limit) : a;
319
+ } catch (e) {
320
+ throw e instanceof t ? e : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
321
+ }
322
+ }
323
+ async fetchCurrentConditions(e, t, n) {
324
+ m(this.name, e, t);
325
+ let r = this.buildUrl("/currentConditions:lookup", e, t), i = await this.fetchWithTimeout(r, n?.timeout);
326
+ return this.transformCurrentConditions(i);
327
+ }
328
+ async fetchHourlyForecast(e, t, n) {
329
+ m(this.name, e, t);
330
+ let r = n?.hours || 240, i = [], o;
331
+ do {
332
+ let a = this.buildUrl("/forecast/hours:lookup", e, t, `&hours=${r}`);
333
+ o && (a += `&pageToken=${encodeURIComponent(o)}`);
334
+ let s = await this.fetchWithTimeout(a, n?.timeout);
335
+ s.forecastHours && i.push(...s.forecastHours), o = s.nextPageToken;
336
+ } while (o && i.length < r);
337
+ if (i.length === 0) throw new a(this.name, e, t);
338
+ return i.slice(0, r).map((e) => this.transformHourlyForecast(e, "hourly"));
339
+ }
340
+ async fetchDailyForecast(e, t, n) {
341
+ m(this.name, e, t);
342
+ let r = n?.days || 10, i = [], o;
343
+ do {
344
+ let a = this.buildUrl("/forecast/days:lookup", e, t, `&days=${r}`);
345
+ o && (a += `&pageToken=${encodeURIComponent(o)}`);
346
+ let s = await this.fetchWithTimeout(a, n?.timeout);
347
+ s.forecastDays && i.push(...s.forecastDays), o = s.nextPageToken;
348
+ } while (o && i.length < r);
349
+ if (i.length === 0) throw new a(this.name, e, t);
350
+ return i.slice(0, r).map((e) => this.transformDailyForecast(e));
351
+ }
352
+ async fetchHourlyHistory(e, t, n) {
353
+ m(this.name, e, t);
354
+ let r = n?.hours || 24, i = this.buildUrl("/history/hours:lookup", e, t, `&hours=${r}`), o = await this.fetchWithTimeout(i, n?.timeout);
355
+ if (!o.historyHours || o.historyHours.length === 0) throw new a(this.name, e, t);
356
+ return o.historyHours.map((e) => this.transformHourlyForecast(e, "history"));
357
+ }
358
+ async fetchHistoricalForLocation(e, n, r) {
359
+ m(this.name, e, n);
360
+ let i = c(this.name, r), s = /* @__PURE__ */ new Date(), d = /* @__PURE__ */ new Date(s.getTime() - 1440 * 60 * 1e3);
361
+ if (i.end.getTime() > s.getTime()) throw new t("Google Weather historical lookup cannot end in the future", this.name, "INVALID_DATE_RANGE");
362
+ if (i.start.getTime() < d.getTime()) throw new o(this.name, "historical-weather", "Google Weather hourly history is limited to the previous 24 hours.");
363
+ let f = Math.min(24, u(i.start, s)), p = l(await this.fetchHourlyHistory(e, n, {
364
+ ...r,
365
+ hours: f
366
+ }), i, r.limit);
367
+ if (p.length === 0) throw new a(this.name, e, n);
368
+ return p;
369
+ }
370
+ async fetchAlerts(e, t, n) {
371
+ m(this.name, e, t);
372
+ let r = this.buildUrl("/publicAlerts:lookup", e, t), i = await this.fetchWithTimeout(r, n?.timeout);
373
+ return !i.alerts || i.alerts.length === 0 ? [] : i.alerts.map((e) => this.transformAlert(e));
374
+ }
375
+ async testConnection() {
376
+ try {
377
+ let e = this.buildUrl("/currentConditions:lookup", 37.422, -122.084), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
378
+ return clearTimeout(n), r.ok;
379
+ } catch {
380
+ return !1;
381
+ }
382
+ }
383
+ async supportsLocation(e, t) {
384
+ return !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180;
385
+ }
386
+ buildUrl(e, t, n, r) {
387
+ let i = new URLSearchParams({
388
+ key: this.apiKey,
389
+ "location.latitude": t.toString(),
390
+ "location.longitude": n.toString()
391
+ }), a = `${this.apiBase}${e}?${i.toString()}`;
392
+ return r && (a += r), a;
393
+ }
394
+ async fetchWithTimeout(e, n) {
395
+ let r = new AbortController(), i = setTimeout(() => r.abort(), n || this.timeout);
396
+ try {
397
+ let t = await fetch(e, { signal: r.signal });
398
+ return clearTimeout(i), t.ok || await this.handleHttpError(t), await t.json();
399
+ } catch (e) {
400
+ throw clearTimeout(i), e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
401
+ }
402
+ }
403
+ async handleHttpError(e) {
404
+ if (e.status === 401 || e.status === 403) throw new r(this.name, "Invalid API key");
405
+ if (e.status === 429) throw new n(this.name);
406
+ let i = await e.text();
407
+ throw new t(`API request failed: ${e.statusText} - ${i}`, this.name, `HTTP_${e.status}`);
408
+ }
409
+ transformCurrentConditions(e) {
410
+ return {
411
+ timestamp: new Date(e.currentTime),
412
+ temperature: x(e.temperature.degrees),
413
+ feelsLike: e.feelsLikeTemperature ? x(e.feelsLikeTemperature.degrees) : void 0,
414
+ conditions: e.weatherCondition.description.text,
415
+ humidity: x(e.relativeHumidity),
416
+ windSpeed: x(_(e.wind.speed.value)),
417
+ windDirection: e.wind.direction.degrees,
418
+ windGust: e.wind.gust ? x(_(e.wind.gust.value)) : void 0,
419
+ pressure: e.airPressure ? x(e.airPressure.meanSeaLevelMillibars) : void 0,
420
+ cloudCover: e.cloudCover,
421
+ visibility: e.visibility ? x(y(e.visibility.distance)) : void 0,
422
+ precipProbability: e.precipitation?.probability?.percent,
423
+ precipAmount: e.precipitation?.qpf?.quantity,
424
+ confidence: 95,
425
+ raw: {
426
+ source: "google-weather-current",
427
+ isDaytime: e.isDaytime,
428
+ uvIndex: e.uvIndex,
429
+ thunderstormProbability: e.thunderstormProbability,
430
+ timeZone: e.timeZone.id
431
+ }
432
+ };
433
+ }
434
+ transformHourlyForecast(e, t) {
435
+ return {
436
+ timestamp: new Date(e.interval.startTime),
437
+ temperature: x(e.temperature.degrees),
438
+ feelsLike: e.feelsLikeTemperature ? x(e.feelsLikeTemperature.degrees) : void 0,
439
+ conditions: e.weatherCondition.description.text,
440
+ humidity: x(e.relativeHumidity),
441
+ windSpeed: x(_(e.wind.speed.value)),
442
+ windDirection: e.wind.direction.degrees,
443
+ windGust: e.wind.gust ? x(_(e.wind.gust.value)) : void 0,
444
+ pressure: e.airPressure ? x(e.airPressure.meanSeaLevelMillibars) : void 0,
445
+ cloudCover: e.cloudCover,
446
+ visibility: e.visibility ? x(y(e.visibility.distance)) : void 0,
447
+ precipProbability: e.precipitation?.probability?.percent,
448
+ precipAmount: e.precipitation?.qpf?.quantity,
449
+ confidence: t === "history" ? 100 : 95,
450
+ raw: {
451
+ source: `google-weather-${t}`,
452
+ isDaytime: e.isDaytime,
453
+ uvIndex: e.uvIndex,
454
+ thunderstormProbability: e.thunderstormProbability,
455
+ displayDateTime: e.displayDateTime
456
+ }
457
+ };
458
+ }
459
+ transformDailyForecast(e) {
460
+ let n = e.daytimeForecast || e.nighttimeForecast;
461
+ if (!n) throw new t("Daily forecast missing both daytime and nighttime data", this.name, "INVALID_DATA");
462
+ return {
463
+ timestamp: new Date(e.interval.startTime),
464
+ temperature: e.maxTemperature ? x(e.maxTemperature.degrees) : x(n.temperature.degrees),
465
+ temperatureMax: e.maxTemperature ? x(e.maxTemperature.degrees) : void 0,
466
+ temperatureMin: e.minTemperature ? x(e.minTemperature.degrees) : void 0,
467
+ feelsLike: e.feelsLikeMaxTemperature ? x(e.feelsLikeMaxTemperature.degrees) : n.feelsLikeTemperature ? x(n.feelsLikeTemperature.degrees) : void 0,
468
+ conditions: n.weatherCondition.description.text,
469
+ humidity: x(n.relativeHumidity),
470
+ windSpeed: x(_(n.wind.speed.value)),
471
+ windDirection: n.wind.direction.degrees,
472
+ windGust: n.wind.gust ? x(_(n.wind.gust.value)) : void 0,
473
+ pressure: n.airPressure ? x(n.airPressure.meanSeaLevelMillibars) : void 0,
474
+ cloudCover: n.cloudCover,
475
+ visibility: n.visibility ? x(y(n.visibility.distance)) : void 0,
476
+ precipProbability: n.precipitation?.probability?.percent,
477
+ precipAmount: n.precipitation?.qpf?.quantity,
478
+ confidence: 90,
479
+ raw: {
480
+ source: "google-weather-daily",
481
+ displayDate: e.displayDate,
482
+ daytimeForecast: e.daytimeForecast,
483
+ nighttimeForecast: e.nighttimeForecast
484
+ }
485
+ };
486
+ }
487
+ transformAlert(e) {
488
+ return {
489
+ id: e.alertId,
490
+ headline: e.headline || "Weather Alert",
491
+ description: e.description || "",
492
+ severity: e.severity || "unknown",
493
+ startTime: e.effective ? new Date(e.effective) : /* @__PURE__ */ new Date(),
494
+ endTime: e.expires ? new Date(e.expires) : /* @__PURE__ */ new Date(),
495
+ raw: e
496
+ };
497
+ }
498
+ }, k = 7200 * 60 * 1e3, A = class {
499
+ name = "Open-Meteo";
500
+ providerType = "community";
501
+ forecastApiBase = "https://api.open-meteo.com/v1/forecast";
502
+ archiveApiBase = "https://archive-api.open-meteo.com/v1/archive";
503
+ timeout;
504
+ constructor(e = {}) {
505
+ this.timeout = e.timeout || 1e4;
506
+ }
507
+ async fetchForLocation(e, t, n) {
508
+ m(this.name, e, t);
509
+ let r = this.buildBaseParams(e, t);
510
+ r.set("forecast_days", "7");
511
+ let i = await this.fetchJson(`${this.forecastApiBase}?${r.toString()}`, n?.timeout), o = this.transformHourlyData(i, "open-meteo-forecast");
512
+ if (o.length === 0) throw new a(this.name, e, t);
513
+ return n?.limit && n.limit > 0 ? o.slice(0, n.limit) : o;
514
+ }
515
+ async fetchHistoricalForLocation(e, n, r) {
516
+ m(this.name, e, n);
517
+ let i = c(this.name, r), o = /* @__PURE__ */ new Date(Date.now() - k);
518
+ if (i.end.getTime() > o.getTime()) throw new t("Open-Meteo archive data is published with a 5-7 day lag", this.name, "INVALID_DATE_RANGE", {
519
+ requestedEnd: i.end.toISOString(),
520
+ latestSupportedEnd: o.toISOString()
521
+ });
522
+ let s = this.buildBaseParams(e, n);
523
+ s.set("start_date", d(i.start)), s.set("end_date", d(i.end));
524
+ let u = await this.fetchJson(`${this.archiveApiBase}?${s.toString()}`, r.timeout), f = l(this.transformHourlyData(u, "open-meteo-archive"), i, r.limit);
525
+ if (f.length === 0) throw new a(this.name, e, n);
526
+ return f;
527
+ }
528
+ async testConnection() {
529
+ try {
530
+ return (await fetch(`${this.forecastApiBase}?latitude=51.0447&longitude=-114.0719&hourly=temperature_2m&forecast_days=1&timezone=UTC`)).ok;
531
+ } catch {
532
+ return !1;
533
+ }
534
+ }
535
+ async supportsLocation(e, t) {
536
+ return !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180;
537
+ }
538
+ buildBaseParams(e, t) {
539
+ return new URLSearchParams({
540
+ latitude: e.toString(),
541
+ longitude: t.toString(),
542
+ hourly: [
543
+ "temperature_2m",
544
+ "relative_humidity_2m",
545
+ "precipitation",
546
+ "weather_code",
547
+ "cloud_cover",
548
+ "wind_speed_10m",
549
+ "wind_direction_10m",
550
+ "wind_gusts_10m",
551
+ "visibility"
552
+ ].join(","),
553
+ temperature_unit: "celsius",
554
+ wind_speed_unit: "kmh",
555
+ precipitation_unit: "mm",
556
+ timezone: "UTC"
557
+ });
558
+ }
559
+ async fetchJson(e, r) {
560
+ let i = new AbortController(), a = setTimeout(() => i.abort(), r || this.timeout);
561
+ try {
562
+ let r = await fetch(e, { signal: i.signal });
563
+ if (r.status === 429) throw new n(this.name);
564
+ if (!r.ok) throw new t(`API request failed: ${r.statusText}`, this.name, `HTTP_${r.status}`);
565
+ let a = await r.json();
566
+ if (a.error) throw new t(a.reason || "Open-Meteo API returned an error", this.name, "API_ERROR", a);
567
+ return a;
568
+ } catch (e) {
569
+ throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
570
+ } finally {
571
+ clearTimeout(a);
572
+ }
573
+ }
574
+ transformHourlyData(e, t) {
575
+ let n = e.hourly;
576
+ if (!n?.time?.length) return [];
577
+ let r = [];
578
+ for (let i = 0; i < n.time.length; i++) {
579
+ let a = M(n.time[i]), o = n.temperature_2m?.[i], s = j(n.relative_humidity_2m?.[i]), c = j(n.wind_speed_10m?.[i]);
580
+ if (Number.isNaN(a.getTime()) || typeof o != "number" || s === void 0 || c === void 0) continue;
581
+ let l = n.weather_code?.[i];
582
+ r.push({
583
+ timestamp: a,
584
+ temperature: x(o),
585
+ humidity: x(s),
586
+ windSpeed: x(c),
587
+ windDirection: j(n.wind_direction_10m?.[i]),
588
+ windGust: typeof n.wind_gusts_10m?.[i] == "number" ? x(n.wind_gusts_10m[i]) : void 0,
589
+ conditions: N(l),
590
+ cloudCover: j(n.cloud_cover?.[i]),
591
+ visibility: typeof n.visibility?.[i] == "number" ? x(y(n.visibility[i])) : void 0,
592
+ precipAmount: j(n.precipitation?.[i]),
593
+ confidence: t === "open-meteo-archive" ? 100 : 90,
594
+ raw: {
595
+ source: t,
596
+ weatherCode: l,
597
+ latitude: e.latitude,
598
+ longitude: e.longitude,
599
+ timezone: e.timezone,
600
+ units: e.hourly_units
601
+ }
602
+ });
603
+ }
604
+ return r.sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime());
605
+ }
606
+ };
607
+ function j(e) {
608
+ return typeof e == "number" ? e : void 0;
609
+ }
610
+ function M(e) {
611
+ let t = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(e);
612
+ return new Date(t ? e : `${e}Z`);
613
+ }
614
+ function N(e) {
615
+ switch (e) {
616
+ case 0: return "Clear sky";
617
+ case 1: return "Mainly clear";
618
+ case 2: return "Partly cloudy";
619
+ case 3: return "Overcast";
620
+ case 45:
621
+ case 48: return "Fog";
622
+ case 51:
623
+ case 53:
624
+ case 55: return "Drizzle";
625
+ case 56:
626
+ case 57: return "Freezing drizzle";
627
+ case 61:
628
+ case 63:
629
+ case 65: return "Rain";
630
+ case 66:
631
+ case 67: return "Freezing rain";
632
+ case 71:
633
+ case 73:
634
+ case 75: return "Snow";
635
+ case 77: return "Snow grains";
636
+ case 80:
637
+ case 81:
638
+ case 82: return "Rain showers";
639
+ case 85:
640
+ case 86: return "Snow showers";
641
+ case 95: return "Thunderstorm";
642
+ case 96:
643
+ case 99: return "Thunderstorm with hail";
644
+ default: return "Unknown";
645
+ }
646
+ }
647
+ //#endregion
648
+ //#region src/providers/openweathermap.ts
649
+ var P = class {
650
+ name = "OpenWeatherMap";
651
+ providerType = "commercial";
652
+ apiBase = "https://api.openweathermap.org/data/2.5/forecast";
653
+ apiKey;
654
+ timeout;
655
+ constructor(e) {
656
+ if (!e.apiKey) throw new r("OpenWeatherMap", "API key is required");
657
+ this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
658
+ }
659
+ async fetchForLocation(e, i, a) {
660
+ m(this.name, e, i);
661
+ try {
662
+ let o = this.buildApiUrl(e, i), s = new AbortController(), c = setTimeout(() => s.abort(), a?.timeout || this.timeout), l = await fetch(o, { signal: s.signal });
663
+ if (clearTimeout(c), !l.ok) throw l.status === 401 || l.status === 403 ? new r(this.name, "Invalid API key") : l.status === 429 ? new n(this.name) : new t(`API request failed: ${l.statusText}`, this.name, `HTTP_${l.status}`);
664
+ let u = await l.json(), d = this.transformForecasts(u);
665
+ return a?.limit && a.limit > 0 ? d.slice(0, a.limit) : d;
666
+ } catch (e) {
667
+ throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
668
+ }
669
+ }
670
+ async fetchHistoricalForLocation(e, t, n) {
671
+ throw m(this.name, e, t), new o(this.name, "historical-weather", "The OpenWeatherMap forecast adapter does not expose OpenWeatherMap historical products yet.");
672
+ }
673
+ buildApiUrl(e, t) {
674
+ let n = new URLSearchParams({
675
+ lat: e.toString(),
676
+ lon: t.toString(),
677
+ appid: this.apiKey,
678
+ units: "metric",
679
+ cnt: "40"
680
+ });
681
+ return `${this.apiBase}?${n.toString()}`;
682
+ }
683
+ transformForecasts(e) {
684
+ let t = [];
685
+ for (let n of e.list) {
686
+ let r = /* @__PURE__ */ new Date(n.dt * 1e3);
687
+ t.push({
688
+ timestamp: r,
689
+ temperature: x(n.main.temp),
690
+ feelsLike: x(n.main.feels_like),
691
+ temperatureMin: x(n.main.temp_min),
692
+ temperatureMax: x(n.main.temp_max),
693
+ pressure: x(n.main.pressure),
694
+ humidity: x(n.main.humidity),
695
+ windSpeed: x(_(n.wind.speed)),
696
+ windDirection: n.wind.deg,
697
+ windGust: n.wind.gust ? x(_(n.wind.gust)) : void 0,
698
+ conditions: n.weather[0]?.description || "Unknown",
699
+ cloudCover: n.clouds.all,
700
+ visibility: n.visibility ? x(y(n.visibility)) : void 0,
701
+ precipProbability: x(n.pop * 100),
702
+ precipAmount: n.rain?.["3h"] || n.snow?.["3h"] || 0,
703
+ confidence: 95,
704
+ raw: {
705
+ source: "openweathermap-5day-3hour",
706
+ timestamp: n.dt,
707
+ weather: n.weather,
708
+ partOfDay: n.sys.pod,
709
+ cityData: {
710
+ name: e.city.name,
711
+ country: e.city.country,
712
+ timezone: e.city.timezone
713
+ }
714
+ }
715
+ });
716
+ }
717
+ return t;
718
+ }
719
+ async testConnection() {
720
+ try {
721
+ let e = this.buildApiUrl(51.0447, -114.0719), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
722
+ return clearTimeout(n), r.ok;
723
+ } catch {
724
+ return !1;
725
+ }
726
+ }
727
+ async supportsLocation(e, t) {
728
+ return { valid: !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180 }.valid;
729
+ }
730
+ }, F = class {
731
+ name = "OpenWeatherMap One Call";
732
+ providerType = "commercial";
733
+ apiBase = "https://api.openweathermap.org/data/3.0/onecall";
734
+ apiKey;
735
+ timeout;
736
+ constructor(e) {
737
+ if (!e.apiKey) throw new r("OpenWeatherMap One Call", "API key is required");
738
+ this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
739
+ }
740
+ async fetchForLocation(e, i, a) {
741
+ m(this.name, e, i);
742
+ try {
743
+ let o = this.buildApiUrl(e, i), s = new AbortController(), c = setTimeout(() => s.abort(), a?.timeout || this.timeout), l = await fetch(o, { signal: s.signal });
744
+ if (clearTimeout(c), !l.ok) {
745
+ if (l.status === 401 || l.status === 403) throw new r(this.name, "Invalid API key");
746
+ if (l.status === 429) throw new n(this.name);
747
+ let e = await l.text();
748
+ throw new t(`API request failed: ${l.statusText} - ${e}`, this.name, `HTTP_${l.status}`);
749
+ }
750
+ let u = await l.json(), d = this.transformForecasts(u);
751
+ return a?.limit && a.limit > 0 ? d.slice(0, a.limit) : d;
752
+ } catch (e) {
753
+ throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
754
+ }
755
+ }
756
+ async fetchHistoricalForLocation(e, t, n) {
757
+ throw m(this.name, e, t), new o(this.name, "historical-weather", "OpenWeatherMap One Call historical data is not exposed through the standardized adapter yet.");
758
+ }
759
+ buildApiUrl(e, t) {
760
+ let n = new URLSearchParams({
761
+ lat: e.toString(),
762
+ lon: t.toString(),
763
+ appid: this.apiKey,
764
+ units: "metric",
765
+ exclude: "minutely,alerts"
766
+ });
767
+ return `${this.apiBase}?${n.toString()}`;
768
+ }
769
+ transformForecasts(e) {
770
+ let t = [];
771
+ if (e.hourly) for (let n of e.hourly) {
772
+ let e = /* @__PURE__ */ new Date(n.dt * 1e3);
773
+ t.push({
774
+ timestamp: e,
775
+ temperature: x(n.temp),
776
+ feelsLike: x(n.feels_like),
777
+ pressure: x(n.pressure),
778
+ humidity: x(n.humidity),
779
+ windSpeed: x(_(n.wind_speed)),
780
+ windDirection: n.wind_deg,
781
+ windGust: n.wind_gust ? x(_(n.wind_gust)) : void 0,
782
+ conditions: n.weather[0]?.description || "Unknown",
783
+ cloudCover: n.clouds,
784
+ visibility: n.visibility ? x(y(n.visibility)) : void 0,
785
+ precipProbability: x(n.pop * 100),
786
+ precipAmount: n.rain?.["1h"] || n.snow?.["1h"] || 0,
787
+ confidence: 95,
788
+ raw: {
789
+ source: "openweathermap-onecall-hourly",
790
+ timestamp: n.dt,
791
+ weather: n.weather
792
+ }
793
+ });
794
+ }
795
+ if (e.daily) for (let n of e.daily) {
796
+ let e = /* @__PURE__ */ new Date(n.dt * 1e3);
797
+ t.push({
798
+ timestamp: e,
799
+ temperature: x(n.temp.day),
800
+ temperatureMax: x(n.temp.max),
801
+ temperatureMin: x(n.temp.min),
802
+ feelsLike: x(n.feels_like.day),
803
+ pressure: x(n.pressure),
804
+ humidity: x(n.humidity),
805
+ windSpeed: x(_(n.wind_speed)),
806
+ windDirection: n.wind_deg,
807
+ windGust: n.wind_gust ? x(_(n.wind_gust)) : void 0,
808
+ conditions: n.weather[0]?.description || "Unknown",
809
+ cloudCover: n.clouds,
810
+ precipProbability: x(n.pop * 100),
811
+ precipAmount: n.rain || n.snow || 0,
812
+ confidence: 95,
813
+ raw: {
814
+ source: "openweathermap-onecall-daily",
815
+ timestamp: n.dt,
816
+ weather: n.weather,
817
+ sunrise: n.sunrise,
818
+ sunset: n.sunset,
819
+ uvi: n.uvi
820
+ }
821
+ });
822
+ }
823
+ return t;
824
+ }
825
+ async testConnection() {
826
+ try {
827
+ let e = this.buildApiUrl(51.0447, -114.0719), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
828
+ return clearTimeout(n), r.ok;
829
+ } catch {
830
+ return !1;
831
+ }
832
+ }
833
+ async supportsLocation(e, t) {
834
+ return { valid: !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180 }.valid;
835
+ }
836
+ }, I = {
837
+ provider: "HAVE_WEATHER_PROVIDER",
838
+ apiKey: "OPENWEATHER_API_KEY",
839
+ googleApiKey: "GOOGLE_API_KEY",
840
+ timeout: "HAVE_WEATHER_TIMEOUT"
1377
841
  };
842
+ function L() {
843
+ let t = e(I), n = {};
844
+ if (t.provider && (n.provider = t.provider), t.apiKey && (n.apiKey = t.apiKey), t.googleApiKey && (n.googleApiKey = t.googleApiKey), t.timeout) {
845
+ let e = Number.parseInt(t.timeout, 10);
846
+ Number.isNaN(e) || (n.timeout = e);
847
+ }
848
+ return n;
849
+ }
850
+ function R(e) {
851
+ let n = L(), r = {
852
+ ...n,
853
+ ...e
854
+ }, i = r.provider || n.provider || "environment-canada";
855
+ switch (i) {
856
+ case "environment-canada": return {
857
+ provider: "environment-canada",
858
+ timeout: r.timeout
859
+ };
860
+ case "openweathermap":
861
+ if (!r.apiKey) throw new t("OpenWeatherMap requires an API key. Set OPENWEATHER_API_KEY environment variable or pass apiKey option.", "openweathermap", "MISSING_API_KEY");
862
+ return {
863
+ provider: "openweathermap",
864
+ apiKey: r.apiKey,
865
+ timeout: r.timeout
866
+ };
867
+ case "openweathermap-onecall":
868
+ if (!r.apiKey) throw new t("OpenWeatherMap One Call requires an API key. Set OPENWEATHER_API_KEY environment variable or pass apiKey option.", "openweathermap-onecall", "MISSING_API_KEY");
869
+ return {
870
+ provider: "openweathermap-onecall",
871
+ apiKey: r.apiKey,
872
+ timeout: r.timeout
873
+ };
874
+ case "google-weather": {
875
+ let e = r.apiKey === void 0 ? n.googleApiKey : r.apiKey;
876
+ if (!e) throw new t("Google Weather requires an API key. Set GOOGLE_API_KEY environment variable or pass apiKey option.", "google-weather", "MISSING_API_KEY");
877
+ return {
878
+ provider: "google-weather",
879
+ apiKey: e,
880
+ timeout: r.timeout
881
+ };
882
+ }
883
+ case "open-meteo": return {
884
+ provider: "open-meteo",
885
+ timeout: r.timeout
886
+ };
887
+ default: throw new t(`Unknown provider: ${i}. Supported providers: environment-canada, openweathermap, openweathermap-onecall, google-weather, open-meteo`, "unknown", "INVALID_PROVIDER");
888
+ }
889
+ }
890
+ async function z(e) {
891
+ let n = R(e);
892
+ switch (n.provider) {
893
+ case "environment-canada": return new T({ timeout: n.timeout });
894
+ case "openweathermap": return new P({
895
+ apiKey: n.apiKey,
896
+ timeout: n.timeout
897
+ });
898
+ case "openweathermap-onecall": return new F({
899
+ apiKey: n.apiKey,
900
+ timeout: n.timeout
901
+ });
902
+ case "google-weather": return new O({
903
+ apiKey: n.apiKey,
904
+ timeout: n.timeout
905
+ });
906
+ case "open-meteo": return new A({ timeout: n.timeout });
907
+ default: throw new t(`Unsupported provider: ${n.provider}`, "unknown", "INVALID_PROVIDER");
908
+ }
909
+ }
910
+ var B = !0;
911
+ //#endregion
912
+ export { r as AuthenticationError, i as InvalidLocationError, a as NoResultsError, B as PACKAGE_VERSION_INITIALIZED, n as RateLimitError, o as UnsupportedWeatherCapabilityError, t as WeatherError, C as calculateDistance, z as default, z as getWeatherAdapter, m as ensureValidCoordinates, g as fahrenheitToCelsius, S as isInCanada, h as kelvinToCelsius, _ as metersPerSecondToKmPerHour, y as metersToKilometers, v as milesPerHourToKmPerHour, b as milesToKilometers, p as validateCoordinates };