@aurelia/fetch-client
Advanced tools
Comparing version 2.0.1-dev.202401261010 to 2.0.1-dev.202402130428
@@ -6,2 +6,19 @@ # Change Log | ||
<a name="2.0.0-beta.11"></a> | ||
# 2.0.0-beta.11 (2024-02-13) | ||
### Features: | ||
* **state:** support redux devtools for the state plugin (#1888) ([bd07160](https://github.com/aurelia/aurelia/commit/bd07160)) | ||
### Bug Fixes: | ||
* ***:** upgrade rollup, tweak build scripts ([bd07160](https://github.com/aurelia/aurelia/commit/bd07160)) | ||
### Refactorings: | ||
* **fetch-client:** cleanup, add tests, tweak doc & prepare cache interceptor (#1756) ([a931dac](https://github.com/aurelia/aurelia/commit/a931dac)) | ||
<a name="2.0.0-beta.10"></a> | ||
@@ -8,0 +25,0 @@ # 2.0.0-beta.10 (2024-01-26) |
@@ -1,2 +0,3 @@ | ||
import { Interceptor, RetryConfiguration } from './interfaces'; | ||
import { IRetryConfiguration } from './interceptors'; | ||
import { IFetchInterceptor } from './interfaces'; | ||
/** | ||
@@ -20,3 +21,3 @@ * A class for configuring HttpClients. | ||
*/ | ||
interceptors: Interceptor[]; | ||
interceptors: IFetchInterceptor[]; | ||
dispatcher: Node | null; | ||
@@ -50,3 +51,3 @@ /** | ||
*/ | ||
withInterceptor(interceptor: Interceptor): HttpClientConfiguration; | ||
withInterceptor(interceptor: IFetchInterceptor): HttpClientConfiguration; | ||
/** | ||
@@ -72,5 +73,5 @@ * Applies a configuration that addresses common application needs, including | ||
rejectErrorResponses(): HttpClientConfiguration; | ||
withRetry(config?: RetryConfiguration): HttpClientConfiguration; | ||
withRetry(config?: IRetryConfiguration): HttpClientConfiguration; | ||
withDispatcher(dispatcher: Node): HttpClientConfiguration; | ||
} | ||
//# sourceMappingURL=http-client-configuration.d.ts.map |
import { HttpClientConfiguration } from './http-client-configuration'; | ||
import { Interceptor } from './interfaces'; | ||
import { IFetchInterceptor } from './interfaces'; | ||
/** | ||
* An interface to resolve what fetch function will be used for the http client | ||
* Default to the global fetch function via global `fetch` variable. | ||
*/ | ||
export declare const IFetchFn: import("@aurelia/kernel").InterfaceSymbol<typeof fetch>; | ||
export declare const IHttpClient: import("@aurelia/kernel").InterfaceSymbol<IHttpClient>; | ||
@@ -34,9 +39,4 @@ export interface IHttpClient extends HttpClient { | ||
*/ | ||
interceptors: Interceptor[]; | ||
dispatcher: Node | null; | ||
get interceptors(): IFetchInterceptor[]; | ||
/** | ||
* Creates an instance of HttpClient. | ||
*/ | ||
constructor(); | ||
/** | ||
* Configure this client with default settings to be used by all requests. | ||
@@ -49,3 +49,3 @@ * | ||
*/ | ||
configure(config: RequestInit | ((config: HttpClientConfiguration) => HttpClientConfiguration) | HttpClientConfiguration): HttpClient; | ||
configure(config: RequestInit | ((config: HttpClientConfiguration) => HttpClientConfiguration | void) | HttpClientConfiguration): HttpClient; | ||
/** | ||
@@ -66,2 +66,5 @@ * Starts the process of fetching a resource. Default configuration parameters | ||
fetch(input: Request | string, init?: RequestInit): Promise<Response>; | ||
/** | ||
* Creates a new Request object using the current configuration of this http client | ||
*/ | ||
buildRequest(input: string | Request, init: RequestInit | undefined): Request; | ||
@@ -122,9 +125,22 @@ /** | ||
delete(input: Request | string, body?: BodyInit, init?: RequestInit): Promise<Response>; | ||
private trackRequestStart; | ||
private trackRequestEnd; | ||
/** | ||
* Dispose and cleanup used resources of this client. | ||
*/ | ||
dispose(): void; | ||
private processRequest; | ||
private processResponse; | ||
private applyInterceptors; | ||
private callFetch; | ||
} | ||
/** | ||
* A lookup containing events used by HttpClient. | ||
*/ | ||
export declare const HttpClientEvent: Readonly<{ | ||
/** | ||
* Event to be triggered when a request is sent. | ||
*/ | ||
started: "aurelia-fetch-client-request-started"; | ||
/** | ||
* Event to be triggered when a request is completed. | ||
*/ | ||
drained: "aurelia-fetch-client-requests-drained"; | ||
}>; | ||
//# sourceMappingURL=http-client.d.ts.map |
@@ -1,6 +0,6 @@ | ||
export { type Interceptor, type RetryConfiguration, type RetryableRequest, type ValidInterceptorMethodName } from './interfaces'; | ||
export { json } from './util'; | ||
export { retryStrategy, RetryInterceptor } from './retry-interceptor'; | ||
export { type IFetchInterceptor, } from './interfaces'; | ||
export { json } from './utilities-fetch-client'; | ||
export { type ICacheEventData, type ICacheConfiguration, CacheEvent, CacheInterceptor, type ICacheItem, CacheService, ICacheService, ICacheStorage, MemoryStorage, BrowserIndexDBStorage, BrowserLocalStorage, BrowserSessionStorage, RetryInterceptor, RetryStrategy, type IRetryConfiguration, type IRetryableRequest, } from './interceptors'; | ||
export { HttpClientConfiguration } from './http-client-configuration'; | ||
export { HttpClient, IHttpClient } from './http-client'; | ||
export { IFetchFn, HttpClient, IHttpClient, HttpClientEvent } from './http-client'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -6,3 +6,3 @@ import { HttpClient } from './http-client'; | ||
*/ | ||
export interface Interceptor { | ||
export interface IFetchInterceptor { | ||
/** | ||
@@ -30,6 +30,8 @@ * Called with the request before it is sent. Request interceptors can modify and | ||
* | ||
* If a Request object was returned at the end of a response interceptor chain, it will rerun the request cycle with that Request object. | ||
* | ||
* @param response - The response. | ||
* @returns The response; or a Promise for one. | ||
*/ | ||
response?(response: Response, request?: Request): Response | Promise<Response>; | ||
response?(response: Response, request?: Request): Request | Response | Promise<Request | Response>; | ||
/** | ||
@@ -40,2 +42,4 @@ * Handles fetch errors and errors generated by previous interceptors. This | ||
* | ||
* If a Request object was returned at the end of a responseError interceptor chain, it will rerun the request cycle with that Request object. | ||
* | ||
* @param error - The rejection value from the fetch request or from a | ||
@@ -46,18 +50,7 @@ * previous interceptor. | ||
responseError?(error: unknown, request?: Request, httpClient?: HttpClient): Response | Promise<Response>; | ||
/** | ||
* Optional. Called when the owning http client is disposed for cleanup purposes. | ||
*/ | ||
dispose?(): void; | ||
} | ||
export type ValidInterceptorMethodName = keyof Interceptor; | ||
export type RetryableRequest = Request & { | ||
retryConfig?: RetryConfiguration; | ||
}; | ||
export interface RetryConfiguration { | ||
maxRetries: number; | ||
interval?: number; | ||
strategy?: number | ((retryCount: number) => number); | ||
minRandomInterval?: number; | ||
maxRandomInterval?: number; | ||
counter?: number; | ||
requestClone?: Request; | ||
doRetry?(response: Response, request: Request): boolean | Promise<boolean>; | ||
beforeRetry?(request: Request, client: HttpClient): Request | Promise<Request>; | ||
} | ||
//# sourceMappingURL=interfaces.d.ts.map |
{ | ||
"name": "@aurelia/fetch-client", | ||
"version": "2.0.1-dev.202401261010", | ||
"version": "2.0.1-dev.202402130428", | ||
"main": "dist/cjs/index.cjs", | ||
@@ -56,4 +56,4 @@ "module": "dist/esm/index.mjs", | ||
"dependencies": { | ||
"@aurelia/kernel": "2.0.1-dev.202401261010", | ||
"@aurelia/metadata": "2.0.1-dev.202401261010" | ||
"@aurelia/kernel": "2.0.1-dev.202402130428", | ||
"@aurelia/metadata": "2.0.1-dev.202402130428" | ||
}, | ||
@@ -60,0 +60,0 @@ "devDependencies": { |
@@ -1,3 +0,4 @@ | ||
import { Interceptor, RetryConfiguration } from './interfaces'; | ||
import { RetryInterceptor } from './retry-interceptor'; | ||
import { IContainer, resolve } from '@aurelia/kernel'; | ||
import { IRetryConfiguration, RetryInterceptor } from './interceptors'; | ||
import { IFetchInterceptor } from './interfaces'; | ||
@@ -24,6 +25,9 @@ /** | ||
*/ | ||
public interceptors: Interceptor[] = []; | ||
public interceptors: IFetchInterceptor[] = []; | ||
public dispatcher: Node | null = null; | ||
/** @internal */ | ||
private readonly _container = resolve(IContainer); | ||
/** | ||
@@ -64,3 +68,3 @@ * Sets the baseUrl. | ||
*/ | ||
public withInterceptor(interceptor: Interceptor): HttpClientConfiguration { | ||
public withInterceptor(interceptor: IFetchInterceptor): HttpClientConfiguration { | ||
this.interceptors.push(interceptor); | ||
@@ -98,4 +102,4 @@ return this; | ||
public withRetry(config?: RetryConfiguration): HttpClientConfiguration { | ||
const interceptor: Interceptor = new RetryInterceptor(config); | ||
public withRetry(config?: IRetryConfiguration): HttpClientConfiguration { | ||
const interceptor = this._container.invoke(RetryInterceptor, [config]); | ||
@@ -105,2 +109,8 @@ return this.withInterceptor(interceptor); | ||
// public withCache(config?: ICacheConfiguration): HttpClientConfiguration { | ||
// const interceptor = this._container.invoke(CacheInterceptor, [config]); | ||
// return this.withInterceptor(interceptor); | ||
// } | ||
public withDispatcher(dispatcher: Node): HttpClientConfiguration { | ||
@@ -107,0 +117,0 @@ this.dispatcher = dispatcher; |
@@ -1,9 +0,33 @@ | ||
import { DI, IIndexable } from '@aurelia/kernel'; | ||
import { DI, IContainer, IIndexable, Registration, resolve } from '@aurelia/kernel'; | ||
import { HttpClientConfiguration } from './http-client-configuration'; | ||
import { Interceptor, ValidInterceptorMethodName } from './interfaces'; | ||
import { RetryInterceptor } from './retry-interceptor'; | ||
import { IFetchInterceptor } from './interfaces'; | ||
import { RetryInterceptor } from './interceptors'; | ||
const absoluteUrlRegexp = /^([a-z][a-z0-9+\-.]*:)?\/\//i; | ||
export const IHttpClient = /*@__PURE__*/DI.createInterface<IHttpClient>('IHttpClient', x => x.singleton(HttpClient)); | ||
/** | ||
* An interface to resolve what fetch function will be used for the http client | ||
* Default to the global fetch function via global `fetch` variable. | ||
*/ | ||
export const IFetchFn = /*@__PURE__*/DI.createInterface<typeof fetch>('fetch', x => { | ||
if (typeof fetch !== 'function') { | ||
throw new Error('Could not resolve fetch function. Please provide a fetch function implementation or a polyfill for the global fetch function.'); | ||
} | ||
return x.instance(fetch); | ||
}); | ||
export const IHttpClient = /*@__PURE__*/DI.createInterface<IHttpClient>('IHttpClient', x => x.cachedCallback((handler) => { | ||
// reason for this gymnastic is because the way fetch-client is used in applications | ||
// there's no registration, there's only direct usage | ||
// which means application won't have the opportunity to register IHttpClient and HttpClient properly | ||
// app may inject HttpClient in some places and IHttpClient in some other | ||
// so we need to make sure HttpClient and IHttpClient are the same instance | ||
if (handler.has(HttpClient, false)) { | ||
return handler.get(HttpClient); | ||
} | ||
const client = handler.invoke(HttpClient); | ||
handler.register(Registration.instance(HttpClient, client)); | ||
return client; | ||
})); | ||
export interface IHttpClient extends HttpClient {} | ||
@@ -18,3 +42,3 @@ /** | ||
*/ | ||
public activeRequestCount: number; | ||
public activeRequestCount: number = 0; | ||
@@ -24,3 +48,3 @@ /** | ||
*/ | ||
public isRequesting: boolean; | ||
public isRequesting: boolean = false; | ||
@@ -30,3 +54,3 @@ /** | ||
*/ | ||
public isConfigured: boolean; | ||
public isConfigured: boolean = false; | ||
@@ -36,3 +60,3 @@ /** | ||
*/ | ||
public baseUrl: string; | ||
public baseUrl: string = ''; | ||
@@ -42,23 +66,24 @@ /** | ||
*/ | ||
public defaults: RequestInit | null; | ||
public defaults: RequestInit | null = null; | ||
/** | ||
* The interceptors to be run during requests. | ||
* @internal | ||
*/ | ||
public interceptors: Interceptor[]; | ||
private _interceptors: IFetchInterceptor[] = []; | ||
public dispatcher: Node | null = null; | ||
/** | ||
* Creates an instance of HttpClient. | ||
* The interceptors to be run during requests. | ||
*/ | ||
public constructor() { | ||
this.activeRequestCount = 0; | ||
this.isRequesting = false; | ||
this.isConfigured = false; | ||
this.baseUrl = ''; | ||
this.defaults = null; | ||
this.interceptors = []; | ||
public get interceptors(): IFetchInterceptor[] { | ||
return this._interceptors.slice(0); | ||
} | ||
/** @internal */ | ||
private _dispatcher: Node | null = null; | ||
/** @internal */ | ||
private readonly _container = resolve(IContainer); | ||
/** @internal */ | ||
private readonly _fetchFn = resolve(IFetchFn); | ||
/** | ||
@@ -72,4 +97,3 @@ * Configure this client with default settings to be used by all requests. | ||
*/ | ||
public configure(config: RequestInit | ((config: HttpClientConfiguration) => HttpClientConfiguration) | HttpClientConfiguration): HttpClient { | ||
public configure(config: RequestInit | ((config: HttpClientConfiguration) => HttpClientConfiguration | void) | HttpClientConfiguration): HttpClient { | ||
let normalizedConfig: HttpClientConfiguration; | ||
@@ -81,20 +105,25 @@ | ||
} else if (typeof config === 'function') { | ||
normalizedConfig = new HttpClientConfiguration(); | ||
normalizedConfig = this._container.invoke(HttpClientConfiguration); | ||
normalizedConfig.baseUrl = this.baseUrl; | ||
normalizedConfig.defaults = { ...this.defaults }; | ||
normalizedConfig.interceptors = this.interceptors; | ||
normalizedConfig.dispatcher = this.dispatcher; | ||
normalizedConfig.interceptors = this._interceptors; | ||
normalizedConfig.dispatcher = this._dispatcher; | ||
const c = config(normalizedConfig); | ||
if (Object.prototype.isPrototypeOf.call(HttpClientConfiguration.prototype, c)) { | ||
normalizedConfig = c; | ||
if (c != null) { | ||
if (typeof c === 'object') { | ||
normalizedConfig = c; | ||
} else { | ||
throw new Error(`The config callback did not return a valid HttpClientConfiguration like instance. Received ${typeof c}`); | ||
} | ||
} | ||
} else { | ||
throw new Error('invalid config'); | ||
throw new Error(`invalid config, expecting a function or an object, received ${typeof config}`); | ||
} | ||
const defaults = normalizedConfig.defaults; | ||
if (defaults !== undefined && Object.prototype.isPrototypeOf.call(Headers.prototype, defaults.headers as HeadersInit)) { | ||
if (defaults?.headers instanceof Headers) { | ||
// Headers instances are not iterable in all browsers. Require a plain | ||
// object here to allow default headers to be merged into request headers. | ||
// extract throwing error into an utility function | ||
throw new Error('Default headers must be a plain object.'); | ||
@@ -104,10 +133,9 @@ } | ||
const interceptors = normalizedConfig.interceptors; | ||
if (interceptors !== undefined && interceptors.length) { | ||
if (interceptors?.length > 0) { | ||
// find if there is a RetryInterceptor | ||
if (interceptors.filter(x => Object.prototype.isPrototypeOf.call(RetryInterceptor.prototype, x)).length > 1) { | ||
if (interceptors.filter(x => x instanceof RetryInterceptor).length > 1) { | ||
throw new Error('Only one RetryInterceptor is allowed.'); | ||
} | ||
const retryInterceptorIndex = interceptors.findIndex(x => Object.prototype.isPrototypeOf.call(RetryInterceptor.prototype, x)); | ||
const retryInterceptorIndex = interceptors.findIndex(x => x instanceof RetryInterceptor); | ||
@@ -117,2 +145,15 @@ if (retryInterceptorIndex >= 0 && retryInterceptorIndex !== interceptors.length - 1) { | ||
} | ||
// const cacheInterceptorIndex = interceptors.findIndex(x => x instanceof CacheInterceptor); | ||
// if (cacheInterceptorIndex >= 0) { | ||
// if (retryInterceptorIndex > 0) { | ||
// if (cacheInterceptorIndex < retryInterceptorIndex - 1) { | ||
// throw new Error('The cache interceptor must be defined before the retry interceptor.'); | ||
// } | ||
// } else { | ||
// if (cacheInterceptorIndex !== interceptors.length - 1) { | ||
// throw new Error('The cache interceptor is only allowed as the last interceptor or second last before the retry interceptor'); | ||
// } | ||
// } | ||
// } | ||
} | ||
@@ -122,4 +163,4 @@ | ||
this.defaults = defaults; | ||
this.interceptors = normalizedConfig.interceptors !== undefined ? normalizedConfig.interceptors : []; | ||
this.dispatcher = normalizedConfig.dispatcher; | ||
this._interceptors = normalizedConfig.interceptors ?? []; | ||
this._dispatcher = normalizedConfig.dispatcher; | ||
this.isConfigured = true; | ||
@@ -145,32 +186,35 @@ | ||
public fetch(input: Request | string, init?: RequestInit): Promise<Response> { | ||
this.trackRequestStart(); | ||
this._trackRequestStart(); | ||
let request = this.buildRequest(input, init); | ||
return this.processRequest(request, this.interceptors).then(result => { | ||
let response: Promise<Response>; | ||
return this.processRequest(request, this._interceptors) | ||
.then(result => { | ||
let response: Promise<Response>; | ||
if (Object.prototype.isPrototypeOf.call(Response.prototype, result)) { | ||
response = Promise.resolve(result as Response); | ||
} else if (Object.prototype.isPrototypeOf.call(Request.prototype, result)) { | ||
request = result as Request; | ||
response = fetch(request); | ||
} else { | ||
throw new Error(`An invalid result was returned by the interceptor chain. Expected a Request or Response instance, but got [${result}]`); | ||
} | ||
if (result instanceof Response) { | ||
response = Promise.resolve(result); | ||
} else if (result instanceof Request) { | ||
request = result; | ||
// if called directly, context of the fetch fn will be this HttpClient instance | ||
// which will throw illegal invokcation | ||
response = this._fetchFn.call(void 0, request); | ||
} else { | ||
throw new Error(`An invalid result was returned by the interceptor chain. Expected a Request or Response instance, but got [${result}]`); | ||
} | ||
return this.processResponse(response, this.interceptors, request); | ||
}) | ||
return this.processResponse(response, this._interceptors, request); | ||
}) | ||
.then(result => { | ||
if (Object.prototype.isPrototypeOf.call(Request.prototype, result)) { | ||
return this.fetch(result as Request); | ||
if (result instanceof Request) { | ||
return this.fetch(result); | ||
} | ||
return result as Response; | ||
return result; | ||
}) | ||
.then( | ||
result => { | ||
this.trackRequestEnd(); | ||
this._trackRequestEnd(); | ||
return result; | ||
}, | ||
error => { | ||
this.trackRequestEnd(); | ||
this._trackRequestEnd(); | ||
throw error; | ||
@@ -181,4 +225,7 @@ } | ||
/** | ||
* Creates a new Request object using the current configuration of this http client | ||
*/ | ||
public buildRequest(input: string | Request, init: RequestInit | undefined): Request { | ||
const defaults = this.defaults !== null ? this.defaults : {}; | ||
const defaults = this.defaults ?? {}; | ||
let request: Request; | ||
@@ -189,4 +236,4 @@ let body: unknown; | ||
const parsedDefaultHeaders = parseHeaderValues(defaults.headers as IIndexable); | ||
if (Object.prototype.isPrototypeOf.call(Request.prototype, input)) { | ||
request = input as Request; | ||
if (input instanceof Request) { | ||
request = input; | ||
requestContentType = new Headers(request.headers).get('Content-Type'); | ||
@@ -201,6 +248,11 @@ } else { | ||
requestContentType = new Headers(requestInit.headers as Headers).get('Content-Type'); | ||
request = new Request(getRequestUrl(this.baseUrl, input as string), requestInit); | ||
request = new Request(getRequestUrl(this.baseUrl, input), requestInit); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions | ||
if (!requestContentType) { | ||
if (new Headers(parsedDefaultHeaders).has('content-type')) { | ||
if (__DEV__) { | ||
// eslint-disable-next-line no-console | ||
console.warn('Request was created with header "content-type", converted to "Content-Type" instead.'); | ||
} | ||
request.headers.set('Content-Type', new Headers(parsedDefaultHeaders).get('content-type') as string); | ||
@@ -212,6 +264,6 @@ } else if (body !== undefined && isJSON(body)) { | ||
setDefaultHeaders(request.headers, parsedDefaultHeaders); | ||
if (body !== undefined && Object.prototype.isPrototypeOf.call(Blob.prototype, body as Blob) && (body as Blob).type) { | ||
if (body instanceof Blob && body.type) { | ||
// work around bug in IE & Edge where the Blob type is ignored in the request | ||
// https://connect.microsoft.com/IE/feedback/details/2136163 | ||
request.headers.set('Content-Type', (body as Blob).type); | ||
request.headers.set('Content-Type', body.type); | ||
} | ||
@@ -245,3 +297,3 @@ return request; | ||
public post(input: Request | string, body?: BodyInit, init?: RequestInit): Promise<Response> { | ||
return this.callFetch(input, body, init, 'POST'); | ||
return this._callFetch(input, body, init, 'POST'); | ||
} | ||
@@ -260,3 +312,3 @@ | ||
public put(input: Request | string, body?: BodyInit, init?: RequestInit): Promise<Response> { | ||
return this.callFetch(input, body, init, 'PUT'); | ||
return this._callFetch(input, body, init, 'PUT'); | ||
} | ||
@@ -275,3 +327,3 @@ | ||
public patch(input: Request | string, body?: BodyInit, init?: RequestInit): Promise<Response> { | ||
return this.callFetch(input, body, init, 'PATCH'); | ||
return this._callFetch(input, body, init, 'PATCH'); | ||
} | ||
@@ -290,31 +342,48 @@ | ||
public delete(input: Request | string, body?: BodyInit, init?: RequestInit): Promise<Response> { | ||
return this.callFetch(input, body, init, 'DELETE'); | ||
return this._callFetch(input, body, init, 'DELETE'); | ||
} | ||
private trackRequestStart(): void { | ||
/** | ||
* Dispose and cleanup used resources of this client. | ||
*/ | ||
public dispose() { | ||
this._interceptors.forEach(i => i.dispose?.()); | ||
this._interceptors.length = 0; | ||
this._dispatcher = null; | ||
} | ||
/** @internal */ | ||
private _trackRequestStart(): void { | ||
this.isRequesting = !!(++this.activeRequestCount); | ||
if (this.isRequesting && this.dispatcher !== null) { | ||
const evt = new this.dispatcher.ownerDocument!.defaultView!.CustomEvent('aurelia-fetch-client-request-started', { bubbles: true, cancelable: true }); | ||
setTimeout(() => { this.dispatcher!.dispatchEvent(evt); }, 1); | ||
if (this.isRequesting && this._dispatcher != null) { | ||
dispatch(this._dispatcher, HttpClientEvent.started); | ||
} | ||
} | ||
private trackRequestEnd(): void { | ||
/** @internal */ | ||
private _trackRequestEnd(): void { | ||
this.isRequesting = !!(--this.activeRequestCount); | ||
if (!this.isRequesting && this.dispatcher !== null) { | ||
const evt = new this.dispatcher.ownerDocument!.defaultView!.CustomEvent('aurelia-fetch-client-requests-drained', { bubbles: true, cancelable: true }); | ||
setTimeout(() => { this.dispatcher!.dispatchEvent(evt); }, 1); | ||
if (!this.isRequesting && this._dispatcher != null) { | ||
dispatch(this._dispatcher, HttpClientEvent.drained); | ||
} | ||
} | ||
private processRequest(request: Request, interceptors: Interceptor[]): Promise<Request | Response> { | ||
return this.applyInterceptors(request, interceptors, 'request', 'requestError', this); | ||
private processRequest(request: Request, interceptors: IFetchInterceptor[]): Promise<Request | Response> { | ||
return this._applyInterceptors(request, interceptors, 'request', 'requestError', Request, this); | ||
} | ||
private processResponse(response: Promise<Response>, interceptors: Interceptor[], request: Request): Promise<Request | Response> { | ||
return this.applyInterceptors(response, interceptors, 'response', 'responseError', request, this); | ||
private processResponse(response: Promise<Response>, interceptors: IFetchInterceptor[], request: Request): Promise<Request | Response> { | ||
return this._applyInterceptors(response, interceptors, 'response', 'responseError', Response, request, this); | ||
} | ||
private applyInterceptors(input: Request | Promise<Response | Request>, interceptors: Interceptor[] | undefined, successName: ValidInterceptorMethodName, errorName: ValidInterceptorMethodName, ...interceptorArgs: unknown[]): Promise<Request | Response> { | ||
return (interceptors !== undefined ? interceptors : []) | ||
/** @internal */ | ||
private _applyInterceptors( | ||
input: Request | Response | Promise<Response | Request>, | ||
interceptors: IFetchInterceptor[] | undefined, | ||
successName: 'request' | 'response', | ||
errorName: 'requestError' | 'responseError', | ||
Type: typeof Request | typeof Response, | ||
...interceptorArgs: unknown[] | ||
): Promise<Request | Response> { | ||
return (interceptors ?? []) | ||
.reduce( | ||
@@ -325,5 +394,4 @@ (chain, interceptor) => { | ||
// TODO: Fix this, as it violates `strictBindCallApply`. | ||
return chain.then( | ||
successHandler ? (value => successHandler.call(interceptor, value, ...interceptorArgs)) : identity, | ||
successHandler ? (value => value instanceof Type ? successHandler.call(interceptor, value, ...interceptorArgs) : value) : identity, | ||
errorHandler ? (reason => errorHandler.call(interceptor, reason, ...interceptorArgs)) : thrower); | ||
@@ -335,3 +403,4 @@ }, | ||
private callFetch(input: string | Request, body: BodyInit | undefined, init: RequestInit | undefined, method: string): Promise<Response> { | ||
/** @internal */ | ||
private _callFetch(input: string | Request, body: BodyInit | undefined, init: RequestInit | undefined, method: string): Promise<Response> { | ||
if (!init) { | ||
@@ -341,3 +410,3 @@ init = {}; | ||
init.method = method; | ||
if (body) { | ||
if (body != null) { | ||
init.body = body; | ||
@@ -351,9 +420,7 @@ } | ||
const parsedHeaders: Record<string, string> = {}; | ||
const $headers = headers !== undefined ? headers : {}; | ||
for (const name in $headers) { | ||
if (Object.prototype.hasOwnProperty.call($headers, name)) { | ||
parsedHeaders[name] = (typeof $headers[name] === 'function') | ||
? ($headers[name] as () => string)() | ||
: $headers[name] as string; | ||
} | ||
const $headers = headers ?? {}; | ||
for (const name of Object.keys($headers)) { | ||
parsedHeaders[name] = (typeof $headers[name] === 'function') | ||
? ($headers[name] as () => string)() | ||
: $headers[name] as string; | ||
} | ||
@@ -368,9 +435,9 @@ return parsedHeaders; | ||
return (baseUrl !== undefined ? baseUrl : '') + url; | ||
return (baseUrl ?? '') + url; | ||
} | ||
function setDefaultHeaders(headers: Headers, defaultHeaders?: Record<string, string>): void { | ||
const $defaultHeaders = defaultHeaders !== undefined ? defaultHeaders : {}; | ||
for (const name in $defaultHeaders) { | ||
if (Object.prototype.hasOwnProperty.call($defaultHeaders, name) && !headers.has(name)) { | ||
const $defaultHeaders = defaultHeaders ?? {}; | ||
for (const name of Object.keys($defaultHeaders)) { | ||
if (!headers.has(name)) { | ||
headers.set(name, $defaultHeaders[name]); | ||
@@ -398,1 +465,20 @@ } | ||
} | ||
function dispatch(node: Node, name: string): void { | ||
const evt = new node.ownerDocument!.defaultView!.CustomEvent(name, { bubbles: true, cancelable: true }); | ||
setTimeout(() => { node.dispatchEvent(evt); }, 1); | ||
} | ||
/** | ||
* A lookup containing events used by HttpClient. | ||
*/ | ||
export const HttpClientEvent = /*@__PURE__*/Object.freeze({ | ||
/** | ||
* Event to be triggered when a request is sent. | ||
*/ | ||
started: 'aurelia-fetch-client-request-started', | ||
/** | ||
* Event to be triggered when a request is completed. | ||
*/ | ||
drained: 'aurelia-fetch-client-requests-drained' | ||
}); |
@@ -1,5 +0,30 @@ | ||
export { type Interceptor, type RetryConfiguration, type RetryableRequest, type ValidInterceptorMethodName } from './interfaces'; | ||
export { json } from './util'; | ||
export { retryStrategy, RetryInterceptor } from './retry-interceptor'; | ||
export { | ||
type IFetchInterceptor, | ||
} from './interfaces'; | ||
export { | ||
json | ||
} from './utilities-fetch-client'; | ||
export { | ||
// cache interceptor | ||
type ICacheEventData, | ||
type ICacheConfiguration, | ||
CacheEvent, | ||
CacheInterceptor, | ||
type ICacheItem, | ||
CacheService, | ||
ICacheService, | ||
ICacheStorage, | ||
// storage implementations | ||
MemoryStorage, | ||
BrowserIndexDBStorage, | ||
BrowserLocalStorage, | ||
BrowserSessionStorage, | ||
// retry interceptor | ||
RetryInterceptor, | ||
RetryStrategy, | ||
type IRetryConfiguration, | ||
type IRetryableRequest, | ||
} from './interceptors'; | ||
export { HttpClientConfiguration } from './http-client-configuration'; | ||
export { HttpClient, IHttpClient } from './http-client'; | ||
export { IFetchFn, HttpClient, IHttpClient, HttpClientEvent } from './http-client'; |
@@ -7,3 +7,3 @@ import { HttpClient } from './http-client'; | ||
*/ | ||
export interface Interceptor { | ||
export interface IFetchInterceptor { | ||
/** | ||
@@ -33,6 +33,8 @@ * Called with the request before it is sent. Request interceptors can modify and | ||
* | ||
* If a Request object was returned at the end of a response interceptor chain, it will rerun the request cycle with that Request object. | ||
* | ||
* @param response - The response. | ||
* @returns The response; or a Promise for one. | ||
*/ | ||
response?(response: Response, request?: Request): Response | Promise<Response>; | ||
response?(response: Response, request?: Request): Request | Response | Promise<Request | Response>; | ||
@@ -44,2 +46,4 @@ /** | ||
* | ||
* If a Request object was returned at the end of a responseError interceptor chain, it will rerun the request cycle with that Request object. | ||
* | ||
* @param error - The rejection value from the fetch request or from a | ||
@@ -50,18 +54,7 @@ * previous interceptor. | ||
responseError?(error: unknown, request?: Request, httpClient?: HttpClient): Response | Promise<Response>; | ||
} | ||
export type ValidInterceptorMethodName = keyof Interceptor; | ||
export type RetryableRequest = Request & { retryConfig?: RetryConfiguration }; | ||
export interface RetryConfiguration { | ||
maxRetries: number; | ||
interval?: number; | ||
strategy?: number | ((retryCount: number) => number); | ||
minRandomInterval?: number; | ||
maxRandomInterval?: number; | ||
counter?: number; | ||
requestClone?: Request; | ||
doRetry?(response: Response, request: Request): boolean | Promise<boolean>; | ||
beforeRetry?(request: Request, client: HttpClient): Request | Promise<Request>; | ||
/** | ||
* Optional. Called when the owning http client is disposed for cleanup purposes. | ||
*/ | ||
dispose?(): void; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
340404
62
6281
12
+ Added@aurelia/kernel@2.0.1-dev.202402130428(transitive)
+ Added@aurelia/metadata@2.0.1-dev.202402130428(transitive)
+ Added@aurelia/platform@2.0.1-dev.202402130428(transitive)
- Removed@aurelia/kernel@2.0.1-dev.202401261010(transitive)
- Removed@aurelia/metadata@2.0.1-dev.202401261010(transitive)
- Removed@aurelia/platform@2.0.1-dev.202401261010(transitive)