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:
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user