@fgrzl/fetch 1.1.0 → 1.3.0-alpha.3
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/README.md +41 -0
- package/dist/cjs/index.js +105 -9
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.min.js +1 -1
- package/dist/cjs/index.min.js.map +1 -1
- package/dist/index.d.ts +162 -1
- package/dist/index.js +103 -9
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,20 @@ interface FetchClientOptions {
|
|
|
59
59
|
* - 'omit': Never send cookies
|
|
60
60
|
*/
|
|
61
61
|
credentials?: RequestCredentials;
|
|
62
|
+
/**
|
|
63
|
+
* Base URL for relative requests.
|
|
64
|
+
*
|
|
65
|
+
* When set, all relative URLs (not starting with http:// or https://) will be
|
|
66
|
+
* prefixed with this base URL. Absolute URLs are used as-is.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const client = new FetchClient({ baseUrl: 'https://api.example.com' });
|
|
71
|
+
* await client.get('/users'); // → GET https://api.example.com/users
|
|
72
|
+
* await client.get('https://other-api.com/data'); // → GET https://other-api.com/data
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
baseUrl?: string;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
/**
|
|
@@ -115,12 +129,45 @@ type FetchMiddleware = (request: RequestInit & {
|
|
|
115
129
|
declare class FetchClient {
|
|
116
130
|
private middlewares;
|
|
117
131
|
private credentials;
|
|
132
|
+
private baseUrl;
|
|
118
133
|
constructor(config?: FetchClientOptions);
|
|
119
134
|
use(middleware: FetchMiddleware): this;
|
|
135
|
+
/**
|
|
136
|
+
* Set or update the base URL for this client instance.
|
|
137
|
+
*
|
|
138
|
+
* When a base URL is set, relative URLs will be resolved against it.
|
|
139
|
+
* Absolute URLs will continue to work unchanged.
|
|
140
|
+
*
|
|
141
|
+
* @param baseUrl - The base URL to set, or undefined to clear it
|
|
142
|
+
* @returns The client instance for method chaining
|
|
143
|
+
*
|
|
144
|
+
* @example Set base URL:
|
|
145
|
+
* ```typescript
|
|
146
|
+
* const client = new FetchClient();
|
|
147
|
+
* client.setBaseUrl('https://api.example.com');
|
|
148
|
+
*
|
|
149
|
+
* // Now relative URLs work
|
|
150
|
+
* await client.get('/users'); // → GET https://api.example.com/users
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* @example Chain with middleware:
|
|
154
|
+
* ```typescript
|
|
155
|
+
* const client = useProductionStack(new FetchClient())
|
|
156
|
+
* .setBaseUrl(process.env.API_BASE_URL);
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
setBaseUrl(baseUrl?: string): this;
|
|
120
160
|
request<T = unknown>(url: string, init?: RequestInit): Promise<FetchResponse<T>>;
|
|
121
161
|
private coreFetch;
|
|
122
162
|
private parseResponse;
|
|
123
163
|
private buildUrlWithParams;
|
|
164
|
+
/**
|
|
165
|
+
* Resolves a URL with the base URL if it's relative and base URL is configured
|
|
166
|
+
* @param url - The URL to resolve
|
|
167
|
+
* @returns The resolved URL
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
private resolveUrl;
|
|
124
171
|
/**
|
|
125
172
|
* HEAD request with query parameter support.
|
|
126
173
|
*
|
|
@@ -314,6 +361,93 @@ declare class NetworkError extends FetchError {
|
|
|
314
361
|
constructor(message: string, url: string, cause?: Error);
|
|
315
362
|
}
|
|
316
363
|
|
|
364
|
+
/**
|
|
365
|
+
* @fileoverview Query parameter utilities for FetchClient
|
|
366
|
+
*
|
|
367
|
+
* Provides utilities for building URL query strings from JavaScript objects
|
|
368
|
+
* with proper handling of arrays, undefined values, and URL encoding.
|
|
369
|
+
*/
|
|
370
|
+
/**
|
|
371
|
+
* Query parameter value types that can be converted to URL parameters.
|
|
372
|
+
* Arrays are handled specially with multiple parameter entries.
|
|
373
|
+
*/
|
|
374
|
+
type QueryValue = string | number | boolean | null | undefined | QueryValue[];
|
|
375
|
+
/**
|
|
376
|
+
* Object representing query parameters for URL construction.
|
|
377
|
+
*/
|
|
378
|
+
type QueryParams = Record<string, QueryValue>;
|
|
379
|
+
/**
|
|
380
|
+
* Builds a URL query string from a JavaScript object.
|
|
381
|
+
*
|
|
382
|
+
* Features:
|
|
383
|
+
* - ✅ Proper URL encoding via native URLSearchParams
|
|
384
|
+
* - ✅ Array handling with multiple parameter entries
|
|
385
|
+
* - ✅ Undefined value filtering (excluded from output)
|
|
386
|
+
* - ✅ Type coercion to strings for all values
|
|
387
|
+
*
|
|
388
|
+
* @param query - Object containing query parameters
|
|
389
|
+
* @returns URL-encoded query string (without leading '?')
|
|
390
|
+
*
|
|
391
|
+
* @example Basic usage:
|
|
392
|
+
* ```typescript
|
|
393
|
+
* buildQueryParams({ name: 'John', age: 30 })
|
|
394
|
+
* // → "name=John&age=30"
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* @example Array handling:
|
|
398
|
+
* ```typescript
|
|
399
|
+
* buildQueryParams({ tags: ['typescript', 'javascript'], active: true })
|
|
400
|
+
* // → "tags=typescript&tags=javascript&active=true"
|
|
401
|
+
* ```
|
|
402
|
+
*
|
|
403
|
+
* @example Undefined filtering:
|
|
404
|
+
* ```typescript
|
|
405
|
+
* buildQueryParams({ name: 'John', email: undefined, age: null })
|
|
406
|
+
* // → "name=John&age=null" (undefined excluded, null converted to string)
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* @example Real-world API usage:
|
|
410
|
+
* ```typescript
|
|
411
|
+
* const filters = {
|
|
412
|
+
* status: ['active', 'pending'],
|
|
413
|
+
* limit: 50,
|
|
414
|
+
* offset: 0,
|
|
415
|
+
* search: searchTerm || undefined // Conditionally included
|
|
416
|
+
* };
|
|
417
|
+
*
|
|
418
|
+
* const queryString = buildQueryParams(filters);
|
|
419
|
+
* // → "status=active&status=pending&limit=50&offset=0"
|
|
420
|
+
* // (search excluded if searchTerm is undefined)
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
declare function buildQueryParams(query: QueryParams): string;
|
|
424
|
+
/**
|
|
425
|
+
* Appends query parameters to a URL, handling existing query strings properly.
|
|
426
|
+
*
|
|
427
|
+
* @param baseUrl - The base URL to append parameters to
|
|
428
|
+
* @param query - Object containing query parameters
|
|
429
|
+
* @returns Complete URL with query parameters appended
|
|
430
|
+
*
|
|
431
|
+
* @example Basic URL building:
|
|
432
|
+
* ```typescript
|
|
433
|
+
* appendQueryParams('/api/users', { limit: 10, active: true })
|
|
434
|
+
* // → "/api/users?limit=10&active=true"
|
|
435
|
+
* ```
|
|
436
|
+
*
|
|
437
|
+
* @example Existing query parameters:
|
|
438
|
+
* ```typescript
|
|
439
|
+
* appendQueryParams('/api/users?sort=name', { limit: 10 })
|
|
440
|
+
* // → "/api/users?sort=name&limit=10"
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* @example Empty query object:
|
|
444
|
+
* ```typescript
|
|
445
|
+
* appendQueryParams('/api/users', {})
|
|
446
|
+
* // → "/api/users" (no change)
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
declare function appendQueryParams(baseUrl: string, query: QueryParams): string;
|
|
450
|
+
|
|
317
451
|
/**
|
|
318
452
|
* @fileoverview Authentication middleware types and configuration.
|
|
319
453
|
*/
|
|
@@ -1410,6 +1544,18 @@ declare function useBasicStack(client: FetchClient, config: {
|
|
|
1410
1544
|
* const activeUsers = await api.get('/api/users', { status: 'active', limit: 10 });
|
|
1411
1545
|
* ```
|
|
1412
1546
|
*
|
|
1547
|
+
* @example Set base URL for API-specific clients:
|
|
1548
|
+
* ```typescript
|
|
1549
|
+
* import api from '@fgrzl/fetch';
|
|
1550
|
+
*
|
|
1551
|
+
* // Configure base URL dynamically
|
|
1552
|
+
* api.setBaseUrl('https://api.example.com');
|
|
1553
|
+
*
|
|
1554
|
+
* // Now all relative URLs are prefixed automatically
|
|
1555
|
+
* const users = await api.get('/users'); // → GET https://api.example.com/users
|
|
1556
|
+
* const posts = await api.get('/posts'); // → GET https://api.example.com/posts
|
|
1557
|
+
* ```
|
|
1558
|
+
*
|
|
1413
1559
|
* @example Configure authentication:
|
|
1414
1560
|
* ```typescript
|
|
1415
1561
|
* import api from '@fgrzl/fetch';
|
|
@@ -1430,7 +1576,22 @@ declare function useBasicStack(client: FetchClient, config: {
|
|
|
1430
1576
|
* tokenProvider: () => getJWTToken()
|
|
1431
1577
|
* });
|
|
1432
1578
|
* ```
|
|
1579
|
+
*
|
|
1580
|
+
* @example Production-ready API client with base URL:
|
|
1581
|
+
* ```typescript
|
|
1582
|
+
* import { FetchClient, useProductionStack } from '@fgrzl/fetch';
|
|
1583
|
+
*
|
|
1584
|
+
* // One-liner production setup with base URL
|
|
1585
|
+
* const apiClient = useProductionStack(new FetchClient(), {
|
|
1586
|
+
* auth: { tokenProvider: () => getAuthToken() },
|
|
1587
|
+
* retry: { maxRetries: 3 },
|
|
1588
|
+
* logging: { level: 'info' }
|
|
1589
|
+
* }).setBaseUrl(process.env.API_BASE_URL || 'https://api.example.com');
|
|
1590
|
+
*
|
|
1591
|
+
* // Ready to use with full production features
|
|
1592
|
+
* const users = await apiClient.get('/users');
|
|
1593
|
+
* ```
|
|
1433
1594
|
*/
|
|
1434
1595
|
declare const api: FetchClient;
|
|
1435
1596
|
|
|
1436
|
-
export { type AuthTokenProvider, type AuthenticationOptions, type AuthorizationOptions, type CacheEntry, type CacheKeyGenerator, type CacheOptions, type CacheStorage, FetchClient, type FetchClientOptions, FetchError, type FetchResponse, HttpError, type FetchMiddleware as InterceptMiddleware, type LogLevel, type Logger, type LoggingOptions, NetworkError, type RateLimitAlgorithm, type RateLimitOptions, type RetryOptions, type UnauthorizedHandler, createAuthenticationMiddleware, createAuthorizationMiddleware, createCacheMiddleware, createLoggingMiddleware, createRateLimitMiddleware, createRetryMiddleware, api as default, useAuthentication, useAuthorization, useBasicStack, useCSRF, useCache, useDevelopmentStack, useLogging, useProductionStack, useRateLimit, useRetry };
|
|
1597
|
+
export { type AuthTokenProvider, type AuthenticationOptions, type AuthorizationOptions, type CacheEntry, type CacheKeyGenerator, type CacheOptions, type CacheStorage, FetchClient, type FetchClientOptions, FetchError, type FetchResponse, HttpError, type FetchMiddleware as InterceptMiddleware, type LogLevel, type Logger, type LoggingOptions, NetworkError, type QueryParams, type QueryValue, type RateLimitAlgorithm, type RateLimitOptions, type RetryOptions, type UnauthorizedHandler, appendQueryParams, buildQueryParams, createAuthenticationMiddleware, createAuthorizationMiddleware, createCacheMiddleware, createLoggingMiddleware, createRateLimitMiddleware, createRetryMiddleware, api as default, useAuthentication, useAuthorization, useBasicStack, useCSRF, useCache, useDevelopmentStack, useLogging, useProductionStack, useRateLimit, useRetry };
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,46 @@ var FetchClient = class {
|
|
|
3
3
|
constructor(config = {}) {
|
|
4
4
|
this.middlewares = [];
|
|
5
5
|
this.credentials = config.credentials ?? "same-origin";
|
|
6
|
+
this.baseUrl = config.baseUrl;
|
|
6
7
|
}
|
|
7
8
|
use(middleware) {
|
|
8
9
|
this.middlewares.push(middleware);
|
|
9
10
|
return this;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Set or update the base URL for this client instance.
|
|
14
|
+
*
|
|
15
|
+
* When a base URL is set, relative URLs will be resolved against it.
|
|
16
|
+
* Absolute URLs will continue to work unchanged.
|
|
17
|
+
*
|
|
18
|
+
* @param baseUrl - The base URL to set, or undefined to clear it
|
|
19
|
+
* @returns The client instance for method chaining
|
|
20
|
+
*
|
|
21
|
+
* @example Set base URL:
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const client = new FetchClient();
|
|
24
|
+
* client.setBaseUrl('https://api.example.com');
|
|
25
|
+
*
|
|
26
|
+
* // Now relative URLs work
|
|
27
|
+
* await client.get('/users'); // → GET https://api.example.com/users
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Chain with middleware:
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const client = useProductionStack(new FetchClient())
|
|
33
|
+
* .setBaseUrl(process.env.API_BASE_URL);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
setBaseUrl(baseUrl) {
|
|
37
|
+
this.baseUrl = baseUrl;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
11
40
|
async request(url, init = {}) {
|
|
41
|
+
const resolvedUrl = this.resolveUrl(url);
|
|
12
42
|
let index = 0;
|
|
13
43
|
const execute = async (request) => {
|
|
14
|
-
const currentRequest = request || { ...init, url };
|
|
15
|
-
const currentUrl = currentRequest.url ||
|
|
44
|
+
const currentRequest = request || { ...init, url: resolvedUrl };
|
|
45
|
+
const currentUrl = currentRequest.url || resolvedUrl;
|
|
16
46
|
if (index >= this.middlewares.length) {
|
|
17
47
|
const { url: _, ...requestInit } = currentRequest;
|
|
18
48
|
return this.coreFetch(requestInit, currentUrl);
|
|
@@ -96,20 +126,48 @@ var FetchClient = class {
|
|
|
96
126
|
if (!params) {
|
|
97
127
|
return url;
|
|
98
128
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
129
|
+
const resolvedUrl = this.resolveUrl(url);
|
|
130
|
+
if (!resolvedUrl.startsWith("http://") && !resolvedUrl.startsWith("https://") && !resolvedUrl.startsWith("//")) {
|
|
131
|
+
const searchParams = new URLSearchParams();
|
|
132
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
133
|
+
if (value !== void 0 && value !== null) {
|
|
134
|
+
searchParams.set(key, String(value));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
const queryString = searchParams.toString();
|
|
138
|
+
return queryString ? `${resolvedUrl}?${queryString}` : resolvedUrl;
|
|
139
|
+
}
|
|
140
|
+
const urlObj = new URL(resolvedUrl);
|
|
103
141
|
Object.entries(params).forEach(([key, value]) => {
|
|
104
142
|
if (value !== void 0 && value !== null) {
|
|
105
143
|
urlObj.searchParams.set(key, String(value));
|
|
106
144
|
}
|
|
107
145
|
});
|
|
108
|
-
if (!url.startsWith("http")) {
|
|
109
|
-
return urlObj.pathname + urlObj.search;
|
|
110
|
-
}
|
|
111
146
|
return urlObj.toString();
|
|
112
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolves a URL with the base URL if it's relative and base URL is configured
|
|
150
|
+
* @param url - The URL to resolve
|
|
151
|
+
* @returns The resolved URL
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
resolveUrl(url) {
|
|
155
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
|
|
156
|
+
return url;
|
|
157
|
+
}
|
|
158
|
+
if (!this.baseUrl) {
|
|
159
|
+
return url;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const baseUrl = new URL(this.baseUrl);
|
|
163
|
+
const resolvedUrl = new URL(url, baseUrl);
|
|
164
|
+
return resolvedUrl.toString();
|
|
165
|
+
} catch {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Invalid URL: Unable to resolve "${url}" with baseUrl "${this.baseUrl}"`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
113
171
|
// 🎯 PIT OF SUCCESS: Convenience methods with smart defaults
|
|
114
172
|
/**
|
|
115
173
|
* HEAD request with query parameter support.
|
|
@@ -1022,6 +1080,40 @@ var NetworkError = class extends FetchError {
|
|
|
1022
1080
|
}
|
|
1023
1081
|
};
|
|
1024
1082
|
|
|
1083
|
+
// src/client/query.ts
|
|
1084
|
+
function buildQueryParams(query) {
|
|
1085
|
+
const params = new URLSearchParams();
|
|
1086
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1087
|
+
if (value !== void 0) {
|
|
1088
|
+
if (Array.isArray(value)) {
|
|
1089
|
+
value.forEach((item) => {
|
|
1090
|
+
if (item !== void 0) {
|
|
1091
|
+
params.append(key, String(item));
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
} else {
|
|
1095
|
+
params.set(key, String(value));
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return params.toString();
|
|
1100
|
+
}
|
|
1101
|
+
function appendQueryParams(baseUrl, query) {
|
|
1102
|
+
const queryString = buildQueryParams(query);
|
|
1103
|
+
if (!queryString) {
|
|
1104
|
+
return baseUrl;
|
|
1105
|
+
}
|
|
1106
|
+
const fragmentIndex = baseUrl.indexOf("#");
|
|
1107
|
+
if (fragmentIndex !== -1) {
|
|
1108
|
+
const urlPart = baseUrl.substring(0, fragmentIndex);
|
|
1109
|
+
const fragmentPart = baseUrl.substring(fragmentIndex);
|
|
1110
|
+
const separator2 = urlPart.includes("?") ? "&" : "?";
|
|
1111
|
+
return `${urlPart}${separator2}${queryString}${fragmentPart}`;
|
|
1112
|
+
}
|
|
1113
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
1114
|
+
return `${baseUrl}${separator}${queryString}`;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1025
1117
|
// src/index.ts
|
|
1026
1118
|
var api = useProductionStack(
|
|
1027
1119
|
new FetchClient({
|
|
@@ -1056,6 +1148,8 @@ export {
|
|
|
1056
1148
|
FetchError,
|
|
1057
1149
|
HttpError,
|
|
1058
1150
|
NetworkError,
|
|
1151
|
+
appendQueryParams,
|
|
1152
|
+
buildQueryParams,
|
|
1059
1153
|
createAuthenticationMiddleware,
|
|
1060
1154
|
createAuthorizationMiddleware,
|
|
1061
1155
|
createCacheMiddleware,
|