@twin.org/core 0.9.1-next.1 → 0.9.1-next.3

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.
@@ -74,12 +74,7 @@ export class Factory {
74
74
  * @returns All the factories.
75
75
  */
76
76
  static getFactories() {
77
- let factories = SharedStore.get("factories");
78
- if (Is.undefined(factories)) {
79
- factories = {};
80
- SharedStore.set("factories", factories);
81
- }
82
- return factories;
77
+ return SharedStore.get("factories", () => ({}));
83
78
  }
84
79
  /**
85
80
  * Get a specific factory by type name.
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/factories/factory.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport { Guards } from \"../utils/guards.js\";\nimport { Is } from \"../utils/is.js\";\nimport { SharedStore } from \"../utils/sharedStore.js\";\n\n/**\n * Factory for creating implementation of generic types.\n */\nexport class Factory<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Factory<unknown>>();\n\n\t/**\n\t * Type name for the instances.\n\t * @internal\n\t */\n\tprivate readonly _typeName: string;\n\n\t/**\n\t * Store the generators.\n\t * @internal\n\t */\n\tprivate _generators: {\n\t\t[name: string]: {\n\t\t\tgenerator: (args?: unknown) => T;\n\t\t\torder: number;\n\t\t};\n\t};\n\n\t/**\n\t * Store the created instances.\n\t * @internal\n\t */\n\tprivate _instances: { [name: string]: T };\n\n\t/**\n\t * Counter for the ordering.\n\t * @internal\n\t */\n\tprivate _orderCounter: number;\n\n\t/**\n\t * Automatically created an instance when registered.\n\t * @internal\n\t */\n\tprivate readonly _autoInstance: boolean;\n\n\t/**\n\t * Match the name of the instance.\n\t * @internal\n\t */\n\tprivate readonly _matcher: (names: string[], name: string) => string | undefined;\n\n\t/**\n\t * Create a new instance of Factory, private use createFactory.\n\t * @param typeName The type name for the instances.\n\t * @param autoInstance Automatically create an instance when registered.\n\t * @param matcher Match the name of the instance.\n\t * @internal\n\t */\n\tprivate constructor(\n\t\ttypeName: string,\n\t\tautoInstance: boolean = false,\n\t\tmatcher?: (names: string[], name: string) => string | undefined\n\t) {\n\t\tthis._typeName = typeName;\n\t\tthis._generators = {};\n\t\tthis._instances = {};\n\t\tthis._orderCounter = 0;\n\t\tthis._autoInstance = autoInstance;\n\t\tthis._matcher = matcher ?? this.defaultMatcher.bind(this);\n\t}\n\n\t/**\n\t * Create a new factory, which is shared throughout all library instances.\n\t * @param typeName The type name for the instances.\n\t * @param autoInstance Automatically create an instance when registered.\n\t * @param matcher Match the name of the instance.\n\t * @returns The factory instance.\n\t */\n\tpublic static createFactory<U>(\n\t\ttypeName: string,\n\t\tautoInstance: boolean = false,\n\t\tmatcher?: (names: string[], name: string) => string | undefined\n\t): Factory<U> {\n\t\tconst factories = Factory.getFactories();\n\n\t\tif (Is.undefined(factories[typeName])) {\n\t\t\tfactories[typeName] = new Factory<U>(typeName, autoInstance, matcher);\n\t\t}\n\t\treturn factories[typeName] as Factory<U>;\n\t}\n\n\t/**\n\t * Get all the factories.\n\t * @returns All the factories.\n\t */\n\tpublic static getFactories(): { [typeName: string]: Factory<unknown> } {\n\t\tlet factories = SharedStore.get<{\n\t\t\t[typeName: string]: Factory<unknown>;\n\t\t}>(\"factories\");\n\n\t\tif (Is.undefined(factories)) {\n\t\t\tfactories = {};\n\t\t\tSharedStore.set(\"factories\", factories);\n\t\t}\n\n\t\treturn factories;\n\t}\n\n\t/**\n\t * Get a specific factory by type name.\n\t * @param typeName The type name of the factory.\n\t * @returns The factory instance if it exists, otherwise undefined.\n\t */\n\tpublic static getFactory<T = unknown>(typeName: string): Factory<T> | undefined {\n\t\tconst factories = Factory.getFactories();\n\n\t\treturn factories[typeName] as Factory<T> | undefined;\n\t}\n\n\t/**\n\t * Reset all the factories, which removes any created instances, but not the registrations.\n\t */\n\tpublic static resetFactories(): void {\n\t\tconst factories = Factory.getFactories();\n\n\t\tfor (const typeName in factories) {\n\t\t\tfactories[typeName].reset();\n\t\t}\n\t}\n\n\t/**\n\t * Clear all the factories, which removes anything registered with the factories.\n\t */\n\tpublic static clearFactories(): void {\n\t\tconst factories = Factory.getFactories();\n\n\t\tfor (const typeName in factories) {\n\t\t\tfactories[typeName].clear();\n\t\t}\n\t}\n\n\t/**\n\t * Get the type name of the factory.\n\t * @returns The type name of the factory.\n\t */\n\tpublic typeName(): string {\n\t\treturn this._typeName;\n\t}\n\n\t/**\n\t * Register a new generator.\n\t * @param name The name of the generator.\n\t * @param generator The function to create an instance.\n\t */\n\tpublic register<U extends T>(name: string, generator: (args?: unknown) => U): void {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tGuards.function(Factory.CLASS_NAME, nameof(generator), generator);\n\t\tthis._generators[name] = {\n\t\t\tgenerator,\n\t\t\torder: this._orderCounter++\n\t\t};\n\n\t\t// Remove any existing instance\n\t\tthis.removeInstance(name);\n\t\tif (this._autoInstance) {\n\t\t\tthis._instances[name] = generator();\n\t\t}\n\t}\n\n\t/**\n\t * Unregister a generator.\n\t * @param name The name of the generator to unregister.\n\t * @throws GuardError if the parameters are invalid.\n\t * @throws GeneralError if no generator exists.\n\t */\n\tpublic unregister(name: string): void {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tif (!this._generators[name]) {\n\t\t\tthrow new GeneralError(Factory.CLASS_NAME, \"noUnregister\", {\n\t\t\t\ttypeName: this._typeName,\n\t\t\t\tname\n\t\t\t});\n\t\t}\n\t\tdelete this._generators[name];\n\t\t// Remove any existing instance\n\t\tthis.removeInstance(name);\n\t}\n\n\t/**\n\t * Get a generator instance.\n\t * @param name The name of the instance to generate.\n\t * @returns An instance of the item.\n\t * @throws GuardError if the parameters are invalid.\n\t * @throws GeneralError if no item exists to get.\n\t */\n\tpublic get<U extends T>(name: string): U {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tconst instance = this.getIfExists(name);\n\t\tif (!instance) {\n\t\t\tthrow new GeneralError(Factory.CLASS_NAME, \"noGet\", {\n\t\t\t\ttypeName: this._typeName,\n\t\t\t\tname\n\t\t\t});\n\t\t}\n\t\treturn instance as U;\n\t}\n\n\t/**\n\t * Get a generator instance with no exceptions.\n\t * @param name The name of the instance to generate.\n\t * @returns An instance of the item or undefined if it does not exist.\n\t */\n\tpublic getIfExists<U extends T>(name?: string): U | undefined {\n\t\tif (Is.empty(name)) {\n\t\t\treturn;\n\t\t}\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\n\t\tconst matchName = this._matcher(Object.keys(this._generators), name);\n\n\t\tif (Is.stringValue(matchName) && this._generators[matchName]) {\n\t\t\tif (!this._instances[matchName]) {\n\t\t\t\tthis._instances[matchName] = this._generators[matchName].generator();\n\t\t\t}\n\t\t\tif (this._instances[matchName]) {\n\t\t\t\treturn this._instances[matchName] as U;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Create a new instance without caching it.\n\t * @param name The name of the instance to generate.\n\t * @param args The arguments to pass to the generator.\n\t * @returns A new instance of the item.\n\t * @throws GuardError if the parameters are invalid.\n\t * @throws GeneralError if no item exists to create.\n\t */\n\tpublic create<U extends T>(name: string, args?: unknown): U {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tconst instance = this.createIfExists(name, args);\n\t\tif (!instance) {\n\t\t\tthrow new GeneralError(Factory.CLASS_NAME, \"noCreate\", {\n\t\t\t\ttypeName: this._typeName,\n\t\t\t\tname,\n\t\t\t\targs: Is.undefined(args) ? \"\" : JSON.stringify(args)\n\t\t\t});\n\t\t}\n\t\treturn instance as U;\n\t}\n\n\t/**\n\t * Create a new instance without caching it if it exists.\n\t * @param name The name of the instance to generate.\n\t * @param args The arguments to pass to the generator.\n\t * @returns A new instance of the item if it exists.\n\t * @throws GuardError if the parameters are invalid.\n\t */\n\tpublic createIfExists<U extends T>(name: string, args?: unknown): U | undefined {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tconst matchName = this._matcher(Object.keys(this._generators), name);\n\n\t\tif (Is.stringValue(matchName) && this._generators[matchName]) {\n\t\t\treturn this._generators[matchName].generator(args) as U;\n\t\t}\n\t}\n\n\t/**\n\t * Remove all the instances and leave the generators intact.\n\t */\n\tpublic reset(): void {\n\t\tfor (const name in this._generators) {\n\t\t\tthis.removeInstance(name);\n\t\t}\n\t\tthis._instances = {};\n\t}\n\n\t/**\n\t * Remove all the instances and the generators.\n\t */\n\tpublic clear(): void {\n\t\tthis._instances = {};\n\t\tthis._generators = {};\n\t\tthis._orderCounter = 0;\n\t}\n\n\t/**\n\t * Get all the instances as a map.\n\t * @returns The instances as a map.\n\t */\n\tpublic instancesMap(): { [name: string]: T } {\n\t\treturn this._instances;\n\t}\n\n\t/**\n\t * Get all the instances as a list in the order they were registered.\n\t * @returns The instances as a list in the order they were registered.\n\t */\n\tpublic instancesList(): T[] {\n\t\tconst orderedInstances: { instance: T; order: number }[] = [];\n\t\tfor (const instanceName in this._instances) {\n\t\t\torderedInstances.push({\n\t\t\t\tinstance: this._instances[instanceName],\n\t\t\t\torder: this._generators[instanceName].order\n\t\t\t});\n\t\t}\n\t\treturn orderedInstances.sort((a, b) => a.order - b.order).map(o => o.instance);\n\t}\n\n\t/**\n\t * Get all the generator names in the order they were registered.\n\t * @returns The ordered generator names.\n\t */\n\tpublic names(): string[] {\n\t\tconst orderedNames: { name: string; order: number }[] = [];\n\t\tfor (const generator in this._generators) {\n\t\t\torderedNames.push({\n\t\t\t\tname: generator,\n\t\t\t\torder: this._generators[generator].order\n\t\t\t});\n\t\t}\n\t\treturn orderedNames.sort((a, b) => a.order - b.order).map(o => o.name);\n\t}\n\n\t/**\n\t * Does the factory contain the name.\n\t * @param name The name of the instance to find.\n\t * @returns True if the factory has a matching name.\n\t */\n\tpublic hasName(name: string): boolean {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\treturn Is.stringValue(this._matcher(Object.keys(this._generators), name));\n\t}\n\n\t/**\n\t * Remove any instances of the given name.\n\t * @param name The name of the instances to remove.\n\t * @internal\n\t */\n\tprivate removeInstance(name: string): void {\n\t\tdelete this._instances[name];\n\t}\n\n\t/**\n\t * Match the requested name to the generator name.\n\t * @param names The list of names for all the generators.\n\t * @param name The name to match.\n\t * @returns The matched name or undefined if no match.\n\t * @internal\n\t */\n\tprivate defaultMatcher(names: string[], name: string): string | undefined {\n\t\treturn this._generators[name] ? name : undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/factories/factory.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport { Guards } from \"../utils/guards.js\";\nimport { Is } from \"../utils/is.js\";\nimport { SharedStore } from \"../utils/sharedStore.js\";\n\n/**\n * Factory for creating implementation of generic types.\n */\nexport class Factory<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Factory<unknown>>();\n\n\t/**\n\t * Type name for the instances.\n\t * @internal\n\t */\n\tprivate readonly _typeName: string;\n\n\t/**\n\t * Store the generators.\n\t * @internal\n\t */\n\tprivate _generators: {\n\t\t[name: string]: {\n\t\t\tgenerator: (args?: unknown) => T;\n\t\t\torder: number;\n\t\t};\n\t};\n\n\t/**\n\t * Store the created instances.\n\t * @internal\n\t */\n\tprivate _instances: { [name: string]: T };\n\n\t/**\n\t * Counter for the ordering.\n\t * @internal\n\t */\n\tprivate _orderCounter: number;\n\n\t/**\n\t * Automatically created an instance when registered.\n\t * @internal\n\t */\n\tprivate readonly _autoInstance: boolean;\n\n\t/**\n\t * Match the name of the instance.\n\t * @internal\n\t */\n\tprivate readonly _matcher: (names: string[], name: string) => string | undefined;\n\n\t/**\n\t * Create a new instance of Factory, private use createFactory.\n\t * @param typeName The type name for the instances.\n\t * @param autoInstance Automatically create an instance when registered.\n\t * @param matcher Match the name of the instance.\n\t * @internal\n\t */\n\tprivate constructor(\n\t\ttypeName: string,\n\t\tautoInstance: boolean = false,\n\t\tmatcher?: (names: string[], name: string) => string | undefined\n\t) {\n\t\tthis._typeName = typeName;\n\t\tthis._generators = {};\n\t\tthis._instances = {};\n\t\tthis._orderCounter = 0;\n\t\tthis._autoInstance = autoInstance;\n\t\tthis._matcher = matcher ?? this.defaultMatcher.bind(this);\n\t}\n\n\t/**\n\t * Create a new factory, which is shared throughout all library instances.\n\t * @param typeName The type name for the instances.\n\t * @param autoInstance Automatically create an instance when registered.\n\t * @param matcher Match the name of the instance.\n\t * @returns The factory instance.\n\t */\n\tpublic static createFactory<U>(\n\t\ttypeName: string,\n\t\tautoInstance: boolean = false,\n\t\tmatcher?: (names: string[], name: string) => string | undefined\n\t): Factory<U> {\n\t\tconst factories = Factory.getFactories();\n\n\t\tif (Is.undefined(factories[typeName])) {\n\t\t\tfactories[typeName] = new Factory<U>(typeName, autoInstance, matcher);\n\t\t}\n\t\treturn factories[typeName] as Factory<U>;\n\t}\n\n\t/**\n\t * Get all the factories.\n\t * @returns All the factories.\n\t */\n\tpublic static getFactories(): { [typeName: string]: Factory<unknown> } {\n\t\treturn SharedStore.get<{\n\t\t\t[typeName: string]: Factory<unknown>;\n\t\t}>(\"factories\", () => ({}));\n\t}\n\n\t/**\n\t * Get a specific factory by type name.\n\t * @param typeName The type name of the factory.\n\t * @returns The factory instance if it exists, otherwise undefined.\n\t */\n\tpublic static getFactory<T = unknown>(typeName: string): Factory<T> | undefined {\n\t\tconst factories = Factory.getFactories();\n\n\t\treturn factories[typeName] as Factory<T> | undefined;\n\t}\n\n\t/**\n\t * Reset all the factories, which removes any created instances, but not the registrations.\n\t */\n\tpublic static resetFactories(): void {\n\t\tconst factories = Factory.getFactories();\n\n\t\tfor (const typeName in factories) {\n\t\t\tfactories[typeName].reset();\n\t\t}\n\t}\n\n\t/**\n\t * Clear all the factories, which removes anything registered with the factories.\n\t */\n\tpublic static clearFactories(): void {\n\t\tconst factories = Factory.getFactories();\n\n\t\tfor (const typeName in factories) {\n\t\t\tfactories[typeName].clear();\n\t\t}\n\t}\n\n\t/**\n\t * Get the type name of the factory.\n\t * @returns The type name of the factory.\n\t */\n\tpublic typeName(): string {\n\t\treturn this._typeName;\n\t}\n\n\t/**\n\t * Register a new generator.\n\t * @param name The name of the generator.\n\t * @param generator The function to create an instance.\n\t */\n\tpublic register<U extends T>(name: string, generator: (args?: unknown) => U): void {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tGuards.function(Factory.CLASS_NAME, nameof(generator), generator);\n\t\tthis._generators[name] = {\n\t\t\tgenerator,\n\t\t\torder: this._orderCounter++\n\t\t};\n\n\t\t// Remove any existing instance\n\t\tthis.removeInstance(name);\n\t\tif (this._autoInstance) {\n\t\t\tthis._instances[name] = generator();\n\t\t}\n\t}\n\n\t/**\n\t * Unregister a generator.\n\t * @param name The name of the generator to unregister.\n\t * @throws GuardError if the parameters are invalid.\n\t * @throws GeneralError if no generator exists.\n\t */\n\tpublic unregister(name: string): void {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tif (!this._generators[name]) {\n\t\t\tthrow new GeneralError(Factory.CLASS_NAME, \"noUnregister\", {\n\t\t\t\ttypeName: this._typeName,\n\t\t\t\tname\n\t\t\t});\n\t\t}\n\t\tdelete this._generators[name];\n\t\t// Remove any existing instance\n\t\tthis.removeInstance(name);\n\t}\n\n\t/**\n\t * Get a generator instance.\n\t * @param name The name of the instance to generate.\n\t * @returns An instance of the item.\n\t * @throws GuardError if the parameters are invalid.\n\t * @throws GeneralError if no item exists to get.\n\t */\n\tpublic get<U extends T>(name: string): U {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tconst instance = this.getIfExists(name);\n\t\tif (!instance) {\n\t\t\tthrow new GeneralError(Factory.CLASS_NAME, \"noGet\", {\n\t\t\t\ttypeName: this._typeName,\n\t\t\t\tname\n\t\t\t});\n\t\t}\n\t\treturn instance as U;\n\t}\n\n\t/**\n\t * Get a generator instance with no exceptions.\n\t * @param name The name of the instance to generate.\n\t * @returns An instance of the item or undefined if it does not exist.\n\t */\n\tpublic getIfExists<U extends T>(name?: string): U | undefined {\n\t\tif (Is.empty(name)) {\n\t\t\treturn;\n\t\t}\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\n\t\tconst matchName = this._matcher(Object.keys(this._generators), name);\n\n\t\tif (Is.stringValue(matchName) && this._generators[matchName]) {\n\t\t\tif (!this._instances[matchName]) {\n\t\t\t\tthis._instances[matchName] = this._generators[matchName].generator();\n\t\t\t}\n\t\t\tif (this._instances[matchName]) {\n\t\t\t\treturn this._instances[matchName] as U;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Create a new instance without caching it.\n\t * @param name The name of the instance to generate.\n\t * @param args The arguments to pass to the generator.\n\t * @returns A new instance of the item.\n\t * @throws GuardError if the parameters are invalid.\n\t * @throws GeneralError if no item exists to create.\n\t */\n\tpublic create<U extends T>(name: string, args?: unknown): U {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tconst instance = this.createIfExists(name, args);\n\t\tif (!instance) {\n\t\t\tthrow new GeneralError(Factory.CLASS_NAME, \"noCreate\", {\n\t\t\t\ttypeName: this._typeName,\n\t\t\t\tname,\n\t\t\t\targs: Is.undefined(args) ? \"\" : JSON.stringify(args)\n\t\t\t});\n\t\t}\n\t\treturn instance as U;\n\t}\n\n\t/**\n\t * Create a new instance without caching it if it exists.\n\t * @param name The name of the instance to generate.\n\t * @param args The arguments to pass to the generator.\n\t * @returns A new instance of the item if it exists.\n\t * @throws GuardError if the parameters are invalid.\n\t */\n\tpublic createIfExists<U extends T>(name: string, args?: unknown): U | undefined {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\tconst matchName = this._matcher(Object.keys(this._generators), name);\n\n\t\tif (Is.stringValue(matchName) && this._generators[matchName]) {\n\t\t\treturn this._generators[matchName].generator(args) as U;\n\t\t}\n\t}\n\n\t/**\n\t * Remove all the instances and leave the generators intact.\n\t */\n\tpublic reset(): void {\n\t\tfor (const name in this._generators) {\n\t\t\tthis.removeInstance(name);\n\t\t}\n\t\tthis._instances = {};\n\t}\n\n\t/**\n\t * Remove all the instances and the generators.\n\t */\n\tpublic clear(): void {\n\t\tthis._instances = {};\n\t\tthis._generators = {};\n\t\tthis._orderCounter = 0;\n\t}\n\n\t/**\n\t * Get all the instances as a map.\n\t * @returns The instances as a map.\n\t */\n\tpublic instancesMap(): { [name: string]: T } {\n\t\treturn this._instances;\n\t}\n\n\t/**\n\t * Get all the instances as a list in the order they were registered.\n\t * @returns The instances as a list in the order they were registered.\n\t */\n\tpublic instancesList(): T[] {\n\t\tconst orderedInstances: { instance: T; order: number }[] = [];\n\t\tfor (const instanceName in this._instances) {\n\t\t\torderedInstances.push({\n\t\t\t\tinstance: this._instances[instanceName],\n\t\t\t\torder: this._generators[instanceName].order\n\t\t\t});\n\t\t}\n\t\treturn orderedInstances.sort((a, b) => a.order - b.order).map(o => o.instance);\n\t}\n\n\t/**\n\t * Get all the generator names in the order they were registered.\n\t * @returns The ordered generator names.\n\t */\n\tpublic names(): string[] {\n\t\tconst orderedNames: { name: string; order: number }[] = [];\n\t\tfor (const generator in this._generators) {\n\t\t\torderedNames.push({\n\t\t\t\tname: generator,\n\t\t\t\torder: this._generators[generator].order\n\t\t\t});\n\t\t}\n\t\treturn orderedNames.sort((a, b) => a.order - b.order).map(o => o.name);\n\t}\n\n\t/**\n\t * Does the factory contain the name.\n\t * @param name The name of the instance to find.\n\t * @returns True if the factory has a matching name.\n\t */\n\tpublic hasName(name: string): boolean {\n\t\tGuards.stringValue(Factory.CLASS_NAME, nameof(name), name);\n\t\treturn Is.stringValue(this._matcher(Object.keys(this._generators), name));\n\t}\n\n\t/**\n\t * Remove any instances of the given name.\n\t * @param name The name of the instances to remove.\n\t * @internal\n\t */\n\tprivate removeInstance(name: string): void {\n\t\tdelete this._instances[name];\n\t}\n\n\t/**\n\t * Match the requested name to the generator name.\n\t * @param names The list of names for all the generators.\n\t * @param name The name to match.\n\t * @returns The matched name or undefined if no match.\n\t * @internal\n\t */\n\tprivate defaultMatcher(names: string[], name: string): string | undefined {\n\t\treturn this._generators[name] ? name : undefined;\n\t}\n}\n"]}
@@ -234,12 +234,7 @@ export class AsyncCache {
234
234
  * @internal
235
235
  */
236
236
  static getSharedCache() {
237
- let sharedCache = SharedStore.get(AsyncCache._CACHE_KEY);
238
- if (Is.undefined(sharedCache)) {
239
- sharedCache = {};
240
- SharedStore.set(AsyncCache._CACHE_KEY, sharedCache);
241
- }
242
- return sharedCache;
237
+ return SharedStore.get(AsyncCache._CACHE_KEY, () => ({}));
243
238
  }
244
239
  /**
245
240
  * Resolve a waiter by re-running its request method safely.
@@ -1 +1 @@
1
- {"version":3,"file":"asyncCache.js","sourceRoot":"","sources":["../../../src/utils/asyncCache.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;GAEG;AACH,MAAM,OAAO,UAAU;IACtB;;;OAGG;IACK,MAAM,CAAU,UAAU,GAAG,YAAY,CAAC;IAElD;;;OAGG;IACK,MAAM,CAAU,sBAAsB,GAAG,yBAAyB,CAAC;IAE3E;;;OAGG;IACK,MAAM,CAAU,iBAAiB,GAAG,uBAAuB,CAAC;IAEpE;;;OAGG;IACK,MAAM,CAAU,oBAAoB,GAAG,IAAI,CAAC;IAEpD;;;;;;;OAOG;IACI,MAAM,CAAC,KAAK,CAAC,IAAI,CACvB,GAAW,EACX,KAAyB,EACzB,aAA+B,EAC/B,aAAuB;QAEvB,MAAM,YAAY,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,8CAA8C;YAC9C,OAAO,aAAa,EAAE,CAAC;QACxB,CAAC;QAED,UAAU,CAAC,cAAc,EAAE,CAAC;QAC5B,8EAA8E;QAC9E,uGAAuG;QACvG,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAE/B,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAK,CAAC;QAC7C,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAE/B,uCAAuC;QACvC,IAAI,WAAW,EAAE,CAAC;YACjB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnC,2DAA2D;gBAC3D,OAAO,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAC5C,CAAC;iBAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzC,2DAA2D;gBAC3D,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC;YAED,8DAA8D;YAC9D,4DAA4D;YAC5D,2BAA2B;YAE3B,IAAI,aAAgE,CAAC;YACrE,IAAI,YAAsD,CAAC;YAC3D,MAAM,IAAI,GAAG,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC/C,aAAa,GAAG,OAAO,CAAC;gBACxB,YAAY,GAAG,MAAM,CAAC;YACvB,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;gBACzD,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC;oBAC7B,aAAa;oBACb,OAAO,EAAE,aAAa;oBACtB,MAAM,EAAE,YAAY;iBACpB,CAAC,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC;QACb,CAAC;QAED,mDAAmD;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACnC,MAAM,UAAU,GAUZ;YACH,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,EAAE;YAChB,OAAO;SACP,CAAC;QACF,KAAK,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;QACxB,UAAU,CAAC,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAE7C,0DAA0D;QAC1D,0DAA0D;QAC1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtC,+CAA+C;YAC/C,aAAa,EAAE;gBACd,wDAAwD;iBACvD,IAAI,CAAC,GAAG,CAAC,EAAE;gBACX,kDAAkD;gBAClD,UAAU,CAAC,UAAU,GAAG,KAAK,CAAC;gBAC9B,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC;gBAExB,oDAAoD;gBACpD,OAAO,CAAC,GAAG,CAAC,CAAC;gBACb,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;oBAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC;gBACD,UAAU,CAAC,YAAY,GAAG,EAAE,CAAC;gBAC7B,OAAO,GAAG,CAAC;YACZ,CAAC,CAAC;gBACF,wDAAwD;iBACvD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBACvB,qBAAqB;gBACrB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,UAAU,CAAC,UAAU,GAAG,KAAK,CAAC;gBAE9B,qDAAqD;gBACrD,IAAI,aAAa,IAAI,KAAK,EAAE,CAAC;oBAC5B,qEAAqE;oBACrE,UAAU,CAAC,KAAK,GAAG,GAAG,CAAC;oBACvB,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;wBAC5C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAClB,CAAC;oBACD,gDAAgD;oBAChD,UAAU,CAAC,YAAY,GAAG,EAAE,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACP,qDAAqD;oBACrD,mDAAmD;oBACnD,gDAAgD;oBAChD,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;wBAC5C,mEAAmE;wBACnE,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;oBACzE,CAAC;oBACD,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,UAAU,EAAE,CAAC;wBAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;oBACnB,CAAC;gBACF,CAAC;YACF,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAc,GAAW;QAC/C,IAAI,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACpC,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAK,CAAC;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAEzB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;YAC9B,2DAA2D;YAC3D,OAAO,KAAK,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;YAC7B,2DAA2D;YAC3D,MAAM,KAAK,CAAC,KAAc,CAAC;QAC5B,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAc,GAAW,EAAE,KAAQ,EAAE,KAAc;QACzE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,KAAK,CAAC,GAAG,CAAC,GAAG;YACZ,MAAM,EAAE,KAAK;YACb,YAAY,EAAE,EAAE;YAChB,OAAO;SACP,CAAC;QACF,UAAU,CAAC,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,MAAM,CAAC,GAAW;QAC/B,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,sBAAsB,CAAC,CAAC;QAC3E,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACrB,UAAU,CAAC,kBAAkB,EAAE,CAAC;QACjC,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,UAAU,CAAC,MAAe;QACvC,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,sBAAsB,CAAC,CAAC;YAC3E,IAAI,cAAc,GAAG,KAAK,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;gBAC3B,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC9B,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;wBACvB,cAAc,GAAG,IAAI,CAAC;oBACvB,CAAC;oBACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC;gBACrB,CAAC;YACF,CAAC;YACD,IAAI,cAAc,EAAE,CAAC;gBACpB,UAAU,CAAC,kBAAkB,EAAE,CAAC;YACjC,CAAC;QACF,CAAC;aAAM,CAAC;YACP,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YAC3C,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;YAC9D,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3D,CAAC;IACF,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,cAAc;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC/E,IAAI,GAAG,GAAG,WAAW,GAAG,UAAU,CAAC,oBAAoB,EAAE,CAAC;YACzD,OAAO;QACR,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;QAC1C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;YAC1C,mFAAmF;YACnF,kFAAkF;YAClF,iEAAiE;YACjE,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;YACnD,OAAO;QACR,CAAC;QAED,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QACnD,UAAU,CAAC,kBAAkB,EAAE,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,cAAc;QAa5B,IAAI,WAAW,GAAG,WAAW,CAAC,GAAG,CAY9B,UAAU,CAAC,UAAU,CAAC,CAAC;QAE1B,IAAI,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,WAAW,GAAG,EAAE,CAAC;YACjB,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACrD,CAAC;QAED,OAAO,WAAW,CAAC;IACpB,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,KAAK,CAAC,aAAa,CACjC,aAA+B,EAC/B,OAA4C,EAC5C,MAAkC;QAElC,IAAI,CAAC;YACJ,OAAO,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,OAAO,EAAE,CAAC;YAClB,MAAM,CAAC,OAAO,CAAC,CAAC;QACjB,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,kBAAkB;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAE1C,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;YAC9D,OAAO;QACR,CAAC;QAED,IAAI,UAA8B,CAAC;QACnC,IAAI,aAAa,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAC5C,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC9B,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,GAAG,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;gBACzD,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC;YACxB,CAAC;iBAAM,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,aAAa,EAAE,CAAC;gBACnD,aAAa,GAAG,OAAO,CAAC;gBACxB,UAAU,GAAG,QAAQ,CAAC;YACvB,CAAC;QACF,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IAChE,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,mBAAmB,CAAC,GAAW,EAAE,OAAe;QAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;QAC1C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;YAC/C,OAAO;QACR,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;IACzD,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,cAAc,CAAC,GAAW;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YAC9F,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,IAAI,CAAC;QACb,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,aAAa;QAC3B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,sBAAsB,CAAC,CAAC;QAC3E,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,OAAO,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC;IAC5D,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\n\n/**\n * Cache the results from asynchronous requests.\n */\nexport class AsyncCache {\n\t/**\n\t * Cache key for the shared cache object in the SharedStore.\n\t * @internal\n\t */\n\tprivate static readonly _CACHE_KEY = \"asyncCache\";\n\n\t/**\n\t * Cache key for the entry key of the soonest-expiring cache entry, used to optimize cleanup.\n\t * @internal\n\t */\n\tprivate static readonly _NEXT_EXPIRY_CACHE_KEY = \"asyncCacheNextExpiryKey\";\n\n\t/**\n\t * Cache key for the timestamp of the last full cleanup scan.\n\t * @internal\n\t */\n\tprivate static readonly _LAST_CLEANUP_KEY = \"asyncCacheLastCleanup\";\n\n\t/**\n\t * Minimum interval in ms between full cleanup scans.\n\t * @internal\n\t */\n\tprivate static readonly _CLEANUP_INTERVAL_MS = 5000;\n\n\t/**\n\t * Execute an async request and cache the result.\n\t * @param key The key for the entry in the cache.\n\t * @param ttlMs The TTL of the entry in the cache.\n\t * @param requestMethod The method to call if not cached.\n\t * @param cacheFailures Cache failure results, defaults to false.\n\t * @returns The response.\n\t */\n\tpublic static async exec<T = unknown>(\n\t\tkey: string,\n\t\tttlMs: number | undefined,\n\t\trequestMethod: () => Promise<T>,\n\t\tcacheFailures?: boolean\n\t): Promise<T> {\n\t\tconst cacheEnabled = Is.integer(ttlMs) && ttlMs > 0;\n\t\tif (!cacheEnabled) {\n\t\t\t// No caching, just execute the request method\n\t\t\treturn requestMethod();\n\t\t}\n\n\t\tAsyncCache.cleanupExpired();\n\t\t// Cleanup will not necessarily remove the entry for the key we are requesting\n\t\t// as it is throttled, so we also check and evict if expired here to ensure we don't return stale data.\n\t\tAsyncCache.evictIfExpired(key);\n\n\t\tconst cache = AsyncCache.getSharedCache<T>();\n\t\tconst cachedEntry = cache[key];\n\n\t\t// Do we have a cache entry for the key\n\t\tif (cachedEntry) {\n\t\t\tif (!Is.empty(cachedEntry.result)) {\n\t\t\t\t// If the cache has already resulted in a value, resolve it\n\t\t\t\treturn Promise.resolve(cachedEntry.result);\n\t\t\t} else if (!Is.empty(cachedEntry.error)) {\n\t\t\t\t// If the cache has already resulted in an error, reject it\n\t\t\t\treturn Promise.reject(cachedEntry.error);\n\t\t\t}\n\n\t\t\t// Otherwise create a promise to return and store the resolver\n\t\t\t// and rejector in the cache entry, so that we can call then\n\t\t\t// when the request is done\n\n\t\t\tlet storedResolve: ((value: T | PromiseLike<T>) => void) | undefined;\n\t\t\tlet storedReject: ((reason?: unknown) => void) | undefined;\n\t\t\tconst wait = new Promise<T>((resolve, reject) => {\n\t\t\t\tstoredResolve = resolve;\n\t\t\t\tstoredReject = reject;\n\t\t\t});\n\t\t\tif (!Is.empty(storedResolve) && !Is.empty(storedReject)) {\n\t\t\t\tcachedEntry.promiseQueue.push({\n\t\t\t\t\trequestMethod,\n\t\t\t\t\tresolve: storedResolve,\n\t\t\t\t\treject: storedReject\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn wait;\n\t\t}\n\n\t\t// If we don't have a cache entry, create a new one\n\t\tconst expires = Date.now() + ttlMs;\n\t\tconst cacheEntry: {\n\t\t\tresult?: T;\n\t\t\terror?: unknown;\n\t\t\tinProgress?: boolean;\n\t\t\tpromiseQueue: {\n\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t}[];\n\t\t\texpires: number;\n\t\t} = {\n\t\t\tinProgress: true,\n\t\t\tpromiseQueue: [],\n\t\t\texpires\n\t\t};\n\t\tcache[key] = cacheEntry;\n\t\tAsyncCache.updateNextExpiryKey(key, expires);\n\n\t\t// Return a promise that wraps the original request method\n\t\t// so that we can store any results or errors in the cache\n\t\treturn new Promise((resolve, reject) => {\n\t\t\t// Call the request method and store the result\n\t\t\trequestMethod()\n\t\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t\t\t.then(res => {\n\t\t\t\t\t// If the request was successful, store the result\n\t\t\t\t\tcacheEntry.inProgress = false;\n\t\t\t\t\tcacheEntry.result = res;\n\n\t\t\t\t\t// and resolve both this promise and all the waiters\n\t\t\t\t\tresolve(res);\n\t\t\t\t\tfor (const wait of cacheEntry.promiseQueue) {\n\t\t\t\t\t\twait.resolve(res);\n\t\t\t\t\t}\n\t\t\t\t\tcacheEntry.promiseQueue = [];\n\t\t\t\t\treturn res;\n\t\t\t\t})\n\t\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\t// Reject the promise\n\t\t\t\t\treject(err);\n\t\t\t\t\tcacheEntry.inProgress = false;\n\n\t\t\t\t\t// Handle the waiters based on the cacheFailures flag\n\t\t\t\t\tif (cacheFailures ?? false) {\n\t\t\t\t\t\t// If we are caching failures, store the error and reject the waiters\n\t\t\t\t\t\tcacheEntry.error = err;\n\t\t\t\t\t\tfor (const wait of cacheEntry.promiseQueue) {\n\t\t\t\t\t\t\twait.reject(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Clear the waiters so we don't call them again\n\t\t\t\t\t\tcacheEntry.promiseQueue = [];\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If not caching failures for any queued requests we\n\t\t\t\t\t\t// have no value to either resolve or reject, so we\n\t\t\t\t\t\t// just resolve with the original request method\n\t\t\t\t\t\tfor (const wait of cacheEntry.promiseQueue) {\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\t\t\t\t\t\tAsyncCache.resolveWaiter(wait.requestMethod, wait.resolve, wait.reject);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (cache[key] === cacheEntry) {\n\t\t\t\t\t\t\tdelete cache[key];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Get an entry from the cache.\n\t * @param key The key to get from the cache.\n\t * @returns The item from the cache if it exists, or undefined if the key is missing, expired, or\n\t * its request is still in-progress. Throws if a cached failure exists for the key.\n\t */\n\tpublic static async get<T = unknown>(key: string): Promise<T | undefined> {\n\t\tif (AsyncCache.evictIfExpired(key)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst cache = AsyncCache.getSharedCache<T>();\n\t\tconst entry = cache[key];\n\n\t\tif (!Is.empty(entry?.result)) {\n\t\t\t// If the cache has already resulted in a value, resolve it\n\t\t\treturn entry.result;\n\t\t}\n\n\t\tif (!Is.empty(entry?.error)) {\n\t\t\t// If the cache has already resulted in an error, reject it\n\t\t\tthrow entry.error as Error;\n\t\t}\n\t}\n\n\t/**\n\t * Set an entry into the cache.\n\t * @param key The key to set in the cache.\n\t * @param value The value to set in the cache.\n\t * @param ttlMs The TTL of the entry in the cache in milliseconds. Defaults to 1000 (1 second).\n\t * @returns A promise that resolves when the entry has been stored.\n\t */\n\tpublic static async set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> {\n\t\tconst expires = Date.now() + (ttlMs ?? 1000);\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tcache[key] = {\n\t\t\tresult: value,\n\t\t\tpromiseQueue: [],\n\t\t\texpires\n\t\t};\n\t\tAsyncCache.updateNextExpiryKey(key, expires);\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * @param key The key to remove from the cache.\n\t */\n\tpublic static remove(key: string): void {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tdelete cache[key];\n\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\tif (nextKey === key) {\n\t\t\tAsyncCache.recalculateNextKey();\n\t\t}\n\t}\n\n\t/**\n\t * Clear the cache.\n\t * @param prefix Optional prefix to clear only entries with that prefix.\n\t */\n\tpublic static clearCache(prefix?: string): void {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tif (Is.stringValue(prefix)) {\n\t\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\t\tlet nextKeyRemoved = false;\n\t\t\tfor (const entry in cache) {\n\t\t\t\tif (entry.startsWith(prefix)) {\n\t\t\t\t\tif (entry === nextKey) {\n\t\t\t\t\t\tnextKeyRemoved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdelete cache[entry];\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (nextKeyRemoved) {\n\t\t\t\tAsyncCache.recalculateNextKey();\n\t\t\t}\n\t\t} else {\n\t\t\tSharedStore.set(AsyncCache._CACHE_KEY, {});\n\t\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);\n\t\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, Date.now());\n\t\t}\n\t}\n\n\t/**\n\t * Perform a cleanup of the expired entries in the cache.\n\t */\n\tpublic static cleanupExpired(): void {\n\t\tconst now = Date.now();\n\t\tconst lastCleanup = SharedStore.get<number>(AsyncCache._LAST_CLEANUP_KEY) ?? 0;\n\t\tif (now - lastCleanup < AsyncCache._CLEANUP_INTERVAL_MS) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst expiry = AsyncCache.getNextExpiry();\n\t\tif (expiry !== undefined && expiry > now) {\n\t\t\t// Nothing is expired yet. Stamp the time so the throttle suppresses further checks\n\t\t\t// for the next interval. Per-key freshness is still guaranteed because exec() and\n\t\t\t// get() call evictIfExpired() directly before reading the cache.\n\t\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);\n\t\t\treturn;\n\t\t}\n\n\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);\n\t\tAsyncCache.recalculateNextKey();\n\t}\n\n\t/**\n\t * Get the shared cache.\n\t * @returns The shared cache.\n\t * @internal\n\t */\n\tprivate static getSharedCache<T = unknown>(): {\n\t\t[url: string]: {\n\t\t\tresult?: T;\n\t\t\terror?: unknown;\n\t\t\tinProgress?: boolean;\n\t\t\tpromiseQueue: {\n\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t}[];\n\t\t\texpires: number;\n\t\t};\n\t} {\n\t\tlet sharedCache = SharedStore.get<{\n\t\t\t[url: string]: {\n\t\t\t\tresult?: T;\n\t\t\t\terror?: unknown;\n\t\t\t\tinProgress?: boolean;\n\t\t\t\tpromiseQueue: {\n\t\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t\t}[];\n\t\t\t\texpires: number;\n\t\t\t};\n\t\t}>(AsyncCache._CACHE_KEY);\n\n\t\tif (Is.undefined(sharedCache)) {\n\t\t\tsharedCache = {};\n\t\t\tSharedStore.set(AsyncCache._CACHE_KEY, sharedCache);\n\t\t}\n\n\t\treturn sharedCache;\n\t}\n\n\t/**\n\t * Resolve a waiter by re-running its request method safely.\n\t * @param requestMethod The method to execute.\n\t * @param resolve The resolver for the waiter.\n\t * @param reject The rejector for the waiter.\n\t * @internal\n\t */\n\tprivate static async resolveWaiter<T>(\n\t\trequestMethod: () => Promise<T>,\n\t\tresolve: (value: T | PromiseLike<T>) => void,\n\t\treject: (reason?: unknown) => void\n\t): Promise<void> {\n\t\ttry {\n\t\t\tresolve(await requestMethod());\n\t\t} catch (waitErr) {\n\t\t\treject(waitErr);\n\t\t}\n\t}\n\n\t/**\n\t * Scan all cache entries, delete any that have expired, and record the key of the\n\t * soonest-expiring remaining entry.\n\t * @internal\n\t */\n\tprivate static recalculateNextKey(): void {\n\t\tconst now = Date.now();\n\t\tconst cache = AsyncCache.getSharedCache();\n\n\t\tif (Object.keys(cache).length === 0) {\n\t\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tlet newNextKey: string | undefined;\n\t\tlet newNextExpiry = Number.MAX_SAFE_INTEGER;\n\t\tfor (const entryKey in cache) {\n\t\t\tconst { expires, inProgress } = cache[entryKey];\n\t\t\tif (expires > 0 && expires < now && inProgress !== true) {\n\t\t\t\tdelete cache[entryKey];\n\t\t\t} else if (expires > 0 && expires < newNextExpiry) {\n\t\t\t\tnewNextExpiry = expires;\n\t\t\t\tnewNextKey = entryKey;\n\t\t\t}\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, newNextKey);\n\t}\n\n\t/**\n\t * Update the tracked next-expiry entry key if the given entry expires sooner than the current one.\n\t * @param key The cache entry key.\n\t * @param expires The expiry timestamp of the entry.\n\t * @internal\n\t */\n\tprivate static updateNextExpiryKey(key: string, expires: number): void {\n\t\tconst expiry = AsyncCache.getNextExpiry();\n\t\tif (expiry !== undefined && expiry <= expires) {\n\t\t\treturn;\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, key);\n\t}\n\n\t/**\n\t * Deletes the given key from the cache if it has expired and is not in-progress.\n\t * Returns true if the entry was evicted.\n\t * @param key The cache entry key to check.\n\t * @returns True if the expired entry was removed; otherwise, false.\n\t * @internal\n\t */\n\tprivate static evictIfExpired(key: string): boolean {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst entry = cache[key];\n\t\tif (!Is.empty(entry) && entry.expires > 0 && entry.expires < Date.now() && !entry.inProgress) {\n\t\t\tdelete cache[key];\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns the expiry timestamp of the tracked next-expiry entry, or undefined if there\n\t * is no tracked entry or it is no longer present in the cache.\n\t * @returns The next tracked expiry timestamp, if one is still available.\n\t * @internal\n\t */\n\tprivate static getNextExpiry(): number | undefined {\n\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\tif (Is.empty(nextKey)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst nextEntry = cache[nextKey];\n\t\treturn Is.empty(nextEntry) ? undefined : nextEntry.expires;\n\t}\n}\n"]}
1
+ {"version":3,"file":"asyncCache.js","sourceRoot":"","sources":["../../../src/utils/asyncCache.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;GAEG;AACH,MAAM,OAAO,UAAU;IACtB;;;OAGG;IACK,MAAM,CAAU,UAAU,GAAG,YAAY,CAAC;IAElD;;;OAGG;IACK,MAAM,CAAU,sBAAsB,GAAG,yBAAyB,CAAC;IAE3E;;;OAGG;IACK,MAAM,CAAU,iBAAiB,GAAG,uBAAuB,CAAC;IAEpE;;;OAGG;IACK,MAAM,CAAU,oBAAoB,GAAG,IAAI,CAAC;IAEpD;;;;;;;OAOG;IACI,MAAM,CAAC,KAAK,CAAC,IAAI,CACvB,GAAW,EACX,KAAyB,EACzB,aAA+B,EAC/B,aAAuB;QAEvB,MAAM,YAAY,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,8CAA8C;YAC9C,OAAO,aAAa,EAAE,CAAC;QACxB,CAAC;QAED,UAAU,CAAC,cAAc,EAAE,CAAC;QAC5B,8EAA8E;QAC9E,uGAAuG;QACvG,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAE/B,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAK,CAAC;QAC7C,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAE/B,uCAAuC;QACvC,IAAI,WAAW,EAAE,CAAC;YACjB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnC,2DAA2D;gBAC3D,OAAO,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAC5C,CAAC;iBAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzC,2DAA2D;gBAC3D,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC;YAED,8DAA8D;YAC9D,4DAA4D;YAC5D,2BAA2B;YAE3B,IAAI,aAAgE,CAAC;YACrE,IAAI,YAAsD,CAAC;YAC3D,MAAM,IAAI,GAAG,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC/C,aAAa,GAAG,OAAO,CAAC;gBACxB,YAAY,GAAG,MAAM,CAAC;YACvB,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;gBACzD,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC;oBAC7B,aAAa;oBACb,OAAO,EAAE,aAAa;oBACtB,MAAM,EAAE,YAAY;iBACpB,CAAC,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC;QACb,CAAC;QAED,mDAAmD;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACnC,MAAM,UAAU,GAUZ;YACH,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,EAAE;YAChB,OAAO;SACP,CAAC;QACF,KAAK,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;QACxB,UAAU,CAAC,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAE7C,0DAA0D;QAC1D,0DAA0D;QAC1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtC,+CAA+C;YAC/C,aAAa,EAAE;gBACd,wDAAwD;iBACvD,IAAI,CAAC,GAAG,CAAC,EAAE;gBACX,kDAAkD;gBAClD,UAAU,CAAC,UAAU,GAAG,KAAK,CAAC;gBAC9B,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC;gBAExB,oDAAoD;gBACpD,OAAO,CAAC,GAAG,CAAC,CAAC;gBACb,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;oBAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC;gBACD,UAAU,CAAC,YAAY,GAAG,EAAE,CAAC;gBAC7B,OAAO,GAAG,CAAC;YACZ,CAAC,CAAC;gBACF,wDAAwD;iBACvD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBACvB,qBAAqB;gBACrB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,UAAU,CAAC,UAAU,GAAG,KAAK,CAAC;gBAE9B,qDAAqD;gBACrD,IAAI,aAAa,IAAI,KAAK,EAAE,CAAC;oBAC5B,qEAAqE;oBACrE,UAAU,CAAC,KAAK,GAAG,GAAG,CAAC;oBACvB,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;wBAC5C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAClB,CAAC;oBACD,gDAAgD;oBAChD,UAAU,CAAC,YAAY,GAAG,EAAE,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACP,qDAAqD;oBACrD,mDAAmD;oBACnD,gDAAgD;oBAChD,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;wBAC5C,mEAAmE;wBACnE,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;oBACzE,CAAC;oBACD,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,UAAU,EAAE,CAAC;wBAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;oBACnB,CAAC;gBACF,CAAC;YACF,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAc,GAAW;QAC/C,IAAI,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACpC,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAK,CAAC;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAEzB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;YAC9B,2DAA2D;YAC3D,OAAO,KAAK,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;YAC7B,2DAA2D;YAC3D,MAAM,KAAK,CAAC,KAAc,CAAC;QAC5B,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAc,GAAW,EAAE,KAAQ,EAAE,KAAc;QACzE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,KAAK,CAAC,GAAG,CAAC,GAAG;YACZ,MAAM,EAAE,KAAK;YACb,YAAY,EAAE,EAAE;YAChB,OAAO;SACP,CAAC;QACF,UAAU,CAAC,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,MAAM,CAAC,GAAW;QAC/B,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,sBAAsB,CAAC,CAAC;QAC3E,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACrB,UAAU,CAAC,kBAAkB,EAAE,CAAC;QACjC,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,UAAU,CAAC,MAAe;QACvC,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,sBAAsB,CAAC,CAAC;YAC3E,IAAI,cAAc,GAAG,KAAK,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;gBAC3B,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC9B,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;wBACvB,cAAc,GAAG,IAAI,CAAC;oBACvB,CAAC;oBACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC;gBACrB,CAAC;YACF,CAAC;YACD,IAAI,cAAc,EAAE,CAAC;gBACpB,UAAU,CAAC,kBAAkB,EAAE,CAAC;YACjC,CAAC;QACF,CAAC;aAAM,CAAC;YACP,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YAC3C,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;YAC9D,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3D,CAAC;IACF,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,cAAc;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC/E,IAAI,GAAG,GAAG,WAAW,GAAG,UAAU,CAAC,oBAAoB,EAAE,CAAC;YACzD,OAAO;QACR,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;QAC1C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;YAC1C,mFAAmF;YACnF,kFAAkF;YAClF,iEAAiE;YACjE,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;YACnD,OAAO;QACR,CAAC;QAED,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QACnD,UAAU,CAAC,kBAAkB,EAAE,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,cAAc;QAa5B,OAAO,WAAW,CAAC,GAAG,CAYnB,UAAU,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,KAAK,CAAC,aAAa,CACjC,aAA+B,EAC/B,OAA4C,EAC5C,MAAkC;QAElC,IAAI,CAAC;YACJ,OAAO,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,OAAO,EAAE,CAAC;YAClB,MAAM,CAAC,OAAO,CAAC,CAAC;QACjB,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,kBAAkB;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAE1C,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;YAC9D,OAAO;QACR,CAAC;QAED,IAAI,UAA8B,CAAC;QACnC,IAAI,aAAa,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAC5C,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC9B,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,GAAG,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;gBACzD,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC;YACxB,CAAC;iBAAM,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,aAAa,EAAE,CAAC;gBACnD,aAAa,GAAG,OAAO,CAAC;gBACxB,UAAU,GAAG,QAAQ,CAAC;YACvB,CAAC;QACF,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IAChE,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,mBAAmB,CAAC,GAAW,EAAE,OAAe;QAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;QAC1C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;YAC/C,OAAO;QACR,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;IACzD,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,cAAc,CAAC,GAAW;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YAC9F,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,IAAI,CAAC;QACb,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,aAAa;QAC3B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAS,UAAU,CAAC,sBAAsB,CAAC,CAAC;QAC3E,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,OAAO,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC;IAC5D,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\n\n/**\n * Cache the results from asynchronous requests.\n */\nexport class AsyncCache {\n\t/**\n\t * Cache key for the shared cache object in the SharedStore.\n\t * @internal\n\t */\n\tprivate static readonly _CACHE_KEY = \"asyncCache\";\n\n\t/**\n\t * Cache key for the entry key of the soonest-expiring cache entry, used to optimize cleanup.\n\t * @internal\n\t */\n\tprivate static readonly _NEXT_EXPIRY_CACHE_KEY = \"asyncCacheNextExpiryKey\";\n\n\t/**\n\t * Cache key for the timestamp of the last full cleanup scan.\n\t * @internal\n\t */\n\tprivate static readonly _LAST_CLEANUP_KEY = \"asyncCacheLastCleanup\";\n\n\t/**\n\t * Minimum interval in ms between full cleanup scans.\n\t * @internal\n\t */\n\tprivate static readonly _CLEANUP_INTERVAL_MS = 5000;\n\n\t/**\n\t * Execute an async request and cache the result.\n\t * @param key The key for the entry in the cache.\n\t * @param ttlMs The TTL of the entry in the cache.\n\t * @param requestMethod The method to call if not cached.\n\t * @param cacheFailures Cache failure results, defaults to false.\n\t * @returns The response.\n\t */\n\tpublic static async exec<T = unknown>(\n\t\tkey: string,\n\t\tttlMs: number | undefined,\n\t\trequestMethod: () => Promise<T>,\n\t\tcacheFailures?: boolean\n\t): Promise<T> {\n\t\tconst cacheEnabled = Is.integer(ttlMs) && ttlMs > 0;\n\t\tif (!cacheEnabled) {\n\t\t\t// No caching, just execute the request method\n\t\t\treturn requestMethod();\n\t\t}\n\n\t\tAsyncCache.cleanupExpired();\n\t\t// Cleanup will not necessarily remove the entry for the key we are requesting\n\t\t// as it is throttled, so we also check and evict if expired here to ensure we don't return stale data.\n\t\tAsyncCache.evictIfExpired(key);\n\n\t\tconst cache = AsyncCache.getSharedCache<T>();\n\t\tconst cachedEntry = cache[key];\n\n\t\t// Do we have a cache entry for the key\n\t\tif (cachedEntry) {\n\t\t\tif (!Is.empty(cachedEntry.result)) {\n\t\t\t\t// If the cache has already resulted in a value, resolve it\n\t\t\t\treturn Promise.resolve(cachedEntry.result);\n\t\t\t} else if (!Is.empty(cachedEntry.error)) {\n\t\t\t\t// If the cache has already resulted in an error, reject it\n\t\t\t\treturn Promise.reject(cachedEntry.error);\n\t\t\t}\n\n\t\t\t// Otherwise create a promise to return and store the resolver\n\t\t\t// and rejector in the cache entry, so that we can call then\n\t\t\t// when the request is done\n\n\t\t\tlet storedResolve: ((value: T | PromiseLike<T>) => void) | undefined;\n\t\t\tlet storedReject: ((reason?: unknown) => void) | undefined;\n\t\t\tconst wait = new Promise<T>((resolve, reject) => {\n\t\t\t\tstoredResolve = resolve;\n\t\t\t\tstoredReject = reject;\n\t\t\t});\n\t\t\tif (!Is.empty(storedResolve) && !Is.empty(storedReject)) {\n\t\t\t\tcachedEntry.promiseQueue.push({\n\t\t\t\t\trequestMethod,\n\t\t\t\t\tresolve: storedResolve,\n\t\t\t\t\treject: storedReject\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn wait;\n\t\t}\n\n\t\t// If we don't have a cache entry, create a new one\n\t\tconst expires = Date.now() + ttlMs;\n\t\tconst cacheEntry: {\n\t\t\tresult?: T;\n\t\t\terror?: unknown;\n\t\t\tinProgress?: boolean;\n\t\t\tpromiseQueue: {\n\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t}[];\n\t\t\texpires: number;\n\t\t} = {\n\t\t\tinProgress: true,\n\t\t\tpromiseQueue: [],\n\t\t\texpires\n\t\t};\n\t\tcache[key] = cacheEntry;\n\t\tAsyncCache.updateNextExpiryKey(key, expires);\n\n\t\t// Return a promise that wraps the original request method\n\t\t// so that we can store any results or errors in the cache\n\t\treturn new Promise((resolve, reject) => {\n\t\t\t// Call the request method and store the result\n\t\t\trequestMethod()\n\t\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t\t\t.then(res => {\n\t\t\t\t\t// If the request was successful, store the result\n\t\t\t\t\tcacheEntry.inProgress = false;\n\t\t\t\t\tcacheEntry.result = res;\n\n\t\t\t\t\t// and resolve both this promise and all the waiters\n\t\t\t\t\tresolve(res);\n\t\t\t\t\tfor (const wait of cacheEntry.promiseQueue) {\n\t\t\t\t\t\twait.resolve(res);\n\t\t\t\t\t}\n\t\t\t\t\tcacheEntry.promiseQueue = [];\n\t\t\t\t\treturn res;\n\t\t\t\t})\n\t\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\t// Reject the promise\n\t\t\t\t\treject(err);\n\t\t\t\t\tcacheEntry.inProgress = false;\n\n\t\t\t\t\t// Handle the waiters based on the cacheFailures flag\n\t\t\t\t\tif (cacheFailures ?? false) {\n\t\t\t\t\t\t// If we are caching failures, store the error and reject the waiters\n\t\t\t\t\t\tcacheEntry.error = err;\n\t\t\t\t\t\tfor (const wait of cacheEntry.promiseQueue) {\n\t\t\t\t\t\t\twait.reject(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Clear the waiters so we don't call them again\n\t\t\t\t\t\tcacheEntry.promiseQueue = [];\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If not caching failures for any queued requests we\n\t\t\t\t\t\t// have no value to either resolve or reject, so we\n\t\t\t\t\t\t// just resolve with the original request method\n\t\t\t\t\t\tfor (const wait of cacheEntry.promiseQueue) {\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\t\t\t\t\t\tAsyncCache.resolveWaiter(wait.requestMethod, wait.resolve, wait.reject);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (cache[key] === cacheEntry) {\n\t\t\t\t\t\t\tdelete cache[key];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Get an entry from the cache.\n\t * @param key The key to get from the cache.\n\t * @returns The item from the cache if it exists, or undefined if the key is missing, expired, or\n\t * its request is still in-progress. Throws if a cached failure exists for the key.\n\t */\n\tpublic static async get<T = unknown>(key: string): Promise<T | undefined> {\n\t\tif (AsyncCache.evictIfExpired(key)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst cache = AsyncCache.getSharedCache<T>();\n\t\tconst entry = cache[key];\n\n\t\tif (!Is.empty(entry?.result)) {\n\t\t\t// If the cache has already resulted in a value, resolve it\n\t\t\treturn entry.result;\n\t\t}\n\n\t\tif (!Is.empty(entry?.error)) {\n\t\t\t// If the cache has already resulted in an error, reject it\n\t\t\tthrow entry.error as Error;\n\t\t}\n\t}\n\n\t/**\n\t * Set an entry into the cache.\n\t * @param key The key to set in the cache.\n\t * @param value The value to set in the cache.\n\t * @param ttlMs The TTL of the entry in the cache in milliseconds. Defaults to 1000 (1 second).\n\t * @returns A promise that resolves when the entry has been stored.\n\t */\n\tpublic static async set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> {\n\t\tconst expires = Date.now() + (ttlMs ?? 1000);\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tcache[key] = {\n\t\t\tresult: value,\n\t\t\tpromiseQueue: [],\n\t\t\texpires\n\t\t};\n\t\tAsyncCache.updateNextExpiryKey(key, expires);\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * @param key The key to remove from the cache.\n\t */\n\tpublic static remove(key: string): void {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tdelete cache[key];\n\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\tif (nextKey === key) {\n\t\t\tAsyncCache.recalculateNextKey();\n\t\t}\n\t}\n\n\t/**\n\t * Clear the cache.\n\t * @param prefix Optional prefix to clear only entries with that prefix.\n\t */\n\tpublic static clearCache(prefix?: string): void {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tif (Is.stringValue(prefix)) {\n\t\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\t\tlet nextKeyRemoved = false;\n\t\t\tfor (const entry in cache) {\n\t\t\t\tif (entry.startsWith(prefix)) {\n\t\t\t\t\tif (entry === nextKey) {\n\t\t\t\t\t\tnextKeyRemoved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdelete cache[entry];\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (nextKeyRemoved) {\n\t\t\t\tAsyncCache.recalculateNextKey();\n\t\t\t}\n\t\t} else {\n\t\t\tSharedStore.set(AsyncCache._CACHE_KEY, {});\n\t\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);\n\t\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, Date.now());\n\t\t}\n\t}\n\n\t/**\n\t * Perform a cleanup of the expired entries in the cache.\n\t */\n\tpublic static cleanupExpired(): void {\n\t\tconst now = Date.now();\n\t\tconst lastCleanup = SharedStore.get<number>(AsyncCache._LAST_CLEANUP_KEY) ?? 0;\n\t\tif (now - lastCleanup < AsyncCache._CLEANUP_INTERVAL_MS) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst expiry = AsyncCache.getNextExpiry();\n\t\tif (expiry !== undefined && expiry > now) {\n\t\t\t// Nothing is expired yet. Stamp the time so the throttle suppresses further checks\n\t\t\t// for the next interval. Per-key freshness is still guaranteed because exec() and\n\t\t\t// get() call evictIfExpired() directly before reading the cache.\n\t\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);\n\t\t\treturn;\n\t\t}\n\n\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);\n\t\tAsyncCache.recalculateNextKey();\n\t}\n\n\t/**\n\t * Get the shared cache.\n\t * @returns The shared cache.\n\t * @internal\n\t */\n\tprivate static getSharedCache<T = unknown>(): {\n\t\t[url: string]: {\n\t\t\tresult?: T;\n\t\t\terror?: unknown;\n\t\t\tinProgress?: boolean;\n\t\t\tpromiseQueue: {\n\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t}[];\n\t\t\texpires: number;\n\t\t};\n\t} {\n\t\treturn SharedStore.get<{\n\t\t\t[url: string]: {\n\t\t\t\tresult?: T;\n\t\t\t\terror?: unknown;\n\t\t\t\tinProgress?: boolean;\n\t\t\t\tpromiseQueue: {\n\t\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t\t}[];\n\t\t\t\texpires: number;\n\t\t\t};\n\t\t}>(AsyncCache._CACHE_KEY, () => ({}));\n\t}\n\n\t/**\n\t * Resolve a waiter by re-running its request method safely.\n\t * @param requestMethod The method to execute.\n\t * @param resolve The resolver for the waiter.\n\t * @param reject The rejector for the waiter.\n\t * @internal\n\t */\n\tprivate static async resolveWaiter<T>(\n\t\trequestMethod: () => Promise<T>,\n\t\tresolve: (value: T | PromiseLike<T>) => void,\n\t\treject: (reason?: unknown) => void\n\t): Promise<void> {\n\t\ttry {\n\t\t\tresolve(await requestMethod());\n\t\t} catch (waitErr) {\n\t\t\treject(waitErr);\n\t\t}\n\t}\n\n\t/**\n\t * Scan all cache entries, delete any that have expired, and record the key of the\n\t * soonest-expiring remaining entry.\n\t * @internal\n\t */\n\tprivate static recalculateNextKey(): void {\n\t\tconst now = Date.now();\n\t\tconst cache = AsyncCache.getSharedCache();\n\n\t\tif (Object.keys(cache).length === 0) {\n\t\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tlet newNextKey: string | undefined;\n\t\tlet newNextExpiry = Number.MAX_SAFE_INTEGER;\n\t\tfor (const entryKey in cache) {\n\t\t\tconst { expires, inProgress } = cache[entryKey];\n\t\t\tif (expires > 0 && expires < now && inProgress !== true) {\n\t\t\t\tdelete cache[entryKey];\n\t\t\t} else if (expires > 0 && expires < newNextExpiry) {\n\t\t\t\tnewNextExpiry = expires;\n\t\t\t\tnewNextKey = entryKey;\n\t\t\t}\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, newNextKey);\n\t}\n\n\t/**\n\t * Update the tracked next-expiry entry key if the given entry expires sooner than the current one.\n\t * @param key The cache entry key.\n\t * @param expires The expiry timestamp of the entry.\n\t * @internal\n\t */\n\tprivate static updateNextExpiryKey(key: string, expires: number): void {\n\t\tconst expiry = AsyncCache.getNextExpiry();\n\t\tif (expiry !== undefined && expiry <= expires) {\n\t\t\treturn;\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, key);\n\t}\n\n\t/**\n\t * Deletes the given key from the cache if it has expired and is not in-progress.\n\t * Returns true if the entry was evicted.\n\t * @param key The cache entry key to check.\n\t * @returns True if the expired entry was removed; otherwise, false.\n\t * @internal\n\t */\n\tprivate static evictIfExpired(key: string): boolean {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst entry = cache[key];\n\t\tif (!Is.empty(entry) && entry.expires > 0 && entry.expires < Date.now() && !entry.inProgress) {\n\t\t\tdelete cache[key];\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns the expiry timestamp of the tracked next-expiry entry, or undefined if there\n\t * is no tracked entry or it is no longer present in the cache.\n\t * @returns The next tracked expiry timestamp, if one is still available.\n\t * @internal\n\t */\n\tprivate static getNextExpiry(): number | undefined {\n\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\tif (Is.empty(nextKey)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst nextEntry = cache[nextKey];\n\t\treturn Is.empty(nextEntry) ? undefined : nextEntry.expires;\n\t}\n}\n"]}
@@ -227,17 +227,12 @@ export class I18n {
227
227
  * @internal
228
228
  */
229
229
  static getI18nShared() {
230
- let i18nShared = SharedStore.get("i18n");
231
- if (Is.undefined(i18nShared)) {
232
- i18nShared = {
233
- localeDictionaries: {},
234
- currentLocale: I18n.DEFAULT_LOCALE,
235
- localeChangedHandlers: {},
236
- dictionaryChangedHandlers: {}
237
- };
238
- SharedStore.set("i18n", i18nShared);
239
- }
240
- return i18nShared;
230
+ return SharedStore.get("i18n", () => ({
231
+ localeDictionaries: {},
232
+ currentLocale: I18n.DEFAULT_LOCALE,
233
+ localeChangedHandlers: {},
234
+ dictionaryChangedHandlers: {}
235
+ }));
241
236
  }
242
237
  }
243
238
  //# sourceMappingURL=i18n.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../src/utils/i18n.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { IntlMessageFormat } from \"intl-messageformat\";\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\nimport type { II18nShared } from \"../models/II18nShared.js\";\nimport type { ILocaleDictionary } from \"../models/ILocaleDictionary.js\";\n\n/**\n * Class to perform internationalization.\n */\nexport class I18n {\n\t/**\n\t * The default translation.\n\t */\n\tpublic static DEFAULT_LOCALE: string = \"en\";\n\n\t/**\n\t * Set the locale.\n\t * @param locale The new locale.\n\t */\n\tpublic static setLocale(locale: string): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\ti18nShared.currentLocale = locale;\n\n\t\tfor (const callback in i18nShared.localeChangedHandlers) {\n\t\t\ti18nShared.localeChangedHandlers[callback](i18nShared.currentLocale);\n\t\t}\n\t}\n\n\t/**\n\t * Get the locale.\n\t * @returns The current locale.\n\t */\n\tpublic static getLocale(): string {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn i18nShared.currentLocale;\n\t}\n\n\t/**\n\t * Add a locale dictionary.\n\t * @param locale The locale.\n\t * @param dictionary The dictionary to add.\n\t */\n\tpublic static addDictionary(locale: string, dictionary: ILocaleDictionary): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\tconst mergedKeys: { [key: string]: string } = {};\n\t\tI18n.flattenTranslationKeys(dictionary, \"\", mergedKeys);\n\t\ti18nShared.localeDictionaries[locale] = mergedKeys;\n\n\t\tfor (const callback in i18nShared.dictionaryChangedHandlers) {\n\t\t\ti18nShared.dictionaryChangedHandlers[callback](i18nShared.currentLocale);\n\t\t}\n\t}\n\n\t/**\n\t * Get a locale dictionary.\n\t * @param locale The locale.\n\t * @returns The dictionary of undefined if it does not exist.\n\t */\n\tpublic static getDictionary(locale: string): { [key: string]: string } | undefined {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn i18nShared.localeDictionaries[locale];\n\t}\n\n\t/**\n\t * Get all the locale dictionaries.\n\t * @returns The dictionaries.\n\t */\n\tpublic static getAllDictionaries(): {\n\t\t[locale: string]: { [key: string]: string };\n\t} {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn i18nShared.localeDictionaries;\n\t}\n\n\t/**\n\t * Add a locale changed handler.\n\t * @param id The id of the handler.\n\t * @param handler The handler to add.\n\t */\n\tpublic static addLocaleHandler(id: string, handler: (locale: string) => void): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\ti18nShared.localeChangedHandlers[id] = handler;\n\t}\n\n\t/**\n\t * Remove a locale changed handler.\n\t * @param id The id of the handler.\n\t */\n\tpublic static removeLocaleHandler(id: string): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\tdelete i18nShared.localeChangedHandlers[id];\n\t}\n\n\t/**\n\t * Add a dictionary changed handler.\n\t * @param id The id of the handler.\n\t * @param handler The handler to add.\n\t */\n\tpublic static addDictionaryHandler(id: string, handler: (locale: string) => void): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\ti18nShared.dictionaryChangedHandlers[id] = handler;\n\t}\n\n\t/**\n\t * Remove a dictionary changed handler.\n\t * @param id The id of the handler.\n\t */\n\tpublic static removeDictionaryHandler(id: string): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\tdelete i18nShared.dictionaryChangedHandlers[id];\n\t}\n\n\t/**\n\t * Format a message.\n\t * @param key The key of the message to format.\n\t * @param values The values to substitute into the message.\n\t * @param overrideLocale Override the locale.\n\t * @returns The formatted string.\n\t */\n\tpublic static formatMessage(\n\t\tkey: string,\n\t\tvalues?: { [key: string]: unknown },\n\t\toverrideLocale?: string\n\t): string {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\tlet cl = overrideLocale ?? i18nShared.currentLocale;\n\t\tif (cl.startsWith(\"debug-\")) {\n\t\t\tcl = I18n.DEFAULT_LOCALE;\n\t\t}\n\n\t\tif (!i18nShared.localeDictionaries[cl]) {\n\t\t\treturn `!!Missing ${cl}`;\n\t\t}\n\t\tif (!i18nShared.localeDictionaries[cl][key]) {\n\t\t\treturn `!!Missing ${cl}.${key}`;\n\t\t}\n\n\t\tif (i18nShared.currentLocale === \"debug-k\") {\n\t\t\treturn key;\n\t\t}\n\n\t\ttry {\n\t\t\tlet ret = new IntlMessageFormat(i18nShared.localeDictionaries[cl][key], cl).format(\n\t\t\t\tvalues\n\t\t\t) as string;\n\n\t\t\tif (i18nShared.currentLocale === \"debug-x\") {\n\t\t\t\tret = ret.replace(/[a-z]/g, \"x\").replace(/[A-Z]/g, \"x\").replace(/\\d/g, \"n\");\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t} catch {\n\t\t\t// if there is an error the assumption is that one of the properties\n\t\t\t// is undefined, which the formatting library does not like\n\t\t\t// in this case we get all the property names and ensure they are defined\n\t\t\tconst propertyNames = I18n.getPropertyNames(i18nShared.localeDictionaries[cl][key]);\n\t\t\tconst safeValues: { [key: string]: unknown } = values ?? {};\n\n\t\t\tfor (const propertyName of propertyNames) {\n\t\t\t\tsafeValues[propertyName] ??= \"\";\n\t\t\t}\n\n\t\t\t// Format again with the safe values, if it still fails we let the error\n\t\t\t// propagate up as there is nothing more we can do\n\t\t\treturn new IntlMessageFormat(i18nShared.localeDictionaries[cl][key], cl).format(\n\t\t\t\tsafeValues\n\t\t\t) as string;\n\t\t}\n\t}\n\n\t/**\n\t * Check if the dictionaries have a message for the given key.\n\t * @param key The key to check for existence.\n\t * @returns True if the key exists.\n\t */\n\tpublic static hasMessage(key: string): boolean {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn Is.string(i18nShared.localeDictionaries[i18nShared.currentLocale]?.[key]);\n\t}\n\n\t/**\n\t * Flatten the translation property paths for faster lookup.\n\t * @param translation The translation to merge.\n\t * @param propertyPath The current root path.\n\t * @param mergedKeys The merged keys dictionary to populate.\n\t */\n\tpublic static flattenTranslationKeys(\n\t\ttranslation: ILocaleDictionary,\n\t\tpropertyPath: string,\n\t\tmergedKeys: { [key: string]: string }\n\t): void {\n\t\tfor (const key in translation) {\n\t\t\tconst val = translation[key];\n\t\t\tconst mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;\n\t\t\tif (Is.string(val)) {\n\t\t\t\tmergedKeys[mergedPath] = val;\n\t\t\t} else if (Is.object<ILocaleDictionary>(val)) {\n\t\t\t\tI18n.flattenTranslationKeys(val, mergedPath, mergedKeys);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get a list of the property names from the message.\n\t * @param message The message to extract the property names from.\n\t * @returns The list of property names.\n\t */\n\tpublic static getPropertyNames(message: string): string[] {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\ttry {\n\t\t\tconst ast = new IntlMessageFormat(message, i18nShared.currentLocale).getAst();\n\t\t\tconst properties = new Set<string>();\n\n\t\t\tI18n.extractPropertiesFromAst(ast, properties);\n\n\t\t\treturn Array.from(properties);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Recursively extract property names from the AST.\n\t * @param node The AST node to process.\n\t * @param properties The set to collect property names.\n\t * @internal\n\t */\n\tprivate static extractPropertiesFromAst(node: unknown, properties: Set<string>): void {\n\t\tif (Is.array(node)) {\n\t\t\tfor (const item of node) {\n\t\t\t\tI18n.extractPropertiesFromAst(item, properties);\n\t\t\t}\n\t\t} else if (Is.object(node)) {\n\t\t\tconst obj = node;\n\n\t\t\t// Check for elements that have property names\n\t\t\t// Type 1 = ArgumentElement (simple placeholder like {name})\n\t\t\t// Type 2 = NumberElement ({count, number})\n\t\t\t// Type 3 = DateElement ({date, date, short})\n\t\t\t// Type 4 = TimeElement ({time, time, medium})\n\t\t\t// Type 5 = SelectElement ({gender, select, ...})\n\t\t\t// Type 6 = PluralElement ({count, plural, ...})\n\t\t\tif (\n\t\t\t\t(obj.type === 1 ||\n\t\t\t\t\tobj.type === 2 ||\n\t\t\t\t\tobj.type === 3 ||\n\t\t\t\t\tobj.type === 4 ||\n\t\t\t\t\tobj.type === 5 ||\n\t\t\t\t\tobj.type === 6) &&\n\t\t\t\tIs.string(obj.value)\n\t\t\t) {\n\t\t\t\tproperties.add(obj.value);\n\t\t\t}\n\n\t\t\t// Recursively process all object properties\n\t\t\tfor (const key in obj) {\n\t\t\t\tconst value = obj[key];\n\t\t\t\tif (Is.object(value) || Is.array(value)) {\n\t\t\t\t\tI18n.extractPropertiesFromAst(value, properties);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the I18n shared data.\n\t * @returns The I18n shared data.\n\t * @internal\n\t */\n\tprivate static getI18nShared(): II18nShared {\n\t\tlet i18nShared = SharedStore.get<II18nShared>(\"i18n\");\n\n\t\tif (Is.undefined(i18nShared)) {\n\t\t\ti18nShared = {\n\t\t\t\tlocaleDictionaries: {},\n\t\t\t\tcurrentLocale: I18n.DEFAULT_LOCALE,\n\t\t\t\tlocaleChangedHandlers: {},\n\t\t\t\tdictionaryChangedHandlers: {}\n\t\t\t};\n\t\t\tSharedStore.set<II18nShared>(\"i18n\", i18nShared);\n\t\t}\n\n\t\treturn i18nShared;\n\t}\n}\n"]}
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../src/utils/i18n.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { IntlMessageFormat } from \"intl-messageformat\";\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\nimport type { II18nShared } from \"../models/II18nShared.js\";\nimport type { ILocaleDictionary } from \"../models/ILocaleDictionary.js\";\n\n/**\n * Class to perform internationalization.\n */\nexport class I18n {\n\t/**\n\t * The default translation.\n\t */\n\tpublic static DEFAULT_LOCALE: string = \"en\";\n\n\t/**\n\t * Set the locale.\n\t * @param locale The new locale.\n\t */\n\tpublic static setLocale(locale: string): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\ti18nShared.currentLocale = locale;\n\n\t\tfor (const callback in i18nShared.localeChangedHandlers) {\n\t\t\ti18nShared.localeChangedHandlers[callback](i18nShared.currentLocale);\n\t\t}\n\t}\n\n\t/**\n\t * Get the locale.\n\t * @returns The current locale.\n\t */\n\tpublic static getLocale(): string {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn i18nShared.currentLocale;\n\t}\n\n\t/**\n\t * Add a locale dictionary.\n\t * @param locale The locale.\n\t * @param dictionary The dictionary to add.\n\t */\n\tpublic static addDictionary(locale: string, dictionary: ILocaleDictionary): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\tconst mergedKeys: { [key: string]: string } = {};\n\t\tI18n.flattenTranslationKeys(dictionary, \"\", mergedKeys);\n\t\ti18nShared.localeDictionaries[locale] = mergedKeys;\n\n\t\tfor (const callback in i18nShared.dictionaryChangedHandlers) {\n\t\t\ti18nShared.dictionaryChangedHandlers[callback](i18nShared.currentLocale);\n\t\t}\n\t}\n\n\t/**\n\t * Get a locale dictionary.\n\t * @param locale The locale.\n\t * @returns The dictionary of undefined if it does not exist.\n\t */\n\tpublic static getDictionary(locale: string): { [key: string]: string } | undefined {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn i18nShared.localeDictionaries[locale];\n\t}\n\n\t/**\n\t * Get all the locale dictionaries.\n\t * @returns The dictionaries.\n\t */\n\tpublic static getAllDictionaries(): {\n\t\t[locale: string]: { [key: string]: string };\n\t} {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn i18nShared.localeDictionaries;\n\t}\n\n\t/**\n\t * Add a locale changed handler.\n\t * @param id The id of the handler.\n\t * @param handler The handler to add.\n\t */\n\tpublic static addLocaleHandler(id: string, handler: (locale: string) => void): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\ti18nShared.localeChangedHandlers[id] = handler;\n\t}\n\n\t/**\n\t * Remove a locale changed handler.\n\t * @param id The id of the handler.\n\t */\n\tpublic static removeLocaleHandler(id: string): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\tdelete i18nShared.localeChangedHandlers[id];\n\t}\n\n\t/**\n\t * Add a dictionary changed handler.\n\t * @param id The id of the handler.\n\t * @param handler The handler to add.\n\t */\n\tpublic static addDictionaryHandler(id: string, handler: (locale: string) => void): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\ti18nShared.dictionaryChangedHandlers[id] = handler;\n\t}\n\n\t/**\n\t * Remove a dictionary changed handler.\n\t * @param id The id of the handler.\n\t */\n\tpublic static removeDictionaryHandler(id: string): void {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\tdelete i18nShared.dictionaryChangedHandlers[id];\n\t}\n\n\t/**\n\t * Format a message.\n\t * @param key The key of the message to format.\n\t * @param values The values to substitute into the message.\n\t * @param overrideLocale Override the locale.\n\t * @returns The formatted string.\n\t */\n\tpublic static formatMessage(\n\t\tkey: string,\n\t\tvalues?: { [key: string]: unknown },\n\t\toverrideLocale?: string\n\t): string {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\tlet cl = overrideLocale ?? i18nShared.currentLocale;\n\t\tif (cl.startsWith(\"debug-\")) {\n\t\t\tcl = I18n.DEFAULT_LOCALE;\n\t\t}\n\n\t\tif (!i18nShared.localeDictionaries[cl]) {\n\t\t\treturn `!!Missing ${cl}`;\n\t\t}\n\t\tif (!i18nShared.localeDictionaries[cl][key]) {\n\t\t\treturn `!!Missing ${cl}.${key}`;\n\t\t}\n\n\t\tif (i18nShared.currentLocale === \"debug-k\") {\n\t\t\treturn key;\n\t\t}\n\n\t\ttry {\n\t\t\tlet ret = new IntlMessageFormat(i18nShared.localeDictionaries[cl][key], cl).format(\n\t\t\t\tvalues\n\t\t\t) as string;\n\n\t\t\tif (i18nShared.currentLocale === \"debug-x\") {\n\t\t\t\tret = ret.replace(/[a-z]/g, \"x\").replace(/[A-Z]/g, \"x\").replace(/\\d/g, \"n\");\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t} catch {\n\t\t\t// if there is an error the assumption is that one of the properties\n\t\t\t// is undefined, which the formatting library does not like\n\t\t\t// in this case we get all the property names and ensure they are defined\n\t\t\tconst propertyNames = I18n.getPropertyNames(i18nShared.localeDictionaries[cl][key]);\n\t\t\tconst safeValues: { [key: string]: unknown } = values ?? {};\n\n\t\t\tfor (const propertyName of propertyNames) {\n\t\t\t\tsafeValues[propertyName] ??= \"\";\n\t\t\t}\n\n\t\t\t// Format again with the safe values, if it still fails we let the error\n\t\t\t// propagate up as there is nothing more we can do\n\t\t\treturn new IntlMessageFormat(i18nShared.localeDictionaries[cl][key], cl).format(\n\t\t\t\tsafeValues\n\t\t\t) as string;\n\t\t}\n\t}\n\n\t/**\n\t * Check if the dictionaries have a message for the given key.\n\t * @param key The key to check for existence.\n\t * @returns True if the key exists.\n\t */\n\tpublic static hasMessage(key: string): boolean {\n\t\tconst i18nShared = I18n.getI18nShared();\n\t\treturn Is.string(i18nShared.localeDictionaries[i18nShared.currentLocale]?.[key]);\n\t}\n\n\t/**\n\t * Flatten the translation property paths for faster lookup.\n\t * @param translation The translation to merge.\n\t * @param propertyPath The current root path.\n\t * @param mergedKeys The merged keys dictionary to populate.\n\t */\n\tpublic static flattenTranslationKeys(\n\t\ttranslation: ILocaleDictionary,\n\t\tpropertyPath: string,\n\t\tmergedKeys: { [key: string]: string }\n\t): void {\n\t\tfor (const key in translation) {\n\t\t\tconst val = translation[key];\n\t\t\tconst mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;\n\t\t\tif (Is.string(val)) {\n\t\t\t\tmergedKeys[mergedPath] = val;\n\t\t\t} else if (Is.object<ILocaleDictionary>(val)) {\n\t\t\t\tI18n.flattenTranslationKeys(val, mergedPath, mergedKeys);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get a list of the property names from the message.\n\t * @param message The message to extract the property names from.\n\t * @returns The list of property names.\n\t */\n\tpublic static getPropertyNames(message: string): string[] {\n\t\tconst i18nShared = I18n.getI18nShared();\n\n\t\ttry {\n\t\t\tconst ast = new IntlMessageFormat(message, i18nShared.currentLocale).getAst();\n\t\t\tconst properties = new Set<string>();\n\n\t\t\tI18n.extractPropertiesFromAst(ast, properties);\n\n\t\t\treturn Array.from(properties);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Recursively extract property names from the AST.\n\t * @param node The AST node to process.\n\t * @param properties The set to collect property names.\n\t * @internal\n\t */\n\tprivate static extractPropertiesFromAst(node: unknown, properties: Set<string>): void {\n\t\tif (Is.array(node)) {\n\t\t\tfor (const item of node) {\n\t\t\t\tI18n.extractPropertiesFromAst(item, properties);\n\t\t\t}\n\t\t} else if (Is.object(node)) {\n\t\t\tconst obj = node;\n\n\t\t\t// Check for elements that have property names\n\t\t\t// Type 1 = ArgumentElement (simple placeholder like {name})\n\t\t\t// Type 2 = NumberElement ({count, number})\n\t\t\t// Type 3 = DateElement ({date, date, short})\n\t\t\t// Type 4 = TimeElement ({time, time, medium})\n\t\t\t// Type 5 = SelectElement ({gender, select, ...})\n\t\t\t// Type 6 = PluralElement ({count, plural, ...})\n\t\t\tif (\n\t\t\t\t(obj.type === 1 ||\n\t\t\t\t\tobj.type === 2 ||\n\t\t\t\t\tobj.type === 3 ||\n\t\t\t\t\tobj.type === 4 ||\n\t\t\t\t\tobj.type === 5 ||\n\t\t\t\t\tobj.type === 6) &&\n\t\t\t\tIs.string(obj.value)\n\t\t\t) {\n\t\t\t\tproperties.add(obj.value);\n\t\t\t}\n\n\t\t\t// Recursively process all object properties\n\t\t\tfor (const key in obj) {\n\t\t\t\tconst value = obj[key];\n\t\t\t\tif (Is.object(value) || Is.array(value)) {\n\t\t\t\t\tI18n.extractPropertiesFromAst(value, properties);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the I18n shared data.\n\t * @returns The I18n shared data.\n\t * @internal\n\t */\n\tprivate static getI18nShared(): II18nShared {\n\t\treturn SharedStore.get<II18nShared>(\"i18n\", () => ({\n\t\t\tlocaleDictionaries: {},\n\t\t\tcurrentLocale: I18n.DEFAULT_LOCALE,\n\t\t\tlocaleChangedHandlers: {},\n\t\t\tdictionaryChangedHandlers: {}\n\t\t}));\n\t}\n}\n"]}
@@ -229,12 +229,7 @@ export class Mutex {
229
229
  * @internal
230
230
  */
231
231
  static getLocks() {
232
- let locks = SharedStore.get(Mutex._LOCKS_KEY);
233
- if (Is.undefined(locks)) {
234
- locks = {};
235
- SharedStore.set(Mutex._LOCKS_KEY, locks);
236
- }
237
- return locks;
232
+ return SharedStore.get(Mutex._LOCKS_KEY, () => ({}));
238
233
  }
239
234
  /**
240
235
  * Lazily loads node:worker_threads, returning null in environments where it is unavailable.
@@ -1 +1 @@
1
- {"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../../src/utils/mutex.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { MessagePort } from \"node:worker_threads\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { Guards } from \"./guards.js\";\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport type { IMutexWorkerMessage } from \"../models/IMutexWorkerMessage.js\";\nimport { MutexMessageTypes } from \"../models/mutexMessageTypes.js\";\n\n/**\n * A cross-thread mutex built on Atomics and SharedArrayBuffer.\n *\n * When isMainThread is true (main thread or fork-mode child process) the class acts as\n * the authoritative registry: it creates a SharedArrayBuffer-backed Int32Array for each\n * key on first use and never discards it, because worker threads may hold references to\n * the same underlying memory.\n *\n * When isMainThread is false (a true worker thread) the class synchronously negotiates\n * the shared buffer with the main thread on first use of each key, then caches it locally.\n * The main thread must call Mutex.handleWorkerMessage(msg) from its worker message handler\n * before that worker first calls Mutex.lock().\n *\n * The lock is not re-entrant: a thread that already holds a key and calls lock() again on\n * the same key will block until the timeout elapses.\n */\nexport class Mutex {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Mutex>();\n\n\t/**\n\t * SharedStore key for the per-thread sparse map from lock key strings to Int32Arrays.\n\t * @internal\n\t */\n\tprivate static readonly _LOCKS_KEY = \"mutexLocks\";\n\n\t/**\n\t * SharedStore key for the default timeout in milliseconds.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TIMEOUT_KEY = \"mutexDefaultTimeoutMs\";\n\n\t/**\n\t * Cached reference to the node:worker_threads module, null if unavailable (browser).\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static _workerThreadsModule: typeof import(\"node:worker_threads\") | null | undefined;\n\n\t/**\n\t * Gets the default timeout in milliseconds for lock acquisition.\n\t * @returns The default timeout in milliseconds.\n\t */\n\tpublic static getDefaultTimeoutMs(): number {\n\t\treturn SharedStore.get<number>(Mutex._DEFAULT_TIMEOUT_KEY) ?? 5000;\n\t}\n\n\t/**\n\t * Sets the default timeout in milliseconds for lock acquisition.\n\t * @param timeoutMs The default timeout in milliseconds.\n\t * @throws GeneralError if timeoutMs is not a non-negative integer.\n\t */\n\tpublic static setDefaultTimeoutMs(timeoutMs: number): void {\n\t\tGuards.integer(Mutex.CLASS_NAME, nameof(timeoutMs), timeoutMs);\n\t\tif (timeoutMs < 0) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"invalidTimeout\", { timeoutMs });\n\t\t}\n\t\tSharedStore.set(Mutex._DEFAULT_TIMEOUT_KEY, timeoutMs);\n\t}\n\n\t/**\n\t * Acquires a lock for the given key without blocking the event loop. If the lock is already\n\t * held, it suspends the current async task until the lock is released or the timeout is reached.\n\t * Use this in async single-threaded contexts (e.g. the main thread or a Fastify route handler)\n\t * where calling the synchronous lock() would freeze the event loop and deadlock.\n\t * The lock is not re-entrant: if the same context holds the key and calls lockAsync() again on\n\t * the same key, it will suspend until the timeout elapses.\n\t * @param key The key to lock on.\n\t * @param options Lock options.\n\t * @param options.timeoutMs The maximum time to wait for the lock in milliseconds, defaults to getDefaultTimeoutMs().\n\t * @param options.throwOnTimeout Whether to throw an error if the lock could not be acquired within the timeout, default is false.\n\t * @returns True if the lock was acquired, false if it timed out and throwOnTimeout is false.\n\t * @throws GeneralError if the key is invalid or if the lock could not be acquired within the timeout and throwOnTimeout is true.\n\t */\n\tpublic static async lock(\n\t\tkey: string,\n\t\toptions?: { timeoutMs?: number; throwOnTimeout?: boolean }\n\t): Promise<boolean> {\n\t\tGuards.stringValue(Mutex.CLASS_NAME, nameof(key), key);\n\t\tif (!Is.empty(options?.timeoutMs)) {\n\t\t\tGuards.integer(Mutex.CLASS_NAME, nameof(options.timeoutMs), options.timeoutMs);\n\t\t\tif (options.timeoutMs < 0) {\n\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"invalidTimeout\", {\n\t\t\t\t\ttimeoutMs: options.timeoutMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? Mutex.getDefaultTimeoutMs();\n\t\tconst throwOnTimeout = options?.throwOnTimeout ?? false;\n\t\tconst deadline = Date.now() + timeoutMs;\n\n\t\t// getOrFetchLock may block once per key on worker threads to negotiate the\n\t\t// shared buffer with the main thread; that one-time fetch is acceptable here.\n\t\tconst lock = await Mutex.getOrFetchLock(key, deadline);\n\n\t\tfor (;;) {\n\t\t\t// Atomically swap 0 → 1; if the previous value was 0 we acquired the lock.\n\t\t\tconst previous = Atomics.compareExchange(lock, 0, 0, 1);\n\t\t\tif (previous === 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise, the lock is held by someone else. Check if we've already timed out.\n\t\t\tconst remaining = deadline - Date.now();\n\t\t\tif (remaining <= 0) {\n\t\t\t\tif (throwOnTimeout) {\n\t\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockTimeout\", { key, timeoutMs });\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Suspend without blocking the event loop so the lock holder's async\n\t\t\t// continuations can run and eventually call unlock().\n\t\t\tconst waitResult = Atomics.waitAsync(lock, 0, 1, remaining);\n\t\t\tconst outcome = waitResult.async ? await waitResult.value : waitResult.value;\n\t\t\tif (outcome === \"timed-out\") {\n\t\t\t\tif (throwOnTimeout) {\n\t\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockTimeout\", { key, timeoutMs });\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Releases the lock for the given key.\n\t * @param key The key to unlock.\n\t * @throws GeneralError if the key is invalid or the lock is not currently held.\n\t */\n\tpublic static unlock(key: string): void {\n\t\tGuards.stringValue(Mutex.CLASS_NAME, nameof(key), key);\n\n\t\tconst locks = Mutex.getLocks();\n\t\tconst lock = locks[key];\n\t\tif (Is.empty(lock)) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockNotFound\", { key });\n\t\t}\n\n\t\tconst previous = Atomics.compareExchange(lock, 0, 1, 0);\n\t\tif (previous !== 1) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockAlreadyReleased\", { key });\n\t\t}\n\n\t\tAtomics.notify(lock, 0, 1);\n\t}\n\n\t/**\n\t * Inspect a message received from a worker and, if it is a Mutex buffer-fetch request,\n\t * respond to it synchronously. Call from the main thread's worker message handler.\n\t * @param msg The raw message received from the worker.\n\t * @returns True if the message was a Mutex protocol message and was handled, false otherwise.\n\t */\n\tpublic static handleWorkerMessage(msg: unknown): boolean {\n\t\tif (!Is.object<IMutexWorkerMessage>(msg) || msg.type !== MutexMessageTypes.GetBuffer) {\n\t\t\treturn false;\n\t\t}\n\n\t\tGuards.stringValue(Mutex.CLASS_NAME, nameof(msg.key), msg.key);\n\t\tGuards.object<SharedArrayBuffer>(Mutex.CLASS_NAME, nameof(msg.signal), msg.signal);\n\t\tGuards.object<MessagePort>(Mutex.CLASS_NAME, nameof(msg.port), msg.port);\n\n\t\tconst locks = Mutex.getLocks();\n\t\tlocks[msg.key] ??= new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\t\t// Send the buffer before updating the signal so it is guaranteed to be in\n\t\t// port1's receive queue when Atomics.wait returns on the worker side.\n\t\tmsg.port.postMessage({ buffer: locks[msg.key].buffer });\n\t\t// Set signal[0] = 1 before notifying. If the OS scheduled the main thread\n\t\t// to process this request before the worker reached Atomics.wait, the notify\n\t\t// would fire with no waiters (lost wakeup). Setting the value first means\n\t\t// Atomics.wait(signal, 0, 0) sees a non-zero value and returns \"not-equal\"\n\t\t// immediately instead of blocking indefinitely.\n\t\tconst signalArr = new Int32Array(msg.signal);\n\t\tAtomics.store(signalArr, 0, 1);\n\t\tAtomics.notify(signalArr, 0, 1);\n\t\tmsg.port.close();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Returns the Int32Array for the given key, fetching it from the main thread if this\n\t * is a worker thread and the key is not yet in the local cache.\n\t * @param key The lock key.\n\t * @param deadline The deadline to use while waiting for the main thread to provide the lock.\n\t * @returns The Int32Array backed by a SharedArrayBuffer for this key.\n\t * @internal\n\t */\n\tprivate static async getOrFetchLock(key: string, deadline: number): Promise<Int32Array> {\n\t\tconst locks = Mutex.getLocks();\n\t\tif (!Is.empty(locks[key])) {\n\t\t\treturn locks[key];\n\t\t}\n\n\t\tconst wt = await Mutex.loadWorkerThreads();\n\n\t\t// Re-check after the await: another coroutine that was also waiting on\n\t\t// loadWorkerThreads() may have allocated the buffer while we yielded.\n\t\tif (!Is.empty(locks[key])) {\n\t\t\treturn locks[key];\n\t\t}\n\n\t\tif (Is.empty(wt) || wt.isMainThread) {\n\t\t\t// Main thread, fork-mode process, or browser: own the registry entry.\n\t\t\tlocks[key] = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\t\t\treturn locks[key];\n\t\t}\n\n\t\t// Worker thread: synchronously request the SharedArrayBuffer from the main thread.\n\t\t// Mutex.handleWorkerMessage(msg) must be called on the main thread's worker message handler.\n\t\tif (Is.empty(wt.parentPort)) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"bufferFetchFailed\", { key });\n\t\t}\n\n\t\tconst { port1, port2 } = new wt.MessageChannel();\n\t\tconst signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\n\t\tconst msg: IMutexWorkerMessage = {\n\t\t\ttype: MutexMessageTypes.GetBuffer,\n\t\t\tkey,\n\t\t\tsignal: signal.buffer,\n\t\t\tport: port2\n\t\t};\n\n\t\twt.parentPort.postMessage(msg, [port2]);\n\n\t\ttry {\n\t\t\t// Block until the main thread signals readiness. The main thread sets\n\t\t\t// signal[0] = 1 before calling notify, so if the notify fired before this\n\t\t\t// wait call (lost-wakeup scenario with concurrent workers), Atomics.wait\n\t\t\t// sees a non-zero value and returns \"not-equal\" immediately.\n\t\t\t// Either way the port message is already in port1's receive queue.\n\t\t\t// Use the lock deadline so the buffer fetch is bounded by the same timeout.\n\t\t\tconst waitResult = Atomics.wait(signal, 0, 0, Math.max(0, deadline - Date.now()));\n\t\t\tif (waitResult === \"timed-out\") {\n\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"bufferFetchFailed\", { key });\n\t\t\t}\n\n\t\t\tconst response = wt.receiveMessageOnPort(port1) as {\n\t\t\t\tmessage: { buffer: SharedArrayBuffer };\n\t\t\t} | null;\n\n\t\t\tif (Is.empty(response)) {\n\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"bufferFetchFailed\", { key });\n\t\t\t}\n\n\t\t\tlocks[key] = new Int32Array(response.message.buffer);\n\t\t\treturn locks[key];\n\t\t} finally {\n\t\t\tport1.close();\n\t\t}\n\t}\n\n\t/**\n\t * Get the shared locks map, creating it if it does not exist.\n\t * @returns The shared locks map.\n\t * @internal\n\t */\n\tprivate static getLocks(): { [key: string]: Int32Array } {\n\t\tlet locks = SharedStore.get<{ [key: string]: Int32Array }>(Mutex._LOCKS_KEY);\n\t\tif (Is.undefined(locks)) {\n\t\t\tlocks = {};\n\t\t\tSharedStore.set(Mutex._LOCKS_KEY, locks);\n\t\t}\n\t\treturn locks;\n\t}\n\n\t/**\n\t * Lazily loads node:worker_threads, returning null in environments where it is unavailable.\n\t * @returns The worker_threads module or null.\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static async loadWorkerThreads(): Promise<typeof import(\"node:worker_threads\") | null> {\n\t\tif (Mutex._workerThreadsModule === undefined) {\n\t\t\ttry {\n\t\t\t\tMutex._workerThreadsModule = await import(\"node:worker_threads\");\n\t\t\t} catch {\n\t\t\t\tMutex._workerThreadsModule = null;\n\t\t\t}\n\t\t}\n\t\treturn Mutex._workerThreadsModule;\n\t}\n}\n"]}
1
+ {"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../../src/utils/mutex.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { MessagePort } from \"node:worker_threads\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { Guards } from \"./guards.js\";\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport type { IMutexWorkerMessage } from \"../models/IMutexWorkerMessage.js\";\nimport { MutexMessageTypes } from \"../models/mutexMessageTypes.js\";\n\n/**\n * A cross-thread mutex built on Atomics and SharedArrayBuffer.\n *\n * When isMainThread is true (main thread or fork-mode child process) the class acts as\n * the authoritative registry: it creates a SharedArrayBuffer-backed Int32Array for each\n * key on first use and never discards it, because worker threads may hold references to\n * the same underlying memory.\n *\n * When isMainThread is false (a true worker thread) the class synchronously negotiates\n * the shared buffer with the main thread on first use of each key, then caches it locally.\n * The main thread must call Mutex.handleWorkerMessage(msg) from its worker message handler\n * before that worker first calls Mutex.lock().\n *\n * The lock is not re-entrant: a thread that already holds a key and calls lock() again on\n * the same key will block until the timeout elapses.\n */\nexport class Mutex {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Mutex>();\n\n\t/**\n\t * SharedStore key for the per-thread sparse map from lock key strings to Int32Arrays.\n\t * @internal\n\t */\n\tprivate static readonly _LOCKS_KEY = \"mutexLocks\";\n\n\t/**\n\t * SharedStore key for the default timeout in milliseconds.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TIMEOUT_KEY = \"mutexDefaultTimeoutMs\";\n\n\t/**\n\t * Cached reference to the node:worker_threads module, null if unavailable (browser).\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static _workerThreadsModule: typeof import(\"node:worker_threads\") | null | undefined;\n\n\t/**\n\t * Gets the default timeout in milliseconds for lock acquisition.\n\t * @returns The default timeout in milliseconds.\n\t */\n\tpublic static getDefaultTimeoutMs(): number {\n\t\treturn SharedStore.get<number>(Mutex._DEFAULT_TIMEOUT_KEY) ?? 5000;\n\t}\n\n\t/**\n\t * Sets the default timeout in milliseconds for lock acquisition.\n\t * @param timeoutMs The default timeout in milliseconds.\n\t * @throws GeneralError if timeoutMs is not a non-negative integer.\n\t */\n\tpublic static setDefaultTimeoutMs(timeoutMs: number): void {\n\t\tGuards.integer(Mutex.CLASS_NAME, nameof(timeoutMs), timeoutMs);\n\t\tif (timeoutMs < 0) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"invalidTimeout\", { timeoutMs });\n\t\t}\n\t\tSharedStore.set(Mutex._DEFAULT_TIMEOUT_KEY, timeoutMs);\n\t}\n\n\t/**\n\t * Acquires a lock for the given key without blocking the event loop. If the lock is already\n\t * held, it suspends the current async task until the lock is released or the timeout is reached.\n\t * Use this in async single-threaded contexts (e.g. the main thread or a Fastify route handler)\n\t * where calling the synchronous lock() would freeze the event loop and deadlock.\n\t * The lock is not re-entrant: if the same context holds the key and calls lockAsync() again on\n\t * the same key, it will suspend until the timeout elapses.\n\t * @param key The key to lock on.\n\t * @param options Lock options.\n\t * @param options.timeoutMs The maximum time to wait for the lock in milliseconds, defaults to getDefaultTimeoutMs().\n\t * @param options.throwOnTimeout Whether to throw an error if the lock could not be acquired within the timeout, default is false.\n\t * @returns True if the lock was acquired, false if it timed out and throwOnTimeout is false.\n\t * @throws GeneralError if the key is invalid or if the lock could not be acquired within the timeout and throwOnTimeout is true.\n\t */\n\tpublic static async lock(\n\t\tkey: string,\n\t\toptions?: { timeoutMs?: number; throwOnTimeout?: boolean }\n\t): Promise<boolean> {\n\t\tGuards.stringValue(Mutex.CLASS_NAME, nameof(key), key);\n\t\tif (!Is.empty(options?.timeoutMs)) {\n\t\t\tGuards.integer(Mutex.CLASS_NAME, nameof(options.timeoutMs), options.timeoutMs);\n\t\t\tif (options.timeoutMs < 0) {\n\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"invalidTimeout\", {\n\t\t\t\t\ttimeoutMs: options.timeoutMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? Mutex.getDefaultTimeoutMs();\n\t\tconst throwOnTimeout = options?.throwOnTimeout ?? false;\n\t\tconst deadline = Date.now() + timeoutMs;\n\n\t\t// getOrFetchLock may block once per key on worker threads to negotiate the\n\t\t// shared buffer with the main thread; that one-time fetch is acceptable here.\n\t\tconst lock = await Mutex.getOrFetchLock(key, deadline);\n\n\t\tfor (;;) {\n\t\t\t// Atomically swap 0 → 1; if the previous value was 0 we acquired the lock.\n\t\t\tconst previous = Atomics.compareExchange(lock, 0, 0, 1);\n\t\t\tif (previous === 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise, the lock is held by someone else. Check if we've already timed out.\n\t\t\tconst remaining = deadline - Date.now();\n\t\t\tif (remaining <= 0) {\n\t\t\t\tif (throwOnTimeout) {\n\t\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockTimeout\", { key, timeoutMs });\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Suspend without blocking the event loop so the lock holder's async\n\t\t\t// continuations can run and eventually call unlock().\n\t\t\tconst waitResult = Atomics.waitAsync(lock, 0, 1, remaining);\n\t\t\tconst outcome = waitResult.async ? await waitResult.value : waitResult.value;\n\t\t\tif (outcome === \"timed-out\") {\n\t\t\t\tif (throwOnTimeout) {\n\t\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockTimeout\", { key, timeoutMs });\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Releases the lock for the given key.\n\t * @param key The key to unlock.\n\t * @throws GeneralError if the key is invalid or the lock is not currently held.\n\t */\n\tpublic static unlock(key: string): void {\n\t\tGuards.stringValue(Mutex.CLASS_NAME, nameof(key), key);\n\n\t\tconst locks = Mutex.getLocks();\n\t\tconst lock = locks[key];\n\t\tif (Is.empty(lock)) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockNotFound\", { key });\n\t\t}\n\n\t\tconst previous = Atomics.compareExchange(lock, 0, 1, 0);\n\t\tif (previous !== 1) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"lockAlreadyReleased\", { key });\n\t\t}\n\n\t\tAtomics.notify(lock, 0, 1);\n\t}\n\n\t/**\n\t * Inspect a message received from a worker and, if it is a Mutex buffer-fetch request,\n\t * respond to it synchronously. Call from the main thread's worker message handler.\n\t * @param msg The raw message received from the worker.\n\t * @returns True if the message was a Mutex protocol message and was handled, false otherwise.\n\t */\n\tpublic static handleWorkerMessage(msg: unknown): boolean {\n\t\tif (!Is.object<IMutexWorkerMessage>(msg) || msg.type !== MutexMessageTypes.GetBuffer) {\n\t\t\treturn false;\n\t\t}\n\n\t\tGuards.stringValue(Mutex.CLASS_NAME, nameof(msg.key), msg.key);\n\t\tGuards.object<SharedArrayBuffer>(Mutex.CLASS_NAME, nameof(msg.signal), msg.signal);\n\t\tGuards.object<MessagePort>(Mutex.CLASS_NAME, nameof(msg.port), msg.port);\n\n\t\tconst locks = Mutex.getLocks();\n\t\tlocks[msg.key] ??= new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\t\t// Send the buffer before updating the signal so it is guaranteed to be in\n\t\t// port1's receive queue when Atomics.wait returns on the worker side.\n\t\tmsg.port.postMessage({ buffer: locks[msg.key].buffer });\n\t\t// Set signal[0] = 1 before notifying. If the OS scheduled the main thread\n\t\t// to process this request before the worker reached Atomics.wait, the notify\n\t\t// would fire with no waiters (lost wakeup). Setting the value first means\n\t\t// Atomics.wait(signal, 0, 0) sees a non-zero value and returns \"not-equal\"\n\t\t// immediately instead of blocking indefinitely.\n\t\tconst signalArr = new Int32Array(msg.signal);\n\t\tAtomics.store(signalArr, 0, 1);\n\t\tAtomics.notify(signalArr, 0, 1);\n\t\tmsg.port.close();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Returns the Int32Array for the given key, fetching it from the main thread if this\n\t * is a worker thread and the key is not yet in the local cache.\n\t * @param key The lock key.\n\t * @param deadline The deadline to use while waiting for the main thread to provide the lock.\n\t * @returns The Int32Array backed by a SharedArrayBuffer for this key.\n\t * @internal\n\t */\n\tprivate static async getOrFetchLock(key: string, deadline: number): Promise<Int32Array> {\n\t\tconst locks = Mutex.getLocks();\n\t\tif (!Is.empty(locks[key])) {\n\t\t\treturn locks[key];\n\t\t}\n\n\t\tconst wt = await Mutex.loadWorkerThreads();\n\n\t\t// Re-check after the await: another coroutine that was also waiting on\n\t\t// loadWorkerThreads() may have allocated the buffer while we yielded.\n\t\tif (!Is.empty(locks[key])) {\n\t\t\treturn locks[key];\n\t\t}\n\n\t\tif (Is.empty(wt) || wt.isMainThread) {\n\t\t\t// Main thread, fork-mode process, or browser: own the registry entry.\n\t\t\tlocks[key] = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\t\t\treturn locks[key];\n\t\t}\n\n\t\t// Worker thread: synchronously request the SharedArrayBuffer from the main thread.\n\t\t// Mutex.handleWorkerMessage(msg) must be called on the main thread's worker message handler.\n\t\tif (Is.empty(wt.parentPort)) {\n\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"bufferFetchFailed\", { key });\n\t\t}\n\n\t\tconst { port1, port2 } = new wt.MessageChannel();\n\t\tconst signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\n\t\tconst msg: IMutexWorkerMessage = {\n\t\t\ttype: MutexMessageTypes.GetBuffer,\n\t\t\tkey,\n\t\t\tsignal: signal.buffer,\n\t\t\tport: port2\n\t\t};\n\n\t\twt.parentPort.postMessage(msg, [port2]);\n\n\t\ttry {\n\t\t\t// Block until the main thread signals readiness. The main thread sets\n\t\t\t// signal[0] = 1 before calling notify, so if the notify fired before this\n\t\t\t// wait call (lost-wakeup scenario with concurrent workers), Atomics.wait\n\t\t\t// sees a non-zero value and returns \"not-equal\" immediately.\n\t\t\t// Either way the port message is already in port1's receive queue.\n\t\t\t// Use the lock deadline so the buffer fetch is bounded by the same timeout.\n\t\t\tconst waitResult = Atomics.wait(signal, 0, 0, Math.max(0, deadline - Date.now()));\n\t\t\tif (waitResult === \"timed-out\") {\n\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"bufferFetchFailed\", { key });\n\t\t\t}\n\n\t\t\tconst response = wt.receiveMessageOnPort(port1) as {\n\t\t\t\tmessage: { buffer: SharedArrayBuffer };\n\t\t\t} | null;\n\n\t\t\tif (Is.empty(response)) {\n\t\t\t\tthrow new GeneralError(Mutex.CLASS_NAME, \"bufferFetchFailed\", { key });\n\t\t\t}\n\n\t\t\tlocks[key] = new Int32Array(response.message.buffer);\n\t\t\treturn locks[key];\n\t\t} finally {\n\t\t\tport1.close();\n\t\t}\n\t}\n\n\t/**\n\t * Get the shared locks map, creating it if it does not exist.\n\t * @returns The shared locks map.\n\t * @internal\n\t */\n\tprivate static getLocks(): { [key: string]: Int32Array } {\n\t\treturn SharedStore.get<{ [key: string]: Int32Array }>(Mutex._LOCKS_KEY, () => ({}));\n\t}\n\n\t/**\n\t * Lazily loads node:worker_threads, returning null in environments where it is unavailable.\n\t * @returns The worker_threads module or null.\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static async loadWorkerThreads(): Promise<typeof import(\"node:worker_threads\") | null> {\n\t\tif (Mutex._workerThreadsModule === undefined) {\n\t\t\ttry {\n\t\t\t\tMutex._workerThreadsModule = await import(\"node:worker_threads\");\n\t\t\t} catch {\n\t\t\t\tMutex._workerThreadsModule = null;\n\t\t\t}\n\t\t}\n\t\treturn Mutex._workerThreadsModule;\n\t}\n}\n"]}
@@ -279,12 +279,7 @@ export class SharedObjectBuffer {
279
279
  * @internal
280
280
  */
281
281
  static getBuffers() {
282
- let buffers = SharedStore.get(SharedObjectBuffer._STORE_KEY);
283
- if (Is.undefined(buffers)) {
284
- buffers = {};
285
- SharedStore.set(SharedObjectBuffer._STORE_KEY, buffers);
286
- }
287
- return buffers;
282
+ return SharedStore.get(SharedObjectBuffer._STORE_KEY, () => ({}));
288
283
  }
289
284
  /**
290
285
  * Lazily load node:worker_threads, returning null in environments where it is unavailable.
@@ -1 +1 @@
1
- {"version":3,"file":"sharedObjectBuffer.js","sourceRoot":"","sources":["../../../src/utils/sharedObjectBuffer.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { MessagePort } from \"node:worker_threads\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport { ObjectHelper } from \"../helpers/objectHelper.js\";\nimport type { ISharedObjectBufferOptions } from \"../models/ISharedObjectBufferOptions.js\";\nimport type { ISharedObjectBufferWorkerMessage } from \"../models/ISharedObjectBufferWorkerMessage.js\";\nimport { SharedObjectBufferMessageTypes } from \"../models/sharedObjectBufferMessageTypes.js\";\n\n/**\n * Manages per-object SharedArrayBuffers that store objects as UTF-8 JSON.\n * Buffer layout: 4-byte Int32 header (current data byte length) followed by the JSON-encoded object.\n * Buffers are explicitly created with create and cached in SharedStore.\n * On a worker thread an existing buffer is fetched via a MessagePort handshake\n * (same protocol as Mutex) and cached locally.\n * Buffers grow automatically when the payload exceeds capacity; when the payload drops well below\n * capacity the buffer is replaced with a smaller one. The caller must hold the objectId-keyed Mutex\n * around every read and write. The main thread's worker message handler must forward messages to\n * both Mutex.handleWorkerMessage and SharedObjectBuffer.handleWorkerMessage.\n */\nexport class SharedObjectBuffer {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SharedObjectBuffer>();\n\n\t/**\n\t * Default payload capacity per object (1 MiB). The first caller that creates the buffer\n\t * for an object determines its initial capacity; later callers that pass a different value\n\t * are ignored.\n\t */\n\tpublic static readonly DEFAULT_CAPACITY_BYTES = 1 * 1024 * 1024;\n\n\t/**\n\t * Default upper bound for how large a buffer may grow (256 MiB).\n\t * Override per-object via the maxCapacityBytes option on create.\n\t */\n\tpublic static readonly MAX_CAPACITY_BYTES = 256 * 1024 * 1024;\n\n\t/**\n\t * Bytes reserved for the data-length header at the start of each buffer.\n\t * @internal\n\t */\n\tprivate static readonly _HEADER_BYTES = 4;\n\n\t/**\n\t * Multiplier applied to the current capacity when growing or sizing a replacement buffer.\n\t * @internal\n\t */\n\tprivate static readonly _GROW_FACTOR = 2;\n\n\t/**\n\t * Fraction of payload capacity below which a buffer is replaced with a smaller one.\n\t * A value of 0.25 means the buffer is shrunk when the payload is below 25 % of capacity.\n\t * Shrinking only applies when the capacity already exceeds DEFAULT_CAPACITY_BYTES.\n\t * @internal\n\t */\n\tprivate static readonly _SHRINK_THRESHOLD = 0.25;\n\n\t/**\n\t * SharedStore key under which the per-object buffer map lives on each thread.\n\t * @internal\n\t */\n\tprivate static readonly _STORE_KEY = \"sharedObjectBuffers\";\n\n\t/**\n\t * Cached worker_threads module; undefined = not yet loaded, null = unavailable.\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static _workerThreadsModule: typeof import(\"node:worker_threads\") | null | undefined;\n\n\t/**\n\t * Create the buffer for the given objectId if it does not already exist.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t * @param options Optional capacity configuration used when creating the buffer.\n\t */\n\tpublic static async create(\n\t\tobjectId: string,\n\t\toptions?: ISharedObjectBufferOptions\n\t): Promise<void> {\n\t\tawait SharedObjectBuffer.getOrFetchBuffer(objectId, options ?? {});\n\t}\n\n\t/**\n\t * Read and decode the object stored for the given objectId.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t * @returns The stored object, or undefined when nothing has been written yet.\n\t */\n\tpublic static async read<T>(objectId: string): Promise<T | undefined> {\n\t\tconst buf = await SharedObjectBuffer.getOrFetchBuffer(objectId);\n\t\tif (Is.undefined(buf)) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn SharedObjectBuffer.decode<T>(buf);\n\t}\n\n\t/**\n\t * Encode and write the object into the buffer for the given objectId.\n\t * The buffer must already exist, usually by calling create first.\n\t * When the encoded payload exceeds the current buffer capacity the buffer is grown\n\t * in-place via SharedArrayBuffer.grow so every thread with a reference sees the\n\t * new size without any pointer swap. When the payload is smaller than\n\t * _SHRINK_THRESHOLD of the current capacity and the capacity exceeds\n\t * DEFAULT_CAPACITY_BYTES, the buffer is replaced with a smaller one on the calling thread.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t * @param value The object to persist.\n\t */\n\tpublic static async write<T>(objectId: string, value: T): Promise<void> {\n\t\tconst encoded = ObjectHelper.toBytes(value);\n\t\tlet buf = await SharedObjectBuffer.getOrFetchBuffer(objectId);\n\n\t\tif (Is.undefined(buf)) {\n\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"notCreated\", {\n\t\t\t\tobjectId\n\t\t\t});\n\t\t}\n\n\t\tconst payloadCapacity = buf.byteLength - SharedObjectBuffer._HEADER_BYTES;\n\t\tconst maxPayloadCapacity = buf.maxByteLength - SharedObjectBuffer._HEADER_BYTES;\n\n\t\tif (encoded.length > payloadCapacity) {\n\t\t\t// Grow the buffer in-place. SharedArrayBuffer.grow is atomic: all threads that\n\t\t\t// hold a reference see the enlarged buffer without a pointer swap.\n\t\t\tconst newPayloadCapacity = Math.max(\n\t\t\t\tencoded.length,\n\t\t\t\tpayloadCapacity * SharedObjectBuffer._GROW_FACTOR\n\t\t\t);\n\t\t\tconst newByteLength = Math.min(\n\t\t\t\tSharedObjectBuffer._HEADER_BYTES + newPayloadCapacity,\n\t\t\t\tbuf.maxByteLength\n\t\t\t);\n\t\t\tif (encoded.length > newByteLength - SharedObjectBuffer._HEADER_BYTES) {\n\t\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"capacityExceeded\", {\n\t\t\t\t\tobjectId,\n\t\t\t\t\trequired: encoded.length,\n\t\t\t\t\tcapacity: maxPayloadCapacity\n\t\t\t\t});\n\t\t\t}\n\t\t\tbuf.grow(newByteLength);\n\t\t} else if (\n\t\t\tpayloadCapacity > SharedObjectBuffer.DEFAULT_CAPACITY_BYTES &&\n\t\t\tencoded.length < payloadCapacity * SharedObjectBuffer._SHRINK_THRESHOLD\n\t\t) {\n\t\t\t// Replace with a smaller buffer when far below capacity. The caller writes\n\t\t\t// the payload into the returned buffer immediately after.\n\t\t\tbuf = SharedObjectBuffer.shrinkBuffer(objectId, encoded.length, buf.maxByteLength);\n\t\t}\n\n\t\tnew Uint8Array(buf, SharedObjectBuffer._HEADER_BYTES).set(encoded);\n\t\tAtomics.store(new Int32Array(buf, 0, 1), 0, encoded.length);\n\t}\n\n\t/**\n\t * Remove the stored object and release the buffer for the given objectId.\n\t * The entry is deleted from the local cache so subsequent reads or writes will\n\t * create or fetch a fresh buffer. Worker threads that have cached the old buffer\n\t * reference continue using it until they restart or re-request via the worker protocol.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t */\n\tpublic static remove(objectId: string): void {\n\t\tdelete SharedObjectBuffer.getBuffers()[objectId];\n\t}\n\n\t/**\n\t * Inspect a message from a worker thread and, if it is a SharedObjectBuffer\n\t * buffer-fetch request, respond to it synchronously.\n\t * Call this from the main thread's worker message handler alongside\n\t * Mutex.handleWorkerMessage.\n\t * @param msg The raw message received from the worker.\n\t * @returns True if the message was a SharedObjectBuffer protocol message, false otherwise.\n\t */\n\tpublic static handleWorkerMessage(msg: unknown): boolean {\n\t\tif (\n\t\t\t!Is.object<ISharedObjectBufferWorkerMessage>(msg) ||\n\t\t\tmsg.type !== SharedObjectBufferMessageTypes.GetBuffer\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst {\n\t\t\tobjectId: objectName,\n\t\t\tport,\n\t\t\tsignal,\n\t\t\toptions\n\t\t} = msg as ISharedObjectBufferWorkerMessage & {\n\t\t\tport: MessagePort;\n\t\t};\n\n\t\tconst buf = Is.object(options)\n\t\t\t? SharedObjectBuffer.getOrCreateBuffer(objectName, options)\n\t\t\t: SharedObjectBuffer.getBuffers()[objectName];\n\n\t\t// Deliver the buffer before waking the worker so it is in the port's receive\n\t\t// queue when Atomics.wait returns (same ordering guarantee as Mutex).\n\t\tport.postMessage({ buffer: buf });\n\t\tAtomics.notify(new Int32Array(signal), 0, 1);\n\t\tport.close();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Decode the object from a SharedArrayBuffer.\n\t * @param buf The SharedArrayBuffer to decode.\n\t * @returns The decoded object, or undefined when nothing has been written.\n\t * @internal\n\t */\n\tprivate static decode<T>(buf: SharedArrayBuffer): T | undefined {\n\t\tconst dataLen = Atomics.load(new Int32Array(buf, 0, 1), 0);\n\t\tif (dataLen === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst bytes = new Uint8Array(buf, SharedObjectBuffer._HEADER_BYTES, dataLen);\n\t\treturn ObjectHelper.fromBytes<T>(bytes);\n\t}\n\n\t/**\n\t * Return the cached buffer for the object, creating or fetching it if needed.\n\t * Applies a double-check after the async loadWorkerThreads call to handle the\n\t * case where another coroutine populated the cache while this one yielded.\n\t * @param objectId The object id that identifies the buffer.\n\t * @param options Optional capacity configuration when creating the buffer.\n\t * @returns The SharedArrayBuffer for the object.\n\t * @internal\n\t */\n\tprivate static async getOrFetchBuffer(\n\t\tobjectId: string,\n\t\toptions?: ISharedObjectBufferOptions\n\t): Promise<SharedArrayBuffer | undefined> {\n\t\tconst buffers = SharedObjectBuffer.getBuffers();\n\n\t\tif (!Is.undefined(buffers[objectId])) {\n\t\t\treturn buffers[objectId];\n\t\t}\n\n\t\tconst wt = await SharedObjectBuffer.loadWorkerThreads();\n\n\t\t// Re-check after the await: another coroutine may have populated the cache.\n\t\tif (!Is.undefined(buffers[objectId])) {\n\t\t\treturn buffers[objectId];\n\t\t}\n\n\t\tif (Is.empty(wt) || wt.isMainThread) {\n\t\t\tif (!Is.object(options)) {\n\t\t\t\treturn buffers[objectId];\n\t\t\t}\n\t\t\treturn SharedObjectBuffer.getOrCreateBuffer(objectId, options);\n\t\t}\n\n\t\t// Worker thread: synchronously request the SharedArrayBuffer from the main thread.\n\t\tif (Is.empty(wt.parentPort)) {\n\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"bufferFetchFailed\", { objectId });\n\t\t}\n\n\t\tconst { port1, port2 } = new wt.MessageChannel();\n\t\tconst signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\n\t\tconst request: ISharedObjectBufferWorkerMessage = {\n\t\t\ttype: SharedObjectBufferMessageTypes.GetBuffer,\n\t\t\tobjectId,\n\t\t\tsignal: signal.buffer,\n\t\t\tport: port2,\n\t\t\toptions\n\t\t};\n\n\t\twt.parentPort.postMessage(request, [port2]);\n\n\t\ttry {\n\t\t\t// Block until the main thread posts the buffer and fires Atomics.notify.\n\t\t\t// The response is guaranteed to be in port1's queue when wait returns because\n\t\t\t// port.postMessage executes before Atomics.notify on the main thread.\n\t\t\tconst waitResult = Atomics.wait(signal, 0, 0, 30_000);\n\t\t\tif (waitResult === \"timed-out\") {\n\t\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"bufferFetchFailed\", { objectId });\n\t\t\t}\n\n\t\t\tconst response = wt.receiveMessageOnPort(port1) as {\n\t\t\t\tmessage: { buffer?: SharedArrayBuffer };\n\t\t\t} | null;\n\n\t\t\tif (Is.empty(response)) {\n\t\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"bufferFetchFailed\", { objectId });\n\t\t\t}\n\n\t\t\tif (Is.undefined(response.message.buffer)) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tbuffers[objectId] = response.message.buffer;\n\t\t\treturn buffers[objectId];\n\t\t} finally {\n\t\t\tport1.close();\n\t\t}\n\t}\n\n\t/**\n\t * Get or create the growable buffer on the main thread (or in a fork-mode process).\n\t * @param objectId The object id that identifies the buffer.\n\t * @param options Optional capacity configuration for new buffers.\n\t * @returns The existing or newly created SharedArrayBuffer.\n\t * @internal\n\t */\n\tprivate static getOrCreateBuffer(\n\t\tobjectId: string,\n\t\toptions?: ISharedObjectBufferOptions\n\t): SharedArrayBuffer {\n\t\tconst buffers = SharedObjectBuffer.getBuffers();\n\t\tif (Is.undefined(buffers[objectId])) {\n\t\t\tconst maxCapacity = options?.maxCapacityBytes ?? SharedObjectBuffer.MAX_CAPACITY_BYTES;\n\t\t\tconst initialCapacity = Math.min(\n\t\t\t\toptions?.initialCapacityBytes ?? SharedObjectBuffer.DEFAULT_CAPACITY_BYTES,\n\t\t\t\tmaxCapacity\n\t\t\t);\n\t\t\tbuffers[objectId] = new SharedArrayBuffer(\n\t\t\t\tSharedObjectBuffer._HEADER_BYTES + initialCapacity,\n\t\t\t\t{\n\t\t\t\t\tmaxByteLength: SharedObjectBuffer._HEADER_BYTES + maxCapacity\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\treturn buffers[objectId];\n\t}\n\n\t/**\n\t * Create a new smaller SharedArrayBuffer, register it in SharedStore, and return it.\n\t * The new buffer is empty; write writes the current payload into it immediately\n\t * after this call returns. Callers on other threads that have cached the old buffer\n\t * reference continue using it until they restart or re-request via the worker protocol.\n\t * @param objectId The object id that identifies the buffer.\n\t * @param dataLen Byte length of the payload that will be written next.\n\t * @param maxByteLength The maxByteLength to preserve from the old buffer.\n\t * @returns The new, smaller SharedArrayBuffer registered in SharedStore.\n\t * @internal\n\t */\n\tprivate static shrinkBuffer(\n\t\tobjectId: string,\n\t\tdataLen: number,\n\t\tmaxByteLength: number\n\t): SharedArrayBuffer {\n\t\tconst newCapacity = Math.max(\n\t\t\tdataLen * SharedObjectBuffer._GROW_FACTOR,\n\t\t\tSharedObjectBuffer.DEFAULT_CAPACITY_BYTES\n\t\t);\n\t\tconst newBuf = new SharedArrayBuffer(SharedObjectBuffer._HEADER_BYTES + newCapacity, {\n\t\t\tmaxByteLength\n\t\t});\n\t\tSharedObjectBuffer.getBuffers()[objectId] = newBuf;\n\t\treturn newBuf;\n\t}\n\n\t/**\n\t * Return the per-thread buffer map from SharedStore, creating it if absent.\n\t * @returns The map of object id to SharedArrayBuffer.\n\t * @internal\n\t */\n\tprivate static getBuffers(): { [objectId: string]: SharedArrayBuffer } {\n\t\tlet buffers = SharedStore.get<{ [objectId: string]: SharedArrayBuffer }>(\n\t\t\tSharedObjectBuffer._STORE_KEY\n\t\t);\n\t\tif (Is.undefined(buffers)) {\n\t\t\tbuffers = {};\n\t\t\tSharedStore.set(SharedObjectBuffer._STORE_KEY, buffers);\n\t\t}\n\t\treturn buffers;\n\t}\n\n\t/**\n\t * Lazily load node:worker_threads, returning null in environments where it is unavailable.\n\t * @returns The worker_threads module or null.\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static async loadWorkerThreads(): Promise<typeof import(\"node:worker_threads\") | null> {\n\t\tif (SharedObjectBuffer._workerThreadsModule === undefined) {\n\t\t\ttry {\n\t\t\t\tSharedObjectBuffer._workerThreadsModule = await import(\"node:worker_threads\");\n\t\t\t} catch {\n\t\t\t\tSharedObjectBuffer._workerThreadsModule = null;\n\t\t\t}\n\t\t}\n\t\treturn SharedObjectBuffer._workerThreadsModule;\n\t}\n}\n"]}
1
+ {"version":3,"file":"sharedObjectBuffer.js","sourceRoot":"","sources":["../../../src/utils/sharedObjectBuffer.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { MessagePort } from \"node:worker_threads\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { Is } from \"./is.js\";\nimport { SharedStore } from \"./sharedStore.js\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport { ObjectHelper } from \"../helpers/objectHelper.js\";\nimport type { ISharedObjectBufferOptions } from \"../models/ISharedObjectBufferOptions.js\";\nimport type { ISharedObjectBufferWorkerMessage } from \"../models/ISharedObjectBufferWorkerMessage.js\";\nimport { SharedObjectBufferMessageTypes } from \"../models/sharedObjectBufferMessageTypes.js\";\n\n/**\n * Manages per-object SharedArrayBuffers that store objects as UTF-8 JSON.\n * Buffer layout: 4-byte Int32 header (current data byte length) followed by the JSON-encoded object.\n * Buffers are explicitly created with create and cached in SharedStore.\n * On a worker thread an existing buffer is fetched via a MessagePort handshake\n * (same protocol as Mutex) and cached locally.\n * Buffers grow automatically when the payload exceeds capacity; when the payload drops well below\n * capacity the buffer is replaced with a smaller one. The caller must hold the objectId-keyed Mutex\n * around every read and write. The main thread's worker message handler must forward messages to\n * both Mutex.handleWorkerMessage and SharedObjectBuffer.handleWorkerMessage.\n */\nexport class SharedObjectBuffer {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SharedObjectBuffer>();\n\n\t/**\n\t * Default payload capacity per object (1 MiB). The first caller that creates the buffer\n\t * for an object determines its initial capacity; later callers that pass a different value\n\t * are ignored.\n\t */\n\tpublic static readonly DEFAULT_CAPACITY_BYTES = 1 * 1024 * 1024;\n\n\t/**\n\t * Default upper bound for how large a buffer may grow (256 MiB).\n\t * Override per-object via the maxCapacityBytes option on create.\n\t */\n\tpublic static readonly MAX_CAPACITY_BYTES = 256 * 1024 * 1024;\n\n\t/**\n\t * Bytes reserved for the data-length header at the start of each buffer.\n\t * @internal\n\t */\n\tprivate static readonly _HEADER_BYTES = 4;\n\n\t/**\n\t * Multiplier applied to the current capacity when growing or sizing a replacement buffer.\n\t * @internal\n\t */\n\tprivate static readonly _GROW_FACTOR = 2;\n\n\t/**\n\t * Fraction of payload capacity below which a buffer is replaced with a smaller one.\n\t * A value of 0.25 means the buffer is shrunk when the payload is below 25 % of capacity.\n\t * Shrinking only applies when the capacity already exceeds DEFAULT_CAPACITY_BYTES.\n\t * @internal\n\t */\n\tprivate static readonly _SHRINK_THRESHOLD = 0.25;\n\n\t/**\n\t * SharedStore key under which the per-object buffer map lives on each thread.\n\t * @internal\n\t */\n\tprivate static readonly _STORE_KEY = \"sharedObjectBuffers\";\n\n\t/**\n\t * Cached worker_threads module; undefined = not yet loaded, null = unavailable.\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static _workerThreadsModule: typeof import(\"node:worker_threads\") | null | undefined;\n\n\t/**\n\t * Create the buffer for the given objectId if it does not already exist.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t * @param options Optional capacity configuration used when creating the buffer.\n\t */\n\tpublic static async create(\n\t\tobjectId: string,\n\t\toptions?: ISharedObjectBufferOptions\n\t): Promise<void> {\n\t\tawait SharedObjectBuffer.getOrFetchBuffer(objectId, options ?? {});\n\t}\n\n\t/**\n\t * Read and decode the object stored for the given objectId.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t * @returns The stored object, or undefined when nothing has been written yet.\n\t */\n\tpublic static async read<T>(objectId: string): Promise<T | undefined> {\n\t\tconst buf = await SharedObjectBuffer.getOrFetchBuffer(objectId);\n\t\tif (Is.undefined(buf)) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn SharedObjectBuffer.decode<T>(buf);\n\t}\n\n\t/**\n\t * Encode and write the object into the buffer for the given objectId.\n\t * The buffer must already exist, usually by calling create first.\n\t * When the encoded payload exceeds the current buffer capacity the buffer is grown\n\t * in-place via SharedArrayBuffer.grow so every thread with a reference sees the\n\t * new size without any pointer swap. When the payload is smaller than\n\t * _SHRINK_THRESHOLD of the current capacity and the capacity exceeds\n\t * DEFAULT_CAPACITY_BYTES, the buffer is replaced with a smaller one on the calling thread.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t * @param value The object to persist.\n\t */\n\tpublic static async write<T>(objectId: string, value: T): Promise<void> {\n\t\tconst encoded = ObjectHelper.toBytes(value);\n\t\tlet buf = await SharedObjectBuffer.getOrFetchBuffer(objectId);\n\n\t\tif (Is.undefined(buf)) {\n\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"notCreated\", {\n\t\t\t\tobjectId\n\t\t\t});\n\t\t}\n\n\t\tconst payloadCapacity = buf.byteLength - SharedObjectBuffer._HEADER_BYTES;\n\t\tconst maxPayloadCapacity = buf.maxByteLength - SharedObjectBuffer._HEADER_BYTES;\n\n\t\tif (encoded.length > payloadCapacity) {\n\t\t\t// Grow the buffer in-place. SharedArrayBuffer.grow is atomic: all threads that\n\t\t\t// hold a reference see the enlarged buffer without a pointer swap.\n\t\t\tconst newPayloadCapacity = Math.max(\n\t\t\t\tencoded.length,\n\t\t\t\tpayloadCapacity * SharedObjectBuffer._GROW_FACTOR\n\t\t\t);\n\t\t\tconst newByteLength = Math.min(\n\t\t\t\tSharedObjectBuffer._HEADER_BYTES + newPayloadCapacity,\n\t\t\t\tbuf.maxByteLength\n\t\t\t);\n\t\t\tif (encoded.length > newByteLength - SharedObjectBuffer._HEADER_BYTES) {\n\t\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"capacityExceeded\", {\n\t\t\t\t\tobjectId,\n\t\t\t\t\trequired: encoded.length,\n\t\t\t\t\tcapacity: maxPayloadCapacity\n\t\t\t\t});\n\t\t\t}\n\t\t\tbuf.grow(newByteLength);\n\t\t} else if (\n\t\t\tpayloadCapacity > SharedObjectBuffer.DEFAULT_CAPACITY_BYTES &&\n\t\t\tencoded.length < payloadCapacity * SharedObjectBuffer._SHRINK_THRESHOLD\n\t\t) {\n\t\t\t// Replace with a smaller buffer when far below capacity. The caller writes\n\t\t\t// the payload into the returned buffer immediately after.\n\t\t\tbuf = SharedObjectBuffer.shrinkBuffer(objectId, encoded.length, buf.maxByteLength);\n\t\t}\n\n\t\tnew Uint8Array(buf, SharedObjectBuffer._HEADER_BYTES).set(encoded);\n\t\tAtomics.store(new Int32Array(buf, 0, 1), 0, encoded.length);\n\t}\n\n\t/**\n\t * Remove the stored object and release the buffer for the given objectId.\n\t * The entry is deleted from the local cache so subsequent reads or writes will\n\t * create or fetch a fresh buffer. Worker threads that have cached the old buffer\n\t * reference continue using it until they restart or re-request via the worker protocol.\n\t * Must be called while holding Mutex.lock(objectId).\n\t * @param objectId The object id that identifies the buffer.\n\t */\n\tpublic static remove(objectId: string): void {\n\t\tdelete SharedObjectBuffer.getBuffers()[objectId];\n\t}\n\n\t/**\n\t * Inspect a message from a worker thread and, if it is a SharedObjectBuffer\n\t * buffer-fetch request, respond to it synchronously.\n\t * Call this from the main thread's worker message handler alongside\n\t * Mutex.handleWorkerMessage.\n\t * @param msg The raw message received from the worker.\n\t * @returns True if the message was a SharedObjectBuffer protocol message, false otherwise.\n\t */\n\tpublic static handleWorkerMessage(msg: unknown): boolean {\n\t\tif (\n\t\t\t!Is.object<ISharedObjectBufferWorkerMessage>(msg) ||\n\t\t\tmsg.type !== SharedObjectBufferMessageTypes.GetBuffer\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst {\n\t\t\tobjectId: objectName,\n\t\t\tport,\n\t\t\tsignal,\n\t\t\toptions\n\t\t} = msg as ISharedObjectBufferWorkerMessage & {\n\t\t\tport: MessagePort;\n\t\t};\n\n\t\tconst buf = Is.object(options)\n\t\t\t? SharedObjectBuffer.getOrCreateBuffer(objectName, options)\n\t\t\t: SharedObjectBuffer.getBuffers()[objectName];\n\n\t\t// Deliver the buffer before waking the worker so it is in the port's receive\n\t\t// queue when Atomics.wait returns (same ordering guarantee as Mutex).\n\t\tport.postMessage({ buffer: buf });\n\t\tAtomics.notify(new Int32Array(signal), 0, 1);\n\t\tport.close();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Decode the object from a SharedArrayBuffer.\n\t * @param buf The SharedArrayBuffer to decode.\n\t * @returns The decoded object, or undefined when nothing has been written.\n\t * @internal\n\t */\n\tprivate static decode<T>(buf: SharedArrayBuffer): T | undefined {\n\t\tconst dataLen = Atomics.load(new Int32Array(buf, 0, 1), 0);\n\t\tif (dataLen === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst bytes = new Uint8Array(buf, SharedObjectBuffer._HEADER_BYTES, dataLen);\n\t\treturn ObjectHelper.fromBytes<T>(bytes);\n\t}\n\n\t/**\n\t * Return the cached buffer for the object, creating or fetching it if needed.\n\t * Applies a double-check after the async loadWorkerThreads call to handle the\n\t * case where another coroutine populated the cache while this one yielded.\n\t * @param objectId The object id that identifies the buffer.\n\t * @param options Optional capacity configuration when creating the buffer.\n\t * @returns The SharedArrayBuffer for the object.\n\t * @internal\n\t */\n\tprivate static async getOrFetchBuffer(\n\t\tobjectId: string,\n\t\toptions?: ISharedObjectBufferOptions\n\t): Promise<SharedArrayBuffer | undefined> {\n\t\tconst buffers = SharedObjectBuffer.getBuffers();\n\n\t\tif (!Is.undefined(buffers[objectId])) {\n\t\t\treturn buffers[objectId];\n\t\t}\n\n\t\tconst wt = await SharedObjectBuffer.loadWorkerThreads();\n\n\t\t// Re-check after the await: another coroutine may have populated the cache.\n\t\tif (!Is.undefined(buffers[objectId])) {\n\t\t\treturn buffers[objectId];\n\t\t}\n\n\t\tif (Is.empty(wt) || wt.isMainThread) {\n\t\t\tif (!Is.object(options)) {\n\t\t\t\treturn buffers[objectId];\n\t\t\t}\n\t\t\treturn SharedObjectBuffer.getOrCreateBuffer(objectId, options);\n\t\t}\n\n\t\t// Worker thread: synchronously request the SharedArrayBuffer from the main thread.\n\t\tif (Is.empty(wt.parentPort)) {\n\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"bufferFetchFailed\", { objectId });\n\t\t}\n\n\t\tconst { port1, port2 } = new wt.MessageChannel();\n\t\tconst signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));\n\n\t\tconst request: ISharedObjectBufferWorkerMessage = {\n\t\t\ttype: SharedObjectBufferMessageTypes.GetBuffer,\n\t\t\tobjectId,\n\t\t\tsignal: signal.buffer,\n\t\t\tport: port2,\n\t\t\toptions\n\t\t};\n\n\t\twt.parentPort.postMessage(request, [port2]);\n\n\t\ttry {\n\t\t\t// Block until the main thread posts the buffer and fires Atomics.notify.\n\t\t\t// The response is guaranteed to be in port1's queue when wait returns because\n\t\t\t// port.postMessage executes before Atomics.notify on the main thread.\n\t\t\tconst waitResult = Atomics.wait(signal, 0, 0, 30_000);\n\t\t\tif (waitResult === \"timed-out\") {\n\t\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"bufferFetchFailed\", { objectId });\n\t\t\t}\n\n\t\t\tconst response = wt.receiveMessageOnPort(port1) as {\n\t\t\t\tmessage: { buffer?: SharedArrayBuffer };\n\t\t\t} | null;\n\n\t\t\tif (Is.empty(response)) {\n\t\t\t\tthrow new GeneralError(SharedObjectBuffer.CLASS_NAME, \"bufferFetchFailed\", { objectId });\n\t\t\t}\n\n\t\t\tif (Is.undefined(response.message.buffer)) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tbuffers[objectId] = response.message.buffer;\n\t\t\treturn buffers[objectId];\n\t\t} finally {\n\t\t\tport1.close();\n\t\t}\n\t}\n\n\t/**\n\t * Get or create the growable buffer on the main thread (or in a fork-mode process).\n\t * @param objectId The object id that identifies the buffer.\n\t * @param options Optional capacity configuration for new buffers.\n\t * @returns The existing or newly created SharedArrayBuffer.\n\t * @internal\n\t */\n\tprivate static getOrCreateBuffer(\n\t\tobjectId: string,\n\t\toptions?: ISharedObjectBufferOptions\n\t): SharedArrayBuffer {\n\t\tconst buffers = SharedObjectBuffer.getBuffers();\n\t\tif (Is.undefined(buffers[objectId])) {\n\t\t\tconst maxCapacity = options?.maxCapacityBytes ?? SharedObjectBuffer.MAX_CAPACITY_BYTES;\n\t\t\tconst initialCapacity = Math.min(\n\t\t\t\toptions?.initialCapacityBytes ?? SharedObjectBuffer.DEFAULT_CAPACITY_BYTES,\n\t\t\t\tmaxCapacity\n\t\t\t);\n\t\t\tbuffers[objectId] = new SharedArrayBuffer(\n\t\t\t\tSharedObjectBuffer._HEADER_BYTES + initialCapacity,\n\t\t\t\t{\n\t\t\t\t\tmaxByteLength: SharedObjectBuffer._HEADER_BYTES + maxCapacity\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\treturn buffers[objectId];\n\t}\n\n\t/**\n\t * Create a new smaller SharedArrayBuffer, register it in SharedStore, and return it.\n\t * The new buffer is empty; write writes the current payload into it immediately\n\t * after this call returns. Callers on other threads that have cached the old buffer\n\t * reference continue using it until they restart or re-request via the worker protocol.\n\t * @param objectId The object id that identifies the buffer.\n\t * @param dataLen Byte length of the payload that will be written next.\n\t * @param maxByteLength The maxByteLength to preserve from the old buffer.\n\t * @returns The new, smaller SharedArrayBuffer registered in SharedStore.\n\t * @internal\n\t */\n\tprivate static shrinkBuffer(\n\t\tobjectId: string,\n\t\tdataLen: number,\n\t\tmaxByteLength: number\n\t): SharedArrayBuffer {\n\t\tconst newCapacity = Math.max(\n\t\t\tdataLen * SharedObjectBuffer._GROW_FACTOR,\n\t\t\tSharedObjectBuffer.DEFAULT_CAPACITY_BYTES\n\t\t);\n\t\tconst newBuf = new SharedArrayBuffer(SharedObjectBuffer._HEADER_BYTES + newCapacity, {\n\t\t\tmaxByteLength\n\t\t});\n\t\tSharedObjectBuffer.getBuffers()[objectId] = newBuf;\n\t\treturn newBuf;\n\t}\n\n\t/**\n\t * Return the per-thread buffer map from SharedStore, creating it if absent.\n\t * @returns The map of object id to SharedArrayBuffer.\n\t * @internal\n\t */\n\tprivate static getBuffers(): { [objectId: string]: SharedArrayBuffer } {\n\t\treturn SharedStore.get<{ [objectId: string]: SharedArrayBuffer }>(\n\t\t\tSharedObjectBuffer._STORE_KEY,\n\t\t\t() => ({})\n\t\t);\n\t}\n\n\t/**\n\t * Lazily load node:worker_threads, returning null in environments where it is unavailable.\n\t * @returns The worker_threads module or null.\n\t * @internal\n\t */\n\t// false positive: this is a type not an actual import\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-imports\n\tprivate static async loadWorkerThreads(): Promise<typeof import(\"node:worker_threads\") | null> {\n\t\tif (SharedObjectBuffer._workerThreadsModule === undefined) {\n\t\t\ttry {\n\t\t\t\tSharedObjectBuffer._workerThreadsModule = await import(\"node:worker_threads\");\n\t\t\t} catch {\n\t\t\t\tSharedObjectBuffer._workerThreadsModule = null;\n\t\t\t}\n\t\t}\n\t\treturn SharedObjectBuffer._workerThreadsModule;\n\t}\n}\n"]}
@@ -1,4 +1,4 @@
1
- // Copyright 2024 IOTA Stiftung.
1
+ // Copyright 2026 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  import { Is } from "./is.js";
4
4
  /**
@@ -7,17 +7,23 @@ import { Is } from "./is.js";
7
7
  */
