import type { CollectionView } from '#/_base/di/collection'; import { Emitter, type Event } from '#/_base/event'; import type { FiberHandle, FiberProvideOptions, RecipeStatics, ServiceClassRecipe, ServiceRecipe, } from '#/_base/di/fiber'; import { LifecycleScope } from '#/app/scopes'; import { ScopeActivation, registerScopedService } from '#/_base/di/scope'; import { isServiceIdentifier, type ServiceIdentifier } from '#/_base/di/instantiation'; import { Service } from '#/_base/di/service'; import { IFeatureManager, type ManagedUnitInfo, } from './featureManager'; import { FeatureServiceContribution, type ContributedFeatureService, } from './featureServiceContribution'; export class FeatureManagerService extends Service implements IFeatureManager { declare readonly _serviceBrand: undefined; private readonly _units = new Map< string, { handle: FiberHandle; meta: Record } >(); private readonly _onDidChangeUnits = new Emitter(); readonly onDidChangeUnits: Event = this._onDidChangeUnits.event; constructor( @FeatureServiceContribution private readonly _contributedServices: CollectionView, ) { super(); this._register(this._onDidChangeUnits); } provideUnit(recipe: ServiceRecipe, opts?: FiberProvideOptions): FiberHandle; provideUnit( id: ServiceIdentifier, recipe: ServiceClassRecipe, opts?: FiberProvideOptions, ): FiberHandle; provideUnit( first: ServiceRecipe | ServiceIdentifier, second?: any, third?: FiberProvideOptions, ): FiberHandle { const handle = isServiceIdentifier(first) ? this.provide(first, second as ServiceClassRecipe, third) : this.provide(first as ServiceRecipe, second as FiberProvideOptions | undefined); const name = handle.name; const previous = this._units.get(name); if (previous !== undefined && previous.handle !== handle) { void previous.handle.dispose(); } const statics = (isServiceIdentifier(first) ? second : first) as RecipeStatics; this._units.set(name, { handle, meta: Object.freeze({ ...statics.meta }) }); this._onDidChangeUnits.fire(); return handle; } async unprovideUnit(name: string): Promise { const entry = this._units.get(name); if (entry === undefined) { return; } this._units.delete(name); try { await entry.handle.dispose(); } finally { this._onDidChangeUnits.fire(); } } async updateUnit(name: string, config?: unknown): Promise { const entry = this._units.get(name); if (entry === undefined) { throw new Error(`feature unit '${name}' is not managed by this FeatureManager`); } await entry.handle.update(config); this._onDidChangeUnits.fire(); } units(): readonly ManagedUnitInfo[] { const infos: ManagedUnitInfo[] = []; for (const [name, entry] of this._units) { let uid: number | undefined; try { uid = entry.handle.uid; } catch { uid = undefined; } infos.push({ name, state: entry.handle.state, uid, meta: entry.meta }); } return infos; } contributedServices(): readonly ContributedFeatureService[] { return this._contributedServices.items; } } registerScopedService( LifecycleScope.App, IFeatureManager, FeatureManagerService, ScopeActivation.OnScopeCreated, 'feature', );