@zap-studio/fetch 0.1.2 → 0.2.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 CHANGED
@@ -1,5 +1,46 @@
1
1
  # @zap-studio/fetch
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 78afb76: ### Standard Schema Support
8
+
9
+ Migrated from Zod-only validation to **Standard Schema v1** specification, enabling support for multiple validation libraries:
10
+
11
+ - Zod
12
+ - Valibot
13
+ - ArkType
14
+ - Any Standard Schema compliant library
15
+
16
+ ### New Features
17
+
18
+ - **Factory Pattern**: `createFetch()` for creating pre-configured fetch instances with `baseURL`, default `headers`, and error handling options
19
+ - **Smart URL Handling**: Absolute URLs bypass `baseURL` configuration
20
+ - **Auto JSON Body**: Automatic `JSON.stringify()` and `Content-Type` header when using schemas with request bodies
21
+
22
+ ### Breaking Changes
23
+
24
+ - Schema validation now requires Standard Schema compliant libraries (Zod 3.23+, Valibot 1.0+, ArkType 2.0+)
25
+ - Internal file structure reorganized (affects deep imports if any were used)
26
+ - `FetchError` constructor signature changed: now requires `(message, response)`
27
+
28
+ ### Migration Guide
29
+
30
+ ```typescript
31
+ // Before (Zod-only)
32
+ import { $fetch } from "@zap-studio/fetch";
33
+ import { z } from "zod";
34
+
35
+ // After (Standard Schema - works the same with Zod!)
36
+ import { $fetch } from "@zap-studio/fetch";
37
+ import { z } from "zod"; // Zod 3.23+ is Standard Schema compliant
38
+
39
+ // Or use other libraries
40
+ import * as v from "valibot";
41
+ import { type } from "arktype";
42
+ ```
43
+
3
44
  ## 0.1.2
4
45
 
5
46
  ### Patch Changes
package/README.md CHANGED
@@ -1,22 +1,38 @@
1
1
  # @zap-studio/fetch
2
2
 
3
- A type-safe fetch wrapper with Zod validation for TypeScript.
3
+ A type-safe fetch wrapper with Standard Schema validation.
4
+
5
+ ## Why @zap-studio/fetch?
6
+
7
+ **Before:**
8
+
9
+ ```typescript
10
+ const response = await fetch("/api/users/1");
11
+ const data = await response.json();
12
+ const user = data as User; // 😱 Unsafe type assertion
13
+ ```
14
+
15
+ **After:**
16
+
17
+ ```typescript
18
+ const user = await api.get("/api/users/1", UserSchema);
19
+ // ✨ Typed, validated, and safe!
20
+ ```
4
21
 
5
22
  ## Features
6
23
 
7
24
  - 🎯 **Type-safe requests** with automatic type inference
8
- - 🛡️ **Runtime validation** using Zod schemas
9
- - 🔄 **Automatic content-type** detection and handling
25
+ - 🛡️ **Runtime validation** using Standard Schema (Zod, Valibot, ArkType, etc.)
10
26
  - ⚡️ **Convenient API methods** (GET, POST, PUT, PATCH, DELETE)
11
- - 📦 **Multiple response types** (JSON, text, blob, arrayBuffer)
12
- - 🚨 **Custom error handling** with FetchError class
27
+ - 🏭 **Factory pattern** for creating pre-configured instances with base URLs
28
+ - 🚨 **Custom error handling** with FetchError and ValidationError classes
13
29
  - 📘 **Full TypeScript support** with zero configuration
14
30
 
15
31
  ## Installation
16
32
 
17
33
  ```bash
18
34
  pnpm add @zap-studio/fetch
19
- #or
35
+ # or
20
36
  npm install @zap-studio/fetch
21
37
  ```
22
38
 
@@ -34,10 +50,7 @@ const UserSchema = z.object({
34
50
  });
35
51
 
36
52
  // Make a type-safe request
37
- const user = await api.get(
38
- "https://api.example.com/users/1",
39
- UserSchema
40
- );
53
+ const user = await api.get("https://api.example.com/users/1", UserSchema);
41
54
 
42
55
  // user is fully typed and validated! ✨
43
56
  console.log(user.name); // TypeScript knows this is a string
@@ -51,28 +64,31 @@ console.log(user.name); // TypeScript knows this is a string
51
64
  const user = await api.get("/api/users/1", UserSchema);
52
65
  ```
53
66
 
54
- ### `api.post(url, schema, body?, options?)`
67
+ ### `api.post(url, schema, options?)`
55
68
 
56
69
  ```typescript
57
70
  const newUser = await api.post("/api/users", UserSchema, {
58
- name: "John Doe",
59
- email: "john@example.com",
71
+ body: {
72
+ name: "John Doe",
73
+ email: "john@example.com",
74
+ },
60
75
  });
76
+ // Automatically stringifies body and sets Content-Type: application/json
61
77
  ```
62
78
 
63
- ### `api.put(url, schema, body?, options?)`
79
+ ### `api.put(url, schema, options?)`
64
80
 
65
81
  ```typescript
66
82
  const updated = await api.put("/api/users/1", UserSchema, {
67
- name: "Jane Doe",
83
+ body: { name: "Jane Doe" },
68
84
  });
69
85
  ```
70
86
 
71
- ### `api.patch(url, schema, body?, options?)`
87
+ ### `api.patch(url, schema, options?)`
72
88
 
73
89
  ```typescript
74
90
  const patched = await api.patch("/api/users/1", UserSchema, {
75
- email: "newemail@example.com",
91
+ body: { email: "newemail@example.com" },
76
92
  });
77
93
  ```
78
94
 
@@ -82,58 +98,121 @@ const patched = await api.patch("/api/users/1", UserSchema, {
82
98
  const deleted = await api.delete("/api/users/1", UserSchema);
83
99
  ```
84
100
 
101
+ > **Note:** The `api.*` methods always require a schema for validation. For raw responses without validation, use `$fetch` directly.
102
+
85
103
  ## Advanced Usage
86
104
 
87
105
  ### Using `$fetch` directly
88
106
 
107
+ For more control or when you don't need schema validation:
108
+
89
109
  ```typescript
90
110
  import { $fetch } from "@zap-studio/fetch";
91
111
 
92
- const result = await $fetch(
93
- "https://api.example.com/users/1",
94
- UserSchema,
95
- {
96
- method: "GET",
97
- headers: {
98
- "Authorization": "Bearer token",
99
- },
100
- }
101
- );
112
+ // With schema validation
113
+ const user = await $fetch("https://api.example.com/users/1", UserSchema, {
114
+ method: "GET",
115
+ headers: {
116
+ Authorization: "Bearer token",
117
+ },
118
+ });
119
+
120
+ // Without schema - returns raw Response object
121
+ const response = await $fetch("https://api.example.com/users/1", {
122
+ method: "GET",
123
+ });
124
+ const data = await response.json();
102
125
  ```
103
126
 
104
- ### Custom response types
127
+ ### Factory Pattern with `createFetch`
128
+
129
+ Create pre-configured fetch instances with base URLs and default headers. Useful for API clients:
105
130
 
106
131
  ```typescript
107
- // Get text response
108
- const text = await $fetch(
109
- "/api/data",
110
- z.string(),
111
- { responseType: "text" }
112
- );
113
-
114
- // Get blob response
115
- const blob = await $fetch(
116
- "/api/file",
117
- z.instanceof(Blob),
118
- { responseType: "blob" }
119
- );
132
+ import { z } from "zod";
133
+ import { createFetch } from "@zap-studio/fetch";
134
+
135
+ // Create a configured instance
136
+ const { $fetch, api } = createFetch({
137
+ baseURL: "https://api.example.com",
138
+ headers: {
139
+ Authorization: "Bearer your-token",
140
+ "X-API-Key": "your-api-key",
141
+ },
142
+ });
143
+
144
+ const UserSchema = z.object({
145
+ id: z.number(),
146
+ name: z.string(),
147
+ });
148
+
149
+ // Now use relative paths - baseURL is prepended automatically
150
+ const user = await api.get("/users/1", UserSchema);
151
+
152
+ // POST with auto-stringified body
153
+ const newUser = await api.post("/users", UserSchema, {
154
+ body: { name: "John Doe" },
155
+ });
120
156
  ```
121
157
 
122
- ### Error handling
158
+ #### Factory Options
159
+
160
+ | Option | Type | Default | Description |
161
+ | ------------------------ | ------------- | ------- | ------------------------------------------------ |
162
+ | `baseURL` | `string` | `""` | Base URL prepended to relative paths only |
163
+ | `headers` | `HeadersInit` | - | Default headers included in all requests |
164
+ | `throwOnFetchError` | `boolean` | `true` | Throw `FetchError` on non-2xx responses |
165
+ | `throwOnValidationError` | `boolean` | `true` | Throw `ValidationError` on schema validation failures |
166
+
167
+ > **Note:** Absolute URLs (starting with `http://`, `https://`, or `//`) are used as-is and ignore the `baseURL`.
168
+
169
+ #### Multiple API Clients
170
+
171
+ You can create separate fetch instances for different APIs:
123
172
 
124
173
  ```typescript
125
- import { FetchError } from "@zap-studio/fetch/errors";
174
+ import { createFetch } from "@zap-studio/fetch";
175
+
176
+ // GitHub API client
177
+ const github = createFetch({
178
+ baseURL: "https://api.github.com",
179
+ headers: { Authorization: "Bearer github-token" },
180
+ });
181
+
182
+ // Your internal API client
183
+ const internal = createFetch({
184
+ baseURL: "https://internal.example.com/api",
185
+ headers: { "X-Internal-Key": "secret" },
186
+ });
187
+
188
+ // Use them independently
189
+ const repo = await github.api.get("/repos/owner/repo", RepoSchema);
190
+ const data = await internal.api.get("/data", DataSchema);
191
+ ```
192
+
193
+ ### Error Handling
194
+
195
+ The package exports specialized error classes for granular error handling:
196
+
197
+ ```typescript
198
+ import { $fetch } from "@zap-studio/fetch";
199
+ import { FetchError, ValidationError } from "@zap-studio/fetch/errors";
126
200
 
127
201
  try {
128
202
  const user = await api.get("/api/users/1", UserSchema);
129
203
  } catch (error) {
130
204
  if (error instanceof FetchError) {
131
- console.error(`HTTP ${error.status}: ${error.statusText}`);
205
+ console.error(`HTTP ${error.status}: ${error.response.statusText}`);
206
+ }
207
+ if (error instanceof ValidationError) {
208
+ console.error("Validation failed:", error.issues);
132
209
  }
133
210
  }
134
211
  ```
