@zap-studio/fetch 0.1.1 → 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,52 @@
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
+
44
+ ## 0.1.2
45
+
46
+ ### Patch Changes
47
+
48
+ - 69c2b21: Change `safeFetch` to `$fetch` syntax and make sure `safeFetch` can also be used for legacy
49
+
3
50
  ## 0.1.1
4
51
 
5
52
  ### 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,88 +98,136 @@ 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
- ### Using `safeFetch` directly
105
+ ### Using `$fetch` directly
106
+
107
+ For more control or when you don't need schema validation:
88
108
 
89
109
  ```typescript
90
- import { safeFetch } from "@zap-studio/fetch";
91
-
92
- const result = await safeFetch(
93
- "https://api.example.com/users/1",
94
- UserSchema,
95
- {
96
- method: "GET",
97
- headers: {
98
- "Authorization": "Bearer token",
99
- },
100
- }
101
- );
110
+ import { $fetch } from "@zap-studio/fetch";
111
+
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();
125
+ ```
126
+
127
+ ### Factory Pattern with `createFetch`
128
+
129
+ Create pre-configured fetch instances with base URLs and default headers. Useful for API clients:
130
+
131
+ ```typescript
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
+ });
102
156
  ```
103
157
 
104
- ### Custom response types
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:
105
172
 
106
173
  ```typescript
107
- // Get text response
108
- const text = await safeFetch(
109
- "/api/data",
110
- z.string(),
111
- { responseType: "text" }
112
- );
113
-
114
- // Get blob response
115
- const blob = await safeFetch(
116
- "/api/file",
117
- z.instanceof(Blob),
118
- { responseType: "blob" }
119
- );
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);
120
191
  ```
121
192
 
122
- ### Error handling
193
+ ### Error Handling
194
+
195
+ The package exports specialized error classes for granular error handling:
123
196
 
