@zap-studio/fetch 0.4.3 → 0.4.5

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,22 @@
1
1
  # @zap-studio/fetch
2
2
 
3
+ ## 0.4.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [f75b984]
8
+ - @zap-studio/validation@0.3.0
9
+
10
+ ## 0.4.4
11
+
12
+ ### Patch Changes
13
+
14
+ - 59a8d71: Fix request body handling for JSON payloads.
15
+
16
+ - Accept JSON values (including arrays) in `ExtendedRequestInit.body`
17
+ - Auto-stringify plain JSON body values regardless of whether a response schema is provided
18
+ - Set `Content-Type: application/json` only when auto-stringifying and no explicit content type is set
19
+
3
20
  ## 0.4.3
4
21
 
5
22
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { a as FetchDefaults } from "./types-CznLOeef.mjs";
1
+ import { FetchDefaults } from "./types.mjs";
2
2
 
3
3
  //#region src/constants.d.ts
4
4
 
@@ -1,3 +1,14 @@
1
- import { t as GLOBAL_DEFAULTS } from "./constants-CzfCghW-.mjs";
1
+ //#region src/constants.ts
2
+ /**
3
+ * Default options for the global $fetch
4
+ */
5
+ const GLOBAL_DEFAULTS = {
6
+ baseURL: "",
7
+ headers: void 0,
8
+ throwOnFetchError: true,
9
+ throwOnValidationError: true
10
+ };
2
11
 
3
- export { GLOBAL_DEFAULTS };
12
+ //#endregion
13
+ export { GLOBAL_DEFAULTS };
14
+ //# sourceMappingURL=constants.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.mjs","names":["GLOBAL_DEFAULTS: FetchDefaults"],"sources":["../src/constants.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"],"mappings":";;;;AAKA,MAAaA,kBAAiC;CAC5C,SAAS;CACT,SAAS;CACT,mBAAmB;CACnB,wBAAwB;CACzB"}
@@ -15,4 +15,4 @@ var FetchError = class extends Error {
15
15
 
16
16
  //#endregion
17
17
  export { FetchError as t };
18
- //# sourceMappingURL=errors-rk_WqFKR.mjs.map
18
+ //# sourceMappingURL=errors-DQfwnwmz.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors-rk_WqFKR.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\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"],"mappings":";;;;AAGA,IAAa,aAAb,cAAgC,MAAM;CACpC;CACA;CAEA,YAAY,SAAiB,UAAoB;AAC/C,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS,SAAS;AACvB,OAAK,WAAW"}
1
+ {"version":3,"file":"errors-DQfwnwmz.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\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"],"mappings":";;;;AAGA,IAAa,aAAb,cAAgC,MAAM;CACpC;CACA;CAEA,YAAY,SAAiB,UAAoB;AAC/C,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS,SAAS;AACvB,OAAK,WAAW"}
package/dist/errors.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as FetchError } from "./errors-rk_WqFKR.mjs";
1
+ import { t as FetchError } from "./errors-DQfwnwmz.mjs";
2
2
 
3
3
  export { FetchError };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as ExtendedRequestInit, n as ApiMethods, r as CreateFetchOptions, t as $Fetch } from "./types-CznLOeef.mjs";
1
+ import { $Fetch, ApiMethods, CreateFetchOptions, ExtendedRequestInit } from "./types.mjs";
2
2
  import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
 
4
4
  //#region src/index.d.ts
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { t as GLOBAL_DEFAULTS } from "./constants-CzfCghW-.mjs";
2
- import { n as fetchInternal, t as createMethod } from "./utils-C1YvgnKM.mjs";
1
+ import { GLOBAL_DEFAULTS } from "./constants.mjs";
2
+ import { createMethod, fetchInternal } from "./utils.mjs";
3
3
  import { isStandardSchema } from "@zap-studio/validation";
4
4
 
5
5
  //#region src/index.ts
