@zap-studio/fetch 0.4.4 → 0.4.6
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 +70 -80
- package/dist/utils.mjs +2 -2
- package/dist/utils.mjs.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,138 +1,128 @@
|
|
|
1
1
|
# @zap-studio/fetch
|
|
2
2
|
|
|
3
|
-
## 0.4.
|
|
3
|
+
## 0.4.6
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 5ea3d3b: Updated dependencies.
|
|
8
|
+
- @zap-studio/validation@0.3.1
|
|
8
9
|
|
|
9
|
-
- Accept JSON values (including arrays) in `ExtendedRequestInit.body`
|
|
10
|
-
- Auto-stringify plain JSON body values regardless of whether a response schema is provided
|
|
11
|
-
- Set `Content-Type: application/json` only when auto-stringifying and no explicit content type is set
|
|
12
10
|
|
|
13
|
-
## 0.4.
|
|
11
|
+
## 0.4.5
|
|
14
12
|
|
|
15
|
-
###
|
|
13
|
+
### Dependencies
|
|
16
14
|
|
|
17
|
-
-
|
|
18
|
-
- Updated dependencies [e4542bb]
|
|
19
|
-
- @zap-studio/validation@0.2.1
|
|
15
|
+
- f75b984: Updated dependency `@zap-studio/validation` to `0.3.0`.
|
|
20
16
|
|
|
21
|
-
## 0.4.
|
|
17
|
+
## 0.4.4
|
|
22
18
|
|
|
23
|
-
###
|
|
19
|
+
### Fixed
|
|
24
20
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
21
|
+
- 59a8d71: Fixed JSON request body handling.
|
|
22
|
+
- Accept JSON values (including arrays) in `ExtendedRequestInit.body`.
|
|
23
|
+
- Auto-stringify plain JSON body values even when no response schema is provided.
|
|
24
|
+
- Set `Content-Type: application/json` only when auto-stringifying and no explicit content type is set.
|
|
27
25
|
|
|
28
|
-
## 0.4.
|
|
26
|
+
## 0.4.3
|
|
29
27
|
|
|
30
|
-
###
|
|
28
|
+
### Changed
|
|
31
29
|
|
|
32
|
-
-
|
|
33
|
-
- Updated dependencies [447dbda]
|
|
34
|
-
- @zap-studio/validation@0.1.0
|
|
30
|
+
- e4542bb: Refined `standardValidate` typings so return types depend on `throwOnError`, and updated `@zap-studio/fetch` integration while preserving the existing boolean configuration API.
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
### Dependencies
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
- e4542bb: Updated dependency `@zap-studio/validation` to `0.2.1`.
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
## 0.4.2
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
### Dependencies
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
- 2de8183: Updated dependency `@zap-studio/validation` to `0.2.0`.
|
|
45
41
|
|
|
46
|
-
|
|
42
|
+
## 0.4.1
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
### Changed
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
- When `throwOnValidationError: false`: returns `Promise<StandardSchemaV1.Result<TSchema>>` (the result object with `value` or `issues`)
|
|
46
|
+
- 447dbda: Switched shared Standard Schema validation utilities to `@zap-studio/validation`.
|
|
52
47
|
|
|
53
|
-
|
|
48
|
+
### Dependencies
|
|
54
49
|
|
|
55
|
-
|
|
50
|
+
- 447dbda: Updated dependency `@zap-studio/validation` to `0.1.0`.
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
## 0.4.0
|
|
58
53
|
|
|
59
|
-
|
|
54
|
+
### Added
|
|
60
55
|
|
|
61
|
-
|
|
56
|
+
- 69057cd: Exposed fetch defaults constants and utility helpers as public exports.
|
|
62
57
|
|
|
63
|
-
|
|
58
|
+
## 0.3.1
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
### Changed
|
|
66
61
|
|
|
67
|
-
|
|
62
|
+
- 9919f63: Added discriminated return types based on `throwOnValidationError`.
|
|
63
|
+
- `throwOnValidationError: true` (default) returns `Promise<TSchema>`.
|
|
64
|
+
- `throwOnValidationError: false` returns `Promise<StandardSchemaV1.Result<TSchema>>`.
|
|
65
|
+
- This improves type safety and removes manual narrowing in default usage.
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
## 0.3.0
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
### Added
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
- 659621c: Added `searchParams` support in `createFetch` for factory-level default query parameters.
|
|
72
|
+
- Per-request `searchParams` still override factory defaults.
|
|
74
73
|
|
|
75
|
-
|
|
74
|
+
## 0.2.2
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
76
77
|
|
|
77
|
-
-
|
|
78
|
+
- 5c3abbf: Prepared JSR publish and `isolatedDeclarations` support with explicit `$Fetch` and `ApiMethods` types.
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
## 0.2.1
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
- Valibot
|
|
83
|
-
- ArkType
|
|
84
|
-
- Any Standard Schema compliant library
|
|
82
|
+
### Changed
|
|
85
83
|
|
|
86
|
-
|
|
84
|
+
- 82bac5c: Replaced regex-based slash trimming with more efficient string manipulation for URL normalization.
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
- **Smart URL Handling**: Absolute URLs bypass `baseURL` configuration
|
|
90
|
-
- **Auto JSON Body**: Automatic `JSON.stringify()` and `Content-Type` header when using schemas with request bodies
|
|
86
|
+
## 0.2.0
|
|
91
87
|
|
|
92
|
-
|
|
88
|
+
### Changed
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
- `FetchError` constructor signature changed: now requires `(message, response)`
|
|
90
|
+
- 78afb76: Migrated from Zod-only validation to Standard Schema v1 for broader validator compatibility.
|
|
91
|
+
- Supported libraries include Zod, Valibot, ArkType, and other Standard Schema-compliant validators.
|
|
97
92
|
|
|
98
|
-
|
|
93
|
+
### Added
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
import { z } from "zod";
|
|
95
|
+
- 78afb76: Added `createFetch()` factory pattern for pre-configured instances.
|
|
96
|
+
- 78afb76: Added smart URL behavior so absolute URLs bypass `baseURL`.
|
|
97
|
+
- 78afb76: Added automatic JSON body serialization and `Content-Type` handling for schema-based requests.
|
|
104
98
|
|
|
105
|
-
|
|
106
|
-
import { $fetch } from "@zap-studio/fetch";
|
|
107
|
-
import { z } from "zod"; // Zod 3.23+ is Standard Schema compliant
|
|
99
|
+
### Breaking
|
|
108
100
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
```
|
|
101
|
+
- 78afb76: Standard Schema-compliant validator libraries are now required (for example Zod 3.23+, Valibot 1.0+, ArkType 2.0+).
|
|
102
|
+
- 78afb76: Internal file structure was reorganized (affects deep imports).
|
|
103
|
+
- 78afb76: `FetchError` constructor now requires `(message, response)`.
|
|
113
104
|
|
|
114
105
|
## 0.1.2
|
|
115
106
|
|
|
116
|
-
###
|
|
107
|
+
### Changed
|
|
117
108
|
|
|
118
|
-
- 69c2b21:
|
|
109
|
+
- 69c2b21: Renamed `safeFetch` to `$fetch` while preserving `safeFetch` compatibility for legacy usage.
|
|
119
110
|
|
|
120
111
|
## 0.1.1
|
|
121
112
|
|
|
122
|
-
###
|
|
113
|
+
### Changed
|
|
123
114
|
|
|
124
|
-
- 5f1812b:
|
|
115
|
+
- 5f1812b: Updated `files` in `package.json` to publish only required artifacts.
|
|
125
116
|
|
|
126
117
|
## 0.1.0
|
|
127
118
|
|
|
128
|
-
###
|
|
129
|
-
|
|
130
|
-
- 1644006: Comprehensive description of the initial release features including:
|
|
119
|
+
### Added
|
|
131
120
|
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
121
|
+
- 1644006: Initial release of `@zap-studio/fetch`.
|
|
122
|
+
- Type-safe HTTP requests with Zod validation.
|
|
123
|
+
- Automatic content-type handling.
|
|
124
|
+
- Multiple response type support.
|
|
125
|
+
- API methods for GET, POST, PUT, PATCH, and DELETE.
|
|
126
|
+
- Flexible error handling.
|
|
127
|
+
- Custom `FetchError` class.
|
|
128
|
+
- Full TypeScript support.
|
package/dist/utils.mjs
CHANGED
|
@@ -156,8 +156,8 @@ async function fetchInternal(resource, schema, options, defaults) {
|
|
|
156
156
|
if (throwOnFetchError && !response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);
|
|
157
157
|
if (schema) {
|
|
158
158
|
const raw = await response.json();
|
|
159
|
-
if (throwOnValidationError) return standardValidate(schema, raw, true);
|
|
160
|
-
return standardValidate(schema, raw, false);
|
|
159
|
+
if (throwOnValidationError) return standardValidate(schema, raw, { throwOnError: true });
|
|
160
|
+
return standardValidate(schema, raw, { throwOnError: false });
|
|
161
161
|
}
|
|
162
162
|
return response;
|
|
163
163
|
}
|
package/dist/utils.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","names":["hash","url: string"],"sources":["../src/utils.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { isStandardSchema, standardValidate } from \"@zap-studio/validation\";\nimport { FetchError } from \"./errors\";\nimport type {\n $Fetch,\n ExtendedRequestInit,\n FetchDefaults,\n SearchParams,\n} from \"./types\";\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(\n input: SearchParams | undefined\n): 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 const isBodyInit =\n typeof init.body === \"string\" ||\n init.body instanceof FormData ||\n init.body instanceof URLSearchParams ||\n init.body instanceof Blob ||\n init.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(init.body) ||\n init.body instanceof ReadableStream;\n\n // Auto-stringify plain JSON bodies and set default Content-Type\n if (init.body != null && !isBodyInit) {\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 if (throwOnValidationError) {\n return standardValidate(schema, raw, true);\n }\n return standardValidate(schema, raw, false);\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 function methodFetch<TSchema extends StandardSchemaV1>(\n resource: string,\n schema: TSchema,\n options: Omit<ExtendedRequestInit<false>, \"method\">\n ): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;\n\n function methodFetch<TSchema extends StandardSchemaV1>(\n resource: string,\n schema: TSchema,\n options?: Omit<ExtendedRequestInit<true | undefined>, \"method\">\n ): Promise<StandardSchemaV1.InferOutput<TSchema>>;\n\n function methodFetch(\n resource: string,\n options?: Omit<ExtendedRequestInit, \"method\">\n ): Promise<Response>;\n\n function methodFetch(\n resource: string,\n schemaOrOptions?: StandardSchemaV1 | Omit<ExtendedRequestInit, \"method\">,\n optionsOrUndefined?: Omit<ExtendedRequestInit, \"method\">\n ): Promise<unknown> {\n const [schema, options] = isStandardSchema(schemaOrOptions)\n ? [schemaOrOptions, optionsOrUndefined]\n : [undefined, schemaOrOptions];\n\n return (fetchFn as (...args: unknown[]) => Promise<unknown>)(\n resource,\n schema,\n { ...options, method }\n );\n }\n\n return methodFetch;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":["hash","url: string"],"sources":["../src/utils.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { isStandardSchema, standardValidate } from \"@zap-studio/validation\";\nimport { FetchError } from \"./errors\";\nimport type {\n $Fetch,\n ExtendedRequestInit,\n FetchDefaults,\n SearchParams,\n} from \"./types\";\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(\n input: SearchParams | undefined\n): 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 const isBodyInit =\n typeof init.body === \"string\" ||\n init.body instanceof FormData ||\n init.body instanceof URLSearchParams ||\n init.body instanceof Blob ||\n init.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(init.body) ||\n init.body instanceof ReadableStream;\n\n // Auto-stringify plain JSON bodies and set default Content-Type\n if (init.body != null && !isBodyInit) {\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 if (throwOnValidationError) {\n return standardValidate(schema, raw, { throwOnError: true });\n }\n return standardValidate(schema, raw, { throwOnError: false });\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 function methodFetch<TSchema extends StandardSchemaV1>(\n resource: string,\n schema: TSchema,\n options: Omit<ExtendedRequestInit<false>, \"method\">\n ): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;\n\n function methodFetch<TSchema extends StandardSchemaV1>(\n resource: string,\n schema: TSchema,\n options?: Omit<ExtendedRequestInit<true | undefined>, \"method\">\n ): Promise<StandardSchemaV1.InferOutput<TSchema>>;\n\n function methodFetch(\n resource: string,\n options?: Omit<ExtendedRequestInit, \"method\">\n ): Promise<Response>;\n\n function methodFetch(\n resource: string,\n schemaOrOptions?: StandardSchemaV1 | Omit<ExtendedRequestInit, \"method\">,\n optionsOrUndefined?: Omit<ExtendedRequestInit, \"method\">\n ): Promise<unknown> {\n const [schema, options] = isStandardSchema(schemaOrOptions)\n ? [schemaOrOptions, optionsOrUndefined]\n : [undefined, schemaOrOptions];\n\n return (fetchFn as (...args: unknown[]) => Promise<unknown>)(\n resource,\n schema,\n { ...options, method }\n );\n }\n\n return methodFetch;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-studio/fetch",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@standard-schema/spec": "^1.1.0",
|
|
42
|
-
"@zap-studio/validation": "0.
|
|
42
|
+
"@zap-studio/validation": "0.3.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.2",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"valibot": "^1.2.0",
|
|
51
51
|
"vitest": "^4.0.18",
|
|
52
52
|
"zod": "^4.2.0",
|
|
53
|
+
"@zap-studio/vitest-config": "0.0.0",
|
|
53
54
|
"@zap-studio/typescript-config": "0.0.0",
|
|
54
|
-
"@zap-studio/tsdown-config": "0.0.0"
|
|
55
|
-
"@zap-studio/vitest-config": "0.0.0"
|
|
55
|
+
"@zap-studio/tsdown-config": "0.0.0"
|
|
56
56
|
},
|
|
57
57
|
"exports": {
|
|
58
58
|
".": "./dist/index.mjs",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"types": "./dist/index.d.mts",
|
|
68
68
|
"scripts": {
|
|
69
69
|
"build": "tsdown --config tsdown.config.ts",
|
|
70
|
-
"
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
71
|
"test": "vitest run",
|
|
72
72
|
"test:watch": "vitest --watch"
|
|
73
73
|
}
|