135
212
 
136
- ### Flexible validation
213
+ ### Flexible Validation
214
+
215
+ You can choose whether validation errors should throw exceptions:
137
216
 
138
217
  ```typescript
139
218
  // Throw on validation error (default)
@@ -146,24 +225,9 @@ const result = await $fetch(url, UserSchema, {
146
225
  throwOnValidationError: false,
147
226
  });
148
227
 
149
- if (result.success) {
150
- console.log(result.data);
228
+ if (result.issues) {
229
+ console.error("Validation failed:", result.issues);
151
230
  } else {
152
- console.error(result.error);
231
+ console.log("Success:", result.value);
153
232
  }
154
233
  ```
155
-
156
- ## Why @zap-studio/fetch?
157
-
158
- **Before:**
159
- ```typescript
160
- const response = await fetch("/api/users/1");
161
- const data = await response.json();
162
- const user = data as User; // 😱 Unsafe type assertion
163
- ```
164
-
165
- **After:**
166
- ```typescript
167
- const user = await api.get("/api/users/1", UserSchema);
168
- // ✨ Typed, validated, and safe!
169
- ```
@@ -0,0 +1,29 @@
1
+ //#region src/errors.ts
2
+ /**
3
+ * Error thrown for HTTP errors (non-2xx responses)
4
+ */
5
+ var FetchError = class extends Error {
6
+ status;
7
+ response;
8
+ constructor(message, response) {
9
+ super(message);
10
+ this.name = "FetchError";
11
+ this.status = response.status;
12
+ this.response = response;
13
+ }
14
+ };
15
+ /**
16
+ * Error thrown for validation errors
17
+ */
18
+ var ValidationError = class extends Error {
19
+ issues;
20
+ constructor(issues) {
21
+ super(JSON.stringify(issues, null, 2));
22
+ this.name = "ValidationError";
23
+ this.issues = issues;
24
+ }
25
+ };
26
+
27
+ //#endregion
28
+ export { ValidationError as n, FetchError as t };
29
+ //# sourceMappingURL=errors-DKxnbFHZ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-DKxnbFHZ.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Error thrown for HTTP errors (non-2xx responses)\n */\nexport class FetchError extends Error {\n status: Response[\"status\"];\n response: Response;\n\n constructor(message: string, response: Response) {\n super(message);\n this.name = \"FetchError\";\n this.status = response.status;\n this.response = response;\n }\n}\n\n/**\n * Error thrown for validation errors\n */\nexport class ValidationError extends Error {\n issues: StandardSchemaV1.Issue[];\n\n constructor(issues: StandardSchemaV1.Issue[]) {\n super(JSON.stringify(issues, null, 2));\n this.name = \"ValidationError\";\n this.issues = issues;\n }\n}\n"],"mappings":";;;;AAKA,IAAa,aAAb,cAAgC,MAAM;CACpC;CACA;CAEA,YAAY,SAAiB,UAAoB;AAC/C,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS,SAAS;AACvB,OAAK,WAAW;;;;;;AAOpB,IAAa,kBAAb,cAAqC,MAAM;CACzC;CAEA,YAAY,QAAkC;AAC5C,QAAM,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AACtC,OAAK,OAAO;AACZ,OAAK,SAAS"}
@@ -0,0 +1,22 @@
1
+ import { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ //#region src/errors.d.ts
4
+
5
+ /**
6
+ * Error thrown for HTTP errors (non-2xx responses)
7
+ */
8
+ declare class FetchError extends Error {
9
+ status: Response["status"];
10
+ response: Response;
11
+ constructor(message: string, response: Response);
12
+ }
13
+ /**
14
+ * Error thrown for validation errors
15
+ */
16
+ declare class ValidationError extends Error {
17
+ issues: StandardSchemaV1.Issue[];
18
+ constructor(issues: StandardSchemaV1.Issue[]);
19
+ }
20
+ //#endregion
21
+ export { FetchError, ValidationError };
22
+ //# sourceMappingURL=errors.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AACU,cADG,UAAA,SAAmB,KAAA,CACtB;EACE,MAAA,EADF,QACE,CAAA,QAAA,CAAA;EAE6B,QAAA,EAF7B,QAE6B;EAJT,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EAIS,QAJT;;AAehC;;;AAAqC,cAAxB,eAAA,SAAwB,KAAA,CAAA;EAAK,MAAA,EAChC,gBAAA,CAAiB,KADe,EAAA;sBAGpB,gBAAA,CAAiB"}
@@ -0,0 +1,3 @@
1
+ import { n as ValidationError, t as FetchError } from "./errors-DKxnbFHZ.mjs";
2
+
3
+ export { FetchError, ValidationError };
package/dist/index.d.mts CHANGED
@@ -1,37 +1,51 @@
1
- import { n as ResponseType, t as FetchConfig } from "./index-Dz58_HD6.mjs";
2
- import { z } from "zod";
1
+ import { n as ExtendedRequestInit, t as CreateFetchOptions } from "./types-C0Uhh2KM.mjs";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
 
4
4
  //#region src/index.d.ts
5
5
 
6
6
  /**
7
- * Type-safe fetch wrapper with Zod validation
7
+ * Type-safe fetch wrapper with Standard Schema validation.
8
+ *
9
+ * - When `throwOnValidationError: true`: validated data of type `TSchema`
10
+ * - When `throwOnValidationError: false`: Standard Schema Result object `{ value?, issues? }`
11
+ * - When `throwOnFetchError: true`: throws `FetchError` on non-ok responses
12
+ *
13
+ * If no schema is provided, returns the raw `Response` object.
14
+ *
15
+ * @throws {FetchError} When `throwOnFetchError: true` and response is not ok
16
+ * @throws {ValidationError} When `throwOnValidationError: true` and validation fails
8
17
  *
9
18
  * @example
10
19
  * import { z } from "zod";
11
20
  * import { $fetch } from "@zap-studio/fetch";
12
21
  *
13
- * const UserSchema = z.object({
14
- * id: z.number(),
15
- * name: z.string(),
16
- * email: z.string().email(),
17
- * });
22
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
23
+ *
24
+ * // Basic usage (schema validation)
25
+ * const user = await $fetch("/api/users/1", UserSchema, { headers: { "Authorization": "Bearer token" } });
26
+ * console.log("Validated user:", user);
18
27
  *
19
- * async function getUser(userId: number) {
20
- * const user = await $fetch(
21
- * `https://api.example.com/users/${userId}`,
22
- * UserSchema,
23
- * { method: "GET" }
24
- * );
25
- * return user; // user is typed as { id: number; name: string; email: string; }
28
+ * // Raw usage (no schema validation and typed Response object)
29
+ * const result = await $fetch("/api/data", { method: "POST", body: JSON.stringify({ key: "value" }) });
30
+ * const json = await result.json() as ResultType;
31
+ * console.log("Raw response data:", json);
32
+ *
33
+ * // Usage with validation errors returned instead of thrown
34
+ * const result = await $fetch("/api/users/1", UserSchema, { throwOnValidationError: false });
35
+ *
36
+ * if (result.issues) {
37
+ * console.error("Validation errors:", result.issues);
38
+ * } else {
39
+ * console.log("Validated user:", result.value);
26
40
  * }
27
41
  */
28
- declare function $fetch<TResponse, TBody = unknown, TResponseType extends ResponseType = "json">(resource: string, responseSchema: z.ZodType<TResponse>, config?: FetchConfig<TBody> & {
29
- throwOnValidationError?: boolean;
30
- responseType?: TResponseType;
31
- }): Promise<TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>>;
32
- declare const safeFetch: typeof $fetch;
42
+ declare function $fetch<TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: ExtendedRequestInit): Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
43
+ declare function $fetch(resource: string, options?: ExtendedRequestInit): Promise<Response>;
33
44
  /**
34
- * Convenience methods for common HTTP verbs
45
+ * Convenience methods for common HTTP verbs.
46
+ *
47
+ * These methods always require a schema for validation.
48
+ * For raw responses without validation, use `$fetch` directly.
35
49
  *
36
50
  * @example
37
51
  * import { z } from "zod";
@@ -44,17 +58,45 @@ declare const safeFetch: typeof $fetch;
44
58
  * });
45
59
  *
46
60
  * async function fetchPost(postId: number) {
47
- * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
61
+ * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
48
62
  * return post; // post is typed as { id: number; title: string; content: string; }
49
63
  * }
50
64
  */
