@whateverjs/client 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/http-client.ts +78 -0
  2. package/package.json +3 -2
package/http-client.ts ADDED
@@ -0,0 +1,78 @@
1
+ export interface HttpClientRequestOptions extends RequestInit {
2
+ query?: Record<string, unknown>;
3
+ }
4
+
5
+ export interface HttpRouteDefinition {
6
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
7
+ path: string;
8
+ description?: string;
9
+ tags?: string[];
10
+ requestBody?: unknown;
11
+ query?: unknown;
12
+ response?: unknown;
13
+ }
14
+
15
+ export type HttpRouteMap = Record<string, HttpRouteDefinition>;
16
+
17
+ export class TypedHttpClient<TRoutes extends HttpRouteMap = HttpRouteMap> {
18
+ constructor(
19
+ private readonly baseUrl = "",
20
+ private readonly fetchImpl: typeof fetch = fetch,
21
+ ) {}
22
+
23
+ private interpolatePath(
24
+ path: string,
25
+ params: Record<string, string | number>,
26
+ ): string {
27
+ return path.replace(/:([a-zA-Z_]\w*)/g, (_, key: string) => {
28
+ const value = params[key];
29
+ if (value === undefined || value === null) {
30
+ throw new Error(`Missing path parameter ${key}`);
31
+ }
32
+ return encodeURIComponent(String(value));
33
+ });
34
+ }
35
+
36
+ private buildUrl(path: string, query?: Record<string, unknown>): string {
37
+ const url = new URL(path, this.baseUrl || "http://localhost");
38
+ if (query) {
39
+ for (const [key, value] of Object.entries(query)) {
40
+ if (value === undefined || value === null) continue;
41
+ url.searchParams.set(key, String(value));
42
+ }
43
+ }
44
+ return url.toString();
45
+ }
46
+
47
+ protected async request<T>(
48
+ method: string,
49
+ path: string,
50
+ options: HttpClientRequestOptions = {},
51
+ ): Promise<T> {
52
+ const url = this.buildUrl(path, options.query);
53
+ const response = await this.fetchImpl(url, { ...options, method });
54
+ if (!response.ok) {
55
+ const errorText = await response.text().catch(() => "");
56
+ throw new Error(
57
+ `HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`,
58
+ );
59
+ }
60
+ if (response.status === 204) {
61
+ return undefined as T;
62
+ }
63
+ const contentType = response.headers.get("content-type") || "";
64
+ if (contentType.includes("application/json")) {
65
+ return (await response.json()) as T;
66
+ }
67
+ return (await response.text()) as unknown as T;
68
+ }
69
+
70
+ protected buildPath(
71
+ path: string,
72
+ params: Record<string, string | number> = {},
73
+ ): string {
74
+ return Object.keys(params).length > 0
75
+ ? this.interpolatePath(path, params)
76
+ : path;
77
+ }
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whateverjs/client",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "main": "./index.ts",
5
5
  "module": "./index.ts",
6
6
  "types": "./index.ts",
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "files": [
14
14
  "index.ts",
15
+ "http-client.ts",
15
16
  "README.md"
16
17
  ],
17
18
  "sideEffects": false,
@@ -24,7 +25,7 @@
24
25
  "@types/bun": "latest"
25
26
  },
26
27
  "peerDependencies": {
27
- "@whateverjs/core": "^0.1.10",
28
+ "@whateverjs/core": "^0.1.16",
28
29
  "typescript": "^5.9.3"
29
30
  }
30
31
  }