backend-app-api: update ServiceRegistry for new factory definition

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:
Patrik Oldsberg
2023-01-13 16:07:03 +01:00
parent d6f4b92097
commit eadc8f7df8
2 changed files with 60 additions and 45 deletions
@@ -29,9 +29,7 @@ const sf1 = createServiceFactory({
service: ref1,
deps: {},
async factory() {
return async () => {
return { x: 1 };
};
return { x: 1 };
},
})();
@@ -61,7 +59,7 @@ const refDefault1 = createServiceRef<{ x: number }>({
service,
deps: {},
async factory() {
return async () => ({ x: 10 });
return { x: 10 };
},
})(),
});
@@ -73,7 +71,7 @@ const refDefault2a = createServiceRef<{ x: number }>({
service,
deps: {},
async factory() {
return async () => ({ x: 20 });
return { x: 20 };
},
}),
});
@@ -85,7 +83,7 @@ const refDefault2b = createServiceRef<{ x: number }>({
service,
deps: {},
async factory() {
return async () => ({ x: 220 });
return { x: 220 };
},
}),
});
@@ -144,7 +142,7 @@ describe('ServiceRegistry', () => {
service: ref1,
deps: { rootDep: ref2 },
factory: async ({ rootDep }) => {
return async () => ({ x: rootDep.x });
return { x: rootDep.x };
},
});
const registry = new ServiceRegistry([factory(), sf2]);
@@ -173,8 +171,8 @@ describe('ServiceRegistry', () => {
const factory = createServiceFactory({
service: ref,
deps: { meta: coreServices.pluginMetadata },
async factory() {
return async ({ meta }) => ({ pluginId: meta.getId() });
async factory({ meta }) {
return { pluginId: meta.getId() };
},
});
const registry = new ServiceRegistry([factory()]);
@@ -224,13 +222,11 @@ describe('ServiceRegistry', () => {
});
it('should only call each default factory loader once', async () => {
const factoryLoader = jest.fn(async (service: ServiceRef<void>) =>
const factoryLoader = jest.fn(async (service: ServiceRef<void, 'plugin'>) =>
createServiceFactory({
service,
deps: {},
async factory() {
return async () => {};
},
async factory() {},
}),
);
const ref = createServiceRef<void>({
@@ -247,10 +243,31 @@ describe('ServiceRegistry', () => {
});
it('should not call factory functions more than once', async () => {
const innerFactory = jest.fn(async () => {
return { x: 1 };
const createRootContext = jest.fn(async () => ({ x: 1 }));
const factory = jest.fn(async () => ({ x: 1 }));
const myFactory = createServiceFactory({
service: ref1,
deps: {},
createRootContext,
factory,
});
const factory = jest.fn(async () => innerFactory);
const registry = new ServiceRegistry([myFactory()]);
await Promise.all([
registry.get(ref1, 'catalog')!,
registry.get(ref1, 'catalog')!,
registry.get(ref1, 'catalog')!,
registry.get(ref1, 'scaffolder')!,
registry.get(ref1, 'scaffolder')!,
]);
expect(createRootContext).toHaveBeenCalledTimes(1);
expect(factory).toHaveBeenCalledTimes(2);
});
it('should not call factory functions more than once without root context', async () => {
const factory = jest.fn(async () => ({ x: 1 }));
const myFactory = createServiceFactory({
service: ref1,
deps: {},
@@ -267,8 +284,7 @@ describe('ServiceRegistry', () => {
registry.get(ref1, 'scaffolder')!,
]);
expect(factory).toHaveBeenCalledTimes(1);
expect(innerFactory).toHaveBeenCalledTimes(2);
expect(factory).toHaveBeenCalledTimes(2);
});
it('should throw if dependencies are not available', async () => {
@@ -296,9 +312,7 @@ describe('ServiceRegistry', () => {
const factoryA = createServiceFactory({
service: refA,
deps: { b: refB },
async factory() {
return async ({ b }) => b;
},
factory: async ({ b }) => b,
});
const factoryB = createServiceFactory({
@@ -320,15 +334,18 @@ describe('ServiceRegistry', () => {
const myFactory = createServiceFactory({
service: ref1,
deps: {},
factory() {
createRootContext() {
throw new Error('top-level error');
},
factory() {
throw new Error(`error in plugin`);
},
});
const registry = new ServiceRegistry([myFactory()]);
await expect(registry.get(ref1, 'catalog')).rejects.toThrow(
"Failed to instantiate service '1' because the top-level factory function threw an error, Error: top-level error",
"Failed to instantiate service '1' because createRootContext threw an error, Error: top-level error",
);
});
@@ -337,9 +354,7 @@ describe('ServiceRegistry', () => {
service: ref1,
deps: {},
async factory() {
return () => {
throw new Error(`error in plugin`);
};
throw new Error(`error in plugin`);
},
});
@@ -37,12 +37,14 @@ export class ServiceRegistry implements EnumerableServiceHolder {
readonly #implementations: Map<
ServiceFactory,
{
factoryFunc: Promise<
(deps: { [name in string]: unknown }) => Promise<unknown>
>;
context: Promise<unknown>;
byPlugin: Map<string, Promise<unknown>>;
}
>;
readonly #rootServiceImplementations = new Map<
ServiceFactory,
Promise<unknown>
>();
constructor(factories: Array<ServiceFactory<unknown>>) {
this.#providedFactories = new Map(factories.map(f => [f.service.id, f]));
@@ -56,15 +58,13 @@ export class ServiceRegistry implements EnumerableServiceHolder {
): Promise<ServiceFactory> | undefined {
// Special case handling of the plugin metadata service, generating a custom factory for it each time
if (ref.id === coreServices.pluginMetadata.id) {
return Promise.resolve({
return Promise.resolve<
ServiceFactory<typeof coreServices.pluginMetadata.T>
>({
scope: 'plugin',
service: coreServices.pluginMetadata,
deps: {},
factory: async () => async () => ({
getId() {
return pluginId;
},
}),
factory: async () => ({ getId: () => pluginId }),
});
}
@@ -100,8 +100,6 @@ export class ServiceRegistry implements EnumerableServiceHolder {
return Promise.resolve(resolvedFactory);
}
#separateMapForTheRootService = new Map<ServiceFactory, Promise<unknown>>();
#checkForMissingDeps(factory: ServiceFactory, pluginId: string) {
const missingDeps = Object.values(factory.deps).filter(ref => {
if (ref.id === coreServices.pluginMetadata.id) {
@@ -129,7 +127,7 @@ export class ServiceRegistry implements EnumerableServiceHolder {
get<T>(ref: ServiceRef<T>, pluginId: string): Promise<T> | undefined {
return this.#resolveFactory(ref, pluginId)?.then(factory => {
if (factory.scope === 'root') {
let existing = this.#separateMapForTheRootService.get(factory);
let existing = this.#rootServiceImplementations.get(factory);
if (!existing) {
this.#checkForMissingDeps(factory, pluginId);
const rootDeps = new Array<Promise<[name: string, impl: unknown]>>();
@@ -147,7 +145,7 @@ export class ServiceRegistry implements EnumerableServiceHolder {
existing = Promise.all(rootDeps).then(entries =>
factory.factory(Object.fromEntries(entries)),
);
this.#separateMapForTheRootService.set(factory, existing);
this.#rootServiceImplementations.set(factory, existing);
}
return existing as Promise<T>;
}
@@ -165,12 +163,14 @@ export class ServiceRegistry implements EnumerableServiceHolder {
}
implementation = {
factoryFunc: Promise.all(rootDeps)
.then(entries => factory.factory(Object.fromEntries(entries)))
context: Promise.all(rootDeps)
.then(entries =>
factory.createRootContext?.(Object.fromEntries(entries)),
)
.catch(error => {
const cause = stringifyError(error);
throw new Error(
`Failed to instantiate service '${ref.id}' because the top-level factory function threw an error, ${cause}`,
`Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`,
);
}),
byPlugin: new Map(),
@@ -188,10 +188,10 @@ export class ServiceRegistry implements EnumerableServiceHolder {
allDeps.push(target.then(impl => [name, impl]));
}
result = implementation.factoryFunc
.then(func =>
result = implementation.context
.then(context =>
Promise.all(allDeps).then(entries =>
func(Object.fromEntries(entries)),
factory.factory(Object.fromEntries(entries), context),
),
)
.catch(error => {