@procedures/weather 0.28.0 → 0.29.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.
- package/CHANGELOG.md +52 -0
- package/README.md +13 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/module.d.ts +6 -0
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +10 -1
- package/dist/module.js.map +1 -1
- package/dist/procedures.d.ts +29 -1
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +45 -26
- package/dist/procedures.js.map +1 -1
- package/package.json +3 -2
- package/src/module.ts +10 -1
- package/src/procedures.ts +62 -25
package/dist/module.d.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
* The procedures of @procedures/weather as data. Importing this file registers nothing.
|
|
3
3
|
*/
|
|
4
4
|
import { type ProcedureModule, type ProcedureRegistry } from "@procedures/client";
|
|
5
|
+
import { type WeatherProceduresOptions } from "./procedures.js";
|
|
5
6
|
export declare const weatherModule: ProcedureModule;
|
|
6
7
|
export default weatherModule;
|
|
8
|
+
/**
|
|
9
|
+
* This function gives the module with the settings of the host: the `fetch` of the requests.
|
|
10
|
+
* Without options, it gives `weatherModule`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createWeatherModule(options?: WeatherProceduresOptions): ProcedureModule;
|
|
7
13
|
/**
|
|
8
14
|
* This function registers the procedures of @procedures/weather in a registry. The default is the
|
|
9
15
|
* global registry.
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAqD,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAqD,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAA8C,KAAK,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE5G,eAAO,MAAM,aAAa,EAAE,eAAwE,CAAC;eAEtF,aAAa;AAE5B;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,wBAA6B,GAAG,eAAe,CAG3F;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,GAAE,iBAAsC,GAAG,IAAI,CAEhG"}
|
package/dist/module.js
CHANGED
|
@@ -2,9 +2,18 @@
|
|
|
2
2
|
* The procedures of @procedures/weather as data. Importing this file registers nothing.
|
|
3
3
|
*/
|
|
4
4
|
import { defineModule, registerModules, PROCEDURE_REGISTRY } from "@procedures/client";
|
|
5
|
-
import { weatherProcedures } from "./procedures.js";
|
|
5
|
+
import { createWeatherProcedures, weatherProcedures } from "./procedures.js";
|
|
6
6
|
export const weatherModule = defineModule("@procedures/weather", weatherProcedures);
|
|
7
7
|
export default weatherModule;
|
|
8
|
+
/**
|
|
9
|
+
* This function gives the module with the settings of the host: the `fetch` of the requests.
|
|
10
|
+
* Without options, it gives `weatherModule`.
|
|
11
|
+
*/
|
|
12
|
+
export function createWeatherModule(options = {}) {
|
|
13
|
+
if (options.fetch === undefined)
|
|
14
|
+
return weatherModule;
|
|
15
|
+
return defineModule("@procedures/weather", createWeatherProcedures(options));
|
|
16
|
+
}
|
|
8
17
|
/**
|
|
9
18
|
* This function registers the procedures of @procedures/weather in a registry. The default is the
|
|
10
19
|
* global registry.
|
package/dist/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAgD,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAgD,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAiC,MAAM,iBAAiB,CAAC;AAE5G,MAAM,CAAC,MAAM,aAAa,GAAoB,YAAY,CAAC,qBAAqB,EAAE,iBAAiB,CAAC,CAAC;AAErG,eAAe,aAAa,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAO,GAA6B,EAAE;IACxE,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC;IACtD,OAAO,YAAY,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAQ,GAAsB,kBAAkB;IACxF,eAAe,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
package/dist/procedures.d.ts
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The procedure of `@procedures/weather`: `weather.forecast`.
|
|
3
|
+
*
|
|
4
|
+
* The request goes through the host policy of `@procedures/http` (audit pass 2, O55). The policy
|
|
5
|
+
* refuses a `baseUrl` on a loopback, private or link-local address, unless the host allows it.
|
|
3
6
|
*/
|
|
4
7
|
import { type AnyProcedure } from "@procedures/client";
|
|
5
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The part of `fetch` that `weather.forecast` uses. The default is `createHostFetch()` of
|
|
10
|
+
* `@procedures/http`: each public host, and no other host.
|
|
11
|
+
*/
|
|
12
|
+
export type FetchForecast = (url: string, init: {
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
signal: AbortSignal;
|
|
15
|
+
}) => Promise<{
|
|
16
|
+
ok: boolean;
|
|
17
|
+
status: number;
|
|
18
|
+
json(): Promise<unknown>;
|
|
19
|
+
}>;
|
|
20
|
+
/** The settings of the host for the procedures of this package. The procedure input cannot change them. */
|
|
21
|
+
export interface WeatherProceduresOptions {
|
|
22
|
+
/**
|
|
23
|
+
* The function that sends the request. The default is `createHostFetch()`. A host with its own
|
|
24
|
+
* Open-Meteo server on a private address gives `createHostFetch({ allow: ["10.0.0.5:8080"] })`.
|
|
25
|
+
*/
|
|
26
|
+
readonly fetch?: FetchForecast | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* This function gives the procedures of this package with the settings of the host. Without
|
|
30
|
+
* options, the requests go through the default host policy of `@procedures/http`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createWeatherProcedures(options?: WeatherProceduresOptions): AnyProcedure[];
|
|
6
33
|
export declare const weatherProcedures: AnyProcedure[];
|
|
34
|
+
export declare const weatherForecastProcedure: AnyProcedure;
|
|
7
35
|
//# sourceMappingURL=procedures.d.ts.map
|
package/dist/procedures.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AAK3G;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,KAC3D,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAExE,2GAA2G;AAC3G,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAmCD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,wBAA6B,GAAG,YAAY,EAAE,CA2B9F;AAED,eAAO,MAAM,iBAAiB,EAAE,YAAY,EAA8B,CAAC;AAE3E,eAAO,MAAM,wBAAwB,EAAE,YAAoC,CAAC"}
|
package/dist/procedures.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The procedure of `@procedures/weather`: `weather.forecast`.
|
|
3
|
+
*
|
|
4
|
+
* The request goes through the host policy of `@procedures/http` (audit pass 2, O55). The policy
|
|
5
|
+
* refuses a `baseUrl` on a loopback, private or link-local address, unless the host allows it.
|
|
3
6
|
*/
|
|
4
7
|
import { createProcedure, zodAdapter } from "@procedures/client";
|
|
8
|
+
import { createHostFetch, isHostNotAllowed } from "@procedures/http";
|
|
5
9
|
import { z } from "zod";
|
|
6
10
|
import { forecastUrl, readForecast } from "./weather.js";
|
|
7
11
|
const ForecastInputSchema = z.object({
|
|
@@ -10,7 +14,10 @@ const ForecastInputSchema = z.object({
|
|
|
10
14
|
days: z.number().int().min(1).max(16).default(7).describe("The number of days (default: 7, at most 16)"),
|
|
11
15
|
units: z.enum(["metric", "imperial"]).default("metric").describe("metric (°C, km/h) or imperial (°F, mph); default: metric"),
|
|
12
16
|
hourly: z.boolean().default(false).describe("Also give each hour (default: false)"),
|
|
13
|
-
baseUrl: z
|
|
17
|
+
baseUrl: z
|
|
18
|
+
.url()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Another Open-Meteo server (default: the public server). The host policy refuses a loopback, private or link-local address."),
|
|
14
21
|
timeoutMs: z.number().int().positive().max(120_000).default(20_000).describe("The time limit, in milliseconds (default: 20000)"),
|
|
15
22
|
});
|
|
16
23
|
const nullableNumber = z.number().nullable();
|
|
@@ -29,29 +36,41 @@ const ForecastOutputSchema = z.object({
|
|
|
29
36
|
.array(z.object({ time: z.string(), temperature: nullableNumber, precipitationProbability: nullableNumber, weatherCode: nullableNumber }))
|
|
30
37
|
.optional(),
|
|
31
38
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
/**
|
|
40
|
+
* This function gives the procedures of this package with the settings of the host. Without
|
|
41
|
+
* options, the requests go through the default host policy of `@procedures/http`.
|
|
42
|
+
*/
|
|
43
|
+
export function createWeatherProcedures(options = {}) {
|
|
44
|
+
const fetchForecast = options.fetch ?? createHostFetch();
|
|
45
|
+
const forecast = createProcedure()
|
|
46
|
+
.path(["weather", "forecast"])
|
|
47
|
+
.input(zodAdapter(ForecastInputSchema))
|
|
48
|
+
.output(zodAdapter(ForecastOutputSchema))
|
|
49
|
+
.meta({
|
|
50
|
+
description: "Give the weather forecast of a point for each day, and optionally each hour (Open-Meteo)",
|
|
51
|
+
output: "json",
|
|
52
|
+
effects: { reads: ["network"], writes: [] },
|
|
53
|
+
})
|
|
54
|
+
.handler(async (input, ctx) => {
|
|
55
|
+
const signals = [AbortSignal.timeout(input.timeoutMs), ...(ctx?.signal ? [ctx.signal] : [])];
|
|
56
|
+
let response;
|
|
57
|
+
try {
|
|
58
|
+
response = await fetchForecast(forecastUrl(input), { signal: AbortSignal.any(signals), headers: { accept: "application/json" } });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// A refusal of the host policy keeps its code and its reason
|
|
62
|
+
if (isHostNotAllowed(error))
|
|
63
|
+
throw error;
|
|
64
|
+
const reason = error.name === "TimeoutError" ? `no answer in ${input.timeoutMs} ms` : "the request failed";
|
|
65
|
+
throw new Error(`Open-Meteo did not answer: ${reason}`);
|
|
66
|
+
}
|
|
67
|
+
if (!response.ok)
|
|
68
|
+
throw new Error(`Open-Meteo gave HTTP ${response.status}`);
|
|
69
|
+
return readForecast(await response.json(), input.hourly);
|
|
70
|
+
})
|
|
71
|
+
.build();
|
|
72
|
+
return [forecast];
|
|
73
|
+
}
|
|
74
|
+
export const weatherProcedures = createWeatherProcedures();
|
|
75
|
+
export const weatherForecastProcedure = weatherProcedures[0];
|
|
57
76
|
//# sourceMappingURL=procedures.js.map
|
package/dist/procedures.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/weather",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.29.0",
|
|
5
5
|
"description": "A weather forecast for a point from Open-Meteo, without a key: each day and each hour, metric or imperial.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"weather",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"provenance": true
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@procedures/client": "0.
|
|
59
|
+
"@procedures/client": "0.29.0",
|
|
60
|
+
"@procedures/http": "0.29.0",
|
|
60
61
|
"zod": "^4.6.5"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
package/src/module.ts
CHANGED
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { defineModule, registerModules, PROCEDURE_REGISTRY, type ProcedureModule, type ProcedureRegistry } from "@procedures/client";
|
|
6
|
-
import { weatherProcedures } from "./procedures.js";
|
|
6
|
+
import { createWeatherProcedures, weatherProcedures, type WeatherProceduresOptions } from "./procedures.js";
|
|
7
7
|
|
|
8
8
|
export const weatherModule: ProcedureModule = defineModule("@procedures/weather", weatherProcedures);
|
|
9
9
|
|
|
10
10
|
export default weatherModule;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* This function gives the module with the settings of the host: the `fetch` of the requests.
|
|
14
|
+
* Without options, it gives `weatherModule`.
|
|
15
|
+
*/
|
|
16
|
+
export function createWeatherModule(options: WeatherProceduresOptions = {}): ProcedureModule {
|
|
17
|
+
if (options.fetch === undefined) return weatherModule;
|
|
18
|
+
return defineModule("@procedures/weather", createWeatherProcedures(options));
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
/**
|
|
13
22
|
* This function registers the procedures of @procedures/weather in a registry. The default is the
|
|
14
23
|
* global registry.
|
package/src/procedures.ts
CHANGED
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The procedure of `@procedures/weather`: `weather.forecast`.
|
|
3
|
+
*
|
|
4
|
+
* The request goes through the host policy of `@procedures/http` (audit pass 2, O55). The policy
|
|
5
|
+
* refuses a `baseUrl` on a loopback, private or link-local address, unless the host allows it.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
import { createProcedure, zodAdapter, type AnyProcedure, type ProcedureContext } from "@procedures/client";
|
|
9
|
+
import { createHostFetch, isHostNotAllowed } from "@procedures/http";
|
|
6
10
|
import { z } from "zod";
|
|
7
11
|
import { forecastUrl, readForecast, type Forecast } from "./weather.js";
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The part of `fetch` that `weather.forecast` uses. The default is `createHostFetch()` of
|
|
15
|
+
* `@procedures/http`: each public host, and no other host.
|
|
16
|
+
*/
|
|
17
|
+
export type FetchForecast = (
|
|
18
|
+
url: string,
|
|
19
|
+
init: { headers: Record<string, string>; signal: AbortSignal }
|
|
20
|
+
) => Promise<{ ok: boolean; status: number; json(): Promise<unknown> }>;
|
|
21
|
+
|
|
22
|
+
/** The settings of the host for the procedures of this package. The procedure input cannot change them. */
|
|
23
|
+
export interface WeatherProceduresOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The function that sends the request. The default is `createHostFetch()`. A host with its own
|
|
26
|
+
* Open-Meteo server on a private address gives `createHostFetch({ allow: ["10.0.0.5:8080"] })`.
|
|
27
|
+
*/
|
|
28
|
+
readonly fetch?: FetchForecast | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
9
31
|
const ForecastInputSchema = z.object({
|
|
10
32
|
lat: z.number().min(-90).max(90).describe("The latitude of the point, in degrees"),
|
|
11
33
|
lon: z.number().min(-180).max(180).describe("The longitude of the point, in degrees"),
|
|
12
34
|
days: z.number().int().min(1).max(16).default(7).describe("The number of days (default: 7, at most 16)"),
|
|
13
35
|
units: z.enum(["metric", "imperial"]).default("metric").describe("metric (°C, km/h) or imperial (°F, mph); default: metric"),
|
|
14
36
|
hourly: z.boolean().default(false).describe("Also give each hour (default: false)"),
|
|
15
|
-
baseUrl: z
|
|
37
|
+
baseUrl: z
|
|
38
|
+
.url()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Another Open-Meteo server (default: the public server). The host policy refuses a loopback, private or link-local address."),
|
|
16
41
|
timeoutMs: z.number().int().positive().max(120_000).default(20_000).describe("The time limit, in milliseconds (default: 20000)"),
|
|
17
42
|
});
|
|
18
43
|
type ForecastInput = z.infer<typeof ForecastInputSchema>;
|
|
@@ -36,27 +61,39 @@ const ForecastOutputSchema = z.object({
|
|
|
36
61
|
.optional(),
|
|
37
62
|
});
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
/**
|
|
65
|
+
* This function gives the procedures of this package with the settings of the host. Without
|
|
66
|
+
* options, the requests go through the default host policy of `@procedures/http`.
|
|
67
|
+
*/
|
|
68
|
+
export function createWeatherProcedures(options: WeatherProceduresOptions = {}): AnyProcedure[] {
|
|
69
|
+
const fetchForecast: FetchForecast = options.fetch ?? createHostFetch();
|
|
70
|
+
const forecast = createProcedure()
|
|
71
|
+
.path(["weather", "forecast"])
|
|
72
|
+
.input(zodAdapter<ForecastInput>(ForecastInputSchema))
|
|
73
|
+
.output(zodAdapter<Forecast>(ForecastOutputSchema))
|
|
74
|
+
.meta({
|
|
75
|
+
description: "Give the weather forecast of a point for each day, and optionally each hour (Open-Meteo)",
|
|
76
|
+
output: "json",
|
|
77
|
+
effects: { reads: ["network"], writes: [] },
|
|
78
|
+
})
|
|
79
|
+
.handler(async (input: ForecastInput, ctx?: ProcedureContext): Promise<Forecast> => {
|
|
80
|
+
const signals = [AbortSignal.timeout(input.timeoutMs), ...(ctx?.signal ? [ctx.signal] : [])];
|
|
81
|
+
let response: Awaited<ReturnType<FetchForecast>>;
|
|
82
|
+
try {
|
|
83
|
+
response = await fetchForecast(forecastUrl(input), { signal: AbortSignal.any(signals), headers: { accept: "application/json" } });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// A refusal of the host policy keeps its code and its reason
|
|
86
|
+
if (isHostNotAllowed(error)) throw error;
|
|
87
|
+
const reason = (error as { name?: string }).name === "TimeoutError" ? `no answer in ${input.timeoutMs} ms` : "the request failed";
|
|
88
|
+
throw new Error(`Open-Meteo did not answer: ${reason}`);
|
|
89
|
+
}
|
|
90
|
+
if (!response.ok) throw new Error(`Open-Meteo gave HTTP ${response.status}`);
|
|
91
|
+
return readForecast(await response.json(), input.hourly);
|
|
92
|
+
})
|
|
93
|
+
.build() as AnyProcedure;
|
|
94
|
+
return [forecast];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const weatherProcedures: AnyProcedure[] = createWeatherProcedures();
|
|
98
|
+
|
|
99
|
+
export const weatherForecastProcedure: AnyProcedure = weatherProcedures[0]!;
|