124
197
  ```typescript
125
- import { FetchError } from "@zap-studio/fetch/errors";
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)
140
- const user = await safeFetch(url, UserSchema, {
219
+ const user = await $fetch(url, UserSchema, {
141
220
  throwOnValidationError: true,
142
221
  });
143
222
 
144
223
  // Return validation result without throwing
145
- const result = await safeFetch(url, UserSchema, {
224
+ 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 };
@@ -0,0 +1,102 @@
1
+ import { n as ExtendedRequestInit, t as CreateFetchOptions } from "./types-C0Uhh2KM.mjs";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
+
4
+ //#region src/index.d.ts
5
+
6
+ /**
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
17
+ *
18
+ * @example
19
+ * import { z } from "zod";
20
+ * import { $fetch } from "@zap-studio/fetch";
21
+ *
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);
27
+ *
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);
40
+ * }
41
+ */
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>;
44
+ /**
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.
49
+ *
50
+ * @example
51
+ * import { z } from "zod";
52
+ * import { api } from "@zap-studio/fetch";
53
+ *
54
+ * const PostSchema = z.object({
55
+ * id: z.number(),
56
+ * title: z.string(),
57
+ * content: z.string(),
58
+ * });
59
+ *
60
+ * async function fetchPost(postId: number) {
61
+ * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
62
+ * return post; // post is typed as { id: number; title: string; content: string; }
63
+ * }
64
+ */
65
+ declare const api: {
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;
99
+ };
100
+ //#endregion
101
+ export { $fetch, api, createFetch };
102
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,172 @@
1
+ import { t as FetchError } from "./errors-DKxnbFHZ.mjs";
2
+ import { n as standardValidate, t as isStandardSchema } from "./validator-BGjWrM4d.mjs";
3
+
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
17
+ /**
18
+ * Merges two HeadersInit objects, with the second one taking precedence
19
+ *
20
+ * @example
21
+ * const baseHeaders = { "Authorization": "Bearer token", "Content-Type": "application/json" };
22
+ * const overrideHeaders = { "Content-Type": "application/xml", "X-Custom-Header": "value" };
23
+ *
24
+ * const merged = mergeHeaders(baseHeaders, overrideHeaders);
25
+ *
26
+ * // Resulting headers:
27
+ * // {
28
+ * // "Authorization": "Bearer token",
29
+ * // "Content-Type": "application/xml",
30
+ * // "X-Custom-Header": "value"
31
+ * // }
32
+ */
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 = {
58
+ ...rest,
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
86
+ });
87
+ }
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
+ }
95
+ /**
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.
100
+ *
101
+ * @example
102
+ * import { z } from "zod";
103
+ * import { api } from "@zap-studio/fetch";
104
+ *
105
+ * const PostSchema = z.object({
106
+ * id: z.number(),
107
+ * title: z.string(),
108
+ * content: z.string(),
109
+ * });
110
+ *
111
+ * async function fetchPost(postId: number) {
112
+ * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
113
+ * return post; // post is typed as { id: number; title: string; content: string; }
114
+ * }
115
+ */
116
+ const api = {
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")
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
+ }
169
+
170
+ //#endregion
171
+ export { $fetch, api, createFetch };
172
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
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.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -12,27 +12,32 @@
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
- "jsdom": "latest",
20
- "tsdown": "latest",
21
- "typescript": "latest",
22
- "vitest": "latest",
23
- "@zap-studio/tsdown-config": "0.0.0",
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",
24
27
  "@zap-studio/vitest-config": "0.0.0",
28
+ "@zap-studio/tsdown-config": "0.0.0",
25
29
  "@zap-studio/typescript-config": "0.0.0"
26
30
  },
27
31
  "exports": {
28
- ".": "./dist/index.js",
29
- "./errors": "./dist/errors/index.js",
30
- "./types": "./dist/types/index.js",
32
+ ".": "./dist/index.mjs",
33
+ "./errors": "./dist/errors.mjs",
34
+ "./types": "./dist/types.mjs",
35
+ "./validator": "./dist/validator.mjs",
31
36
  "./package.json": "./package.json"
32
37
  },
33
- "main": "./dist/index.js",
34
- "module": "./dist/index.js",
35
- "types": "./dist/index.d.ts",
38
+ "main": "./dist/index.mjs",
39
+ "module": "./dist/index.mjs",
40
+ "types": "./dist/index.d.mts",
36
41
  "scripts": {
37
42
  "build": "tsdown --config tsdown.config.ts",
38
43
  "check-types": "tsc --noEmit",
@@ -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.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/errors/index.ts"],"sourcesContent":[],"mappings":";cAAa,UAAA,SAAmB,KAAA;EAAnB,MAAA,EAAA,MAAW;EAKL,UAAA,EAAA,MAAA;EAAA,QAAA,EAAA,QAAA;EALa,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAKb,QALa"}
@@ -1,3 +0,0 @@
1
- import { t as FetchError } from "../errors-DrfSty0J.js";
2
-
3
- export { FetchError };
@@ -1,14 +0,0 @@
1
- //#region src/errors/index.ts
2
- var FetchError = class extends Error {
3
- constructor(message, status, statusText, response) {
4
- super(message);
5
- this.status = status;
6
- this.statusText = statusText;
7
- this.response = response;
8
- this.name = "FetchError";
9
- }
10
- };
11
-
12
- //#endregion
13
- export { FetchError as t };
14
- //# sourceMappingURL=errors-DrfSty0J.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-DrfSty0J.js","names":["status: number","statusText: string","response: Response"],"sources":["../src/errors/index.ts"],"sourcesContent":["export class FetchError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic response: Response,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"FetchError\";\n\t}\n}\n"],"mappings":";AAAA,IAAa,aAAb,cAAgC,MAAM;CACrC,YACC,SACA,AAAOA,QACP,AAAOC,YACP,AAAOC,UACN;AACD,QAAM,QAAQ;EAJP;EACA;EACA;AAGP,OAAK,OAAO"}
@@ -1,9 +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 = "json" | "text" | "blob" | "arrayBuffer" | "formData";
7
- //#endregion
8
- export { ResponseType as n, FetchConfig as t };
9
- //# sourceMappingURL=index-BkzLFXkF.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-BkzLFXkF.d.ts","names":[],"sources":["../src/types/index.ts"],"sourcesContent":[],"mappings":";KAAY,qBAAqB,KAAK;EAA1B,IAAA,CAAA,EACJ,KADI;EAA0B,sBAAA,CAAA,EAAA,OAAA;CAAL;AACzB,KAII,YAAA,GAJJ,MAAA,GAAA,MAAA,GAAA,MAAA,GAAA,aAAA,GAAA,UAAA"}
package/dist/index.d.ts DELETED
@@ -1,63 +0,0 @@
1
- import { n as ResponseType, t as FetchConfig } from "./index-BkzLFXkF.js";
2
- import { z } from "zod";
3
-
4
- //#region src/index.d.ts
5
-
6
- /**
7
- * Type-safe fetch wrapper with Zod validation
8
- *
9
- * @example
10
- * import { z } from "zod";
11
- * import { safeFetch } from "@zap-studio/fetch";
12
- *
13
- * const UserSchema = z.object({
14
- * id: z.number(),
15
- * name: z.string(),
16
- * email: z.string().email(),
17
- * });
18
- *
19
- * async function getUser(userId: number) {
20
- * const user = await safeFetch(
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; }
26
- * }
27
- */
28
- declare function safeFetch<TResponse$1, TBody$1 = unknown>(url: string, responseSchema: z.ZodType<TResponse$1>, config?: FetchConfig<TBody$1> & {
29
- throwOnValidationError?: true;
30
- responseType?: ResponseType;
31
- }): Promise<TResponse$1>;
32
- declare function safeFetch<TResponse$1, TBody$1 = unknown>(url: string, responseSchema: z.ZodType<TResponse$1>, config?: FetchConfig<TBody$1> & {
33
- throwOnValidationError: false;
34
- responseType?: ResponseType;
35
- }): Promise<ReturnType<typeof responseSchema.safeParse>>;
36
- /**
37
- * Convenience methods for common HTTP verbs
38
- *
39
- * @example
40
- * import { z } from "zod";
41
- * import { api } from "@zap-studio/fetch";
42
- *
43
- * const PostSchema = z.object({
44
- * id: z.number(),
45
- * title: z.string(),
46
- * content: z.string(),
47
- * });
48
- *
49
- * async function fetchPost(postId: number) {
50
- * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
51
- * return post; // post is typed as { id: number; title: string; content: string; }
52
- * }
53
- */
54
- declare const api: {
55
- get: <TResponse>(url: string, schema: z.ZodType<TResponse>, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
56
- post: <TResponse, TBody = unknown>(url: string, schema: z.ZodType<TResponse>, body?: TBody, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
57
- put: <TResponse, TBody = unknown>(url: string, schema: z.ZodType<TResponse>, body?: TBody, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
58
- patch: <TResponse, TBody = unknown>(url: string, schema: z.ZodType<TResponse>, body?: TBody, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
59
- delete: <TResponse>(url: string, schema: z.ZodType<TResponse>, config?: Omit<RequestInit, "method" | "body">) => Promise<TResponse>;
60
- };
61
- //#endregion
62
- export { api, safeFetch };
63
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AA0BA;;;;;;;;;AASA;;;;;;;;;;AAoHA;AAGoB,iBAhIE,SAgIF,CAAA,WAAA,EAAA,UAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,cAAA,EA9HH,CAAA,CAAE,OA8HC,CA9HO,WA8HP,CAAA,EAAA,MACT,CADS,EA7HV,WA6HU,CA7HE,OA6HF,CAAA,GAAA;EAAR,sBAAA,CAAA,EAAA,IAAA;EACI,YAAA,CAAA,EA5HC,YA4HD;CAAL,CAAA,EA1HR,OA0HQ,CA1HA,WA0HA,CAAA;AAAoC,iBAxHzB,SAwHyB,CAAA,WAAA,EAAA,UAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,cAAA,EAtH9B,CAAA,CAAE,OAsH4B,CAtHpB,WAsHoB,CAAA,EAAA,MAKrC,CALqC,EArHrC,WAqHqC,CArHzB,OAqHyB,CAAA,GAAA;EAAA,sBAAA,EAAA,KAAA;EAK3B,YAAA,CAAA,EAxHH,YAwHG;CAAV,CAAA,EAtHP,OAsHS,CAtHD,UAsHC,CAAA,OAtHiB,cAAA,CAAe,SAsHhC,CAAA,CAAA;;;;;;;;;;;;;;;;;;;AAgBmC,cAzBlC,GAyBkC,EAAA;EAK3B,GAAA,EAAA,CAAA,SAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EA3BV,CAAA,CAAE,OA2BQ,CA3BA,SA2BA,CAAA,EAAA,MAAA,CAAA,EA1BT,IA0BS,CA1BJ,WA0BI,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GA1B2B,OA0B3B,CA1B2B,SA0B3B,CAAA;EAAR,IAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EArBF,CAAA,CAAE,OAqBA,CArBQ,SAqBR,CAAA,EAAA,IAAA,CAAA,EApBH,KAoBG,EAAA,MAAA,CAAA,EAnBD,IAmBC,CAnBI,WAmBJ,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAnBmC,OAmBnC,CAnBmC,SAmBnC,CAAA;EACI,GAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EAfN,CAAA,CAAE,OAeI,CAfI,SAeJ,CAAA,EAAA,IAAA,CAAA,EAdP,KAcO,EAAA,MAAA,CAAA,EAbL,IAaK,CAbA,WAaA,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAb+B,OAa/B,CAb+B,SAa/B,CAAA;EAAL,KAAA,EAAA,CAAA,SAAA,EAAA,QAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EARD,CAAA,CAAE,OAQD,CARS,SAQT,CAAA,EAAA,IAAA,CAAA,EAPF,KAOE,EAAA,MAAA,CAAA,EANA,IAMA,CANK,WAML,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GANoC,OAMpC,CANoC,SAMpC,CAAA;EAAoC,MAAA,EAAA,CAAA,SAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EADrC,CAAA,CAAE,OACmC,CAD3B,SAC2B,CAAA,EAAA,MAAA,CAAA,EAApC,IAAoC,CAA/B,WAA+B,EAAA,QAAA,GAAA,MAAA,CAAA,EAAA,GAAA,OAAA,CAAA,SAAA,CAAA;CAAA"}
package/dist/index.js DELETED
@@ -1,79 +0,0 @@
1
- import { t as FetchError } from "./errors-DrfSty0J.js";
2
-
3
- //#region src/index.ts
4
- async function safeFetch(url, responseSchema, config) {
5
- const { body, headers, throwOnValidationError = true, responseType = "json",...rest } = config || {};
6
- const fetchConfig = {
7
- ...rest,
8
- headers: { ...headers }
9
- };
10
- if (body !== void 0) {
11
- const shouldSetContentType = !headers || typeof headers === "object" && !Array.isArray(headers) && !(headers instanceof Headers) && !("Content-Type" in headers);
12
- if (body instanceof FormData) fetchConfig.body = body;
13
- else if (typeof body === "string") {
14
- fetchConfig.body = body;
15
- if (shouldSetContentType) fetchConfig.headers["Content-Type"] = "text/plain";
16
- } else {
17
- fetchConfig.body = JSON.stringify(body);
18
- if (shouldSetContentType) fetchConfig.headers["Content-Type"] = "application/json";
19
- }
20
- }
21
- const response = await fetch(url, fetchConfig);
22
- if (!response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response.status, response.statusText, response);
23
- let data;
24
- if (responseType === "json") if (response.headers.get("content-type")?.includes("application/json")) data = await response.json();
25
- else throw new FetchError("Expected JSON response but received different content type", response.status, response.statusText, response);
26
- else if (responseType === "text") data = await response.text();
27
- else if (responseType === "blob") data = await response.blob();
28
- else if (responseType === "arrayBuffer") data = await response.arrayBuffer();
29
- else if (responseType === "formData") data = await response.formData();
30
- if (throwOnValidationError) return responseSchema.parse(data);
31
- return responseSchema.safeParse(data);
32
- }
33
- /**
34
- * Convenience methods for common HTTP verbs
35
- *
36
- * @example
37
- * import { z } from "zod";
38
- * import { api } from "@zap-studio/fetch";
39
- *
40
- * const PostSchema = z.object({
41
- * id: z.number(),
42
- * title: z.string(),
43
- * content: z.string(),
44
- * });
45
- *
46
- * async function fetchPost(postId: number) {
47
- * const post = await api.get(`https://api.example.com/posts/${postId}`, PostSchema);
48
- * return post; // post is typed as { id: number; title: string; content: string; }
49
- * }
50
- */
51
- const api = {
52
- get: (url, schema, config) => safeFetch(url, schema, {
53
- ...config,
54
- method: "GET"
55
- }),
56
- post: (url, schema, body, config) => safeFetch(url, schema, {
57
- ...config,
58
- method: "POST",
59
- body
60
- }),
61
- put: (url, schema, body, config) => safeFetch(url, schema, {
62
- ...config,
63
- method: "PUT",
64
- body
65
- }),
66
- patch: (url, schema, body, config) => safeFetch(url, schema, {
67
- ...config,
68
- method: "PATCH",
69
- body
70
- }),
71
- delete: (url, schema, config) => safeFetch(url, schema, {
72
- ...config,
73
- method: "DELETE"
74
- })
75
- };
76
-
77
- //#endregion
78
- export { api, safeFetch };
79
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["fetchConfig: RequestInit","data: unknown"],"sources":["../src/index.ts"],"sourcesContent":["import type { z } from \"zod\";\nimport { FetchError } from \"./errors\";\nimport type { FetchConfig, ResponseType } from \"./types\";\n\n/**\n * Type-safe fetch wrapper with Zod validation\n *\n * @example\n * import { z } from \"zod\";\n * import { safeFetch } 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 safeFetch(\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 safeFetch<TResponse, TBody = unknown>(\n\turl: string,\n\tresponseSchema: z.ZodType<TResponse>,\n\tconfig?: FetchConfig<TBody> & {\n\t\tthrowOnValidationError?: true;\n\t\tresponseType?: ResponseType;\n\t},\n): Promise<TResponse>;\n\nexport async function safeFetch<TResponse, TBody = unknown>(\n\turl: string,\n\tresponseSchema: z.ZodType<TResponse>,\n\tconfig?: FetchConfig<TBody> & {\n\t\tthrowOnValidationError: false;\n\t\tresponseType?: ResponseType;\n\t},\n): Promise<ReturnType<typeof responseSchema.safeParse>>;\n\nexport async function safeFetch<TResponse, TBody = unknown>(\n\turl: string,\n\tresponseSchema: z.ZodType<TResponse>,\n\tconfig?: FetchConfig<TBody> & { responseType?: ResponseType },\n): Promise<TResponse | ReturnType<typeof responseSchema.safeParse>> {\n\tconst {\n\t\tbody,\n\t\theaders,\n\t\tthrowOnValidationError = true,\n\t\tresponseType = \"json\",\n\t\t...rest\n\t} = config || {};\n\n\tconst fetchConfig: RequestInit = {\n\t\t...rest,\n\t\theaders: {\n\t\t\t...(headers as Record<string, string>),\n\t\t},\n\t};\n\n\t// Only set Content-Type if body exists and it's not FormData\n\tif (body !== undefined) {\n\t\tconst shouldSetContentType =\n\t\t\t!headers ||\n\t\t\t(typeof headers === \"object\" &&\n\t\t\t\t!Array.isArray(headers) &&\n\t\t\t\t!(headers instanceof Headers) &&\n\t\t\t\t!(\"Content-Type\" in headers));\n\n\t\tif (body instanceof FormData) {\n\t\t\tfetchConfig.body = body;\n\t\t} else if (typeof body === \"string\") {\n\t\t\tfetchConfig.body = body;\n\t\t\tif (shouldSetContentType) {\n\t\t\t\t(fetchConfig.headers as Record<string, string>)[\"Content-Type\"] =\n\t\t\t\t\t\"text/plain\";\n\t\t\t}\n\t\t} else {\n\t\t\tfetchConfig.body = JSON.stringify(body);\n\t\t\tif (shouldSetContentType) {\n\t\t\t\t(fetchConfig.headers as Record<string, string>)[\"Content-Type\"] =\n\t\t\t\t\t\"application/json\";\n\t\t\t}\n\t\t}\n\t}\n\n\tconst response = await fetch(url, fetchConfig);\n\n\tif (!response.ok) {\n\t\tthrow new FetchError(\n\t\t\t`HTTP ${response.status}: ${response.statusText}`,\n\t\t\tresponse.status,\n\t\t\tresponse.statusText,\n\t\t\tresponse,\n\t\t);\n\t}\n\n\t// Parse response based on responseType\n\tlet data: unknown;\n\n\tif (responseType === \"json\") {\n\t\tconst contentType = response.headers.get(\"content-type\");\n\t\tif (contentType?.includes(\"application/json\")) {\n\t\t\tdata = await response.json();\n\t\t} else {\n\t\t\tthrow new FetchError(\n\t\t\t\t\"Expected JSON response but received different content type\",\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\tresponse,\n\t\t\t);\n\t\t}\n\t} else if (responseType === \"text\") {\n\t\tdata = await response.text();\n\t} else if (responseType === \"blob\") {\n\t\tdata = await response.blob();\n\t} else if (responseType === \"arrayBuffer\") {\n\t\tdata = await response.arrayBuffer();\n\t} else if (responseType === \"formData\") {\n\t\tdata = await response.formData();\n\t}\n\n\tif (throwOnValidationError) {\n\t\treturn responseSchema.parse(data);\n\t}\n\n\treturn responseSchema.safeParse(data);\n}\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\tget: <TResponse>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"GET\" }),\n\n\tpost: <TResponse, TBody = unknown>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tbody?: TBody,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"POST\", body }),\n\n\tput: <TResponse, TBody = unknown>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tbody?: TBody,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"PUT\", body }),\n\n\tpatch: <TResponse, TBody = unknown>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tbody?: TBody,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"PATCH\", body }),\n\n\tdelete: <TResponse>(\n\t\turl: string,\n\t\tschema: z.ZodType<TResponse>,\n\t\tconfig?: Omit<RequestInit, \"method\" | \"body\">,\n\t) => safeFetch(url, schema, { ...config, method: \"DELETE\" }),\n};\n"],"mappings":";;;AA4CA,eAAsB,UACrB,KACA,gBACA,QACmE;CACnE,MAAM,EACL,MACA,SACA,yBAAyB,MACzB,eAAe,OACf,GAAG,SACA,UAAU,EAAE;CAEhB,MAAMA,cAA2B;EAChC,GAAG;EACH,SAAS,EACR,GAAI,SACJ;EACD;AAGD,KAAI,SAAS,QAAW;EACvB,MAAM,uBACL,CAAC,WACA,OAAO,YAAY,YACnB,CAAC,MAAM,QAAQ,QAAQ,IACvB,EAAE,mBAAmB,YACrB,EAAE,kBAAkB;AAEtB,MAAI,gBAAgB,SACnB,aAAY,OAAO;WACT,OAAO,SAAS,UAAU;AACpC,eAAY,OAAO;AACnB,OAAI,qBACH,CAAC,YAAY,QAAmC,kBAC/C;SAEI;AACN,eAAY,OAAO,KAAK,UAAU,KAAK;AACvC,OAAI,qBACH,CAAC,YAAY,QAAmC,kBAC/C;;;CAKJ,MAAM,WAAW,MAAM,MAAM,KAAK,YAAY;AAE9C,KAAI,CAAC,SAAS,GACb,OAAM,IAAI,WACT,QAAQ,SAAS,OAAO,IAAI,SAAS,cACrC,SAAS,QACT,SAAS,YACT,SACA;CAIF,IAAIC;AAEJ,KAAI,iBAAiB,OAEpB,KADoB,SAAS,QAAQ,IAAI,eAAe,EACvC,SAAS,mBAAmB,CAC5C,QAAO,MAAM,SAAS,MAAM;KAE5B,OAAM,IAAI,WACT,8DACA,SAAS,QACT,SAAS,YACT,SACA;UAEQ,iBAAiB,OAC3B,QAAO,MAAM,SAAS,MAAM;UAClB,iBAAiB,OAC3B,QAAO,MAAM,SAAS,MAAM;UAClB,iBAAiB,cAC3B,QAAO,MAAM,SAAS,aAAa;UACzB,iBAAiB,WAC3B,QAAO,MAAM,SAAS,UAAU;AAGjC,KAAI,uBACH,QAAO,eAAe,MAAM,KAAK;AAGlC,QAAO,eAAe,UAAU,KAAK;;;;;;;;;;;;;;;;;;;;AAqBtC,MAAa,MAAM;CAClB,MACC,KACA,QACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAO,CAAC;CAEzD,OACC,KACA,QACA,MACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAQ;EAAM,CAAC;CAEhE,MACC,KACA,QACA,MACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAO;EAAM,CAAC;CAE/D,QACC,KACA,QACA,MACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAS;EAAM,CAAC;CAEjE,SACC,KACA,QACA,WACI,UAAU,KAAK,QAAQ;EAAE,GAAG;EAAQ,QAAQ;EAAU,CAAC;CAC5D"}
@@ -1,2 +0,0 @@
1
- import { n as ResponseType, t as FetchConfig } from "../index-BkzLFXkF.js";
2
- export { FetchConfig, ResponseType };
File without changes