File size: 3,441 Bytes
f0634fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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<string, unknown> }
  >();
  private readonly _onDidChangeUnits = new Emitter<void>();
  readonly onDidChangeUnits: Event<void> = this._onDidChangeUnits.event;

  constructor(
    @FeatureServiceContribution
    private readonly _contributedServices: CollectionView<ContributedFeatureService>,
  ) {
    super();
    this._register(this._onDidChangeUnits);
  }

  provideUnit(recipe: ServiceRecipe, opts?: FiberProvideOptions): FiberHandle;
  provideUnit<T>(
    id: ServiceIdentifier<T>,
    recipe: ServiceClassRecipe,
    opts?: FiberProvideOptions,
  ): FiberHandle<T>;
  provideUnit(
    first: ServiceRecipe | ServiceIdentifier<any>,
    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<void> {
    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<void> {
    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',
);