@grest-ts/config 0.0.6 → 0.0.8

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.
package/src/GGConfig.ts CHANGED
@@ -1,70 +1,70 @@
1
- import {GGConfigDefinition} from "./GGConfigLocator";
2
- import {AsyncLocalStorage} from "node:async_hooks";
3
- import {GGConfigKey} from "./GGConfigKey";
4
- import {ConstructorOf} from "@grest-ts/common";
5
-
6
- export interface GGConfigKeyCreationContext {
7
- name: string
8
- add: (key: GGConfigKey) => void;
9
- }
10
-
11
- const defineContext = new AsyncLocalStorage<GGConfigKeyCreationContext>();
12
-
13
- export class GGConfig {
14
-
15
- public static define<T>(name: string, define: () => T): GGConfigDefinition<T> {
16
- const keysMap: Map<ConstructorOf<GGConfigKey>, GGConfigKey[]> = new Map()
17
- const ctx: GGConfigKeyCreationContext = Object.freeze({
18
- name,
19
- add: (key: GGConfigKey) => {
20
- const cls = key.constructor as ConstructorOf<GGConfigKey>;
21
- if (!keysMap.has(cls)) {
22
- keysMap.set(cls, [])
23
- }
24
- keysMap.get(cls).push(key)
25
- }
26
- });
27
- const result = defineContext.run(ctx, define) as GGConfigDefinition<T>;
28
- keysMap.forEach(list => Object.freeze(list))
29
- result.__getKeysMap = ((cls: ConstructorOf<GGConfigKey>) => keysMap) as any
30
- Object.freeze(result);
31
- return result
32
- }
33
-
34
- public static getCreationContext(): GGConfigKeyCreationContext {
35
- const store = defineContext.getStore()
36
- if (!store) {
37
- throw new Error("No prefix defined in context")
38
- }
39
- return store;
40
- }
41
-
42
- public static toJSON(config: GGConfigDefinition<any>): Record<string, unknown> {
43
- const result: Record<string, Record<string, unknown>> = {};
44
- this.collectKeys(config, result);
45
- return result;
46
- }
47
-
48
- private static collectKeys(obj: unknown, result: Record<string, Record<string, unknown>>): void {
49
- if (obj instanceof GGConfigKey) {
50
- const storeKey = obj.getStoreKey();
51
- if (!result[storeKey]) {
52
- result[storeKey] = {};
53
- }
54
- const segments = obj.name.split('/').filter(s => s.length > 0);
55
- let current: Record<string, unknown> = result[storeKey];
56
- for (let i = 0; i < segments.length - 1; i++) {
57
- if (!current[segments[i]]) {
58
- current[segments[i]] = {};
59
- }
60
- current = current[segments[i]] as Record<string, unknown>;
61
- }
62
- current[segments[segments.length - 1]] = obj.getDefault();
63
- } else if (obj && typeof obj === 'object') {
64
- for (const value of Object.values(obj)) {
65
- this.collectKeys(value, result);
66
- }
67
- }
68
- }
69
-
1
+ import {GGConfigDefinition} from "./GGConfigLocator";
2
+ import {AsyncLocalStorage} from "node:async_hooks";
3
+ import {GGConfigKey} from "./GGConfigKey";
4
+ import {ConstructorOf} from "@grest-ts/common";
5
+
6
+ export interface GGConfigKeyCreationContext {
7
+ name: string
8
+ add: (key: GGConfigKey) => void;
9
+ }
10
+
11
+ const defineContext = new AsyncLocalStorage<GGConfigKeyCreationContext>();
12
+
13
+ export class GGConfig {
14
+
15
+ public static define<T>(name: string, define: () => T): GGConfigDefinition<T> {
16
+ const keysMap: Map<ConstructorOf<GGConfigKey>, GGConfigKey[]> = new Map()
17
+ const ctx: GGConfigKeyCreationContext = Object.freeze({
18
+ name,
19
+ add: (key: GGConfigKey) => {
20
+ const cls = key.constructor as ConstructorOf<GGConfigKey>;
21
+ if (!keysMap.has(cls)) {
22
+ keysMap.set(cls, [])
23
+ }
24
+ keysMap.get(cls).push(key)
25
+ }
26
+ });
27
+ const result = defineContext.run(ctx, define) as GGConfigDefinition<T>;
28
+ keysMap.forEach(list => Object.freeze(list))
29
+ result.__getKeysMap = ((cls: ConstructorOf<GGConfigKey>) => keysMap) as any
30
+ Object.freeze(result);
31
+ return result
32
+ }
33
+
34
+ public static getCreationContext(): GGConfigKeyCreationContext {
35
+ const store = defineContext.getStore()
36
+ if (!store) {
37
+ throw new Error("No prefix defined in context")
38
+ }
39
+ return store;
40
+ }
41
+
42
+ public static toJSON(config: GGConfigDefinition<any>): Record<string, unknown> {
43
+ const result: Record<string, Record<string, unknown>> = {};
44
+ this.collectKeys(config, result);
45
+ return result;
46
+ }
47
+
48
+ private static collectKeys(obj: unknown, result: Record<string, Record<string, unknown>>): void {
49
+ if (obj instanceof GGConfigKey) {
50
+ const storeKey = obj.getStoreKey();
51
+ if (!result[storeKey]) {
52
+ result[storeKey] = {};
53
+ }
54
+ const segments = obj.name.split('/').filter(s => s.length > 0);
55
+ let current: Record<string, unknown> = result[storeKey];
56
+ for (let i = 0; i < segments.length - 1; i++) {
57
+ if (!current[segments[i]]) {
58
+ current[segments[i]] = {};
59
+ }
60
+ current = current[segments[i]] as Record<string, unknown>;
61
+ }
62
+ current[segments[segments.length - 1]] = obj.getDefault();
63
+ } else if (obj && typeof obj === 'object') {
64
+ for (const value of Object.values(obj)) {
65
+ this.collectKeys(value, result);
66
+ }
67
+ }
68
+ }
69
+
70
70
  }
@@ -1,107 +1,107 @@
1
- import {GGConfig} from "./GGConfig";
2
- import {assureValidConfigPath} from "./assureValidConfigPath";
3
- import {GGValidator} from "@grest-ts/schema";
4
- import {GG_CONFIG} from "./GG_CONFIG";
5
-
6
- /**
7
- * Widens literal types to their base primitive type.
8
- * This allows T to be inferred from the validator while defaultValue accepts compatible values.
9
- * e.g., Widen<tPosInt> = number, so defaultValue can be 5000
10
- */
11
- export type Widen<T> = T extends number ? number : T extends string ? string : T extends boolean ? boolean : T;
12
-
13
- export type GGConfigKeyConstructor<T = unknown> = {
14
- readonly NAME: string,
15
- new(...args: any[]): GGConfigKey<T>
16
- };
17
-
18
- export abstract class GGConfigKey<T = unknown> {
19
-
20
- private static readonly registry = new Map<string, GGConfigKey>();
21
-
22
- public static hasKey(name: string): boolean {
23
- return GGConfigKey.registry.has(name);
24
- }
25
-
26
- public static getKey(name: string): GGConfigKey {
27
- const result = GGConfigKey.registry.get(name);
28
- if (result === undefined) {
29
- throw new Error(`Config key not found: ${name}`);
30
- }
31
- return result;
32
- }
33
-
34
- public readonly root: string;
35
- public readonly name: string;
36
- public readonly description: string | undefined;
37
- public readonly schema: GGValidator<T> | undefined;
38
- /** File location where this ConfigKey was defined (file:line:col) */
39
- public readonly definedAt: string | undefined;
40
-
41
- constructor(name: string, schema: GGValidator<T>, description: string) {
42
- this.definedAt = captureDefinitionLocation();
43
- const ctx = GGConfig.getCreationContext();
44
- ctx.add(this);
45
- this.root = ctx.name
46
- this.name = this.root + name;
47
- assureValidConfigPath(this.name);
48
- this.description = description;
49
- this.schema = schema;
50
-
51
- Object.freeze(this);
52
-
53
- if (GGConfigKey.registry.has(this.name)) {
54
- throw new Error(`Duplicate config key: ${this.name}`);
55
- }
56
- GGConfigKey.registry.set(this.name, this);
57
- }
58
-
59
- public getDefault(): T | undefined {
60
- return undefined;
61
- }
62
-
63
- protected getValue(): T {
64
- const store = this.getStore();
65
- if (!store.started) {
66
- throw new Error(`Cannot read config key "${this.name}" before the config store has been started.`);
67
- }
68
- return store.getValue(this);
69
- }
70
-
71
- public watch(listener: (value: T) => void): () => void {
72
- return this.getStore().watch(this, listener);
73
- }
74
-
75
- public abstract getStoreKey(): string;
76
-
77
- public getStore() {
78
- return GG_CONFIG.get().getStore(this.constructor as GGConfigKeyConstructor<any>);
79
- }
80
- }
81
-
82
- function captureDefinitionLocation(): string | undefined {
83
- const stack = new Error().stack;
84
-
85
- /* This could be one possible error we are filtering for.
86
- Error
87
- at captureDefinitionLocation (\GG_ROOT\packages\config\src\GGConfigKey.ts:24:19)
88
- at new GGConfigKey (\GG_ROOT\packages\config\src\GGConfigKey.ts:60:26)
89
- at new GGResource (\GG_ROOT\packages\config\src\keys\GGResource.ts:3:8)
90
- at Object.createAwsSnsProviderConfig (\GG_ROOT\packages-libs\events\events-aws\src\AwsSnsAdapter.ts:26:19)
91
- at new EventPublisherConfig (\GG_ROOT\packages-libs\events\events\src\pub\EventPublisherConfig.ts:56:52)
92
- at EventPublisherResource.config (\GG_ROOT\packages-libs\events\events\src\pub\EventPublisherResource.ts:39:16)
93
- at <anonymous> (\GG_ROOT\examples\checklist\checklist\ChecklistConfig.ts:26:41)
94
- at AsyncLocalStorage.run (node:internal/async_local_storage/async_context_frame:63:14)
95
- at GGConfig.define (\GG_ROOT\packages\config\src\GGConfig.ts:27:38)
96
- at <anonymous> (\GG_ROOT\examples\checklist\checklist\ChecklistConfig.ts:8:41)
97
- */
98
- const lines = stack.split('\n').reverse();
99
- for (let i = 1; i < lines.length; i++) {
100
- const line = lines[i];
101
- // Remember that minification might change the names of things.
102
- if (lines[i - 1]?.includes('at AsyncLocalStorage.run ') && line?.includes('at <anonymous>')) {
103
- return line.substring(0, line.length - 1).trim().replace("at <anonymous> (", "")
104
- }
105
- }
106
- return stack;
1
+ import {GGConfig} from "./GGConfig";
2
+ import {assureValidConfigPath} from "./assureValidConfigPath";
3
+ import {GGValidator} from "@grest-ts/schema";
4
+ import {GG_CONFIG} from "./GG_CONFIG";
5
+
6
+ /**
7
+ * Widens literal types to their base primitive type.
8
+ * This allows T to be inferred from the validator while defaultValue accepts compatible values.
9
+ * e.g., Widen<tPosInt> = number, so defaultValue can be 5000
10
+ */
11
+ export type Widen<T> = T extends number ? number : T extends string ? string : T extends boolean ? boolean : T;
12
+
13
+ export type GGConfigKeyConstructor<T = unknown> = {
14
+ readonly NAME: string,
15
+ new(...args: any[]): GGConfigKey<T>
16
+ };
17
+
18
+ export abstract class GGConfigKey<T = unknown> {
19
+
20
+ private static readonly registry = new Map<string, GGConfigKey>();
21
+
22
+ public static hasKey(name: string): boolean {
23
+ return GGConfigKey.registry.has(name);
24
+ }
25
+
26
+ public static getKey(name: string): GGConfigKey {
27
+ const result = GGConfigKey.registry.get(name);
28
+ if (result === undefined) {
29
+ throw new Error(`Config key not found: ${name}`);
30
+ }
31
+ return result;
32
+ }
33
+
34
+ public readonly root: string;
35
+ public readonly name: string;
36
+ public readonly description: string | undefined;
37
+ public readonly schema: GGValidator<T> | undefined;
38
+ /** File location where this ConfigKey was defined (file:line:col) */
39
+ public readonly definedAt: string | undefined;
40
+
41
+ constructor(name: string, schema: GGValidator<T>, description: string) {
42
+ this.definedAt = captureDefinitionLocation();
43
+ const ctx = GGConfig.getCreationContext();
44
+ ctx.add(this);
45
+ this.root = ctx.name
46
+ this.name = this.root + name;
47
+ assureValidConfigPath(this.name);
48
+ this.description = description;
49
+ this.schema = schema;
50
+
51
+ Object.freeze(this);
52
+
53
+ if (GGConfigKey.registry.has(this.name)) {
54
+ throw new Error(`Duplicate config key: ${this.name}`);
55
+ }
56
+ GGConfigKey.registry.set(this.name, this);
57
+ }
58
+
59
+ public getDefault(): T | undefined {
60
+ return undefined;
61
+ }
62
+
63
+ protected getValue(): T {
64
+ const store = this.getStore();
65
+ if (!store.started) {
66
+ throw new Error(`Cannot read config key "${this.name}" before the config store has been started.`);
67
+ }
68
+ return store.getValue(this);
69
+ }
70
+
71
+ public watch(listener: (value: T) => void): () => void {
72
+ return this.getStore().watch(this, listener);
73
+ }
74
+
75
+ public abstract getStoreKey(): string;
76
+
77
+ public getStore() {
78
+ return GG_CONFIG.get().getStore(this.constructor as GGConfigKeyConstructor<any>);
79
+ }
80
+ }
81
+
82
+ function captureDefinitionLocation(): string | undefined {
83
+ const stack = new Error().stack;
84
+
85
+ /* This could be one possible error we are filtering for.
86
+ Error
87
+ at captureDefinitionLocation (\GG_ROOT\packages\config\src\GGConfigKey.ts:24:19)
88
+ at new GGConfigKey (\GG_ROOT\packages\config\src\GGConfigKey.ts:60:26)
89
+ at new GGResource (\GG_ROOT\packages\config\src\keys\GGResource.ts:3:8)
90
+ at Object.createAwsSnsProviderConfig (\GG_ROOT\packages-libs\events\events-aws\src\AwsSnsAdapter.ts:26:19)
91
+ at new EventPublisherConfig (\GG_ROOT\packages-libs\events\events\src\pub\EventPublisherConfig.ts:56:52)
92
+ at EventPublisherResource.config (\GG_ROOT\packages-libs\events\events\src\pub\EventPublisherResource.ts:39:16)
93
+ at <anonymous> (\GG_ROOT\examples\checklist\checklist\ChecklistConfig.ts:26:41)
94
+ at AsyncLocalStorage.run (node:internal/async_local_storage/async_context_frame:63:14)
95
+ at GGConfig.define (\GG_ROOT\packages\config\src\GGConfig.ts:27:38)
96
+ at <anonymous> (\GG_ROOT\examples\checklist\checklist\ChecklistConfig.ts:8:41)
97
+ */
98
+ const lines = stack.split('\n').reverse();
99
+ for (let i = 1; i < lines.length; i++) {
100
+ const line = lines[i];
101
+ // Remember that minification might change the names of things.
102
+ if (lines[i - 1]?.includes('at AsyncLocalStorage.run ') && line?.includes('at <anonymous>')) {
103
+ return line.substring(0, line.length - 1).trim().replace("at <anonymous> (", "")
104
+ }
105
+ }
106
+ return stack;
107
107
  }
@@ -1,112 +1,112 @@
1
- import {GGConfigStore} from "./GGConfigStore";
2
- import {GGConfigKey, GGConfigKeyConstructor} from "./GGConfigKey";
3
- import {GGLocator, GGLocatorServiceType} from "@grest-ts/locator";
4
- import {GG_CONFIG} from "./GG_CONFIG";
5
- import {ConfigValues, GGConfigStoreLocal} from "./stores/GGConfigStoreLocal";
6
-
7
- export type GGConfigDefinition<T> = T & {
8
- __isGGConfigDefinition: never,
9
- __getKeysMap: () => ReadonlyMap<GGConfigKeyConstructor, GGConfigKey[]>
10
- };
11
-
12
- export class GGConfigLocator<T extends object> {
13
-
14
- #isStarted = false;
15
- readonly #config: GGConfigDefinition<T>;
16
- readonly #storesList: GGConfigStore<GGConfigKey>[] = []
17
- readonly #storesMap: Map<string, GGConfigStore<GGConfigKey>> = new Map()
18
-
19
- protected readonly localConfig: GGConfigStoreLocal<T, any> = undefined
20
-
21
- /**
22
- * @param config - Config definition.
23
- * @param localConfig - Optional local config values. If provided, stores will use this instead of the real values when not running in production.
24
- * new GGConfigLocator(MyConfig, localConfig) is shorthand for this: new GGConfigLocator(MyConfig).add(AllKeys, new GGConfigStoreLocal(MyConfig, localConfig))
25
- */
26
- constructor(config: GGConfigDefinition<T>, localConfig?: ConfigValues<T>) {
27
- if (!config) throw new Error("Config definition is required");
28
- this.#config = config;
29
- this.localConfig = localConfig ? new GGConfigStoreLocal(config, localConfig) : undefined
30
- GGLocator.getScope().setWithLifecycle(GG_CONFIG, this, {
31
- type: GGLocatorServiceType.CONFIG,
32
- start: () => this.start(),
33
- teardown: () => this.teardown()
34
- });
35
- }
36
-
37
- /**
38
- * Can override this if you want custom handling.
39
- */
40
- public onNotifyError = (error: Error): never => {
41
- throw error;
42
- }
43
-
44
- /**
45
- * Add a config store for a key type.
46
- *
47
- * Note for framework development: Testkit overrides this function.
48
- */
49
- public add<Key extends GGConfigKey>(key: GGConfigKeyConstructor<Key> | GGConfigKeyConstructor<Key>[], store: GGConfigStore<Key>): this {
50
- return this._add(key, this._useLocalStoreIfNeeded(store))
51
- }
52
-
53
- protected _useLocalStoreIfNeeded<Key extends GGConfigKey>(store: GGConfigStore<Key>): GGConfigStore<Key> {
54
- if (process.env.NODE_ENV === "production" || !this.localConfig) {
55
- return store;
56
- } else {
57
- return this.localConfig;
58
- }
59
- }
60
-
61
- protected _add<Key extends GGConfigKey>(key: GGConfigKeyConstructor<Key> | GGConfigKeyConstructor<Key>[], store: GGConfigStore<Key>): this {
62
- if (this.#isStarted) {
63
- throw new Error("Cannot add store after config holder is started");
64
- }
65
- const keyTypes = Array.isArray(key) ? key : [key];
66
- const keysMap = this.#config.__getKeysMap();
67
- for (const keyType of keyTypes) {
68
- const keysForType = (keysMap.get(keyType) ?? []) as Key[];
69
- store.setKeys(keysForType);
70
- this.#storesMap.set(keyType.NAME, store as GGConfigStore<GGConfigKey>);
71
- }
72
- if (!this.#storesList.includes(store as GGConfigStore<GGConfigKey>)) {
73
- this.#storesList.push(store as GGConfigStore<GGConfigKey>);
74
- }
75
- return this;
76
- }
77
-
78
- public getStore<Key extends GGConfigKey>(keyType: GGConfigKeyConstructor<Key>): GGConfigStore<Key> {
79
- return this.getStoreByConfigKeyName(keyType.NAME);
80
- }
81
-
82
- public getStoreByConfigKeyName<Key extends GGConfigKey>(name: string): GGConfigStore<Key> {
83
- const store = this.#storesMap.get(name);
84
- if (!store) {
85
- throw new Error(`No store for store key: ${name ?? 'undefined'}.`);
86
- }
87
- return store as GGConfigStore<Key>
88
- }
89
-
90
- public getStores(): GGConfigStore<GGConfigKey>[] {
91
- return this.#storesList;
92
- }
93
-
94
- public async start(): Promise<void> {
95
- if (this.#isStarted) {
96
- throw new Error("Already started");
97
- }
98
- this.#isStarted = true;
99
- this.#config.__getKeysMap().forEach((_, keyType) => {
100
- if (!this.#storesMap.has(keyType.NAME)) {
101
- throw new Error(`Missing stores for config key type '${keyType.NAME}'`);
102
- }
103
- })
104
- Object.freeze(this.#storesMap);
105
- Object.freeze(this.#storesList);
106
- await Promise.all(this.#storesList.map((store) => store.start()));
107
- }
108
-
109
- public async teardown(): Promise<void> {
110
- await Promise.all(this.#storesList.map(s => s.teardown()));
111
- }
112
- }
1
+ import {GGConfigStore} from "./GGConfigStore";
2
+ import {GGConfigKey, GGConfigKeyConstructor} from "./GGConfigKey";
3
+ import {GGLocator, GGLocatorServiceType} from "@grest-ts/locator";
4
+ import {GG_CONFIG} from "./GG_CONFIG";
5
+ import {ConfigValues, GGConfigStoreLocal} from "./stores/GGConfigStoreLocal";
6
+
7
+ export type GGConfigDefinition<T> = T & {
8
+ __isGGConfigDefinition: never,
9
+ __getKeysMap: () => ReadonlyMap<GGConfigKeyConstructor, GGConfigKey[]>
10
+ };
11
+
12
+ export class GGConfigLocator<T extends object> {
13
+
14
+ #isStarted = false;
15
+ readonly #config: GGConfigDefinition<T>;
16
+ readonly #storesList: GGConfigStore<GGConfigKey>[] = []
17
+ readonly #storesMap: Map<string, GGConfigStore<GGConfigKey>> = new Map()
18
+
19
+ protected readonly localConfig: GGConfigStoreLocal<T, any> = undefined
20
+
21
+ /**
22
+ * @param config - Config definition.
23
+ * @param localConfig - Optional local config values. If provided, stores will use this instead of the real values when not running in production.
24
+ * new GGConfigLocator(MyConfig, localConfig) is shorthand for this: new GGConfigLocator(MyConfig).add(AllKeys, new GGConfigStoreLocal(MyConfig, localConfig))
25
+ */
26
+ constructor(config: GGConfigDefinition<T>, localConfig?: ConfigValues<T>) {
27
+ if (!config) throw new Error("Config definition is required");
28
+ this.#config = config;
29
+ this.localConfig = localConfig ? new GGConfigStoreLocal(config, localConfig) : undefined
30
+ GGLocator.getScope().setWithLifecycle(GG_CONFIG, this, {
31
+ type: GGLocatorServiceType.CONFIG,
32
+ start: () => this.start(),
33
+ teardown: () => this.teardown()
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Can override this if you want custom handling.
39
+ */
40
+ public onNotifyError = (error: Error): never => {
41
+ throw error;
42
+ }
43
+
44
+ /**
45
+ * Add a config store for a key type.
46
+ *
47
+ * Note for framework development: Testkit overrides this function.
48
+ */
49
+ public add<Key extends GGConfigKey>(key: GGConfigKeyConstructor<Key> | GGConfigKeyConstructor<Key>[], store: GGConfigStore<Key>): this {
50
+ return this._add(key, this._useLocalStoreIfNeeded(store))
51
+ }
52
+
53
+ protected _useLocalStoreIfNeeded<Key extends GGConfigKey>(store: GGConfigStore<Key>): GGConfigStore<Key> {
54
+ if (process.env.NODE_ENV === "production" || !this.localConfig) {
55
+ return store;
56
+ } else {
57
+ return this.localConfig;
58
+ }
59
+ }
60
+
61
+ protected _add<Key extends GGConfigKey>(key: GGConfigKeyConstructor<Key> | GGConfigKeyConstructor<Key>[], store: GGConfigStore<Key>): this {
62
+ if (this.#isStarted) {
63
+ throw new Error("Cannot add store after config holder is started");
64
+ }
65
+ const keyTypes = Array.isArray(key) ? key : [key];
66
+ const keysMap = this.#config.__getKeysMap();
67
+ for (const keyType of keyTypes) {
68
+ const keysForType = (keysMap.get(keyType) ?? []) as Key[];
69
+ store.setKeys(keysForType);
70
+ this.#storesMap.set(keyType.NAME, store as GGConfigStore<GGConfigKey>);
71
+ }
72
+ if (!this.#storesList.includes(store as GGConfigStore<GGConfigKey>)) {
73
+ this.#storesList.push(store as GGConfigStore<GGConfigKey>);
74
+ }
75
+ return this;
76
+ }
77
+
78
+ public getStore<Key extends GGConfigKey>(keyType: GGConfigKeyConstructor<Key>): GGConfigStore<Key> {
79
+ return this.getStoreByConfigKeyName(keyType.NAME);
80
+ }
81
+
82
+ public getStoreByConfigKeyName<Key extends GGConfigKey>(name: string): GGConfigStore<Key> {
83
+ const store = this.#storesMap.get(name);
84
+ if (!store) {
85
+ throw new Error(`No store for store key: ${name ?? 'undefined'}.`);
86
+ }
87
+ return store as GGConfigStore<Key>
88
+ }
89
+
90
+ public getStores(): GGConfigStore<GGConfigKey>[] {
91
+ return this.#storesList;
92
+ }
93
+
94
+ public async start(): Promise<void> {
95
+ if (this.#isStarted) {
96
+ throw new Error("Already started");
97
+ }
98
+ this.#isStarted = true;
99
+ this.#config.__getKeysMap().forEach((_, keyType) => {
100
+ if (!this.#storesMap.has(keyType.NAME)) {
101
+ throw new Error(`Missing stores for config key type '${keyType.NAME}'`);
102
+ }
103
+ })
104
+ Object.freeze(this.#storesMap);
105
+ Object.freeze(this.#storesList);
106
+ await Promise.all(this.#storesList.map((store) => store.start()));
107
+ }
108
+
109
+ public async teardown(): Promise<void> {
110
+ await Promise.all(this.#storesList.map(s => s.teardown()));
111
+ }
112
+ }