Skip to content

Commit 2f2900f

Browse files
authored
Merge pull request #246 from proto-kit/feature/closeable
Added closeable collection and shutdown mechanism
2 parents 956ba9a + 3f084ed commit 2f2900f

File tree

30 files changed

+361
-35
lines changed

30 files changed

+361
-35
lines changed

packages/api/src/graphql/GraphqlSequencerModule.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from "node:assert";
22

3-
import { SequencerModule } from "@proto-kit/sequencer";
3+
import { Closeable, closeable, SequencerModule } from "@proto-kit/sequencer";
44
import {
55
ChildContainerProvider,
66
Configurable,
@@ -29,9 +29,10 @@ export interface GraphqlModulesDefintion<
2929
config?: ModulesConfig<GraphQLModules>;
3030
}
3131

32+
@closeable()
3233
export class GraphqlSequencerModule<GraphQLModules extends GraphqlModulesRecord>
3334
extends ModuleContainer<GraphQLModules>
34-
implements Configurable<unknown>, SequencerModule<unknown>
35+
implements Configurable<unknown>, SequencerModule<unknown>, Closeable
3536
{
3637
public static from<GraphQLModules extends GraphqlModulesRecord>(
3738
definition: GraphqlModulesDefintion<GraphQLModules>
@@ -93,4 +94,10 @@ export class GraphqlSequencerModule<GraphQLModules extends GraphqlModulesRecord>
9394
}
9495
await this.graphqlServer.startServer();
9596
}
97+
98+
public async close() {
99+
if (this.graphqlServer !== undefined) {
100+
await this.graphqlServer.close();
101+
}
102+
}
96103
}

packages/api/src/graphql/GraphqlServer.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,18 @@ export class GraphqlServer extends SequencerModule<GraphqlServerOptions> {
145145
});
146146
}
147147

148-
public close() {
149-
this.server?.close();
148+
public async close() {
149+
if (this.server !== undefined) {
150+
const { server } = this;
151+
152+
await new Promise<void>((res) => {
153+
server.close((error) => {
154+
if (error !== undefined) {
155+
log.error(error);
156+
}
157+
res();
158+
});
159+
});
160+
}
150161
}
151162
}

packages/common/src/config/ModuleContainer.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from "./ConfigurableModule";
2929
import { ChildContainerProvider } from "./ChildContainerProvider";
3030
import { ChildContainerCreatable } from "./ChildContainerCreatable";
31+
import { getInjectAliases } from "./injectAlias";
3132

3233
const errors = {
3334
configNotSetInContainer: (moduleName: string) =>
@@ -228,6 +229,16 @@ export class ModuleContainer<
228229
}
229230
}
230231

