@difizen/mana-core
Version:
238 lines (205 loc) • 7.25 kB
text/typescript
import type { MaybePromise } from '@difizen/mana-common';
import { DisposableCollection } from '@difizen/mana-common';
import type { Contribution } from '@difizen/mana-syringe';
import { contrib, inject, singleton, Syringe } from '@difizen/mana-syringe';
import { ApplicationContribution } from '../application/index';
import type { ConfigurationNode } from './configuration-protocol';
import type { ConfigurationStorage } from './configuration-storage';
import { DefaultConfigurationStorage } from './configuration-storage';
import { SchemaValidator } from './validation';
export const ConfigurationContribution = Syringe.defineToken(
'ConfigurationContribution',
);
export interface ConfigurationContribution {
registerConfiguration?: () => MaybePromise<ConfigurationNode<any>>;
registerConfigurations?: () => MaybePromise<ConfigurationNode<any>[]>;
deregisterConfigurations?: () => MaybePromise<ConfigurationNode<any>[]>;
}
({ contrib: [ApplicationContribution] })
export class ConfigurationRegistry implements ApplicationContribution {
protected readonly schemaValidator: SchemaValidator;
protected providers: Contribution.Provider<ConfigurationContribution>;
protected toDispose = new DisposableCollection();
protected defaultStorage = DefaultConfigurationStorage;
protected configurationNodes: ConfigurationNode<unknown>[] = [];
protected scopes = new Map<ConfigurationStorage['id'], ConfigurationStorage>();
constructor(
(SchemaValidator) schemaValidator: SchemaValidator,
(ConfigurationContribution)
providers: Contribution.Provider<ConfigurationContribution>,
) {
this.schemaValidator = schemaValidator;
this.providers = providers;
}
async onStart() {
await this.setupConfiguration();
}
protected async setupConfiguration() {
const contribs = this.providers.getContributions();
for (const configurationContrib of contribs) {
if (configurationContrib.registerConfiguration) {
const node = await configurationContrib.registerConfiguration();
this.registerConfiguration(node);
}
if (configurationContrib.registerConfigurations) {
const nodes = await configurationContrib.registerConfigurations();
this.registerConfigurations(nodes);
}
}
for (const configurationContrib of contribs) {
if (configurationContrib.deregisterConfigurations) {
const nodes = await configurationContrib.deregisterConfigurations();
this.deregisterConfigurations(nodes);
}
}
}
getStorage(configuration: ConfigurationNode<any>) {
return configuration.storage ?? this.defaultStorage;
}
hasConfiguration(configuration: ConfigurationNode<any>): boolean {
let has = false;
for (const node of this.configurationNodes) {
if (node.id === configuration.id) {
has = true;
break;
}
}
return has;
}
findConfiguration<T>(id: string): ConfigurationNode<T> | undefined {
let result: ConfigurationNode<T> | undefined;
for (const node of this.configurationNodes) {
if (node.id === id) {
result = node as ConfigurationNode<T>;
break;
}
}
return result;
}
/**
* Register a configuration to the registry.
*/
registerConfiguration(configuration: ConfigurationNode<any>) {
const config = this.findConfiguration(configuration.id);
if (config && config.overridable === false) {
console.warn(`cannot override configuration: ${config.id}`);
return;
}
this.schemaValidator.addSchema(configuration);
this.deregisterConfiguration(configuration);
this.configurationNodes.push(configuration as ConfigurationNode<unknown>);
const scope = this.getStorage(configuration);
this.addStorage(scope);
}
/**
* Register multiple configurations to the registry.
*/
registerConfigurations(configurations: ConfigurationNode<any>[]) {
configurations.forEach((config) => {
this.registerConfiguration(config);
});
}
/**
* Deregister multiple configurations from the registry.
*/
deregisterConfigurations(configurations: ConfigurationNode<any>[]) {
configurations.forEach((config) => {
this.deregisterConfiguration(config);
});
}
deregisterConfiguration(configuration: ConfigurationNode<any>) {
const currentConfig = this.findConfiguration(configuration.id);
if (!currentConfig) {
return;
}
const index = this.configurationNodes.findIndex(
(item) => item.id === configuration.id,
);
if (index >= 0) {
this.configurationNodes.splice(index, 1);
}
}
getConfigurationsByScope(
storage: ConfigurationStorage,
): ConfigurationNode<unknown>[] | undefined {
return this.configurationNodes.filter((node) => {
const nodeStorage = node.storage ?? DefaultConfigurationStorage;
return nodeStorage === storage;
});
}
getConfigurationsByStorage(
storage: ConfigurationStorage,
): ConfigurationNode<unknown>[] | undefined {
return this.configurationNodes.filter((node) => {
return this.getStorage(node) === storage;
});
}
getStorages() {
return Array.from(this.scopes.values());
}
/**
* 添加scope,已包含错误处理
*/
addStorage(scope: ConfigurationStorage) {
if (
this.scopes.get(scope.id) &&
this.scopes.get(scope.id)?.priority !== scope.priority
) {
console.warn('添加scope时,scopeId相同,priority不一致');
return;
}
if (!this.scopes.has(scope.id)) {
this.scopes.set(scope.id, scope);
}
}
getConfigurationByNamespace(
namespace: string[],
isFullMatch = true,
): ConfigurationNode<unknown>[] {
const nodes = [];
for (const node of this.configurationNodes) {
const nodeNamespace = this.getNamespace(node);
if (isFullMatch) {
if (nodeNamespace.join('.') === namespace.join('.')) {
nodes.push(node);
}
} else {
if (nodeNamespace.join('.').startsWith(namespace.join('.'))) {
nodes.push(node);
}
}
}
return nodes;
}
getNamespace(node: ConfigurationNode<any>): string[] {
const segments = node.id.split('.').filter((item) => item.trim() !== '');
if (segments.length <= 1) {
throw new Error('invalid configuration! id must have dot, eg. editor.fontsize');
}
return segments.slice(0, segments.length - 1);
}
getRootNamespaces(): string[] {
const ids = this.configurationNodes
.map((item) => item.id.split('.').at(0))
.filter((item) => item !== undefined) as string[];
return Array.from(new Set(ids));
}
getChildNamespaces(namespace: string[]): string[][] {
if (namespace.length === 0) {
console.warn('invalid namespace');
return [];
}
return this.configurationNodes
.map((item) => {
const nodeNamespace = this.getNamespace(item);
if (
namespace.length < nodeNamespace.length &&
nodeNamespace.join('.').startsWith(namespace.join('.'))
) {
return nodeNamespace.slice(0, namespace.length + 1);
}
return [];
})
.filter((item) => item.length > 0);
}
}