@twin.org/core 0.0.4-next.8 → 0.9.0-next.1

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 (119) hide show
  1. package/dist/es/encoding/base64.js +1 -1
  2. package/dist/es/encoding/base64.js.map +1 -1
  3. package/dist/es/errors/conflictError.js +1 -1
  4. package/dist/es/errors/conflictError.js.map +1 -1
  5. package/dist/es/errors/generalError.js +1 -1
  6. package/dist/es/errors/generalError.js.map +1 -1
  7. package/dist/es/errors/notImplementedError.js +1 -1
  8. package/dist/es/errors/notImplementedError.js.map +1 -1
  9. package/dist/es/errors/validationError.js +1 -1
  10. package/dist/es/errors/validationError.js.map +1 -1
  11. package/dist/es/helpers/arrayHelper.js +1 -1
  12. package/dist/es/helpers/arrayHelper.js.map +1 -1
  13. package/dist/es/helpers/errorHelper.js +1 -1
  14. package/dist/es/helpers/errorHelper.js.map +1 -1
  15. package/dist/es/helpers/jsonHelper.js +1 -1
  16. package/dist/es/helpers/jsonHelper.js.map +1 -1
  17. package/dist/es/helpers/objectHelper.js +2 -2
  18. package/dist/es/helpers/objectHelper.js.map +1 -1
  19. package/dist/es/helpers/stringHelper.js +2 -2
  20. package/dist/es/helpers/stringHelper.js.map +1 -1
  21. package/dist/es/index.js +7 -0
  22. package/dist/es/index.js.map +1 -1
  23. package/dist/es/models/IComponent.js.map +1 -1
  24. package/dist/es/models/IDuration.js +4 -0
  25. package/dist/es/models/IDuration.js.map +1 -0
  26. package/dist/es/models/ILocale.js.map +1 -1
  27. package/dist/es/models/ISharedObjectBufferOptions.js +4 -0
  28. package/dist/es/models/ISharedObjectBufferOptions.js.map +1 -0
  29. package/dist/es/models/ISharedObjectBufferWorkerMessage.js +2 -0
  30. package/dist/es/models/ISharedObjectBufferWorkerMessage.js.map +1 -0
  31. package/dist/es/models/IValidationFailure.js.map +1 -1
  32. package/dist/es/models/coerceType.js +5 -1
  33. package/dist/es/models/coerceType.js.map +1 -1
  34. package/dist/es/models/sharedObjectBufferMessageTypes.js +13 -0
  35. package/dist/es/models/sharedObjectBufferMessageTypes.js.map +1 -0
  36. package/dist/es/types/duration.js +184 -0
  37. package/dist/es/types/duration.js.map +1 -0
  38. package/dist/es/types/durationRegExp.js +4 -0
  39. package/dist/es/types/durationRegExp.js.map +1 -0
  40. package/dist/es/types/url.js +1 -0
  41. package/dist/es/types/url.js.map +1 -1
  42. package/dist/es/utils/asyncCache.js +1 -1
  43. package/dist/es/utils/asyncCache.js.map +1 -1
  44. package/dist/es/utils/coerce.js +34 -20
  45. package/dist/es/utils/coerce.js.map +1 -1
  46. package/dist/es/utils/compression.js +1 -1
  47. package/dist/es/utils/compression.js.map +1 -1
  48. package/dist/es/utils/guards.js +12 -0
  49. package/dist/es/utils/guards.js.map +1 -1
  50. package/dist/es/utils/is.js +65 -16
  51. package/dist/es/utils/is.js.map +1 -1
  52. package/dist/es/utils/mutex.js +49 -8
  53. package/dist/es/utils/mutex.js.map +1 -1
  54. package/dist/es/utils/sharedObjectBuffer.js +308 -0
  55. package/dist/es/utils/sharedObjectBuffer.js.map +1 -0
  56. package/dist/es/utils/sharedStore.js +2 -2
  57. package/dist/es/utils/sharedStore.js.map +1 -1
  58. package/dist/types/errors/conflictError.d.ts +1 -1
  59. package/dist/types/errors/generalError.d.ts +1 -1
  60. package/dist/types/errors/notImplementedError.d.ts +1 -1
  61. package/dist/types/errors/validationError.d.ts +1 -1
  62. package/dist/types/helpers/arrayHelper.d.ts +1 -1
  63. package/dist/types/helpers/errorHelper.d.ts +1 -1
  64. package/dist/types/helpers/jsonHelper.d.ts +1 -1
  65. package/dist/types/helpers/objectHelper.d.ts +2 -2
  66. package/dist/types/helpers/stringHelper.d.ts +2 -2
  67. package/dist/types/index.d.ts +7 -0
  68. package/dist/types/models/IComponent.d.ts +2 -2
  69. package/dist/types/models/IDuration.d.ts +45 -0
  70. package/dist/types/models/ILocale.d.ts +1 -1
  71. package/dist/types/models/ISharedObjectBufferOptions.d.ts +17 -0
  72. package/dist/types/models/ISharedObjectBufferWorkerMessage.d.ts +29 -0
  73. package/dist/types/models/IValidationFailure.d.ts +1 -1
  74. package/dist/types/models/coerceType.d.ts +4 -0
  75. package/dist/types/models/sharedObjectBufferMessageTypes.d.ts +13 -0
  76. package/dist/types/types/duration.d.ts +29 -0
  77. package/dist/types/types/durationRegExp.d.ts +1 -0
  78. package/dist/types/types/url.d.ts +1 -0
  79. package/dist/types/utils/asyncCache.d.ts +1 -1
  80. package/dist/types/utils/coerce.d.ts +19 -20
  81. package/dist/types/utils/compression.d.ts +1 -1
  82. package/dist/types/utils/guards.d.ts +9 -0
  83. package/dist/types/utils/is.d.ts +16 -9
  84. package/dist/types/utils/mutex.d.ts +12 -1
  85. package/dist/types/utils/sharedObjectBuffer.d.ts +74 -0
  86. package/dist/types/utils/sharedStore.d.ts +2 -2
  87. package/docs/changelog.md +273 -0
  88. package/docs/reference/classes/ArrayHelper.md +1 -1
  89. package/docs/reference/classes/AsyncCache.md +1 -1
  90. package/docs/reference/classes/Coerce.md +32 -48
  91. package/docs/reference/classes/Compression.md +1 -1
  92. package/docs/reference/classes/ConflictError.md +1 -1
  93. package/docs/reference/classes/Duration.md +88 -0
  94. package/docs/reference/classes/ErrorHelper.md +1 -1
  95. package/docs/reference/classes/GeneralError.md +1 -1
  96. package/docs/reference/classes/Guards.md +36 -0
  97. package/docs/reference/classes/Is.md +31 -9
  98. package/docs/reference/classes/JsonHelper.md +1 -1
  99. package/docs/reference/classes/Mutex.md +39 -1
  100. package/docs/reference/classes/NotImplementedError.md +1 -1
  101. package/docs/reference/classes/ObjectHelper.md +10 -2
  102. package/docs/reference/classes/SharedObjectBuffer.md +192 -0
  103. package/docs/reference/classes/SharedStore.md +2 -2
  104. package/docs/reference/classes/StringHelper.md +16 -13
  105. package/docs/reference/classes/Url.md +4 -0
  106. package/docs/reference/classes/ValidationError.md +1 -1
  107. package/docs/reference/index.md +8 -0
  108. package/docs/reference/interfaces/IComponent.md +2 -2
  109. package/docs/reference/interfaces/IDuration.md +83 -0
  110. package/docs/reference/interfaces/ILocale.md +1 -1
  111. package/docs/reference/interfaces/ISharedObjectBufferOptions.md +33 -0
  112. package/docs/reference/interfaces/ISharedObjectBufferWorkerMessage.md +44 -0
  113. package/docs/reference/interfaces/IValidationFailure.md +1 -1
  114. package/docs/reference/type-aliases/SharedObjectBufferMessageTypes.md +5 -0
  115. package/docs/reference/variables/CoerceType.md +6 -0
  116. package/docs/reference/variables/DURATION_REG_EXP.md +3 -0
  117. package/docs/reference/variables/SharedObjectBufferMessageTypes.md +13 -0
  118. package/locales/en.json +9 -2
  119. package/package.json +3 -3
