### File

`src/lib/base.data-source.ts`

### Extends

`BaseDefinition`

### Index

###### Properties
Protected _connectedViewer Protected _connectedViewerTeardown Protected Optional _data Protected _data$ Protected _lastRefreshed Protected _retry$ Protected _viewerIds Public Readonly change$ Public Readonly error$ Public Optional hasError Public Readonly hasError$ Public Optional loading Public loading$
###### Methods
Protected _connect Protected _disconnect Public attach Public connect Public derive Public disconnect Protected genericRetryFunction Public getViewerId Protected handelError Public isConnected Public refresh Public reset Public retry Public toJSON Public Async toPromise
###### Accessors
lastRefreshed data hasConnections

### Constructor

`constructor(metadata: Metadata | null)`
Defined in src/lib/base.data-source.ts:94
Parameters :
Name | Type | Optional
metadata | `Metadata | null` | No

### Methods

Protected _connect
`_connect(viewer: BaseDataSourceViewer)`
Defined in src/lib/base.data-source.ts:323
Parameters :
Name | Type | Optional
viewer | `BaseDataSourceViewer` | No
Returns : `| Observable`

Protected _disconnect
`_disconnect(viewerId: DataSourceViewerId)`
Defined in src/lib/base.data-source.ts:308
Parameters :
Name | Type | Optional
viewerId | `DataSourceViewerId` | No
Returns : `void`

Public attach
`attach(viewerId: DataSourceViewerId)`
Defined in src/lib/base.data-source.ts:210
Parameters :
Name | Type | Optional
viewerId | `DataSourceViewerId` | No
Returns : `Observable<Data>`

Public connect
`connect(viewerOrString: Viewer | DataSourceViewerId)`
Defined in src/lib/base.data-source.ts:124
Parameters :
Name | Type | Optional
viewerOrString | `Viewer | DataSourceViewerId` | No
Returns : `Observable<Data>`

Public derive
`derive(id: string, metadata: Partial<BaseDataSourceMetadata>)`
Defined in src/lib/base.data-source.ts:270
Parameters :
Name | Type | Optional | Default value
id | `string` | No |
metadata | `Partial<BaseDataSourceMetadata>` | No | `this.metadata`
Returns : `BaseDataSource<any>`

Public disconnect
`disconnect(viewerOrId: Viewer | DataSourceViewerId)`
Defined in src/lib/base.data-source.ts:225
Parameters :
Name | Type | Optional
viewerOrId | `Viewer | DataSourceViewerId` | No
Returns : `void`

Protected genericRetryFunction
`genericRetryFunction(error: any, retryCount: number)`
Defined in src/lib/base.data-source.ts:311
Parameters :
Name | Type | Optional
error | `any` | No
retryCount | `number` | No
Returns : `Observable<any>`

Public getViewerId
`getViewerId(viewer: Viewer)`
Defined in src/lib/base.data-source.ts:114
Parameters :
Name | Type | Optional
viewer | `Viewer` | No
Returns : `string`

Protected handelError
`handelError(error: any)`
Defined in src/lib/base.data-source.ts:317
Parameters :
Name | Type | Optional
error | `any` | No
Returns : `void`

Public isConnected
`isConnected(viewerOrId: Viewer | DataSourceViewerId)`
Defined in src/lib/base.data-source.ts:217
Parameters :
Name | Type | Optional
viewerOrId | `Viewer | DataSourceViewerId` | No
Returns : `boolean`

Public refresh
`refresh()`
Defined in src/lib/base.data-source.ts:290
Returns : `any`

Public reset
`reset()`
Defined in src/lib/base.data-source.ts:297
Returns : `any`

Public retry
`retry()`
Defined in src/lib/base.data-source.ts:293
Returns : `any`

Public toJSON
`toJSON()`
Defined in src/lib/base.data-source.ts:280
Returns : `object`

