@proto-kit/common 0.1.1-develop.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/LICENSE.md +201 -0
  2. package/dist/compiling/AtomicCompileHelper.d.ts +13 -0
  3. package/dist/compiling/AtomicCompileHelper.d.ts.map +1 -0
  4. package/dist/compiling/AtomicCompileHelper.js +40 -0
  5. package/dist/compiling/AtomicCompileHelper.js.map +1 -0
  6. package/dist/compiling/CompilableModule.d.ts +6 -0
  7. package/dist/compiling/CompilableModule.d.ts.map +1 -0
  8. package/dist/compiling/CompilableModule.js +2 -0
  9. package/dist/compiling/CompilableModule.js.map +1 -0
  10. package/dist/compiling/CompileRegistry.d.ts +26 -0
  11. package/dist/compiling/CompileRegistry.d.ts.map +1 -0
  12. package/dist/compiling/CompileRegistry.js +68 -0
  13. package/dist/compiling/CompileRegistry.js.map +1 -0
  14. package/dist/compiling/services/ChildVerificationKeyService.d.ts +10 -0
  15. package/dist/compiling/services/ChildVerificationKeyService.d.ts.map +1 -0
  16. package/dist/compiling/services/ChildVerificationKeyService.js +27 -0
  17. package/dist/compiling/services/ChildVerificationKeyService.js.map +1 -0
  18. package/dist/config/ChildContainerCreatable.d.ts +5 -0
  19. package/dist/config/ChildContainerCreatable.d.ts.map +1 -0
  20. package/dist/config/ChildContainerCreatable.js +2 -0
  21. package/dist/config/ChildContainerCreatable.js.map +1 -0
  22. package/dist/config/ChildContainerProvider.d.ts +5 -0
  23. package/dist/config/ChildContainerProvider.d.ts.map +1 -0
  24. package/dist/config/ChildContainerProvider.js +2 -0
  25. package/dist/config/ChildContainerProvider.js.map +1 -0
  26. package/dist/config/ConfigurableModule.d.ts +25 -0
  27. package/dist/config/ConfigurableModule.d.ts.map +1 -0
  28. package/dist/config/ConfigurableModule.js +24 -0
  29. package/dist/config/ConfigurableModule.js.map +1 -0
  30. package/dist/config/ModuleContainer.d.ts +162 -0
  31. package/dist/config/ModuleContainer.d.ts.map +1 -0
  32. package/dist/config/ModuleContainer.js +289 -0
  33. package/dist/config/ModuleContainer.js.map +1 -0
  34. package/dist/config/injectAlias.d.ts +18 -0
  35. package/dist/config/injectAlias.d.ts.map +1 -0
  36. package/dist/config/injectAlias.js +47 -0
  37. package/dist/config/injectAlias.js.map +1 -0
  38. package/dist/dependencyFactory/DependencyFactory.d.ts +29 -0
  39. package/dist/dependencyFactory/DependencyFactory.d.ts.map +1 -0
  40. package/dist/dependencyFactory/DependencyFactory.js +2 -0
  41. package/dist/dependencyFactory/DependencyFactory.js.map +1 -0
  42. package/dist/dependencyFactory/injectOptional.d.ts +16 -0
  43. package/dist/dependencyFactory/injectOptional.d.ts.map +1 -0
  44. package/dist/dependencyFactory/injectOptional.js +40 -0
  45. package/dist/dependencyFactory/injectOptional.js.map +1 -0
  46. package/dist/dummyVerificationKey.d.ts +3 -0
  47. package/dist/dummyVerificationKey.d.ts.map +1 -0
  48. package/dist/dummyVerificationKey.js +8 -0
  49. package/dist/dummyVerificationKey.js.map +1 -0
  50. package/dist/events/EventEmitter.d.ts +19 -0
  51. package/dist/events/EventEmitter.d.ts.map +1 -0
  52. package/dist/events/EventEmitter.js +35 -0
  53. package/dist/events/EventEmitter.js.map +1 -0
  54. package/dist/events/EventEmitterProxy.d.ts +18 -0
  55. package/dist/events/EventEmitterProxy.d.ts.map +1 -0
  56. package/dist/events/EventEmitterProxy.js +35 -0
  57. package/dist/events/EventEmitterProxy.js.map +1 -0
  58. package/dist/events/EventEmittingComponent.d.ts +9 -0
  59. package/dist/events/EventEmittingComponent.d.ts.map +1 -0
  60. package/dist/events/EventEmittingComponent.js +2 -0
  61. package/dist/events/EventEmittingComponent.js.map +1 -0
  62. package/dist/events/ReplayingSingleUseEventEmitter.d.ts +17 -0
  63. package/dist/events/ReplayingSingleUseEventEmitter.d.ts.map +1 -0
  64. package/dist/events/ReplayingSingleUseEventEmitter.js +34 -0
  65. package/dist/events/ReplayingSingleUseEventEmitter.js.map +1 -0
  66. package/dist/index.d.ts +26 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +26 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/log.d.ts +37 -0
  71. package/dist/log.d.ts.map +1 -0
  72. package/dist/log.js +114 -0
  73. package/dist/log.js.map +1 -0
  74. package/dist/trees/InMemoryMerkleTreeStorage.d.ts +11 -0
  75. package/dist/trees/InMemoryMerkleTreeStorage.d.ts.map +1 -0
  76. package/dist/trees/InMemoryMerkleTreeStorage.js +13 -0
  77. package/dist/trees/InMemoryMerkleTreeStorage.js.map +1 -0
  78. package/dist/trees/MerkleTreeStore.d.ts +5 -0
  79. package/dist/trees/MerkleTreeStore.d.ts.map +1 -0
  80. package/dist/trees/MerkleTreeStore.js +2 -0
  81. package/dist/trees/MerkleTreeStore.js.map +1 -0
  82. package/dist/trees/MockAsyncMerkleStore.d.ts +9 -0
  83. package/dist/trees/MockAsyncMerkleStore.d.ts.map +1 -0
  84. package/dist/trees/MockAsyncMerkleStore.js +20 -0
  85. package/dist/trees/MockAsyncMerkleStore.js.map +1 -0
  86. package/dist/trees/RollupMerkleTree.d.ts +147 -0
  87. package/dist/trees/RollupMerkleTree.d.ts.map +1 -0
  88. package/dist/trees/RollupMerkleTree.js +218 -0
  89. package/dist/trees/RollupMerkleTree.js.map +1 -0
  90. package/dist/trees/VirtualMerkleTreeStore.d.ts +13 -0
  91. package/dist/trees/VirtualMerkleTreeStore.d.ts.map +1 -0
  92. package/dist/trees/VirtualMerkleTreeStore.js +18 -0
  93. package/dist/trees/VirtualMerkleTreeStore.js.map +1 -0
  94. package/dist/types.d.ts +27 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +12 -0
  97. package/dist/types.js.map +1 -0
  98. package/dist/utils.d.ts +48 -0
  99. package/dist/utils.d.ts.map +1 -0
  100. package/dist/utils.js +113 -0
  101. package/dist/utils.js.map +1 -0
  102. package/dist/zkProgrammable/ProvableMethodExecutionContext.d.ts +54 -0
  103. package/dist/zkProgrammable/ProvableMethodExecutionContext.d.ts.map +1 -0
  104. package/dist/zkProgrammable/ProvableMethodExecutionContext.js +97 -0
  105. package/dist/zkProgrammable/ProvableMethodExecutionContext.js.map +1 -0
  106. package/dist/zkProgrammable/ZkProgrammable.d.ts +40 -0
  107. package/dist/zkProgrammable/ZkProgrammable.d.ts.map +1 -0
  108. package/dist/zkProgrammable/ZkProgrammable.js +79 -0
  109. package/dist/zkProgrammable/ZkProgrammable.js.map +1 -0
  110. package/dist/zkProgrammable/provableMethod.d.ts +19 -0
  111. package/dist/zkProgrammable/provableMethod.d.ts.map +1 -0
  112. package/dist/zkProgrammable/provableMethod.js +71 -0
  113. package/dist/zkProgrammable/provableMethod.js.map +1 -0
  114. package/jest.config.cjs +12 -0
  115. package/package.json +34 -0
  116. package/src/compiling/AtomicCompileHelper.ts +62 -0
  117. package/src/compiling/CompilableModule.ts +6 -0
  118. package/src/compiling/CompileRegistry.ts +78 -0
  119. package/src/compiling/services/ChildVerificationKeyService.ts +26 -0
  120. package/src/config/ChildContainerCreatable.ts +5 -0
  121. package/src/config/ChildContainerProvider.ts +5 -0
  122. package/src/config/ConfigurableModule.ts +57 -0
  123. package/src/config/ModuleContainer.ts +487 -0
  124. package/src/config/injectAlias.ts +70 -0
  125. package/src/dependencyFactory/DependencyFactory.ts +57 -0
  126. package/src/dependencyFactory/injectOptional.ts +41 -0
  127. package/src/dummyVerificationKey.ts +10 -0
  128. package/src/events/EventEmitter.ts +61 -0
  129. package/src/events/EventEmitterProxy.ts +83 -0
  130. package/src/events/EventEmittingComponent.ts +11 -0
  131. package/src/events/ReplayingSingleUseEventEmitter.ts +42 -0
  132. package/src/index.ts +25 -0
  133. package/src/log.ts +143 -0
  134. package/src/trees/InMemoryMerkleTreeStorage.ts +17 -0
  135. package/src/trees/MerkleTreeStore.ts +5 -0
  136. package/src/trees/MockAsyncMerkleStore.ts +30 -0
  137. package/src/trees/RollupMerkleTree.ts +356 -0
  138. package/src/trees/VirtualMerkleTreeStore.ts +20 -0
  139. package/src/types.ts +58 -0
  140. package/src/utils.ts +200 -0
  141. package/src/zkProgrammable/ProvableMethodExecutionContext.ts +122 -0
  142. package/src/zkProgrammable/ZkProgrammable.ts +151 -0
  143. package/src/zkProgrammable/provableMethod.ts +124 -0
  144. package/test/config/ContainerEvents.test.ts +67 -0
  145. package/test/config/ModuleContainer.test.ts +215 -0
  146. package/test/config/injectAlias.test.ts +28 -0
  147. package/test/trees/MerkleTree.test.ts +106 -0
  148. package/test/tsconfig.json +7 -0
  149. package/test/zkProgrammable/ZkProgrammable.test.ts +304 -0
  150. package/tsconfig.json +8 -0
