backend-{plugin,app}-api: introduce service scopes and update registry to support
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: blam <ben@blam.sh> Co-authored-by: Johan Haals <johan.haals@gmail.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -19,63 +19,84 @@
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ServiceRef<T> = {
|
||||
export type ServiceRef<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin' = 'root' | 'plugin',
|
||||
> = {
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* This determines the scope at which this service is available.
|
||||
*
|
||||
* Root scoped services are available to all other services but
|
||||
* may only depend on other root scoped services.
|
||||
*
|
||||
* Plugin scoped services are only available to other plugin scoped
|
||||
* services but may depend on all other services.
|
||||
*/
|
||||
scope: TScope;
|
||||
|
||||
/**
|
||||
* Utility for getting the type of the service, using `typeof serviceRef.T`.
|
||||
* Attempting to actually read this value will result in an exception.
|
||||
*/
|
||||
T: T;
|
||||
T: TService;
|
||||
|
||||
toString(): string;
|
||||
|
||||
$$ref: 'service';
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type InternalServiceRef<T> = ServiceRef<T> & {
|
||||
/**
|
||||
* The default factory that will be used to create service
|
||||
* instances if no other factory is provided.
|
||||
*/
|
||||
__defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type TypesToServiceRef<T> = { [key in keyof T]: ServiceRef<T[key]> };
|
||||
|
||||
/** @public */
|
||||
export type DepsToDepFactories<T> = {
|
||||
[key in keyof T]: (pluginId: string) => Promise<T[key]>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type FactoryFunc<Impl> = (pluginId: string) => Promise<Impl>;
|
||||
|
||||
/** @public */
|
||||
export type ServiceFactory<TService = unknown> = {
|
||||
service: ServiceRef<TService>;
|
||||
deps: { [key in string]: ServiceRef<unknown> };
|
||||
factory(deps: { [key in string]: unknown }): Promise<FactoryFunc<TService>>;
|
||||
};
|
||||
export type ServiceFactory<TService = unknown> =
|
||||
| {
|
||||
// This scope prop is needed in addition to the service ref, as TypeScript
|
||||
// can't properly discriminate the two factory types otherwise.
|
||||
scope: 'root';
|
||||
service: ServiceRef<TService, 'root'>;
|
||||
deps: { [key in string]: ServiceRef<unknown> };
|
||||
factory(deps: { [key in string]: unknown }): Promise<TService>;
|
||||
}
|
||||
| {
|
||||
scope: 'plugin';
|
||||
service: ServiceRef<TService, 'plugin'>;
|
||||
deps: { [key in string]: ServiceRef<unknown> };
|
||||
factory(deps: { [key in string]: unknown }): Promise<
|
||||
(deps: { [key in string]: unknown }) => Promise<TService>
|
||||
>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function createServiceRef<T>(options: {
|
||||
id: string;
|
||||
scope?: 'plugin';
|
||||
defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
}): ServiceRef<T> {
|
||||
const { id, defaultFactory } = options;
|
||||
}): ServiceRef<T, 'plugin'>;
|
||||
export function createServiceRef<T>(options: {
|
||||
id: string;
|
||||
scope: 'root';
|
||||
defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
}): ServiceRef<T, 'root'>;
|
||||
export function createServiceRef<T>(options: {
|
||||
id: string;
|
||||
scope?: 'plugin' | 'root';
|
||||
defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
}): ServiceRef<T, 'plugin' | 'root'> {
|
||||
const { id, scope = 'plugin', defaultFactory } = options;
|
||||
return {
|
||||
id,
|
||||
scope,
|
||||
get T(): T {
|
||||
throw new Error(`tried to read ServiceRef.T of ${this}`);
|
||||
},
|
||||
@@ -84,32 +105,58 @@ export function createServiceRef<T>(options: {
|
||||
},
|
||||
$$ref: 'service', // TODO: declare
|
||||
__defaultFactory: defaultFactory,
|
||||
} as InternalServiceRef<T>;
|
||||
} as ServiceRef<T, typeof scope> & {
|
||||
__defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
};
|
||||
}
|
||||
|
||||
type OnlyRootScopeDependencies<
|
||||
TDeps extends { [key in string]: ServiceRef<unknown> },
|
||||
> = Pick<
|
||||
TDeps,
|
||||
{
|
||||
[name in keyof TDeps]: TDeps[name]['scope'] extends 'root' ? name : never;
|
||||
}[keyof TDeps]
|
||||
>;
|
||||
|
||||
type DependencyRefsToInstances<
|
||||
T extends { [key in string]: ServiceRef<unknown> },
|
||||
> = {
|
||||
[key in keyof T]: T[key] extends ServiceRef<infer TImpl> ? TImpl : never;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: unknown },
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TOpts extends { [name in string]: unknown } | undefined = undefined,
|
||||
>(factory: {
|
||||
service: ServiceRef<TService>;
|
||||
deps: TypesToServiceRef<TDeps>;
|
||||
>(config: {
|
||||
service: ServiceRef<TService, TScope>;
|
||||
deps: TDeps;
|
||||
factory(
|
||||
deps: DepsToDepFactories<TDeps>,
|
||||
deps: DependencyRefsToInstances<OnlyRootScopeDependencies<TDeps>>,
|
||||
options: TOpts,
|
||||
): Promise<FactoryFunc<TImpl>>;
|
||||
): TScope extends 'root'
|
||||
? Promise<TImpl>
|
||||
: Promise<(deps: DependencyRefsToInstances<TDeps>) => Promise<TImpl>>;
|
||||
}): undefined extends TOpts
|
||||
? (options?: TOpts) => ServiceFactory<TService>
|
||||
: (options: TOpts) => ServiceFactory<TService> {
|
||||
return (options?: TOpts) => ({
|
||||
service: factory.service,
|
||||
deps: factory.deps,
|
||||
factory(deps: DepsToDepFactories<TDeps>) {
|
||||
return factory.factory(deps, options!);
|
||||
},
|
||||
});
|
||||
return (options?: TOpts) =>
|
||||
({
|
||||
scope: config.service.scope,
|
||||
service: config.service,
|
||||
deps: config.deps,
|
||||
factory(
|
||||
deps: DependencyRefsToInstances<OnlyRootScopeDependencies<TDeps>>,
|
||||
) {
|
||||
return config.factory(deps, options!);
|
||||
},
|
||||
} as ServiceFactory<TService>);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user