232+
protected registerAliases(originalToken: string, clas: TypedClass<any>) {
233+
const aliases = getInjectAliases(clas);
234+
235+
aliases.forEach((alias) =>
236+
this.container.register(alias, {
237+
useToken: originalToken,
238+
})
239+
);
240+
}
241+
231242
/**
232243
* Register modules into the current container, and registers
233244
* a respective resolution hook in order to decorate the module
@@ -250,6 +261,8 @@ export class ModuleContainer<
250261
{ lifecycle: Lifecycle.ContainerScoped }
251262
);
252263
this.onAfterModuleResolution(moduleName);
264+
265+
this.registerAliases(moduleName, useClass);
253266
}
254267
});
255268
}
@@ -412,7 +425,11 @@ export class ModuleContainer<
412425
this.container.register(key, declaration, {
413426
lifecycle: Lifecycle.Singleton,
414427
});
415-
// eslint-disable-next-line sonarjs/no-duplicated-branches
428+
this.registerAliases(
429+
key,
430+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
431+
declaration.useClass as TypedClass<unknown>
432+
);
416433
} else if (isTokenProvider(declaration)) {
417434
this.container.register(key, declaration, {
418435
lifecycle: Lifecycle.Singleton,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { TypedClass } from "../types";
2+
3+
export const injectAliasMetadataKey = "protokit-inject-alias";
4+
5+
/**
6+
* Attaches metadata to the class that the ModuleContainer can pick up
7+
* and inject this class in the DI container under the specified aliases.
8+
* This method supports inheritance, therefore also gets aliases defined
9+
* on superclasses
10+
*/
11+
export function injectAlias(aliases: string[]) {
12+
return (target: TypedClass<unknown>) => {
13+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
14+
const superAliases = Reflect.getMetadata(
15+
injectAliasMetadataKey,
16+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
17+
Object.getPrototypeOf(target)
18+
) as string[] | undefined;
19+
20+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
21+
const existingAliases = Reflect.getMetadata(
22+
injectAliasMetadataKey,
23+
target
24+
) as string[] | undefined;
25+
26+
let allAliases = aliases;
27+
28+
if (superAliases !== undefined) {
29+
allAliases = allAliases.concat(superAliases);
30+
}
31+
if (existingAliases !== undefined) {
32+
allAliases = allAliases.concat(existingAliases);
33+
}
34+
35+
Reflect.defineMetadata(
36+
injectAliasMetadataKey,
37+
allAliases.filter(
38+
(value, index, array) => array.indexOf(value) === index
39+
),
40+
target
41+
);
42+
};
43+
}
44+
45+
/**
46+
* Marks the class to implement a certain interface T, while also attaching
47+
* a DI-injection alias as metadata, that will be picked up by the ModuleContainer
48+
* to allow resolving by that interface name
49+
* @param name The name of the injection alias, convention is to use the same as the name of T
50+
*/
51+
export function implement<T>(name: string) {
52+
return (
53+
/**
54+
* Check if the target class extends RuntimeModule, while
55+
* also providing static config presets
56+
*/
57+
target: TypedClass<T>
58+
) => {
59+
injectAlias([name])(target);
60+
};
61+
}
62+
63+
export function getInjectAliases(target: TypedClass<unknown>): string[] {
64+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
65+
const aliases = Reflect.getMetadata(
66+
injectAliasMetadataKey,
67+
target
68+
) as string[];
69+
return aliases ?? [];
70+
}

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from "./compiling/AtomicCompileHelper";
2222
export * from "./compiling/CompileRegistry";
2323
export * from "./compiling/CompilableModule";
2424
export * from "./compiling/services/ChildVerificationKeyService";
25+
export * from "./config/injectAlias";

packages/common/test/config/ModuleContainer.test.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
ModulesRecord,
1111
} from "../../src/config/ModuleContainer";
1212
import { TypedClass } from "../../src/types";
13-
import { DependencyFactory } from "../../src";
13+
import { DependencyFactory, expectDefined } from "../../src";
14+
import { injectAlias } from "../../src/config/injectAlias";
1415

1516
// module container will accept modules that extend this type
1617
class BaseTestModule<Config> extends ConfigurableModule<Config> {}
@@ -24,6 +25,7 @@ interface TestModuleConfig {
2425
}
2526