8
8
  export class SharedStore {
9
9
  /**
10
- * Get a property from the shared store.
11
- * @param prop The name of the property to get.
12
- * @returns The property if it exists.
10
+ * Get a property from the shared store, creating and storing it with the
11
+ * factory if absent. The factory is invoked synchronously so its return
12
+ * value - including a Promise - is stored before any async yield, making
13
+ * this safe against concurrent callers.
14
+ * @param prop The name of the property to get or create.
15
+ * @param factory A synchronous factory that produces the initial value.
16
+ * @returns The existing or newly created value.
13
17
  */
14
- static get(prop) {
18
+ static get(prop, factory) {
15
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
20
  const shared = globalThis.__TWIN_SHARED__;
17
- if (Is.undefined(shared)) {
18
- return;
21
+ let value = Is.undefined(shared) ? undefined : shared[prop];
22
+ if (Is.empty(value) && !Is.undefined(factory)) {
23
+ value = factory();
24
+ SharedStore.set(prop, value);
19
25
  }
20
- return shared[prop];
26
+ return value;
21
27
  }
22
28
  /**
23
29
  * Set the property in the shared store.
@@ -1 +1 @@
1
- {"version":3,"file":"sharedStore.js","sourceRoot":"","sources":["../../../src/utils/sharedStore.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B;;;GAGG;AACH,MAAM,OAAO,WAAW;IACvB;;;;OAIG;IACI,MAAM,CAAC,GAAG,CAAc,IAAY;QAC1C,8DAA8D;QAC9D,MAAM,MAAM,GAAI,UAAkB,CAAC,eAAe,CAAC;QACnD,IAAI,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,CAAM,CAAC;IAC1B,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,GAAG,CAAc,IAAY,EAAE,KAAQ;QACpD,8DAA8D;QAC9D,IAAI,EAAE,CAAC,SAAS,CAAE,UAAkB,CAAC,eAAe,CAAC,EAAE,CAAC;YACvD,8DAA8D;YAC7D,UAAkB,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1C,CAAC;QAED,8DAA8D;QAC7D,UAAkB,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACnD,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,MAAM,CAAC,IAAY;QAChC,8DAA8D;QAC9D,MAAM,MAAM,GAAI,UAAkB,CAAC,eAAe,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACF,CAAC;CACD","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"./is.js\";\n\n/**\n * Provide a store for shared objects which can be accessed through multiple\n * instance loads of a package.\n */\nexport class SharedStore {\n\t/**\n\t * Get a property from the shared store.\n\t * @param prop The name of the property to get.\n\t * @returns The property if it exists.\n\t */\n\tpublic static get<T = unknown>(prop: string): T | undefined {\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst shared = (globalThis as any).__TWIN_SHARED__;\n\t\tif (Is.undefined(shared)) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn shared[prop] as T;\n\t}\n\n\t/**\n\t * Set the property in the shared store.\n\t * @param prop The name of the property to set.\n\t * @param value The value to set.\n\t */\n\tpublic static set<T = unknown>(prop: string, value: T): void {\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tif (Is.undefined((globalThis as any).__TWIN_SHARED__)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(globalThis as any).__TWIN_SHARED__ = {};\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t(globalThis as any).__TWIN_SHARED__[prop] = value;\n\t}\n\n\t/**\n\t * Remove a property from the shared store.\n\t * @param prop The name of the property to remove.\n\t */\n\tpublic static remove(prop: string): void {\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst shared = (globalThis as any).__TWIN_SHARED__;\n\t\tif (!Is.undefined(shared)) {\n\t\t\tdelete shared[prop];\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"sharedStore.js","sourceRoot":"","sources":["../../../src/utils/sharedStore.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B;;;GAGG;AACH,MAAM,OAAO,WAAW;IAmBvB;;;;;;;;OAQG;IACI,MAAM,CAAC,GAAG,CAAc,IAAY,EAAE,OAAiB;QAC7D,8DAA8D;QAC9D,MAAM,MAAM,GAAI,UAAkB,CAAC,eAAe,CAAC;QACnD,IAAI,KAAK,GAAkB,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,MAAM,CAAC,IAAI,CAAO,CAAC;QAElF,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/C,KAAK,GAAG,OAAO,EAAE,CAAC;YAClB,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,GAAG,CAAc,IAAY,EAAE,KAAQ;QACpD,8DAA8D;QAC9D,IAAI,EAAE,CAAC,SAAS,CAAE,UAAkB,CAAC,eAAe,CAAC,EAAE,CAAC;YACvD,8DAA8D;YAC7D,UAAkB,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1C,CAAC;QAED,8DAA8D;QAC7D,UAAkB,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACnD,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,MAAM,CAAC,IAAY;QAChC,8DAA8D;QAC9D,MAAM,MAAM,GAAI,UAAkB,CAAC,eAAe,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACF,CAAC;CACD","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"./is.js\";\n\n/**\n * Provide a store for shared objects which can be accessed through multiple\n * instance loads of a package.\n */\nexport class SharedStore {\n\t/**\n\t * Get a property from the shared store.\n\t * @param prop The name of the property to get.\n\t * @returns The property if it exists.\n\t */\n\tpublic static get<T = unknown>(prop: string): T | undefined;\n\n\t/**\n\t * Get a property from the shared store, creating and storing it with the\n\t * factory if absent. The factory is invoked synchronously so its return\n\t * value - including a Promise - is stored before any async yield, making\n\t * this safe against concurrent callers.\n\t * @param prop The name of the property to get or create.\n\t * @param factory A synchronous factory that produces the initial value.\n\t * @returns The existing or newly created value.\n\t */\n\tpublic static get<T = unknown>(prop: string, factory: () => T): T;\n\n\t/**\n\t * Get a property from the shared store, creating and storing it with the\n\t * factory if absent. The factory is invoked synchronously so its return\n\t * value - including a Promise - is stored before any async yield, making\n\t * this safe against concurrent callers.\n\t * @param prop The name of the property to get or create.\n\t * @param factory A synchronous factory that produces the initial value.\n\t * @returns The existing or newly created value.\n\t */\n\tpublic static get<T = unknown>(prop: string, factory?: () => T): T | undefined {\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst shared = (globalThis as any).__TWIN_SHARED__;\n\t\tlet value: T | undefined = Is.undefined(shared) ? undefined : (shared[prop] as T);\n\n\t\tif (Is.empty(value) && !Is.undefined(factory)) {\n\t\t\tvalue = factory();\n\t\t\tSharedStore.set(prop, value);\n\t\t}\n\n\t\treturn value;\n\t}\n\n\t/**\n\t * Set the property in the shared store.\n\t * @param prop The name of the property to set.\n\t * @param value The value to set.\n\t */\n\tpublic static set<T = unknown>(prop: string, value: T): void {\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tif (Is.undefined((globalThis as any).__TWIN_SHARED__)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(globalThis as any).__TWIN_SHARED__ = {};\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t(globalThis as any).__TWIN_SHARED__[prop] = value;\n\t}\n\n\t/**\n\t * Remove a property from the shared store.\n\t * @param prop The name of the property to remove.\n\t */\n\tpublic static remove(prop: string): void {\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst shared = (globalThis as any).__TWIN_SHARED__;\n\t\tif (!Is.undefined(shared)) {\n\t\t\tdelete shared[prop];\n\t\t}\n\t}\n}\n"]}
@@ -9,6 +9,16 @@ export declare class SharedStore {
9
9
  * @returns The property if it exists.
10
10
  */
11
11
  static get<T = unknown>(prop: string): T | undefined;
12
+ /**
13
+ * Get a property from the shared store, creating and storing it with the
14
+ * factory if absent. The factory is invoked synchronously so its return
15
+ * value - including a Promise - is stored before any async yield, making
16
+ * this safe against concurrent callers.
17
+ * @param prop The name of the property to get or create.
18
+ * @param factory A synchronous factory that produces the initial value.
19
+ * @returns The existing or newly created value.
20
+ */
21
+ static get<T = unknown>(prop: string, factory: () => T): T;
12
22
  /**
13
23
  * Set the property in the shared store.
14
24
  * @param prop The name of the property to set.
package/docs/changelog.md CHANGED
@@ -1,5 +1,104 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.1-next.3](https://github.com/iotaledger/twin-framework/compare/core-v0.9.1-next.2...core-v0.9.1-next.3) (2026-06-26)
4
+
5
+
6
+ ### Features
7
+
8
+ * add context id features ([#206](https://github.com/iotaledger/twin-framework/issues/206)) ([ef0d4ee](https://github.com/iotaledger/twin-framework/commit/ef0d4ee11a4f5fc6cc6f52a4958ce905c04ee13b))
9
+ * add duration support ([#362](https://github.com/iotaledger/twin-framework/issues/362)) ([24dbe8b](https://github.com/iotaledger/twin-framework/commit/24dbe8b208eb127a52024cb219d31d27731e9ae3))
10
+ * add factory.createIfExists ([aad5a53](https://github.com/iotaledger/twin-framework/commit/aad5a53cef1b1c2e04344ea46244d41e371dff9b))
11
+ * add Factory.getFactory ([ad6cd2d](https://github.com/iotaledger/twin-framework/commit/ad6cd2d30351c145c70212f8cce1b66d5e8a5235))
12
+ * add guard.dateString ([#335](https://github.com/iotaledger/twin-framework/issues/335)) ([a26a166](https://github.com/iotaledger/twin-framework/commit/a26a166eb1f62ece05b2432730cb7354feacecfc))
13
+ * add health method to components ([a88016d](https://github.com/iotaledger/twin-framework/commit/a88016d90d172413e5bc5238dc1b3e35f82fcc2c))
14
+ * add Is.class method ([4988205](https://github.com/iotaledger/twin-framework/commit/498820543e256a130b4888c958fe1d87ca865d7f))
15
+ * add isDefault option to factory registration ([a8a700b](https://github.com/iotaledger/twin-framework/commit/a8a700bb8ddaf7dd5097869a358b8fc5f7c40ce7))
16
+ * add mutex ([#316](https://github.com/iotaledger/twin-framework/issues/316)) ([1027e5a](https://github.com/iotaledger/twin-framework/commit/1027e5ac77aa9804178b4253abdeefd6f1c3d521))
17
+ * add objectHelper.split ([386830a](https://github.com/iotaledger/twin-framework/commit/386830a77f8e842a5b119be0983708e042c3b14b))
18
+ * add partial second support ([39bf087](https://github.com/iotaledger/twin-framework/commit/39bf087ddc113a9bf73e0d0e99f738f6f60c2f2d))
19
+ * add rsa cipher support ([7af6cc6](https://github.com/iotaledger/twin-framework/commit/7af6cc67512d3363bd4a2f2e87bd7733c2800147))
20
+ * add single occurrence array type ([e890e43](https://github.com/iotaledger/twin-framework/commit/e890e4399e75ae5097f3ad8b1007321cbb1ed4ac))
21
+ * add single occurrence array type ([#245](https://github.com/iotaledger/twin-framework/issues/245)) ([771dc78](https://github.com/iotaledger/twin-framework/commit/771dc78025b5546c9c9681be9494a76ab71171c6))
22
+ * add support for health i18n validation ([7a286dd](https://github.com/iotaledger/twin-framework/commit/7a286ddb0c1bfa498bf3a77126cd589042bad6de))
23
+ * add teardown to IComponent interface ([32c173c](https://github.com/iotaledger/twin-framework/commit/32c173cda115c20d3b4cee14b8a29cdc08e91555))
24
+ * add teardown to IComponent interface ([98ce864](https://github.com/iotaledger/twin-framework/commit/98ce8648e709310c4435de334825b381fb4e32cb))
25
+ * add typeName method to factory ([699fcbd](https://github.com/iotaledger/twin-framework/commit/699fcbd1168228401ddb81fdacb959b8cdc4206a))
26
+ * add uuidv7 support ([#219](https://github.com/iotaledger/twin-framework/issues/219)) ([916c657](https://github.com/iotaledger/twin-framework/commit/916c657d270ce99fafe554233739fdd9ca28635b))
27
+ * adding link header helper ([#225](https://github.com/iotaledger/twin-framework/issues/225)) ([703c072](https://github.com/iotaledger/twin-framework/commit/703c0725aceac6b6ec0c4fa729ef832d12fb3fd7))
28
+ * additional nameof operators ([a5aab60](https://github.com/iotaledger/twin-framework/commit/a5aab60bf66a86f1b7ff8af7c4f044cb03706d50))
29
+ * additional RSA methods and async ([1fceee2](https://github.com/iotaledger/twin-framework/commit/1fceee2d1248a24a7620846025fcf906495c07f4))
30
+ * bump version ([c5354fa](https://github.com/iotaledger/twin-framework/commit/c5354fa906f4493c70f2642a9175a66780a10636))
31
+ * concurrency in SharedStore ([#388](https://github.com/iotaledger/twin-framework/issues/388)) ([0610198](https://github.com/iotaledger/twin-framework/commit/0610198ba482273c3f6ba918e34f5875a3659571))
32
+ * configurable timeout for mutex ([2087e04](https://github.com/iotaledger/twin-framework/commit/2087e04464a110a3f009fbc8b066696ed6784fc2))
33
+ * env helper conversion methods ([e8c607e](https://github.com/iotaledger/twin-framework/commit/e8c607e59f6f79d4b1d562e9313babe348778835))
34
+ * eslint migration to flat config ([74427d7](https://github.com/iotaledger/twin-framework/commit/74427d78d342167f7850e49ab87269326355befe))
35
+ * expand error params to accept properties ([032e9fd](https://github.com/iotaledger/twin-framework/commit/032e9fd1388e457cde32ca1005dfe014a5a9c077))
36
+ * factory create and integrity ([#235](https://github.com/iotaledger/twin-framework/issues/235)) ([9f98b99](https://github.com/iotaledger/twin-framework/commit/9f98b99daf46eb365346fae49cc4ffba63e74cb3))
37
+ * health status grouping ([5007c29](https://github.com/iotaledger/twin-framework/commit/5007c29fe4123fe56f754450b993dbe5dc32a057))
38
+ * improve async cache edge cases ([4e57a6e](https://github.com/iotaledger/twin-framework/commit/4e57a6ec4113533b0ea903eae7d469200fa1348c))
39
+ * improve error formatting ([#313](https://github.com/iotaledger/twin-framework/issues/313)) ([5a19623](https://github.com/iotaledger/twin-framework/commit/5a196231bcbf088bf9ba92a93b7478d3b8c5593f))
40
+ * improve Is.function and ModuleHelper.getModuleMethod signatures ([ecf968b](https://github.com/iotaledger/twin-framework/commit/ecf968b02934b3676be4bf7cd2d1e7f8e7af6ce2))
41
+ * improve Is.function definition to retain types ([f20b6b0](https://github.com/iotaledger/twin-framework/commit/f20b6b0dd16e74b75dc359be72b05989305c6410))
42
+ * improve signatures ([45a7d58](https://github.com/iotaledger/twin-framework/commit/45a7d58baa5b6abc8c0735656f393d402fabb4ad))
43
+ * improve signatures ([cdd24be](https://github.com/iotaledger/twin-framework/commit/cdd24be6fb898d33955b6f2f93c3ddbd73582269))
44
+ * improve signatures ([2041ae2](https://github.com/iotaledger/twin-framework/commit/2041ae214cbd8d472d5e8be8d3403cd06a114040))
45
+ * improve signatures ([d8a0ee1](https://github.com/iotaledger/twin-framework/commit/d8a0ee16542134a3f8b0653065239f42045125b4))
46
+ * improve signatures ([1d084c5](https://github.com/iotaledger/twin-framework/commit/1d084c58c7eb41ae50b0ce80ce57e48a22dd3102))
47
+ * improve signatures ([#287](https://github.com/iotaledger/twin-framework/issues/287)) ([c9f38f0](https://github.com/iotaledger/twin-framework/commit/c9f38f00e1cea8759e1105b41872a650c66c00f4))
48
+ * is and coerce accept duration object ([51dffa1](https://github.com/iotaledger/twin-framework/commit/51dffa18066eb86a14c700d90d3c1bd8b322c0fc))
49
+ * locales validation ([#197](https://github.com/iotaledger/twin-framework/issues/197)) ([55fdadb](https://github.com/iotaledger/twin-framework/commit/55fdadb13595ce0047f787bd1d4135d429a99f12))
50
+ * mutex safe usage in browser ([#339](https://github.com/iotaledger/twin-framework/issues/339)) ([02d409f](https://github.com/iotaledger/twin-framework/commit/02d409f211e91267964208d58a27f29791715086))
51
+ * nodeIdentity optional in IComponent methods ([c78dc17](https://github.com/iotaledger/twin-framework/commit/c78dc17f4357d3e1ae40e415f468d3eae13e81f4))
52
+ * shared object buffer ([#355](https://github.com/iotaledger/twin-framework/issues/355)) ([70d82ea](https://github.com/iotaledger/twin-framework/commit/70d82ea8f43f01aa840ae90ee0b1f23b064a07c6))
53
+ * simplify factory options ([7f85a85](https://github.com/iotaledger/twin-framework/commit/7f85a8553dd3008364e8e1104f2e6f6b6595d77e))
54
+ * simplify StringHelper signature ([0390403](https://github.com/iotaledger/twin-framework/commit/039040344952f91ee3c671249bc0e1c8f3b38e17))
55
+ * typescript 6 update ([1d10f31](https://github.com/iotaledger/twin-framework/commit/1d10f31e6516ec622773f45e88af82fe749b384a))
56
+ * update dependencies ([4da77ab](https://github.com/iotaledger/twin-framework/commit/4da77ab30f499e52825ac5a76f51436ceb59c26e))
57
+ * update dependencies ([f3bd015](https://github.com/iotaledger/twin-framework/commit/f3bd015efd169196b7e0335f5cab876ba6ca1d75))
58
+ * urn random switched to using uuidv7 ([606c9a2](https://github.com/iotaledger/twin-framework/commit/606c9a2ed68a10fc7fc2bc5f93388c1b71033154))
59
+ * urn random switched to using uuidv7 ([6a29f8b](https://github.com/iotaledger/twin-framework/commit/6a29f8bd573d06992b7eaa027b1daf4c2a2e1e85))
60
+ * use cause instead of inner for errors ([1f4acc4](https://github.com/iotaledger/twin-framework/commit/1f4acc4d7a6b71a134d9547da9bf40de1e1e49da))
61
+
62
+
63
+ ### Bug Fixes
64
+
65
+ * async thread lock ([97a96a5](https://github.com/iotaledger/twin-framework/commit/97a96a57f60c7b45a0ccbbfa1c43199b6e7512f9))
66
+ * ensure __decorate is defined for decorators ([103a563](https://github.com/iotaledger/twin-framework/commit/103a563ce01ebdef6240d2e590e7b026e8692684))
67
+ * improve exception handling in asyncCache ([c81b29b](https://github.com/iotaledger/twin-framework/commit/c81b29b660b152d2f0757d323430287e6491bf59))
68
+ * improve jsdoc comments ([f7c8e43](https://github.com/iotaledger/twin-framework/commit/f7c8e43fab9803dffa6461950d5b9979e25bcd24))
69
+ * linting ([b77511d](https://github.com/iotaledger/twin-framework/commit/b77511de8781707088b1beb4c92f12d7d6e0fd5f))
70
+ * mutex locking ([d5af3c1](https://github.com/iotaledger/twin-framework/commit/d5af3c1e66070f86408ba1410809689c6d22958b))
71
+ * pick non iterable keys ([399399a](https://github.com/iotaledger/twin-framework/commit/399399abfb8947c4eb235df527532ffe186986b3))
72
+ * prevent TOCTOU race in Mutex.getOrFetchLock for concurrent async callers ([#343](https://github.com/iotaledger/twin-framework/issues/343)) ([5238ad1](https://github.com/iotaledger/twin-framework/commit/5238ad1a68e24c69162204b02030c6686c15e9f4))
73
+ * remove misleading undefined from AsyncCache return type ([05b3291](https://github.com/iotaledger/twin-framework/commit/05b32914e03a63c7daf7b77952a2ebedb7ca7f6b))
74
+
75
+
76
+ ### Dependencies
77
+
78
+ * The following workspace dependencies were updated
79
+ * dependencies
80
+ * @twin.org/nameof bumped from 0.9.1-next.2 to 0.9.1-next.3
81
+ * devDependencies
82
+ * @twin.org/nameof-transformer bumped from 0.9.1-next.2 to 0.9.1-next.3
83
+ * @twin.org/nameof-vitest-plugin bumped from 0.9.1-next.2 to 0.9.1-next.3
84
+
85
+ ## [0.9.1-next.2](https://github.com/iotaledger/twin-framework/compare/core-v0.9.1-next.1...core-v0.9.1-next.2) (2026-06-26)
86
+
87
+
88
+ ### Features
89
+
90
+ * concurrency in SharedStore ([#388](https://github.com/iotaledger/twin-framework/issues/388)) ([0610198](https://github.com/iotaledger/twin-framework/commit/0610198ba482273c3f6ba918e34f5875a3659571))
91
+
92
+
93
+ ### Dependencies
94
+
95
+ * The following workspace dependencies were updated
96
+ * dependencies
97
+ * @twin.org/nameof bumped from 0.9.1-next.1 to 0.9.1-next.2
98
+ * devDependencies
99
+ * @twin.org/nameof-transformer bumped from 0.9.1-next.1 to 0.9.1-next.2
100
+ * @twin.org/nameof-vitest-plugin bumped from 0.9.1-next.1 to 0.9.1-next.2
101
+
3
102
  ## [0.9.1-next.1](https://github.com/iotaledger/twin-framework/compare/core-v0.9.1-next.0...core-v0.9.1-next.1) (2026-06-25)
4
103
 
5
104
 
@@ -17,30 +17,84 @@ instance loads of a package.
17
17
 
18
18
  ### get() {#get}
19
19
 
20
+ Get a property from the shared store, creating and storing it with the
21
+ factory if absent. The factory is invoked synchronously so its return
22
+ value - including a Promise - is stored before any async yield, making
23
+ this safe against concurrent callers.
24
+
25
+ #### Param
26
+
27
+ **prop**
28
+
29
+ The name of the property to get or create.
30
+
31
+ #### Param
32
+
33
+ **factory**
34
+
35
+ A synchronous factory that produces the initial value.
36
+
37
+ #### Call Signature
38
+
20
39
  > `static` **get**\<`T`\>(`prop`): `T` \| `undefined`
21
40
 
22
41
  Get a property from the shared store.
23
42
 
24
- #### Type Parameters
43
+ ##### Type Parameters
25
44
 
26
- ##### T
45
+ ###### T
27
46
 
28
47
  `T` = `unknown`
29
48
 
30
- #### Parameters
49
+ ##### Parameters
31
50
 
32
- ##### prop
51
+ ###### prop
33
52
 
34
53
  `string`
35
54
 
36
55
  The name of the property to get.
37
56
 
38
- #### Returns
57
+ ##### Returns
39
58
 
40
59
  `T` \| `undefined`
41
60
 
42
61
  The property if it exists.
43
62
 
63
+ #### Call Signature
64
+
65
+ > `static` **get**\<`T`\>(`prop`, `factory`): `T`
66
+
67
+ Get a property from the shared store, creating and storing it with the
68
+ factory if absent. The factory is invoked synchronously so its return
69
+ value - including a Promise - is stored before any async yield, making
70
+ this safe against concurrent callers.
71
+
72
+ ##### Type Parameters
73
+
74
+ ###### T
75
+
76
+ `T` = `unknown`
77
+
78
+ ##### Parameters
79
+
80
+ ###### prop
81
+
82
+ `string`
83
+
84
+ The name of the property to get or create.
85
+
86
+ ###### factory
87
+
88
+ () => `T`
89
+
90
+ A synchronous factory that produces the initial value.
91
+
92
+ ##### Returns
93
+
94
+ `T`
95
+
96
+ The existing or newly created value.
97
+
44
98
  ***
45
99
 
46
100
  ### set() {#set}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/core",
3
- "version": "0.9.1-next.1",
3
+ "version": "0.9.1-next.3",
4
4
  "description": "Helper methods/classes for data type checking/validation/guarding/error handling",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/nameof": "0.9.1-next.1",
17
+ "@twin.org/nameof": "0.9.1-next.3",
18
18
  "intl-messageformat": "11.2.8",
19
19
  "rfc6902": "5.2.0"
20
20
  },