@@ -0,0 +1,4 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=IDuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IDuration.js","sourceRoot":"","sources":["../../../src/models/IDuration.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Represents a duration broken down into its component parts.\n */\nexport interface IDuration {\n\t/**\n\t * The number of years.\n\t */\n\tyears: number;\n\n\t/**\n\t * The number of months.\n\t */\n\tmonths: number;\n\n\t/**\n\t * The number of weeks.\n\t */\n\tweeks: number;\n\n\t/**\n\t * The number of days.\n\t */\n\tdays: number;\n\n\t/**\n\t * The number of hours.\n\t */\n\thours: number;\n\n\t/**\n\t * The number of minutes.\n\t */\n\tminutes: number;\n\n\t/**\n\t * The number of seconds.\n\t */\n\tseconds: number;\n\n\t/**\n\t * The number of milliseconds.\n\t */\n\tmilliseconds?: number;\n\n\t/**\n\t * The number of microseconds.\n\t */\n\tmicroseconds?: number;\n\n\t/**\n\t * The number of nanoseconds.\n\t */\n\tnanoseconds?: number;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ILocale.js","sourceRoot":"","sources":["../../../src/models/ILocale.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Model for a local.\n */\nexport interface ILocale {\n\t/**\n\t * The label.\n\t */\n\tlabel: string;\n\n\t/**\n\t * The code.\n\t */\n\tcode: string;\n}\n"]}
1
+ {"version":3,"file":"ILocale.js","sourceRoot":"","sources":["../../../src/models/ILocale.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Model for a locale.\n */\nexport interface ILocale {\n\t/**\n\t * The label.\n\t */\n\tlabel: string;\n\n\t/**\n\t * The code.\n\t */\n\tcode: string;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=ISharedObjectBufferOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ISharedObjectBufferOptions.js","sourceRoot":"","sources":["../../../src/models/ISharedObjectBufferOptions.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Options for configuring buffer capacity when creating a shared object buffer.\n */\nexport interface ISharedObjectBufferOptions {\n\t/**\n\t * Initial payload capacity hint in bytes.\n\t * Only honoured when the buffer does not yet exist; ignored on subsequent writes.\n\t * @default 1 MiB.\n\t */\n\tinitialCapacityBytes?: number;\n\n\t/**\n\t * Maximum allowed payload capacity in bytes. The buffer will never grow beyond this limit.\n\t * Only honoured when the buffer does not yet exist; ignored on subsequent writes.\n\t * @default 256 MiB.\n\t */\n\tmaxCapacityBytes?: number;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ISharedObjectBufferWorkerMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ISharedObjectBufferWorkerMessage.js","sourceRoot":"","sources":["../../../src/models/ISharedObjectBufferWorkerMessage.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { MessagePort } from \"node:worker_threads\";\nimport type { ISharedObjectBufferOptions } from \"./ISharedObjectBufferOptions.js\";\nimport type { SharedObjectBufferMessageTypes } from \"./sharedObjectBufferMessageTypes.js\";\n\n/**\n * Message sent from a worker thread to the main thread to request the SharedArrayBuffer for an object.\n */\nexport interface ISharedObjectBufferWorkerMessage {\n\t/**\n\t * The message type discriminant.\n\t */\n\ttype: typeof SharedObjectBufferMessageTypes.GetBuffer;\n\n\t/**\n\t * The object id name that identifies which buffer is being requested.\n\t */\n\tobjectId: string;\n\n\t/**\n\t * One-shot SharedArrayBuffer used for the Atomics.wait/notify handshake so the\n\t * worker can block synchronously until the main thread has posted the response.\n\t */\n\tsignal: SharedArrayBuffer;\n\n\t/**\n\t * MessagePort through which the main thread returns the object buffer.\n\t */\n\tport: MessagePort;\n\n\t/**\n\t * Options for creating or fetching the buffer.\n\t */\n\toptions?: ISharedObjectBufferOptions;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"IValidationFailure.js","sourceRoot":"","sources":["../../../src/models/IValidationFailure.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Interface describing the reason a validation failed.\n */\nexport interface IValidationFailure {\n\t/**\n\t * The property that failed validation.\n\t */\n\tproperty: string;\n\n\t/**\n\t * The reason the validation failed as an i18 resource error.\n\t */\n\treason: string;\n\n\t/**\n\t * Additional properties for the validation failure.\n\t */\n\tproperties?: { [id: string]: unknown };\n}\n"]}
1
+ {"version":3,"file":"IValidationFailure.js","sourceRoot":"","sources":["../../../src/models/IValidationFailure.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Interface describing the reason a validation failed.\n */\nexport interface IValidationFailure {\n\t/**\n\t * The property that failed validation.\n\t */\n\tproperty: string;\n\n\t/**\n\t * The reason the validation failed as an i18n resource key.\n\t */\n\treason: string;\n\n\t/**\n\t * Additional properties for the validation failure.\n\t */\n\tproperties?: { [id: string]: unknown };\n}\n"]}
@@ -44,6 +44,10 @@ export const CoerceType = {
44
44
  /**
45
45
  * Uint8Array.
46
46
  */
47
- Uint8Array: "uint8array"
47
+ Uint8Array: "uint8array",
48
+ /**
49
+ * Duration in seconds.
50
+ */
51
+ Duration: "duration"
48
52
  };
49
53
  //# sourceMappingURL=coerceType.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"coerceType.js","sourceRoot":"","sources":["../../../src/models/coerceType.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AAEvC;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,OAAO,EAAE,SAAS;IAElB;;OAEG;IACH,OAAO,EAAE,SAAS;IAElB;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,IAAI,EAAE,MAAM;IAEZ;;OAEG;IACH,QAAQ,EAAE,UAAU;IAEpB;;OAEG;IACH,IAAI,EAAE,MAAM;IAEZ;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,UAAU,EAAE,YAAY;CACf,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * The types the extracted data can be coerced to.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const CoerceType = {\n\t/**\n\t * String.\n\t */\n\tString: \"string\",\n\n\t/**\n\t * Number.\n\t */\n\tNumber: \"number\",\n\n\t/**\n\t * Integer.\n\t */\n\tInteger: \"integer\",\n\n\t/**\n\t * Boolean.\n\t */\n\tBoolean: \"boolean\",\n\n\t/**\n\t * Big Integer.\n\t */\n\tBigInt: \"bigint\",\n\n\t/**\n\t * Date.\n\t */\n\tDate: \"date\",\n\n\t/**\n\t * Date Time.\n\t */\n\tDateTime: \"datetime\",\n\n\t/**\n\t * Time.\n\t */\n\tTime: \"time\",\n\n\t/**\n\t * Object.\n\t */\n\tObject: \"object\",\n\n\t/**\n\t * Uint8Array.\n\t */\n\tUint8Array: \"uint8array\"\n} as const;\n\n/**\n * The types the extracted data can be coerced to.\n */\nexport type CoerceType = (typeof CoerceType)[keyof typeof CoerceType];\n"]}
1
+ {"version":3,"file":"coerceType.js","sourceRoot":"","sources":["../../../src/models/coerceType.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AAEvC;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,OAAO,EAAE,SAAS;IAElB;;OAEG;IACH,OAAO,EAAE,SAAS;IAElB;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,IAAI,EAAE,MAAM;IAEZ;;OAEG;IACH,QAAQ,EAAE,UAAU;IAEpB;;OAEG;IACH,IAAI,EAAE,MAAM;IAEZ;;OAEG;IACH,MAAM,EAAE,QAAQ;IAEhB;;OAEG;IACH,UAAU,EAAE,YAAY;IAExB;;OAEG;IACH,QAAQ,EAAE,UAAU;CACX,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * The types the extracted data can be coerced to.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const CoerceType = {\n\t/**\n\t * String.\n\t */\n\tString: \"string\",\n\n\t/**\n\t * Number.\n\t */\n\tNumber: \"number\",\n\n\t/**\n\t * Integer.\n\t */\n\tInteger: \"integer\",\n\n\t/**\n\t * Boolean.\n\t */\n\tBoolean: \"boolean\",\n\n\t/**\n\t * Big Integer.\n\t */\n\tBigInt: \"bigint\",\n\n\t/**\n\t * Date.\n\t */\n\tDate: \"date\",\n\n\t/**\n\t * Date Time.\n\t */\n\tDateTime: \"datetime\",\n\n\t/**\n\t * Time.\n\t */\n\tTime: \"time\",\n\n\t/**\n\t * Object.\n\t */\n\tObject: \"object\",\n\n\t/**\n\t * Uint8Array.\n\t */\n\tUint8Array: \"uint8array\",\n\n\t/**\n\t * Duration in seconds.\n\t */\n\tDuration: \"duration\"\n} as const;\n\n/**\n * The types the extracted data can be coerced to.\n */\nexport type CoerceType = (typeof CoerceType)[keyof typeof CoerceType];\n"]}
@@ -0,0 +1,13 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ /**
4
+ * Message type constants for the SharedObjectBuffer worker-to-main-thread protocol.
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/naming-convention
7
+ export const SharedObjectBufferMessageTypes = {
8
+ /**
9
+ * Worker requests the SharedArrayBuffer for a named object from the main thread.
10
+ */
11
+ GetBuffer: "twin:sharedObjectBuffer:getBuffer"
12
+ };
13
+ //# sourceMappingURL=sharedObjectBufferMessageTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharedObjectBufferMessageTypes.js","sourceRoot":"","sources":["../../../src/models/sharedObjectBufferMessageTypes.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AAEvC;;GAEG;AACH,gEAAgE;AAChE,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC7C;;OAEG;IACH,SAAS,EAAE,mCAAmC;CACrC,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Message type constants for the SharedObjectBuffer worker-to-main-thread protocol.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const SharedObjectBufferMessageTypes = {\n\t/**\n\t * Worker requests the SharedArrayBuffer for a named object from the main thread.\n\t */\n\tGetBuffer: \"twin:sharedObjectBuffer:getBuffer\"\n} as const;\n\n/**\n * Union of all SharedObjectBuffer message type strings.\n */\nexport type SharedObjectBufferMessageTypes =\n\t(typeof SharedObjectBufferMessageTypes)[keyof typeof SharedObjectBufferMessageTypes];\n"]}
@@ -0,0 +1,184 @@
1
+ import { DURATION_REG_EXP } from "./durationRegExp.js";
2
+ import { Guards } from "../utils/guards.js";
3
+ /**
4
+ * Helper methods for working with ISO 8601 durations.
5
+ */
6
+ export class Duration {
7
+ /**
8
+ * Runtime name for the class.
9
+ */
10
+ static CLASS_NAME = "Duration";
11
+ /**
12
+ * Parse an ISO 8601 duration string into its component parts.
13
+ * @param value The string to parse.
14
+ * @returns The parsed duration, or undefined if the string is not a valid ISO 8601 duration.
15
+ */
16
+ static parse(value) {
17
+ const match = DURATION_REG_EXP.exec(value);
18
+ if (!match?.slice(2).some(Boolean)) {
19
+ return undefined;
20
+ }
21
+ const sign = match[1] === "-" ? -1 : 1;
22
+ const secondsValue = match[8] ?? "0";
23
+ const [secondsIntegerPart, secondsFractionPart] = secondsValue.split(".");
24
+ const fractionPadded = `${secondsFractionPart ?? ""}000000000`.slice(0, 9);
25
+ const milliseconds = Number.parseInt(fractionPadded.slice(0, 3), 10);
26
+ const microseconds = Number.parseInt(fractionPadded.slice(3, 6), 10);
27
+ const nanoseconds = Number.parseInt(fractionPadded.slice(6, 9), 10);
28
+ const duration = {
29
+ years: Duration.applySign(Number(match[2] ?? 0), sign),
30
+ months: Duration.applySign(Number(match[3] ?? 0), sign),
31
+ weeks: Duration.applySign(Number(match[4] ?? 0), sign),
32
+ days: Duration.applySign(Number(match[5] ?? 0), sign),
33
+ hours: Duration.applySign(Number(match[6] ?? 0), sign),
34
+ minutes: Duration.applySign(Number(match[7] ?? 0), sign),
35
+ seconds: Duration.applySign(Number(secondsIntegerPart ?? 0), sign)
36
+ };
37
+ if (milliseconds !== 0) {
38
+ duration.milliseconds = milliseconds * sign;
39
+ }
40
+ if (microseconds !== 0) {
41
+ duration.microseconds = microseconds * sign;
42
+ }
43
+ if (nanoseconds !== 0) {
44
+ duration.nanoseconds = nanoseconds * sign;
45
+ }
46
+ return duration;
47
+ }
48
+ /**
49
+ * Convert a duration object to an ISO 8601 duration string.
50
+ * @param duration The duration to convert.
51
+ * @returns The ISO 8601 duration string (e.g. "P1Y2M3DT4H5M6S").
52
+ */
53
+ static toString(duration) {
54
+ Guards.object(Duration.CLASS_NAME, "duration", duration);
55
+ const sign = Duration.resolveSign(duration);
56
+ const dateParts = [];
57
+ const absYears = Duration.absolute(duration.years);
58
+ if (absYears !== 0) {
59
+ dateParts.push(`${absYears}Y`);
60
+ }
61
+ const absMonths = Duration.absolute(duration.months);
62
+ if (absMonths !== 0) {
63
+ dateParts.push(`${absMonths}M`);
64
+ }
65
+ const absWeeks = Duration.absolute(duration.weeks);
66
+ if (absWeeks !== 0) {
67
+ dateParts.push(`${absWeeks}W`);
68
+ }
69
+ const absDays = Duration.absolute(duration.days);
70
+ if (absDays !== 0) {
71
+ dateParts.push(`${absDays}D`);
72
+ }
73
+ const timeParts = [];
74
+ const absHours = Duration.absolute(duration.hours);
75
+ if (absHours !== 0) {
76
+ timeParts.push(`${absHours}H`);
77
+ }
78
+ const absMinutes = Duration.absolute(duration.minutes);
79
+ if (absMinutes !== 0) {
80
+ timeParts.push(`${absMinutes}M`);
81
+ }
82
+ const secondFraction = Duration.formatSecondFraction(Duration.absolute(duration.milliseconds), Duration.absolute(duration.microseconds), Duration.absolute(duration.nanoseconds));
83
+ const absSeconds = Duration.absolute(duration.seconds);
84
+ if (absSeconds !== 0 || secondFraction !== undefined) {
85
+ timeParts.push(secondFraction === undefined ? `${absSeconds}S` : `${absSeconds}.${secondFraction}S`);
86
+ }
87
+ if (dateParts.length === 0 && timeParts.length === 0) {
88
+ return "PT0S";
89
+ }
90
+ const timeSection = timeParts.length > 0 ? `T${timeParts.join("")}` : "";
91
+ const prefix = sign < 0 ? "-" : "";
92
+ return `${prefix}P${dateParts.join("")}${timeSection}`;
93
+ }
94
+ /**
95
+ * Convert a duration object to total seconds.
96
+ * Year and month components use the average values 365.25 days and 30.4375 days.
97
+ * @param duration The duration to convert.
98
+ * @returns The total number of seconds.
99
+ */
100
+ static toSeconds(duration) {
101
+ Guards.object(Duration.CLASS_NAME, "duration", duration);
102
+ const years = duration.years * 31_557_600;
103
+ const months = duration.months * 2_629_800;
104
+ const weeks = duration.weeks * 604_800;
105
+ const days = duration.days * 86_400;
106
+ const hours = duration.hours * 3_600;
107
+ const minutes = duration.minutes * 60;
108
+ const milliseconds = (duration.milliseconds ?? 0) / 1_000;
109
+ const microseconds = (duration.microseconds ?? 0) / 1_000_000;
110
+ const nanoseconds = (duration.nanoseconds ?? 0) / 1_000_000_000;
111
+ return (years +
112
+ months +
113
+ weeks +
114
+ days +
115
+ hours +
116
+ minutes +
117
+ duration.seconds +
118
+ milliseconds +
119
+ microseconds +
120
+ nanoseconds);
121
+ }
122
+ /**
123
+ * Resolve a common sign for all non-zero components.
124
+ * @param duration The duration.
125
+ * @returns -1 for negative, 1 for positive, 0 for zero.
126
+ * @internal
127
+ */
128
+ static resolveSign(duration) {
129
+ const fields = [
130
+ duration.years,
131
+ duration.months,
132
+ duration.weeks,
133
+ duration.days,
134
+ duration.hours,
135
+ duration.minutes,
136
+ duration.seconds,
137
+ duration.milliseconds,
138
+ duration.microseconds,
139
+ duration.nanoseconds
140
+ ];
141
+ for (const field of fields) {
142
+ const v = field ?? 0;
143
+ if (v !== 0) {
144
+ return v < 0 ? -1 : 1;
145
+ }
146
+ }
147
+ return 0;
148
+ }
149
+ /**
150
+ * Format the sub-second components as a 9-digit fractional second string.
151
+ * @param milliseconds The milliseconds component.
152
+ * @param microseconds The microseconds component.
153
+ * @param nanoseconds The nanoseconds component.
154
+ * @returns The formatted fraction without a leading dot.
155
+ * @internal
156
+ */
157
+ static formatSecondFraction(milliseconds, microseconds, nanoseconds) {
158
+ if (milliseconds === 0 && microseconds === 0 && nanoseconds === 0) {
159
+ return undefined;
160
+ }
161
+ const fraction = `${(milliseconds % 1000).toString().padStart(3, "0")}${(microseconds % 1000).toString().padStart(3, "0")}${(nanoseconds % 1000).toString().padStart(3, "0")}`;
162
+ return fraction.replace(/0+$/u, "");
163
+ }
164
+ /**
165
+ * Normalize a numeric value to its absolute magnitude.
166
+ * @param value The value.
167
+ * @returns The absolute value, defaulting undefined to 0.
168
+ * @internal
169
+ */
170
+ static absolute(value) {
171
+ return Math.abs(value ?? 0);
172
+ }
173
+ /**
174
+ * Apply a global sign while normalizing zero values.
175
+ * @param value The value.
176
+ * @param sign The sign.
177
+ * @returns The signed value with zero normalized to +0.
178
+ * @internal
179
+ */
180
+ static applySign(value, sign) {
181
+ return value === 0 ? 0 : value * sign;
182
+ }
183
+ }
184
+ //# sourceMappingURL=duration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.js","sourceRoot":"","sources":["../../../src/types/duration.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { DURATION_REG_EXP } from \"./durationRegExp.js\";\nimport type { IDuration } from \"../models/IDuration.js\";\nimport { Guards } from \"../utils/guards.js\";\n\n/**\n * Helper methods for working with ISO 8601 durations.\n */\nexport class Duration {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Duration>();\n\n\t/**\n\t * Parse an ISO 8601 duration string into its component parts.\n\t * @param value The string to parse.\n\t * @returns The parsed duration, or undefined if the string is not a valid ISO 8601 duration.\n\t */\n\tpublic static parse(value: string): IDuration | undefined {\n\t\tconst match = DURATION_REG_EXP.exec(value);\n\t\tif (!match?.slice(2).some(Boolean)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst sign = match[1] === \"-\" ? -1 : 1;\n\t\tconst secondsValue = match[8] ?? \"0\";\n\t\tconst [secondsIntegerPart, secondsFractionPart] = secondsValue.split(\".\");\n\t\tconst fractionPadded = `${secondsFractionPart ?? \"\"}000000000`.slice(0, 9);\n\t\tconst milliseconds = Number.parseInt(fractionPadded.slice(0, 3), 10);\n\t\tconst microseconds = Number.parseInt(fractionPadded.slice(3, 6), 10);\n\t\tconst nanoseconds = Number.parseInt(fractionPadded.slice(6, 9), 10);\n\n\t\tconst duration: IDuration = {\n\t\t\tyears: Duration.applySign(Number(match[2] ?? 0), sign),\n\t\t\tmonths: Duration.applySign(Number(match[3] ?? 0), sign),\n\t\t\tweeks: Duration.applySign(Number(match[4] ?? 0), sign),\n\t\t\tdays: Duration.applySign(Number(match[5] ?? 0), sign),\n\t\t\thours: Duration.applySign(Number(match[6] ?? 0), sign),\n\t\t\tminutes: Duration.applySign(Number(match[7] ?? 0), sign),\n\t\t\tseconds: Duration.applySign(Number(secondsIntegerPart ?? 0), sign)\n\t\t};\n\n\t\tif (milliseconds !== 0) {\n\t\t\tduration.milliseconds = milliseconds * sign;\n\t\t}\n\t\tif (microseconds !== 0) {\n\t\t\tduration.microseconds = microseconds * sign;\n\t\t}\n\t\tif (nanoseconds !== 0) {\n\t\t\tduration.nanoseconds = nanoseconds * sign;\n\t\t}\n\n\t\treturn duration;\n\t}\n\n\t/**\n\t * Convert a duration object to an ISO 8601 duration string.\n\t * @param duration The duration to convert.\n\t * @returns The ISO 8601 duration string (e.g. \"P1Y2M3DT4H5M6S\").\n\t */\n\tpublic static toString(duration: IDuration): string {\n\t\tGuards.object<IDuration>(Duration.CLASS_NAME, nameof(duration), duration);\n\t\tconst sign = Duration.resolveSign(duration);\n\t\tconst dateParts: string[] = [];\n\t\tconst absYears = Duration.absolute(duration.years);\n\t\tif (absYears !== 0) {\n\t\t\tdateParts.push(`${absYears}Y`);\n\t\t}\n\t\tconst absMonths = Duration.absolute(duration.months);\n\t\tif (absMonths !== 0) {\n\t\t\tdateParts.push(`${absMonths}M`);\n\t\t}\n\t\tconst absWeeks = Duration.absolute(duration.weeks);\n\t\tif (absWeeks !== 0) {\n\t\t\tdateParts.push(`${absWeeks}W`);\n\t\t}\n\t\tconst absDays = Duration.absolute(duration.days);\n\t\tif (absDays !== 0) {\n\t\t\tdateParts.push(`${absDays}D`);\n\t\t}\n\n\t\tconst timeParts: string[] = [];\n\t\tconst absHours = Duration.absolute(duration.hours);\n\t\tif (absHours !== 0) {\n\t\t\ttimeParts.push(`${absHours}H`);\n\t\t}\n\t\tconst absMinutes = Duration.absolute(duration.minutes);\n\t\tif (absMinutes !== 0) {\n\t\t\ttimeParts.push(`${absMinutes}M`);\n\t\t}\n\t\tconst secondFraction = Duration.formatSecondFraction(\n\t\t\tDuration.absolute(duration.milliseconds),\n\t\t\tDuration.absolute(duration.microseconds),\n\t\t\tDuration.absolute(duration.nanoseconds)\n\t\t);\n\t\tconst absSeconds = Duration.absolute(duration.seconds);\n\t\tif (absSeconds !== 0 || secondFraction !== undefined) {\n\t\t\ttimeParts.push(\n\t\t\t\tsecondFraction === undefined ? `${absSeconds}S` : `${absSeconds}.${secondFraction}S`\n\t\t\t);\n\t\t}\n\n\t\tif (dateParts.length === 0 && timeParts.length === 0) {\n\t\t\treturn \"PT0S\";\n\t\t}\n\n\t\tconst timeSection = timeParts.length > 0 ? `T${timeParts.join(\"\")}` : \"\";\n\t\tconst prefix = sign < 0 ? \"-\" : \"\";\n\t\treturn `${prefix}P${dateParts.join(\"\")}${timeSection}`;\n\t}\n\n\t/**\n\t * Convert a duration object to total seconds.\n\t * Year and month components use the average values 365.25 days and 30.4375 days.\n\t * @param duration The duration to convert.\n\t * @returns The total number of seconds.\n\t */\n\tpublic static toSeconds(duration: IDuration): number {\n\t\tGuards.object<IDuration>(Duration.CLASS_NAME, nameof(duration), duration);\n\t\tconst years = duration.years * 31_557_600;\n\t\tconst months = duration.months * 2_629_800;\n\t\tconst weeks = duration.weeks * 604_800;\n\t\tconst days = duration.days * 86_400;\n\t\tconst hours = duration.hours * 3_600;\n\t\tconst minutes = duration.minutes * 60;\n\t\tconst milliseconds = (duration.milliseconds ?? 0) / 1_000;\n\t\tconst microseconds = (duration.microseconds ?? 0) / 1_000_000;\n\t\tconst nanoseconds = (duration.nanoseconds ?? 0) / 1_000_000_000;\n\t\treturn (\n\t\t\tyears +\n\t\t\tmonths +\n\t\t\tweeks +\n\t\t\tdays +\n\t\t\thours +\n\t\t\tminutes +\n\t\t\tduration.seconds +\n\t\t\tmilliseconds +\n\t\t\tmicroseconds +\n\t\t\tnanoseconds\n\t\t);\n\t}\n\n\t/**\n\t * Resolve a common sign for all non-zero components.\n\t * @param duration The duration.\n\t * @returns -1 for negative, 1 for positive, 0 for zero.\n\t * @internal\n\t */\n\tprivate static resolveSign(duration: IDuration): number {\n\t\tconst fields = [\n\t\t\tduration.years,\n\t\t\tduration.months,\n\t\t\tduration.weeks,\n\t\t\tduration.days,\n\t\t\tduration.hours,\n\t\t\tduration.minutes,\n\t\t\tduration.seconds,\n\t\t\tduration.milliseconds,\n\t\t\tduration.microseconds,\n\t\t\tduration.nanoseconds\n\t\t];\n\t\tfor (const field of fields) {\n\t\t\tconst v = field ?? 0;\n\t\t\tif (v !== 0) {\n\t\t\t\treturn v < 0 ? -1 : 1;\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Format the sub-second components as a 9-digit fractional second string.\n\t * @param milliseconds The milliseconds component.\n\t * @param microseconds The microseconds component.\n\t * @param nanoseconds The nanoseconds component.\n\t * @returns The formatted fraction without a leading dot.\n\t * @internal\n\t */\n\tprivate static formatSecondFraction(\n\t\tmilliseconds: number,\n\t\tmicroseconds: number,\n\t\tnanoseconds: number\n\t): string | undefined {\n\t\tif (milliseconds === 0 && microseconds === 0 && nanoseconds === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst fraction = `${(milliseconds % 1000).toString().padStart(3, \"0\")}${(microseconds % 1000).toString().padStart(3, \"0\")}${(nanoseconds % 1000).toString().padStart(3, \"0\")}`;\n\t\treturn fraction.replace(/0+$/u, \"\");\n\t}\n\n\t/**\n\t * Normalize a numeric value to its absolute magnitude.\n\t * @param value The value.\n\t * @returns The absolute value, defaulting undefined to 0.\n\t * @internal\n\t */\n\tprivate static absolute(value: number | undefined): number {\n\t\treturn Math.abs(value ?? 0);\n\t}\n\n\t/**\n\t * Apply a global sign while normalizing zero values.\n\t * @param value The value.\n\t * @param sign The sign.\n\t * @returns The signed value with zero normalized to +0.\n\t * @internal\n\t */\n\tprivate static applySign(value: number, sign: number): number {\n\t\treturn value === 0 ? 0 : value * sign;\n\t}\n}\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export const DURATION_REG_EXP = /^([+-])?P(?:(\d+(?:\.\d+)?)Y)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
4
+ //# sourceMappingURL=durationRegExp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durationRegExp.js","sourceRoot":"","sources":["../../../src/types/durationRegExp.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAC5B,qKAAqK,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport const DURATION_REG_EXP =\n\t/^([+-])?P(?:(\\d+(?:\\.\\d+)?)Y)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)W)?(?:(\\d+(?:\\.\\d+)?)D)?(?:T(?:(\\d+(?:\\.\\d+)?)H)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)S)?)?$/;\n"]}
@@ -18,6 +18,7 @@ export class Url {
18
18
  /**
19
19
  * Create a new instance of Url.
20
20
  * @param url The url string.
21
+ * @throws GuardError if the url is not valid.
21
22
  */
22
23
  constructor(url) {
23
24
  Guards.stringValue(Url.CLASS_NAME, "url", url);
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","sourceRoot":"","sources":["../../../src/types/url.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 type { IUrlParts } from \"../models/IUrlParts.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\nimport { Coerce } from \"../utils/coerce.js\";\nimport { Guards } from \"../utils/guards.js\";\nimport { Is } from \"../utils/is.js\";\n\n/**\n * Class to help with urls.\n */\nexport class Url {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Url>();\n\n\t/**\n\t * The internal representation of the url.\n\t * @internal\n\t */\n\tprivate _urlParts: IUrlParts;\n\n\t/**\n\t * Create a new instance of Url.\n\t * @param url The url string.\n\t */\n\tconstructor(url: string) {\n\t\tGuards.stringValue(Url.CLASS_NAME, nameof(url), url);\n\n\t\ttry {\n\t\t\tconst u = new URL(url);\n\t\t\tthis._urlParts = Url.fromURLToParts(u);\n\t\t} catch {\n\t\t\tthrow new GuardError(Url.CLASS_NAME, \"guard.url\", \"url\", url);\n\t\t}\n\t}\n\n\t/**\n\t * Try and parse a string into the url parts.\n\t * @param url The url to parse.\n\t * @returns The formatted url or undefined if the value is not a url.\n\t */\n\tpublic static tryParseExact(url: unknown): Url | undefined {\n\t\tif (!Is.stringValue(url)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// By constructing a new standard URL class this will\n\t\t\t// validate the format\n\t\t\tconst u = new URL(url);\n\t\t\treturn Url.fromParts(Url.fromURLToParts(u));\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Parse a string into the url parts.\n\t * @param source The source of the error.\n\t * @param property The name of the property.\n\t * @param value The url 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 = Url.tryParseExact(value);\n\n\t\tif (!result) {\n\t\t\tthrow new GuardError(source, \"guard.url\", property, value);\n\t\t}\n\t}\n\n\t/**\n\t * Validate a string as a Url.\n\t * @param property Throw an exception if the url property is invalid.\n\t * @param value The url 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 url.\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 Url {\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 = Url.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.beUrl\",\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 * Construct a url from a URL.\n\t * @param url The url to construct from.\n\t * @returns The formatted url.\n\t */\n\tpublic static fromURLToParts(url: URL): IUrlParts {\n\t\treturn {\n\t\t\tschema: url.protocol.replace(/:$/, \"\"),\n\t\t\thost: url.hostname,\n\t\t\tport: Coerce.number(url.port),\n\t\t\tpath: url.pathname,\n\t\t\tparams: Is.stringValue(url.search) ? url.search.replace(/^\\?/, \"\") : undefined,\n\t\t\thash: Is.stringValue(url.hash) ? url.hash.replace(/^#/, \"\") : undefined\n\t\t};\n\t}\n\n\t/**\n\t * Construct a url from valid parts.\n\t * @param urlParts The url to create the parts from.\n\t * @returns The formatted url.\n\t */\n\tpublic static fromParts(urlParts: IUrlParts): Url {\n\t\tconst u = new Url(\"http://dummy\");\n\t\tu._urlParts = urlParts;\n\t\treturn u;\n\t}\n\n\t/**\n\t * Get the parts of the url.\n\t * @returns The parts of the url.\n\t */\n\tpublic parts(): IUrlParts {\n\t\treturn this._urlParts;\n\t}\n\n\t/**\n\t * Convert the parts in to a full string.\n\t * @returns The formatted url.\n\t */\n\tpublic toString(): string {\n\t\tconst parts: string[] = [this._urlParts.schema, \"://\", this._urlParts.host];\n\t\tif (Is.number(this._urlParts.port)) {\n\t\t\tparts.push(`:${this._urlParts.port}`);\n\t\t}\n\t\tif (Is.stringValue(this._urlParts.path)) {\n\t\t\tparts.push(this._urlParts.path);\n\t\t}\n\t\tif (Is.stringValue(this._urlParts.params)) {\n\t\t\tparts.push(`?${this._urlParts.params}`);\n\t\t}\n\t\tif (Is.stringValue(this._urlParts.hash)) {\n\t\t\tparts.push(`#${this._urlParts.hash}`);\n\t\t}\n\t\treturn parts.join(\"\");\n\t}\n}\n"]}
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../../src/types/url.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 type { IUrlParts } from \"../models/IUrlParts.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\nimport { Coerce } from \"../utils/coerce.js\";\nimport { Guards } from \"../utils/guards.js\";\nimport { Is } from \"../utils/is.js\";\n\n/**\n * Class to help with urls.\n */\nexport class Url {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<Url>();\n\n\t/**\n\t * The internal representation of the url.\n\t * @internal\n\t */\n\tprivate _urlParts: IUrlParts;\n\n\t/**\n\t * Create a new instance of Url.\n\t * @param url The url string.\n\t * @throws GuardError if the url is not valid.\n\t */\n\tconstructor(url: string) {\n\t\tGuards.stringValue(Url.CLASS_NAME, nameof(url), url);\n\n\t\ttry {\n\t\t\tconst u = new URL(url);\n\t\t\tthis._urlParts = Url.fromURLToParts(u);\n\t\t} catch {\n\t\t\tthrow new GuardError(Url.CLASS_NAME, \"guard.url\", \"url\", url);\n\t\t}\n\t}\n\n\t/**\n\t * Try and parse a string into the url parts.\n\t * @param url The url to parse.\n\t * @returns The formatted url or undefined if the value is not a url.\n\t */\n\tpublic static tryParseExact(url: unknown): Url | undefined {\n\t\tif (!Is.stringValue(url)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// By constructing a new standard URL class this will\n\t\t\t// validate the format\n\t\t\tconst u = new URL(url);\n\t\t\treturn Url.fromParts(Url.fromURLToParts(u));\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Parse a string into the url parts.\n\t * @param source The source of the error.\n\t * @param property The name of the property.\n\t * @param value The url 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 = Url.tryParseExact(value);\n\n\t\tif (!result) {\n\t\t\tthrow new GuardError(source, \"guard.url\", property, value);\n\t\t}\n\t}\n\n\t/**\n\t * Validate a string as a Url.\n\t * @param property Throw an exception if the url property is invalid.\n\t * @param value The url 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 url.\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 Url {\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 = Url.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.beUrl\",\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 * Construct a url from a URL.\n\t * @param url The url to construct from.\n\t * @returns The formatted url.\n\t */\n\tpublic static fromURLToParts(url: URL): IUrlParts {\n\t\treturn {\n\t\t\tschema: url.protocol.replace(/:$/, \"\"),\n\t\t\thost: url.hostname,\n\t\t\tport: Coerce.number(url.port),\n\t\t\tpath: url.pathname,\n\t\t\tparams: Is.stringValue(url.search) ? url.search.replace(/^\\?/, \"\") : undefined,\n\t\t\thash: Is.stringValue(url.hash) ? url.hash.replace(/^#/, \"\") : undefined\n\t\t};\n\t}\n\n\t/**\n\t * Construct a url from valid parts.\n\t * @param urlParts The url to create the parts from.\n\t * @returns The formatted url.\n\t */\n\tpublic static fromParts(urlParts: IUrlParts): Url {\n\t\tconst u = new Url(\"http://dummy\");\n\t\tu._urlParts = urlParts;\n\t\treturn u;\n\t}\n\n\t/**\n\t * Get the parts of the url.\n\t * @returns The parts of the url.\n\t */\n\tpublic parts(): IUrlParts {\n\t\treturn this._urlParts;\n\t}\n\n\t/**\n\t * Convert the parts in to a full string.\n\t * @returns The formatted url.\n\t */\n\tpublic toString(): string {\n\t\tconst parts: string[] = [this._urlParts.schema, \"://\", this._urlParts.host];\n\t\tif (Is.number(this._urlParts.port)) {\n\t\t\tparts.push(`:${this._urlParts.port}`);\n\t\t}\n\t\tif (Is.stringValue(this._urlParts.path)) {\n\t\t\tparts.push(this._urlParts.path);\n\t\t}\n\t\tif (Is.stringValue(this._urlParts.params)) {\n\t\t\tparts.push(`?${this._urlParts.params}`);\n\t\t}\n\t\tif (Is.stringValue(this._urlParts.hash)) {\n\t\t\tparts.push(`#${this._urlParts.hash}`);\n\t\t}\n\t\treturn parts.join(\"\");\n\t}\n}\n"]}
@@ -157,7 +157,7 @@ export class AsyncCache {
157
157
  * @param key The key to set in the cache.
158
158
  * @param value The value to set in the cache.
159
159
  * @param ttlMs The TTL of the entry in the cache in milliseconds. Defaults to 1000 (1 second).
160
- * @returns Nothing.
160
+ * @returns A promise that resolves when the entry has been stored.
161
161
  */
162
162
  static async set(key, value, ttlMs) {
163
163
  const expires = Date.now() + (ttlMs ?? 1000);
@@ -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 * @internal\n\t */\n\tprivate static async resolveWaiter<T>(\n\t\trequestMethod: () => Promise<T>,\n\t\tresolve: (value: T | PromiseLike<T>) => void,\n\t\treject: (reason?: unknown) => void\n\t): Promise<void> {\n\t\ttry {\n\t\t\tresolve(await requestMethod());\n\t\t} catch (waitErr) {\n\t\t\treject(waitErr);\n\t\t}\n\t}\n\n\t/**\n\t * Scan all cache entries, delete any that have expired, and record the key of the\n\t * soonest-expiring remaining entry.\n\t * @internal\n\t */\n\tprivate static recalculateNextKey(): void {\n\t\tconst now = Date.now();\n\t\tconst cache = AsyncCache.getSharedCache();\n\n\t\tif (Object.keys(cache).length === 0) {\n\t\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tlet newNextKey: string | undefined;\n\t\tlet newNextExpiry = Number.MAX_SAFE_INTEGER;\n\t\tfor (const entryKey in cache) {\n\t\t\tconst { expires, inProgress } = cache[entryKey];\n\t\t\tif (expires > 0 && expires < now && inProgress !== true) {\n\t\t\t\tdelete cache[entryKey];\n\t\t\t} else if (expires > 0 && expires < newNextExpiry) {\n\t\t\t\tnewNextExpiry = expires;\n\t\t\t\tnewNextKey = entryKey;\n\t\t\t}\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, newNextKey);\n\t}\n\n\t/**\n\t * Update the tracked next-expiry entry key if the given entry expires sooner than the current one.\n\t * @param key The cache entry key.\n\t * @param expires The expiry timestamp of the entry.\n\t * @internal\n\t */\n\tprivate static updateNextExpiryKey(key: string, expires: number): void {\n\t\tconst expiry = AsyncCache.getNextExpiry();\n\t\tif (expiry !== undefined && expiry <= expires) {\n\t\t\treturn;\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, key);\n\t}\n\n\t/**\n\t * Deletes the given key from the cache if it has expired and is not in-progress.\n\t * Returns true if the entry was evicted.\n\t * @param key The cache entry key to check.\n\t * @returns True if the expired entry was removed; otherwise, false.\n\t * @internal\n\t */\n\tprivate static evictIfExpired(key: string): boolean {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst entry = cache[key];\n\t\tif (!Is.empty(entry) && entry.expires > 0 && entry.expires < Date.now() && !entry.inProgress) {\n\t\t\tdelete cache[key];\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns the expiry timestamp of the tracked next-expiry entry, or undefined if there\n\t * is no tracked entry or it is no longer present in the cache.\n\t * @returns The next tracked expiry timestamp, if one is still available.\n\t * @internal\n\t */\n\tprivate static getNextExpiry(): number | undefined {\n\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\tif (Is.empty(nextKey)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst nextEntry = cache[nextKey];\n\t\treturn Is.empty(nextEntry) ? undefined : nextEntry.expires;\n\t}\n}\n"]}
1
+ {"version":3,"file":"asyncCache.js","sourceRoot":"","sources":["../../../src/utils/asyncCache.ts"],"names":[],"mappings":"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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 A promise that resolves when the entry has been stored.\n\t */\n\tpublic static async set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> {\n\t\tconst expires = Date.now() + (ttlMs ?? 1000);\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tcache[key] = {\n\t\t\tresult: value,\n\t\t\tpromiseQueue: [],\n\t\t\texpires\n\t\t};\n\t\tAsyncCache.updateNextExpiryKey(key, expires);\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * @param key The key to remove from the cache.\n\t */\n\tpublic static remove(key: string): void {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tdelete cache[key];\n\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\tif (nextKey === key) {\n\t\t\tAsyncCache.recalculateNextKey();\n\t\t}\n\t}\n\n\t/**\n\t * Clear the cache.\n\t * @param prefix Optional prefix to clear only entries with that prefix.\n\t */\n\tpublic static clearCache(prefix?: string): void {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tif (Is.stringValue(prefix)) {\n\t\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\t\tlet nextKeyRemoved = false;\n\t\t\tfor (const entry in cache) {\n\t\t\t\tif (entry.startsWith(prefix)) {\n\t\t\t\t\tif (entry === nextKey) {\n\t\t\t\t\t\tnextKeyRemoved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdelete cache[entry];\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (nextKeyRemoved) {\n\t\t\t\tAsyncCache.recalculateNextKey();\n\t\t\t}\n\t\t} else {\n\t\t\tSharedStore.set(AsyncCache._CACHE_KEY, {});\n\t\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);\n\t\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, Date.now());\n\t\t}\n\t}\n\n\t/**\n\t * Perform a cleanup of the expired entries in the cache.\n\t */\n\tpublic static cleanupExpired(): void {\n\t\tconst now = Date.now();\n\t\tconst lastCleanup = SharedStore.get<number>(AsyncCache._LAST_CLEANUP_KEY) ?? 0;\n\t\tif (now - lastCleanup < AsyncCache._CLEANUP_INTERVAL_MS) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst expiry = AsyncCache.getNextExpiry();\n\t\tif (expiry !== undefined && expiry > now) {\n\t\t\t// Nothing is expired yet. Stamp the time so the throttle suppresses further checks\n\t\t\t// for the next interval. Per-key freshness is still guaranteed because exec() and\n\t\t\t// get() call evictIfExpired() directly before reading the cache.\n\t\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);\n\t\t\treturn;\n\t\t}\n\n\t\tSharedStore.set(AsyncCache._LAST_CLEANUP_KEY, now);\n\t\tAsyncCache.recalculateNextKey();\n\t}\n\n\t/**\n\t * Get the shared cache.\n\t * @returns The shared cache.\n\t * @internal\n\t */\n\tprivate static getSharedCache<T = unknown>(): {\n\t\t[url: string]: {\n\t\t\tresult?: T;\n\t\t\terror?: unknown;\n\t\t\tinProgress?: boolean;\n\t\t\tpromiseQueue: {\n\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t}[];\n\t\t\texpires: number;\n\t\t};\n\t} {\n\t\tlet sharedCache = SharedStore.get<{\n\t\t\t[url: string]: {\n\t\t\t\tresult?: T;\n\t\t\t\terror?: unknown;\n\t\t\t\tinProgress?: boolean;\n\t\t\t\tpromiseQueue: {\n\t\t\t\t\trequestMethod: () => Promise<T>;\n\t\t\t\t\tresolve: (value: T | PromiseLike<T>) => void;\n\t\t\t\t\treject: (reason?: unknown) => void;\n\t\t\t\t}[];\n\t\t\t\texpires: number;\n\t\t\t};\n\t\t}>(AsyncCache._CACHE_KEY);\n\n\t\tif (Is.undefined(sharedCache)) {\n\t\t\tsharedCache = {};\n\t\t\tSharedStore.set(AsyncCache._CACHE_KEY, sharedCache);\n\t\t}\n\n\t\treturn sharedCache;\n\t}\n\n\t/**\n\t * Resolve a waiter by re-running its request method safely.\n\t * @param requestMethod The method to execute.\n\t * @param resolve The resolver for the waiter.\n\t * @param reject The rejector for the waiter.\n\t * @internal\n\t */\n\tprivate static async resolveWaiter<T>(\n\t\trequestMethod: () => Promise<T>,\n\t\tresolve: (value: T | PromiseLike<T>) => void,\n\t\treject: (reason?: unknown) => void\n\t): Promise<void> {\n\t\ttry {\n\t\t\tresolve(await requestMethod());\n\t\t} catch (waitErr) {\n\t\t\treject(waitErr);\n\t\t}\n\t}\n\n\t/**\n\t * Scan all cache entries, delete any that have expired, and record the key of the\n\t * soonest-expiring remaining entry.\n\t * @internal\n\t */\n\tprivate static recalculateNextKey(): void {\n\t\tconst now = Date.now();\n\t\tconst cache = AsyncCache.getSharedCache();\n\n\t\tif (Object.keys(cache).length === 0) {\n\t\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tlet newNextKey: string | undefined;\n\t\tlet newNextExpiry = Number.MAX_SAFE_INTEGER;\n\t\tfor (const entryKey in cache) {\n\t\t\tconst { expires, inProgress } = cache[entryKey];\n\t\t\tif (expires > 0 && expires < now && inProgress !== true) {\n\t\t\t\tdelete cache[entryKey];\n\t\t\t} else if (expires > 0 && expires < newNextExpiry) {\n\t\t\t\tnewNextExpiry = expires;\n\t\t\t\tnewNextKey = entryKey;\n\t\t\t}\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, newNextKey);\n\t}\n\n\t/**\n\t * Update the tracked next-expiry entry key if the given entry expires sooner than the current one.\n\t * @param key The cache entry key.\n\t * @param expires The expiry timestamp of the entry.\n\t * @internal\n\t */\n\tprivate static updateNextExpiryKey(key: string, expires: number): void {\n\t\tconst expiry = AsyncCache.getNextExpiry();\n\t\tif (expiry !== undefined && expiry <= expires) {\n\t\t\treturn;\n\t\t}\n\t\tSharedStore.set(AsyncCache._NEXT_EXPIRY_CACHE_KEY, key);\n\t}\n\n\t/**\n\t * Deletes the given key from the cache if it has expired and is not in-progress.\n\t * Returns true if the entry was evicted.\n\t * @param key The cache entry key to check.\n\t * @returns True if the expired entry was removed; otherwise, false.\n\t * @internal\n\t */\n\tprivate static evictIfExpired(key: string): boolean {\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst entry = cache[key];\n\t\tif (!Is.empty(entry) && entry.expires > 0 && entry.expires < Date.now() && !entry.inProgress) {\n\t\t\tdelete cache[key];\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns the expiry timestamp of the tracked next-expiry entry, or undefined if there\n\t * is no tracked entry or it is no longer present in the cache.\n\t * @returns The next tracked expiry timestamp, if one is still available.\n\t * @internal\n\t */\n\tprivate static getNextExpiry(): number | undefined {\n\t\tconst nextKey = SharedStore.get<string>(AsyncCache._NEXT_EXPIRY_CACHE_KEY);\n\t\tif (Is.empty(nextKey)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst cache = AsyncCache.getSharedCache();\n\t\tconst nextEntry = cache[nextKey];\n\t\treturn Is.empty(nextEntry) ? undefined : nextEntry.expires;\n\t}\n}\n"]}
@@ -3,6 +3,7 @@
3
3
  import { Converter } from "./converter.js";
4
4
  import { Is } from "./is.js";
5
5
  import { CoerceType } from "../models/coerceType.js";
6
+ import { Duration } from "../types/duration.js";
6
7
  /**
7
8
  * Coerce an object from one type to another.
8
9
  */
@@ -10,8 +11,7 @@ export class Coerce {
10
11
  /**
11
12
  * Coerce the value to a string.
12
13
  * @param value The value to coerce.
13
- * @throws TypeError If the value can not be coerced.
14
- * @returns The value if it can be coerced.
14
+ * @returns The coerced string, or undefined if the value cannot be coerced.
15
15
  */
16
16
  static string(value) {
17
17
  if (Is.undefined(value)) {
@@ -33,8 +33,7 @@ export class Coerce {
33
33
  /**
34
34
  * Coerce the value to a number.
35
35
  * @param value The value to coerce.
36
- * @throws TypeError If the value can not be coerced.
37
- * @returns The value if it can be coerced.
36
+ * @returns The coerced number, or undefined if the value cannot be coerced.
38
37
  */
39
38
  static number(value) {
40
39
  if (Is.undefined(value)) {
@@ -59,8 +58,7 @@ export class Coerce {
59
58
  /**
60
59
  * Coerce the value to an integer.
61
60
  * @param value The value to coerce.
62
- * @throws TypeError If the value can not be coerced.
63
- * @returns The value if it can be coerced.
61
+ * @returns The coerced integer, or undefined if the value cannot be coerced.
64
62
  */
65
63
  static integer(value) {
66
64
  const num = Coerce.number(value);
@@ -71,8 +69,7 @@ export class Coerce {
71
69
  /**
72
70
  * Coerce the value to a bigint.
73
71
  * @param value The value to coerce.
74
- * @throws TypeError If the value can not be coerced.
75
- * @returns The value if it can be coerced.
72
+ * @returns The coerced bigint, or undefined if the value cannot be coerced.
76
73
  */
77
74
  static bigint(value) {
78
75
  if (Is.undefined(value)) {
@@ -97,8 +94,7 @@ export class Coerce {
97
94
  /**
98
95
  * Coerce the value to a boolean.
99
96
  * @param value The value to coerce.
100
- * @throws TypeError If the value can not be coerced.
101
- * @returns The value if it can be coerced.
97
+ * @returns The coerced boolean, or undefined if the value cannot be coerced.
102
98
  */
103
99
  static boolean(value) {
104
100
  if (Is.undefined(value)) {
@@ -123,8 +119,7 @@ export class Coerce {
123
119
  /**
124
120
  * Coerce the value to a date.
125
121
  * @param value The value to coerce.
126
- * @throws TypeError If the value can not be coerced.
127
- * @returns The value if it can be coerced.
122
+ * @returns The coerced date, or undefined if the value cannot be coerced.
128
123
  */
129
124
  static date(value) {
130
125
  if (Is.undefined(value)) {
@@ -147,8 +142,7 @@ export class Coerce {
147
142
  /**
148
143
  * Coerce the value to a date/time.
149
144
  * @param value The value to coerce.
150
- * @throws TypeError If the value can not be coerced.
151
- * @returns The value if it can be coerced.
145
+ * @returns The coerced date/time, or undefined if the value cannot be coerced.
152
146
  */
153
147
  static dateTime(value) {
154
148
  if (Is.undefined(value)) {
@@ -171,8 +165,7 @@ export class Coerce {
171
165
  /**
172
166
  * Coerce the value to a time.
173
167
  * @param value The value to coerce.
174
- * @throws TypeError If the value can not be coerced.
175
- * @returns The value if it can be coerced.
168
+ * @returns The coerced time, or undefined if the value cannot be coerced.
176
169
  */
177
170
  static time(value) {
178
171
  if (Is.undefined(value)) {
@@ -194,11 +187,31 @@ export class Coerce {
194
187
  }
195
188
  }
196
189
  }
190
+ /**
191
+ * Coerce the value to a duration object.
192
+ * Accepts an IDuration object, ISO 8601 duration strings (e.g. "PT1H", "P1Y2M3DT4H5M6S"),
193
+ * or numeric values already expressed as seconds (stored in the seconds field).
194
+ * @param value The value to coerce.
195
+ * @returns The duration object, or undefined if the value cannot be coerced.
196
+ */
197
+ static duration(value) {
198
+ if (Is.undefined(value)) {
199
+ return undefined;
200
+ }
201
+ if (Is.duration(value) && !Is.string(value)) {
202
+ return value;
203
+ }
204
+ if (Is.number(value)) {
205
+ return { years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: value };
206
+ }
207
+ if (Is.stringValue(value)) {
208
+ return Duration.parse(value);
209
+ }
210
+ }
197
211
  /**
198
212
  * Coerce the value to an object.
199
213
  * @param value The value to coerce.
200
- * @throws TypeError If the value can not be coerced.
201
- * @returns The value if it can be coerced.
214
+ * @returns The coerced object, or undefined if the value cannot be coerced.
202
215
  */
203
216
  static object(value) {
204
217
  if (Is.undefined(value)) {
@@ -217,8 +230,7 @@ export class Coerce {
217
230
  /**
218
231
  * Coerce the value to a Uint8Array.
219
232
  * @param value The value to coerce.
220
- * @throws TypeError If the value can not be coerced.
221
- * @returns The value if it can be coerced.
233
+ * @returns The coerced Uint8Array, or undefined if the value cannot be coerced.
222
234
  */
223
235
  static uint8Array(value) {
224
236
  if (Is.undefined(value)) {
@@ -261,6 +273,8 @@ export class Coerce {
261
273
  return Coerce.object(value);
262
274
  case CoerceType.Uint8Array:
263
275
  return Coerce.uint8Array(value);
276
+ case CoerceType.Duration:
277
+ return Coerce.duration(value);
264
278
  default:
265
279
  return value;
266
280
  }