package/dist/types.d.mts CHANGED
@@ -1,2 +1,140 @@
1
- import { a as FetchDefaults, i as ExtendedRequestInit, n as ApiMethods, o as SearchParams, r as CreateFetchOptions, t as $Fetch } from "./types-CznLOeef.mjs";
2
- export { $Fetch, ApiMethods, CreateFetchOptions, ExtendedRequestInit, FetchDefaults, SearchParams };
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
+ * JSON-serializable value used for request bodies.
14
+ */
15
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
16
+ [key: string]: JsonValue;
17
+ };
18
+ /**
19
+ * Request body type accepted by the fetch wrapper.
20
+ */
21
+ type RequestBody = BodyInit | JsonValue;
22
+ /**
23
+ * Base extended request options without throwOnValidationError
24
+ */
25
+ type BaseExtendedRequestInit = Omit<RequestInit, "body"> & {
26
+ /**
27
+ * Request body - can be a BodyInit value or an object that will be JSON-stringified
28
+ */
29
+ body?: RequestBody;
30
+ /**
31
+ * Per-request query/search params
32
+ * @default undefined
33
+ */
34
+ searchParams?: SearchParams;
35
+ /**
36
+ * Whether to throw a FetchError on HTTP errors (non-2xx responses)
37
+ * @default true
38
+ */
39
+ throwOnFetchError?: boolean;
40
+ };
41
+ /**
42
+ * Extended RequestInit type to include custom fetch options
43
+ */
44
+ type ExtendedRequestInit<TThrowOnValidationError extends boolean | undefined = boolean | undefined> = BaseExtendedRequestInit & {
45
+ /**
46
+ * Whether to throw a ValidationError on validation errors
47
+ * @default true
48
+ */
49
+ throwOnValidationError?: TThrowOnValidationError;
50
+ };
51
+ /**
52
+ * Internal defaults used by fetchInternal
53
+ */
54
+ interface FetchDefaults {
55
+ /**
56
+ * Base URL to prepend to all requests
57
+ * @default ""
58
+ */
59
+ baseURL: string;
60
+ /**
61
+ * Default headers to include in all requests (can be overridden per request)
62
+ * @default undefined
63
+ */
64
+ headers?: HeadersInit;
65
+ /**
66
+ * Default query/search params applied to every request (can be overridden per request)
67
+ * @default undefined
68
+ */
69
+ searchParams?: SearchParams;
70
+ /**
71
+ * Whether to throw a `FetchError` on HTTP errors (non-2xx responses)
72
+ * @default true
73
+ */
74
+ throwOnFetchError: boolean;
75
+ /**
76
+ * Whether to throw a `ValidationError` on validation errors
77
+ * @default true
78
+ */
79
+ throwOnValidationError: boolean;
80
+ }
81
+ /**
82
+ * Options for creating a custom fetch instance with `createFetch`
83
+ */
84
+ type CreateFetchOptions = Partial<FetchDefaults>;
85
+ /**
86
+ * Type-safe fetch function with Standard Schema validation support
87
+ */
88
+ interface $Fetch {
89
+ /**
90
+ * Fetch with schema validation and throwOnValidationError: false
91
+ * @param resource - URL or path to fetch
92
+ * @param schema - Standard Schema for response validation
93
+ * @param options - Extended request options with throwOnValidationError: false
94
+ * @returns Standard Schema Result object with value or issues
95
+ */
96
+ <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options: ExtendedRequestInit<false>): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
97
+ /**
98
+ * Fetch with schema validation and throwOnValidationError: true or undefined (default)
99
+ * @param resource - URL or path to fetch
100
+ * @param schema - Standard Schema for response validation
101
+ * @param options - Extended request options
102
+ * @returns Validated data of type TSchema
103
+ */
104
+ <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: ExtendedRequestInit<true | undefined>): Promise<StandardSchemaV1.InferOutput<TSchema>>;
105
+ /**
106
+ * Fetch without schema validation
107
+ * @param resource - URL or path to fetch
108
+ * @param options - Extended request options
109
+ * @returns Raw Response object
110
+ */
111
+ (resource: string, options?: ExtendedRequestInit): Promise<Response>;
112
+ }
113
+ /**
114
+ * API HTTP method-specific fetch functions
115
+ */
116
+ interface ApiMethods {
117
+ /**
118
+ * DELETE method fetch function
119
+ */
120
+ delete: $Fetch;
121
+ /**
122
+ * GET method fetch function
123
+ */
124
+ get: $Fetch;
125
+ /**
126
+ * PATCH method fetch function
127
+ */
128
+ patch: $Fetch;
129
+ /**
130
+ * POST method fetch function
131
+ */
132
+ post: $Fetch;
133
+ /**
134
+ * PUT method fetch function
135
+ */
136
+ put: $Fetch;
137
+ }
138
+ //#endregion
139
+ export { $Fetch, ApiMethods, CreateFetchOptions, ExtendedRequestInit, FetchDefaults, JsonValue, RequestBody, SearchParams };
140
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;AAQA;AASA;AAWA;AAAqC;AAKD,KAzBxB,YAAA,GACR,eAwBgC,GAvBhC,MAuBgC,CAAA,MAAA,EAAA,MAAA,CAAA,GAAA,MAAA,GAAA,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA;;;;AASnB,KAzBL,SAAA,GAyBK,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,IAAA,GApBb,SAoBa,EAAA,GAAA;EAWjB,CAAA,GAAY,EAAA,MAAA,CAAA,EA9BS,SA8BT;AAaZ,CAAA;AA+BA;AAKA;;AAUY,KApFA,WAAA,GAAc,QAoFd,GApFyB,SAoFzB;;;;KA/EP,uBAAA,GAA0B,IAiFD,CAjFM,WAiFN,EAAA,MAAA,CAAA,GAAA;EAAzB;;;EAYS,IAAA,CAAA,EAzFL,WAyFK;EAC4B;;;;EAQmB,YAAA,CAAA,EA7F5C,YA6F4C;EAAR;;AAMrD;;EAQO,iBAAA,CAAA,EAAA,OAAA;CAIE;;;;KApGG,iGAER;;;;;2BAKuB;;;;;UAMV,aAAA;;;;;;;;;;YAUL;;;;;iBAKK;;;;;;;;;;;;;;;KAgBL,kBAAA,GAAqB,QAAQ;;;;UAKxB,MAAA;;;;;;;;mBAQE,4CAEP,kBACC,6BACR,QAAQ,gBAAA,CAAiB,OAAO,gBAAA,CAAiB,YAAY;;;;;;;;mBAS/C,4CAEP,mBACE,wCACT,QAAQ,gBAAA,CAAiB,YAAY;;;;;;;+BAQX,sBAAsB,QAAQ;;;;;UAM5C,UAAA;;;;UAIP;;;;OAIH;;;;SAIE;;;;QAID;;;;OAID"}
package/dist/utils.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as FetchDefaults, i as ExtendedRequestInit, t as $Fetch } from "./types-CznLOeef.mjs";
1
+ import { $Fetch, ExtendedRequestInit, FetchDefaults } from "./types.mjs";
2
2
  import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
 
