UNPKG

@rxap/data-source

Version:

Provides a set of classes and decorators for creating and managing data sources in Angular applications, including base classes, static data sources, observable data sources, and method data sources. It also includes a component for displaying data source

273 lines (265 loc) 12.2 kB
import * as i1$1 from '@rxap/data-source'; import { RxapDataSourceError, BaseDataSource, RXAP_DATA_SOURCE_METADATA, RXAP_DATA_SOURCE_REFRESH, RxapDataSource, DataSourceLoader } from '@rxap/data-source'; import * as i2 from 'rxjs'; import { Subject, firstValueFrom, ReplaySubject, EMPTY, of, throwError } from 'rxjs'; import * as i1 from '@angular/common/http'; import { HttpRequest, HttpClient, HttpEventType, HttpResponse } from '@angular/common/http'; import { joinPath, deepMerge, hasIndexSignature } from '@rxap/utilities'; import * as i0 from '@angular/core'; import { Injectable, Inject, Optional } from '@angular/core'; import { tap, switchMap, finalize, skip, retry, filter, catchError, map, take, timeout } from 'rxjs/operators'; import { RequestInProgressSubject } from '@rxap/rxjs'; class RxapHttpDataSourceError extends RxapDataSourceError { constructor(message, code, className) { super(message, code, className); this.addSubPackageName('http'); } } class BaseHttpDataSource extends BaseDataSource { constructor(http, metadata = null, refresh$ = null) { super(metadata); this.http = http; this.requestsInProgress$ = new RequestInProgressSubject(); this.refresh$ = new Subject(); this.refreshed$ = new Subject(); this.timeout = 60 * 1000; this._refreshSubscription = null; this.timeout = this.metadata.timeout ?? this.timeout; this.loading$ = this.requestsInProgress$.loading$(); if (http === undefined) { throw new RxapDataSourceError('HttpClient is undefined. Ensure that the HttpClient is added to the deps property!', '', this.constructor.name); } if (refresh$) { this.refresh$ = refresh$; } } request$(options = this._options, merge = false) { this.init(); if (merge) { // TODO : add deep merge options = { ...this._options, ...options }; } // test if options is defined. Can be undefined if the init method is not yet been called if (!options) { options = this._options; } return firstValueFrom(this.buildRequest(options)); } reset() { this._data = undefined; if (this._refreshSubscription) { this._refreshSubscription.unsubscribe(); } super.reset(); this._initialised = false; this.init(); } refresh(options = this._options, merge = false, force = false) { this.init(); if (force || this._connectedViewer.size !== 0) { if (merge) { // TODO : add deep merge options = { ...this._options, ...options }; } // test if options is defined. Can be undefined if the init method is not yet been called if (!options) { options = this._options; } this.refresh$.next(options); this.refreshed$.next(); } } init() { if (this._initialised) { return; } super.init(); if (this._refreshSubscription) { throw new RxapHttpDataSourceError('The refresh subscription is already initialised', ''); } this._options = { params: this.metadata.params, pathParams: this.metadata.pathParams, body: this.metadata.body, headers: this.metadata.headers, }; this._data$ = new ReplaySubject(this.metadata.bufferSize ?? 1); this._refreshSubscription = this.refresh$.pipe(tap(options => this._options = options), tap(() => this.requestsInProgress$.increase()), switchMap(options => this.buildRequest(options).pipe(finalize(() => this.requestsInProgress$.decrease())))).subscribe(this._data$); } isEqualToLastOptions(options) { const keys = [ 'url', 'pathParams', 'body', 'params', 'headers', ]; return !!this._options && keys.every(key => this._options[key] === options[key]); } _connect(viewer) { this.init(); if (viewer.url === this.metadata.url) { delete viewer.url; } if (viewer.viewChange === EMPTY && (!viewer.lazy || this._data === undefined || !this.isEqualToLastOptions(viewer))) { viewer.realtime = this._data !== undefined; viewer.viewChange = of(viewer); } return [ viewer.realtime ? this._data$.pipe(skip(this.metadata.bufferSize ?? 1)) : this._data$, viewer.viewChange.pipe(tap(options => this.refresh(options, true, true))).subscribe(), ]; } getRequestUrl() { if (typeof this.metadata.url === 'function') { return this.metadata.url(); } return this.metadata.url; } buildHttpRequest(options) { return new HttpRequest(this.metadata.method ?? 'GET', this.buildUrlWithParams(joinPath(this.getRequestUrl(), options.url ?? null), deepMerge(this.metadata.pathParams ?? {}, options.pathParams ?? {})), options.body ?? this.metadata.body ?? null, { params: options.params ?? this.metadata.params, headers: options.headers ?? this.metadata.headers, reportProgress: true, responseType: this.metadata.responseType ?? 'json', withCredentials: this.metadata.withCredentials, }); } buildUrlWithParams(url, pathParams) { const matches = url.match(/\{[^}]+\}/g); if (matches) { if (!hasIndexSignature(pathParams)) { throw new RxapDataSourceError(`Path params for connection '${this.id}' has not an index signature`, '', this.constructor.name); } for (const match of matches) { const param = match.substr(1, match.length - 2); let replace = null; // eslint-disable-next-line no-prototype-builtins if (pathParams.hasOwnProperty(param)) { replace = encodeURIComponent(pathParams[param]); } if (replace === null) { throw new RxapDataSourceError(`Path params for connection '${this.id}' has not a defined value for '${param}'`, '', this.constructor.name); } url = url.replace(match, replace); } } return url; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: BaseHttpDataSource, deps: [{ token: HttpClient }, { token: RXAP_DATA_SOURCE_METADATA, optional: true }, { token: RXAP_DATA_SOURCE_REFRESH, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: BaseHttpDataSource }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: BaseHttpDataSource, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.HttpClient, decorators: [{ type: Inject, args: [HttpClient] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_DATA_SOURCE_METADATA] }] }, { type: i2.Subject, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_DATA_SOURCE_REFRESH] }] }] }); class HttpDataSource extends BaseHttpDataSource { constructor() { super(...arguments); this.onSentEvent$ = new Subject(); this.onResponse$ = new Subject(); this.onDownloadProgress$ = new Subject(); this.onUploadProgress$ = new Subject(); this.onResponseHeader$ = new Subject(); } derive(id, metadata = this.metadata, isolated = false) { return new HttpDataSource(this.http, { ...deepMerge(this.metadata, metadata), id, }, isolated ? null : this.refresh$); } transform(data) { return data; } handelHttpEvent(event) { switch (event.type) { case HttpEventType.Sent: this.onSentEvent$.next(event); break; case HttpEventType.Response: this.onResponse$.next(event); break; case HttpEventType.DownloadProgress: this.onDownloadProgress$.next(event); break; case HttpEventType.UploadProgress: this.onUploadProgress$.next(event); break; case HttpEventType.ResponseHeader: this.onResponseHeader$.next(event); break; } } buildRequest(options) { return this.http.request(this.buildHttpRequest(options)).pipe(retry(this.metadata.retry ?? 0), tap(event => this.handelHttpEvent(event)), filter((event) => event instanceof HttpResponse), tap((response) => { this.interceptors?.forEach(interceptor => interceptor .next({ response, options, })); }), catchError((response) => { this.interceptors?.forEach(interceptor => interceptor .next({ response, options, })); return throwError(response); }), map((event) => event.body), map(data => this.transform(data)), take(1), timeout(this.timeout)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: HttpDataSource, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: HttpDataSource }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: HttpDataSource, decorators: [{ type: Injectable }] }); function RxapHttpDataSource(metadata, className = 'HttpDataSource', packageName = '@rxap/data-source/http') { return function (target) { RxapDataSource(metadata, className, packageName)(target); }; } class HttpDataSourceLoader { // Instead of extanding the DataSourceLoader class the DataSourceLoader instance // will be injected, else there could be multiple instance of DataSourceLoader's. constructor(dataSourceLoader) { this.dataSourceLoader = dataSourceLoader; } request$(dataSourceIdOrInstanceOrToken, options, merge = false, metadata, injector, notFoundValue, flags) { const dataSource = this .dataSourceLoader .load(dataSourceIdOrInstanceOrToken, metadata, injector, notFoundValue, flags); if (!(dataSource instanceof HttpDataSource)) { throw new RxapDataSourceError(`The data source is not a HttpDataSource`, '', 'HttpDataSourceLoader'); } return dataSource.request$(options, merge); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: HttpDataSourceLoader, deps: [{ token: DataSourceLoader }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: HttpDataSourceLoader, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: HttpDataSourceLoader, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1$1.DataSourceLoader, decorators: [{ type: Inject, args: [DataSourceLoader] }] }] }); // region // endregion /** * Generated bundle index. Do not edit. */ export { BaseHttpDataSource, HttpDataSource, HttpDataSourceLoader, RxapHttpDataSource, RxapHttpDataSourceError }; //# sourceMappingURL=rxap-data-source-http.mjs.map