Merge pull request #2649 from Start9Labs/cyclic-dep

feat: get rid of cyclic dep between patch-db and api service
This commit is contained in:
Matt Hill
2024-06-21 21:26:38 -06:00
committed by GitHub
6 changed files with 70 additions and 80 deletions

View File

@@ -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,

View File

@@ -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],

View File

@@ -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<DataModel>,
@Inject(PATCH_CACHE) private readonly cache$: Observable<Dump<DataModel>>,
) {
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

View File

@@ -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<Dump<DataModel>>({
id: 0,
value: {} as DataModel,
}),
})
@Injectable({
providedIn: 'root',
})
export class PatchDbSource extends Observable<Update<DataModel>[]> {
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$<Revision>(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))
}
}

View File

@@ -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<Observable<Update<DataModel>[]>>(
'',
)
export function sourceFactory(
injector: Injector,
): Observable<Update<DataModel>[]> {
// 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$<Revision>(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()]),
)
})
}

View File

@@ -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 {}