4
4
  //#region src/utils.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;AA0BA;;;;;AAyLA;;;;;;AAwEA;;;AAGG,iBApQa,YAAA,CAoQb,IAAA,EAnQK,WAmQL,GAAA,SAAA,EAAA,QAAA,EAlQS,WAkQT,GAAA,SAAA,CAAA,EAjQA,OAiQA,GAAA,SAAA;;;;iBA3EmB,aAAA,2BAEZ,uCACC,2CACC,gBACT;;;;iBAmEa,4BAA4B,iBACjC,yBAER"}
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;AA0BA;;;;;AAyLA;;;;;;AAiFA;;;AAGG,iBA7Qa,YAAA,CA6Qb,IAAA,EA5QK,WA4QL,GAAA,SAAA,EAAA,QAAA,EA3QS,WA2QT,GAAA,SAAA,CAAA,EA1QA,OA0QA,GAAA,SAAA;;;;iBApFmB,aAAA,2BAEZ,uCACC,2CACC,gBACT;;;;iBA4Ea,4BAA4B,iBACjC,yBAER"}
package/dist/utils.mjs CHANGED
@@ -1,3 +1,180 @@
1
- import { n as fetchInternal, r as mergeHeaders, t as createMethod } from "./utils-C1YvgnKM.mjs";
1
+ import { t as FetchError } from "./errors-DQfwnwmz.mjs";
2
+ import { isStandardSchema, standardValidate } from "@zap-studio/validation";
2
3
 
