@zap-studio/fetch 0.2.1 → 0.3.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,17 @@
1
1
  # @zap-studio/fetch
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 659621c: Add `searchParams` option to `createFetch` to allow factory-level default query/search parameters. Per-request `searchParams` continue to override factory defaults.
8
+
9
+ ## 0.2.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 5c3abbf: Prepare JSR publish and isolatedDeclarations support with new explicit types for `$Fetch` and `ApiMethods`
14
+
3
15
  ## 0.2.1
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -55,179 +55,3 @@ const user = await api.get("https://api.example.com/users/1", UserSchema);
55
55
  // user is fully typed and validated! ✨
56
56
  console.log(user.name); // TypeScript knows this is a string
57
57
  ```
58
-
59
- ## API
60
-
61
- ### `api.get(url, schema, options?)`
62
-
63
- ```typescript
64
- const user = await api.get("/api/users/1", UserSchema);
65
- ```
66
-
67
- ### `api.post(url, schema, options?)`
68
-
69
- ```typescript
70
- const newUser = await api.post("/api/users", UserSchema, {
71
- body: {
72
- name: "John Doe",
73
- email: "john@example.com",
74
- },
75
- });
76
- // Automatically stringifies body and sets Content-Type: application/json
77
- ```
78
-
79
- ### `api.put(url, schema, options?)`
80
-
81
- ```typescript
82
- const updated = await api.put("/api/users/1", UserSchema, {
83
- body: { name: "Jane Doe" },
84
- });
85
- ```
86
-
87
- ### `api.patch(url, schema, options?)`
88
-
89
- ```typescript
90
- const patched = await api.patch("/api/users/1", UserSchema, {
91
- body: { email: "newemail@example.com" },
92
- });
93
- ```
94
-
95
- ### `api.delete(url, schema, options?)`
96
-
97
- ```typescript
98
- const deleted = await api.delete("/api/users/1", UserSchema);
99
- ```
100
-
101
- > **Note:** The `api.*` methods always require a schema for validation. For raw responses without validation, use `$fetch` directly.
102
-
103
- ## Advanced Usage
104
-
105
- ### Using `$fetch` directly
106
-
107
- For more control or when you don't need schema validation:
108
-
109
- ```typescript
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
- });
156
- ```
157
-
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:
172
-
173
- ```typescript
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";
200
-
201
- try {
202
- const user = await api.get("/api/users/1", UserSchema);
203
- } catch (error) {
204
- if (error instanceof FetchError) {
205
- console.error(`HTTP ${error.status}: ${error.response.statusText}`);
206
- }
207
- if (error instanceof ValidationError) {
208
- console.error("Validation failed:", error.issues);
209
- }
210
- }
211
- ```
212
-
213
- ### Flexible Validation
214
-
215
- You can choose whether validation errors should throw exceptions:
216
-
217
- ```typescript
218
- // Throw on validation error (default)
219
- const user = await $fetch(url, UserSchema, {
220
- throwOnValidationError: true,
221
- });
222
-
223
- // Return validation result without throwing
224
- const result = await $fetch(url, UserSchema, {
225
- throwOnValidationError: false,
226
- });
227
-
228
- if (result.issues) {
229
- console.error("Validation failed:", result.issues);
230
- } else {
231
- console.log("Success:", result.value);
232
- }
233
- ```
package/dist/errors.d.mts CHANGED
@@ -3,16 +3,16 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
  //#region src/errors.d.ts
4
4
 
5
5
  /**
6
- * Error thrown for HTTP errors (non-2xx responses)
7
- */
6
+ * Error thrown for HTTP errors (non-2xx responses)
7
+ */
8
8
  declare class FetchError extends Error {
9
9
  status: Response["status"];
10
10
  response: Response;
11
11
  constructor(message: string, response: Response);
12
12
  }
13
13
  /**
14
- * Error thrown for validation errors
15
- */
14
+ * Error thrown for validation errors
15
+ */
16
16
  declare class ValidationError extends Error {
17
17
  issues: StandardSchemaV1.Issue[];
18
18
  constructor(issues: StandardSchemaV1.Issue[]);
@@ -1 +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"}
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;EAAA,MAAA,EAC3B,gBAAA,CAAiB,KADU,EAAA;sBAGf,gBAAA,CAAiB"}
package/dist/index.d.mts CHANGED
@@ -1,101 +1,95 @@
1
- import { n as ExtendedRequestInit, t as CreateFetchOptions } from "./types-C0Uhh2KM.mjs";
1
+ import { i as ExtendedRequestInit, n as ApiMethods, r as CreateFetchOptions, t as $Fetch } from "./types-WVLRjya1.mjs";
2
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 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
- */
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
42
  declare function $fetch<TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: ExtendedRequestInit): Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
43
43
  declare function $fetch(resource: string, options?: ExtendedRequestInit): Promise<Response>;
44
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
- };
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: ApiMethods;
72
66
  /**
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
- */
67
+ * Creates a custom fetch instance with pre-configured defaults.
68
+ *
69
+ * Use this factory to create API clients with a base URL, default headers,
70
+ * and other shared configuration. Each instance is independent.
71
+ *
72
+ * @example
73
+ * import { z } from "zod";
74
+ * import { createFetch } from "@zap-studio/fetch";
75
+ *
76
+ * // Create a configured instance
77
+ * const { $fetch, api } = createFetch({
78
+ * baseURL: "https://api.example.com",
79
+ * headers: { "Authorization": "Bearer token" },
80
+ * });
81
+ *
82
+ * const UserSchema = z.object({ id: z.number(), name: z.string() });
83
+ *
84
+ * // Now use relative paths - baseURL is prepended automatically
85
+ * const user = await api.get("/users/1", UserSchema);
86
+ *
87
+ * // Or use $fetch directly
88
+ * const response = await $fetch("/users", UserSchema, { method: "POST", body: { name: "John" } });
89
+ */
96
90
  declare function createFetch(factoryOptions?: CreateFetchOptions): {
97
- $fetch: typeof $fetch;
98
- api: typeof api;
91
+ $fetch: $Fetch;
92
+ api: ApiMethods;
99
93
  };
100
94
  //#endregion
101
95
  export { $fetch, api, createFetch };
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.mts","names":["api: ApiMethods"],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AAgDA;;;;;;;;;;;AASA;;;;;AAsCA;AAgCA;;;;;;;;;;;;;;;;;iBA/EsB,uBAAuB,4CAEnC,mBACE,sBACT,QACC,gBAAA,CAAiB,YAAY,WAC7B,gBAAA,CAAiB,OAAO,gBAAA,CAAiB,YAAY;iBAGnC,MAAA,6BAEV,sBACT,QAAQ;;;;;;;;;;;;;;;;;;;;;;cAmCEA,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCF,WAAA,kBAA4B;UAClC;OACH"}
package/dist/index.mjs CHANGED
@@ -63,10 +63,87 @@ function isAbsoluteURL(url) {
63
63
  return ABSOLUTE_URL_PATTERN.test(url);
64
64
  }
65
65
  /**
66
+ * Normalizes search parameters into a URLSearchParams object.
67
+ */
68
+ function normalizeSearchParams(input) {
69
+ if (input === void 0 || input === null) return new URLSearchParams();
70
+ if (input instanceof URLSearchParams) return new URLSearchParams(input);
71
+ if (typeof input === "string") return new URLSearchParams(input);
72
+ if (Array.isArray(input)) return new URLSearchParams(input);
73
+ const params = new URLSearchParams();
74
+ for (const [k, v] of Object.entries(input)) params.set(k, v);
75
+ return params;
76
+ }
77
+ /**
78
+ * Parses a URL string into its path, query, and hash components.
79
+ * Handles both absolute URLs (using URL constructor) and relative paths.
80
+ */
81
+ function parseUrlComponents(url) {
82
+ if (isAbsoluteURL(url)) try {
83
+ const parsed = new URL(url);
84
+ const pathOnly = `${parsed.origin}${parsed.pathname}`;
85
+ const existingQuery = parsed.search.startsWith("?") ? parsed.search.slice(1) : parsed.search;
86
+ let hash$1 = parsed.hash;
87
+ if (hash$1 === "" && url.endsWith("#")) hash$1 = "#";
88
+ return {
89
+ pathOnly,
90
+ existingQuery,
91
+ hash: hash$1
92
+ };
93
+ } catch {}
94
+ let hash = "";
95
+ let urlWithoutHash = url;
96
+ const hashIndex = url.indexOf("#");
97
+ if (hashIndex !== -1) {
98
+ hash = url.slice(hashIndex);
99
+ urlWithoutHash = url.slice(0, hashIndex);
100
+ }
101
+ const queryIndex = urlWithoutHash.indexOf("?");
102
+ if (queryIndex === -1) return {
103
+ pathOnly: urlWithoutHash,
104
+ existingQuery: "",
105
+ hash
106
+ };
107
+ return {
108
+ pathOnly: urlWithoutHash.slice(0, queryIndex),
109
+ existingQuery: urlWithoutHash.slice(queryIndex + 1),
110
+ hash
111
+ };
112
+ }
113
+ /**
114
+ * Builds a URL with merged search parameters from factory defaults,
115
+ * request options, and existing query parameters.
116
+ *
117
+ * This function takes a base URL, factory search parameters,
118
+ * and request search parameters, and combines them into a single URL.
119
+ *
120
+ * Priority (highest to lowest):
121
+ * 1. Request search parameters (highest priority, overwrites all others)
122
+ * 2. Existing query parameters from the URL (overwrites factory defaults)
123
+ * 3. Factory search parameters (lowest priority, overwritten by all others)
124
+ *
125
+ * If no search parameters are provided, the URL will not have a query string.
126
+ * Any trailing hash/fragment is preserved.
127
+ */
128
+ function buildUrlWithMergedSearchParams(url, factorySearch, requestSearch) {
129
+ const { pathOnly, existingQuery, hash } = parseUrlComponents(url);
130
+ if (!(factorySearch ?? requestSearch ?? existingQuery)) return url;
131
+ const mergedParams = new URLSearchParams();
132
+ const resourceParams = new URLSearchParams(existingQuery);
133
+ const factoryParams = normalizeSearchParams(factorySearch);
134
+ const reqParams = normalizeSearchParams(requestSearch);
135
+ for (const [k, v] of factoryParams.entries()) mergedParams.set(k, v);
136
+ for (const [k, v] of resourceParams.entries()) mergedParams.set(k, v);
137
+ for (const [k, v] of reqParams.entries()) mergedParams.set(k, v);
138
+ const queryString = mergedParams.toString();
139
+ if (queryString) return `${pathOnly}?${queryString}${hash}`;
140
+ return `${pathOnly}${hash}`;
141
+ }
142
+ /**
66
143
  * Internal fetch implementation used by both $fetch and createFetch
67
144
  */
68
145
  async function fetchInternal(resource, schema, options, defaults) {
69
- const { throwOnValidationError = defaults.throwOnValidationError, throwOnFetchError = defaults.throwOnFetchError, headers: requestHeaders, ...rest } = options || {};
146
+ const { throwOnValidationError = defaults.throwOnValidationError, throwOnFetchError = defaults.throwOnFetchError, headers: requestHeaders, searchParams: requestSearchParams, ...rest } = options || {};
70
147
  const mergedHeaders = mergeHeaders(defaults.headers, requestHeaders);
71
148
  const init = {
72
149
  ...rest,
@@ -85,6 +162,7 @@ async function fetchInternal(resource, schema, options, defaults) {
85
162
  const path = trimLeadingSlashes(resource);
86
163
  url = base ? `${base}/${path}` : resource;
87
164
  }
165
+ url = buildUrlWithMergedSearchParams(url, defaults.searchParams, requestSearchParams);
88
166
  const response = await fetch(url, init);
89
167
  if (throwOnFetchError && !response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);
90
168
  if (schema) return standardValidate(schema, await response.json(), throwOnValidationError);
@@ -162,6 +240,7 @@ function createFetch(factoryOptions = {}) {
162
240
  const defaults = {
163
241
  baseURL: factoryOptions.baseURL ?? "",
164
242
  headers: factoryOptions.headers,
243
+ searchParams: factoryOptions.searchParams,
165
244
  throwOnFetchError: factoryOptions.throwOnFetchError ?? true,
166
245
  throwOnValidationError: factoryOptions.throwOnValidationError ?? true
167
246
  };
@@ -1 +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\n/**\n * Removes trailing slashes from a string\n */\nfunction trimTrailingSlashes(str: string): string {\n let end = str.length;\n while (end > 0 && str[end - 1] === \"/\") {\n end -= 1;\n }\n return str.slice(0, end);\n}\n\n/**\n * Removes leading slashes from a string\n */\nfunction trimLeadingSlashes(str: string): string {\n let start = 0;\n while (start < str.length && str[start] === \"/\") {\n start += 1;\n }\n return str.slice(start);\n}\n\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 = trimTrailingSlashes(defaults.baseURL);\n const path = trimLeadingSlashes(resource);\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":"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"}
1
+ {"version":3,"file":"index.mjs","names":["GLOBAL_DEFAULTS: FetchDefaults","hash","url: string","api: ApiMethods","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: FetchDefaults = {\n baseURL: \"\",\n headers: undefined,\n throwOnFetchError: true,\n throwOnValidationError: true,\n};\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { FetchError } from \"../errors\";\nimport type {\n $Fetch,\n ExtendedRequestInit,\n FetchDefaults,\n SearchParams,\n} 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\n/**\n * Removes trailing slashes from a string\n */\nfunction trimTrailingSlashes(str: string): string {\n let end = str.length;\n while (end > 0 && str[end - 1] === \"/\") {\n end -= 1;\n }\n return str.slice(0, end);\n}\n\n/**\n * Removes leading slashes from a string\n */\nfunction trimLeadingSlashes(str: string): string {\n let start = 0;\n while (start < str.length && str[start] === \"/\") {\n start += 1;\n }\n return str.slice(start);\n}\n\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 * Normalizes search parameters into a URLSearchParams object.\n */\nfunction normalizeSearchParams(input: SearchParams | undefined): URLSearchParams {\n if (input === undefined || input === null) {\n return new URLSearchParams();\n }\n\n if (input instanceof URLSearchParams) {\n return new URLSearchParams(input);\n }\n\n if (typeof input === \"string\") {\n return new URLSearchParams(input);\n }\n\n if (Array.isArray(input)) {\n return new URLSearchParams(input);\n }\n\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(input)) {\n params.set(k, v);\n }\n\n return params;\n}\n\n/**\n * Parses a URL string into its path, query, and hash components.\n * Handles both absolute URLs (using URL constructor) and relative paths.\n */\nfunction parseUrlComponents(url: string): {\n pathOnly: string;\n existingQuery: string;\n hash: string;\n} {\n // Try parsing as absolute URL first\n if (isAbsoluteURL(url)) {\n try {\n const parsed = new URL(url);\n const pathOnly = `${parsed.origin}${parsed.pathname}`;\n // parsed.search includes the leading \"?\", so strip it\n const existingQuery = parsed.search.startsWith(\"?\")\n ? parsed.search.slice(1)\n : parsed.search;\n // URL constructor normalizes away empty hash, if original URL had # at end, preserve it\n let hash = parsed.hash;\n if (hash === \"\" && url.endsWith(\"#\")) {\n hash = \"#\";\n }\n return { pathOnly, existingQuery, hash };\n } catch {\n // Fall through to manual parsing if URL constructor fails\n }\n }\n\n // Manual parsing for relative URLs or if URL constructor failed\n let hash = \"\";\n let urlWithoutHash = url;\n const hashIndex = url.indexOf(\"#\");\n if (hashIndex !== -1) {\n hash = url.slice(hashIndex);\n urlWithoutHash = url.slice(0, hashIndex);\n }\n\n // Find the first \"?\" to split path and query\n const queryIndex = urlWithoutHash.indexOf(\"?\");\n if (queryIndex === -1) {\n return { pathOnly: urlWithoutHash, existingQuery: \"\", hash };\n }\n\n const pathOnly = urlWithoutHash.slice(0, queryIndex);\n const existingQuery = urlWithoutHash.slice(queryIndex + 1);\n return { pathOnly, existingQuery, hash };\n}\n\n/**\n * Builds a URL with merged search parameters from factory defaults,\n * request options, and existing query parameters.\n *\n * This function takes a base URL, factory search parameters,\n * and request search parameters, and combines them into a single URL.\n *\n * Priority (highest to lowest):\n * 1. Request search parameters (highest priority, overwrites all others)\n * 2. Existing query parameters from the URL (overwrites factory defaults)\n * 3. Factory search parameters (lowest priority, overwritten by all others)\n *\n * If no search parameters are provided, the URL will not have a query string.\n * Any trailing hash/fragment is preserved.\n */\nfunction buildUrlWithMergedSearchParams(\n url: string,\n factorySearch: FetchDefaults[\"searchParams\"] | undefined,\n requestSearch: ExtendedRequestInit[\"searchParams\"] | undefined\n): string {\n const { pathOnly, existingQuery, hash } = parseUrlComponents(url);\n\n // Early return if no search params to merge\n const hasSearchParams = factorySearch ?? requestSearch ?? existingQuery;\n if (!hasSearchParams) {\n return url;\n }\n\n const mergedParams = new URLSearchParams();\n\n const resourceParams = new URLSearchParams(existingQuery);\n const factoryParams = normalizeSearchParams(factorySearch);\n const reqParams = normalizeSearchParams(requestSearch);\n\n for (const [k, v] of factoryParams.entries()) {\n mergedParams.set(k, v);\n }\n\n for (const [k, v] of resourceParams.entries()) {\n mergedParams.set(k, v);\n }\n\n for (const [k, v] of reqParams.entries()) {\n mergedParams.set(k, v);\n }\n\n const queryString = mergedParams.toString();\n if (queryString) {\n return `${pathOnly}?${queryString}${hash}`;\n }\n\n return `${pathOnly}${hash}`;\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 searchParams: requestSearchParams,\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 = trimTrailingSlashes(defaults.baseURL);\n const path = trimLeadingSlashes(resource);\n url = base ? `${base}/${path}` : resource;\n }\n\n // Merge query/search params\n url = buildUrlWithMergedSearchParams(\n url,\n defaults.searchParams,\n requestSearchParams\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 $Fetch>(\n fetchFn: TFetch,\n method: string\n): $Fetch {\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 $Fetch,\n ApiMethods,\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: ApiMethods = {\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: $Fetch;\n api: ApiMethods;\n} {\n const defaults: FetchDefaults = {\n baseURL: factoryOptions.baseURL ?? \"\",\n headers: factoryOptions.headers,\n searchParams: factoryOptions.searchParams,\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":"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"}
@@ -0,0 +1,117 @@
1
+ import { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ //#region src/types.d.ts
4
+
5
+ /**
6
+ * Type representing various formats for search parameters
7
+ * that can be used in requests.
8
+ * Can be a URLSearchParams object, a record of string pairs,
9
+ * a query string, or an array of tuples.
10
+ */
11
+ type SearchParams = URLSearchParams | Record<string, string> | string | [string, string][];
12
+ /**
13
+ * Extended RequestInit type to include custom fetch options
14
+ */
15
+ type ExtendedRequestInit = Omit<RequestInit, "body"> & {
16
+ /**
17
+ * Request body - can be a BodyInit value or an object that will be JSON-stringified
18
+ */
19
+ body?: BodyInit | Record<string, unknown>;
20
+ /**
21
+ * Per-request query/search params
22
+ * @default undefined
23
+ */
24
+ searchParams?: SearchParams;
25
+ /**
26
+ * Whether to throw a FetchError on HTTP errors (non-2xx responses)
27
+ * @default true
28
+ */
29
+ throwOnFetchError?: boolean;
30
+ /**
31
+ * Whether to throw a ValidationError on validation errors
32
+ * @default true
33
+ */
34
+ throwOnValidationError?: boolean;
35
+ };
36
+ /**
37
+ * Internal defaults used by fetchInternal
38
+ */
39
+ type FetchDefaults = {
40
+ /**
41
+ * Base URL to prepend to all requests
42
+ * @default ""
43
+ */
44
+ baseURL: string;
45
+ /**
46
+ * Default headers to include in all requests (can be overridden per request)
47
+ * @default undefined
48
+ */
49
+ headers?: HeadersInit;
50
+ /**
51
+ * Default query/search params applied to every request (can be overridden per request)
52
+ * @default undefined
53
+ */
54
+ searchParams?: SearchParams;
55
+ /**
56
+ * Whether to throw a `FetchError` on HTTP errors (non-2xx responses)
57
+ * @default true
58
+ */
59
+ throwOnFetchError: boolean;
60
+ /**
61
+ * Whether to throw a `ValidationError` on validation errors
62
+ * @default true
63
+ */
64
+ throwOnValidationError: boolean;
65
+ };
66
+ /**
67
+ * Options for creating a custom fetch instance with `createFetch`
68
+ */
69
+ type CreateFetchOptions = Partial<FetchDefaults>;
70
+ /**
71
+ * Type-safe fetch function with Standard Schema validation support
72
+ */
73
+ type $Fetch = {
74
+ /**
75
+ * Fetch with schema validation
76
+ * @param resource - URL or path to fetch
77
+ * @param schema - Standard Schema for response validation
78
+ * @param options - Extended request options
79
+ * @returns Validated data or Standard Schema Result based on throwOnValidationError option
80
+ */
81
+ <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: ExtendedRequestInit): Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
82
+ /**
83
+ * Fetch without schema validation
84
+ * @param resource - URL or path to fetch
85
+ * @param options - Extended request options
86
+ * @returns Raw Response object
87
+ */
88
+ (resource: string, options?: ExtendedRequestInit): Promise<Response>;
89
+ };
90
+ /**
91
+ * API HTTP method-specific fetch functions
92
+ */
93
+ type ApiMethods = {
94
+ /**
95
+ * GET method fetch function
96
+ */
97
+ get: $Fetch;
98
+ /**
99
+ * POST method fetch function
100
+ */
101
+ post: $Fetch;
102
+ /**
103
+ * PUT method fetch function
104
+ */
105
+ put: $Fetch;
106
+ /**
107
+ * DELETE method fetch function
108
+ */
109
+ delete: $Fetch;
110
+ /**
111
+ * PATCH method fetch function
112
+ */
113
+ patch: $Fetch;
114
+ };
115
+ //#endregion
116
+ export { FetchDefaults as a, ExtendedRequestInit as i, ApiMethods as n, SearchParams as o, CreateFetchOptions as r, $Fetch as t };
117
+ //# sourceMappingURL=types-WVLRjya1.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-WVLRjya1.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;AAQA;AASA;;;AAIS,KAbG,YAAA,GACR,eAYK,GAXL,MAWK,CAAA,MAAA,EAAA,MAAA,CAAA,GAAA,MAAA,GAAA,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA;;;;AAqBG,KAzBA,mBAAA,GAAsB,IAmCtB,CAnC2B,WAwCtB,EAAA,MAAA,CAAA,GAAA;EAgBjB;AAKA;;EAUY,IAAA,CAAA,EAnEH,QAmEG,GAnEQ,MAmER,CAAA,MAAA,EAAA,OAAA,CAAA;EACE;;;;EAGgB,YAAA,CAAA,EAlEb,YAkE8B;EAAzC;;;;EAS+C,iBAAA,CAAA,EAAA,OAAA;EAAA;AAMrD;;;EAYO,sBAAA,CAAA,EAAA,OAAA;CAIG;;;;KAjFE,aAAA;;;;;;;;;;YAUA;;;;;iBAKK;;;;;;;;;;;;;;;KAgBL,kBAAA,GAAqB,QAAQ;;;;KAK7B,MAAA;;;;;;;;mBAQO,4CAEP,mBACE,sBACT,QACC,gBAAA,CAAiB,YAAY,WAC7B,gBAAA,CAAiB,OAAO,gBAAA,CAAiB,YAAY;;;;;;;+BAS5B,sBAAsB,QAAQ;;;;;KAMjD,UAAA;;;;OAIL;;;;QAIC;;;;OAID;;;;UAIG;;;;SAID"}
package/dist/types.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as ExtendedRequestInit, r as FetchDefaults, t as CreateFetchOptions } from "./types-C0Uhh2KM.mjs";
2
- export { CreateFetchOptions, ExtendedRequestInit, FetchDefaults };
1
+ import { a as FetchDefaults, i as ExtendedRequestInit, n as ApiMethods, o as SearchParams, r as CreateFetchOptions, t as $Fetch } from "./types-WVLRjya1.mjs";
2
+ export { $Fetch, ApiMethods, CreateFetchOptions, ExtendedRequestInit, FetchDefaults, SearchParams };
@@ -3,31 +3,31 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
  //#region src/validator.d.ts
4
4
 
5
5
  /**
6
- * Type guard to check if a value is a Standard Schema schema
7
- */
6
+ * Type guard to check if a value is a Standard Schema schema
7
+ */
8
8
  declare function isStandardSchema(value: unknown): value is StandardSchemaV1;
9
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
- */
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
31
  declare function standardValidate<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, throwOnError: boolean): Promise<StandardSchemaV1.InferOutput<TSchema> | StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
32
32
  //#endregion
33
33
  export { isStandardSchema, standardValidate };
@@ -1 +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"}
1
+ {"version":3,"file":"validator.d.mts","names":[],"sources":["../src/validator.ts"],"sourcesContent":[],"mappings":";;;;;;AAMA;AA8BsB,iBA9BN,gBAAA,CA8BM,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IA9BqC,gBA8BrC;;;;;;;;;;;;;;;;;;;;;;;iBAAA,iCAAiC,0BAC7C,iDAGP,QACC,gBAAA,CAAiB,YAAY,WAC7B,gBAAA,CAAiB,OAAO,gBAAA,CAAiB,YAAY"}
@@ -1,4 +1,3 @@
1
- import "./errors-DKxnbFHZ.mjs";
2
1
  import { n as standardValidate, t as isStandardSchema } from "./validator-BGjWrM4d.mjs";
3
2
 
4
3
  export { isStandardSchema, standardValidate };
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@zap-studio/fetch",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
+ "license": "MIT",
5
6
  "private": false,
6
7
  "publishConfig": {
7
8
  "access": "public"
@@ -16,17 +17,17 @@
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/node": "^24.10.1",
19
- "@vitest/coverage-v8": "^4.0.14",
20
- "arktype": "^2.1.27",
20
+ "@vitest/coverage-v8": "^4.0.15",
21
+ "arktype": "^2.1.28",
21
22
  "jsdom": "^27.2.0",
22
- "tsdown": "^0.17.0-beta.4",
23
+ "tsdown": "0.17.0-beta.6",
23
24
  "typescript": "^5.9.3",
24
25
  "valibot": "^1.2.0",
25
- "vitest": "^4.0.14",
26
+ "vitest": "^4.0.15",
26
27
  "zod": "^4.1.13",
27
28
  "@zap-studio/tsdown-config": "0.0.0",
28
- "@zap-studio/typescript-config": "0.0.0",
29
- "@zap-studio/vitest-config": "0.0.0"
29
+ "@zap-studio/vitest-config": "0.0.0",
30
+ "@zap-studio/typescript-config": "0.0.0"
30
31
  },
31
32
  "exports": {
32
33
  ".": "./dist/index.mjs",
@@ -1,52 +0,0 @@
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
@@ -1 +0,0 @@
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"}