@twin.org/core 0.0.3-next.8 → 0.0.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.
Files changed (163) hide show
  1. package/README.md +1 -9
  2. package/dist/es/encoding/base32.js +1 -1
  3. package/dist/es/encoding/base32.js.map +1 -1
  4. package/dist/es/encoding/base64.js +1 -1
  5. package/dist/es/encoding/base64.js.map +1 -1
  6. package/dist/es/errors/alreadyExistsError.js +1 -1
  7. package/dist/es/errors/alreadyExistsError.js.map +1 -1
  8. package/dist/es/errors/baseError.js +1 -1
  9. package/dist/es/errors/baseError.js.map +1 -1
  10. package/dist/es/errors/conflictError.js +1 -1
  11. package/dist/es/errors/conflictError.js.map +1 -1
  12. package/dist/es/errors/generalError.js +1 -1
  13. package/dist/es/errors/generalError.js.map +1 -1
  14. package/dist/es/errors/guardError.js +1 -1
  15. package/dist/es/errors/guardError.js.map +1 -1
  16. package/dist/es/errors/notFoundError.js +1 -1
  17. package/dist/es/errors/notFoundError.js.map +1 -1
  18. package/dist/es/errors/notSupportedError.js +1 -1
  19. package/dist/es/errors/notSupportedError.js.map +1 -1
  20. package/dist/es/errors/unauthorizedError.js +2 -2
  21. package/dist/es/errors/unauthorizedError.js.map +1 -1
  22. package/dist/es/errors/unprocessableError.js +1 -1
  23. package/dist/es/errors/unprocessableError.js.map +1 -1
  24. package/dist/es/factories/factory.js +41 -0
  25. package/dist/es/factories/factory.js.map +1 -1
  26. package/dist/es/helpers/arrayHelper.js +2 -0
  27. package/dist/es/helpers/arrayHelper.js.map +1 -1
  28. package/dist/es/helpers/errorHelper.js +18 -14
  29. package/dist/es/helpers/errorHelper.js.map +1 -1
  30. package/dist/es/helpers/jsonHelper.js.map +1 -1
  31. package/dist/es/helpers/objectHelper.js +12 -36
  32. package/dist/es/helpers/objectHelper.js.map +1 -1
  33. package/dist/es/helpers/randomHelper.js +50 -2
  34. package/dist/es/helpers/randomHelper.js.map +1 -1
  35. package/dist/es/helpers/stringHelper.js +11 -0
  36. package/dist/es/helpers/stringHelper.js.map +1 -1
  37. package/dist/es/index.js +8 -1
  38. package/dist/es/index.js.map +1 -1
  39. package/dist/es/models/IComponent.js +0 -2
  40. package/dist/es/models/IComponent.js.map +1 -1
  41. package/dist/es/models/IError.js.map +1 -1
  42. package/dist/es/models/IHealth.js +2 -0
  43. package/dist/es/models/IHealth.js.map +1 -0
  44. package/dist/es/models/IMutexWorkerMessage.js +2 -0
  45. package/dist/es/models/IMutexWorkerMessage.js.map +1 -0
  46. package/dist/es/models/healthStatus.js +21 -0
  47. package/dist/es/models/healthStatus.js.map +1 -0
  48. package/dist/es/models/mutexMessageTypes.js +13 -0
  49. package/dist/es/models/mutexMessageTypes.js.map +1 -0
  50. package/dist/es/types/objectOrArray.js.map +1 -0
  51. package/dist/es/types/singleOccurrenceArray.js +2 -0
  52. package/dist/es/types/singleOccurrenceArray.js.map +1 -0
  53. package/dist/es/types/singleOccurrenceArrayDepthHelper.js +2 -0
  54. package/dist/es/types/singleOccurrenceArrayDepthHelper.js.map +1 -0
  55. package/dist/es/types/urn.js +1 -2
  56. package/dist/es/types/urn.js.map +1 -1
  57. package/dist/es/utils/asyncCache.js +236 -88
  58. package/dist/es/utils/asyncCache.js.map +1 -1
  59. package/dist/es/utils/guards.js +16 -0
  60. package/dist/es/utils/guards.js.map +1 -1
  61. package/dist/es/utils/i18n.js.map +1 -1
  62. package/dist/es/utils/is.js +16 -0
  63. package/dist/es/utils/is.js.map +1 -1
  64. package/dist/es/utils/mutex.js +185 -0
  65. package/dist/es/utils/mutex.js.map +1 -0
  66. package/dist/types/encoding/base32.d.ts +1 -1
  67. package/dist/types/errors/alreadyExistsError.d.ts +1 -1
  68. package/dist/types/errors/baseError.d.ts +1 -1
  69. package/dist/types/errors/conflictError.d.ts +1 -1
  70. package/dist/types/errors/generalError.d.ts +1 -1
  71. package/dist/types/errors/guardError.d.ts +1 -1
  72. package/dist/types/errors/notFoundError.d.ts +1 -1
  73. package/dist/types/errors/notSupportedError.d.ts +1 -1
  74. package/dist/types/errors/unauthorizedError.d.ts +2 -2
  75. package/dist/types/errors/unprocessableError.d.ts +1 -1
  76. package/dist/types/factories/factory.d.ts +23 -1
  77. package/dist/types/helpers/arrayHelper.d.ts +1 -8
  78. package/dist/types/helpers/errorHelper.d.ts +11 -4
  79. package/dist/types/helpers/objectHelper.d.ts +18 -14
  80. package/dist/types/helpers/randomHelper.d.ts +16 -0
  81. package/dist/types/helpers/stringHelper.d.ts +6 -0
  82. package/dist/types/index.d.ts +8 -1
  83. package/dist/types/models/IComponent.d.ts +12 -0
  84. package/dist/types/models/IError.d.ts +1 -1
  85. package/dist/types/models/IHealth.d.ts +32 -0
  86. package/dist/types/models/IMutexWorkerMessage.d.ts +23 -0
  87. package/dist/types/models/healthStatus.d.ts +21 -0
  88. package/dist/types/models/mutexMessageTypes.d.ts +13 -0
  89. package/dist/types/types/singleOccurrenceArray.d.ts +6 -0
  90. package/dist/types/types/singleOccurrenceArrayDepthHelper.d.ts +4 -0
  91. package/dist/types/utils/asyncCache.d.ts +11 -3
  92. package/dist/types/utils/guards.d.ts +10 -1
  93. package/dist/types/utils/is.d.ts +7 -0
  94. package/dist/types/utils/mutex.d.ts +53 -0
  95. package/docs/changelog.md +945 -216
  96. package/docs/examples.md +308 -1
  97. package/docs/reference/classes/AlreadyExistsError.md +36 -36
  98. package/docs/reference/classes/ArrayHelper.md +10 -44
  99. package/docs/reference/classes/AsyncCache.md +13 -12
  100. package/docs/reference/classes/Base32.md +4 -4
  101. package/docs/reference/classes/Base58.md +3 -3
  102. package/docs/reference/classes/Base64.md +4 -4
  103. package/docs/reference/classes/Base64Url.md +3 -3
  104. package/docs/reference/classes/BaseError.md +35 -35
  105. package/docs/reference/classes/BitString.md +6 -6
  106. package/docs/reference/classes/Coerce.md +11 -11
  107. package/docs/reference/classes/Compression.md +3 -3
  108. package/docs/reference/classes/ConflictError.md +36 -36
  109. package/docs/reference/classes/Converter.md +18 -18
  110. package/docs/reference/classes/EnvHelper.md +1 -1
  111. package/docs/reference/classes/ErrorHelper.md +20 -10
  112. package/docs/reference/classes/Factory.md +112 -18
  113. package/docs/reference/classes/FilenameHelper.md +1 -1
  114. package/docs/reference/classes/GeneralError.md +36 -36
  115. package/docs/reference/classes/GuardError.md +36 -36
  116. package/docs/reference/classes/Guards.md +72 -30
  117. package/docs/reference/classes/HexHelper.md +6 -6
  118. package/docs/reference/classes/I18n.md +14 -14
  119. package/docs/reference/classes/Is.md +67 -39
  120. package/docs/reference/classes/JsonHelper.md +10 -10
  121. package/docs/reference/classes/Mutex.md +128 -0
  122. package/docs/reference/classes/NotFoundError.md +36 -36
  123. package/docs/reference/classes/NotImplementedError.md +35 -35
  124. package/docs/reference/classes/NotSupportedError.md +36 -36
  125. package/docs/reference/classes/NumberHelper.md +2 -2
  126. package/docs/reference/classes/ObjectHelper.md +135 -73
  127. package/docs/reference/classes/RandomHelper.md +53 -1
  128. package/docs/reference/classes/SharedStore.md +3 -3
  129. package/docs/reference/classes/StringHelper.md +45 -23
  130. package/docs/reference/classes/Uint8ArrayHelper.md +1 -1
  131. package/docs/reference/classes/UnauthorizedError.md +37 -37
  132. package/docs/reference/classes/UnprocessableError.md +36 -36
  133. package/docs/reference/classes/Url.md +8 -8
  134. package/docs/reference/classes/Urn.md +24 -24
  135. package/docs/reference/classes/Validation.md +25 -25
  136. package/docs/reference/classes/ValidationError.md +35 -35
  137. package/docs/reference/index.md +9 -0
  138. package/docs/reference/interfaces/IComponent.md +40 -4
  139. package/docs/reference/interfaces/IError.md +11 -11
  140. package/docs/reference/interfaces/IHealth.md +55 -0
  141. package/docs/reference/interfaces/II18nShared.md +4 -4
  142. package/docs/reference/interfaces/IKeyValue.md +2 -2
  143. package/docs/reference/interfaces/ILabelledValue.md +2 -2
  144. package/docs/reference/interfaces/ILocale.md +2 -2
  145. package/docs/reference/interfaces/ILocaleDictionary.md +1 -1
  146. package/docs/reference/interfaces/ILocalesIndex.md +1 -1
  147. package/docs/reference/interfaces/IMutexWorkerMessage.md +35 -0
  148. package/docs/reference/interfaces/IPatchOperation.md +6 -6
  149. package/docs/reference/interfaces/IUrlParts.md +9 -9
  150. package/docs/reference/interfaces/IValidationFailure.md +4 -4
  151. package/docs/reference/type-aliases/HealthStatus.md +5 -0
  152. package/docs/reference/type-aliases/MutexMessageTypes.md +5 -0
  153. package/docs/reference/type-aliases/SingleOccurrenceArray.md +15 -0
  154. package/docs/reference/type-aliases/SingleOccurrenceArrayDepthHelper.md +19 -0
  155. package/docs/reference/variables/CoerceType.md +10 -10
  156. package/docs/reference/variables/CompressionType.md +2 -2
  157. package/docs/reference/variables/HealthStatus.md +25 -0
  158. package/docs/reference/variables/MutexMessageTypes.md +13 -0
  159. package/locales/en.json +11 -2
  160. package/package.json +6 -6
  161. package/dist/es/models/objectOrArray.js.map +0 -1
  162. /package/dist/es/{models → types}/objectOrArray.js +0 -0
  163. /package/dist/types/{models → types}/objectOrArray.d.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IHealth.js","sourceRoot":"","sources":["../../../src/models/IHealth.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { HealthStatus } from \"./healthStatus.js\";\n\n/**\n * Provides health information for a component.\n */\nexport interface IHealth {\n\t/**\n\t * The source of the health information.\n\t */\n\tsource: string;\n\n\t/**\n\t * The description of the component as an i18n key.\n\t */\n\tdescription?: string;\n\n\t/**\n\t * The overall status of the component, the entries can also report their own health.\n\t */\n\tstatus: HealthStatus;\n\n\t/**\n\t * The message for the status if there are further details to provide as an i18n key.\n\t */\n\tmessage?: string;\n\n\t/**\n\t * Data to substitute in the i18n key for the message.\n\t */\n\tdata?: { [id: string]: unknown };\n\n\t/**\n\t * The grouped child components, if any.\n\t */\n\tgrouped?: IHealth[];\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IMutexWorkerMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IMutexWorkerMessage.js","sourceRoot":"","sources":["../../../src/models/IMutexWorkerMessage.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { MessagePort } from \"node:worker_threads\";\nimport type { MutexMessageTypes } from \"./mutexMessageTypes.js\";\n\n/**\n * Message sent from a worker thread to the main thread to request a SharedArrayBuffer for a given mutex key.\n */\nexport interface IMutexWorkerMessage {\n\t/**\n\t * The message type.\n\t */\n\ttype: typeof MutexMessageTypes.GetBuffer;\n\n\t/**\n\t * The mutex key for which the buffer is requested.\n\t */\n\tkey: string;\n\n\t/**\n\t * The SharedArrayBuffer for the mutex, sent from the worker to the main thread.\n\t */\n\tsignal: SharedArrayBuffer;\n\n\t/**\n\t * The MessagePort for the main thread to respond with the buffer, sent from the worker to the main thread.\n\t */\n\tport: MessagePort;\n}\n"]}