3
- export { createMethod, fetchInternal, mergeHeaders };
4
+ //#region src/utils.ts
5
+ /**
6
+ * Merges two HeadersInit objects, with the second one taking precedence
7
+ *
8
+ * @example
9
+ * const baseHeaders = { "Authorization": "Bearer token", "Content-Type": "application/json" };
10
+ * const overrideHeaders = { "Content-Type": "application/xml", "X-Custom-Header": "value" };
11
+ *
12
+ * const merged = mergeHeaders(baseHeaders, overrideHeaders);
13
+ *
14
+ * // Resulting headers:
15
+ * // {
16
+ * // "Authorization": "Bearer token",
17
+ * // "Content-Type": "application/xml",
18
+ * // "X-Custom-Header": "value"
19
+ * // }
20
+ */
21
+ function mergeHeaders(base, override) {
22
+ if (!(base || override)) return;
23
+ const merged = new Headers(base);
24
+ if (override) {
25
+ const overrideHeaders = new Headers(override);
26
+ for (const [key, value] of overrideHeaders.entries()) merged.set(key, value);
27
+ }
28
+ return merged;
29
+ }
30
+ /**
31
+ * Removes trailing slashes from a string
32
+ */
33
+ function trimTrailingSlashes(str) {
34
+ let end = str.length;
35
+ while (end > 0 && str[end - 1] === "/") end -= 1;
36
+ return str.slice(0, end);
37
+ }
38
+ /**
39
+ * Removes leading slashes from a string
40
+ */
41
+ function trimLeadingSlashes(str) {
42
+ let start = 0;
43
+ while (start < str.length && str[start] === "/") start += 1;
44
+ return str.slice(start);
45
+ }
46
+ const ABSOLUTE_URL_PATTERN = /^(?:https?:)?\/\//i;
47
+ /**
48
+ * Checks if a URL is absolute (starts with http://, https://, or //)
49
+ */
50
+ function isAbsoluteURL(url) {
51
+ return ABSOLUTE_URL_PATTERN.test(url);
52
+ }
53
+ /**
54
+ * Normalizes search parameters into a URLSearchParams object.
55
+ */
56
+ function normalizeSearchParams(input) {
57
+ if (input === void 0 || input === null) return new URLSearchParams();
58
+ if (input instanceof URLSearchParams) return new URLSearchParams(input);
59
+ if (typeof input === "string") return new URLSearchParams(input);
60
+ if (Array.isArray(input)) return new URLSearchParams(input);
61
+ const params = new URLSearchParams();
62
+ for (const [k, v] of Object.entries(input)) params.set(k, v);
63
+ return params;
64
+ }
65
+ /**
66
+ * Parses a URL string into its path, query, and hash components.
67
+ * Handles both absolute URLs (using URL constructor) and relative paths.
68
+ */
69
+ function parseUrlComponents(url) {
70
+ if (isAbsoluteURL(url)) try {
71
+ const parsed = new URL(url);
72
+ const pathOnly = `${parsed.origin}${parsed.pathname}`;
73
+ const existingQuery = parsed.search.startsWith("?") ? parsed.search.slice(1) : parsed.search;
74
+ let hash$1 = parsed.hash;
75
+ if (hash$1 === "" && url.endsWith("#")) hash$1 = "#";
76
+ return {
77
+ pathOnly,
78
+ existingQuery,
79
+ hash: hash$1
80
+ };
81
+ } catch {}
82
+ let hash = "";
83
+ let urlWithoutHash = url;
84
+ const hashIndex = url.indexOf("#");
85
+ if (hashIndex !== -1) {
86
+ hash = url.slice(hashIndex);
87
+ urlWithoutHash = url.slice(0, hashIndex);
88
+ }
89
+ const queryIndex = urlWithoutHash.indexOf("?");
90
+ if (queryIndex === -1) return {
91
+ pathOnly: urlWithoutHash,
92
+ existingQuery: "",
93
+ hash
94
+ };
95
+ return {
96
+ pathOnly: urlWithoutHash.slice(0, queryIndex),
97
+ existingQuery: urlWithoutHash.slice(queryIndex + 1),
98
+ hash
99
+ };
100
+ }
101
+ /**
102
+ * Builds a URL with merged search parameters from factory defaults,
103
+ * request options, and existing query parameters.
104
+ *
105
+ * This function takes a base URL, factory search parameters,
106
+ * and request search parameters, and combines them into a single URL.
107
+ *
108
+ * Priority (highest to lowest):
109
+ * 1. Request search parameters (highest priority, overwrites all others)
110
+ * 2. Existing query parameters from the URL (overwrites factory defaults)
111
+ * 3. Factory search parameters (lowest priority, overwritten by all others)
112
+ *
113
+ * If no search parameters are provided, the URL will not have a query string.
114
+ * Any trailing hash/fragment is preserved.
115
+ */
116
+ function buildUrlWithMergedSearchParams(url, factorySearch, requestSearch) {
117
+ const { pathOnly, existingQuery, hash } = parseUrlComponents(url);
118
+ if (!(factorySearch ?? requestSearch ?? existingQuery)) return url;
119
+ const mergedParams = new URLSearchParams();
120
+ const resourceParams = new URLSearchParams(existingQuery);
121
+ const factoryParams = normalizeSearchParams(factorySearch);
122
+ const reqParams = normalizeSearchParams(requestSearch);
123
+ for (const [k, v] of factoryParams.entries()) mergedParams.set(k, v);
124
+ for (const [k, v] of resourceParams.entries()) mergedParams.set(k, v);
125
+ for (const [k, v] of reqParams.entries()) mergedParams.set(k, v);
126
+ const queryString = mergedParams.toString();
127
+ if (queryString) return `${pathOnly}?${queryString}${hash}`;
128
+ return `${pathOnly}${hash}`;
129
+ }
130
+ /**
131
+ * Internal fetch implementation used by both $fetch and createFetch
132
+ */
133
+ async function fetchInternal(resource, schema, options, defaults) {
134
+ const { throwOnValidationError = defaults.throwOnValidationError, throwOnFetchError = defaults.throwOnFetchError, headers: requestHeaders, searchParams: requestSearchParams, ...rest } = options || {};
135
+ const mergedHeaders = mergeHeaders(defaults.headers, requestHeaders);
136
+ const init = {
137
+ ...rest,
138
+ headers: mergedHeaders
139
+ };
140
+ const isBodyInit = typeof init.body === "string" || init.body instanceof FormData || init.body instanceof URLSearchParams || init.body instanceof Blob || init.body instanceof ArrayBuffer || ArrayBuffer.isView(init.body) || init.body instanceof ReadableStream;
141
+ if (init.body != null && !isBodyInit) {
142
+ init.body = JSON.stringify(init.body);
143
+ const existingHeaders = new Headers(init.headers);
144
+ if (!existingHeaders.has("Content-Type")) existingHeaders.set("Content-Type", "application/json");
145
+ init.headers = existingHeaders;
146
+ }
147
+ let url;
148
+ if (isAbsoluteURL(resource)) url = resource;
149
+ else {
150
+ const base = trimTrailingSlashes(defaults.baseURL);
151
+ const path = trimLeadingSlashes(resource);
152
+ url = base ? `${base}/${path}` : resource;
153
+ }
154
+ url = buildUrlWithMergedSearchParams(url, defaults.searchParams, requestSearchParams);
155
+ const response = await fetch(url, init);
156
+ if (throwOnFetchError && !response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);
157
+ if (schema) {
158
+ const raw = await response.json();
159
+ if (throwOnValidationError) return standardValidate(schema, raw, { throwOnError: true });
160
+ return standardValidate(schema, raw, { throwOnError: false });
161
+ }
162
+ return response;
163
+ }
164
+ /**
165
+ * Creates an HTTP method helper bound to a fetch function
166
+ */
167
+ function createMethod(fetchFn, method) {
168
+ function methodFetch(resource, schemaOrOptions, optionsOrUndefined) {
169
+ const [schema, options] = isStandardSchema(schemaOrOptions) ? [schemaOrOptions, optionsOrUndefined] : [void 0, schemaOrOptions];
170
+ return fetchFn(resource, schema, {
171
+ ...options,
172
+ method
173
+ });
174
+ }
175
+ return methodFetch;
176
+ }
177
+
178
+ //#endregion
179
+ export { createMethod, fetchInternal, mergeHeaders };
180
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +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, { 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",
3
+ "version": "0.4.5",
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.2.1"
42
+ "@zap-studio/validation": "0.3.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.2",
@@ -48,7 +48,7 @@
48
48
  "tsdown": "^0.18.0",
49
49
  "typescript": "^5.9.3",
50
50
  "valibot": "^1.2.0",
51
- "vitest": "^4.0.15",
51
+ "vitest": "^4.0.18",
52
52
  "zod": "^4.2.0",
53
53
  "@zap-studio/vitest-config": "0.0.0",
54
54
  "@zap-studio/tsdown-config": "0.0.0",
@@ -1,14 +0,0 @@
1
- //#region src/constants.ts
2
- /**
3
- * Default options for the global $fetch
4
- */
5
- const GLOBAL_DEFAULTS = {
6
- baseURL: "",
7
- headers: void 0,
8
- throwOnFetchError: true,
9
- throwOnValidationError: true
10
- };
11
-
12
- //#endregion
13
- export { GLOBAL_DEFAULTS as t };
14
- //# sourceMappingURL=constants-CzfCghW-.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants-CzfCghW-.mjs","names":["GLOBAL_DEFAULTS: FetchDefaults"],"sources":["../src/constants.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"],"mappings":";;;;AAKA,MAAaA,kBAAiC;CAC5C,SAAS;CACT,SAAS;CACT,mBAAmB;CACnB,wBAAwB;CACzB"}
@@ -1,130 +0,0 @@
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
- * Base extended request options without throwOnValidationError
14
- */
15
- type BaseExtendedRequestInit = 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
- /**
32
- * Extended RequestInit type to include custom fetch options
33
- */
34
- type ExtendedRequestInit<TThrowOnValidationError extends boolean | undefined = boolean | undefined> = BaseExtendedRequestInit & {
35
- /**
36
- * Whether to throw a ValidationError on validation errors
37
- * @default true
38
- */
39
- throwOnValidationError?: TThrowOnValidationError;
40
- };
41
- /**
42
- * Internal defaults used by fetchInternal
43
- */
44
- type FetchDefaults = {
45
- /**
46
- * Base URL to prepend to all requests
47
- * @default ""
48
- */
49
- baseURL: string;
50
- /**
51
- * Default headers to include in all requests (can be overridden per request)
52
- * @default undefined
53
- */
54
- headers?: HeadersInit;
55
- /**
56
- * Default query/search params applied to every request (can be overridden per request)
57
- * @default undefined
58
- */
59
- searchParams?: SearchParams;
60
- /**
61
- * Whether to throw a `FetchError` on HTTP errors (non-2xx responses)
62
- * @default true
63
- */
64
- throwOnFetchError: boolean;
65
- /**
66
- * Whether to throw a `ValidationError` on validation errors
67
- * @default true
68
- */
69
- throwOnValidationError: boolean;
70
- };
71
- /**
72
- * Options for creating a custom fetch instance with `createFetch`
73
- */
74
- type CreateFetchOptions = Partial<FetchDefaults>;
75
- /**
76
- * Type-safe fetch function with Standard Schema validation support
77
- */
78
- type $Fetch = {
79
- /**
80
- * Fetch with schema validation and throwOnValidationError: false
81
- * @param resource - URL or path to fetch
82
- * @param schema - Standard Schema for response validation
83
- * @param options - Extended request options with throwOnValidationError: false
84
- * @returns Standard Schema Result object with value or issues
85
- */
86
- <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options: ExtendedRequestInit<false>): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<TSchema>>>;
87
- /**
88
- * Fetch with schema validation and throwOnValidationError: true or undefined (default)
89
- * @param resource - URL or path to fetch
90
- * @param schema - Standard Schema for response validation
91
- * @param options - Extended request options
92
- * @returns Validated data of type TSchema
93
- */
94
- <TSchema extends StandardSchemaV1>(resource: string, schema: TSchema, options?: ExtendedRequestInit<true | undefined>): Promise<StandardSchemaV1.InferOutput<TSchema>>;
95
- /**
96
- * Fetch without schema validation
97
- * @param resource - URL or path to fetch
98
- * @param options - Extended request options
99
- * @returns Raw Response object
100
- */
101
- (resource: string, options?: ExtendedRequestInit): Promise<Response>;
102
- };
103
- /**
104
- * API HTTP method-specific fetch functions
105
- */
106
- type ApiMethods = {
107
- /**
108
- * GET method fetch function
109
- */
110
- get: $Fetch;
111
- /**
112
- * POST method fetch function
113
- */
114
- post: $Fetch;
115
- /**
116
- * PUT method fetch function
117
- */
118
- put: $Fetch;
119
- /**
120
- * DELETE method fetch function
121
- */
122
- delete: $Fetch;
123
- /**
124
- * PATCH method fetch function
125
- */
126
- patch: $Fetch;
127
- };
128
- //#endregion
129
- export { FetchDefaults as a, ExtendedRequestInit as i, ApiMethods as n, SearchParams as o, CreateFetchOptions as r, $Fetch as t };
130
- //# sourceMappingURL=types-CznLOeef.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-CznLOeef.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;AAQA;AAEI;;;AAWK,KAbG,YAAA,GACR,eAYK,GAXL,MAWK,CAAA,MAAA,EAAA,MAAA,CAAA,GAAA,MAAA,GAAA,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA;;;;AAgBT,KApBK,uBAAA,GAA0B,IAoBnB,CApBwB,WAoBxB,EAAA,MAAA,CAAA,GAAA;EAaZ;AA+BA;AAKA;EAQmB,IAAA,CAAA,EAzEV,QAyEU,GAzEC,MAyED,CAAA,MAAA,EAAA,OAAA,CAAA;EAEP;;;;EAEC,YAAA,CAAA,EAxEI,YAwEa;EAAzB;;;;EAaqC,iBAAA,CAAA,EAAA,OAAA;CAA7B;;;;AAQwC,KAlFzC,mBAkFyC,CAAA,gCAAA,OAAA,GAAA,SAAA,GAAA,OAAA,GAAA,SAAA,CAAA,GAhFjD,uBAgFiD,GAAA;EAAA;AAMrD;;;EAYO,sBAAA,CAAA,EA7FoB,uBA6FpB;CAIG;;;;KA3FE,aAAA;;;;;;;;;;YAUA;;;;;iBAKK;;;;;;;;;;;;;;;KAgBL,kBAAA,GAAqB,QAAQ;;;;KAK7B,MAAA;;;;;;;;mBAQO,4CAEP,kBACC,6BACR,QAAQ,gBAAA,CAAiB,OAAO,gBAAA,CAAiB,YAAY;;;;;;;;mBAS/C,4CAEP,mBACE,wCACT,QAAQ,gBAAA,CAAiB,YAAY;;;;;;;+BAQX,sBAAsB,QAAQ;;;;;KAMjD,UAAA;;;;OAIL;;;;QAIC;;;;OAID;;;;UAIG;;;;SAID"}
@@ -1,179 +0,0 @@
1
- import { t as FetchError } from "./errors-rk_WqFKR.mjs";
2
- import { isStandardSchema, standardValidate } from "@zap-studio/validation";
3
-
4
- //#region src/utils.ts
5
- /**
6
- * Merges two HeadersInit objects, with the second one taking precedence
7
- *
8
- * @example
9
- * const baseHeaders = { "Authorization": "Bearer token", "Content-Type": "application/json" };
10
- * const overrideHeaders = { "Content-Type": "application/xml", "X-Custom-Header": "value" };
11
- *
12
- * const merged = mergeHeaders(baseHeaders, overrideHeaders);
13
- *
14
- * // Resulting headers:
15
- * // {
16
- * // "Authorization": "Bearer token",
17
- * // "Content-Type": "application/xml",
18
- * // "X-Custom-Header": "value"
19
- * // }
20
- */
21
- function mergeHeaders(base, override) {
22
- if (!(base || override)) return;
23
- const merged = new Headers(base);
24
- if (override) {
25
- const overrideHeaders = new Headers(override);
26
- for (const [key, value] of overrideHeaders.entries()) merged.set(key, value);
27
- }
28
- return merged;
29
- }
30
- /**
31
- * Removes trailing slashes from a string
32
- */
33
- function trimTrailingSlashes(str) {
34
- let end = str.length;
35
- while (end > 0 && str[end - 1] === "/") end -= 1;
36
- return str.slice(0, end);
37
- }
38
- /**
39
- * Removes leading slashes from a string
40
- */
41
- function trimLeadingSlashes(str) {
42
- let start = 0;
43
- while (start < str.length && str[start] === "/") start += 1;
44
- return str.slice(start);
45
- }
46
- const ABSOLUTE_URL_PATTERN = /^(?:https?:)?\/\//i;
47
- /**
48
- * Checks if a URL is absolute (starts with http://, https://, or //)
49
- */
50
- function isAbsoluteURL(url) {
51
- return ABSOLUTE_URL_PATTERN.test(url);
52
- }
53
- /**
54
- * Normalizes search parameters into a URLSearchParams object.
55
- */
56
- function normalizeSearchParams(input) {
57
- if (input === void 0 || input === null) return new URLSearchParams();
58
- if (input instanceof URLSearchParams) return new URLSearchParams(input);
59
- if (typeof input === "string") return new URLSearchParams(input);
60
- if (Array.isArray(input)) return new URLSearchParams(input);
61
- const params = new URLSearchParams();
62
- for (const [k, v] of Object.entries(input)) params.set(k, v);
63
- return params;
64
- }
65
- /**
66
- * Parses a URL string into its path, query, and hash components.
67
- * Handles both absolute URLs (using URL constructor) and relative paths.
68
- */
69
- function parseUrlComponents(url) {
70
- if (isAbsoluteURL(url)) try {
71
- const parsed = new URL(url);
72
- const pathOnly = `${parsed.origin}${parsed.pathname}`;
73
- const existingQuery = parsed.search.startsWith("?") ? parsed.search.slice(1) : parsed.search;
74
- let hash$1 = parsed.hash;
75
- if (hash$1 === "" && url.endsWith("#")) hash$1 = "#";
76
- return {
77
- pathOnly,
78
- existingQuery,
79
- hash: hash$1
80
- };
81
- } catch {}
82
- let hash = "";
83
- let urlWithoutHash = url;
84
- const hashIndex = url.indexOf("#");
85
- if (hashIndex !== -1) {
86
- hash = url.slice(hashIndex);
87
- urlWithoutHash = url.slice(0, hashIndex);
88
- }
89
- const queryIndex = urlWithoutHash.indexOf("?");
90
- if (queryIndex === -1) return {
91
- pathOnly: urlWithoutHash,
92
- existingQuery: "",
93
- hash
94
- };
95
- return {
96
- pathOnly: urlWithoutHash.slice(0, queryIndex),
97
- existingQuery: urlWithoutHash.slice(queryIndex + 1),
98
- hash
99
- };
100
- }
101
- /**
102
- * Builds a URL with merged search parameters from factory defaults,
103
- * request options, and existing query parameters.
104
- *
105
- * This function takes a base URL, factory search parameters,
106
- * and request search parameters, and combines them into a single URL.
107
- *
108
- * Priority (highest to lowest):
109
- * 1. Request search parameters (highest priority, overwrites all others)
110
- * 2. Existing query parameters from the URL (overwrites factory defaults)
111
- * 3. Factory search parameters (lowest priority, overwritten by all others)
112
- *
113
- * If no search parameters are provided, the URL will not have a query string.
114
- * Any trailing hash/fragment is preserved.
115
- */
116
- function buildUrlWithMergedSearchParams(url, factorySearch, requestSearch) {
117
- const { pathOnly, existingQuery, hash } = parseUrlComponents(url);
118
- if (!(factorySearch ?? requestSearch ?? existingQuery)) return url;
119
- const mergedParams = new URLSearchParams();
120
- const resourceParams = new URLSearchParams(existingQuery);
121
- const factoryParams = normalizeSearchParams(factorySearch);
122
- const reqParams = normalizeSearchParams(requestSearch);
123
- for (const [k, v] of factoryParams.entries()) mergedParams.set(k, v);
124
- for (const [k, v] of resourceParams.entries()) mergedParams.set(k, v);
125
- for (const [k, v] of reqParams.entries()) mergedParams.set(k, v);
126
- const queryString = mergedParams.toString();
127
- if (queryString) return `${pathOnly}?${queryString}${hash}`;
128
- return `${pathOnly}${hash}`;
129
- }
130
- /**
131
- * Internal fetch implementation used by both $fetch and createFetch
132
- */
133
- async function fetchInternal(resource, schema, options, defaults) {
134
- const { throwOnValidationError = defaults.throwOnValidationError, throwOnFetchError = defaults.throwOnFetchError, headers: requestHeaders, searchParams: requestSearchParams, ...rest } = options || {};
135
- const mergedHeaders = mergeHeaders(defaults.headers, requestHeaders);
136
- const init = {
137
- ...rest,
138
- headers: mergedHeaders
139
- };
140
- if (schema && init.body) {
141
- init.body = JSON.stringify(init.body);
142
- const existingHeaders = new Headers(init.headers);
143
- if (!existingHeaders.has("Content-Type")) existingHeaders.set("Content-Type", "application/json");
144
- init.headers = existingHeaders;
145
- }
146
- let url;
147
- if (isAbsoluteURL(resource)) url = resource;
148
- else {
149
- const base = trimTrailingSlashes(defaults.baseURL);
150
- const path = trimLeadingSlashes(resource);
151
- url = base ? `${base}/${path}` : resource;
152
- }
153
- url = buildUrlWithMergedSearchParams(url, defaults.searchParams, requestSearchParams);
154
- const response = await fetch(url, init);
155
- if (throwOnFetchError && !response.ok) throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);
156
- if (schema) {
157
- const raw = await response.json();
158
- if (throwOnValidationError) return standardValidate(schema, raw, true);
159
- return standardValidate(schema, raw, false);
160
- }
161
- return response;
162
- }
163
- /**
164
- * Creates an HTTP method helper bound to a fetch function
165
- */
166
- function createMethod(fetchFn, method) {
167
- function methodFetch(resource, schemaOrOptions, optionsOrUndefined) {
168
- const [schema, options] = isStandardSchema(schemaOrOptions) ? [schemaOrOptions, optionsOrUndefined] : [void 0, schemaOrOptions];
169
- return fetchFn(resource, schema, {
170
- ...options,
171
- method
172
- });
173
- }
174
- return methodFetch;
175
- }
176
-
177
- //#endregion
178
- export { fetchInternal as n, mergeHeaders as r, createMethod as t };
179
- //# sourceMappingURL=utils-C1YvgnKM.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils-C1YvgnKM.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 // 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 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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"}