51
65
  declare const api: {
52
- get: <TResponse>(resource: string, schema: z.ZodType<TResponse>, options?: Omit<RequestInit, "method" | "body">) => Promise<TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>>;
53
- post: <TResponse, TBody = unknown>(resource: string, schema: z.ZodType<TResponse>, body?: TBody, options?: Omit<RequestInit, "method" | "body">) => Promise<TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>>;
54
- put: <TResponse, TBody = unknown>(resource: string, schema: z.ZodType<TResponse>, body?: TBody, options?: Omit<RequestInit, "method" | "body">) => Promise<TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>>;
55
- patch: <TResponse, TBody = unknown>(resource: string, schema: z.ZodType<TResponse>, body?: TBody, options?: Omit<RequestInit, "method" | "body">) => Promise<TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>>;
56
- delete: <TResponse>(resource: string, schema: z.ZodType<TResponse>, options?: Omit<RequestInit, "method" | "body">) => Promise<TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>>;
66
+ get: <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: Omit<ExtendedRequestInit, "method">) => Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
67
+ post: <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: Omit<ExtendedRequestInit, "method">) => Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
68
+ put: <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: Omit<ExtendedRequestInit, "method">) => Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
69
+ patch: <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: Omit<ExtendedRequestInit, "method">) => Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
70
+ delete: <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: Omit<ExtendedRequestInit, "method">) => Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
71
+ };
72
+ /**
73
+ * Creates a custom fetch instance with pre-configured defaults.
74
+ *
75
+ * Use this factory to create API clients with a base URL, default headers,
76
+ * and other shared configuration. Each instance is independent.
77
+ *
78
+ * @example
79
+ * import { z } from "zod";
80
+ * import { createFetch } from "@zap-studio/fetch";
81
+ *
82
+ * // Create a configured instance
83
+ * const { $fetch, api } = createFetch({
84
+ * baseURL: "https://api.example.com",
85
+ * headers: { "Authorization": "Bearer token" },
86
+ * });
87
+ *
88
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
89
+ *
90
+ * // Now use relative paths - baseURL is prepended automatically
91
+ * const user = await api.get("/users/1", UserSchema);
92
+ *
93
+ * // Or use $fetch directly
94
+ * const response = await $fetch("/users", UserSchema, { method: "POST", body: { name: "John" } });
95
+ */
96
+ declare function createFetch(factoryOptions?: CreateFetchOptions): {
97
+ $fetch: typeof $fetch;
98
+ api: typeof api;
57
99
  };
58
100
  //#endregion
59
- export { $fetch, api, safeFetch };
101
+ export { $fetch, api, createFetch };
60
102
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AA2BA;;;;;;;;;;;;;;AA+CA;AAoBA;;;;;AAIkD,iBAvE5B,MAuE4B,CAAA,SAAA,EAAA,QAAA,OAAA,EAAA,sBApE1B,YAoE0B,GAAA,MAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,cAAA,EAjEhC,CAAA,CAAE,OAiE8B,CAjEtB,SAiEsB,CAAA,EAAA,MAAA,CAAA,EAhEvC,WAgEuC,CAhE3B,KAgE2B,CAAA,GAAA;EAAA,sBAAA,CAAA,EAAA,OAAA;EAAA,YAAA,CAAA,EA9D/B,aA8D+B;CAAA,CAAA,EA5D/C,OA4D+C,CA3DhD,SA2DgD,GA3DpC,CAAA,CAAE,mBA2DkC,CA3Dd,SA2Dc,CAAA,GA3DD,CAAA,CAAE,iBA2DD,CA3DmB,SA2DnB,CAAA,CAAA;AAAA,cAxBrC,SAwBqC,EAAA,OAxB5B,MAwB4B;;;;;;;;;;;;;;;;;;;AAcA,cAlBrC,GAkBqC,EAAA;EAAA,GAAA,EAAA,CAAA,SAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAftC,CAAA,CAAE,OAeoC,CAf5B,SAe4B,CAAA,EAAA,OAAA,CAAA,EAdpC,IAcoC,CAd/B,WAc+B,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAdA,OAcA,CAdA,SAcA,GAdA,CAAA,CAAA,mBAcA,CAdA,SAcA,CAAA,GAdA,CAAA,CAAA,iBAcA,CAdA,SAcA,CAAA,CAAA;EAAA,IAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EATtC,CAAA,CAAE,OASoC,CAT5B,SAS4B,CAAA,EAAA,IAAA,CAAA,EARvC,KAQuC,EAAA,OAAA,CAAA,EAPpC,IAOoC,CAP/B,WAO+B,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAPA,OAOA,CAPA,SAOA,GAPA,CAAA,CAAA,mBAOA,CAPA,SAOA,CAAA,GAPA,CAAA,CAAA,iBAOA,CAPA,SAOA,CAAA,CAAA;EAAA,GAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAFtC,CAAA,CAAE,OAEoC,CAF5B,SAE4B,CAAA,EAAA,IAAA,CAAA,EADvC,KACuC,EAAA,OAAA,CAAA,EAApC,IAAoC,CAA/B,WAA+B,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAAA,OAAA,CAAA,SAAA,GAAA,CAAA,CAAA,mBAAA,CAAA,SAAA,CAAA,GAAA,CAAA,CAAA,iBAAA,CAAA,SAAA,CAAA,CAAA;EAAA,KAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAKtC,CAAA,CAAE,OALoC,CAK5B,SAL4B,CAAA,EAAA,IAAA,CAAA,EAMvC,KANuC,EAAA,OAAA,CAAA,EAOpC,IAPoC,CAO/B,WAP+B,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAOA,OAPA,CAOA,SAPA,GAOA,CAAA,CAAA,mBAPA,CAOA,SAPA,CAAA,GAOA,CAAA,CAAA,iBAPA,CAOA,SAPA,CAAA,CAAA;EAK5B,MAAA,EAAA,CAAA,SAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAOV,CAAA,CAAE,OAPQ,CAOA,SAPA,CAAA,EAAA,OAAA,CAAA,EAQR,IARQ,CAQH,WARG,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAQ4B,OAR5B,CAQ4B,SAR5B,GAQ4B,CAAA,CAAA,mBAR5B,CAQ4B,SAR5B,CAAA,GAQ4B,CAAA,CAAA,iBAR5B,CAQ4B,SAR5B,CAAA,CAAA;CAAV"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AA8CA;;;;;;;;;;;AASA;;;;;AAsCA;;;;;;;;;;;;;;;;;;iBA/CsB,uBAAuB,4CAEnC,mBACE,sBACT,QACC,gBAAA,CAAiB,YAAY,WAC7B,gBAAA,CAAiB,OAAO,gBAAA,CAAiB,YAAY;iBAGnC,MAAA,6BAEV,sBACT,QAAQ;;;;;;;;;;;;;;;;;;;;;;cAmCE;;;;;;;;;;;AAgCb;;;;;;;;;;;;;;;;;;;;iBAAgB,WAAA,kBAA4B;iBAC3B;cACH"}
package/dist/index.mjs CHANGED
@@ -1,44 +1,102 @@
1
- import { t as FetchError } from "./errors-BCMOymYz.mjs";
2
- import { n as prepareHeadersAndBody, t as parseResponse } from "./utils-CZ-1Z2-1.mjs";
1
+ import { t as FetchError } from "./errors-DKxnbFHZ.mjs";
2
+ import { n as standardValidate, t as isStandardSchema } from "./validator-BGjWrM4d.mjs";
3
3
 
4
- //#region src/index.ts
4
+ //#region src/internal/constants.ts
5
+ /**
6
+ * Default options for the global $fetch
7
+ */
8
+ const GLOBAL_DEFAULTS = {
9
+ baseURL: "",
10
+ headers: void 0,
11
+ throwOnFetchError: true,
12
+ throwOnValidationError: true
13
+ };
14
+
15
+ //#endregion
16
+ //#region src/internal/utils.ts
5
17
  /**
6
- * Type-safe fetch wrapper with Zod validation
18
+ * Merges two HeadersInit objects, with the second one taking precedence
7
19
  *
8
20
  * @example
9
- * import { z } from "zod";
10
- * import { $fetch } from "@zap-studio/fetch";
21
+ * const baseHeaders = { "Authorization": "Bearer token", "Content-Type": "application/json" };
22
+ * const overrideHeaders = { "Content-Type": "application/xml", "X-Custom-Header": "value" };
11
23
  *
12
- * const UserSchema = z.object({
13
- * id: z.number(),
14
- * name: z.string(),
15
- * email: z.string().email(),
16
- * });
24
+ * const merged = mergeHeaders(baseHeaders, overrideHeaders);
17
25
  *
18
- * async function getUser(userId: number) {
19
- * const user = await $fetch(
20
- * `https://api.example.com/users/${userId}`,
21
- * UserSchema,
22
- * { method: "GET" }
23
- * );
24
- * return user; // user is typed as { id: number; name: string; email: string; }
25
- * }
26
+ * // Resulting headers:
27
+ * // {
28
+ * // "Authorization": "Bearer token",
29
+ * // "Content-Type": "application/xml",
30
+ * // "X-Custom-Header": "value"
31
+ * // }
26
32
  */