@@ -0,0 +1,21 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ /**
4
+ * The health status of the component.
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/naming-convention
7
+ export const HealthStatus = {
8
+ /**
9
+ * OK.
10
+ */
11
+ Ok: "ok",
12
+ /**
13
+ * Warning.
14
+ */
15
+ Warning: "warning",
16
+ /**
17
+ * Error.
18
+ */
19
+ Error: "error"
20
+ };
21
+ //# sourceMappingURL=healthStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healthStatus.js","sourceRoot":"","sources":["../../../src/models/healthStatus.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AAEvC;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B;;OAEG;IACH,EAAE,EAAE,IAAI;IAER;;OAEG;IACH,OAAO,EAAE,SAAS;IAElB;;OAEG;IACH,KAAK,EAAE,OAAO;CACL,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * The health status of the component.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const HealthStatus = {\n\t/**\n\t * OK.\n\t */\n\tOk: \"ok\",\n\n\t/**\n\t * Warning.\n\t */\n\tWarning: \"warning\",\n\n\t/**\n\t * Error.\n\t */\n\tError: \"error\"\n} as const;\n\n/**\n * The health status of the component.\n */\nexport type HealthStatus = (typeof HealthStatus)[keyof typeof HealthStatus];\n"]}
@@ -0,0 +1,13 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ /**
4
+ * Mutex message types.
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/naming-convention
7
+ export const MutexMessageTypes = {
8
+ /**
9
+ * Get buffer.
10
+ */
11
+ GetBuffer: "twin:mutex:getBuffer"
12
+ };
13
+ //# sourceMappingURL=mutexMessageTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutexMessageTypes.js","sourceRoot":"","sources":["../../../src/models/mutexMessageTypes.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AAEvC;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC;;OAEG;IACH,SAAS,EAAE,sBAAsB;CACxB,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Mutex message types.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const MutexMessageTypes = {\n\t/**\n\t * Get buffer.\n\t */\n\tGetBuffer: \"twin:mutex:getBuffer\"\n} as const;\n\n/**\n * Mutex message types.\n */\nexport type MutexMessageTypes = (typeof MutexMessageTypes)[keyof typeof MutexMessageTypes];\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objectOrArray.js","sourceRoot":"","sources":["../../../src/types/objectOrArray.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Object or array data type\n */\nexport type ObjectOrArray<T = unknown> = T | T[];\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=singleOccurrenceArray.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleOccurrenceArray.js","sourceRoot":"","sources":["../../../src/types/singleOccurrenceArray.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { SingleOccurrenceArrayDepthHelper } from \"./singleOccurrenceArrayDepthHelper.js\";\n\n/**\n * Utility type to create a non-empty array with values of type T and exactly one value of type U.\n */\nexport type SingleOccurrenceArray<T = unknown, U = never> = SingleOccurrenceArrayDepthHelper<\n\tT,\n\tU,\n\t[]\n>;\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=singleOccurrenceArrayDepthHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleOccurrenceArrayDepthHelper.js","sourceRoot":"","sources":["../../../src/types/singleOccurrenceArrayDepthHelper.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Helper with bounded recursion depth to keep type instantiation tractable.\n */\nexport type SingleOccurrenceArrayDepthHelper<T, U, Depth extends 0[]> = Depth[\"length\"] extends 16\n\t? [U, ...T[]]\n\t: [U, ...T[]] | [T, ...SingleOccurrenceArrayDepthHelper<T, U, [0, ...Depth]>];\n"]}
@@ -1,6 +1,5 @@
1
1
  import { GuardError } from "../errors/guardError.js";