2627
@injectable()
28+
@injectAlias(["child-alias", "multi-alias"])
2729
class ChildModule extends BaseTestModule<NoConfig> {
2830
public constructor(@inject("TestModule") public readonly testModule: any) {
2931
super();
@@ -34,6 +36,7 @@ class ChildModule extends BaseTestModule<NoConfig> {
3436
}
3537
}
3638

39+
@injectAlias(["base-alias", "multi-alias"])
3740
class TestModule
3841
extends BaseTestModule<TestModuleConfig>
3942
implements DependencyFactory
@@ -66,7 +69,11 @@ class WrongTestModule {}
6669

6770
class TestModuleContainer<
6871
Modules extends TestModulesRecord,
69-
> extends ModuleContainer<Modules> {}
72+
> extends ModuleContainer<Modules> {
73+
public get dependencyContainer() {
74+
return this.container;
75+
}
76+
}
7077

7178
describe("moduleContainer", () => {
7279
let container: TestModuleContainer<{
@@ -169,4 +176,40 @@ describe("moduleContainer", () => {
169176
expect(config.testConfigProperty2).toBe(2);
170177
expect(config.testConfigProperty3).toBe(undefined);
171178
});
179+
180+
it("should resolve dependencies correctly via alias", () => {
181+
container.configure({
182+
TestModule: {
183+
testConfigProperty,
184+
},
185+
186+
OtherTestModule: {
187+
otherTestConfigProperty: testConfigProperty,
188+
},
189+
});
190+
191+
container.create(() => tsyringeContainer.createChildContainer());
192+
193+
// Unfortunately we still need this so that the dependencies are registered
194+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
195+
const m1 = container.resolve("base-alias" as any);
196+
const m2 = container.resolve("TestModule");
197+
198+
expectDefined(m1);
199+
// Check if its the same reference
200+
expect(m1).toBe(m2);
201+
202+
const dm1 = container.resolve("child-alias" as any) as ChildModule;
203+
const dm2 = container.resolve("DependencyModule1");
204+
205+
expect(dm1.x()).toBe("dependency factory works");
206+
expect(dm1.testModule).toBeDefined();
207+
expect(dm1).toBe(dm2);
208+
209+
const multi =
210+
container.dependencyContainer.resolveAll<BaseTestModule<unknown>>(
211+
"multi-alias"
212+
);
213+
expect(multi).toHaveLength(2);
214+
});
172215
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import "reflect-metadata";
2+
import { getInjectAliases, injectAlias } from "../../src/config/injectAlias";
3+
4+
@injectAlias(["foo", "bar"])
5+
class TestClass {}
6+
7+
@injectAlias(["ayy"])
8+
class TestClass2 extends TestClass {}
9+
10+
describe("injectAlias metadata", () => {
11+
it("set and retrieve", () => {
12+
expect.assertions(2);
13+
14+
const aliases = getInjectAliases(TestClass);
15+
16+
expect(aliases).toHaveLength(2);
17+
expect(aliases).toStrictEqual(["foo", "bar"]);
18+
});
19+
20+
it("recursive", () => {
21+
expect.assertions(2);
22+
23+
const aliases = getInjectAliases(TestClass2);
24+
25+
expect(aliases).toHaveLength(3);
26+
expect(aliases).toStrictEqual(["ayy", "foo", "bar"]);
27+
});
28+
});

packages/deployment/src/queue/BullQueue.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
InstantiatedQueue,
77
TaskQueue,
88
AbstractTaskQueue,
9+
closeable,
910
} from "@proto-kit/sequencer";
1011

1112
import { InstantiatedBullQueue } from "./InstantiatedBullQueue";
@@ -24,9 +25,10 @@ export interface BullQueueConfig {
2425
/**
2526
* TaskQueue implementation for BullMQ
2627
*/
28+
@closeable()
2729
export class BullQueue
2830
extends AbstractTaskQueue<BullQueueConfig>
29-
implements TaskQueue
31+
implements TaskQueue, Closeable
3032
{
3133
private activePromise?: Promise<void>;
3234

@@ -101,4 +103,10 @@ export class BullQueue
101103
public async start() {
102104
noop();
103105
}
106+
107+
public async close() {
108+
await this.closeQueues();
109+
110+
// Closing of active workers is handled by the LocalTaskWorkerModule
111+
}
104112
}

packages/indexer/test/GeneratedResolverFactoryGraphqlModule.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe("GeneratedResolverFactoryGraphqlModule", () => {
139139
}
140140
});
141141

142-
afterAll(() => {
143-
indexer.resolve("GraphqlServer").close();
142+
afterAll(async () => {
143+
await indexer.resolve("GraphqlServer").close();
144144
});
145145
});

packages/persistance/src/PrismaRedisDatabase.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
SequencerModule,
44
StorageDependencyMinimumDependencies,
55
Database,
6+
closeable,
67
} from "@proto-kit/sequencer";
78
import { ChildContainerProvider } from "@proto-kit/common";
89
import { PrismaClient } from "@prisma/client";
@@ -26,6 +27,7 @@ export interface PrismaRedisCombinedConfig {
2627
}
2728

2829
@sequencerModule()
30+
@closeable()
2931
export class PrismaRedisDatabase
3032
extends SequencerModule<PrismaRedisCombinedConfig>
3133
implements PrismaConnection, RedisConnection, Database

0 commit comments

Comments
 (0)