@@ -0,0 +1,57 @@
1
+ import { noop } from "../utils";
2
+
3
+ import { ChildContainerProvider } from "./ChildContainerProvider";
4
+ import type { BaseModuleInstanceType } from "./ModuleContainer";
5
+
6
+ const errors = {
7
+ configNotSet: (moduleName: string) =>
8
+ new Error(
9
+ `Trying to retrieve config of ${moduleName}, which was not yet set`
10
+ ),
11
+ };
12
+
13
+ // defines how presets can be provided, either a function or a record
14
+ export type Preset<Config> = Config | ((...args: unknown[]) => Config);
15
+ export type Presets<Config> = Record<string, Preset<Config>>;
16
+
17
+ // describes the interface of a configurable module
18
+ export interface Configurable<Config> {
19
+ config: Config;
20
+ }
21
+
22
+ export type NoConfig = Record<never, never>;
23
+
24
+ /**
25
+ * Used by various module sub-types that may need to be configured
26
+ */
27
+ export class ConfigurableModule<Config = NoConfig>
28
+ implements BaseModuleInstanceType
29
+ {
30
+ /**
31
+ * Store the config separately, so that we can apply additional
32
+ * checks when retrieving it via the getter
33
+ */
34
+ protected currentConfig: Config | undefined;
35
+
36
+ // retrieve the existing config
37
+ public get config(): Config {
38
+ if (this.currentConfig === undefined) {
39
+ throw errors.configNotSet(this.constructor.name);
40
+ }
41
+ return this.currentConfig;
42
+ }
43
+
44
+ // set the config
45
+ public set config(config: Config) {
46
+ this.currentConfig = config;
47
+ }
48
+
49
+ public create(childContainerProvider: ChildContainerProvider): void {
50
+ noop();
51
+ }
52
+ }
53
+
54
+ // Helps ensure that the target class implements static presets
55
+ export interface StaticConfigurableModule<Config> {
56
+ presets: Presets<Config>;
57
+ }
@@ -0,0 +1,487 @@
1
+ import "reflect-metadata";
2
+
3
+ import {
4
+ DependencyContainer,
5
+ Frequency,
6
+ InjectionToken,
7
+ instanceCachingFactory,
8
+ isClassProvider,
9
+ isFactoryProvider,
10
+ isTokenProvider,
11
+ isValueProvider,
12
+ Lifecycle,
13
+ } from "tsyringe";
14
+ import log from "loglevel";
15
+ import merge from "lodash/merge";
16
+
17
+ import { MergeObjects, StringKeyOf, TypedClass } from "../types";
18
+ import {
19
+ DependencyFactory,
20
+ InferDependencies,
21
+ } from "../dependencyFactory/DependencyFactory";
22
+ import { EventEmitterProxy } from "../events/EventEmitterProxy";
23
+
24
+ import {
25
+ Configurable,
26
+ ConfigurableModule,
27
+ NoConfig,
28
+ } from "./ConfigurableModule";
29
+ import { ChildContainerProvider } from "./ChildContainerProvider";
30
+ import { ChildContainerCreatable } from "./ChildContainerCreatable";
31
+ import { getInjectAliases } from "./injectAlias";
32
+
33
+ const errors = {
34
+ configNotSetInContainer: (moduleName: string) =>
35
+ new Error(
36
+ `Trying to get config of ${moduleName}, but it was not yet set in the module container`
37
+ ),
38
+
39
+ onlyValidModuleNames: (moduleName: NonNullable<unknown>) =>
40
+ new Error(
41
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
42
+ `Only known module names are allowed, using unknown module name: ${moduleName}`
43
+ ),
44
+
45
+ unableToDecorateModule: (moduleName: InjectionToken<unknown>) =>
46
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
47
+ new Error(`Unable to decorate module ${moduleName.toString()}`),
48
+
49
+ nonModuleDependency: (runtimeModuleName: string) =>
50
+ new Error(`
51
+ Unable to register module: ${runtimeModuleName}, attempting to inject a non-module dependency`),
52
+
53
+ unknownDependency: (runtimeModuleName: string, name: string) =>
54
+ new Error(
55
+ `Unable to register module: ${runtimeModuleName},
56
+ attempting to inject a dependency that is not registered
57
+ as a runtime module for this chain: ${name}`
58
+ ),
59
+
60
+ dependencyContainerNotSet: (className: string) =>
61
+ new Error(
62
+ `DependencyContainer not set. Be sure to only call DI-related function in create() and not inside the constructor. (${className})`
63
+ ),
64
+
65
+ validModuleInstance: (moduleName: string, moduleTypeName: string) =>
66
+ new Error(
67
+ `Incompatible module instance ("${moduleName}" not instanceof ${moduleTypeName})`
68
+ ),
69
+ };
70
+
71
+ export const ModuleContainerErrors = errors;
72
+
73
+ export interface BaseModuleInstanceType
74
+ extends ChildContainerCreatable,
75
+ Configurable<unknown> {}
76
+
77
+ // determines that a module should be configurable by default
78
+ export type BaseModuleType = TypedClass<BaseModuleInstanceType>;
79
+
80
+ // allows to specify what kind of modules can be passed into a container
81
+ export interface ModulesRecord<
82
+ // use the default configurable module type
83
+ ModuleType extends BaseModuleType = BaseModuleType,
84
+ > {
85
+ [name: string]: ModuleType;
86
+ }
87
+
88
+ // config record derived from the provided modules and their config types
89
+ export type ModulesConfig<Modules extends ModulesRecord> = {
90
+ // this will translate into = key: module name, value: module.config
91
+ [ConfigKey in StringKeyOf<Modules>]: InstanceType<
92
+ Modules[ConfigKey]
93
+ > extends Configurable<infer Config>
94
+ ? Config extends NoConfig
95
+ ? Config | undefined
96
+ : Config
97
+ : never;
98
+ };
99
+
100
+ /**
101
+ * This type make any config partial (i.e. optional) up to the first level
102
+ * So { Module: { a: { b: string } } }
103
+ * will become
104
+ * { Module?: { a?: { b: string } } }
105
+ * Note that b does not become optional, as we don't want nested objects to
106
+ * become unreasonably partialized (for example Field).
107
+ */
108
+ export type RecursivePartial<T> = {
109
+ [Key in keyof T]?: Partial<T[Key]>;
110
+ };
111
+
112
+ /**
113
+ * Parameters required when creating a module container instance
114
+ */
115
+ export interface ModuleContainerDefinition<Modules extends ModulesRecord> {
116
+ modules: Modules;
117
+ // config is optional, as it may be provided by the parent/wrapper class
118
+ /**
119
+ * @deprecated
120
+ */
121
+ config?: ModulesConfig<Modules>;
122
+ }
123
+
124
+ // Removes all keys with a "never" value from an object
125
+ export type FilterNeverValues<Type extends Record<string, unknown>> = {
126
+ [Key in keyof Type as Type[Key] extends never ? never : Key]: Type[Key];
127
+ };
128
+
129
+ export type DependenciesFromModules<Modules extends ModulesRecord> =
130
+ FilterNeverValues<{
131
+ [Key in keyof Modules]: Modules[Key] extends TypedClass<DependencyFactory>
132
+ ? InferDependencies<InstanceType<Modules[Key]>>
133
+ : never;
134
+ }>;
135
+
136
+ export type ResolvableModules<Modules extends ModulesRecord> = MergeObjects<
137
+ DependenciesFromModules<Modules>
138
+ > &
139
+ Modules;
140
+
141
+ /**
142
+ * Reusable module container facilitating registration, resolution
143
+ * configuration, decoration and validation of modules
144
+ */
145
+ export class ModuleContainer<
146
+ Modules extends ModulesRecord,
147
+ > extends ConfigurableModule<ModulesConfig<Modules>> {
148
+ /**
149
+ * Determines how often are modules decorated upon resolution
150
+ * from the tsyringe DI container
151
+ */
152
+ private static readonly moduleDecorationFrequency: Frequency = "Once";
153
+
154
+ // DI container holding all the registered modules
155
+ private providedContainer?: DependencyContainer = undefined;
156
+
157
+ private eventEmitterProxy: EventEmitterProxy<Modules> | undefined = undefined;
158
+
159
+ public constructor(public definition: ModuleContainerDefinition<Modules>) {
160
+ super();
161
+ }
162
+
163
+ /**
164
+ * @returns list of module names
165
+ */
166
+ public get moduleNames() {
167
+ return Object.keys(this.definition.modules);
168
+ }
169
+
170
+ /**
171
+ * Check if the provided module satisfies the container requirements,
172
+ * such as only injecting other known modules.
173
+ *
174
+ * @param moduleName
175
+ * @param containedModule
176
+ */
177
+ protected validateModule(
178
+ moduleName: StringKeyOf<Modules>,
179
+ containedModule: ConfigurableModule<unknown>
180
+ ): void {
181
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
182
+ const dependencies: { name?: string }[] | string[] | undefined =
183
+ Reflect.getMetadata("design:paramtypes", containedModule);
184
+
185
+ dependencies?.forEach((dependency: string | { name?: string }) => {
186
+ const name =
187
+ typeof dependency === "string" ? dependency : dependency.name;
188
+
189
+ if (name === undefined) {
190
+ throw errors.nonModuleDependency(moduleName);
191
+ }
192
+
193
+ if (!this.moduleNames.includes(name)) {
194
+ throw errors.unknownDependency(moduleName, name);
195
+ }
196
+ });
197
+ }
198
+
199
+ protected get container(): DependencyContainer {
200
+ this.assertContainerInitialized(this.providedContainer);
201
+ return this.providedContainer;
202
+ }
203
+
204
+ /**
205
+ * Assert that the iterated `moduleName` is of ModuleName type,
206
+ * otherwise it may be just string e.g. when modules are iterated over
207
+ * using e.g. a for loop.
208
+ */
209
+ public assertIsValidModuleName(
210
+ moduleName: string
211
+ ): asserts moduleName is StringKeyOf<Modules> {
212
+ if (!this.isValidModuleName(this.definition.modules, moduleName)) {
213
+ throw errors.onlyValidModuleNames(moduleName);
214
+ }
215
+ }
216
+
217
+ public isValidModuleName(
218
+ modules: Modules,
219
+ moduleName: number | string | symbol
220
+ ): moduleName is StringKeyOf<Modules> {
221
+ return Object.prototype.hasOwnProperty.call(modules, moduleName);
222
+ }
223
+
224
+ public assertContainerInitialized(
225
+ container: DependencyContainer | undefined
226
+ ): asserts container is DependencyContainer {
227
+ if (container === undefined) {
228
+ throw errors.dependencyContainerNotSet(this.constructor.name);
229
+ }
230
+ }
231
+
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
+
242
+ /**
243
+ * Register modules into the current container, and registers
244
+ * a respective resolution hook in order to decorate the module
245
+ * upon/after resolution.
246
+ *
247
+ * @param modules
248
+ */
249
+ protected registerModules(modules: Modules) {
250
+ Object.keys(modules).forEach((moduleName) => {
251
+ if (Object.prototype.hasOwnProperty.call(modules, moduleName)) {
252
+ this.assertIsValidModuleName(moduleName);
253
+
254
+ log.debug(`Registering module: ${moduleName}`);
255
+
256
+ const useClass = modules[moduleName];
257
+
258
+ this.container.register(
259
+ moduleName,
260
+ { useClass },
261
+ { lifecycle: Lifecycle.ContainerScoped }
262
+ );
263
+ this.onAfterModuleResolution(moduleName);
264
+
265
+ this.registerAliases(moduleName, useClass);
266
+ }
267
+ });
268
+ }
269
+
270
+ public get events(): EventEmitterProxy<Modules> {
271
+ if (this.eventEmitterProxy === undefined) {
272
+ this.eventEmitterProxy = new EventEmitterProxy<Modules>(this);
273
+ }
274
+ return this.eventEmitterProxy;
275
+ }
276
+
277
+ /**
278
+ * Register a non-module value into the current container
279
+ * @param modules
280
+ */
281
+ // TODO Rename to plural since object is param
282
+ public registerValue<Value>(modules: Record<string, Value>) {
283
+ Object.entries(modules).forEach(([moduleName, useValue]) => {
284
+ this.container.register(moduleName, { useValue });
285
+ });
286
+ }
287
+
288
+ protected registerClasses(modules: Record<string, TypedClass<unknown>>) {
289
+ Object.entries(modules).forEach(([moduleName, useClass]) => {
290
+ this.container.register(
291
+ moduleName,
292
+ { useClass },
293
+ { lifecycle: Lifecycle.ContainerScoped }
294
+ );
295
+ });
296
+ }
297
+
298
+ /**
299
+ * Provide additional configuration after the ModuleContainer was created.
300
+ *
301
+ * Keep in mind that modules are only decorated once after they are resolved,
302
+ * therefore applying any configuration must happen
303
+ * before the first resolution.
304
+ * @param config
305
+ */
306
+ public configure(config: ModulesConfig<Modules>) {
307
+ this.config = config;
308
+ }
309
+
310
+ public configurePartial(config: RecursivePartial<ModulesConfig<Modules>>) {
311
+ this.config = merge<
312
+ ModulesConfig<Modules> | NoConfig,
313
+ RecursivePartial<ModulesConfig<Modules>>
314
+ >(this.currentConfig ?? {}, config);
315
+ }
316
+
317
+ public get config() {
318
+ return super.config;
319
+ }
320
+
321
+ public set config(config: ModulesConfig<Modules>) {
322
+ super.config = merge<
323
+ ModulesConfig<Modules> | NoConfig,
324
+ ModulesConfig<Modules>
325
+ >(this.currentConfig ?? {}, config);
326
+ }
327
+
328
+ /**
329
+ * Resolves a module from the current module container
330
+ *
331
+ * We have to narrow down the `ModuleName` type here to
332
+ * `ResolvableModuleName`, otherwise the resolved value might
333
+ * be any module instance, not the one specifically requested as argument.
334
+ *
335
+ * @param moduleName
336
+ * @returns
337
+ */
338
+ public resolve<KeyType extends StringKeyOf<ResolvableModules<Modules>>>(
339
+ moduleName: KeyType
340
+ ): InstanceType<ResolvableModules<Modules>[KeyType]> {
341
+ return this.container.resolve<
342
+ InstanceType<ResolvableModules<Modules>[KeyType]>
343
+ >(moduleName);
344
+ }
345
+
346
+ public resolveOrFail<ModuleType>(
347
+ moduleName: string,
348
+ moduleType: TypedClass<ModuleType>
349
+ ) {
350
+ const instance = this.container.resolve<ModuleType>(moduleName);
351
+ const isValidModuleInstance = instance instanceof moduleType;
352
+
353
+ if (!isValidModuleInstance) {
354
+ throw errors.validModuleInstance(moduleName, moduleType.name);
355
+ }
356
+
357
+ return instance;
358
+ }
359
+
360
+ /**
361
+ * Override this in the child class to provide custom
362
+ * features or module checks
363
+ */
364
+ protected decorateModule(
365
+ moduleName: StringKeyOf<Modules>,
366
+ containedModule: InstanceType<Modules[StringKeyOf<Modules>]>
367
+ ) {
368
+ const config = super.config?.[moduleName];
369
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
370
+ if (!config) {
371
+ throw errors.configNotSetInContainer(moduleName.toString());
372
+ }
373
+
374
+ if (containedModule instanceof ModuleContainer) {
375
+ containedModule.configure(config);
376
+ } else {
377
+ containedModule.config = config;
378
+ }
379
+ }
380
+
381
+ private isDependencyFactory(type: any): type is DependencyFactory {
382
+ return "dependencies" in type;
383
+ }
384
+
385
+ /**
386
+ * Inject a set of dependencies using the given list of DependencyFactories
387
+ * This method should be called during startup
388
+ */
389
+ protected initializeDependencyFactories(factories: StringKeyOf<Modules>[]) {
390
+ factories.forEach((factoryName) => {
391
+ this.resolve(factoryName);
392
+ });
393
+ }
394
+
395
+ /**
396
+ * Retrieves all dependencies generated by a particular dependencyfactory
397
+ * and injects them inside this modulecontainer's DI container.
398
+ * This will be automatically called for every module, but can also be called
399
+ * explicitly to initialize an extra factory
400
+ * @param factory
401
+ * @private
402
+ */
403
+ protected useDependencyFactory(factory: DependencyFactory) {
404
+ const dependencies = factory.dependencies();
405
+
406
+ Object.entries(dependencies).forEach(([rawKey, declaration]) => {
407
+ const key = rawKey.charAt(0).toUpperCase() + rawKey.slice(1);
408
+
409
+ if (
410
+ !this.container.isRegistered(key) ||
411
+ declaration.forceOverwrite === true
412
+ ) {
413
+ // Find correct provider type and call respective register
414
+ if (isValueProvider(declaration)) {
415
+ this.container.register(key, declaration);
416
+ } else if (isFactoryProvider(declaration)) {
417
+ // this enables us to have a singletoned factory
418
+ // that returns the same instance for each resolve
419
+ this.container.register(key, {
420
+ useFactory: instanceCachingFactory(declaration.useFactory),
421
+ });
422
+ } else if (isClassProvider(declaration)) {
423
+ this.container.register(key, declaration, {
424
+ lifecycle: Lifecycle.Singleton,
425
+ });
426
+ this.registerAliases(
427
+ key,
428
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
429
+ declaration.useClass as TypedClass<unknown>
430
+ );
431
+ } else if (isTokenProvider(declaration)) {
432
+ this.container.register(key, declaration, {
433
+ lifecycle: Lifecycle.Singleton,
434
+ });
435
+ } else {
436
+ // Can never be reached
437
+ throw new Error("Above if-statement is exhaustive");
438
+ }
439
+ } else {
440
+ log.debug(`Dependency ${key} already registered, skipping`);
441
+ }
442
+ });
443
+ }
444
+
445
+ /**
446
+ * Handle module resolution, e.g. by decorating resolved modules
447
+ * @param moduleName
448
+ */
449
+ protected onAfterModuleResolution(moduleName: StringKeyOf<Modules>) {
450
+ this.container.afterResolution<InstanceType<Modules[StringKeyOf<Modules>]>>(
451
+ moduleName,
452
+ (containedModuleName, containedModule) => {
453
+ // special case where tsyringe may return multiple known instances (?)
454
+ if (Array.isArray(containedModule)) {
455
+ throw errors.unableToDecorateModule(containedModuleName);
456
+ }
457
+ this.decorateModule(moduleName, containedModule);
458
+ containedModule.create(() => {
459
+ const container = this.container.createChildContainer();
460
+ container.reset();
461
+ return container;
462
+ });
463
+
464
+ if (this.isDependencyFactory(containedModule)) {
465
+ this.useDependencyFactory(containedModule);
466
+ }
467
+ },
468
+ { frequency: ModuleContainer.moduleDecorationFrequency }
469
+ );
470
+ }
471
+
472
+ /**
473
+ * This is a placeholder for individual modules to override.
474
+ * This method will be called whenever the underlying container fully
475
+ * initialized
476
+ */
477
+ public create(childContainerProvider: ChildContainerProvider): void {
478
+ this.providedContainer = childContainerProvider();
479
+
480
+ this.registerValue({
481
+ ChildContainerProvider: () => this.container.createChildContainer(),
482
+ });
483
+
484
+ // register all provided modules when the container is created
485
+ this.registerModules(this.definition.modules);
486
+ }
487
+ }
@@ -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
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ ClassProvider,
3
+ FactoryProvider,
4
+ TokenProvider,
5
+ ValueProvider,
6
+ } from "tsyringe";
7
+
8
+ import { TypedClass } from "../types";
9
+ import type { BaseModuleInstanceType } from "../config/ModuleContainer";
10
+
11
+ export type DependencyDeclaration<Dependency> =
12
+ | ClassProvider<Dependency>
13
+ | FactoryProvider<Dependency>
14
+ | TokenProvider<Dependency>
15
+ | ValueProvider<Dependency>;
16
+
17
+ export type DependencyRecord = Record<
18
+ string,
19
+ DependencyDeclaration<unknown> & { forceOverwrite?: boolean }
20
+ >;
21
+
22
+ /**
23
+ * This is an abstract class for creating DependencyFactories, a pattern
24
+ * to bundle multiple smaller services into one and register them into the
25
+ * injection context.
26
+ *
27
+ * This can for example be a StorageDependencyFactory that creates dependencies
28
+ * like StateService, MerkleWitnessService, etc. So in general, services that
29
+ * are not ConfigurableModules, but still are their own logical unit.
30
+ *
31
+ * DependencyFactories are designed to only be used statically for sets of
32
+ * deps that are necessary for the sequencer to work.
33
+ */
34
+ export interface DependencyFactory {
35
+ dependencies: () => DependencyRecord;
36
+ }
37
+
38
+ export type TypeFromDependencyDeclaration<
39
+ Declaration extends DependencyDeclaration<unknown>,
40
+ > =
41
+ Declaration extends DependencyDeclaration<infer Dependency>
42
+ ? Dependency
43
+ : never;
44
+
45
+ export type CapitalizeAny<Key extends string | number | symbol> =
46
+ Key extends string ? Capitalize<Key> : Key;
47
+
48
+ export type MapDependencyRecordToTypes<Record extends DependencyRecord> = {
49
+ [Key in keyof Record as CapitalizeAny<Key>]: TypedClass<
50
+ TypeFromDependencyDeclaration<Record[Key]>
51
+ >;
52
+ };
53
+
54
+ export type InferDependencies<Class extends BaseModuleInstanceType> =
55
+ Class extends DependencyFactory
56
+ ? MapDependencyRecordToTypes<ReturnType<Class["dependencies"]>>
57
+ : never;