@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
JavaScript
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.1", 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.1", ngImport: i0, type: BaseHttpDataSource }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", 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.1", ngImport: i0, type: HttpDataSource, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: HttpDataSource }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", 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.1", ngImport: i0, type: HttpDataSourceLoader, deps: [{ token: DataSourceLoader }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: HttpDataSourceLoader, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", 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