2
2
  import { RandomHelper } from "../helpers/randomHelper.js";
3
- import { Converter } from "../utils/converter.js";
4
3
  import { Guards } from "../utils/guards.js";
5
4
  import { Is } from "../utils/is.js";
6
5
  /**
@@ -40,7 +39,7 @@ export class Urn {
40
39
  * @returns A new Id in URN format.
41
40
  */
42
41
  static generateRandom(namespace) {
43
- return new Urn(namespace, Converter.bytesToHex(RandomHelper.generate(32)));
42
+ return new Urn(namespace, RandomHelper.generateUuidV7("compact"));
44
43
  }
45
44
  /**
46
45
  * Does the provided urn match the namespace.
@@ -1 +1 @@
1
- {"version":3,"file":"urn.js","sourceRoot":"","sources":["../../../src/types/urn.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 { GuardError } from \"../errors/guardError.js\";\nimport { RandomHelper } from \"../helpers/randomHelper.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\nimport { Converter } from \"../utils/converter.js\";\nimport { Guards } from \"../utils/guards.js\";\nimport { Is } from \"../utils/is.js\";\n\n/**\n * Class to help with urns.\n */\nexport class Urn {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Urn>();\n\n\t/**\n\t * The specific part of the namespace.\n\t * @internal\n\t */\n\tprivate readonly _urnParts: string[];\n\n\t/**\n\t * Create a new instance of Urn.\n\t * @param namespaceIdentifier The identifier for the namespace.\n\t * @param namespaceSpecific The specific part of the namespace.\n\t */\n\tconstructor(namespaceIdentifier: string, namespaceSpecific: string | string[]) {\n\t\tGuards.stringValue(Urn.CLASS_NAME, nameof(namespaceIdentifier), namespaceIdentifier);\n\n\t\t// Strip leading and trailing colons\n\t\tthis._urnParts = [this.stripColons(namespaceIdentifier)];\n\n\t\tif (Is.array(namespaceSpecific)) {\n\t\t\tGuards.arrayValue(Urn.CLASS_NAME, nameof(namespaceSpecific), namespaceSpecific);\n\t\t\tthis._urnParts.push(...namespaceSpecific);\n\t\t} else {\n\t\t\tGuards.stringValue(Urn.CLASS_NAME, nameof(namespaceSpecific), namespaceSpecific);\n\t\t\tthis._urnParts.push(...this.stripColons(namespaceSpecific).split(\":\"));\n\t\t}\n\t}\n\n\t/**\n\t * Generate a random identifier with 32 byte id.\n\t * @param namespace The prefix for the urn.\n\t * @returns A new Id in URN format.\n\t */\n\tpublic static generateRandom(namespace: string): Urn {\n\t\treturn new Urn(namespace, Converter.bytesToHex(RandomHelper.generate(32)));\n\t}\n\n\t/**\n\t * Does the provided urn match the namespace.\n\t * @param urn The urn to check.\n\t * @param namespace The namespace to match.\n\t * @returns True if the namespace matches.\n\t */\n\tpublic static hasNamespace(urn: string, namespace: string): boolean {\n\t\tif (!Is.stringValue(urn)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (urn.startsWith(\"urn:\")) {\n\t\t\turn = urn.slice(4);\n\t\t}\n\n\t\tif (urn.length === namespace.length) {\n\t\t\treturn urn === namespace;\n\t\t}\n\n\t\treturn urn.startsWith(`${namespace}:`);\n\t}\n\n\t/**\n\t * Try and parse a string into the urn parts.\n\t * @param urn The urn to parse.\n\t * @returns The formatted urn or undefined if the value is not a urn.\n\t */\n\tpublic static tryParseExact(urn: unknown): Urn | undefined {\n\t\tif (!Is.stringValue(urn)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst parts = urn.split(\":\");\n\n\t\tif (parts[0] === \"urn\") {\n\t\t\tparts.shift();\n\t\t}\n\n\t\tif (parts.length < 2) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!/[\\da-z][\\da-z-]{0,31}/.test(parts[0])) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let i = 1; i < parts.length; i++) {\n\t\t\tif (!/[\\d!#$%'()*+,./:;=?@_a-z-]+/.test(parts[i])) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\treturn new Urn(parts[0], parts.slice(1));\n\t}\n\n\t/**\n\t * Construct a urn from a string that has already been validated.\n\t * @param urn The urn to parse.\n\t * @returns The formatted urn.\n\t */\n\tpublic static fromValidString(urn: string): Urn {\n\t\tconst parts = urn.split(\":\");\n\n\t\tif (parts[0] === \"urn\") {\n\t\t\tparts.shift();\n\t\t}\n\n\t\treturn new Urn(parts[0], parts.slice(1));\n\t}\n\n\t/**\n\t * Add a urn: prefix if there isn't one already.\n\t * @param urn The urn string to add a prefix to.\n\t * @returns The urn with a prefix.\n\t */\n\tpublic static addPrefix(urn: unknown): string | undefined {\n\t\tif (Is.stringValue(urn)) {\n\t\t\tif (urn.startsWith(\"urn:\")) {\n\t\t\t\treturn urn;\n\t\t\t}\n\t\t\treturn `urn:${urn}`;\n\t\t}\n\t}\n\n\t/**\n\t * Parse a string into the urn parts.\n\t * @param source The source of the error.\n\t * @param property The name of the property.\n\t * @param value The urn to parse.\n\t * @throws GuardError If the value does not match the assertion.\n\t */\n\tpublic static guard(source: string, property: string, value: unknown): asserts value is string {\n\t\tGuards.stringValue(source, property, value);\n\n\t\tconst result = Urn.tryParseExact(value);\n\n\t\tif (!result) {\n\t\t\tthrow new GuardError(source, \"guard.urn\", property, value);\n\t\t}\n\t}\n\n\t/**\n\t * Validate a string as a Urn.\n\t * @param property Throw an exception if the urn property is invalid.\n\t * @param value The urn to parse.\n\t * @param failures The list of failures to add to.\n\t * @param fieldNameResource The optional human readable name for the field as an i18 resource.\n\t * @returns The formatted urn.\n\t */\n\tpublic static validate(\n\t\tproperty: string,\n\t\tvalue: unknown,\n\t\tfailures: IValidationFailure[],\n\t\tfieldNameResource?: string\n\t): value is string {\n\t\tif (!Is.stringValue(value)) {\n\t\t\tfailures.push({\n\t\t\t\tproperty,\n\t\t\t\treason: \"validation.beNotEmpty\",\n\t\t\t\tproperties: { fieldName: fieldNameResource ?? \"validation.defaultFieldName\", value }\n\t\t\t});\n\n\t\t\treturn false;\n\t\t}\n\n\t\tconst result = Urn.tryParseExact(value);\n\n\t\tif (Is.undefined(result)) {\n\t\t\tfailures.push({\n\t\t\t\tproperty,\n\t\t\t\treason: \"validation.beUrn\",\n\t\t\t\tproperties: { fieldName: fieldNameResource ?? \"validation.defaultFieldName\", value }\n\t\t\t});\n\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Get the parts.\n\t * @param startIndex The index to start from, defaults to 0.\n\t * @returns The parts.\n\t */\n\tpublic parts(startIndex: number = 0): string[] {\n\t\treturn this._urnParts.slice(startIndex);\n\t}\n\n\t/**\n\t * Get the namespace identifier.\n\t * @returns The namespace identifier.\n\t */\n\tpublic namespaceIdentifier(): string {\n\t\treturn this._urnParts[0];\n\t}\n\n\t/**\n\t * Get the namespace method, the first component after the identifier.\n\t * @returns The namespace method.\n\t */\n\tpublic namespaceMethod(): string {\n\t\treturn this._urnParts.length > 1 ? this._urnParts[1] : \"\";\n\t}\n\n\t/**\n\t * Get the namespace specific parts.\n\t * @param startIndex The index to start from, defaults to 0.\n\t * @returns The namespace specific parts.\n\t */\n\tpublic namespaceSpecificParts(startIndex: number = 0): string[] {\n\t\treturn this._urnParts.length > 1 ? this._urnParts.slice(startIndex + 1) : [];\n\t}\n\n\t/**\n\t * Get the namespace specific.\n\t * @param startIndex The index to start from, defaults to 0.\n\t * @returns The namespace specific.\n\t */\n\tpublic namespaceSpecific(startIndex: number = 0): string {\n\t\treturn this._urnParts.length > 1 ? this._urnParts.slice(startIndex + 1).join(\":\") : \"\";\n\t}\n\n\t/**\n\t * Convert the parts in to a full string.\n\t * @param omitPrefix Omit the urn: prefix from the string.\n\t * @returns The formatted urn.\n\t */\n\tpublic toString(omitPrefix: boolean = true): string {\n\t\treturn omitPrefix ? this._urnParts.join(\":\") : `urn:${this._urnParts.join(\":\")}`;\n\t}\n\n\t/**\n\t * Strip the leading and trailing colons from a string.\n\t * @param val The value to strip.\n\t * @returns The stripped string.\n\t * @internal\n\t */\n\tprivate stripColons(val: string): string {\n\t\treturn val.replace(/^:?(.*?):?$/, \"$1\");\n\t}\n}\n"]}
1
+ {"version":3,"file":"urn.js","sourceRoot":"","sources":["../../../src/types/urn.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 { GuardError } from \"../errors/guardError.js\";\nimport { RandomHelper } from \"../helpers/randomHelper.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\nimport { Guards } from \"../utils/guards.js\";\nimport { Is } from \"../utils/is.js\";\n\n/**\n * Class to help with urns.\n */\nexport class Urn {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Urn>();\n\n\t/**\n\t * The specific part of the namespace.\n\t * @internal\n\t */\n\tprivate readonly _urnParts: string[];\n\n\t/**\n\t * Create a new instance of Urn.\n\t * @param namespaceIdentifier The identifier for the namespace.\n\t * @param namespaceSpecific The specific part of the namespace.\n\t */\n\tconstructor(namespaceIdentifier: string, namespaceSpecific: string | string[]) {\n\t\tGuards.stringValue(Urn.CLASS_NAME, nameof(namespaceIdentifier), namespaceIdentifier);\n\n\t\t// Strip leading and trailing colons\n\t\tthis._urnParts = [this.stripColons(namespaceIdentifier)];\n\n\t\tif (Is.array(namespaceSpecific)) {\n\t\t\tGuards.arrayValue(Urn.CLASS_NAME, nameof(namespaceSpecific), namespaceSpecific);\n\t\t\tthis._urnParts.push(...namespaceSpecific);\n\t\t} else {\n\t\t\tGuards.stringValue(Urn.CLASS_NAME, nameof(namespaceSpecific), namespaceSpecific);\n\t\t\tthis._urnParts.push(...this.stripColons(namespaceSpecific).split(\":\"));\n\t\t}\n\t}\n\n\t/**\n\t * Generate a random identifier with 32 byte id.\n\t * @param namespace The prefix for the urn.\n\t * @returns A new Id in URN format.\n\t */\n\tpublic static generateRandom(namespace: string): Urn {\n\t\treturn new Urn(namespace, RandomHelper.generateUuidV7(\"compact\"));\n\t}\n\n\t/**\n\t * Does the provided urn match the namespace.\n\t * @param urn The urn to check.\n\t * @param namespace The namespace to match.\n\t * @returns True if the namespace matches.\n\t */\n\tpublic static hasNamespace(urn: string, namespace: string): boolean {\n\t\tif (!Is.stringValue(urn)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (urn.startsWith(\"urn:\")) {\n\t\t\turn = urn.slice(4);\n\t\t}\n\n\t\tif (urn.length === namespace.length) {\n\t\t\treturn urn === namespace;\n\t\t}\n\n\t\treturn urn.startsWith(`${namespace}:`);\n\t}\n\n\t/**\n\t * Try and parse a string into the urn parts.\n\t * @param urn The urn to parse.\n\t * @returns The formatted urn or undefined if the value is not a urn.\n\t */\n\tpublic static tryParseExact(urn: unknown): Urn | undefined {\n\t\tif (!Is.stringValue(urn)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst parts = urn.split(\":\");\n\n\t\tif (parts[0] === \"urn\") {\n\t\t\tparts.shift();\n\t\t}\n\n\t\tif (parts.length < 2) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!/[\\da-z][\\da-z-]{0,31}/.test(parts[0])) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let i = 1; i < parts.length; i++) {\n\t\t\tif (!/[\\d!#$%'()*+,./:;=?@_a-z-]+/.test(parts[i])) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\treturn new Urn(parts[0], parts.slice(1));\n\t}\n\n\t/**\n\t * Construct a urn from a string that has already been validated.\n\t * @param urn The urn to parse.\n\t * @returns The formatted urn.\n\t */\n\tpublic static fromValidString(urn: string): Urn {\n\t\tconst parts = urn.split(\":\");\n\n\t\tif (parts[0] === \"urn\") {\n\t\t\tparts.shift();\n\t\t}\n\n\t\treturn new Urn(parts[0], parts.slice(1));\n\t}\n\n\t/**\n\t * Add a urn: prefix if there isn't one already.\n\t * @param urn The urn string to add a prefix to.\n\t * @returns The urn with a prefix.\n\t */\n\tpublic static addPrefix(urn: unknown): string | undefined {\n\t\tif (Is.stringValue(urn)) {\n\t\t\tif (urn.startsWith(\"urn:\")) {\n\t\t\t\treturn urn;\n\t\t\t}\n\t\t\treturn `urn:${urn}`;\n\t\t}\n\t}\n\n\t/**\n\t * Parse a string into the urn parts.\n\t * @param source The source of the error.\n\t * @param property The name of the property.\n\t * @param value The urn to parse.\n\t * @throws GuardError If the value does not match the assertion.\n\t */\n\tpublic static guard(source: string, property: string, value: unknown): asserts value is string {\n\t\tGuards.stringValue(source, property, value);\n\n\t\tconst result = Urn.tryParseExact(value);\n\n\t\tif (!result) {\n\t\t\tthrow new GuardError(source, \"guard.urn\", property, value);\n\t\t}\n\t}\n\n\t/**\n\t * Validate a string as a Urn.\n\t * @param property Throw an exception if the urn property is invalid.\n\t * @param value The urn to parse.\n\t * @param failures The list of failures to add to.\n\t * @param fieldNameResource The optional human readable name for the field as an i18 resource.\n\t * @returns The formatted urn.\n\t */\n\tpublic static validate(\n\t\tproperty: string,\n\t\tvalue: unknown,\n\t\tfailures: IValidationFailure[],\n\t\tfieldNameResource?: string\n\t): value is string {\n\t\tif (!Is.stringValue(value)) {\n\t\t\tfailures.push({\n\t\t\t\tproperty,\n\t\t\t\treason: \"validation.beNotEmpty\",\n\t\t\t\tproperties: { fieldName: fieldNameResource ?? \"validation.defaultFieldName\", value }\n\t\t\t});\n\n\t\t\treturn false;\n\t\t}\n\n\t\tconst result = Urn.tryParseExact(value);\n\n\t\tif (Is.undefined(result)) {\n\t\t\tfailures.push({\n\t\t\t\tproperty,\n\t\t\t\treason: \"validation.beUrn\",\n\t\t\t\tproperties: { fieldName: fieldNameResource ?? \"validation.defaultFieldName\", value }\n\t\t\t});\n\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Get the parts.\n\t * @param startIndex The index to start from, defaults to 0.\n\t * @returns The parts.\n\t */\n\tpublic parts(startIndex: number = 0): string[] {\n\t\treturn this._urnParts.slice(startIndex);\n\t}\n\n\t/**\n\t * Get the namespace identifier.\n\t * @returns The namespace identifier.\n\t */\n\tpublic namespaceIdentifier(): string {\n\t\treturn this._urnParts[0];\n\t}\n\n\t/**\n\t * Get the namespace method, the first component after the identifier.\n\t * @returns The namespace method.\n\t */\n\tpublic namespaceMethod(): string {\n\t\treturn this._urnParts.length > 1 ? this._urnParts[1] : \"\";\n\t}\n\n\t/**\n\t * Get the namespace specific parts.\n\t * @param startIndex The index to start from, defaults to 0.\n\t * @returns The namespace specific parts.\n\t */\n\tpublic namespaceSpecificParts(startIndex: number = 0): string[] {\n\t\treturn this._urnParts.length > 1 ? this._urnParts.slice(startIndex + 1) : [];\n\t}\n\n\t/**\n\t * Get the namespace specific.\n\t * @param startIndex The index to start from, defaults to 0.\n\t * @returns The namespace specific.\n\t */\n\tpublic namespaceSpecific(startIndex: number = 0): string {\n\t\treturn this._urnParts.length > 1 ? this._urnParts.slice(startIndex + 1).join(\":\") : \"\";\n\t}\n\n\t/**\n\t * Convert the parts in to a full string.\n\t * @param omitPrefix Omit the urn: prefix from the string.\n\t * @returns The formatted urn.\n\t */\n\tpublic toString(omitPrefix: boolean = true): string {\n\t\treturn omitPrefix ? this._urnParts.join(\":\") : `urn:${this._urnParts.join(\":\")}`;\n\t}\n\n\t/**\n\t * Strip the leading and trailing colons from a string.\n\t * @param val The value to strip.\n\t * @returns The stripped string.\n\t * @internal\n\t */\n\tprivate stripColons(val: string): string {\n\t\treturn val.replace(/^:?(.*?):?$/, \"$1\");\n\t}\n}\n"]}
@@ -6,6 +6,26 @@ import { SharedStore } from "./sharedStore.js";
6
6
  * Cache the results from asynchronous requests.
7
7
  */
8
8
  export class AsyncCache {
9
+ /**
10
+ * Cache key for the shared cache object in the SharedStore.
11
+ * @internal
12
+ */
13
+ static _CACHE_KEY = "asyncCache";
14
+ /**
15
+ * Cache key for the entry key of the soonest-expiring cache entry, used to optimize cleanup.
16
+ * @internal
17
+ */
18
+ static _NEXT_EXPIRY_CACHE_KEY = "asyncCacheNextExpiryKey";
19
+ /**
20
+ * Cache key for the timestamp of the last full cleanup scan.
21
+ * @internal
22
+ */
23
+ static _LAST_CLEANUP_KEY = "asyncCacheLastCleanup";
24
+ /**
25
+ * Minimum interval in ms between full cleanup scans.
26
+ * @internal
27
+ */
28
+ static _CLEANUP_INTERVAL_MS = 5000;
9
29
  /**
10
30
  * Execute an async request and cache the result.
11
31
  * @param key The key for the entry in the cache.
@@ -14,117 +34,140 @@ export class AsyncCache {
14
34
  * @param cacheFailures Cache failure results, defaults to false.
15
35
  * @returns The response.
16
36
  */
17
- static exec(key, ttlMs, requestMethod, cacheFailures) {
18
- const cacheEnabled = Is.integer(ttlMs) && ttlMs >= 0;
19
- if (cacheEnabled) {
20
- AsyncCache.cleanupExpired();
21
- const cache = AsyncCache.getSharedCache();
22
- // Do we have a cache entry for the key
23
- if (cache[key]) {
24
- if (!Is.empty(cache[key].result)) {
25
- // If the cache has already resulted in a value, resolve it
26
- return Promise.resolve(cache[key].result);
27
- }
28
- else if (!Is.empty(cache[key].error)) {
29
- // If the cache has already resulted in an error, reject it
30
- return Promise.reject(cache[key].error);
31
- }
32
- // Otherwise create a promise to return and store the resolver
33
- // and rejector in the cache entry, so that we can call then
34
- // when the request is done
35
- let storedResolve;
36
- let storedReject;
37
- const wait = new Promise((resolve, reject) => {
38
- storedResolve = resolve;
39
- storedReject = reject;
37
+ static async exec(key, ttlMs, requestMethod, cacheFailures) {
38
+ const cacheEnabled = Is.integer(ttlMs) && ttlMs > 0;
39
+ if (!cacheEnabled) {
40
+ // No caching, just execute the request method
41
+ return requestMethod();
42
+ }
43
+ AsyncCache.cleanupExpired();
44
+ // Cleanup will not necessarily remove the entry for the key we are requesting
45
+ // as it is throttled, so we also check and evict if expired here to ensure we don't return stale data.
46
+ AsyncCache.evictIfExpired(key);
47
+ const cache = AsyncCache.getSharedCache();
48
+ const cachedEntry = cache[key];
49
+ // Do we have a cache entry for the key
50
+ if (cachedEntry) {
51
+ if (!Is.empty(cachedEntry.result)) {
52
+ // If the cache has already resulted in a value, resolve it
53
+ return Promise.resolve(cachedEntry.result);
54
+ }
55
+ else if (!Is.empty(cachedEntry.error)) {
56
+ // If the cache has already resulted in an error, reject it
57
+ return Promise.reject(cachedEntry.error);
58
+ }
59
+ // Otherwise create a promise to return and store the resolver
60
+ // and rejector in the cache entry, so that we can call then
61
+ // when the request is done
62
+ let storedResolve;
63
+ let storedReject;
64
+ const wait = new Promise((resolve, reject) => {
65
+ storedResolve = resolve;
66
+ storedReject = reject;
67
+ });
68
+ if (!Is.empty(storedResolve) && !Is.empty(storedReject)) {
69
+ cachedEntry.promiseQueue.push({
70
+ requestMethod,
71
+ resolve: storedResolve,
72
+ reject: storedReject
40
73
  });
41
- if (!Is.empty(storedResolve) && !Is.empty(storedReject)) {
42
- cache[key].promiseQueue.push({
43
- requestMethod,
44
- resolve: storedResolve,
45
- reject: storedReject
46
- });
47
- }
48
- return wait;
49
74
  }
50
- // If we don't have a cache entry, create a new one
51
- cache[key] = {
52
- promiseQueue: [],
53
- expires: ttlMs === 0 ? 0 : Date.now() + ttlMs
54
- };
55
- // Return a promise that wraps the original request method
56
- // so that we can store any results or errors in the cache
57
- return new Promise((resolve, reject) => {
58
- // Call the request method and store the result
59
- requestMethod()
60
- // eslint-disable-next-line promise/prefer-await-to-then
61
- .then(res => {
62
- // If the request was successful, store the result
63
- cache[key].result = res;
64
- // and resolve both this promise and all the waiters
65
- resolve(res);
66
- for (const wait of cache[key].promiseQueue) {
67
- wait.resolve(res);
75
+ return wait;
76
+ }
77
+ // If we don't have a cache entry, create a new one
78
+ const expires = Date.now() + ttlMs;
79
+ const cacheEntry = {
80
+ inProgress: true,
81
+ promiseQueue: [],
82
+ expires
83
+ };
84
+ cache[key] = cacheEntry;
85
+ AsyncCache.updateNextExpiryKey(key, expires);
86
+ // Return a promise that wraps the original request method
87
+ // so that we can store any results or errors in the cache
88
+ return new Promise((resolve, reject) => {
89
+ // Call the request method and store the result
90
+ requestMethod()
91
+ // eslint-disable-next-line promise/prefer-await-to-then
92
+ .then(res => {
93
+ // If the request was successful, store the result
94
+ cacheEntry.inProgress = false;
95
+ cacheEntry.result = res;
96
+ // and resolve both this promise and all the waiters
97
+ resolve(res);
98
+ for (const wait of cacheEntry.promiseQueue) {
99
+ wait.resolve(res);
100
+ }
101
+ cacheEntry.promiseQueue = [];
102
+ return res;
103
+ })
104
+ // eslint-disable-next-line promise/prefer-await-to-then
105
+ .catch((err) => {
106
+ // Reject the promise
107
+ reject(err);
108
+ cacheEntry.inProgress = false;
109
+ // Handle the waiters based on the cacheFailures flag
110
+ if (cacheFailures ?? false) {
111
+ // If we are caching failures, store the error and reject the waiters
112
+ cacheEntry.error = err;
113
+ for (const wait of cacheEntry.promiseQueue) {
114
+ wait.reject(err);
68
115
  }
69
- return res;
70
- })
71
- // eslint-disable-next-line promise/prefer-await-to-then
72
- .catch((err) => {
73
- // Reject the promise
74
- reject(err);
75
- // Handle the waiters based on the cacheFailures flag
76
- if (cacheFailures ?? false) {
77
- // If we are caching failures, store the error and reject the waiters
78
- cache[key].error = err;
79
- for (const wait of cache[key].promiseQueue) {
80
- wait.reject(err);
81
- }
82
- // Clear the waiters so we don't call them again
83
- cache[key].promiseQueue = [];
116
+ // Clear the waiters so we don't call them again
117
+ cacheEntry.promiseQueue = [];
118
+ }
119
+ else {
120
+ // If not caching failures for any queued requests we
121
+ // have no value to either resolve or reject, so we
122
+ // just resolve with the original request method
123
+ for (const wait of cacheEntry.promiseQueue) {
124
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
125
+ AsyncCache.resolveWaiter(wait.requestMethod, wait.resolve, wait.reject);
84
126
  }
85
- else {
86
- // If not caching failures for any queued requests we
87
- // have no value to either resolve or reject, so we
88
- // just resolve with the original request method
89
- for (const wait of cache[key].promiseQueue) {
90
- wait.resolve(wait.requestMethod());
91
- }
127
+ if (cache[key] === cacheEntry) {
92
128
  delete cache[key];
93
129
  }
94
- });
130
+ }
95
131
  });
96
- }
132
+ });
97
133
  }
98
134
  /**
99
135
  * Get an entry from the cache.
100
136
  * @param key The key to get from the cache.
101
- * @returns The item from the cache if it exists.
137
+ * @returns The item from the cache if it exists, or undefined if the key is missing, expired, or
138
+ * its request is still in-progress. Throws if a cached failure exists for the key.
102
139
  */
103
140
  static async get(key) {
141
+ if (AsyncCache.evictIfExpired(key)) {
142
+ return undefined;
143
+ }
104
144
  const cache = AsyncCache.getSharedCache();
105
- if (!Is.empty(cache[key].result)) {
145
+ const entry = cache[key];
146
+ if (!Is.empty(entry?.result)) {
106
147
  // If the cache has already resulted in a value, resolve it
107
- return cache[key].result;
148
+ return entry.result;
108
149
  }
109
- else if (!Is.empty(cache[key].error)) {
150
+ if (!Is.empty(entry?.error)) {
110
151
  // If the cache has already resulted in an error, reject it
111
- throw cache[key].error;
152
+ throw entry.error;
112
153
  }
113
154
  }
114
155
  /**
115
156
  * Set an entry into the cache.
116
157
  * @param key The key to set in the cache.
117
158
  * @param value The value to set in the cache.
118
- * @param ttlMs The TTL of the entry in the cache in ms, defaults to 1s.
159
+ * @param ttlMs The TTL of the entry in the cache in milliseconds. Defaults to 1000 (1 second).
119
160
  * @returns Nothing.
120
161
  */
121
162
  static async set(key, value, ttlMs) {
163
+ const expires = Date.now() + (ttlMs ?? 1000);
122
164
  const cache = AsyncCache.getSharedCache();
123
165
  cache[key] = {
124
166
  result: value,
125
167
  promiseQueue: [],
126
- expires: Date.now() + (ttlMs ?? 1000)
168
+ expires
127
169
  };
170
+ AsyncCache.updateNextExpiryKey(key, expires);
128
171
  }
129
172
  /**
130
173
  * Remove an entry from the cache.
@@ -133,6 +176,10 @@ export class AsyncCache {
133
176
  static remove(key) {
134
177
  const cache = AsyncCache.getSharedCache();
135
178
  delete cache[key];
179
+ const nextKey = SharedStore.get(AsyncCache._NEXT_EXPIRY_CACHE_KEY);
180
+ if (nextKey === key) {
181
+ AsyncCache.recalculateNextKey();
182
+ }
136
183
  }
137
184
  /**
138
185
  * Clear the cache.
@@ -141,26 +188,45 @@ export class AsyncCache {
141
188
  static clearCache(prefix) {
142
189
  const cache = AsyncCache.getSharedCache();
143
190
  if (Is.stringValue(prefix)) {
191
+ const nextKey = SharedStore.get(AsyncCache._NEXT_EXPIRY_CACHE_KEY);
192
+ let nextKeyRemoved = false;
144
193
  for (const entry in cache) {
145
194
  if (entry.startsWith(prefix)) {
195
+ if (entry === nextKey) {
196
+ nextKeyRemoved = true;
197
+ }
146
198
  delete cache[entry];
147
199
  }
148
200
  }
201
+ if (nextKeyRemoved) {
202
+ AsyncCache.recalculateNextKey();
203
+ }
149
204
  }
150
205
  else {
151
- SharedStore.set("asyncCache", {});
206
+ SharedStore.set(AsyncCache._CACHE_KEY, {});
207
+ SharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);
208
+ SharedStore.set(AsyncCache._LAST_CLEANUP_KEY, Date.now());
152
209
  }
153
210
  }
154
211
  /**
155
212
  * Perform a cleanup of the expired entries in the cache.
156
213
  */
157
214
  static cleanupExpired() {
158
- const cache = AsyncCache.getSharedCache();
159
- for (const entry in cache) {
160
- if (cache[entry].expires > 0 && cache[entry].expires < Date.now()) {
161
- delete cache[entry];
162
- }
215
+ const now = Date.now();
216
+ const lastCleanup = SharedStore.get(AsyncCache._LAST_CLEANUP_KEY) ?? 0;
217
+ if (now - lastCleanup < AsyncCache._CLEANUP_INTERVAL_MS) {
218
+ return;
219
+ }
220
+ const expiry = AsyncCache.getNextExpiry();
221
+ if (expiry !== undefined && expiry > now) {
222
+ // Nothing is expired yet. Stamp the time so the throttle suppresses further checks
223
+ // for the next interval. Per-key freshness is still guaranteed because exec() and
224
+ // get() call evictIfExpired() directly before reading the cache.
225
+ SharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);
226
+ return;
163
227
  }
228
+ SharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);
229
+ AsyncCache.recalculateNextKey();
164
230
  }
165
231
  /**
166
232
  * Get the shared cache.
@@ -168,12 +234,94 @@ export class AsyncCache {
168
234
  * @internal
169
235
  */
170
236
  static getSharedCache() {
171
- let sharedCache = SharedStore.get("asyncCache");
237
+ let sharedCache = SharedStore.get(AsyncCache._CACHE_KEY);
172
238
  if (Is.undefined(sharedCache)) {
173
239
  sharedCache = {};
174
- SharedStore.set("asyncCache", sharedCache);
240
+ SharedStore.set(AsyncCache._CACHE_KEY, sharedCache);
175
241
  }
176
242
  return sharedCache;
177
243
  }
244
+ /**
245
+ * Resolve a waiter by re-running its request method safely.
246
+ * @param requestMethod The method to execute.
247
+ * @param resolve The resolver for the waiter.
248
+ * @param reject The rejector for the waiter.
249
+ */
250
+ static async resolveWaiter(requestMethod, resolve, reject) {
251
+ try {
252
+ resolve(await requestMethod());
253
+ }
254
+ catch (waitErr) {
255
+ reject(waitErr);
256
+ }
257
+ }
258
+ /**
259
+ * Scan all cache entries, delete any that have expired, and record the key of the
260
+ * soonest-expiring remaining entry.
261
+ * @internal
262
+ */
263
+ static recalculateNextKey() {
264
+ const now = Date.now();
265
+ const cache = AsyncCache.getSharedCache();
266
+ if (Object.keys(cache).length === 0) {
267
+ SharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);
268
+ return;
269
+ }
270
+ let newNextKey;
271
+ let newNextExpiry = Number.MAX_SAFE_INTEGER;
272
+ for (const entryKey in cache) {
273
+ const { expires, inProgress } = cache[entryKey];
274
+ if (expires > 0 && expires < now && inProgress !== true) {
275
+ delete cache[entryKey];
276
+ }
277
+ else if (expires > 0 && expires < newNextExpiry) {
278
+ newNextExpiry = expires;
279
+ newNextKey = entryKey;
280
+ }
281
+ }
282
+ SharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, newNextKey);
283
+ }
284
+ /**
285
+ * Update the tracked next-expiry entry key if the given entry expires sooner than the current one.
286
+ * @param key The cache entry key.
287
+ * @param expires The expiry timestamp of the entry.
288
+ * @internal
289
+ */
290
+ static updateNextExpiryKey(key, expires) {
291
+ const expiry = AsyncCache.getNextExpiry();
292
+ if (expiry !== undefined && expiry <= expires) {
293
+ return;
294
+ }
295
+ SharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, key);
296
+ }
297
+ /**
298
+ * Deletes the given key from the cache if it has expired and is not in-progress.
299
+ * Returns true if the entry was evicted.
300
+ * @param key The cache entry key to check.
301
+ * @internal
302
+ */
303
+ static evictIfExpired(key) {
304
+ const cache = AsyncCache.getSharedCache();
305
+ const entry = cache[key];
306
+ if (!Is.empty(entry) && entry.expires > 0 && entry.expires < Date.now() && !entry.inProgress) {
307
+ delete cache[key];
308
+ return true;
309
+ }
310
+ return false;
311
+ }
312
+ /**
313
+ * Returns the expiry timestamp of the tracked next-expiry entry, or undefined if there
314
+ * is no tracked entry or it is no longer present in the cache.
315
+ * @internal
316
+ */
317
+ static getNextExpiry() {
318
+ const nextKey = SharedStore.get(AsyncCache._NEXT_EXPIRY_CACHE_KEY);
319
+ if (Is.empty(nextKey)) {
320
+ return undefined;
321
+ }
322
+ const cache = AsyncCache.getSharedCache();
323
+ const nextEntry = cache[nextKey];
324
+ return Is.empty(nextEntry) ? undefined : nextEntry.expires;
325
+ }
178
326
  }
179
327
  //# sourceMappingURL=asyncCache.js.map