@twin.org/core 0.0.1-next.14 → 0.0.1-next.16

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.
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var intlMessageformat = require('intl-messageformat');
4
3
  var rfc6902 = require('rfc6902');
4
+ var intlMessageformat = require('intl-messageformat');
5
5
 
6
6
  // Copyright 2024 IOTA Stiftung.
7
7
  // SPDX-License-Identifier: Apache-2.0.
@@ -2064,987 +2064,1023 @@ class ArrayHelper {
2064
2064
  // Copyright 2024 IOTA Stiftung.
2065
2065
  // SPDX-License-Identifier: Apache-2.0.
2066
2066
  /**
2067
- * Class to perform internationalization.
2067
+ * Helpers methods for JSON objects.
2068
2068
  */
2069
- class I18n {
2069
+ class JsonHelper {
2070
2070
  /**
2071
- * The default translation.
2071
+ * Serializes in canonical format.
2072
+ * Based on https://www.rfc-editor.org/rfc/rfc8785.
2073
+ * @param object The object to be serialized.
2074
+ * @returns The serialized object.
2072
2075
  */
2073
- static DEFAULT_LOCALE = "en";
2076
+ static canonicalize(object) {
2077
+ const buffer = [];
2078
+ if (object === null ||
2079
+ typeof object !== "object" ||
2080
+ ("toJSON" in object && object.toJSON instanceof Function)) {
2081
+ // Primitive data type
2082
+ buffer.push(JSON.stringify(object));
2083
+ }
2084
+ else if (Array.isArray(object)) {
2085
+ // Array maintain element order
2086
+ const parts = [];
2087
+ for (const element of object) {
2088
+ if (element === undefined) {
2089
+ parts.push("null");
2090
+ }
2091
+ else {
2092
+ parts.push(JsonHelper.canonicalize(element));
2093
+ }
2094
+ }
2095
+ buffer.push(`[${parts.join(",")}]`);
2096
+ }
2097
+ else {
2098
+ // Object sort properties
2099
+ const props = [];
2100
+ const keys = Object.keys(object).sort();
2101
+ const o = object;
2102
+ for (const key of keys) {
2103
+ if (o[key] !== undefined) {
2104
+ props.push(`${JSON.stringify(key)}:${JsonHelper.canonicalize(o[key])}`);
2105
+ }
2106
+ }
2107
+ buffer.push(`{${props.join(",")}}`);
2108
+ }
2109
+ return buffer.join("");
2110
+ }
2074
2111
  /**
2075
- * Dictionaries for lookups.
2076
- * @internal
2112
+ * Creates a RFC 6902 diff set.
2113
+ * Based on https://www.rfc-editor.org/rfc/rfc6902.
2114
+ * @param object1 The first object.
2115
+ * @param object2 The second object.
2116
+ * @returns The list of patches.
2077
2117
  */
2078
- static _localeDictionaries = {};
2118
+ static diff(object1, object2) {
2119
+ const operations = rfc6902.createPatch(object1, object2);
2120
+ return operations;
2121
+ }
2079
2122
  /**
2080
- * The current locale.
2081
- * @internal
2123
+ * Applies a RFC 6902 diff set to an object.
2124
+ * Based on https://www.rfc-editor.org/rfc/rfc6902.
2125
+ * @param object The object to patch.
2126
+ * @param patches The second object.
2127
+ * @returns The updated object.
2082
2128
  */
2083
- static _currentLocale = I18n.DEFAULT_LOCALE;
2129
+ static patch(object, patches) {
2130
+ return rfc6902.applyPatch(object, patches);
2131
+ }
2132
+ }
2133
+
2134
+ // Copyright 2024 IOTA Stiftung.
2135
+ // SPDX-License-Identifier: Apache-2.0.
2136
+ /* eslint-disable no-bitwise */
2137
+ /**
2138
+ * Convert arrays to and from different formats.
2139
+ */
2140
+ class Converter {
2084
2141
  /**
2085
- * Change handler for the locale being updated.
2142
+ * Lookup table for encoding.
2086
2143
  * @internal
2087
2144
  */
2088
- static _localeChangedHandlers = {};
2145
+ static _ENCODE_LOOKUP;
2089
2146
  /**
2090
- * Change handler for the dictionaries being updated.
2147
+ * Lookup table for decoding.
2091
2148
  * @internal
2092
2149
  */
2093
- static _dictionaryChangedHandlers = {};
2150
+ static _DECODE_LOOKUP;
2094
2151
  /**
2095
- * Set the locale.
2096
- * @param locale The new locale.
2152
+ * Encode a raw array to UTF8 string.
2153
+ * @param array The bytes to encode.
2154
+ * @param startIndex The index to start in the bytes.
2155
+ * @param length The length of bytes to read.
2156
+ * @returns The array formatted as UTF8.
2097
2157
  */
2098
- static setLocale(locale) {
2099
- I18n._currentLocale = locale;
2100
- for (const callback in I18n._localeChangedHandlers) {
2101
- I18n._localeChangedHandlers[callback](I18n._currentLocale);
2158
+ static bytesToUtf8(array, startIndex, length) {
2159
+ const start = startIndex ?? 0;
2160
+ const len = length ?? array.length;
2161
+ let str = "";
2162
+ for (let i = start; i < start + len; i++) {
2163
+ const value = array[i];
2164
+ if (value < 0x80) {
2165
+ str += String.fromCharCode(value);
2166
+ }
2167
+ else if (value > 0xbf && value < 0xe0) {
2168
+ str += String.fromCharCode(((value & 0x1f) << 6) | (array[i + 1] & 0x3f));
2169
+ i += 1;
2170
+ }
2171
+ else if (value > 0xdf && value < 0xf0) {
2172
+ str += String.fromCharCode(((value & 0x0f) << 12) | ((array[i + 1] & 0x3f) << 6) | (array[i + 2] & 0x3f));
2173
+ i += 2;
2174
+ }
2175
+ else {
2176
+ // surrogate pair
2177
+ const charCode = (((value & 0x07) << 18) |
2178
+ ((array[i + 1] & 0x3f) << 12) |
2179
+ ((array[i + 2] & 0x3f) << 6) |
2180
+ (array[i + 3] & 0x3f)) -
2181
+ 0x010000;
2182
+ str += String.fromCharCode((charCode >> 10) | 0xd800, (charCode & 0x03ff) | 0xdc00);
2183
+ i += 3;
2184
+ }
2102
2185
  }
2186
+ return str;
2103
2187
  }
2104
2188
  /**
2105
- * Get the locale.
2106
- * @returns The current locale.
2189
+ * Convert a UTF8 string to raw array.
2190
+ * @param utf8 The text to decode.
2191
+ * @returns The array.
2107
2192
  */
2108
- static getLocale() {
2109
- return I18n._currentLocale;
2193
+ static utf8ToBytes(utf8) {
2194
+ const bytes = [];
2195
+ for (let i = 0; i < utf8.length; i++) {
2196
+ let charCode = utf8.charCodeAt(i);
2197
+ if (charCode < 0x80) {
2198
+ bytes.push(charCode);
2199
+ }
2200
+ else if (charCode < 0x800) {
2201
+ bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
2202
+ }
2203
+ else if (charCode < 0xd800 || charCode >= 0xe000) {
2204
+ bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2205
+ }
2206
+ else {
2207
+ // surrogate pair
2208
+ i++;
2209
+ // UTF-16 encodes 0x10000-0x10FFFF by
2210
+ // subtracting 0x10000 and splitting the
2211
+ // 20 bits of 0x0-0xFFFFF into two halves
2212
+ charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (utf8.charCodeAt(i) & 0x3ff));
2213
+ bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2214
+ }
2215
+ }
2216
+ return Uint8Array.from(bytes);
2110
2217
  }
2111
2218
  /**
2112
- * Add a locale dictionary.
2113
- * @param locale The locale.
2114
- * @param dictionary The dictionary to add.
2219
+ * Encode a raw array to hex string.
2220
+ * @param array The bytes to encode.
2221
+ * @param includePrefix Include the 0x prefix on the returned hex.
2222
+ * @param startIndex The index to start in the bytes.
2223
+ * @param length The length of bytes to read.
2224
+ * @param reverse Reverse the combine direction.
2225
+ * @returns The array formatted as hex.
2115
2226
  */
2116
- static addDictionary(locale, dictionary) {
2117
- const mergedKeys = {};
2118
- I18n.flattenTranslationKeys(dictionary, "", mergedKeys);
2119
- I18n._localeDictionaries[locale] = mergedKeys;
2120
- for (const callback in I18n._dictionaryChangedHandlers) {
2121
- I18n._dictionaryChangedHandlers[callback](I18n._currentLocale);
2227
+ static bytesToHex(array, includePrefix = false, startIndex, length, reverse) {
2228
+ let hex = "";
2229
+ this.buildHexLookups();
2230
+ if (Converter._ENCODE_LOOKUP) {
2231
+ const len = length ?? array.length;
2232
+ const start = startIndex ?? 0;
2233
+ if (reverse) {
2234
+ for (let i = 0; i < len; i++) {
2235
+ hex = Converter._ENCODE_LOOKUP[array[start + i]] + hex;
2236
+ }
2237
+ }
2238
+ else {
2239
+ for (let i = 0; i < len; i++) {
2240
+ hex += Converter._ENCODE_LOOKUP[array[start + i]];
2241
+ }
2242
+ }
2122
2243
  }
2244
+ return includePrefix ? HexHelper.addPrefix(hex) : hex;
2123
2245
  }
2124
2246
  /**
2125
- * Get a locale dictionary.
2126
- * @param locale The locale.
2127
- * @returns The dictionary of undefined if it does not exist.
2247
+ * Decode a hex string to raw array.
2248
+ * @param hex The hex to decode.
2249
+ * @param reverse Store the characters in reverse.
2250
+ * @returns The array.
2128
2251
  */
2129
- static getDictionary(locale) {
2130
- return I18n._localeDictionaries[locale];
2252
+ static hexToBytes(hex, reverse) {
2253
+ const strippedHex = HexHelper.stripPrefix(hex);
2254
+ const sizeof = strippedHex.length >> 1;
2255
+ const length = sizeof << 1;
2256
+ const array = new Uint8Array(sizeof);
2257
+ this.buildHexLookups();
2258
+ if (Converter._DECODE_LOOKUP) {
2259
+ let i = 0;
2260
+ let n = 0;
2261
+ while (i < length) {
2262
+ array[n++] =
2263
+ (Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)] << 4) |
2264
+ Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)];
2265
+ }
2266
+ if (reverse) {
2267
+ array.reverse();
2268
+ }
2269
+ }
2270
+ return array;
2131
2271
  }
2132
2272
  /**
2133
- * Get all the locale dictionaries.
2134
- * @returns The dictionaries.
2273
+ * Convert the UTF8 to hex.
2274
+ * @param utf8 The text to convert.
2275
+ * @param includePrefix Include the 0x prefix on the returned hex.
2276
+ * @returns The hex version of the bytes.
2135
2277
  */
2136
- static getAllDictionaries() {
2137
- return I18n._localeDictionaries;
2278
+ static utf8ToHex(utf8, includePrefix = false) {
2279
+ const hex = Converter.bytesToHex(Converter.utf8ToBytes(utf8));
2280
+ return includePrefix ? HexHelper.addPrefix(hex) : hex;
2138
2281
  }
2139
2282
  /**
2140
- * Add a locale changed handler.
2141
- * @param id The id of the handler.
2142
- * @param handler The handler to add.
2283
+ * Convert the hex text to text.
2284
+ * @param hex The hex to convert.
2285
+ * @returns The UTF8 version of the bytes.
2143
2286
  */
2144
- static addLocaleHandler(id, handler) {
2145
- I18n._localeChangedHandlers[id] = handler;
2287
+ static hexToUtf8(hex) {
2288
+ return Converter.bytesToUtf8(Converter.hexToBytes(HexHelper.stripPrefix(hex)));
2146
2289
  }
2147
2290
  /**
2148
- * Remove a locale changed handler.
2149
- * @param id The id of the handler.
2291
+ * Convert bytes to binary string.
2292
+ * @param bytes The bytes to convert.
2293
+ * @returns A binary string of the bytes.
2150
2294
  */
2151
- static removeLocaleHandler(id) {
2152
- delete I18n._localeChangedHandlers[id];
2295
+ static bytesToBinary(bytes) {
2296
+ const b = [];
2297
+ for (let i = 0; i < bytes.length; i++) {
2298
+ b.push(bytes[i].toString(2).padStart(8, "0"));
2299
+ }
2300
+ return b.join("");
2153
2301
  }
2154
2302
  /**
2155
- * Add a dictionary changed handler.
2156
- * @param id The id of the handler.
2157
- * @param handler The handler to add.
2158
- */
2159
- static addDictionaryHandler(id, handler) {
2160
- I18n._dictionaryChangedHandlers[id] = handler;
2303
+ * Convert a binary string to bytes.
2304
+ * @param binary The binary string.
2305
+ * @returns The bytes.
2306
+ */
2307
+ static binaryToBytes(binary) {
2308
+ const bytes = new Uint8Array(Math.ceil(binary.length / 8));
2309
+ for (let i = 0; i < bytes.length; i++) {
2310
+ bytes[i] = Number.parseInt(binary.slice(i * 8, (i + 1) * 8), 2);
2311
+ }
2312
+ return bytes;
2161
2313
  }
2162
2314
  /**
2163
- * Remove a dictionary changed handler.
2164
- * @param id The id of the handler.
2315
+ * Convert bytes to base64 string.
2316
+ * @param bytes The bytes to convert.
2317
+ * @returns A base64 string of the bytes.
2165
2318
  */
2166
- static removeDictionaryHandler(id) {
2167
- delete I18n._dictionaryChangedHandlers[id];
2319
+ static bytesToBase64(bytes) {
2320
+ return Base64.encode(bytes);
2168
2321
  }
2169
2322
  /**
2170
- * Format a message.
2171
- * @param key The key of the message to format.
2172
- * @param values The values to substitute into the message.
2173
- * @param overrideLocale Override the locale.
2174
- * @returns The formatted string.
2323
+ * Convert a base64 string to bytes.
2324
+ * @param base64 The base64 string.
2325
+ * @returns The bytes.
2175
2326
  */
2176
- static formatMessage(key, values, overrideLocale) {
2177
- let cl = overrideLocale ?? I18n._currentLocale;
2178
- if (cl.startsWith("debug-")) {
2179
- cl = I18n.DEFAULT_LOCALE;
2180
- }
2181
- if (!I18n._localeDictionaries[cl]) {
2182
- return `!!Missing ${cl}`;
2183
- }
2184
- if (!I18n._localeDictionaries[cl][key]) {
2185
- return `!!Missing ${cl}.${key}`;
2186
- }
2187
- if (I18n._currentLocale === "debug-k") {
2188
- return key;
2189
- }
2190
- let ret = new intlMessageformat.IntlMessageFormat(I18n._localeDictionaries[cl][key], cl).format(values);
2191
- if (I18n._currentLocale === "debug-x") {
2192
- ret = ret.replace(/[a-z]/g, "x").replace(/[A-Z]/g, "x").replace(/\d/g, "n");
2193
- }
2194
- return ret;
2327
+ static base64ToBytes(base64) {
2328
+ return Base64.decode(base64);
2195
2329
  }
2196
2330
  /**
2197
- * Check if the dictionaries have a message for the given key.
2198
- * @param key The key to check for existence.
2199
- * @returns True if the key exists.
2331
+ * Convert bytes to base64 url string.
2332
+ * @param bytes The bytes to convert.
2333
+ * @returns A base64 url string of the bytes.
2200
2334
  */
2201
- static hasMessage(key) {
2202
- return Is.string(I18n._localeDictionaries[I18n._currentLocale]?.[key]);
2335
+ static bytesToBase64Url(bytes) {
2336
+ return Base64Url.encode(bytes);
2203
2337
  }
2204
2338
  /**
2205
- * Flatten the translation property paths for faster lookup.
2206
- * @param translation The translation to merge.
2207
- * @param propertyPath The current root path.
2208
- * @param mergedKeys The merged keys dictionary to populate.
2209
- * @internal
2339
+ * Convert a base64 url string to bytes.
2340
+ * @param base64Url The base64 url string.
2341
+ * @returns The bytes.
2210
2342
  */
2211
- static flattenTranslationKeys(translation, propertyPath, mergedKeys) {
2212
- for (const key in translation) {
2213
- const val = translation[key];
2214
- const mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;
2215
- if (Is.string(val)) {
2216
- mergedKeys[mergedPath] = val;
2217
- }
2218
- else if (Is.object(val)) {
2219
- I18n.flattenTranslationKeys(val, mergedPath, mergedKeys);
2220
- }
2221
- }
2343
+ static base64UrlToBytes(base64Url) {
2344
+ return Base64Url.decode(base64Url);
2222
2345
  }
2223
- }
2224
-
2225
- // Copyright 2024 IOTA Stiftung.
2226
- // SPDX-License-Identifier: Apache-2.0.
2227
- /**
2228
- * Error helper functions.
2229
- */
2230
- class ErrorHelper {
2231
2346
  /**
2232
- * Format Errors and returns just their messages.
2233
- * @param error The error to format.
2234
- * @returns The error formatted including any inner errors.
2347
+ * Convert bytes to base58 string.
2348
+ * @param bytes The bytes to convert.
2349
+ * @returns A base58 string of the bytes.
2235
2350
  */
2236
- static formatErrors(error) {
2237
- return ErrorHelper.localizeErrors(error).map(e => e.message);
2351
+ static bytesToBase58(bytes) {
2352
+ return Base58.encode(bytes);
2238
2353
  }
2239
2354
  /**
2240
- * Localize the content of an error and any inner errors.
2241
- * @param error The error to format.
2242
- * @returns The localized version of the errors flattened.
2355
+ * Convert a base58 string to bytes.
2356
+ * @param base58 The base58 string.
2357
+ * @returns The bytes.
2243
2358
  */
2244
- static localizeErrors(error) {
2245
- const formattedErrors = [];
2246
- if (Is.notEmpty(error)) {
2247
- const errors = BaseError.flatten(error);
2248
- for (const err of errors) {
2249
- const errorNameKey = `errorNames.${StringHelper.camelCase(err.name)}`;
2250
- const errorMessageKey = `error.${err.message}`;
2251
- // If there is no error message then it is probably
2252
- // from a 3rd party lib, so don't format it just display
2253
- const hasErrorName = I18n.hasMessage(errorNameKey);
2254
- const hasErrorMessage = I18n.hasMessage(errorMessageKey);
2255
- const localizedError = {
2256
- name: I18n.formatMessage(hasErrorName ? errorNameKey : "errorNames.error"),
2257
- message: hasErrorMessage
2258
- ? I18n.formatMessage(errorMessageKey, err.properties)
2259
- : err.message
2260
- };
2261
- if (Is.stringValue(err.source)) {
2262
- localizedError.source = err.source;
2263
- }
2264
- if (Is.stringValue(err.stack)) {
2265
- // Remove the first line from the stack traces as they
2266
- // just have the error type and message duplicated
2267
- const lines = err.stack.split("\n");
2268
- lines.shift();
2269
- localizedError.stack = lines.join("\n");
2270
- }
2271
- const additional = ErrorHelper.formatValidationErrors(err);
2272
- if (Is.stringValue(additional)) {
2273
- localizedError.additional = additional;
2274
- }
2275
- formattedErrors.push(localizedError);
2276
- }
2277
- }
2278
- return formattedErrors;
2359
+ static base58ToBytes(base58) {
2360
+ return Base58.decode(base58);
2279
2361
  }
2280
2362
  /**
2281
- * Localize the content of an error and any inner errors.
2282
- * @param error The error to format.
2283
- * @returns The localized version of the errors flattened.
2363
+ * Build the static lookup tables.
2364
+ * @internal
2284
2365
  */
2285
- static formatValidationErrors(error) {
2286
- if (Is.object(error.properties) &&
2287
- Object.keys(error.properties).length > 0 &&
2288
- Is.object(error.properties) &&
2289
- Is.arrayValue(error.properties.validationFailures)) {
2290
- const validationErrors = [];
2291
- for (const validationFailure of error.properties.validationFailures) {
2292
- const errorI18n = `error.${validationFailure.reason}`;
2293
- const errorMessage = I18n.hasMessage(errorI18n)
2294
- ? I18n.formatMessage(errorI18n, validationFailure.properties)
2295
- : errorI18n;
2296
- let v = `${validationFailure.property}: ${errorMessage}`;
2297
- if (Is.object(validationFailure.properties) &&
2298
- Is.notEmpty(validationFailure.properties.value)) {
2299
- v += ` = ${JSON.stringify(validationFailure.properties.value)}`;
2366
+ static buildHexLookups() {
2367
+ if (!Converter._ENCODE_LOOKUP || !Converter._DECODE_LOOKUP) {
2368
+ const alphabet = "0123456789abcdef";
2369
+ Converter._ENCODE_LOOKUP = [];
2370
+ Converter._DECODE_LOOKUP = [];
2371
+ for (let i = 0; i < 256; i++) {
2372
+ Converter._ENCODE_LOOKUP[i] = alphabet[(i >> 4) & 0xf] + alphabet[i & 0xf];
2373
+ if (i < 16) {
2374
+ if (i < 10) {
2375
+ Converter._DECODE_LOOKUP[0x30 + i] = i;
2376
+ }
2377
+ else {
2378
+ Converter._DECODE_LOOKUP[0x61 - 10 + i] = i;
2379
+ }
2300
2380
  }
2301
- validationErrors.push(v);
2302
2381
  }
2303
- return validationErrors.join("\n");
2304
2382
  }
2305
2383
  }
2306
2384
  }
2307
2385
 
2308
- // Copyright 2024 IOTA Stiftung.
2309
- // SPDX-License-Identifier: Apache-2.0.
2310
2386
  /**
2311
- * Coerce an object from one type to another.
2387
+ * Class to help with objects.
2312
2388
  */
2313
- class Coerce {
2389
+ class ObjectHelper {
2314
2390
  /**
2315
- * Coerce the value to a string.
2316
- * @param value The value to coerce.
2317
- * @throws TypeError If the value can not be coerced.
2318
- * @returns The value if it can be coerced.
2391
+ * Runtime name for the class.
2392
+ * @internal
2319
2393
  */
2320
- static string(value) {
2321
- if (Is.undefined(value)) {
2322
- return value;
2323
- }
2324
- if (Is.string(value)) {
2325
- return value;
2326
- }
2327
- if (Is.number(value)) {
2328
- return value.toString();
2329
- }
2330
- if (Is.boolean(value)) {
2331
- return value ? "true" : "false";
2332
- }
2333
- if (Is.date(value)) {
2334
- return value.toISOString();
2394
+ static _CLASS_NAME = "ObjectHelper";
2395
+ /**
2396
+ * Convert an object to bytes.
2397
+ * @param obj The object to convert.
2398
+ * @param format Format the JSON content.
2399
+ * @returns The object as bytes.
2400
+ */
2401
+ static toBytes(obj, format = false) {
2402
+ if (obj === undefined) {
2403
+ return new Uint8Array();
2335
2404
  }
2405
+ const json = format ? JSON.stringify(obj, undefined, "\t") : JSON.stringify(obj);
2406
+ return Converter.utf8ToBytes(json);
2336
2407
  }
2337
2408
  /**
2338
- * Coerce the value to a number.
2339
- * @param value The value to coerce.
2340
- * @throws TypeError If the value can not be coerced.
2341
- * @returns The value if it can be coerced.
2409
+ * Convert a bytes to an object.
2410
+ * @param bytes The bytes to convert to an object.
2411
+ * @returns The object.
2412
+ * @throws GeneralError if there was an error parsing the JSON.
2342
2413
  */
2343
- static number(value) {
2344
- if (Is.undefined(value)) {
2345
- return value;
2346
- }
2347
- if (Is.number(value)) {
2348
- return value;
2349
- }
2350
- if (Is.string(value)) {
2351
- const parsed = Number.parseFloat(value);
2352
- if (Is.number(parsed)) {
2353
- return parsed;
2354
- }
2414
+ static fromBytes(bytes) {
2415
+ if (Is.empty(bytes) || bytes.length === 0) {
2416
+ return undefined;
2355
2417
  }
2356
- if (Is.boolean(value)) {
2357
- return value ? 1 : 0;
2418
+ try {
2419
+ const utf8 = Converter.bytesToUtf8(bytes);
2420
+ return JSON.parse(utf8);
2358
2421
  }
2359
- if (Is.date(value)) {
2360
- return value.getTime();
2422
+ catch (err) {
2423
+ throw new GeneralError(ObjectHelper._CLASS_NAME, "failedBytesToJSON", undefined, err);
2361
2424
  }
2362
2425
  }
2363
2426
  /**
2364
- * Coerce the value to a bigint.
2365
- * @param value The value to coerce.
2366
- * @throws TypeError If the value can not be coerced.
2367
- * @returns The value if it can be coerced.
2427
+ * Make a deep clone of an object.
2428
+ * @param obj The object to clone.
2429
+ * @returns The objects clone.
2368
2430
  */
2369
- static bigint(value) {
2370
- if (Is.undefined(value)) {
2371
- return value;
2372
- }
2373
- if (Is.bigint(value)) {
2374
- return value;
2375
- }
2376
- if (Is.number(value)) {
2377
- return BigInt(value);
2378
- }
2379
- if (Is.string(value)) {
2380
- const parsed = Number.parseFloat(value);
2381
- if (Is.integer(parsed)) {
2382
- return BigInt(parsed);
2383
- }
2384
- }
2385
- if (Is.boolean(value)) {
2386
- return value ? 1n : 0n;
2431
+ static clone(obj) {
2432
+ if (Is.undefined(obj)) {
2433
+ return undefined;
2387
2434
  }
2435
+ return structuredClone(obj);
2388
2436
  }
2389
2437
  /**
2390
- * Coerce the value to a boolean.
2391
- * @param value The value to coerce.
2392
- * @throws TypeError If the value can not be coerced.
2393
- * @returns The value if it can be coerced.
2438
+ * Deep merge objects.
2439
+ * @param obj1 The first object to merge.
2440
+ * @param obj2 The second object to merge.
2441
+ * @returns The combined deep merge of the objects.
2394
2442
  */
2395
- static boolean(value) {
2396
- if (Is.undefined(value)) {
2397
- return value;
2398
- }
2399
- if (Is.boolean(value)) {
2400
- return value;
2443
+ static merge(obj1, obj2) {
2444
+ if (Is.empty(obj1)) {
2445
+ return ObjectHelper.clone(obj2);
2401
2446
  }
2402
- if (Is.number(value)) {
2403
- // eslint-disable-next-line no-unneeded-ternary
2404
- return value ? true : false;
2447
+ if (Is.empty(obj2)) {
2448
+ return ObjectHelper.clone(obj1);
2405
2449
  }
2406
- if (Is.string(value)) {
2407
- if (/true/i.test(value)) {
2408
- return true;
2409
- }
2410
- if (/false/i.test(value)) {
2411
- return false;
2450
+ const obj1Clone = ObjectHelper.clone(obj1);
2451
+ if (Is.object(obj1Clone) && Is.object(obj2)) {
2452
+ const keys = Object.keys(obj2);
2453
+ for (const key of keys) {
2454
+ if (Is.object(obj1Clone[key]) && Is.object(obj2[key])) {
2455
+ ObjectHelper.propertySet(obj1Clone, key, ObjectHelper.merge(obj1Clone[key], obj2[key]));
2456
+ }
2457
+ else {
2458
+ ObjectHelper.propertySet(obj1Clone, key, obj2[key]);
2459
+ }
2412
2460
  }
2413
2461
  }
2462
+ return obj1Clone;
2414
2463
  }
2415
2464
  /**
2416
- * Coerce the value to a date.
2417
- * @param value The value to coerce.
2418
- * @throws TypeError If the value can not be coerced.
2419
- * @returns The value if it can be coerced.
2465
+ * Does one object equal another.
2466
+ * @param obj1 The first object to compare.
2467
+ * @param obj2 The second object to compare.
2468
+ * @param strictPropertyOrder Should the properties be in the same order, defaults to true.
2469
+ * @returns True is the objects are equal.
2420
2470
  */
2421
- static date(value) {
2422
- if (Is.undefined(value)) {
2423
- return value;
2424
- }
2425
- if (Is.date(value)) {
2426
- return value;
2427
- }
2428
- if (Is.number(value)) {
2429
- return new Date(value);
2430
- }
2431
- if (Is.string(value)) {
2432
- const dt = new Date(value);
2433
- if (!Number.isNaN(dt.getTime())) {
2434
- const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate());
2435
- return new Date(utc);
2436
- }
2471
+ static equal(obj1, obj2, strictPropertyOrder) {
2472
+ if (strictPropertyOrder ?? true) {
2473
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
2437
2474
  }
2475
+ return JsonHelper.canonicalize(obj1) === JsonHelper.canonicalize(obj2);
2438
2476
  }
2439
2477
  /**
2440
- * Coerce the value to a date/time.
2441
- * @param value The value to coerce.
2442
- * @throws TypeError If the value can not be coerced.
2443
- * @returns The value if it can be coerced.
2478
+ * Get the property of an unknown object.
2479
+ * @param obj The object to get the property from.
2480
+ * @param property The property to get, can be separated by dots for nested path.
2481
+ * @returns The property.
2444
2482
  */
2445
- static dateTime(value) {
2446
- if (Is.undefined(value)) {
2483
+ static propertyGet(obj, property) {
2484
+ if (property.includes(".")) {
2485
+ const parts = property.split(".");
2486
+ let value = obj;
2487
+ for (const part of parts) {
2488
+ if (Is.object(value)) {
2489
+ value = value[part];
2490
+ }
2491
+ else {
2492
+ return undefined;
2493
+ }
2494
+ }
2447
2495
  return value;
2448
2496
  }
2449
- if (Is.date(value)) {
2450
- return value;
2497
+ return Is.object(obj) ? obj[property] : undefined;
2498
+ }
2499
+ /**
2500
+ * Set the property of an unknown object.
2501
+ * @param obj The object to set the property from.
2502
+ * @param property The property to set.
2503
+ * @param value The value to set.
2504
+ */
2505
+ static propertySet(obj, property, value) {
2506
+ if (Is.object(obj)) {
2507
+ obj[property] = value;
2451
2508
  }
2452
- if (Is.number(value)) {
2453
- return new Date(value);
2509
+ }
2510
+ /**
2511
+ * Delete the property of an unknown object.
2512
+ * @param obj The object to set the property from.
2513
+ * @param property The property to set
2514
+ */
2515
+ static propertyDelete(obj, property) {
2516
+ if (Is.object(obj)) {
2517
+ delete obj[property];
2454
2518
  }
2455
- if (Is.string(value)) {
2456
- const dt = new Date(value);
2457
- if (!Number.isNaN(dt.getTime())) {
2458
- const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate(), dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
2459
- return new Date(utc);
2519
+ }
2520
+ /**
2521
+ * Extract a property from the object, providing alternative names.
2522
+ * @param obj The object to extract from.
2523
+ * @param propertyNames The possible names for the property.
2524
+ * @param removeProperties Remove the properties from the object, defaults to true.
2525
+ * @returns The property if available.
2526
+ */
2527
+ static extractProperty(obj, propertyNames, removeProperties = true) {
2528
+ let retVal;
2529
+ if (Is.object(obj)) {
2530
+ const names = Is.string(propertyNames) ? [propertyNames] : propertyNames;
2531
+ for (const prop of names) {
2532
+ retVal ??= ObjectHelper.propertyGet(obj, prop);
2533
+ if (removeProperties) {
2534
+ ObjectHelper.propertyDelete(obj, prop);
2535
+ }
2460
2536
  }
2461
2537
  }
2538
+ return retVal;
2462
2539
  }
2463
2540
  /**
2464
- * Coerce the value to a time.
2465
- * @param value The value to coerce.
2466
- * @throws TypeError If the value can not be coerced.
2467
- * @returns The value if it can be coerced.
2541
+ * Pick a subset of properties from an object.
2542
+ * @param obj The object to pick the properties from.
2543
+ * @param keys The property keys to pick.
2544
+ * @returns The partial object.
2468
2545
  */
2469
- static time(value) {
2470
- if (Is.undefined(value)) {
2471
- return value;
2472
- }
2473
- if (Is.date(value)) {
2474
- return value;
2475
- }
2476
- if (Is.number(value)) {
2477
- const dt = new Date(value);
2478
- dt.setFullYear(1970, 0, 1);
2479
- return dt;
2480
- }
2481
- if (Is.string(value)) {
2482
- const dt = new Date(value);
2483
- if (!Number.isNaN(dt.getTime())) {
2484
- const utc = Date.UTC(1970, 0, 1, dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
2485
- return new Date(utc);
2546
+ static pick(obj, keys) {
2547
+ if (Is.object(obj) && Is.arrayValue(keys)) {
2548
+ const result = {};
2549
+ for (const key of keys) {
2550
+ result[key] = obj[key];
2486
2551
  }
2552
+ return result;
2487
2553
  }
2554
+ return obj;
2488
2555
  }
2489
2556
  /**
2490
- * Coerce the value to an object.
2491
- * @param value The value to coerce.
2492
- * @throws TypeError If the value can not be coerced.
2493
- * @returns The value if it can be coerced.
2557
+ * Omit a subset of properties from an object.
2558
+ * @param obj The object to omit the properties from.
2559
+ * @param keys The property keys to omit.
2560
+ * @returns The partial object.
2494
2561
  */
2495
- static object(value) {
2496
- if (Is.undefined(value)) {
2497
- return value;
2498
- }
2499
- if (Is.object(value)) {
2500
- return value;
2501
- }
2502
- if (Is.stringValue(value)) {
2503
- try {
2504
- return JSON.parse(value);
2562
+ static omit(obj, keys) {
2563
+ if (Is.object(obj) && Is.arrayValue(keys)) {
2564
+ const result = { ...obj };
2565
+ for (const key of keys) {
2566
+ delete result[key];
2505
2567
  }
2506
- catch { }
2568
+ return result;
2507
2569
  }
2570
+ return obj;
2508
2571
  }
2509
2572
  }
2510
2573
 
2511
2574
  // Copyright 2024 IOTA Stiftung.
2512
2575
  // SPDX-License-Identifier: Apache-2.0.
2513
2576
  /**
2514
- * Class to help with filenames.
2577
+ * Environment variable helper.
2515
2578
  */
2516
- class FilenameHelper {
2517
- /**
2518
- * Replaces any unsafe characters in the filename.
2519
- * @param filename The filename to make safe.
2520
- * @returns The safe filename.
2521
- */
2522
- static safeFilename(filename) {
2523
- let safe = Coerce.string(filename);
2524
- if (Is.empty(safe)) {
2525
- return "";
2579
+ class EnvHelper {
2580
+ /**
2581
+ * Get the environment variable as an object with camel cased names.
2582
+ * @param envVars The environment variables.
2583
+ * @param prefix The prefix of the environment variables, if not provided gets all.
2584
+ * @returns The object with camel cased names.
2585
+ */
2586
+ static envToJson(envVars, prefix) {
2587
+ const result = {};
2588
+ if (!Is.empty(envVars)) {
2589
+ if (Is.empty(prefix)) {
2590
+ for (const envVar in envVars) {
2591
+ if (Is.stringValue(envVars[envVar])) {
2592
+ const camelCaseName = StringHelper.camelCase(envVar.toLowerCase());
2593
+ ObjectHelper.propertySet(result, camelCaseName, envVars[envVar]);
2594
+ }
2595
+ }
2596
+ }
2597
+ else {
2598
+ for (const envVar in envVars) {
2599
+ if (envVar.startsWith(prefix) && Is.stringValue(envVars[envVar])) {
2600
+ const camelCaseName = StringHelper.camelCase(envVar.replace(prefix, "").toLowerCase());
2601
+ ObjectHelper.propertySet(result, camelCaseName, envVars[envVar]);
2602
+ }
2603
+ }
2604
+ }
2526
2605
  }
2527
- // Common non filename characters
2528
- safe = safe.replace(/["*/:<>?\\|]/g, "_");
2529
- // Windows non filename characters
2530
- safe = safe.replace(/^(con|prn|aux|nul|com\d|lpt\d)$/i, "_");
2531
- // Control characters
2532
- safe = safe.replace(/[\u0000-\u001F\u0080-\u009F]/g, "_");
2533
- // Relative paths
2534
- safe = safe.replace(/^\.+/, "_");
2535
- // Trailing periods
2536
- safe = safe.replace(/\.+$/, "");
2537
- return safe;
2606
+ return result;
2538
2607
  }
2539
2608
  }
2540
2609
 
2541
2610
  // Copyright 2024 IOTA Stiftung.
2542
2611
  // SPDX-License-Identifier: Apache-2.0.
2543
2612
  /**
2544
- * Helpers methods for JSON objects.
2613
+ * Class to perform internationalization.
2545
2614
  */
2546
- class JsonHelper {
2615
+ class I18n {
2547
2616
  /**
2548
- * Serializes in canonical format.
2549
- * Based on https://www.rfc-editor.org/rfc/rfc8785.
2550
- * @param object The object to be serialized.
2551
- * @returns The serialized object.
2617
+ * The default translation.
2552
2618
  */
2553
- static canonicalize(object) {
2554
- const buffer = [];
2555
- if (object === null ||
2556
- typeof object !== "object" ||
2557
- ("toJSON" in object && object.toJSON instanceof Function)) {
2558
- // Primitive data type
2559
- buffer.push(JSON.stringify(object));
2560
- }
2561
- else if (Array.isArray(object)) {
2562
- // Array maintain element order
2563
- const parts = [];
2564
- for (const element of object) {
2565
- if (element === undefined) {
2566
- parts.push("null");
2567
- }
2568
- else {
2569
- parts.push(JsonHelper.canonicalize(element));
2570
- }
2571
- }
2572
- buffer.push(`[${parts.join(",")}]`);
2573
- }
2574
- else {
2575
- // Object sort properties
2576
- const props = [];
2577
- const keys = Object.keys(object).sort();
2578
- const o = object;
2579
- for (const key of keys) {
2580
- if (o[key] !== undefined) {
2581
- props.push(`${JSON.stringify(key)}:${JsonHelper.canonicalize(o[key])}`);
2582
- }
2583
- }
2584
- buffer.push(`{${props.join(",")}}`);
2585
- }
2586
- return buffer.join("");
2587
- }
2619
+ static DEFAULT_LOCALE = "en";
2588
2620
  /**
2589
- * Creates a RFC 6902 diff set.
2590
- * Based on https://www.rfc-editor.org/rfc/rfc6902.
2591
- * @param object1 The first object.
2592
- * @param object2 The second object.
2593
- * @returns The list of patches.
2621
+ * Dictionaries for lookups.
2622
+ * @internal
2594
2623
  */
2595
- static diff(object1, object2) {
2596
- const operations = rfc6902.createPatch(object1, object2);
2597
- return operations;
2598
- }
2624
+ static _localeDictionaries = {};
2599
2625
  /**
2600
- * Applies a RFC 6902 diff set to an object.
2601
- * Based on https://www.rfc-editor.org/rfc/rfc6902.
2602
- * @param object The object to patch.
2603
- * @param patches The second object.
2604
- * @returns The updated object.
2626
+ * The current locale.
2627
+ * @internal
2605
2628
  */
2606
- static patch(object, patches) {
2607
- return rfc6902.applyPatch(object, patches);
2608
- }
2609
- }
2610
-
2611
- // Copyright 2024 IOTA Stiftung.
2612
- // SPDX-License-Identifier: Apache-2.0.
2613
- /* eslint-disable no-bitwise */
2614
- /**
2615
- * Convert arrays to and from different formats.
2616
- */
2617
- class Converter {
2629
+ static _currentLocale = I18n.DEFAULT_LOCALE;
2618
2630
  /**
2619
- * Lookup table for encoding.
2631
+ * Change handler for the locale being updated.
2620
2632
  * @internal
2621
2633
  */
2622
- static _ENCODE_LOOKUP;
2634
+ static _localeChangedHandlers = {};
2623
2635
  /**
2624
- * Lookup table for decoding.
2636
+ * Change handler for the dictionaries being updated.
2625
2637
  * @internal
2626
2638
  */
2627
- static _DECODE_LOOKUP;
2639
+ static _dictionaryChangedHandlers = {};
2628
2640
  /**
2629
- * Encode a raw array to UTF8 string.
2630
- * @param array The bytes to encode.
2631
- * @param startIndex The index to start in the bytes.
2632
- * @param length The length of bytes to read.
2633
- * @returns The array formatted as UTF8.
2641
+ * Set the locale.
2642
+ * @param locale The new locale.
2634
2643
  */
2635
- static bytesToUtf8(array, startIndex, length) {
2636
- const start = startIndex ?? 0;
2637
- const len = length ?? array.length;
2638
- let str = "";
2639
- for (let i = start; i < start + len; i++) {
2640
- const value = array[i];
2641
- if (value < 0x80) {
2642
- str += String.fromCharCode(value);
2643
- }
2644
- else if (value > 0xbf && value < 0xe0) {
2645
- str += String.fromCharCode(((value & 0x1f) << 6) | (array[i + 1] & 0x3f));
2646
- i += 1;
2647
- }
2648
- else if (value > 0xdf && value < 0xf0) {
2649
- str += String.fromCharCode(((value & 0x0f) << 12) | ((array[i + 1] & 0x3f) << 6) | (array[i + 2] & 0x3f));
2650
- i += 2;
2651
- }
2652
- else {
2653
- // surrogate pair
2654
- const charCode = (((value & 0x07) << 18) |
2655
- ((array[i + 1] & 0x3f) << 12) |
2656
- ((array[i + 2] & 0x3f) << 6) |
2657
- (array[i + 3] & 0x3f)) -
2658
- 0x010000;
2659
- str += String.fromCharCode((charCode >> 10) | 0xd800, (charCode & 0x03ff) | 0xdc00);
2660
- i += 3;
2661
- }
2644
+ static setLocale(locale) {
2645
+ I18n._currentLocale = locale;
2646
+ for (const callback in I18n._localeChangedHandlers) {
2647
+ I18n._localeChangedHandlers[callback](I18n._currentLocale);
2662
2648
  }
2663
- return str;
2664
2649
  }
2665
2650
  /**
2666
- * Convert a UTF8 string to raw array.
2667
- * @param utf8 The text to decode.
2668
- * @returns The array.
2651
+ * Get the locale.
2652
+ * @returns The current locale.
2669
2653
  */
2670
- static utf8ToBytes(utf8) {
2671
- const bytes = [];
2672
- for (let i = 0; i < utf8.length; i++) {
2673
- let charCode = utf8.charCodeAt(i);
2674
- if (charCode < 0x80) {
2675
- bytes.push(charCode);
2676
- }
2677
- else if (charCode < 0x800) {
2678
- bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
2679
- }
2680
- else if (charCode < 0xd800 || charCode >= 0xe000) {
2681
- bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2682
- }
2683
- else {
2684
- // surrogate pair
2685
- i++;
2686
- // UTF-16 encodes 0x10000-0x10FFFF by
2687
- // subtracting 0x10000 and splitting the
2688
- // 20 bits of 0x0-0xFFFFF into two halves
2689
- charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (utf8.charCodeAt(i) & 0x3ff));
2690
- bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2691
- }
2692
- }
2693
- return Uint8Array.from(bytes);
2654
+ static getLocale() {
2655
+ return I18n._currentLocale;
2694
2656
  }
2695
2657
  /**
2696
- * Encode a raw array to hex string.
2697
- * @param array The bytes to encode.
2698
- * @param includePrefix Include the 0x prefix on the returned hex.
2699
- * @param startIndex The index to start in the bytes.
2700
- * @param length The length of bytes to read.
2701
- * @param reverse Reverse the combine direction.
2702
- * @returns The array formatted as hex.
2658
+ * Add a locale dictionary.
2659
+ * @param locale The locale.
2660
+ * @param dictionary The dictionary to add.
2703
2661
  */
2704
- static bytesToHex(array, includePrefix = false, startIndex, length, reverse) {
2705
- let hex = "";
2706
- this.buildHexLookups();
2707
- if (Converter._ENCODE_LOOKUP) {
2708
- const len = length ?? array.length;
2709
- const start = startIndex ?? 0;
2710
- if (reverse) {
2711
- for (let i = 0; i < len; i++) {
2712
- hex = Converter._ENCODE_LOOKUP[array[start + i]] + hex;
2713
- }
2714
- }
2715
- else {
2716
- for (let i = 0; i < len; i++) {
2717
- hex += Converter._ENCODE_LOOKUP[array[start + i]];
2718
- }
2719
- }
2662
+ static addDictionary(locale, dictionary) {
2663
+ const mergedKeys = {};
2664
+ I18n.flattenTranslationKeys(dictionary, "", mergedKeys);
2665
+ I18n._localeDictionaries[locale] = mergedKeys;
2666
+ for (const callback in I18n._dictionaryChangedHandlers) {
2667
+ I18n._dictionaryChangedHandlers[callback](I18n._currentLocale);
2720
2668
  }
2721
- return includePrefix ? HexHelper.addPrefix(hex) : hex;
2722
2669
  }
2723
2670
  /**
2724
- * Decode a hex string to raw array.
2725
- * @param hex The hex to decode.
2726
- * @param reverse Store the characters in reverse.
2727
- * @returns The array.
2671
+ * Get a locale dictionary.
2672
+ * @param locale The locale.
2673
+ * @returns The dictionary of undefined if it does not exist.
2728
2674
  */
2729
- static hexToBytes(hex, reverse) {
2730
- const strippedHex = HexHelper.stripPrefix(hex);
2731
- const sizeof = strippedHex.length >> 1;
2732
- const length = sizeof << 1;
2733
- const array = new Uint8Array(sizeof);
2734
- this.buildHexLookups();
2735
- if (Converter._DECODE_LOOKUP) {
2736
- let i = 0;
2737
- let n = 0;
2738
- while (i < length) {
2739
- array[n++] =
2740
- (Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)] << 4) |
2741
- Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)];
2742
- }
2743
- if (reverse) {
2744
- array.reverse();
2745
- }
2746
- }
2747
- return array;
2675
+ static getDictionary(locale) {
2676
+ return I18n._localeDictionaries[locale];
2748
2677
  }
2749
2678
  /**
2750
- * Convert the UTF8 to hex.
2751
- * @param utf8 The text to convert.
2752
- * @param includePrefix Include the 0x prefix on the returned hex.
2753
- * @returns The hex version of the bytes.
2679
+ * Get all the locale dictionaries.
2680
+ * @returns The dictionaries.
2754
2681
  */
2755
- static utf8ToHex(utf8, includePrefix = false) {
2756
- const hex = Converter.bytesToHex(Converter.utf8ToBytes(utf8));
2757
- return includePrefix ? HexHelper.addPrefix(hex) : hex;
2682
+ static getAllDictionaries() {
2683
+ return I18n._localeDictionaries;
2758
2684
  }
2759
2685
  /**
2760
- * Convert the hex text to text.
2761
- * @param hex The hex to convert.
2762
- * @returns The UTF8 version of the bytes.
2686
+ * Add a locale changed handler.
2687
+ * @param id The id of the handler.
2688
+ * @param handler The handler to add.
2763
2689
  */
2764
- static hexToUtf8(hex) {
2765
- return Converter.bytesToUtf8(Converter.hexToBytes(HexHelper.stripPrefix(hex)));
2690
+ static addLocaleHandler(id, handler) {
2691
+ I18n._localeChangedHandlers[id] = handler;
2766
2692
  }
2767
2693
  /**
2768
- * Convert bytes to binary string.
2769
- * @param bytes The bytes to convert.
2770
- * @returns A binary string of the bytes.
2694
+ * Remove a locale changed handler.
2695
+ * @param id The id of the handler.
2771
2696
  */
2772
- static bytesToBinary(bytes) {
2773
- const b = [];
2774
- for (let i = 0; i < bytes.length; i++) {
2775
- b.push(bytes[i].toString(2).padStart(8, "0"));
2776
- }
2777
- return b.join("");
2697
+ static removeLocaleHandler(id) {
2698
+ delete I18n._localeChangedHandlers[id];
2778
2699
  }
2779
2700
  /**
2780
- * Convert a binary string to bytes.
2781
- * @param binary The binary string.
2782
- * @returns The bytes.
2701
+ * Add a dictionary changed handler.
2702
+ * @param id The id of the handler.
2703
+ * @param handler The handler to add.
2783
2704
  */
2784
- static binaryToBytes(binary) {
2785
- const bytes = new Uint8Array(Math.ceil(binary.length / 8));
2786
- for (let i = 0; i < bytes.length; i++) {
2787
- bytes[i] = Number.parseInt(binary.slice(i * 8, (i + 1) * 8), 2);
2788
- }
2789
- return bytes;
2705
+ static addDictionaryHandler(id, handler) {
2706
+ I18n._dictionaryChangedHandlers[id] = handler;
2790
2707
  }
2791
2708
  /**
2792
- * Convert bytes to base64 string.
2793
- * @param bytes The bytes to convert.
2794
- * @returns A base64 string of the bytes.
2709
+ * Remove a dictionary changed handler.
2710
+ * @param id The id of the handler.
2795
2711
  */
2796
- static bytesToBase64(bytes) {
2797
- return Base64.encode(bytes);
2712
+ static removeDictionaryHandler(id) {
2713
+ delete I18n._dictionaryChangedHandlers[id];
2798
2714
  }
2799
2715
  /**
2800
- * Convert a base64 string to bytes.
2801
- * @param base64 The base64 string.
2802
- * @returns The bytes.
2716
+ * Format a message.
2717
+ * @param key The key of the message to format.
2718
+ * @param values The values to substitute into the message.
2719
+ * @param overrideLocale Override the locale.
2720
+ * @returns The formatted string.
2803
2721
  */
2804
- static base64ToBytes(base64) {
2805
- return Base64.decode(base64);
2722
+ static formatMessage(key, values, overrideLocale) {
2723
+ let cl = overrideLocale ?? I18n._currentLocale;
2724
+ if (cl.startsWith("debug-")) {
2725
+ cl = I18n.DEFAULT_LOCALE;
2726
+ }
2727
+ if (!I18n._localeDictionaries[cl]) {
2728
+ return `!!Missing ${cl}`;
2729
+ }
2730
+ if (!I18n._localeDictionaries[cl][key]) {
2731
+ return `!!Missing ${cl}.${key}`;
2732
+ }
2733
+ if (I18n._currentLocale === "debug-k") {
2734
+ return key;
2735
+ }
2736
+ let ret = new intlMessageformat.IntlMessageFormat(I18n._localeDictionaries[cl][key], cl).format(values);
2737
+ if (I18n._currentLocale === "debug-x") {
2738
+ ret = ret.replace(/[a-z]/g, "x").replace(/[A-Z]/g, "x").replace(/\d/g, "n");
2739
+ }
2740
+ return ret;
2806
2741
  }
2807
2742
  /**
2808
- * Convert bytes to base64 url string.
2809
- * @param bytes The bytes to convert.
2810
- * @returns A base64 url string of the bytes.
2743
+ * Check if the dictionaries have a message for the given key.
2744
+ * @param key The key to check for existence.
2745
+ * @returns True if the key exists.
2811
2746
  */
2812
- static bytesToBase64Url(bytes) {
2813
- return Base64Url.encode(bytes);
2747
+ static hasMessage(key) {
2748
+ return Is.string(I18n._localeDictionaries[I18n._currentLocale]?.[key]);
2814
2749
  }
2815
2750
  /**
2816
- * Convert a base64 url string to bytes.
2817
- * @param base64Url The base64 url string.
2818
- * @returns The bytes.
2751
+ * Flatten the translation property paths for faster lookup.
2752
+ * @param translation The translation to merge.
2753
+ * @param propertyPath The current root path.
2754
+ * @param mergedKeys The merged keys dictionary to populate.
2755
+ * @internal
2819
2756
  */
2820
- static base64UrlToBytes(base64Url) {
2821
- return Base64Url.decode(base64Url);
2757
+ static flattenTranslationKeys(translation, propertyPath, mergedKeys) {
2758
+ for (const key in translation) {
2759
+ const val = translation[key];
2760
+ const mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;
2761
+ if (Is.string(val)) {
2762
+ mergedKeys[mergedPath] = val;
2763
+ }
2764
+ else if (Is.object(val)) {
2765
+ I18n.flattenTranslationKeys(val, mergedPath, mergedKeys);
2766
+ }
2767
+ }
2822
2768
  }
2769
+ }
2770
+
2771
+ // Copyright 2024 IOTA Stiftung.
2772
+ // SPDX-License-Identifier: Apache-2.0.
2773
+ /**
2774
+ * Error helper functions.
2775
+ */
2776
+ class ErrorHelper {
2823
2777
  /**
2824
- * Convert bytes to base58 string.
2825
- * @param bytes The bytes to convert.
2826
- * @returns A base58 string of the bytes.
2778
+ * Format Errors and returns just their messages.
2779
+ * @param error The error to format.
2780
+ * @returns The error formatted including any inner errors.
2827
2781
  */
2828
- static bytesToBase58(bytes) {
2829
- return Base58.encode(bytes);
2782
+ static formatErrors(error) {
2783
+ return ErrorHelper.localizeErrors(error).map(e => e.message);
2830
2784
  }
2831
2785
  /**
2832
- * Convert a base58 string to bytes.
2833
- * @param base58 The base58 string.
2834
- * @returns The bytes.
2786
+ * Localize the content of an error and any inner errors.
2787
+ * @param error The error to format.
2788
+ * @returns The localized version of the errors flattened.
2835
2789
  */
2836
- static base58ToBytes(base58) {
2837
- return Base58.decode(base58);
2790
+ static localizeErrors(error) {
2791
+ const formattedErrors = [];
2792
+ if (Is.notEmpty(error)) {
2793
+ const errors = BaseError.flatten(error);
2794
+ for (const err of errors) {
2795
+ const errorNameKey = `errorNames.${StringHelper.camelCase(err.name)}`;
2796
+ const errorMessageKey = `error.${err.message}`;
2797
+ // If there is no error message then it is probably
2798
+ // from a 3rd party lib, so don't format it just display
2799
+ const hasErrorName = I18n.hasMessage(errorNameKey);
2800
+ const hasErrorMessage = I18n.hasMessage(errorMessageKey);
2801
+ const localizedError = {
2802
+ name: I18n.formatMessage(hasErrorName ? errorNameKey : "errorNames.error"),
2803
+ message: hasErrorMessage
2804
+ ? I18n.formatMessage(errorMessageKey, err.properties)
2805
+ : err.message
2806
+ };
2807
+ if (Is.stringValue(err.source)) {
2808
+ localizedError.source = err.source;
2809
+ }
2810
+ if (Is.stringValue(err.stack)) {
2811
+ // Remove the first line from the stack traces as they
2812
+ // just have the error type and message duplicated
2813
+ const lines = err.stack.split("\n");
2814
+ lines.shift();
2815
+ localizedError.stack = lines.join("\n");
2816
+ }
2817
+ const additional = ErrorHelper.formatValidationErrors(err);
2818
+ if (Is.stringValue(additional)) {
2819
+ localizedError.additional = additional;
2820
+ }
2821
+ formattedErrors.push(localizedError);
2822
+ }
2823
+ }
2824
+ return formattedErrors;
2838
2825
  }
2839
2826
  /**
2840
- * Build the static lookup tables.
2841
- * @internal
2827
+ * Localize the content of an error and any inner errors.
2828
+ * @param error The error to format.
2829
+ * @returns The localized version of the errors flattened.
2842
2830
  */
2843
- static buildHexLookups() {
2844
- if (!Converter._ENCODE_LOOKUP || !Converter._DECODE_LOOKUP) {
2845
- const alphabet = "0123456789abcdef";
2846
- Converter._ENCODE_LOOKUP = [];
2847
- Converter._DECODE_LOOKUP = [];
2848
- for (let i = 0; i < 256; i++) {
2849
- Converter._ENCODE_LOOKUP[i] = alphabet[(i >> 4) & 0xf] + alphabet[i & 0xf];
2850
- if (i < 16) {
2851
- if (i < 10) {
2852
- Converter._DECODE_LOOKUP[0x30 + i] = i;
2853
- }
2854
- else {
2855
- Converter._DECODE_LOOKUP[0x61 - 10 + i] = i;
2856
- }
2831
+ static formatValidationErrors(error) {
2832
+ if (Is.object(error.properties) &&
2833
+ Object.keys(error.properties).length > 0 &&
2834
+ Is.object(error.properties) &&
2835
+ Is.arrayValue(error.properties.validationFailures)) {
2836
+ const validationErrors = [];
2837
+ for (const validationFailure of error.properties.validationFailures) {
2838
+ const errorI18n = `error.${validationFailure.reason}`;
2839
+ const errorMessage = I18n.hasMessage(errorI18n)
2840
+ ? I18n.formatMessage(errorI18n, validationFailure.properties)
2841
+ : errorI18n;
2842
+ let v = `${validationFailure.property}: ${errorMessage}`;
2843
+ if (Is.object(validationFailure.properties) &&
2844
+ Is.notEmpty(validationFailure.properties.value)) {
2845
+ v += ` = ${JSON.stringify(validationFailure.properties.value)}`;
2857
2846
  }
2847
+ validationErrors.push(v);
2858
2848
  }
2849
+ return validationErrors.join("\n");
2859
2850
  }
2860
2851
  }
2861
2852
  }
2862
2853
 
2854
+ // Copyright 2024 IOTA Stiftung.
2855
+ // SPDX-License-Identifier: Apache-2.0.
2863
2856
  /**
2864
- * Class to help with objects.
2857
+ * Coerce an object from one type to another.
2865
2858
  */
2866
- class ObjectHelper {
2867
- /**
2868
- * Runtime name for the class.
2869
- * @internal
2870
- */
2871
- static _CLASS_NAME = "ObjectHelper";
2859
+ class Coerce {
2872
2860
  /**
2873
- * Convert an object to bytes.
2874
- * @param obj The object to convert.
2875
- * @param format Format the JSON content.
2876
- * @returns The object as bytes.
2861
+ * Coerce the value to a string.
2862
+ * @param value The value to coerce.
2863
+ * @throws TypeError If the value can not be coerced.
2864
+ * @returns The value if it can be coerced.
2877
2865
  */
2878
- static toBytes(obj, format = false) {
2879
- if (obj === undefined) {
2880
- return new Uint8Array();
2866
+ static string(value) {
2867
+ if (Is.undefined(value)) {
2868
+ return value;
2869
+ }
2870
+ if (Is.string(value)) {
2871
+ return value;
2872
+ }
2873
+ if (Is.number(value)) {
2874
+ return value.toString();
2875
+ }
2876
+ if (Is.boolean(value)) {
2877
+ return value ? "true" : "false";
2878
+ }
2879
+ if (Is.date(value)) {
2880
+ return value.toISOString();
2881
2881
  }
2882
- const json = format ? JSON.stringify(obj, undefined, "\t") : JSON.stringify(obj);
2883
- return Converter.utf8ToBytes(json);
2884
2882
  }
2885
2883
  /**
2886
- * Convert a bytes to an object.
2887
- * @param bytes The bytes to convert to an object.
2888
- * @returns The object.
2889
- * @throws GeneralError if there was an error parsing the JSON.
2884
+ * Coerce the value to a number.
2885
+ * @param value The value to coerce.
2886
+ * @throws TypeError If the value can not be coerced.
2887
+ * @returns The value if it can be coerced.
2890
2888
  */
2891
- static fromBytes(bytes) {
2892
- if (Is.empty(bytes) || bytes.length === 0) {
2893
- return undefined;
2889
+ static number(value) {
2890
+ if (Is.undefined(value)) {
2891
+ return value;
2894
2892
  }
2895
- try {
2896
- const utf8 = Converter.bytesToUtf8(bytes);
2897
- return JSON.parse(utf8);
2893
+ if (Is.number(value)) {
2894
+ return value;
2895
+ }
2896
+ if (Is.string(value)) {
2897
+ const parsed = Number.parseFloat(value);
2898
+ if (Is.number(parsed)) {
2899
+ return parsed;
2900
+ }
2901
+ }
2902
+ if (Is.boolean(value)) {
2903
+ return value ? 1 : 0;
2898
2904
  }
2899
- catch (err) {
2900
- throw new GeneralError(ObjectHelper._CLASS_NAME, "failedBytesToJSON", undefined, err);
2905
+ if (Is.date(value)) {
2906
+ return value.getTime();
2901
2907
  }
2902
2908
  }
2903
2909
  /**
2904
- * Make a deep clone of an object.
2905
- * @param obj The object to clone.
2906
- * @returns The objects clone.
2910
+ * Coerce the value to a bigint.
2911
+ * @param value The value to coerce.
2912
+ * @throws TypeError If the value can not be coerced.
2913
+ * @returns The value if it can be coerced.
2907
2914
  */
2908
- static clone(obj) {
2909
- if (Is.undefined(obj)) {
2910
- return undefined;
2915
+ static bigint(value) {
2916
+ if (Is.undefined(value)) {
2917
+ return value;
2911
2918
  }
2912
- return structuredClone(obj);
2913
- }
2914
- /**
2915
- * Deep merge objects.
2916
- * @param obj1 The first object to merge.
2917
- * @param obj2 The second object to merge.
2918
- * @returns The combined deep merge of the objects.
2919
- */
2920
- static merge(obj1, obj2) {
2921
- if (Is.empty(obj1)) {
2922
- return ObjectHelper.clone(obj2);
2919
+ if (Is.bigint(value)) {
2920
+ return value;
2923
2921
  }
2924
- if (Is.empty(obj2)) {
2925
- return ObjectHelper.clone(obj1);
2922
+ if (Is.number(value)) {
2923
+ return BigInt(value);
2926
2924
  }
2927
- const obj1Clone = ObjectHelper.clone(obj1);
2928
- if (Is.object(obj1Clone) && Is.object(obj2)) {
2929
- const keys = Object.keys(obj2);
2930
- for (const key of keys) {
2931
- if (Is.object(obj1Clone[key]) && Is.object(obj2[key])) {
2932
- ObjectHelper.propertySet(obj1Clone, key, ObjectHelper.merge(obj1Clone[key], obj2[key]));
2933
- }
2934
- else {
2935
- ObjectHelper.propertySet(obj1Clone, key, obj2[key]);
2936
- }
2925
+ if (Is.string(value)) {
2926
+ const parsed = Number.parseFloat(value);
2927
+ if (Is.integer(parsed)) {
2928
+ return BigInt(parsed);
2937
2929
  }
2938
2930
  }
2939
- return obj1Clone;
2940
- }
2941
- /**
2942
- * Does one object equal another.
2943
- * @param obj1 The first object to compare.
2944
- * @param obj2 The second object to compare.
2945
- * @param strictPropertyOrder Should the properties be in the same order, defaults to true.
2946
- * @returns True is the objects are equal.
2947
- */
2948
- static equal(obj1, obj2, strictPropertyOrder) {
2949
- if (strictPropertyOrder ?? true) {
2950
- return JSON.stringify(obj1) === JSON.stringify(obj2);
2931
+ if (Is.boolean(value)) {
2932
+ return value ? 1n : 0n;
2951
2933
  }
2952
- return JsonHelper.canonicalize(obj1) === JsonHelper.canonicalize(obj2);
2953
2934
  }
2954
2935
  /**
2955
- * Get the property of an unknown object.
2956
- * @param obj The object to get the property from.
2957
- * @param property The property to get, can be separated by dots for nested path.
2958
- * @returns The property.
2936
+ * Coerce the value to a boolean.
2937
+ * @param value The value to coerce.
2938
+ * @throws TypeError If the value can not be coerced.
2939
+ * @returns The value if it can be coerced.
2959
2940
  */
2960
- static propertyGet(obj, property) {
2961
- if (property.includes(".")) {
2962
- const parts = property.split(".");
2963
- let value = obj;
2964
- for (const part of parts) {
2965
- if (Is.object(value)) {
2966
- value = value[part];
2967
- }
2968
- else {
2969
- return undefined;
2970
- }
2971
- }
2941
+ static boolean(value) {
2942
+ if (Is.undefined(value)) {
2972
2943
  return value;
2973
2944
  }
2974
- return Is.object(obj) ? obj[property] : undefined;
2945
+ if (Is.boolean(value)) {
2946
+ return value;
2947
+ }
2948
+ if (Is.number(value)) {
2949
+ // eslint-disable-next-line no-unneeded-ternary
2950
+ return value ? true : false;
2951
+ }
2952
+ if (Is.string(value)) {
2953
+ if (/true/i.test(value)) {
2954
+ return true;
2955
+ }
2956
+ if (/false/i.test(value)) {
2957
+ return false;
2958
+ }
2959
+ }
2975
2960
  }
2976
2961
  /**
2977
- * Set the property of an unknown object.
2978
- * @param obj The object to set the property from.
2979
- * @param property The property to set.
2980
- * @param value The value to set.
2962
+ * Coerce the value to a date.
2963
+ * @param value The value to coerce.
2964
+ * @throws TypeError If the value can not be coerced.
2965
+ * @returns The value if it can be coerced.
2981
2966
  */
2982
- static propertySet(obj, property, value) {
2983
- if (Is.object(obj)) {
2984
- obj[property] = value;
2967
+ static date(value) {
2968
+ if (Is.undefined(value)) {
2969
+ return value;
2970
+ }
2971
+ if (Is.date(value)) {
2972
+ return value;
2973
+ }
2974
+ if (Is.number(value)) {
2975
+ return new Date(value);
2976
+ }
2977
+ if (Is.string(value)) {
2978
+ const dt = new Date(value);
2979
+ if (!Number.isNaN(dt.getTime())) {
2980
+ const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate());
2981
+ return new Date(utc);
2982
+ }
2985
2983
  }
2986
2984
  }
2987
2985
  /**
2988
- * Delete the property of an unknown object.
2989
- * @param obj The object to set the property from.
2990
- * @param property The property to set
2986
+ * Coerce the value to a date/time.
2987
+ * @param value The value to coerce.
2988
+ * @throws TypeError If the value can not be coerced.
2989
+ * @returns The value if it can be coerced.
2991
2990
  */
2992
- static propertyDelete(obj, property) {
2993
- if (Is.object(obj)) {
2994
- delete obj[property];
2991
+ static dateTime(value) {
2992
+ if (Is.undefined(value)) {
2993
+ return value;
2994
+ }
2995
+ if (Is.date(value)) {
2996
+ return value;
2997
+ }
2998
+ if (Is.number(value)) {
2999
+ return new Date(value);
3000
+ }
3001
+ if (Is.string(value)) {
3002
+ const dt = new Date(value);
3003
+ if (!Number.isNaN(dt.getTime())) {
3004
+ const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate(), dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
3005
+ return new Date(utc);
3006
+ }
2995
3007
  }
2996
3008
  }
2997
3009
  /**
2998
- * Extract a property from the object, providing alternative names.
2999
- * @param obj The object to extract from.
3000
- * @param propertyNames The possible names for the property.
3001
- * @param removeProperties Remove the properties from the object, defaults to true.
3002
- * @returns The property if available.
3010
+ * Coerce the value to a time.
3011
+ * @param value The value to coerce.
3012
+ * @throws TypeError If the value can not be coerced.
3013
+ * @returns The value if it can be coerced.
3003
3014
  */
3004
- static extractProperty(obj, propertyNames, removeProperties = true) {
3005
- let retVal;
3006
- if (Is.object(obj)) {
3007
- const names = Is.string(propertyNames) ? [propertyNames] : propertyNames;
3008
- for (const prop of names) {
3009
- retVal ??= ObjectHelper.propertyGet(obj, prop);
3010
- if (removeProperties) {
3011
- ObjectHelper.propertyDelete(obj, prop);
3012
- }
3015
+ static time(value) {
3016
+ if (Is.undefined(value)) {
3017
+ return value;
3018
+ }
3019
+ if (Is.date(value)) {
3020
+ return value;
3021
+ }
3022
+ if (Is.number(value)) {
3023
+ const dt = new Date(value);
3024
+ dt.setFullYear(1970, 0, 1);
3025
+ return dt;
3026
+ }
3027
+ if (Is.string(value)) {
3028
+ const dt = new Date(value);
3029
+ if (!Number.isNaN(dt.getTime())) {
3030
+ const utc = Date.UTC(1970, 0, 1, dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
3031
+ return new Date(utc);
3013
3032
  }
3014
3033
  }
3015
- return retVal;
3016
3034
  }
3017
3035
  /**
3018
- * Pick a subset of properties from an object.
3019
- * @param obj The object to pick the properties from.
3020
- * @param keys The property keys to pick.
3021
- * @returns The partial object.
3036
+ * Coerce the value to an object.
3037
+ * @param value The value to coerce.
3038
+ * @throws TypeError If the value can not be coerced.
3039
+ * @returns The value if it can be coerced.
3022
3040
  */
3023
- static pick(obj, keys) {
3024
- if (Is.object(obj) && Is.arrayValue(keys)) {
3025
- const result = {};
3026
- for (const key of keys) {
3027
- result[key] = obj[key];
3041
+ static object(value) {
3042
+ if (Is.undefined(value)) {
3043
+ return value;
3044
+ }
3045
+ if (Is.object(value)) {
3046
+ return value;
3047
+ }
3048
+ if (Is.stringValue(value)) {
3049
+ try {
3050
+ return JSON.parse(value);
3028
3051
  }
3029
- return result;
3052
+ catch { }
3030
3053
  }
3031
- return obj;
3032
3054
  }
3055
+ }
3056
+
3057
+ // Copyright 2024 IOTA Stiftung.
3058
+ // SPDX-License-Identifier: Apache-2.0.
3059
+ /**
3060
+ * Class to help with filenames.
3061
+ */
3062
+ class FilenameHelper {
3033
3063
  /**
3034
- * Omit a subset of properties from an object.
3035
- * @param obj The object to omit the properties from.
3036
- * @param keys The property keys to omit.
3037
- * @returns The partial object.
3064
+ * Replaces any unsafe characters in the filename.
3065
+ * @param filename The filename to make safe.
3066
+ * @returns The safe filename.
3038
3067
  */
3039
- static omit(obj, keys) {
3040
- if (Is.object(obj) && Is.arrayValue(keys)) {
3041
- const result = { ...obj };
3042
- for (const key of keys) {
3043
- delete result[key];
3044
- }
3045
- return result;
3068
+ static safeFilename(filename) {
3069
+ let safe = Coerce.string(filename);
3070
+ if (Is.empty(safe)) {
3071
+ return "";
3046
3072
  }
3047
- return obj;
3073
+ // Common non filename characters
3074
+ safe = safe.replace(/["*/:<>?\\|]/g, "_");
3075
+ // Windows non filename characters
3076
+ safe = safe.replace(/^(con|prn|aux|nul|com\d|lpt\d)$/i, "_");
3077
+ // Control characters
3078
+ safe = safe.replace(/[\u0000-\u001F\u0080-\u009F]/g, "_");
3079
+ // Relative paths
3080
+ safe = safe.replace(/^\.+/, "_");
3081
+ // Trailing periods
3082
+ safe = safe.replace(/\.+$/, "");
3083
+ return safe;
3048
3084
  }
3049
3085
  }
3050
3086
 
@@ -4435,6 +4471,7 @@ exports.Compression = Compression;
4435
4471
  exports.CompressionType = CompressionType;
4436
4472
  exports.ConflictError = ConflictError;
4437
4473
  exports.Converter = Converter;
4474
+ exports.EnvHelper = EnvHelper;
4438
4475
  exports.ErrorHelper = ErrorHelper;
4439
4476
  exports.Factory = Factory;
4440
4477
  exports.FilenameHelper = FilenameHelper;