@proto-kit/common 0.1.1-develop.1086

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