Public Async toPromise
`toPromise(viewer: Viewer)`
Defined in src/lib/base.data-source.ts:264
Creates a connection to tha data source and converts the Observable into a promise and then disconnects the viewer Parameters :
Name | Type | Optional
viewer | `Viewer` | No
Returns : `Promise<Data>`

### Properties

Protected _connectedViewer
Default value : `new Map<DataSourceViewerId, Observable<Data>>()`
Defined in src/lib/base.data-source.ts:78

Protected _connectedViewerTeardown
Default value : `new Map< DataSourceViewerId, TeardownLogic >()`
Defined in src/lib/base.data-source.ts:79

Protected Optional _data
Type : `Data`
Defined in src/lib/base.data-source.ts:104

Protected _data$
Type : `Observable<Data>`
Default value : `EMPTY`
Defined in src/lib/base.data-source.ts:83

Protected _lastRefreshed
Type : `Date | null`
Default value : `null`
Defined in src/lib/base.data-source.ts:77

Protected _retry$
Default value : `new Subject<void>()`
Defined in src/lib/base.data-source.ts:90

Protected _viewerIds
Default value : `new Map<Viewer, string>()`
Defined in src/lib/base.data-source.ts:89
a map of viewer to view id. Allows to create a view id from the viewer object reference

Public Readonly change$
Default value : `new Subject<Data>()`
Defined in src/lib/base.data-source.ts:67

Public Readonly error$
Default value : `new ReplaySubject<Error>(1)`
Defined in src/lib/base.data-source.ts:73

Public Optional hasError
Type : `Signal<boolean>`
Defined in src/lib/base.data-source.ts:94

Public Readonly hasError$
Default value : `new ToggleSubject()`
Defined in src/lib/base.data-source.ts:72

Public Optional loading
Type : `Signal<boolean>`
Defined in src/lib/base.data-source.ts:92

Public loading$
Type : `Observable<boolean>`
Default value : `EMPTY`
Defined in src/lib/base.data-source.ts:71
Indicates weather the data source is currently loading new data

### Accessors

lastRefreshed
get `lastRefreshed()`
Defined in src/lib/base.data-source.ts:74

data
get `data()`
Defined in src/lib/base.data-source.ts:106

