@twin.org/core 0.0.4-next.3 → 0.0.4-next.5

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.
@@ -182,6 +182,7 @@ export class Base64 {
182
182
  * Get the valid and placeholder lengths from a bas64 string.
183
183
  * @param base64 The base64 string.
184
184
  * @returns The lengths.
185
+ * @throws GeneralError if the string length is not a multiple of 4.
185
186
  * @internal
186
187
  */
187
188
  static getLengths(base64) {
@@ -1 +1 @@
1
- {"version":3,"file":"base64.js","sourceRoot":"","sources":["../../../src/encoding/base64.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/* eslint-disable no-bitwise */\n/* eslint-disable no-mixed-operators */\n\nimport { nameof } from \"@twin.org/nameof\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport { Guards } from \"../utils/guards.js\";\n\n/**\n * Class to help with base64 Encoding/Decoding.\n * Sourced from https://github.com/beatgammit/base64-js.\n */\nexport class Base64 {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Base64>();\n\n\t/**\n\t * Alphabet table for encoding.\n\t * @internal\n\t */\n\tprivate static readonly _LOOKUP: string =\n\t\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\n\t/**\n\t * Alphabet table for decoding.\n\t * @internal\n\t */\n\tprivate static readonly _REVERSE_LOOKUP: { [id: number]: number } = {\n\t\t\"43\": 62,\n\t\t\"45\": 62,\n\t\t\"47\": 63,\n\t\t\"48\": 52,\n\t\t\"49\": 53,\n\t\t\"50\": 54,\n\t\t\"51\": 55,\n\t\t\"52\": 56,\n\t\t\"53\": 57,\n\t\t\"54\": 58,\n\t\t\"55\": 59,\n\t\t\"56\": 60,\n\t\t\"57\": 61,\n\t\t\"65\": 0,\n\t\t\"66\": 1,\n\t\t\"67\": 2,\n\t\t\"68\": 3,\n\t\t\"69\": 4,\n\t\t\"70\": 5,\n\t\t\"71\": 6,\n\t\t\"72\": 7,\n\t\t\"73\": 8,\n\t\t\"74\": 9,\n\t\t\"75\": 10,\n\t\t\"76\": 11,\n\t\t\"77\": 12,\n\t\t\"78\": 13,\n\t\t\"79\": 14,\n\t\t\"80\": 15,\n\t\t\"81\": 16,\n\t\t\"82\": 17,\n\t\t\"83\": 18,\n\t\t\"84\": 19,\n\t\t\"85\": 20,\n\t\t\"86\": 21,\n\t\t\"87\": 22,\n\t\t\"88\": 23,\n\t\t\"89\": 24,\n\t\t\"90\": 25,\n\t\t\"95\": 63,\n\t\t\"97\": 26,\n\t\t\"98\": 27,\n\t\t\"99\": 28,\n\t\t\"100\": 29,\n\t\t\"101\": 30,\n\t\t\"102\": 31,\n\t\t\"103\": 32,\n\t\t\"104\": 33,\n\t\t\"105\": 34,\n\t\t\"106\": 35,\n\t\t\"107\": 36,\n\t\t\"108\": 37,\n\t\t\"109\": 38,\n\t\t\"110\": 39,\n\t\t\"111\": 40,\n\t\t\"112\": 41,\n\t\t\"113\": 42,\n\t\t\"114\": 43,\n\t\t\"115\": 44,\n\t\t\"116\": 45,\n\t\t\"117\": 46,\n\t\t\"118\": 47,\n\t\t\"119\": 48,\n\t\t\"120\": 49,\n\t\t\"121\": 50,\n\t\t\"122\": 51\n\t};\n\n\t/**\n\t * Get the byte length of the data.\n\t * @param base64 The base64 string.\n\t * @returns The byte length of the data.\n\t */\n\tpublic static byteLength(base64: string): number {\n\t\tconst lens = Base64.getLengths(base64);\n\t\treturn Base64.calcByteLength(lens[0], lens[1]);\n\t}\n\n\t/**\n\t * Convert the base 64 string to a byte array.\n\t * @param base64 The base64 string to convert.\n\t * @returns The byte array.\n\t */\n\tpublic static decode(base64: string): Uint8Array {\n\t\tGuards.string(Base64.CLASS_NAME, nameof(base64), base64);\n\n\t\tlet tmp;\n\t\tconst lens = Base64.getLengths(base64);\n\t\tconst validLen = lens[0];\n\t\tconst placeHoldersLen = lens[1];\n\n\t\tconst arr = new Uint8Array(Base64.calcByteLength(validLen, placeHoldersLen));\n\n\t\tlet curByte = 0;\n\n\t\t// if there are placeholders, only get up to the last complete 4 chars\n\t\tconst len = placeHoldersLen > 0 ? validLen - 4 : validLen;\n\n\t\tlet i;\n\t\tfor (i = 0; i < len; i += 4) {\n\t\t\ttmp =\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i)] << 18) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 1)] << 12) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 2)] << 6) |\n\t\t\t\tBase64._REVERSE_LOOKUP[base64.charCodeAt(i + 3)];\n\t\t\tarr[curByte++] = (tmp >> 16) & 0xff;\n\t\t\tarr[curByte++] = (tmp >> 8) & 0xff;\n\t\t\tarr[curByte++] = tmp & 0xff;\n\t\t}\n\n\t\tif (placeHoldersLen === 2) {\n\t\t\ttmp =\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i)] << 2) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 1)] >> 4);\n\t\t\tarr[curByte++] = tmp & 0xff;\n\t\t}\n\n\t\tif (placeHoldersLen === 1) {\n\t\t\ttmp =\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i)] << 10) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 1)] << 4) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 2)] >> 2);\n\t\t\tarr[curByte++] = (tmp >> 8) & 0xff;\n\t\t\tarr[curByte] = tmp & 0xff;\n\t\t}\n\n\t\treturn arr;\n\t}\n\n\t/**\n\t * Convert a byte array to base 64.\n\t * @param bytes The byte array to convert.\n\t * @returns The data as base64 string.\n\t */\n\tpublic static encode(bytes: Uint8Array): string {\n\t\tGuards.uint8Array(Base64.CLASS_NAME, nameof(bytes), bytes);\n\n\t\tlet tmp;\n\t\tconst len = bytes.length;\n\t\tconst extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes\n\t\tconst parts = [];\n\t\tconst maxChunkLength = 16383; // must be multiple of 3\n\n\t\t// go through the array every three bytes, we'll deal with trailing stuff later\n\t\tfor (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n\t\t\tparts.push(Base64.encodeChunk(bytes, i, Math.min(i + maxChunkLength, len2)));\n\t\t}\n\n\t\t// pad the end with zeros, but make sure to not forget the extra bytes\n\t\tif (extraBytes === 1) {\n\t\t\ttmp = bytes[len - 1];\n\t\t\tparts.push(`${Base64._LOOKUP[tmp >> 2] + Base64._LOOKUP[(tmp << 4) & 0x3f]}==`);\n\t\t} else if (extraBytes === 2) {\n\t\t\ttmp = (bytes[len - 2] << 8) + bytes[len - 1];\n\t\t\tparts.push(\n\t\t\t\t`${Base64._LOOKUP[tmp >> 10] + Base64._LOOKUP[(tmp >> 4) & 0x3f] + Base64._LOOKUP[(tmp << 2) & 0x3f]}=`\n\t\t\t);\n\t\t}\n\n\t\treturn parts.join(\"\");\n\t}\n\n\t/**\n\t * Calculate the byte length.\n\t * @param validLen The valid length.\n\t * @param placeHoldersLen The placeholder length.\n\t * @returns The length.\n\t * @internal\n\t */\n\tprivate static calcByteLength(validLen: number, placeHoldersLen: number): number {\n\t\treturn ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;\n\t}\n\n\t/**\n\t * Get the valid and placeholder lengths from a bas64 string.\n\t * @param base64 The base64 string.\n\t * @returns The lengths.\n\t * @internal\n\t */\n\tprivate static getLengths(base64: string): number[] {\n\t\tconst len = base64.length;\n\n\t\tif (len % 4 > 0) {\n\t\t\tthrow new GeneralError(Base64.CLASS_NAME, \"length4Multiple\", { value: len });\n\t\t}\n\n\t\t// Trim off extra bytes after placeholder bytes are found\n\t\t// See: https://github.com/beatgammit/base64-js/issues/42\n\t\tlet validLen = base64.indexOf(\"=\");\n\t\tif (validLen === -1) {\n\t\t\tvalidLen = len;\n\t\t}\n\n\t\tconst placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4);\n\n\t\treturn [validLen, placeHoldersLen];\n\t}\n\n\t/**\n\t * Convert the triplet to base 64.\n\t * @param num The number to convert.\n\t * @returns The base64 encoding.\n\t * @internal\n\t */\n\tprivate static tripletToBase64(num: number): string {\n\t\treturn (\n\t\t\tBase64._LOOKUP[(num >> 18) & 0x3f] +\n\t\t\tBase64._LOOKUP[(num >> 12) & 0x3f] +\n\t\t\tBase64._LOOKUP[(num >> 6) & 0x3f] +\n\t\t\tBase64._LOOKUP[num & 0x3f]\n\t\t);\n\t}\n\n\t/**\n\t * Encode a chunk.\n\t * @param bytes The byte array.\n\t * @param start The start index in the buffer.\n\t * @param end The end index in the buffer.\n\t * @returns The encoded chunk.\n\t * @internal\n\t */\n\tprivate static encodeChunk(bytes: Uint8Array, start: number, end: number): string {\n\t\tlet tmp;\n\t\tconst output = [];\n\t\tfor (let i = start; i < end; i += 3) {\n\t\t\ttmp = ((bytes[i] << 16) & 0xff0000) + ((bytes[i + 1] << 8) & 0xff00) + (bytes[i + 2] & 0xff);\n\t\t\toutput.push(Base64.tripletToBase64(tmp));\n\t\t}\n\t\treturn output.join(\"\");\n\t}\n}\n"]}
1
+ {"version":3,"file":"base64.js","sourceRoot":"","sources":["../../../src/encoding/base64.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/* eslint-disable no-bitwise */\n/* eslint-disable no-mixed-operators */\n\nimport { nameof } from \"@twin.org/nameof\";\nimport { GeneralError } from \"../errors/generalError.js\";\nimport { Guards } from \"../utils/guards.js\";\n\n/**\n * Class to help with base64 Encoding/Decoding.\n * Sourced from https://github.com/beatgammit/base64-js.\n */\nexport class Base64 {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Base64>();\n\n\t/**\n\t * Alphabet table for encoding.\n\t * @internal\n\t */\n\tprivate static readonly _LOOKUP: string =\n\t\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\n\t/**\n\t * Alphabet table for decoding.\n\t * @internal\n\t */\n\tprivate static readonly _REVERSE_LOOKUP: { [id: number]: number } = {\n\t\t\"43\": 62,\n\t\t\"45\": 62,\n\t\t\"47\": 63,\n\t\t\"48\": 52,\n\t\t\"49\": 53,\n\t\t\"50\": 54,\n\t\t\"51\": 55,\n\t\t\"52\": 56,\n\t\t\"53\": 57,\n\t\t\"54\": 58,\n\t\t\"55\": 59,\n\t\t\"56\": 60,\n\t\t\"57\": 61,\n\t\t\"65\": 0,\n\t\t\"66\": 1,\n\t\t\"67\": 2,\n\t\t\"68\": 3,\n\t\t\"69\": 4,\n\t\t\"70\": 5,\n\t\t\"71\": 6,\n\t\t\"72\": 7,\n\t\t\"73\": 8,\n\t\t\"74\": 9,\n\t\t\"75\": 10,\n\t\t\"76\": 11,\n\t\t\"77\": 12,\n\t\t\"78\": 13,\n\t\t\"79\": 14,\n\t\t\"80\": 15,\n\t\t\"81\": 16,\n\t\t\"82\": 17,\n\t\t\"83\": 18,\n\t\t\"84\": 19,\n\t\t\"85\": 20,\n\t\t\"86\": 21,\n\t\t\"87\": 22,\n\t\t\"88\": 23,\n\t\t\"89\": 24,\n\t\t\"90\": 25,\n\t\t\"95\": 63,\n\t\t\"97\": 26,\n\t\t\"98\": 27,\n\t\t\"99\": 28,\n\t\t\"100\": 29,\n\t\t\"101\": 30,\n\t\t\"102\": 31,\n\t\t\"103\": 32,\n\t\t\"104\": 33,\n\t\t\"105\": 34,\n\t\t\"106\": 35,\n\t\t\"107\": 36,\n\t\t\"108\": 37,\n\t\t\"109\": 38,\n\t\t\"110\": 39,\n\t\t\"111\": 40,\n\t\t\"112\": 41,\n\t\t\"113\": 42,\n\t\t\"114\": 43,\n\t\t\"115\": 44,\n\t\t\"116\": 45,\n\t\t\"117\": 46,\n\t\t\"118\": 47,\n\t\t\"119\": 48,\n\t\t\"120\": 49,\n\t\t\"121\": 50,\n\t\t\"122\": 51\n\t};\n\n\t/**\n\t * Get the byte length of the data.\n\t * @param base64 The base64 string.\n\t * @returns The byte length of the data.\n\t */\n\tpublic static byteLength(base64: string): number {\n\t\tconst lens = Base64.getLengths(base64);\n\t\treturn Base64.calcByteLength(lens[0], lens[1]);\n\t}\n\n\t/**\n\t * Convert the base 64 string to a byte array.\n\t * @param base64 The base64 string to convert.\n\t * @returns The byte array.\n\t */\n\tpublic static decode(base64: string): Uint8Array {\n\t\tGuards.string(Base64.CLASS_NAME, nameof(base64), base64);\n\n\t\tlet tmp;\n\t\tconst lens = Base64.getLengths(base64);\n\t\tconst validLen = lens[0];\n\t\tconst placeHoldersLen = lens[1];\n\n\t\tconst arr = new Uint8Array(Base64.calcByteLength(validLen, placeHoldersLen));\n\n\t\tlet curByte = 0;\n\n\t\t// if there are placeholders, only get up to the last complete 4 chars\n\t\tconst len = placeHoldersLen > 0 ? validLen - 4 : validLen;\n\n\t\tlet i;\n\t\tfor (i = 0; i < len; i += 4) {\n\t\t\ttmp =\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i)] << 18) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 1)] << 12) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 2)] << 6) |\n\t\t\t\tBase64._REVERSE_LOOKUP[base64.charCodeAt(i + 3)];\n\t\t\tarr[curByte++] = (tmp >> 16) & 0xff;\n\t\t\tarr[curByte++] = (tmp >> 8) & 0xff;\n\t\t\tarr[curByte++] = tmp & 0xff;\n\t\t}\n\n\t\tif (placeHoldersLen === 2) {\n\t\t\ttmp =\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i)] << 2) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 1)] >> 4);\n\t\t\tarr[curByte++] = tmp & 0xff;\n\t\t}\n\n\t\tif (placeHoldersLen === 1) {\n\t\t\ttmp =\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i)] << 10) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 1)] << 4) |\n\t\t\t\t(Base64._REVERSE_LOOKUP[base64.charCodeAt(i + 2)] >> 2);\n\t\t\tarr[curByte++] = (tmp >> 8) & 0xff;\n\t\t\tarr[curByte] = tmp & 0xff;\n\t\t}\n\n\t\treturn arr;\n\t}\n\n\t/**\n\t * Convert a byte array to base 64.\n\t * @param bytes The byte array to convert.\n\t * @returns The data as base64 string.\n\t */\n\tpublic static encode(bytes: Uint8Array): string {\n\t\tGuards.uint8Array(Base64.CLASS_NAME, nameof(bytes), bytes);\n\n\t\tlet tmp;\n\t\tconst len = bytes.length;\n\t\tconst extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes\n\t\tconst parts = [];\n\t\tconst maxChunkLength = 16383; // must be multiple of 3\n\n\t\t// go through the array every three bytes, we'll deal with trailing stuff later\n\t\tfor (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n\t\t\tparts.push(Base64.encodeChunk(bytes, i, Math.min(i + maxChunkLength, len2)));\n\t\t}\n\n\t\t// pad the end with zeros, but make sure to not forget the extra bytes\n\t\tif (extraBytes === 1) {\n\t\t\ttmp = bytes[len - 1];\n\t\t\tparts.push(`${Base64._LOOKUP[tmp >> 2] + Base64._LOOKUP[(tmp << 4) & 0x3f]}==`);\n\t\t} else if (extraBytes === 2) {\n\t\t\ttmp = (bytes[len - 2] << 8) + bytes[len - 1];\n\t\t\tparts.push(\n\t\t\t\t`${Base64._LOOKUP[tmp >> 10] + Base64._LOOKUP[(tmp >> 4) & 0x3f] + Base64._LOOKUP[(tmp << 2) & 0x3f]}=`\n\t\t\t);\n\t\t}\n\n\t\treturn parts.join(\"\");\n\t}\n\n\t/**\n\t * Calculate the byte length.\n\t * @param validLen The valid length.\n\t * @param placeHoldersLen The placeholder length.\n\t * @returns The length.\n\t * @internal\n\t */\n\tprivate static calcByteLength(validLen: number, placeHoldersLen: number): number {\n\t\treturn ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;\n\t}\n\n\t/**\n\t * Get the valid and placeholder lengths from a bas64 string.\n\t * @param base64 The base64 string.\n\t * @returns The lengths.\n\t * @throws GeneralError if the string length is not a multiple of 4.\n\t * @internal\n\t */\n\tprivate static getLengths(base64: string): number[] {\n\t\tconst len = base64.length;\n\n\t\tif (len % 4 > 0) {\n\t\t\tthrow new GeneralError(Base64.CLASS_NAME, \"length4Multiple\", { value: len });\n\t\t}\n\n\t\t// Trim off extra bytes after placeholder bytes are found\n\t\t// See: https://github.com/beatgammit/base64-js/issues/42\n\t\tlet validLen = base64.indexOf(\"=\");\n\t\tif (validLen === -1) {\n\t\t\tvalidLen = len;\n\t\t}\n\n\t\tconst placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4);\n\n\t\treturn [validLen, placeHoldersLen];\n\t}\n\n\t/**\n\t * Convert the triplet to base 64.\n\t * @param num The number to convert.\n\t * @returns The base64 encoding.\n\t * @internal\n\t */\n\tprivate static tripletToBase64(num: number): string {\n\t\treturn (\n\t\t\tBase64._LOOKUP[(num >> 18) & 0x3f] +\n\t\t\tBase64._LOOKUP[(num >> 12) & 0x3f] +\n\t\t\tBase64._LOOKUP[(num >> 6) & 0x3f] +\n\t\t\tBase64._LOOKUP[num & 0x3f]\n\t\t);\n\t}\n\n\t/**\n\t * Encode a chunk.\n\t * @param bytes The byte array.\n\t * @param start The start index in the buffer.\n\t * @param end The end index in the buffer.\n\t * @returns The encoded chunk.\n\t * @internal\n\t */\n\tprivate static encodeChunk(bytes: Uint8Array, start: number, end: number): string {\n\t\tlet tmp;\n\t\tconst output = [];\n\t\tfor (let i = start; i < end; i += 3) {\n\t\t\ttmp = ((bytes[i] << 16) & 0xff0000) + ((bytes[i + 1] << 8) & 0xff00) + (bytes[i + 2] & 0xff);\n\t\t\toutput.push(Base64.tripletToBase64(tmp));\n\t\t}\n\t\treturn output.join(\"\");\n\t}\n}\n"]}
@@ -246,6 +246,7 @@ export class AsyncCache {
246
246
  * @param requestMethod The method to execute.
247
247
  * @param resolve The resolver for the waiter.
248
248
  * @param reject The rejector for the waiter.
249
+ * @internal
249
250
  */
250
251
  static async resolveWaiter(requestMethod, resolve, reject) {
251
252
  try {
@@ -298,6 +299,7 @@ export class AsyncCache {
298
299
  * Deletes the given key from the cache if it has expired and is not in-progress.
299
300
  * Returns true if the entry was evicted.
300
301
  * @param key The cache entry key to check.
302
+ * @returns True if the expired entry was removed; otherwise, false.
301
303
  * @internal
302
304
  */
303
305
  static evictIfExpired(key) {
@@ -312,6 +314,7 @@ export class AsyncCache {
312
314
  /**
313
315
  * Returns the expiry timestamp of the tracked next-expiry entry, or undefined if there
314
316
  * is no tracked entry or it is no longer present in the cache.
317
+ * @returns The next tracked expiry timestamp, if one is still available.
315
318
  * @internal
316
319
  */
317
320
  static getNextExpiry() {
@@ -1 +1 @@
1
- {"version":3,"file":"asyncCache.js","sourceRoot":"","sources":["../../../src/utils/asyncCache.ts"],"names":[],"mappings":"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","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 Nothing.\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 */\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 * @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 * @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":"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","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 Nothing.\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"]}
@@ -128,6 +128,7 @@ export class Mutex {
128
128
  * Returns the Int32Array for the given key, fetching it from the main thread if this
129
129
  * is a worker thread and the key is not yet in the local cache.
130
130
  * @param key The lock key.
131
+ * @param deadline The deadline to use while waiting for the main thread to provide the lock.
131
132
  * @returns The Int32Array backed by a SharedArrayBuffer for this key.
132
133
  * @internal
133
134
  */
@@ -137,6 +138,11 @@ export class Mutex {
137
138
  return locks[key];
138
139
  }
139
140
  const wt = await Mutex.loadWorkerThreads();
141
+ // Re-check after the await: another coroutine that was also waiting on
142
+ // loadWorkerThreads() may have allocated the buffer while we yielded.
143
+ if (!Is.empty(locks[key])) {
144
+ return locks[key];
145
+ }
140
146
  if (Is.empty(wt) || wt.isMainThread) {
141
147
  // Main thread, fork-mode process, or browser: own the registry entry.
142
148
  locks[key] = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
@@ -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 * 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 * 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, default is 5000.\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\n\t\tconst timeoutMs = options?.timeoutMs ?? 5000;\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 to the worker before notifying so that it is guaranteed\n\t\t// to be in port1's receive queue when Atomics.wait returns on the worker side.\n\t\tmsg.port.postMessage({ buffer: locks[msg.key].buffer });\n\t\tAtomics.notify(new Int32Array(msg.signal), 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 * @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\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 posts the response and fires Atomics.notify.\n\t\t\t// The response is guaranteed to be in port1's queue at this point because\n\t\t\t// port.postMessage executes before Atomics.notify on the main thread.\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 * 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 * 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, default is 5000.\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\n\t\tconst timeoutMs = options?.timeoutMs ?? 5000;\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 to the worker before notifying so that it is guaranteed\n\t\t// to be in port1's receive queue when Atomics.wait returns on the worker side.\n\t\tmsg.port.postMessage({ buffer: locks[msg.key].buffer });\n\t\tAtomics.notify(new Int32Array(msg.signal), 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 posts the response and fires Atomics.notify.\n\t\t\t// The response is guaranteed to be in port1's queue at this point because\n\t\t\t// port.postMessage executes before Atomics.notify on the main thread.\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"]}
@@ -40,11 +40,4 @@ export declare class AsyncCache {
40
40
  * Perform a cleanup of the expired entries in the cache.
41
41
  */
42
42
  static cleanupExpired(): void;
43
- /**
44
- * Resolve a waiter by re-running its request method safely.
45
- * @param requestMethod The method to execute.
46
- * @param resolve The resolver for the waiter.
47
- * @param reject The rejector for the waiter.
48
- */
49
- private static resolveWaiter;
50
43
  }
package/docs/changelog.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.4-next.5](https://github.com/iotaledger/twin-framework/compare/core-v0.0.4-next.4...core-v0.0.4-next.5) (2026-06-04)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **core:** Synchronize repo versions
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/nameof bumped from 0.0.4-next.4 to 0.0.4-next.5
16
+ * devDependencies
17
+ * @twin.org/nameof-transformer bumped from 0.0.4-next.4 to 0.0.4-next.5
18
+ * @twin.org/nameof-vitest-plugin bumped from 0.0.4-next.4 to 0.0.4-next.5
19
+
20
+ ## [0.0.4-next.4](https://github.com/iotaledger/twin-framework/compare/core-v0.0.4-next.3...core-v0.0.4-next.4) (2026-06-02)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * 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))
26
+
27
+
28
+ ### Dependencies
29
+
30
+ * The following workspace dependencies were updated
31
+ * dependencies
32
+ * @twin.org/nameof bumped from 0.0.4-next.3 to 0.0.4-next.4
33
+ * devDependencies
34
+ * @twin.org/nameof-transformer bumped from 0.0.4-next.3 to 0.0.4-next.4
35
+ * @twin.org/nameof-vitest-plugin bumped from 0.0.4-next.3 to 0.0.4-next.4
36
+
3
37
  ## [0.0.4-next.3](https://github.com/iotaledger/twin-framework/compare/core-v0.0.4-next.2...core-v0.0.4-next.3) (2026-05-28)
4
38
 
5
39
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@twin.org/core",
3
- "version": "0.0.4-next.3",
3
+ "version": "0.0.4-next.5",
4
4
  "description": "Helper methods/classes for data type checking/validation/guarding/error handling",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/iotaledger/framework.git",
7
+ "url": "git+https://github.com/iotaledger/twin-framework.git",
8
8
  "directory": "packages/core"
9
9
  },
10
10
  "author": "martyn.janes@iota.org",
@@ -14,7 +14,7 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/nameof": "0.0.4-next.3",
17
+ "@twin.org/nameof": "0.0.4-next.5",
18
18
  "intl-messageformat": "11.2.6",
19
19
  "rfc6902": "5.2.0"
20
20
  },
@@ -45,7 +45,7 @@
45
45
  "utilities"
46
46
  ],
47
47
  "bugs": {
48
- "url": "git+https://github.com/iotaledger/framework/issues"
48
+ "url": "git+https://github.com/iotaledger/twin-framework/issues"
49
49
  },
50
50
  "homepage": "https://twindev.org"
51
51
  }