From e6abf4e33b841961ff37c701daa2eb4e203d7e9f Mon Sep 17 00:00:00 2001 From: waterplea Date: Fri, 21 Jun 2024 15:51:04 +0500 Subject: [PATCH] feat: get rid of cyclic dep between patch-db and api service Signed-off-by: waterplea --- web/projects/ui/src/app/app.module.ts | 2 - web/projects/ui/src/app/app.providers.ts | 10 ++++ .../services/api/embassy-live-api.service.ts | 7 ++- .../app/services/patch-db/patch-db-source.ts | 56 +++++++++++++++++++ .../app/services/patch-db/patch-db.factory.ts | 55 ------------------ .../app/services/patch-db/patch-db.module.ts | 20 ------- 6 files changed, 70 insertions(+), 80 deletions(-) create mode 100644 web/projects/ui/src/app/services/patch-db/patch-db-source.ts delete mode 100644 web/projects/ui/src/app/services/patch-db/patch-db.factory.ts delete mode 100644 web/projects/ui/src/app/services/patch-db/patch-db.module.ts diff --git a/web/projects/ui/src/app/app.module.ts b/web/projects/ui/src/app/app.module.ts index c0264f064..f13b4fbfa 100644 --- a/web/projects/ui/src/app/app.module.ts +++ b/web/projects/ui/src/app/app.module.ts @@ -28,7 +28,6 @@ import { PreloaderModule } from './app/preloader/preloader.module' import { FooterModule } from './app/footer/footer.module' import { MenuModule } from './app/menu/menu.module' import { APP_PROVIDERS } from './app.providers' -import { PatchDbModule } from './services/patch-db/patch-db.module' import { ToastContainerModule } from './components/toast-container/toast-container.module' import { ConnectionBarComponentModule } from './components/connection-bar/connection-bar.component.module' import { WidgetsPageModule } from './pages/widgets/widgets.module' @@ -54,7 +53,6 @@ import { environment } from '../environments/environment' MonacoEditorModule, SharedPipesModule, MarketplaceModule, - PatchDbModule, ToastContainerModule, ConnectionBarComponentModule, TuiRootModule, diff --git a/web/projects/ui/src/app/app.providers.ts b/web/projects/ui/src/app/app.providers.ts index bf26a8cb9..c6a8d756e 100644 --- a/web/projects/ui/src/app/app.providers.ts +++ b/web/projects/ui/src/app/app.providers.ts @@ -3,6 +3,11 @@ import { UntypedFormBuilder } from '@angular/forms' import { Router, RouteReuseStrategy } from '@angular/router' import { IonicRouteStrategy, IonNav } from '@ionic/angular' import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared' +import { PatchDB } from 'patch-db-client' +import { + PATCH_CACHE, + PatchDbSource, +} from 'src/app/services/patch-db/patch-db-source' import { ApiService } from './services/api/embassy-api.service' import { MockApiService } from './services/api/embassy-mock-api.service' import { LiveApiService } from './services/api/embassy-live-api.service' @@ -29,6 +34,11 @@ export const APP_PROVIDERS: Provider[] = [ provide: ApiService, useClass: useMocks ? MockApiService : LiveApiService, }, + { + provide: PatchDB, + deps: [PatchDbSource, PATCH_CACHE], + useClass: PatchDB, + }, { provide: APP_INITIALIZER, deps: [StorageService, AuthService, ClientStorageService, Router], diff --git a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts index 8826b2883..954a4475e 100644 --- a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -7,6 +7,7 @@ import { RpcError, RPCOptions, } from '@start9labs/shared' +import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source' import { ApiService } from './embassy-api.service' import { RR } from './api.types' import { parsePropertiesPermissive } from 'src/app/util/properties.util' @@ -16,7 +17,7 @@ import { Observable, filter, firstValueFrom } from 'rxjs' import { AuthService } from '../auth.service' import { DOCUMENT } from '@angular/common' import { DataModel } from '../patch-db/data-model' -import { PatchDB, pathFromArray } from 'patch-db-client' +import { Dump, pathFromArray } from 'patch-db-client' @Injectable() export class LiveApiService extends ApiService { @@ -25,7 +26,7 @@ export class LiveApiService extends ApiService { private readonly http: HttpService, private readonly config: ConfigService, private readonly auth: AuthService, - private readonly patch: PatchDB, + @Inject(PATCH_CACHE) private readonly cache$: Observable>, ) { super() ; (window as any).rpcClient = this @@ -482,7 +483,7 @@ export class LiveApiService extends ApiService { const patchSequence = res.headers.get('x-patch-sequence') if (patchSequence) await firstValueFrom( - this.patch.cache$.pipe(filter(({ id }) => id >= Number(patchSequence))), + this.cache$.pipe(filter(({ id }) => id >= Number(patchSequence))), ) return body.result diff --git a/web/projects/ui/src/app/services/patch-db/patch-db-source.ts b/web/projects/ui/src/app/services/patch-db/patch-db-source.ts new file mode 100644 index 000000000..803fbce2c --- /dev/null +++ b/web/projects/ui/src/app/services/patch-db/patch-db-source.ts @@ -0,0 +1,56 @@ +import { inject, Injectable, InjectionToken } from '@angular/core' +import { Dump, Revision, Update } from 'patch-db-client' +import { BehaviorSubject, EMPTY, Observable } from 'rxjs' +import { + bufferTime, + catchError, + filter, + startWith, + switchMap, + take, +} from 'rxjs/operators' +import { StateService } from 'src/app/services/state.service' +import { ApiService } from '../api/embassy-api.service' +import { AuthService } from '../auth.service' +import { DataModel } from './data-model' +import { LocalStorageBootstrap } from './local-storage-bootstrap' + +export const PATCH_CACHE = new InjectionToken('', { + factory: () => + new BehaviorSubject>({ + id: 0, + value: {} as DataModel, + }), +}) + +@Injectable({ + providedIn: 'root', +}) +export class PatchDbSource extends Observable[]> { + private readonly api = inject(ApiService) + private readonly state = inject(StateService) + private readonly stream$ = inject(AuthService).isVerified$.pipe( + switchMap(verified => (verified ? this.api.subscribeToPatchDB({}) : EMPTY)), + switchMap(({ dump, guid }) => + this.api.openWebsocket$(guid, {}).pipe( + bufferTime(250), + filter(revisions => !!revisions.length), + startWith([dump]), + ), + ), + catchError((_, original$) => { + this.state.retrigger() + + return this.state.pipe( + filter(current => current === 'running'), + take(1), + switchMap(() => original$), + ) + }), + startWith([inject(LocalStorageBootstrap).init()]), + ) + + constructor() { + super(subscriber => this.stream$.subscribe(subscriber)) + } +} diff --git a/web/projects/ui/src/app/services/patch-db/patch-db.factory.ts b/web/projects/ui/src/app/services/patch-db/patch-db.factory.ts deleted file mode 100644 index bb2dcdf57..000000000 --- a/web/projects/ui/src/app/services/patch-db/patch-db.factory.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { InjectionToken, Injector } from '@angular/core' -import { Revision, Update } from 'patch-db-client' -import { defer, EMPTY, from, Observable } from 'rxjs' -import { - bufferTime, - catchError, - filter, - startWith, - switchMap, - take, -} from 'rxjs/operators' -import { StateService } from 'src/app/services/state.service' -import { ApiService } from '../api/embassy-api.service' -import { AuthService } from '../auth.service' -import { DataModel } from './data-model' -import { LocalStorageBootstrap } from './local-storage-bootstrap' - -export const PATCH_SOURCE = new InjectionToken[]>>( - '', -) - -export function sourceFactory( - injector: Injector, -): Observable[]> { - // defer() needed to avoid circular dependency with ApiService, since PatchDB is needed there - return defer(() => { - const api = injector.get(ApiService) - const auth = injector.get(AuthService) - const state = injector.get(StateService) - const bootstrapper = injector.get(LocalStorageBootstrap) - - return auth.isVerified$.pipe( - switchMap(verified => - verified ? from(api.subscribeToPatchDB({})) : EMPTY, - ), - switchMap(({ dump, guid }) => - api.openWebsocket$(guid, {}).pipe( - bufferTime(250), - filter(revisions => !!revisions.length), - startWith([dump]), - ), - ), - catchError((_, original$) => { - state.retrigger() - - return state.pipe( - filter(current => current === 'running'), - take(1), - switchMap(() => original$), - ) - }), - startWith([bootstrapper.init()]), - ) - }) -} diff --git a/web/projects/ui/src/app/services/patch-db/patch-db.module.ts b/web/projects/ui/src/app/services/patch-db/patch-db.module.ts deleted file mode 100644 index 3c816e339..000000000 --- a/web/projects/ui/src/app/services/patch-db/patch-db.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { PatchDB } from 'patch-db-client' -import { Injector, NgModule } from '@angular/core' -import { PATCH_SOURCE, sourceFactory } from './patch-db.factory' - -// This module is purely for providers organization purposes -@NgModule({ - providers: [ - { - provide: PATCH_SOURCE, - deps: [Injector], - useFactory: sourceFactory, - }, - { - provide: PatchDB, - deps: [PATCH_SOURCE], - useClass: PatchDB, - }, - ], -}) -export class PatchDbModule {}