27
- async function $fetch(resource, responseSchema, config) {
28
- const { body, headers, throwOnValidationError = true, responseType = "json", ...rest } = config || {};
29
- const { body: preparedBody, headers: preparedHeaders } = prepareHeadersAndBody(body, headers);
30
- const response = await fetch(resource, {
33
+ function mergeHeaders(base, override) {
34
+ if (!(base || override)) return;
35
+ const merged = new Headers(base);
36
+ if (override) {
37
+ const overrideHeaders = new Headers(override);
38
+ for (const [key, value] of overrideHeaders.entries()) merged.set(key, value);
39
+ }
40
+ return merged;
41
+ }
42
+ const TRAILING_SLASHES = /\/+$/;
43
+ const LEADING_SLASHES = /^\/+/;
44
+ const ABSOLUTE_URL_PATTERN = /^(https?:)?\/\//i;
45
+ /**
46
+ * Checks if a URL is absolute (starts with http://, https://, or //)
47
+ */
48
+ function isAbsoluteURL(url) {
49
+ return ABSOLUTE_URL_PATTERN.test(url);
50
+ }
51
+ /**
52
+ * Internal fetch implementation used by both $fetch and createFetch
53
+ */
54
+ async function fetchInternal(resource, schema, options, defaults) {
55
+ const { throwOnValidationError = defaults.throwOnValidationError, throwOnFetchError = defaults.throwOnFetchError, headers: requestHeaders, ...rest } = options || {};
56
+ const mergedHeaders = mergeHeaders(defaults.headers, requestHeaders);
57
+ const init = {
31
58
  ...rest,
32
- body: preparedBody,
33
- headers: preparedHeaders
59
+ headers: mergedHeaders
60
+ };
61
+ if (schema && init.body) {
62
+ init.body = JSON.stringify(init.body);
63
+ const existingHeaders = new Headers(init.headers);
64
+ if (!existingHeaders.has("Content-Type")) existingHeaders.set("Content-Type", "application/json");
65
+ init.headers = existingHeaders;
66
+ }
67
+ let url;
68
+ if (isAbsoluteURL(resource)) url = resource;
69
+ else {
70
+ const base = defaults.baseURL.replace(TRAILING_SLASHES, "");
71
+ const path = resource.replace(LEADING_SLASHES, "");
72
+ url = base ? `${base}/${path}` : resource;
73
+ }
74
+ const response = await fetch(url, init);
75
+ if (throwOnFetchError && !response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);
76
+ if (schema) return standardValidate(schema, await response.json(), throwOnValidationError);
77
+ return response;
78
+ }
79
+ /**
80
+ * Creates an HTTP method helper bound to a fetch function
81
+ */
82
+ function createMethod(fetchFn, method) {
83
+ return (resource, schema, options) => fetchFn(resource, schema, {
84
+ ...options,
85
+ method
34
86
  });
35
- if (!response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response.status, response.statusText, response);
36
- const data = await parseResponse(response, responseType);
37
- return throwOnValidationError ? responseSchema.parse(data) : responseSchema.safeParse(data);
38
87
  }
39
- const safeFetch = $fetch;
88
+
89
+ //#endregion
90
+ //#region src/index.ts
91
+ async function $fetch(resource, schemaOrOptions, optionsOrUndefined) {
92
+ const [schema, options] = isStandardSchema(schemaOrOptions) ? [schemaOrOptions, optionsOrUndefined] : [void 0, schemaOrOptions];
93
+ return await fetchInternal(resource, schema, options, GLOBAL_DEFAULTS);
94
+ }
40
95
  /**
41
- * Convenience methods for common HTTP verbs
96
+ * Convenience methods for common HTTP verbs.
97
+ *
98
+ * These methods always require a schema for validation.
99
+ * For raw responses without validation, use `$fetch` directly.
42
100
  *
43
101
  * @example
44
102
  * import { z } from "zod";
@@ -51,36 +109,64 @@ const safeFetch = $fetch;
51
109
  * });
52
110
  *
53
111
  * async function fetchPost(postId: number) {
54
- * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
112
+ * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
55
113
  * return post; // post is typed as { id: number; title: string; content: string; }
56
114
  * }
57
115
  */
58
116
  const api = {
59
- get: (resource, schema, options) => $fetch(resource, schema, {
60
- ...options,
61
- method: "GET"
62
- }),
63
- post: (resource, schema, body, options) => $fetch(resource, schema, {
64
- ...options,
65
- method: "POST",
66
- body
67
- }),
68
- put: (resource, schema, body, options) => $fetch(resource, schema, {
69
- ...options,
70
- method: "PUT",
71
- body
72
- }),
73
- patch: (resource, schema, body, options) => $fetch(resource, schema, {
74
- ...options,
75
- method: "PATCH",
76
- body
77
- }),
78
- delete: (resource, schema, options) => $fetch(resource, schema, {
79
- ...options,
80
- method: "DELETE"
81
- })
117
+ get: createMethod($fetch, "GET"),
118
+ post: createMethod($fetch, "POST"),
119
+ put: createMethod($fetch, "PUT"),
120
+ patch: createMethod($fetch, "PATCH"),
121
+ delete: createMethod($fetch, "DELETE")
82
122
  };
123
+ /**
124
+ * Creates a custom fetch instance with pre-configured defaults.
125
+ *
126
+ * Use this factory to create API clients with a base URL, default headers,
127
+ * and other shared configuration. Each instance is independent.
128
+ *
129
+ * @example
130
+ * import { z } from "zod";
131
+ * import { createFetch } from "@zap-studio/fetch";
132
+ *
133
+ * // Create a configured instance
134
+ * const { $fetch, api } = createFetch({
135
+ * baseURL: "https://api.example.com",
136
+ * headers: { "Authorization": "Bearer token" },
137
+ * });
138
+ *
139
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
140
+ *
141
+ * // Now use relative paths - baseURL is prepended automatically
142
+ * const user = await api.get("/users/1", UserSchema);
143
+ *
144
+ * // Or use $fetch directly
145
+ * const response = await $fetch("/users", UserSchema, { method: "POST", body: { name: "John" } });
146
+ */
147
+ function createFetch(factoryOptions = {}) {
148
+ const defaults = {
149
+ baseURL: factoryOptions.baseURL ?? "",
150
+ headers: factoryOptions.headers,
151
+ throwOnFetchError: factoryOptions.throwOnFetchError ?? true,
152
+ throwOnValidationError: factoryOptions.throwOnValidationError ?? true
153
+ };
154
+ async function customFetch(resource, schemaOrOptions, optionsOrUndefined) {
155
+ const [schema, options] = isStandardSchema(schemaOrOptions) ? [schemaOrOptions, optionsOrUndefined] : [void 0, schemaOrOptions];
156
+ return await fetchInternal(resource, schema, options, defaults);
157
+ }
158
+ return {
159
+ $fetch: customFetch,
160
+ api: {
161
+ get: createMethod(customFetch, "GET"),
162
+ post: createMethod(customFetch, "POST"),
163
+ put: createMethod(customFetch, "PUT"),
164
+ patch: createMethod(customFetch, "PATCH"),
165
+ delete: createMethod(customFetch, "DELETE")
166
+ }
167
+ };
168
+ }
83
169
 
84
170
  //#endregion
85
- export { $fetch, api, safeFetch };
171
+ export { $fetch, api, createFetch };
86
172
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { z } from \"zod\";\nimport { FetchError } from \"./errors\";\nimport type { FetchConfig, ResponseType } from \"./types\";\nimport { parseResponse, prepareHeadersAndBody } from \"./utils\";\n\n/**\n * Type-safe fetch wrapper with Zod validation\n *\n * @example\n * import { z } from \"zod\";\n * import { $fetch } from \"@zap-studio/fetch\";\n *\n * const UserSchema = z.object({\n * id: z.number(),\n * name: z.string(),\n * email: z.string().email(),\n * });\n *\n * async function getUser(userId: number) {\n * const user = await $fetch(\n * `https://api.example.com/users/${userId}`,\n * UserSchema,\n * { method: \"GET\" }\n * );\n * return user; // user is typed as { id: number; name: string; email: string; }\n * }\n */\nexport async function $fetch<\n TResponse,\n TBody = unknown,\n TResponseType extends ResponseType = \"json\",\n>(\n resource: string,\n responseSchema: z.ZodType<TResponse>,\n config?: FetchConfig<TBody> & {\n throwOnValidationError?: boolean;\n responseType?: TResponseType;\n }\n): Promise<\n TResponse | z.ZodSafeParseSuccess<TResponse> | z.ZodSafeParseError<TResponse>\n> {\n const {\n body,\n headers,\n throwOnValidationError = true,\n responseType = \"json\" as TResponseType,\n ...rest\n } = config || {};\n\n const { body: preparedBody, headers: preparedHeaders } =\n prepareHeadersAndBody(body, headers);\n\n const response = await fetch(resource, {\n ...rest,\n body: preparedBody,\n headers: preparedHeaders,\n });\n\n if (!response.ok) {\n throw new FetchError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n response.statusText,\n response\n );\n }\n\n const data = await parseResponse(response, responseType);\n\n return throwOnValidationError\n ? responseSchema.parse(data)\n : responseSchema.safeParse(data);\n}\n\nexport const safeFetch = $fetch;\n\n/**\n * Convenience methods for common HTTP verbs\n *\n * @example\n * import { z } from \"zod\";\n * import { api } from \"@zap-studio/fetch\";\n *\n * const PostSchema = z.object({\n * id: z.number(),\n * title: z.string(),\n * content: z.string(),\n * });\n *\n * async function fetchPost(postId: number) {\n * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);\n * return post; // post is typed as { id: number; title: string; content: string; }\n * }\n */\nexport const api = {\n get: <TResponse>(\n resource: string,\n schema: z.ZodType<TResponse>,\n options?: Omit<RequestInit, \"method\" | \"body\">\n ) => $fetch(resource, schema, { ...options, method: \"GET\" }),\n\n post: <TResponse, TBody = unknown>(\n resource: string,\n schema: z.ZodType<TResponse>,\n body?: TBody,\n options?: Omit<RequestInit, \"method\" | \"body\">\n ) => $fetch(resource, schema, { ...options, method: \"POST\", body }),\n\n put: <TResponse, TBody = unknown>(\n resource: string,\n schema: z.ZodType<TResponse>,\n body?: TBody,\n options?: Omit<RequestInit, \"method\" | \"body\">\n ) => $fetch(resource, schema, { ...options, method: \"PUT\", body }),\n\n patch: <TResponse, TBody = unknown>(\n resource: string,\n schema: z.ZodType<TResponse>,\n body?: TBody,\n options?: Omit<RequestInit, \"method\" | \"body\">\n ) => $fetch(resource, schema, { ...options, method: \"PATCH\", body }),\n\n delete: <TResponse>(\n resource: string,\n schema: z.ZodType<TResponse>,\n options?: Omit<RequestInit, \"method\" | \"body\">\n ) => $fetch(resource, schema, { ...options, method: \"DELETE\" }),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,OAKpB,UACA,gBACA,QAMA;CACA,MAAM,EACJ,MACA,SACA,yBAAyB,MACzB,eAAe,QACf,GAAG,SACD,UAAU,EAAE;CAEhB,MAAM,EAAE,MAAM,cAAc,SAAS,oBACnC,sBAAsB,MAAM,QAAQ;CAEtC,MAAM,WAAW,MAAM,MAAM,UAAU;EACrC,GAAG;EACH,MAAM;EACN,SAAS;EACV,CAAC;AAEF,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,WACR,QAAQ,SAAS,OAAO,IAAI,SAAS,cACrC,SAAS,QACT,SAAS,YACT,SACD;CAGH,MAAM,OAAO,MAAM,cAAc,UAAU,aAAa;AAExD,QAAO,yBACH,eAAe,MAAM,KAAK,GAC1B,eAAe,UAAU,KAAK;;AAGpC,MAAa,YAAY;;;;;;;;;;;;;;;;;;;AAoBzB,MAAa,MAAM;CACjB,MACE,UACA,QACA,YACG,OAAO,UAAU,QAAQ;EAAE,GAAG;EAAS,QAAQ;EAAO,CAAC;CAE5D,OACE,UACA,QACA,MACA,YACG,OAAO,UAAU,QAAQ;EAAE,GAAG;EAAS,QAAQ;EAAQ;EAAM,CAAC;CAEnE,MACE,UACA,QACA,MACA,YACG,OAAO,UAAU,QAAQ;EAAE,GAAG;EAAS,QAAQ;EAAO;EAAM,CAAC;CAElE,QACE,UACA,QACA,MACA,YACG,OAAO,UAAU,QAAQ;EAAE,GAAG;EAAS,QAAQ;EAAS;EAAM,CAAC;CAEpE,SACE,UACA,QACA,YACG,OAAO,UAAU,QAAQ;EAAE,GAAG;EAAS,QAAQ;EAAU,CAAC;CAChE"}
1
+ {"version":3,"file":"index.mjs","names":["url: string","defaults: FetchDefaults"],"sources":["../src/internal/constants.ts","../src/internal/utils.ts","../src/index.ts"],"sourcesContent":["import type { FetchDefaults } from \"../types\";\n\n/**\n * Default options for the global $fetch\n */\nexport const GLOBAL_DEFAULTS = {\n baseURL: \"\",\n headers: undefined,\n throwOnFetchError: true,\n throwOnValidationError: true,\n} as const satisfies FetchDefaults;\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { $fetch } from \"..\";\nimport { FetchError } from \"../errors\";\nimport type { ExtendedRequestInit, FetchDefaults } from \"../types\";\nimport { standardValidate } from \"../validator\";\n\n/**\n * Merges two HeadersInit objects, with the second one taking precedence\n *\n * @example\n * const baseHeaders = { \"Authorization\": \"Bearer token\", \"Content-Type\": \"application/json\" };\n * const overrideHeaders = { \"Content-Type\": \"application/xml\", \"X-Custom-Header\": \"value\" };\n *\n * const merged = mergeHeaders(baseHeaders, overrideHeaders);\n *\n * // Resulting headers:\n * // {\n * // \"Authorization\": \"Bearer token\",\n * // \"Content-Type\": \"application/xml\",\n * // \"X-Custom-Header\": \"value\"\n * // }\n */\nexport function mergeHeaders(\n base: HeadersInit | undefined,\n override: HeadersInit | undefined\n): Headers | undefined {\n if (!(base || override)) {\n return;\n }\n\n const merged = new Headers(base);\n if (override) {\n const overrideHeaders = new Headers(override);\n for (const [key, value] of overrideHeaders.entries()) {\n merged.set(key, value);\n }\n }\n return merged;\n}\n\nconst TRAILING_SLASHES = /\\/+$/;\nconst LEADING_SLASHES = /^\\/+/;\nconst ABSOLUTE_URL_PATTERN = /^(https?:)?\\/\\//i;\n\n/**\n * Checks if a URL is absolute (starts with http://, https://, or //)\n */\nfunction isAbsoluteURL(url: string): boolean {\n return ABSOLUTE_URL_PATTERN.test(url);\n}\n\n/**\n * Internal fetch implementation used by both $fetch and createFetch\n */\nexport async function fetchInternal(\n resource: string,\n schema: StandardSchemaV1 | undefined,\n options: ExtendedRequestInit | undefined,\n defaults: FetchDefaults\n): Promise<unknown> {\n const {\n throwOnValidationError = defaults.throwOnValidationError,\n throwOnFetchError = defaults.throwOnFetchError,\n headers: requestHeaders,\n ...rest\n } = options || {};\n\n const mergedHeaders = mergeHeaders(defaults.headers, requestHeaders);\n const init = { ...rest, headers: mergedHeaders } as RequestInit;\n\n // Auto-stringify body and set default Content-Type if we have a schema\n if (schema && init.body) {\n init.body = JSON.stringify(init.body);\n\n const existingHeaders = new Headers(init.headers);\n if (!existingHeaders.has(\"Content-Type\")) {\n existingHeaders.set(\"Content-Type\", \"application/json\");\n }\n\n init.headers = existingHeaders;\n }\n\n // For absolute URLs, ignore baseURL entirely\n let url: string;\n if (isAbsoluteURL(resource)) {\n url = resource;\n } else {\n // Normalize URL by avoiding double slashes between baseURL and resource\n const base = defaults.baseURL.replace(TRAILING_SLASHES, \"\");\n const path = resource.replace(LEADING_SLASHES, \"\");\n url = base ? `${base}/${path}` : resource;\n }\n\n const response = await fetch(url, init);\n\n if (throwOnFetchError && !response.ok) {\n throw new FetchError(\n `HTTP ${response.status}: ${response.statusText}`,\n response\n );\n }\n\n // For json with schema, validate\n if (schema) {\n const raw = await response.json();\n return standardValidate(schema, raw, throwOnValidationError);\n }\n\n // No validation, return raw response data\n return response;\n}\n\n/**\n * Creates an HTTP method helper bound to a fetch function\n */\nexport function createMethod<TFetch extends typeof $fetch>(\n fetchFn: TFetch,\n method: string\n) {\n return <TSchema extends StandardSchemaV1>(\n resource: string,\n schema: TSchema,\n options?: Omit<ExtendedRequestInit, \"method\">\n ) => fetchFn(resource, schema, { ...options, method });\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { GLOBAL_DEFAULTS } from \"./internal/constants\";\nimport { createMethod, fetchInternal } from \"./internal/utils\";\nimport type {\n CreateFetchOptions,\n ExtendedRequestInit,\n FetchDefaults,\n} from \"./types\";\nimport { isStandardSchema } from \"./validator\";\n\n/**\n * Type-safe fetch wrapper with Standard Schema validation.\n *\n * - When `throwOnValidationError: true`: validated data of type `TSchema`\n * - When `throwOnValidationError: false`: Standard Schema Result object `{ value?, issues? }`\n * - When `throwOnFetchError: true`: throws `FetchError` on non-ok responses\n *\n * If no schema is provided, returns the raw `Response` object.\n *\n * @throws {FetchError} When `throwOnFetchError: true` and response is not ok\n * @throws {ValidationError} When `throwOnValidationError: true` and validation fails\n *\n * @example\n * import { z } from \"zod\";\n * import { $fetch } from \"@zap-studio/fetch\";\n *\n * const UserSchema = z.object({ id: z.number(), name: z.string() });\n *\n * // Basic usage (schema validation)\n * const user = await $fetch(\"/api/users/1\", UserSchema, { headers: { \"Authorization\": \"Bearer token\" } });\n * console.log(\"Validated user:\", user);\n *\n * // Raw usage (no schema validation and typed Response object)\n * const result = await $fetch(\"/api/data\", { method: \"POST\", body: JSON.stringify({ key: \"value\" }) });\n * const json = await result.json() as ResultType;\n * console.log(\"Raw response data:\", json);\n *\n * // Usage with validation errors returned instead of thrown\n * const result = await $fetch(\"/api/users/1\", UserSchema, { throwOnValidationError: false });\n *\n * if (result.issues) {\n * console.error(\"Validation errors:\", result.issues);\n * } else {\n * console.log(\"Validated user:\", result.value);\n * }\n */\nexport async function $fetch<TSchema extends StandardSchemaV1>(\n resource: string,\n schema: TSchema,\n options?: ExtendedRequestInit\n): Promise<\n | StandardSchemaV1.InferOutput<TSchema>\n | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>\n>;\n\nexport async function $fetch(\n resource: string,\n options?: ExtendedRequestInit\n): Promise<Response>;\n\nexport async function $fetch(\n resource: string,\n schemaOrOptions?: StandardSchemaV1 | ExtendedRequestInit,\n optionsOrUndefined?: ExtendedRequestInit\n): Promise<unknown> {\n const [schema, options] = isStandardSchema(schemaOrOptions)\n ? [schemaOrOptions, optionsOrUndefined]\n : [undefined, schemaOrOptions];\n\n return await fetchInternal(resource, schema, options, GLOBAL_DEFAULTS);\n}\n\n/**\n * Convenience methods for common HTTP verbs.\n *\n * These methods always require a schema for validation.\n * For raw responses without validation, use `$fetch` directly.\n *\n * @example\n * import { z } from \"zod\";\n * import { api } from \"@zap-studio/fetch\";\n *\n * const PostSchema = z.object({\n * id: z.number(),\n * title: z.string(),\n * content: z.string(),\n * });\n *\n * async function fetchPost(postId: number) {\n * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);\n * return post; // post is typed as { id: number; title: string; content: string; }\n * }\n */\nexport const api = {\n get: createMethod($fetch, \"GET\"),\n post: createMethod($fetch, \"POST\"),\n put: createMethod($fetch, \"PUT\"),\n patch: createMethod($fetch, \"PATCH\"),\n delete: createMethod($fetch, \"DELETE\"),\n};\n\n/**\n * Creates a custom fetch instance with pre-configured defaults.\n *\n * Use this factory to create API clients with a base URL, default headers,\n * and other shared configuration. Each instance is independent.\n *\n * @example\n * import { z } from \"zod\";\n * import { createFetch } from \"@zap-studio/fetch\";\n *\n * // Create a configured instance\n * const { $fetch, api } = createFetch({\n * baseURL: \"https://api.example.com\",\n * headers: { \"Authorization\": \"Bearer token\" },\n * });\n *\n * const UserSchema = z.object({ id: z.number(), name: z.string() });\n *\n * // Now use relative paths - baseURL is prepended automatically\n * const user = await api.get(\"/users/1\", UserSchema);\n *\n * // Or use $fetch directly\n * const response = await $fetch(\"/users\", UserSchema, { method: \"POST\", body: { name: \"John\" } });\n */\nexport function createFetch(factoryOptions: CreateFetchOptions = {}): {\n $fetch: typeof $fetch;\n api: typeof api;\n} {\n const defaults: FetchDefaults = {\n baseURL: factoryOptions.baseURL ?? \"\",\n headers: factoryOptions.headers,\n throwOnFetchError: factoryOptions.throwOnFetchError ?? true,\n throwOnValidationError: factoryOptions.throwOnValidationError ?? true,\n };\n\n async function customFetch<TSchema extends StandardSchemaV1>(\n resource: string,\n schema: TSchema,\n options?: ExtendedRequestInit\n ): Promise<\n | StandardSchemaV1.InferOutput<TSchema>\n | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>\n >;\n\n async function customFetch(\n resource: string,\n options?: ExtendedRequestInit\n ): Promise<Response>;\n\n async function customFetch(\n resource: string,\n schemaOrOptions?: StandardSchemaV1 | ExtendedRequestInit,\n optionsOrUndefined?: ExtendedRequestInit\n ): Promise<unknown> {\n const [schema, options] = isStandardSchema(schemaOrOptions)\n ? [schemaOrOptions, optionsOrUndefined]\n : [undefined, schemaOrOptions];\n\n return await fetchInternal(resource, schema, options, defaults);\n }\n\n const customApi = {\n get: createMethod(customFetch, \"GET\"),\n post: createMethod(customFetch, \"POST\"),\n put: createMethod(customFetch, \"PUT\"),\n patch: createMethod(customFetch, \"PATCH\"),\n delete: createMethod(customFetch, \"DELETE\"),\n };\n\n return {\n $fetch: customFetch,\n api: customApi,\n };\n}\n"],"mappings":";;;;;;;AAKA,MAAa,kBAAkB;CAC7B,SAAS;CACT,SAAS;CACT,mBAAmB;CACnB,wBAAwB;CACzB;;;;;;;;;;;;;;;;;;;;ACYD,SAAgB,aACd,MACA,UACqB;AACrB,KAAI,EAAE,QAAQ,UACZ;CAGF,MAAM,SAAS,IAAI,QAAQ,KAAK;AAChC,KAAI,UAAU;EACZ,MAAM,kBAAkB,IAAI,QAAQ,SAAS;AAC7C,OAAK,MAAM,CAAC,KAAK,UAAU,gBAAgB,SAAS,CAClD,QAAO,IAAI,KAAK,MAAM;;AAG1B,QAAO;;AAGT,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;;;;AAK7B,SAAS,cAAc,KAAsB;AAC3C,QAAO,qBAAqB,KAAK,IAAI;;;;;AAMvC,eAAsB,cACpB,UACA,QACA,SACA,UACkB;CAClB,MAAM,EACJ,yBAAyB,SAAS,wBAClC,oBAAoB,SAAS,mBAC7B,SAAS,gBACT,GAAG,SACD,WAAW,EAAE;CAEjB,MAAM,gBAAgB,aAAa,SAAS,SAAS,eAAe;CACpE,MAAM,OAAO;EAAE,GAAG;EAAM,SAAS;EAAe;AAGhD,KAAI,UAAU,KAAK,MAAM;AACvB,OAAK,OAAO,KAAK,UAAU,KAAK,KAAK;EAErC,MAAM,kBAAkB,IAAI,QAAQ,KAAK,QAAQ;AACjD,MAAI,CAAC,gBAAgB,IAAI,eAAe,CACtC,iBAAgB,IAAI,gBAAgB,mBAAmB;AAGzD,OAAK,UAAU;;CAIjB,IAAIA;AACJ,KAAI,cAAc,SAAS,CACzB,OAAM;MACD;EAEL,MAAM,OAAO,SAAS,QAAQ,QAAQ,kBAAkB,GAAG;EAC3D,MAAM,OAAO,SAAS,QAAQ,iBAAiB,GAAG;AAClD,QAAM,OAAO,GAAG,KAAK,GAAG,SAAS;;CAGnC,MAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AAEvC,KAAI,qBAAqB,CAAC,SAAS,GACjC,OAAM,IAAI,WACR,QAAQ,SAAS,OAAO,IAAI,SAAS,cACrC,SACD;AAIH,KAAI,OAEF,QAAO,iBAAiB,QADZ,MAAM,SAAS,MAAM,EACI,uBAAuB;AAI9D,QAAO;;;;;AAMT,SAAgB,aACd,SACA,QACA;AACA,SACE,UACA,QACA,YACG,QAAQ,UAAU,QAAQ;EAAE,GAAG;EAAS;EAAQ,CAAC;;;;;AC/DxD,eAAsB,OACpB,UACA,iBACA,oBACkB;CAClB,MAAM,CAAC,QAAQ,WAAW,iBAAiB,gBAAgB,GACvD,CAAC,iBAAiB,mBAAmB,GACrC,CAAC,QAAW,gBAAgB;AAEhC,QAAO,MAAM,cAAc,UAAU,QAAQ,SAAS,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;AAwBxE,MAAa,MAAM;CACjB,KAAK,aAAa,QAAQ,MAAM;CAChC,MAAM,aAAa,QAAQ,OAAO;CAClC,KAAK,aAAa,QAAQ,MAAM;CAChC,OAAO,aAAa,QAAQ,QAAQ;CACpC,QAAQ,aAAa,QAAQ,SAAS;CACvC;;;;;;;;;;;;;;;;;;;;;;;;;AA0BD,SAAgB,YAAY,iBAAqC,EAAE,EAGjE;CACA,MAAMC,WAA0B;EAC9B,SAAS,eAAe,WAAW;EACnC,SAAS,eAAe;EACxB,mBAAmB,eAAe,qBAAqB;EACvD,wBAAwB,eAAe,0BAA0B;EAClE;CAgBD,eAAe,YACb,UACA,iBACA,oBACkB;EAClB,MAAM,CAAC,QAAQ,WAAW,iBAAiB,gBAAgB,GACvD,CAAC,iBAAiB,mBAAmB,GACrC,CAAC,QAAW,gBAAgB;AAEhC,SAAO,MAAM,cAAc,UAAU,QAAQ,SAAS,SAAS;;AAWjE,QAAO;EACL,QAAQ;EACR,KAVgB;GAChB,KAAK,aAAa,aAAa,MAAM;GACrC,MAAM,aAAa,aAAa,OAAO;GACvC,KAAK,aAAa,aAAa,MAAM;GACrC,OAAO,aAAa,aAAa,QAAQ;GACzC,QAAQ,aAAa,aAAa,SAAS;GAC5C;EAKA"}
@@ -0,0 +1,52 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Extended RequestInit type to include custom fetch options
4
+ */
5
+ type ExtendedRequestInit = Omit<RequestInit, "body"> & {
6
+ /**
7
+ * Request body - can be a BodyInit value or an object that will be JSON-stringified
8
+ */
9
+ body?: BodyInit | Record<string, unknown>;
10
+ /**
11
+ * Whether to throw a FetchError on HTTP errors (non-2xx responses)
12
+ * @default true
13
+ */
14
+ throwOnFetchError?: boolean;
15
+ /**
16
+ * Whether to throw a ValidationError on validation errors
17
+ * @default true
18
+ */
19
+ throwOnValidationError?: boolean;
20
+ };
21
+ /**
22
+ * Internal defaults used by fetchInternal
23
+ */
24
+ type FetchDefaults = {
25
+ /**
26
+ * Base URL to prepend to all requests
27
+ * @default ""
28
+ */
29
+ baseURL: string;
30
+ /**
31
+ * Default headers to include in all requests
32
+ * @default undefined
33
+ */
34
+ headers: HeadersInit | undefined;
35
+ /**
36
+ * Whether to throw a `FetchError` on HTTP errors (non-2xx responses)
37
+ * @default true
38
+ */
39
+ throwOnFetchError: boolean;
40
+ /**
41
+ * Whether to throw a `ValidationError` on validation errors
42
+ * @default true
43
+ */
44
+ throwOnValidationError: boolean;
45
+ };
46
+ /**
47
+ * Options for creating a custom fetch instance with `createFetch`
48
+ */
49
+ type CreateFetchOptions = Partial<FetchDefaults>;
50
+ //#endregion
51
+ export { ExtendedRequestInit as n, FetchDefaults as r, CreateFetchOptions as t };
52
+ //# sourceMappingURL=types-C0Uhh2KM.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-C0Uhh2KM.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;AAGA;;AAAkC,KAAtB,mBAAA,GAAsB,IAAA,CAAK,WAAL,EAAA,MAAA,CAAA,GAAA;EAIzB;;;EAgBG,IAAA,CAAA,EAhBH,QAgBG,GAhBQ,MAgBK,CAAA,MAUd,EAAA,OAAW,CAAA;EAgBV;;;;;;;;;;;;;;KA1BA,aAAA;;;;;;;;;;WAUD;;;;;;;;;;;;;;;KAgBC,kBAAA,GAAqB,QAAQ"}
@@ -0,0 +1,2 @@
1
+ import { n as ExtendedRequestInit, r as FetchDefaults, t as CreateFetchOptions } from "./types-C0Uhh2KM.mjs";
2
+ export { CreateFetchOptions, ExtendedRequestInit, FetchDefaults };
@@ -0,0 +1,44 @@
1
+ import { n as ValidationError } from "./errors-DKxnbFHZ.mjs";
2
+
3
+ //#region src/validator.ts
4
+ /**
5
+ * Type guard to check if a value is a Standard Schema schema
6
+ */
7
+ function isStandardSchema(value) {
8
+ return !!value && (typeof value === "object" || typeof value === "function") && "~standard" in value;
9
+ }
10
+ /**
11
+ * Helper function to validate data using Standard Schema
12
+ *
13
+ * @throws {ValidationError} When `throwOnError` is true and validation fails
14
+ *
15
+ * @example
16
+ * import { standardValidate } from "@zap-studio/fetch/validator";
17
+ * import { z } from "zod";
18
+ *
19
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
20
+ *
21
+ * // Basic usage
22
+ * const user = await standardValidate(UserSchema, data);
23
+ *
24
+ * // Non-throwing usage
25
+ * const result = await standardValidate(UserSchema, data, false);
26
+ * if (result.issues) {
27
+ * console.error("Validation failed:", result.issues);
28
+ * } else {
29
+ * console.log("Success:", result.value);
30
+ * }
31
+ */
32
+ async function standardValidate(schema, input, throwOnError) {
33
+ let result = schema["~standard"].validate(input);
34
+ if (result instanceof Promise) result = await result;
35
+ if (result.issues) {
36
+ if (throwOnError) throw new ValidationError([...result.issues]);
37
+ return result;
38
+ }
39
+ return throwOnError ? result.value : result;
40
+ }
41
+
42
+ //#endregion
43
+ export { standardValidate as n, isStandardSchema as t };
44
+ //# sourceMappingURL=validator-BGjWrM4d.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator-BGjWrM4d.mjs","names":[],"sources":["../src/validator.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { ValidationError } from \"./errors\";\n\n/**\n * Type guard to check if a value is a Standard Schema schema\n */\nexport function isStandardSchema(value: unknown): value is StandardSchemaV1 {\n return (\n !!value &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n \"~standard\" in value\n );\n}\n\n/**\n * Helper function to validate data using Standard Schema\n *\n * @throws {ValidationError} When `throwOnError` is true and validation fails\n *\n * @example\n * import { standardValidate } from \"@zap-studio/fetch/validator\";\n * import { z } from \"zod\";\n *\n * const UserSchema = z.object({ id: z.number(), name: z.string() });\n *\n * // Basic usage\n * const user = await standardValidate(UserSchema, data);\n *\n * // Non-throwing usage\n * const result = await standardValidate(UserSchema, data, false);\n * if (result.issues) {\n * console.error(\"Validation failed:\", result.issues);\n * } else {\n * console.log(\"Success:\", result.value);\n * }\n */\nexport async function standardValidate<TSchema extends StandardSchemaV1>(\n schema: TSchema,\n input: unknown,\n throwOnError: boolean\n): Promise<\n | StandardSchemaV1.InferOutput<TSchema>\n | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>\n> {\n let result = schema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n result = await result;\n }\n\n if (result.issues) {\n if (throwOnError) {\n throw new ValidationError([...result.issues]);\n }\n return result;\n }\n\n return throwOnError ? result.value : result;\n}\n"],"mappings":";;;;;;AAMA,SAAgB,iBAAiB,OAA2C;AAC1E,QACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,eAAe;;;;;;;;;;;;;;;;;;;;;;;;AA0BnB,eAAsB,iBACpB,QACA,OACA,cAIA;CACA,IAAI,SAAS,OAAO,aAAa,SAAS,MAAM;AAChD,KAAI,kBAAkB,QACpB,UAAS,MAAM;AAGjB,KAAI,OAAO,QAAQ;AACjB,MAAI,aACF,OAAM,IAAI,gBAAgB,CAAC,GAAG,OAAO,OAAO,CAAC;AAE/C,SAAO;;AAGT,QAAO,eAAe,OAAO,QAAQ"}
@@ -0,0 +1,34 @@
1
+ import { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ //#region src/validator.d.ts
4
+
5
+ /**
6
+ * Type guard to check if a value is a Standard Schema schema
7
+ */
8
+ declare function isStandardSchema(value: unknown): value is StandardSchemaV1;
9
+ /**
10
+ * Helper function to validate data using Standard Schema
11
+ *
12
+ * @throws {ValidationError} When `throwOnError` is true and validation fails
13
+ *
14
+ * @example
15
+ * import { standardValidate } from "@zap-studio/fetch/validator";
16
+ * import { z } from "zod";
17
+ *
18
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
19
+ *
20
+ * // Basic usage
21
+ * const user = await standardValidate(UserSchema, data);
22
+ *
23
+ * // Non-throwing usage
24
+ * const result = await standardValidate(UserSchema, data, false);
25
+ * if (result.issues) {
26
+ * console.error("Validation failed:", result.issues);
27
+ * } else {
28
+ * console.log("Success:", result.value);
29
+ * }
30
+ */
31
+ declare function standardValidate<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, throwOnError: boolean): Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
32
+ //#endregion
33
+ export { isStandardSchema, standardValidate };
34
+ //# sourceMappingURL=validator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.mts","names":[],"sources":["../src/validator.ts"],"sourcesContent":[],"mappings":";;;;;;AAMA;AA8BsB,iBA9BN,gBAAA,CA8BsB,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IA9BqB,gBA8BrB;;;;;;;;;;;;;;;;;;;;;;;iBAAhB,iCAAiC,0BAC7C,iDAGP,QACC,gBAAA,CAAiB,YAAY,WAC7B,gBAAA,CAAiB,OAAO,gBAAA,CAAiB,YAAY"}
@@ -0,0 +1,4 @@
1
+ import "./errors-DKxnbFHZ.mjs";
2
+ import { n as standardValidate, t as isStandardSchema } from "./validator-BGjWrM4d.mjs";
3
+
4
+ export { isStandardSchema, standardValidate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zap-studio/fetch",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -12,24 +12,27 @@
12
12
  "CHANGELOG.md"
13
13
  ],
14
14
  "dependencies": {
15
- "zod": "^4.1.12"
15
+ "@standard-schema/spec": "^1.0.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@types/node": "latest",
19
- "@vitest/coverage-v8": "^4.0.13",
20
- "jsdom": "latest",
21
- "tsdown": "latest",
22
- "typescript": "latest",
23
- "vitest": "latest",
18
+ "@types/node": "^24.10.1",
19
+ "@vitest/coverage-v8": "^4.0.14",
20
+ "arktype": "^2.1.27",
21
+ "jsdom": "^27.2.0",
22
+ "tsdown": "^0.17.0-beta.4",
23
+ "typescript": "^5.9.3",
24
+ "valibot": "^1.2.0",
25
+ "vitest": "^4.0.14",
26
+ "zod": "^4.1.13",
27
+ "@zap-studio/vitest-config": "0.0.0",
24
28
  "@zap-studio/tsdown-config": "0.0.0",
25
- "@zap-studio/typescript-config": "0.0.0",
26
- "@zap-studio/vitest-config": "0.0.0"
29
+ "@zap-studio/typescript-config": "0.0.0"
27
30
  },
28
31
  "exports": {
29
32
  ".": "./dist/index.mjs",
30
- "./errors": "./dist/errors/index.mjs",
31
- "./types": "./dist/types/index.mjs",
32
- "./utils": "./dist/utils/index.mjs",
33
+ "./errors": "./dist/errors.mjs",
34
+ "./types": "./dist/types.mjs",
35
+ "./validator": "./dist/validator.mjs",
33
36
  "./package.json": "./package.json"
34
37
  },
35
38
  "main": "./dist/index.mjs",
@@ -1,10 +0,0 @@
1
- //#region src/errors/index.d.ts
2
- declare class FetchError extends Error {
3
- status: number;
4
- statusText: string;
5
- response: Response;
6
- constructor(message: string, status: number, statusText: string, response: Response);
7
- }
8
- //#endregion
9
- export { FetchError };
10
- //# sourceMappingURL=index.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/errors/index.ts"],"sourcesContent":[],"mappings":";cAAa,UAAA,SAAmB,KAAA;EAAnB,MAAA,EAAA,MAAW;EAGZ,UAAA,EAAA,MAAA;EAME,QAAA,EANF,QAME;EATkB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EASlB,QATkB"}
@@ -1,3 +0,0 @@
1
- import { t as FetchError } from "../errors-BCMOymYz.mjs";
2
-
3
- export { FetchError };
@@ -1,17 +0,0 @@
1
- //#region src/errors/index.ts
2
- var FetchError = class extends Error {
3
- status;
4
- statusText;
5
- response;
6
- constructor(message, status, statusText, response) {
7
- super(message);
8
- this.name = "FetchError";
9
- this.status = status;
10
- this.statusText = statusText;
11
- this.response = response;
12
- }
13
- };
14
-
15
- //#endregion
16
- export { FetchError as t };
17
- //# sourceMappingURL=errors-BCMOymYz.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-BCMOymYz.mjs","names":[],"sources":["../src/errors/index.ts"],"sourcesContent":["export class FetchError extends Error {\n status: number;\n statusText: string;\n response: Response;\n\n constructor(\n message: string,\n status: number,\n statusText: string,\n response: Response\n ) {\n super(message);\n this.name = \"FetchError\";\n this.status = status;\n this.statusText = statusText;\n this.response = response;\n }\n}\n"],"mappings":";AAAA,IAAa,aAAb,cAAgC,MAAM;CACpC;CACA;CACA;CAEA,YACE,SACA,QACA,YACA,UACA;AACA,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,aAAa;AAClB,OAAK,WAAW"}
@@ -1,18 +0,0 @@
1
- //#region src/types/index.d.ts
2
- type FetchConfig<TBody> = Omit<RequestInit, "body"> & {
3
- body?: TBody;
4
- throwOnValidationError?: boolean;
5
- };
6
- type ResponseType = "arrayBuffer" | "blob" | "bytes" | "clone" | "formData" | "json" | "text";
7
- type ResponseTypeMap = {
8
- arrayBuffer: ArrayBuffer;
9
- blob: Blob;
10
- bytes: Uint8Array;
11
- clone: Response;
12
- formData: FormData;
13
- json: unknown;
14
- text: string;
15
- };
16
- //#endregion
17
- export { ResponseType as n, ResponseTypeMap as r, FetchConfig as t };
18
- //# sourceMappingURL=index-Dz58_HD6.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-Dz58_HD6.d.mts","names":[],"sources":["../src/types/index.ts"],"sourcesContent":[],"mappings":";KAAY,qBAAqB,KAAK;EAA1B,IAAA,CAAA,EACH,KADG;EAA0B,sBAAA,CAAA,EAAA,OAAA;CAAL;AACxB,KAIG,YAAA,GAJH,aAAA,GAAA,MAAA,GAAA,OAAA,GAAA,OAAA,GAAA,UAAA,GAAA,MAAA,GAAA,MAAA;AAAK,KAaF,eAAA,GAbE;EAIF,WAAA,EAUG,WAVS;EASZ,IAAA,EAEJ,IAFI;EACG,KAAA,EAEN,UAFM;EACP,KAAA,EAEC,QAFD;EACC,QAAA,EAEG,QAFH;EACA,IAAA,EAAA,OAAA;EACG,IAAA,EAAA,MAAA;CAAQ"}
@@ -1,2 +0,0 @@
1
- import { n as ResponseType, r as ResponseTypeMap, t as FetchConfig } from "../index-Dz58_HD6.mjs";
2
- export { FetchConfig, ResponseType, ResponseTypeMap };
@@ -1,11 +0,0 @@
1
- import { n as ResponseType, r as ResponseTypeMap } from "../index-Dz58_HD6.mjs";
2
-
3
- //#region src/utils/index.d.ts
4
- declare function parseResponse<TResponseType extends ResponseType>(response: Response, responseType: TResponseType): Promise<ResponseTypeMap[TResponseType]>;
5
- declare function prepareHeadersAndBody<TBody = unknown>(body: TBody, headers: HeadersInit | undefined): {
6
- body: BodyInit | null;
7
- headers: HeadersInit | undefined;
8
- };
9
- //#endregion
10
- export { parseResponse, prepareHeadersAndBody };
11
- //# sourceMappingURL=index.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utils/index.ts"],"sourcesContent":[],"mappings":";;;iBAGgB,oCAAoC,wBACxC,wBACI,gBACb,QAAQ,gBAAgB;iBAmEX,6CACR,gBACG;EAxEK,IAAA,EAyEL,QAzEK,GAAa,IAAA;EAAuB,OAAA,EAyEf,WAzEe,GAAA,SAAA;CACxC"}
@@ -1,4 +0,0 @@
1
- import "../errors-BCMOymYz.mjs";
2
- import { n as prepareHeadersAndBody, t as parseResponse } from "../utils-CZ-1Z2-1.mjs";
3
-
4
- export { parseResponse, prepareHeadersAndBody };
@@ -1,53 +0,0 @@
1
- import { t as FetchError } from "./errors-BCMOymYz.mjs";
2
-
3
- //#region src/utils/index.ts
4
- function parseResponse(response, responseType) {
5
- const contentType = response.headers.get("content-type");
6
- const parser = {
7
- json: async () => {
8
- if (!contentType?.includes("application/json")) throw new FetchError(contentType ? `Expected JSON response but received content type: ${contentType}` : "Expected JSON response but received no content type", response.status, response.statusText, response);
9
- return await response.json();
10
- },
11
- arrayBuffer: async () => response.arrayBuffer(),
12
- blob: async () => response.blob(),
13
- bytes: async () => {
14
- const buffer = await response.arrayBuffer();
15
- return new Uint8Array(buffer);
16
- },
17
- clone: async () => response.clone(),
18
- formData: async () => {
19
- if (!contentType?.includes("multipart/form-data")) throw new FetchError("Expected FormData response but received different content type", response.status, response.statusText, response);
20
- return await response.formData();
21
- },
22
- text: async () => {
23
- if (!contentType?.includes("text/")) throw new FetchError("Expected text response but received different content type", response.status, response.statusText, response);
24
- return await response.text();
25
- }
26
- }[responseType];
27
- if (!parser) return Promise.reject(new FetchError(`Unsupported response type: ${responseType}`, response.status, response.statusText, response));
28
- return parser();
29
- }
30
- function prepareHeadersAndBody(body, headers) {
31
- let preparedBody = null;
32
- let preparedHeaders = headers;
33
- if (body === null || body === void 0) return {
34
- body: null,
35
- headers: preparedHeaders
36
- };
37
- if (body instanceof FormData || body instanceof URLSearchParams || body instanceof Blob || body instanceof ArrayBuffer || body instanceof ReadableStream || typeof body === "string") preparedBody = body;
38
- else if (typeof body === "object") {
39
- preparedBody = JSON.stringify(body);
40
- preparedHeaders = {
41
- "Content-Type": "application/json",
42
- ...headers
43
- };
44
- } else preparedBody = String(body);
45
- return {
46
- body: preparedBody,
47
- headers: preparedHeaders
48
- };
49
- }
50
-
51
- //#endregion
52
- export { prepareHeadersAndBody as n, parseResponse as t };
53
- //# sourceMappingURL=utils-CZ-1Z2-1.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils-CZ-1Z2-1.mjs","names":["preparedBody: BodyInit | null"],"sources":["../src/utils/index.ts"],"sourcesContent":["import { FetchError } from \"../errors\";\nimport type { ResponseType, ResponseTypeMap } from \"../types\";\n\nexport function parseResponse<TResponseType extends ResponseType>(\n response: Response,\n responseType: TResponseType\n): Promise<ResponseTypeMap[TResponseType]> {\n const contentType = response.headers.get(\"content-type\");\n\n const parsers: Record<\n ResponseType,\n () => Promise<ResponseTypeMap[ResponseType]>\n > = {\n json: async () => {\n if (!contentType?.includes(\"application/json\")) {\n const errorMessage = contentType\n ? `Expected JSON response but received content type: ${contentType}`\n : \"Expected JSON response but received no content type\";\n throw new FetchError(\n errorMessage,\n response.status,\n response.statusText,\n response\n );\n }\n return await response.json();\n },\n arrayBuffer: async () => response.arrayBuffer(),\n blob: async () => response.blob(),\n bytes: async () => {\n const buffer = await response.arrayBuffer();\n return new Uint8Array(buffer);\n },\n clone: async () => response.clone(),\n formData: async () => {\n if (!contentType?.includes(\"multipart/form-data\")) {\n throw new FetchError(\n \"Expected FormData response but received different content type\",\n response.status,\n response.statusText,\n response\n );\n }\n return await response.formData();\n },\n text: async () => {\n if (!contentType?.includes(\"text/\")) {\n throw new FetchError(\n \"Expected text response but received different content type\",\n response.status,\n response.statusText,\n response\n );\n }\n return await response.text();\n },\n };\n\n const parser = parsers[responseType];\n if (!parser) {\n return Promise.reject(\n new FetchError(\n `Unsupported response type: ${responseType}`,\n response.status,\n response.statusText,\n response\n )\n ) as Promise<ResponseTypeMap[TResponseType]>;\n }\n\n return parser() as Promise<ResponseTypeMap[TResponseType]>;\n}\n\nexport function prepareHeadersAndBody<TBody = unknown>(\n body: TBody,\n headers: HeadersInit | undefined\n): { body: BodyInit | null; headers: HeadersInit | undefined } {\n let preparedBody: BodyInit | null = null;\n let preparedHeaders = headers;\n\n // Handle null/undefined body\n if (body === null || body === undefined) {\n return { body: null, headers: preparedHeaders };\n }\n\n // Check if body is already a valid BodyInit type\n if (\n body instanceof FormData ||\n body instanceof URLSearchParams ||\n body instanceof Blob ||\n body instanceof ArrayBuffer ||\n body instanceof ReadableStream ||\n typeof body === \"string\"\n ) {\n preparedBody = body as BodyInit;\n } else if (typeof body === \"object\") {\n // If body is a plain object, stringify it and set Content-Type\n preparedBody = JSON.stringify(body);\n preparedHeaders = {\n \"Content-Type\": \"application/json\",\n ...headers,\n };\n } else {\n // Handle other primitive types by converting to string\n preparedBody = String(body);\n }\n\n return { body: preparedBody, headers: preparedHeaders };\n}\n"],"mappings":";;;AAGA,SAAgB,cACd,UACA,cACyC;CACzC,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe;CAmDxD,MAAM,SA9CF;EACF,MAAM,YAAY;AAChB,OAAI,CAAC,aAAa,SAAS,mBAAmB,CAI5C,OAAM,IAAI,WAHW,cACjB,qDAAqD,gBACrD,uDAGF,SAAS,QACT,SAAS,YACT,SACD;AAEH,UAAO,MAAM,SAAS,MAAM;;EAE9B,aAAa,YAAY,SAAS,aAAa;EAC/C,MAAM,YAAY,SAAS,MAAM;EACjC,OAAO,YAAY;GACjB,MAAM,SAAS,MAAM,SAAS,aAAa;AAC3C,UAAO,IAAI,WAAW,OAAO;;EAE/B,OAAO,YAAY,SAAS,OAAO;EACnC,UAAU,YAAY;AACpB,OAAI,CAAC,aAAa,SAAS,sBAAsB,CAC/C,OAAM,IAAI,WACR,kEACA,SAAS,QACT,SAAS,YACT,SACD;AAEH,UAAO,MAAM,SAAS,UAAU;;EAElC,MAAM,YAAY;AAChB,OAAI,CAAC,aAAa,SAAS,QAAQ,CACjC,OAAM,IAAI,WACR,8DACA,SAAS,QACT,SAAS,YACT,SACD;AAEH,UAAO,MAAM,SAAS,MAAM;;EAE/B,CAEsB;AACvB,KAAI,CAAC,OACH,QAAO,QAAQ,OACb,IAAI,WACF,8BAA8B,gBAC9B,SAAS,QACT,SAAS,YACT,SACD,CACF;AAGH,QAAO,QAAQ;;AAGjB,SAAgB,sBACd,MACA,SAC6D;CAC7D,IAAIA,eAAgC;CACpC,IAAI,kBAAkB;AAGtB,KAAI,SAAS,QAAQ,SAAS,OAC5B,QAAO;EAAE,MAAM;EAAM,SAAS;EAAiB;AAIjD,KACE,gBAAgB,YAChB,gBAAgB,mBAChB,gBAAgB,QAChB,gBAAgB,eAChB,gBAAgB,kBAChB,OAAO,SAAS,SAEhB,gBAAe;UACN,OAAO,SAAS,UAAU;AAEnC,iBAAe,KAAK,UAAU,KAAK;AACnC,oBAAkB;GAChB,gBAAgB;GAChB,GAAG;GACJ;OAGD,gBAAe,OAAO,KAAK;AAG7B,QAAO;EAAE,MAAM;EAAc,SAAS;EAAiB"}
File without changes