@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/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.
@@ -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;AAGrI,eAAO,MAAM,aAAa,EAAE,eAAwE,CAAC;eAEtF,aAAa;AAE5B;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,GAAE,iBAAsC,GAAG,IAAI,CAEhG"}
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.
@@ -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,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,CAAC,MAAM,aAAa,GAAoB,YAAY,CAAC,qBAAqB,EAAE,iBAAiB,CAAC,CAAC;AAErG,eAAe,aAAa,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAQ,GAAsB,kBAAkB;IACxF,eAAe,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;AAC7C,CAAC"}
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"}
@@ -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
- export declare const weatherForecastProcedure: AnyProcedure;
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
@@ -1 +1 @@
1
- {"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AAkC3G,eAAO,MAAM,wBAAwB,EAAE,YAqBb,CAAC;AAE3B,eAAO,MAAM,iBAAiB,EAAE,YAAY,EAA+B,CAAC"}
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"}
@@ -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.url().optional().describe("Another Open-Meteo server (default: the public server)"),
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
- export const weatherForecastProcedure = createProcedure()
33
- .path(["weather", "forecast"])
34
- .input(zodAdapter(ForecastInputSchema))
35
- .output(zodAdapter(ForecastOutputSchema))
36
- .meta({
37
- description: "Give the weather forecast of a point for each day, and optionally each hour (Open-Meteo)",
38
- output: "json",
39
- effects: { reads: ["network"], writes: [] },
40
- })
41
- .handler(async (input, ctx) => {
42
- const signals = [AbortSignal.timeout(input.timeoutMs), ...(ctx?.signal ? [ctx.signal] : [])];
43
- let response;
44
- try {
45
- response = await fetch(forecastUrl(input), { signal: AbortSignal.any(signals), headers: { accept: "application/json" } });
46
- }
47
- catch (error) {
48
- const reason = error.name === "TimeoutError" ? `no answer in ${input.timeoutMs} ms` : "the request failed";
49
- throw new Error(`Open-Meteo did not answer: ${reason}`);
50
- }
51
- if (!response.ok)
52
- throw new Error(`Open-Meteo gave HTTP ${response.status}`);
53
- return readForecast(await response.json(), input.hourly);
54
- })
55
- .build();
56
- export const weatherProcedures = [weatherForecastProcedure];
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
@@ -1 +1 @@
1
- {"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
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.28.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.28.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.url().optional().describe("Another Open-Meteo server (default: the public server)"),
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
- export const weatherForecastProcedure: AnyProcedure = createProcedure()
40
- .path(["weather", "forecast"])
41
- .input(zodAdapter<ForecastInput>(ForecastInputSchema))
42
- .output(zodAdapter<Forecast>(ForecastOutputSchema))
43
- .meta({
44
- description: "Give the weather forecast of a point for each day, and optionally each hour (Open-Meteo)",
45
- output: "json",
46
- effects: { reads: ["network"], writes: [] },
47
- })
48
- .handler(async (input: ForecastInput, ctx?: ProcedureContext): Promise<Forecast> => {
49
- const signals = [AbortSignal.timeout(input.timeoutMs), ...(ctx?.signal ? [ctx.signal] : [])];
50
- let response: Response;
51
- try {
52
- response = await fetch(forecastUrl(input), { signal: AbortSignal.any(signals), headers: { accept: "application/json" } });
53
- } catch (error) {
54
- const reason = (error as { name?: string }).name === "TimeoutError" ? `no answer in ${input.timeoutMs} ms` : "the request failed";
55
- throw new Error(`Open-Meteo did not answer: ${reason}`);
56
- }
57
- if (!response.ok) throw new Error(`Open-Meteo gave HTTP ${response.status}`);
58
- return readForecast(await response.json(), input.hourly);
59
- })
60
- .build() as AnyProcedure;
61
-
62
- export const weatherProcedures: AnyProcedure[] = [weatherForecastProcedure];
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]!;