hasConnections
get `hasConnections()`
Defined in src/lib/base.data-source.ts:110

    import {
      Inject,
      Injectable,
      isDevMode,
      Optional,
      Signal,
    } from '@angular/core';
    import { toSignal } from '@angular/core/rxjs-interop';
    import {
      BaseDefinition,
      BaseDefinitionMetadata,
      DefinitionMetadata,
      RXAP_DEFINITION_METADATA,
    } from '@rxap/definition';
    import {
      CloneObservable,
      ToggleSubject,
    } from '@rxap/rxjs';
    import {
      clone,
      Constructor,
      deepMerge,
      GenerateRandomString,
    } from '@rxap/utilities';
    import {
      EMPTY,
      firstValueFrom,
      Observable,
      ReplaySubject,
      Subject,
      TeardownLogic,
    } from 'rxjs';
    import {
      finalize,
      startWith,
      take,
      takeUntil,
      tap,
    } from 'rxjs/operators';

    export type DataSourceViewerId = string;

    export interface BaseDataSourceViewer<View = any> {
      id?: DataSourceViewerId;
      viewChange?: Observable<View>;
      /**
       * Indicates weather the data source should restore the last value from local storage
       */
      restore?: boolean;

      [key: string]: any;
    }

    export interface BaseDataSourceMetadata extends BaseDefinitionMetadata {
      /**
       * Indicates weather the data source should restore the last value from local storage
       */
      restore?: boolean;
    }

    @Injectable()
    export class BaseDataSource<
      Data = any,
      Metadata extends BaseDataSourceMetadata = BaseDataSourceMetadata,
      Viewer extends BaseDataSourceViewer = BaseDataSourceViewer
    > extends BaseDefinition<Metadata> {
      public readonly change$ = new Subject<Data>();
      /**
       * Indicates weather the data source is currently loading new data
       */
      public loading$: Observable<boolean> = EMPTY;
      public readonly hasError$ = new ToggleSubject();
      public readonly error$ = new ReplaySubject<Error>(1);
      public get lastRefreshed(): Date | null {
        return this._lastRefreshed;
      }
      protected _lastRefreshed: Date | null = null;
      protected _connectedViewer = new Map<DataSourceViewerId, Observable<Data>>();
      protected _connectedViewerTeardown = new Map<
        DataSourceViewerId,
        TeardownLogic
      >();
      protected _data$: Observable<Data> = EMPTY;
      /**
       * a map of viewer to view id.
       * Allows to create a view id from the viewer object reference
       * @protected
       */
      protected _viewerIds = new Map<Viewer, string>();
      protected _retry$ = new Subject<void>();

      public loading?: Signal<boolean>;

      public hasError?: Signal<boolean>;

      constructor(
        @Optional()
        @Inject(RXAP_DEFINITION_METADATA)
          metadata: Metadata | null = null,
      ) {
        super(metadata);
      }

      protected _data?: Data;

      public get data() {
        return clone(this._data);
      }

      public get hasConnections(): boolean {
        return this._connectedViewer.size > 0;
      }

      public getViewerId(viewer: Viewer): string {
        if (viewer.id) {
          return viewer.id;
        }
        if (!this._viewerIds.has(viewer)) {
          this._viewerIds.set(viewer, GenerateRandomString());
        }
        return this._viewerIds.get(viewer)!;
      }

      public connect(viewerOrString: Viewer | DataSourceViewerId): Observable<Data> {
        let viewer: Viewer;
        if (typeof viewerOrString === 'string') {
          viewer = { id: viewerOrString } as Viewer;
        } else {
          viewer = viewerOrString;
        }
        if (!viewer.id) {
          viewer.id = this.getViewerId(viewer);
        }
        if (this.isConnected(viewer)) {
          return this._connectedViewer.get(viewer.id)!;
        }
        if (!viewer.viewChange) {
          viewer.viewChange = EMPTY;
        }
        const _connection = this._connect(viewer);
        let connection: Observable<Data>;
        let teardownLogic: TeardownLogic | null = null;
        if (Array.isArray(_connection)) {
          if (_connection.length !== 2) {
            throw new Error(
              'if this._connect returns an array. The array should have two items',
            );
          }
          connection = _connection[0];
          teardownLogic = _connection[1];
        } else {
          connection = _connection;
        }

        const destroy$ = new Subject<void>();

        const cacheKey = [ 'rxap', 'data-source', this.constructor.name, this.id ].join('_');
        const restore = viewer.restore ?? this.metadata.restore ?? false;

        connection = connection.pipe(
          tap((data) => {
            this._data = data;
            if (restore) {
              try {
                const cache = JSON.stringify(data);
                localStorage.setItem(cacheKey, cache);
              } catch (e: any) {
                console.warn(
                  `Failed to store data source '${ this.id }' data in local storage`,
                );
              }
            }
          }),
          tap((data) => this.change$.next(data)),
          tap(() => this._lastRefreshed = new Date()),
          finalize(() => this.disconnect(viewer)),
          takeUntil(destroy$),
        );

        if (restore) {
          const data = localStorage.getItem(cacheKey);
          if (data) {
            this._data = JSON.parse(data) as Data;
            connection = connection.pipe(startWith(this._data));
          }
        }

        this._connectedViewer.set(viewer.id, connection);

        if (teardownLogic) {
          const tl = teardownLogic;
          teardownLogic = () => {
            destroy$.next();
            if (typeof tl === 'function') {
              tl();
            } else {
              tl.unsubscribe();
            }
          };
        } else {
          teardownLogic = () => {
            destroy$.next();
          };
        }

        this._connectedViewerTeardown.set(viewer.id, teardownLogic);
        return connection;
      }

      public attach(viewerId: DataSourceViewerId): Observable<Data> {
        if (this.isConnected(viewerId)) {
          return CloneObservable(this._connectedViewer.get(viewerId)!);
        }
        throw new Error(`No active connection with viewer id '${ viewerId }' found`);
      }

      public isConnected(viewerOrId: Viewer | DataSourceViewerId): boolean {
        const viewerId =
          typeof viewerOrId === 'string'
            ? viewerOrId
            : viewerOrId.id ?? this.getViewerId(viewerOrId);
        return this._connectedViewer.has(viewerId);
      }

      public disconnect(viewerOrId: Viewer | DataSourceViewerId) {
        const viewerId =
          typeof viewerOrId === 'string'
            ? viewerOrId
            : viewerOrId.id ?? this.getViewerId(viewerOrId);
        if (this.isConnected(viewerId)) {
          this._disconnect(viewerId);
          this._connectedViewer.delete(viewerId);
          if (this._connectedViewerTeardown.has(viewerId)) {
            const teardownLogic = this._connectedViewerTeardown.get(viewerId)!;
            if (teardownLogic) {
              if (typeof teardownLogic === 'function') {
                teardownLogic();
              } else {
                teardownLogic.unsubscribe();
              }
            }
          }
        } else {
          console.warn(
            `Connection with viewer id '${ viewerId }' is not connected to the data source '${ this.id }'`,
          );
        }
        // TODO : find better cleanup solution
        if (typeof viewerOrId === 'string') {
          for (const [ viewer, id ] of this._viewerIds.entries()) {
            if (viewerOrId === id) {
              this._viewerIds.delete(viewer);
            }
          }
        } else {
          this._viewerIds.delete(viewerOrId);
        }
      }

      /**
       * Creates a connection to tha data source and converts the Observable into a
       * promise and then disconnects the viewer
       */
      public async toPromise(viewer: Viewer): Promise<Data> {
        const result = await firstValueFrom(this.connect(viewer).pipe(take(1)));
        this.disconnect(viewer);
        return result;
      }

      public derive(
        id: string,
        metadata: Partial<BaseDataSourceMetadata> = this.metadata,
      ): BaseDataSource<any> {
        return new BaseDataSource<any>({
          ...deepMerge(this.metadata, metadata),
          id,
        });
      }

      public toJSON(): object {
        return {
          id: this.id,
          metadata: this.metadata,
          connected: this._connectedViewer.keys(),
          data: this._data,
        };
      }

      // eslint-disable-next-line @typescript-eslint/no-empty-function
      public refresh(): any {
      }

      public retry(): any {
        this._retry$.next();
      }

      public reset(): any {
        for (const viewerId of this._connectedViewer.keys()) {
          this.disconnect(viewerId);
        }
        // eslint-disable-next-line no-prototype-builtins
        if (this.metadata.hasOwnProperty('context')) {
          delete this.metadata['context'];
        }
      }

      // eslint-disable-next-line @typescript-eslint/no-empty-function
      protected _disconnect(viewerId: DataSourceViewerId): void {
      }

      protected genericRetryFunction(error: any, retryCount: number): Observable<any> {
        return this._retry$.asObservable().pipe(
          tap(() => this.hasError$.disable()),
        );
      }

      protected handelError(error: any) {
        if (isDevMode()) {
          console.log(`DataSource '${ this.id }' has an error:`, error);
        }
      }

      protected _connect(
        viewer: BaseDataSourceViewer,
      ): [ Observable<Data>, TeardownLogic ] | Observable<Data> {
        this.init();
        return this._data$;
      }
    }

    export function RxapDataSource<Metadata extends BaseDataSourceMetadata>(
      dataSourceIdOrMetadata: string | Metadata,
      className = 'BaseDataSource',
      packageName = '@rxap/data-source',
    ) {
      return function (target: Constructor<BaseDataSource>) {
        DefinitionMetadata(dataSourceIdOrMetadata, className, packageName)(target);
      };
    }

# results matching ""

# No results matching ""