@zap-studio/fetch 0.5.3 → 0.5.4
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 +7 -0
- package/dist/headers.mjs +3 -4
- package/dist/headers.mjs.map +1 -1
- package/dist/internal.mjs +6 -3
- package/dist/internal.mjs.map +1 -1
- package/dist/request.d.mts.map +1 -1
- package/dist/request.mjs +2 -1
- package/dist/request.mjs.map +1 -1
- package/dist/url.mjs +8 -3
- package/dist/url.mjs.map +1 -1
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @zap-studio/fetch
|
|
2
2
|
|
|
3
|
+
## 0.5.4
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Added fetch ecosystem benchmarks and grouped benchmark output for easier cross-library comparisons.
|
|
8
|
+
- Applied small internal performance optimizations in request preparation, URL resolution, and header merging without changing public API behavior.
|
|
9
|
+
|
|
3
10
|
## 0.5.3
|
|
4
11
|
|
|
5
12
|
### Changed
|
package/dist/headers.mjs
CHANGED
|
@@ -22,11 +22,10 @@
|
|
|
22
22
|
*/
|
|
23
23
|
function mergeHeaders(base, override) {
|
|
24
24
|
if (!(base || override)) return;
|
|
25
|
+
if (!base) return new Headers(override);
|
|
26
|
+
if (!override) return new Headers(base);
|
|
25
27
|
const merged = new Headers(base);
|
|
26
|
-
|
|
27
|
-
const overrideHeaders = new Headers(override);
|
|
28
|
-
for (const [key, value] of overrideHeaders.entries()) merged.set(key, value);
|
|
29
|
-
}
|
|
28
|
+
for (const [key, value] of new Headers(override).entries()) merged.set(key, value);
|
|
30
29
|
return merged;
|
|
31
30
|
}
|
|
32
31
|
//#endregion
|
package/dist/headers.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.mjs","names":[],"sources":["../src/headers.ts"],"sourcesContent":["/**\n * Header utility helpers for request normalization and merging.\n *\n * @module @zap-studio/fetch/headers\n */\n\n/**\n * Merges two HeadersInit objects, with the second one taking precedence.\n *\n * @param base - Base/default headers.\n * @param override - Request-level override headers.\n * @returns A merged `Headers` object, or `undefined` when both inputs are empty.\n * @throws {TypeError} When either header input contains invalid header names or values.\n *\n * @example\n * const headers = mergeHeaders(\n * { Authorization: \"Bearer token\" },\n * { \"X-Trace-Id\": \"abc\" },\n * );\n *\n * console.log(headers?.get(\"Authorization\")); // Bearer token\n */\nexport function mergeHeaders(base?: HeadersInit, override?: HeadersInit): Headers | undefined {\n if (!(base || override)) {\n return;\n }\n\n
|
|
1
|
+
{"version":3,"file":"headers.mjs","names":[],"sources":["../src/headers.ts"],"sourcesContent":["/**\n * Header utility helpers for request normalization and merging.\n *\n * @module @zap-studio/fetch/headers\n */\n\n/**\n * Merges two HeadersInit objects, with the second one taking precedence.\n *\n * @param base - Base/default headers.\n * @param override - Request-level override headers.\n * @returns A merged `Headers` object, or `undefined` when both inputs are empty.\n * @throws {TypeError} When either header input contains invalid header names or values.\n *\n * @example\n * const headers = mergeHeaders(\n * { Authorization: \"Bearer token\" },\n * { \"X-Trace-Id\": \"abc\" },\n * );\n *\n * console.log(headers?.get(\"Authorization\")); // Bearer token\n */\nexport function mergeHeaders(base?: HeadersInit, override?: HeadersInit): Headers | undefined {\n if (!(base || override)) {\n return;\n }\n\n if (!base) {\n return new Headers(override);\n }\n\n if (!override) {\n return new Headers(base);\n }\n\n const merged = new Headers(base);\n for (const [key, value] of new Headers(override).entries()) {\n merged.set(key, value);\n }\n return merged;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,aAAa,MAAoB,UAA6C;AAC5F,KAAI,EAAE,QAAQ,UACZ;AAGF,KAAI,CAAC,KACH,QAAO,IAAI,QAAQ,SAAS;AAG9B,KAAI,CAAC,SACH,QAAO,IAAI,QAAQ,KAAK;CAG1B,MAAM,SAAS,IAAI,QAAQ,KAAK;AAChC,MAAK,MAAM,CAAC,KAAK,UAAU,IAAI,QAAQ,SAAS,CAAC,SAAS,CACxD,QAAO,IAAI,KAAK,MAAM;AAExB,QAAO"}
|
package/dist/internal.mjs
CHANGED
|
@@ -56,9 +56,12 @@ function prepareRequestInit(options, defaults) {
|
|
|
56
56
|
if (json !== void 0) {
|
|
57
57
|
if (init.body != null) throw new TypeError("Cannot provide both `body` and `json`.");
|
|
58
58
|
init.body = JSON.stringify(json);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
if (!init.headers) init.headers = new Headers({ "Content-Type": "application/json" });
|
|
60
|
+
else {
|
|
61
|
+
const headers = new Headers(init.headers);
|
|
62
|
+
if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
63
|
+
init.headers = headers;
|
|
64
|
+
}
|
|
62
65
|
}
|
|
63
66
|
return {
|
|
64
67
|
init,
|
package/dist/internal.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.mjs","names":[],"sources":["../src/internal.ts"],"sourcesContent":["/**\n * Internal request execution and option preparation utilities.\n *\n * @module @zap-studio/fetch/internal\n */\n\nimport type { StandardSchemaV1 } from \"@zap-studio/validation\";\nimport { standardValidate } from \"@zap-studio/validation\";\n\nimport { FetchError } from \"./errors.js\";\nimport { mergeHeaders } from \"./headers.js\";\nimport { normalizeRequest } from \"./request.js\";\nimport type { ExtendedRequestInit, FetchDefaults, FetchInput } from \"./types.js\";\nimport { resolveRequestUrl } from \"./url.js\";\n\n/**\n * Internal fetch implementation used by both $fetch and createFetch.\n *\n * This function normalizes request input, resolves final URL + query params,\n * executes `fetch`, optionally throws `FetchError`, and optionally validates\n * JSON response payloads using Standard Schema.\n *\n * @param input - Request URL, path, or Request object.\n * @param schema - Optional Standard Schema for response validation.\n * @param options - Optional request options and package-specific flags.\n * @param defaults - Effective client defaults.\n * @returns Raw `Response` when no schema is provided; otherwise validated output.\n * @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.\n * @throws {ValidationError} When a schema is provided, validation returns issues, and\n * `throwOnValidationError` is `true`.\n * @throws {TypeError} When both `body` and `json` are provided, when JSON request\n * serialization fails, when request construction fails, when headers/search params are\n * invalid, when `response.json()` cannot read the body, or when the runtime `fetch`\n * implementation rejects network-level failures as `TypeError`.\n * @throws {DOMException} When the runtime rejects an aborted request or response body read\n * as an `AbortError` DOMException.\n * @throws {SyntaxError} When a schema is provided and `response.json()` cannot parse the\n * response body.\n * @throws Any error thrown or rejected by the provided Standard Schema validator.\n */\nexport async function fetchInternal(\n input: FetchInput,\n schema: StandardSchemaV1 | undefined,\n options: ExtendedRequestInit | undefined,\n defaults: FetchDefaults,\n): Promise<unknown> {\n const request = normalizeRequest(input, options);\n const { init, searchParams, throwOnFetchError, throwOnValidationError } = prepareRequestInit(\n request.options,\n defaults,\n );\n const url = resolveRequestUrl(request.url, defaults, searchParams);\n const response = request.request\n ? await fetch(new Request(url, request.request), init)\n : await fetch(url, init);\n\n if (throwOnFetchError && !response.ok) {\n throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);\n }\n\n if (!schema) {\n return response;\n }\n\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/**\n * Normalizes request-level options into a final RequestInit payload and runtime flags.\n *\n * @param options - Request-level options.\n * @param defaults - Client-level defaults.\n * @returns Fully merged request init payload and effective runtime flags.\n */\nfunction prepareRequestInit(\n options: ExtendedRequestInit,\n defaults: FetchDefaults,\n): {\n init: RequestInit;\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined;\n throwOnFetchError: boolean;\n throwOnValidationError: boolean;\n} {\n const {\n headers,\n json,\n searchParams,\n throwOnFetchError = defaults.throwOnFetchError,\n throwOnValidationError = defaults.throwOnValidationError,\n ...rest\n } = options;\n\n const init = {\n ...rest,\n headers: mergeHeaders(defaults.headers, headers),\n } as RequestInit;\n\n if (json !== undefined) {\n if (init.body != null) {\n throw new TypeError(\"Cannot provide both `body` and `json`.\");\n }\n\n init.body = JSON.stringify(json);\n const headers = new Headers(init.headers);\n
|
|
1
|
+
{"version":3,"file":"internal.mjs","names":[],"sources":["../src/internal.ts"],"sourcesContent":["/**\n * Internal request execution and option preparation utilities.\n *\n * @module @zap-studio/fetch/internal\n */\n\nimport type { StandardSchemaV1 } from \"@zap-studio/validation\";\nimport { standardValidate } from \"@zap-studio/validation\";\n\nimport { FetchError } from \"./errors.js\";\nimport { mergeHeaders } from \"./headers.js\";\nimport { normalizeRequest } from \"./request.js\";\nimport type { ExtendedRequestInit, FetchDefaults, FetchInput } from \"./types.js\";\nimport { resolveRequestUrl } from \"./url.js\";\n\n/**\n * Internal fetch implementation used by both $fetch and createFetch.\n *\n * This function normalizes request input, resolves final URL + query params,\n * executes `fetch`, optionally throws `FetchError`, and optionally validates\n * JSON response payloads using Standard Schema.\n *\n * @param input - Request URL, path, or Request object.\n * @param schema - Optional Standard Schema for response validation.\n * @param options - Optional request options and package-specific flags.\n * @param defaults - Effective client defaults.\n * @returns Raw `Response` when no schema is provided; otherwise validated output.\n * @throws {FetchError} When `throwOnFetchError` is `true` and the response is not ok.\n * @throws {ValidationError} When a schema is provided, validation returns issues, and\n * `throwOnValidationError` is `true`.\n * @throws {TypeError} When both `body` and `json` are provided, when JSON request\n * serialization fails, when request construction fails, when headers/search params are\n * invalid, when `response.json()` cannot read the body, or when the runtime `fetch`\n * implementation rejects network-level failures as `TypeError`.\n * @throws {DOMException} When the runtime rejects an aborted request or response body read\n * as an `AbortError` DOMException.\n * @throws {SyntaxError} When a schema is provided and `response.json()` cannot parse the\n * response body.\n * @throws Any error thrown or rejected by the provided Standard Schema validator.\n */\nexport async function fetchInternal(\n input: FetchInput,\n schema: StandardSchemaV1 | undefined,\n options: ExtendedRequestInit | undefined,\n defaults: FetchDefaults,\n): Promise<unknown> {\n const request = normalizeRequest(input, options);\n const { init, searchParams, throwOnFetchError, throwOnValidationError } = prepareRequestInit(\n request.options,\n defaults,\n );\n const url = resolveRequestUrl(request.url, defaults, searchParams);\n const response = request.request\n ? await fetch(new Request(url, request.request), init)\n : await fetch(url, init);\n\n if (throwOnFetchError && !response.ok) {\n throw new FetchError(`HTTP ${response.status}: ${response.statusText}`, response);\n }\n\n if (!schema) {\n return response;\n }\n\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/**\n * Normalizes request-level options into a final RequestInit payload and runtime flags.\n *\n * @param options - Request-level options.\n * @param defaults - Client-level defaults.\n * @returns Fully merged request init payload and effective runtime flags.\n */\nfunction prepareRequestInit(\n options: ExtendedRequestInit,\n defaults: FetchDefaults,\n): {\n init: RequestInit;\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined;\n throwOnFetchError: boolean;\n throwOnValidationError: boolean;\n} {\n const {\n headers,\n json,\n searchParams,\n throwOnFetchError = defaults.throwOnFetchError,\n throwOnValidationError = defaults.throwOnValidationError,\n ...rest\n } = options;\n\n const init = {\n ...rest,\n headers: mergeHeaders(defaults.headers, headers),\n } as RequestInit;\n\n if (json !== undefined) {\n if (init.body != null) {\n throw new TypeError(\"Cannot provide both `body` and `json`.\");\n }\n\n init.body = JSON.stringify(json);\n if (!init.headers) {\n init.headers = new Headers({ \"Content-Type\": \"application/json\" });\n } else {\n const headers = new Headers(init.headers);\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n init.headers = headers;\n }\n }\n\n return {\n init,\n searchParams,\n throwOnFetchError,\n throwOnValidationError,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,eAAsB,cACpB,OACA,QACA,SACA,UACkB;CAClB,MAAM,UAAU,iBAAiB,OAAO,QAAQ;CAChD,MAAM,EAAE,MAAM,cAAc,mBAAmB,2BAA2B,mBACxE,QAAQ,SACR,SACD;CACD,MAAM,MAAM,kBAAkB,QAAQ,KAAK,UAAU,aAAa;CAClE,MAAM,WAAW,QAAQ,UACrB,MAAM,MAAM,IAAI,QAAQ,KAAK,QAAQ,QAAQ,EAAE,KAAK,GACpD,MAAM,MAAM,KAAK,KAAK;AAE1B,KAAI,qBAAqB,CAAC,SAAS,GACjC,OAAM,IAAI,WAAW,QAAQ,SAAS,OAAO,IAAI,SAAS,cAAc,SAAS;AAGnF,KAAI,CAAC,OACH,QAAO;CAGT,MAAM,MAAM,MAAM,SAAS,MAAM;AACjC,KAAI,uBACF,QAAO,iBAAiB,QAAQ,KAAK,EAAE,cAAc,MAAM,CAAC;AAE9D,QAAO,iBAAiB,QAAQ,KAAK,EAAE,cAAc,OAAO,CAAC;;;;;;;;;AAU/D,SAAS,mBACP,SACA,UAMA;CACA,MAAM,EACJ,SACA,MACA,cACA,oBAAoB,SAAS,mBAC7B,yBAAyB,SAAS,wBAClC,GAAG,SACD;CAEJ,MAAM,OAAO;EACX,GAAG;EACH,SAAS,aAAa,SAAS,SAAS,QAAQ;EACjD;AAED,KAAI,SAAS,KAAA,GAAW;AACtB,MAAI,KAAK,QAAQ,KACf,OAAM,IAAI,UAAU,yCAAyC;AAG/D,OAAK,OAAO,KAAK,UAAU,KAAK;AAChC,MAAI,CAAC,KAAK,QACR,MAAK,UAAU,IAAI,QAAQ,EAAE,gBAAgB,oBAAoB,CAAC;OAC7D;GACL,MAAM,UAAU,IAAI,QAAQ,KAAK,QAAQ;AACzC,OAAI,CAAC,QAAQ,IAAI,eAAe,CAC9B,SAAQ,IAAI,gBAAgB,mBAAmB;AAEjD,QAAK,UAAU;;;AAInB,QAAO;EACL;EACA;EACA;EACA;EACD"}
|
package/dist/request.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.mts","names":[],"sources":["../src/request.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"request.d.mts","names":[],"sources":["../src/request.ts"],"mappings":";;;;;AAkBA;;;;;UAAiB,iBAAA;EACf,GAAA;EACA,OAAA,GAAU,OAAA;EACV,OAAA,EAAS,mBAAA;AAAA;;AAeX;;;;;;;;;;;iBAAgB,gBAAA,CACd,KAAA,EAAO,UAAA,EACP,OAAA,GAAU,mBAAA,GACT,iBAAA"}
|
package/dist/request.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { mergeHeaders } from "./headers.mjs";
|
|
|
5
5
|
*
|
|
6
6
|
* @module @zap-studio/fetch/request
|
|
7
7
|
*/
|
|
8
|
+
const EMPTY_OPTIONS = {};
|
|
8
9
|
/**
|
|
9
10
|
* Normalizes fetch `input` and request-level options into a consistent internal shape.
|
|
10
11
|
*
|
|
@@ -20,7 +21,7 @@ import { mergeHeaders } from "./headers.mjs";
|
|
|
20
21
|
function normalizeRequest(input, options) {
|
|
21
22
|
if (!(input instanceof Request)) return {
|
|
22
23
|
url: input instanceof URL ? input.href : input,
|
|
23
|
-
options: options ??
|
|
24
|
+
options: options ?? EMPTY_OPTIONS
|
|
24
25
|
};
|
|
25
26
|
const request = new Request(input);
|
|
26
27
|
const { headers, ...rest } = options || {};
|
package/dist/request.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.mjs","names":[],"sources":["../src/request.ts"],"sourcesContent":["/**\n * Request normalization helpers for fetch `input` values.\n *\n * @module @zap-studio/fetch/request\n */\n\nimport { mergeHeaders } from \"./headers.js\";\nimport type { ExtendedRequestInit, FetchInput } from \"./types.js\";\n\n/**\n * Normalized representation used by internal request execution.\n *\n * - `url`: resolved string URL from the input (string or `URL`; `Request` uses `request.url`)\n * - `request`: original Request clone when input is a Request\n * - `options`: normalized request options merged with Request headers\n */\nexport interface NormalizedRequest {\n url: string;\n request?: Request;\n options: ExtendedRequestInit;\n}\n\n/**\n * Normalizes fetch `input` and request-level options into a consistent internal shape.\n *\n * @param input - Request URL/path or Request instance.\n * @param options - Optional request options.\n * @returns A normalized request structure for internal processing.\n * @throws {TypeError} When cloning a `Request` fails or the merged headers are invalid.\n *\n * @example\n * const normalized = normalizeRequest(\"/users\", { method: \"GET\" });\n * console.log(normalized.url); // \"/users\"\n */\nexport function normalizeRequest(\n input: FetchInput,\n options?: ExtendedRequestInit,\n): NormalizedRequest {\n if (!(input instanceof Request)) {\n const url = input instanceof URL ? input.href : input;\n return {\n url,\n options: options ??
|
|
1
|
+
{"version":3,"file":"request.mjs","names":[],"sources":["../src/request.ts"],"sourcesContent":["/**\n * Request normalization helpers for fetch `input` values.\n *\n * @module @zap-studio/fetch/request\n */\n\nimport { mergeHeaders } from \"./headers.js\";\nimport type { ExtendedRequestInit, FetchInput } from \"./types.js\";\n\nconst EMPTY_OPTIONS = {} as ExtendedRequestInit;\n\n/**\n * Normalized representation used by internal request execution.\n *\n * - `url`: resolved string URL from the input (string or `URL`; `Request` uses `request.url`)\n * - `request`: original Request clone when input is a Request\n * - `options`: normalized request options merged with Request headers\n */\nexport interface NormalizedRequest {\n url: string;\n request?: Request;\n options: ExtendedRequestInit;\n}\n\n/**\n * Normalizes fetch `input` and request-level options into a consistent internal shape.\n *\n * @param input - Request URL/path or Request instance.\n * @param options - Optional request options.\n * @returns A normalized request structure for internal processing.\n * @throws {TypeError} When cloning a `Request` fails or the merged headers are invalid.\n *\n * @example\n * const normalized = normalizeRequest(\"/users\", { method: \"GET\" });\n * console.log(normalized.url); // \"/users\"\n */\nexport function normalizeRequest(\n input: FetchInput,\n options?: ExtendedRequestInit,\n): NormalizedRequest {\n if (!(input instanceof Request)) {\n const url = input instanceof URL ? input.href : input;\n return {\n url,\n options: options ?? EMPTY_OPTIONS,\n };\n }\n\n const request = new Request(input);\n const { headers, ...rest } = options || {};\n const mergedHeaders = mergeHeaders(request.headers, headers);\n const normalizedOptions = { ...rest } as ExtendedRequestInit;\n\n if (mergedHeaders) {\n normalizedOptions.headers = mergedHeaders;\n }\n\n return {\n url: request.url,\n request,\n options: normalizedOptions,\n };\n}\n"],"mappings":";;;;;;;AASA,MAAM,gBAAgB,EAAE;;;;;;;;;;;;;AA2BxB,SAAgB,iBACd,OACA,SACmB;AACnB,KAAI,EAAE,iBAAiB,SAErB,QAAO;EACL,KAFU,iBAAiB,MAAM,MAAM,OAAO;EAG9C,SAAS,WAAW;EACrB;CAGH,MAAM,UAAU,IAAI,QAAQ,MAAM;CAClC,MAAM,EAAE,SAAS,GAAG,SAAS,WAAW,EAAE;CAC1C,MAAM,gBAAgB,aAAa,QAAQ,SAAS,QAAQ;CAC5D,MAAM,oBAAoB,EAAE,GAAG,MAAM;AAErC,KAAI,cACF,mBAAkB,UAAU;AAG9B,QAAO;EACL,KAAK,QAAQ;EACb;EACA,SAAS;EACV"}
|
package/dist/url.mjs
CHANGED
|
@@ -26,9 +26,14 @@ function resolveRequestUrl(resourceUrl, defaults, searchParams) {
|
|
|
26
26
|
* Resolves search params by applying default params, URL params, then request params.
|
|
27
27
|
*/
|
|
28
28
|
function resolveSearchParams(url, defaultSearchParams, searchParams) {
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
29
|
+
if (!defaultSearchParams && searchParams === void 0) return url;
|
|
30
|
+
const hashIndex = url.indexOf("#");
|
|
31
|
+
const hasFragment = hashIndex >= 0;
|
|
32
|
+
const urlWithoutHash = hasFragment ? url.slice(0, hashIndex) : url;
|
|
33
|
+
const hash = hasFragment ? url.slice(hashIndex + 1) : "";
|
|
34
|
+
const queryIndex = urlWithoutHash.indexOf("?");
|
|
35
|
+
const pathname = queryIndex >= 0 ? urlWithoutHash.slice(0, queryIndex) : urlWithoutHash;
|
|
36
|
+
const urlSearchParams = queryIndex >= 0 ? urlWithoutHash.slice(queryIndex + 1) : void 0;
|
|
32
37
|
const resolvedSearchParams = new URLSearchParams();
|
|
33
38
|
mergeSearchParams(resolvedSearchParams, defaultSearchParams);
|
|
34
39
|
mergeSearchParams(resolvedSearchParams, urlSearchParams);
|
package/dist/url.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.mjs","names":[],"sources":["../src/url.ts"],"sourcesContent":["/**\n * URL resolution and query merging utilities.\n *\n * @module @zap-studio/fetch/url\n */\n\nimport type { ExtendedRequestInit, FetchDefaults } from \"./types.js\";\n\n/**\n * Resolves final request URL by applying baseURL and layered search params.\n *\n * Search param precedence:\n * 1. `defaults.searchParams`\n * 2. search params already present in `resourceUrl`\n * 3. per-request `searchParams`\n *\n * @example\n * const finalUrl = resolveRequestUrl(\n * \"/users?page=2\",\n * { ...defaults, baseURL: \"https://api.example.com\", searchParams: { locale: \"en\" } },\n * { page: \"3\" },\n * );\n * // https://api.example.com/users?locale=en&page=3\n *\n * @throws {TypeError} When `baseURL` and `resourceUrl` cannot be resolved by\n * `URL`, or when default/per-request search params cannot be converted by\n * `URLSearchParams`.\n */\nexport function resolveRequestUrl(\n resourceUrl: string,\n defaults: FetchDefaults,\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined,\n): string {\n const url = defaults.baseURL\n ? new URL(resourceUrl, ensureTrailingSlash(defaults.baseURL)).toString()\n : resourceUrl;\n\n return resolveSearchParams(url, defaults.searchParams, searchParams);\n}\n\n/**\n * Resolves search params by applying default params, URL params, then request params.\n */\nfunction resolveSearchParams(\n url: string,\n defaultSearchParams: FetchDefaults[\"searchParams\"] | undefined,\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined,\n): string {\n const
|
|
1
|
+
{"version":3,"file":"url.mjs","names":[],"sources":["../src/url.ts"],"sourcesContent":["/**\n * URL resolution and query merging utilities.\n *\n * @module @zap-studio/fetch/url\n */\n\nimport type { ExtendedRequestInit, FetchDefaults } from \"./types.js\";\n\n/**\n * Resolves final request URL by applying baseURL and layered search params.\n *\n * Search param precedence:\n * 1. `defaults.searchParams`\n * 2. search params already present in `resourceUrl`\n * 3. per-request `searchParams`\n *\n * @example\n * const finalUrl = resolveRequestUrl(\n * \"/users?page=2\",\n * { ...defaults, baseURL: \"https://api.example.com\", searchParams: { locale: \"en\" } },\n * { page: \"3\" },\n * );\n * // https://api.example.com/users?locale=en&page=3\n *\n * @throws {TypeError} When `baseURL` and `resourceUrl` cannot be resolved by\n * `URL`, or when default/per-request search params cannot be converted by\n * `URLSearchParams`.\n */\nexport function resolveRequestUrl(\n resourceUrl: string,\n defaults: FetchDefaults,\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined,\n): string {\n const url = defaults.baseURL\n ? new URL(resourceUrl, ensureTrailingSlash(defaults.baseURL)).toString()\n : resourceUrl;\n\n return resolveSearchParams(url, defaults.searchParams, searchParams);\n}\n\n/**\n * Resolves search params by applying default params, URL params, then request params.\n */\nfunction resolveSearchParams(\n url: string,\n defaultSearchParams: FetchDefaults[\"searchParams\"] | undefined,\n searchParams: ExtendedRequestInit[\"searchParams\"] | undefined,\n): string {\n if (!defaultSearchParams && searchParams === undefined) {\n return url;\n }\n\n const hashIndex = url.indexOf(\"#\");\n const hasFragment = hashIndex >= 0;\n const urlWithoutHash = hasFragment ? url.slice(0, hashIndex) : url;\n const hash = hasFragment ? url.slice(hashIndex + 1) : \"\";\n const queryIndex = urlWithoutHash.indexOf(\"?\");\n const pathname = queryIndex >= 0 ? urlWithoutHash.slice(0, queryIndex) : urlWithoutHash;\n const urlSearchParams = queryIndex >= 0 ? urlWithoutHash.slice(queryIndex + 1) : undefined;\n const resolvedSearchParams = new URLSearchParams();\n\n mergeSearchParams(resolvedSearchParams, defaultSearchParams);\n mergeSearchParams(resolvedSearchParams, urlSearchParams);\n mergeSearchParams(resolvedSearchParams, searchParams);\n\n const resolvedSearch = resolvedSearchParams.toString();\n const fragmentSuffix = hasFragment ? `#${hash}` : \"\";\n\n if (!resolvedSearch) {\n return `${pathname}${fragmentSuffix}`;\n }\n\n return `${pathname}?${resolvedSearch}${fragmentSuffix}`;\n}\n\n/**\n * Ensures a URL has a trailing slash for relative URL resolution.\n */\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url : `${url}/`;\n}\n\n/**\n * Copies search params into target, overriding duplicate keys.\n */\nfunction mergeSearchParams(\n target: URLSearchParams,\n source: ExtendedRequestInit[\"searchParams\"] | undefined,\n): void {\n for (const [key, value] of new URLSearchParams(source)) {\n target.set(key, value);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,kBACd,aACA,UACA,cACQ;AAKR,QAAO,oBAJK,SAAS,UACjB,IAAI,IAAI,aAAa,oBAAoB,SAAS,QAAQ,CAAC,CAAC,UAAU,GACtE,aAE4B,SAAS,cAAc,aAAa;;;;;AAMtE,SAAS,oBACP,KACA,qBACA,cACQ;AACR,KAAI,CAAC,uBAAuB,iBAAiB,KAAA,EAC3C,QAAO;CAGT,MAAM,YAAY,IAAI,QAAQ,IAAI;CAClC,MAAM,cAAc,aAAa;CACjC,MAAM,iBAAiB,cAAc,IAAI,MAAM,GAAG,UAAU,GAAG;CAC/D,MAAM,OAAO,cAAc,IAAI,MAAM,YAAY,EAAE,GAAG;CACtD,MAAM,aAAa,eAAe,QAAQ,IAAI;CAC9C,MAAM,WAAW,cAAc,IAAI,eAAe,MAAM,GAAG,WAAW,GAAG;CACzE,MAAM,kBAAkB,cAAc,IAAI,eAAe,MAAM,aAAa,EAAE,GAAG,KAAA;CACjF,MAAM,uBAAuB,IAAI,iBAAiB;AAElD,mBAAkB,sBAAsB,oBAAoB;AAC5D,mBAAkB,sBAAsB,gBAAgB;AACxD,mBAAkB,sBAAsB,aAAa;CAErD,MAAM,iBAAiB,qBAAqB,UAAU;CACtD,MAAM,iBAAiB,cAAc,IAAI,SAAS;AAElD,KAAI,CAAC,eACH,QAAO,GAAG,WAAW;AAGvB,QAAO,GAAG,SAAS,GAAG,iBAAiB;;;;;AAMzC,SAAS,oBAAoB,KAAqB;AAChD,QAAO,IAAI,SAAS,IAAI,GAAG,MAAM,GAAG,IAAI;;;;;AAM1C,SAAS,kBACP,QACA,QACM;AACN,MAAK,MAAM,CAAC,KAAK,UAAU,IAAI,gBAAgB,OAAO,CACpD,QAAO,IAAI,KAAK,MAAM"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-studio/fetch",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A type-safe fetch wrapper for HTTP requests with runtime schema validation.",
|
|
6
6
|
"keywords": [
|
|
@@ -55,7 +55,11 @@
|
|
|
55
55
|
"@zap-studio/validation": "0.3.4"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
+
"@better-fetch/fetch": "^1.1.21",
|
|
58
59
|
"arktype": "^2.2.0",
|
|
60
|
+
"axios": "^1.15.2",
|
|
61
|
+
"ky": "^2.0.2",
|
|
62
|
+
"ofetch": "^1.5.1",
|
|
59
63
|
"typescript": "^6.0.3",
|
|
60
64
|
"valibot": "^1.3.1",
|
|
61
65
|
"vite-plus": "^0.1.19",
|