@talismn/util 0.5.0 → 0.5.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.
@@ -1,9 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var util = require('@polkadot/util');
4
- var utilCrypto = require('@polkadot/util-crypto');
5
3
  var tailwindMerge = require('tailwind-merge');
6
- var keyring = require('@polkadot/keyring');
7
4
  var rxjs = require('rxjs');
8
5
  var BigNumber = require('bignumber.js');
9
6
 
@@ -45,54 +42,8 @@ const BigMath = {
45
42
  }
46
43
  };
47
44
 
48
- const bitLength$1 = 128;
49
- function blake2Concat(input) {
50
- return util.u8aToHex(util.u8aConcat(utilCrypto.blake2AsU8a(input, bitLength$1), util.u8aToU8a(input)));
51
- }
52
-
53
45
  const classNames = tailwindMerge.twMerge;
54
-
55
- function encodeAnyAddress(key, ss58Format) {
56
- try {
57
- return keyring.encodeAddress(key, ss58Format);
58
- } catch (error) {
59
- if (typeof key !== "string") throw error;
60
- if (!utilCrypto.isEthereumAddress(key)) throw error;
61
- return utilCrypto.ethereumEncode(key);
62
- }
63
- }
64
-
65
- /**
66
- *
67
- * @param address substrate SS58 address
68
- * @param prefix prefix used to format the address
69
- * @returns address encoded with supplied prefix
70
- */
71
- const convertAddress = (address, prefix) => {
72
- return encodeAnyAddress(address, prefix ?? undefined);
73
- };
74
-
75
- function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
76
- try {
77
- return keyring.decodeAddress(encoded, ignoreChecksum, ss58Format);
78
- } catch (error) {
79
- if (typeof encoded !== "string") throw error;
80
- if (!utilCrypto.isEthereumAddress(encoded)) throw error;
81
- return util.hexToU8a(encoded.slice("0x".length));
82
- }
83
- }
84
-
85
- const decodeSs58Format = address => {
86
- if (!address) return;
87
- try {
88
- utilCrypto.decodeAddress(address);
89
- const decoded = utilCrypto.base58Decode(address);
90
- const [,,, ss58Format] = utilCrypto.checkAddressChecksum(decoded);
91
- return ss58Format;
92
- } catch {
93
- return; // invalid address
94
- }
95
- };
46
+ const cn = tailwindMerge.twMerge;
96
47
 
97
48
  /**
98
49
  * In TypeScript, a deferred promise refers to a pattern that involves creating a promise that can be
@@ -195,113 +146,57 @@ const formatPrice = (price, currency, compact) => {
195
146
  }).format(price);
196
147
  };
197
148
 
149
+ const CACHE = new Map();
150
+
151
+ /**
152
+ * When using react-rxjs hooks and state observables, the options are used as weak map keys.
153
+ * This means that if the options object is recreated on each render, the observable will be recreated as well.
154
+ * This utility function allows you to create a shared observable based on a namespace and arguments that, so react-rxjs can reuse the same observables
155
+ *
156
+ * @param namespace
157
+ * @param args
158
+ * @param createObservable
159
+ * @param serializer
160
+ * @returns
161
+ */
162
+ const getSharedObservable = (namespace, args, createObservable, serializer = args => JSON.stringify(args)) => {
163
+ const cacheKey = `${namespace}:${serializer(args)}`;
164
+ if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
165
+ const obs = createObservable(args);
166
+ const sharedObs = obs.pipe(rxjs.shareReplay({
167
+ bufferSize: 1,
168
+ refCount: true
169
+ }));
170
+ CACHE.set(cacheKey, sharedObs);
171
+ return sharedObs;
172
+ };
173
+
198
174
  function hasOwnProperty(obj, prop) {
199
175
  if (typeof obj !== "object") return false;
200
176
  if (obj === null) return false;
201
177
  return prop in obj;
202
178
  }
203
179
 
180
+ const isAbortError = error => {
181
+ return error instanceof Error && error.name === "AbortError";
182
+ };
183
+
204
184
  function isArrayOf(array, func) {
205
185
  if (array.length > 0 && array[0] instanceof func) return true;
206
186
  return false;
207
187
  }
208
188
 
209
- const isBigInt = value => typeof value === "bigint";
210
-
211
- const isBooleanTrue = x => !!x;
212
-
213
- const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
214
-
215
- /**
216
- * Similar to isValidAddress but will not call isEthereumAddress under the hood
217
- * @param address
218
- * @param prefix if supplied, the method will also check the prefix
219
- * @returns true if valid substrate (SS58) address, false otherwise
220
- */
221
- const isValidSubstrateAddress = (address, prefix) => {
222
- try {
223
- // attempt to encode, it will throw an error if address is invalid
224
- const encoded = keyring.encodeAddress(address, prefix ?? undefined);
225
-
226
- //if a prefix is supplied, check that reencoding using this prefix matches the input address
227
- if (prefix !== undefined) return address === encoded;
228
-
229
- //if no prefix supplied, the fact that decoding + encoding succeded indicates that the address is valid
230
- return true;
231
- } catch (error) {
232
- // input is not a substrate (SS58) address
233
- return false;
234
- }
235
- };
236
-
237
- function planckToTokens(planck, tokenDecimals) {
238
- if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
239
- const base = 10;
240
- const exponent = -1 * tokenDecimals;
241
- const multiplier = base ** exponent;
242
- return new BigNumber__default.default(planck).multipliedBy(multiplier).toString(10);
243
- }
244
-
245
- const sleep = ms => new Promise(resolve => {
246
- setTimeout(resolve, ms);
247
- });
248
-
249
- const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
250
-
251
- function tokensToPlanck(tokens, tokenDecimals) {
252
- if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
253
- const base = 10;
254
- const exponent = tokenDecimals;
255
- const multiplier = base ** exponent;
256
- return new BigNumber__default.default(tokens).multipliedBy(multiplier).toString(10);
257
- }
258
-
259
- const bitLength = 64;
260
- function twox64Concat(input) {
261
- const inputAsU8a = typeof input === "string" ? input : new Uint8Array(input);
262
- return util.u8aToHex(util.u8aConcat(utilCrypto.xxhashAsU8a(inputAsU8a, bitLength), util.u8aToU8a(inputAsU8a)));
263
- }
264
-
265
- /**
266
- * @name validateHexString
267
- * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
268
- * @param {string} str - string to check
269
- * @returns {HexString} - boolean
270
- * @example
271
- * validateHexString("0x1234") // "0x1234"
272
- * validateHexString("1234") // Error: Expected a hex string
273
- * validateHexString(1234) // Error: Expected a string
274
- **/
275
- const validateHexString = str => {
276
- if (typeof str !== "string") {
277
- throw new Error("Expected a string");
278
- }
279
- if (str.startsWith("0x")) {
280
- return str;
281
- }
282
- throw new Error("Expected a hex string");
189
+ const isAscii = str => {
190
+ return [...str].every(char => char.charCodeAt(0) <= 127);
283
191
  };
284
192
 
285
- const CACHE$1 = new Map();
286
-
287
- // Normalize an address in a way that it can be compared to other addresses that have also been normalized
288
- const normalizeAddress = address => {
289
- try {
290
- if (!CACHE$1.has(address)) CACHE$1.set(address, encodeAnyAddress(address));
291
- return CACHE$1.get(address);
292
- } catch (cause) {
293
- throw new Error(`Unable to normalize address: ${address}`, {
294
- cause
295
- });
296
- }
297
- };
193
+ const isBigInt = value => typeof value === "bigint";
298
194
 
299
- const isAddressEqual = (address1, address2) => {
300
- return normalizeAddress(address1) === normalizeAddress(address2);
301
- };
195
+ const isBooleanTrue = x => !!x;
302
196
 
303
- const isAscii = str => {
304
- return [...str].every(char => char.charCodeAt(0) <= 127);
197
+ const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
198
+ const isHexString = value => {
199
+ return typeof value === "string" && REGEX_HEX_STRING.test(value);
305
200
  };
306
201
 
307
202
  /**
@@ -314,36 +209,33 @@ const isAscii = str => {
314
209
  */
315
210
  const isNotNil = value => value !== null && value !== undefined;
316
211
 
317
- const isTruthy = value => Boolean(value);
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ const isPromise = value => !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
318
214
 
319
- const isAbortError = error => {
320
- return error instanceof Error && error.name === "AbortError";
321
- };
215
+ /**
216
+ * Tests to see if an object is an RxJS {@link Subject}.
217
+ */
218
+ function isSubject(object) {
219
+ if (!object) return false;
220
+ if (object instanceof rxjs.Subject) return true;
221
+ return "asObservable" in object && isFn(object.asObservable) && "complete" in object && isFn(object.complete) && "error" in object && isFn(object.error) && "forEach" in object && isFn(object.forEach) && "next" in object && isFn(object.next) && "pipe" in object && isFn(object.pipe) && "subscribe" in object && isFn(object.subscribe) && "unsubscribe" in object && isFn(object.unsubscribe) && "closed" in object && isBool(object.closed) && "observed" in object && isBool(object.observed);
222
+ }
322
223
 
323
- const CACHE = new Map();
224
+ /**
225
+ * Returns `true` if `value` is a function.
226
+ */
227
+ function isFn(value) {
228
+ return typeof value === "function";
229
+ }
324
230
 
325
231
  /**
326
- * When using react-rxjs hooks and state observables, the options are used as weak map keys.
327
- * This means that if the options object is recreated on each render, the observable will be recreated as well.
328
- * This utility function allows you to create a shared observable based on a namespace and arguments that, so react-rxjs can reuse the same observables
329
- *
330
- * @param namespace
331
- * @param args
332
- * @param createObservable
333
- * @param serializer
334
- * @returns
232
+ * Returns `true` if `value` is a boolean.
335
233
  */
336
- const getSharedObservable = (namespace, args, createObservable, serializer = args => JSON.stringify(args)) => {
337
- const cacheKey = `${namespace}:${serializer(args)}`;
338
- if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
339
- const obs = createObservable(args);
340
- const sharedObs = obs.pipe(rxjs.shareReplay({
341
- bufferSize: 1,
342
- refCount: true
343
- }));
344
- CACHE.set(cacheKey, sharedObs);
345
- return sharedObs;
346
- };
234
+ function isBool(value) {
235
+ return typeof value === "boolean";
236
+ }
237
+
238
+ const isTruthy = value => Boolean(value);
347
239
 
348
240
  /**
349
241
  * An RxJS operator that keeps the source observable alive for a specified duration
@@ -379,9 +271,120 @@ const keepSourceSubscribed = (observable, ms) => {
379
271
  return () => setTimeout(() => sub.unsubscribe(), ms);
380
272
  };
381
273
 
382
- const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
383
- const isHexString = value => {
384
- return typeof value === "string" && REGEX_HEX_STRING.test(value);
274
+ function planckToTokens(planck, tokenDecimals) {
275
+ if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
276
+ const base = 10;
277
+ const exponent = -1 * tokenDecimals;
278
+ const multiplier = base ** exponent;
279
+ return new BigNumber__default.default(planck).multipliedBy(multiplier).toString(10);
280
+ }
281
+
282
+ /**
283
+ * Turns a value into a {@link ReplaySubject} of size 1.
284
+ *
285
+ * If the value is already a {@link ReplaySubject}, it will be returned as-is.
286
+ *
287
+ * If the value is a {@link Promise}, it will be awaited,
288
+ * and the awaited value will be published into the {@link ReplaySubject} when it becomes available.
289
+ *
290
+ * For any other type of value, it will be immediately published into the {@link ReplaySubject}.
291
+ */
292
+ const replaySubjectFrom = initialValue => {
293
+ if (initialValue instanceof rxjs.ReplaySubject) return initialValue;
294
+ const subject = new rxjs.ReplaySubject(1);
295
+
296
+ // if initialValue is a promise, await it and then call `subject.next()` with the awaited value
297
+ if (isPromise(initialValue)) {
298
+ initialValue.then(value => subject.next(value), error => subject.error(error));
299
+ return subject;
300
+ }
301
+
302
+ // if initialValue is not a promise, immediately call `subject.next()` with the value
303
+ subject.next(initialValue);
304
+ return subject;
305
+ };
306
+
307
+ const sleep = ms => new Promise(resolve => {
308
+ setTimeout(resolve, ms);
309
+ });
310
+
311
+ /**
312
+ * Takes a subject and splits it into two parts:
313
+ *
314
+ * 1. A function to submit new values into the subject.
315
+ * 2. An observable for subscribing to new values from the subject.
316
+ *
317
+ * This can be helpful when, to avoid bugs, you want to expose only one
318
+ * of these parts to external code and keep the other part private.
319
+ */
320
+ function splitSubject(subject) {
321
+ const next = value => subject.next(value);
322
+ const observable = subject.asObservable();
323
+ return [next, observable];
324
+ }
325
+
326
+ const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
327
+
328
+ function tokensToPlanck(tokens, tokenDecimals) {
329
+ if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
330
+ const base = 10;
331
+ const exponent = tokenDecimals;
332
+ const multiplier = base ** exponent;
333
+ return new BigNumber__default.default(tokens).multipliedBy(multiplier).toString(10);
334
+ }
335
+
336
+ /**
337
+ * @name validateHexString
338
+ * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
339
+ * @param {string} str - string to check
340
+ * @returns {`0x${string}`} - boolean
341
+ * @example
342
+ * validateHexString("0x1234") // "0x1234"
343
+ * validateHexString("1234") // Error: Expected a hex string
344
+ * validateHexString(1234) // Error: Expected a string
345
+ **/
346
+ const validateHexString = str => {
347
+ if (typeof str !== "string") {
348
+ throw new Error("Expected a string");
349
+ }
350
+ if (str.startsWith("0x")) {
351
+ return str;
352
+ }
353
+ throw new Error("Expected a hex string");
354
+ };
355
+
356
+ // Designed to be serializable as it can be sent to the frontend
357
+
358
+ function getLoadable$(factory, options = {}) {
359
+ const {
360
+ getError,
361
+ refreshInterval
362
+ } = options;
363
+ const createLoadableStream = () => rxjs.from(factory()).pipe(rxjs.map(data => ({
364
+ status: "success",
365
+ data
366
+ })), rxjs.catchError(error => rxjs.of({
367
+ status: "error",
368
+ error: getError ? getError(error) : getGenericError(error)
369
+ })));
370
+ const source$ = refreshInterval ? rxjs.timer(0, refreshInterval).pipe(rxjs.switchMap(() => createLoadableStream())) : createLoadableStream();
371
+ return source$.pipe(rxjs.startWith({
372
+ status: "loading"
373
+ }));
374
+ }
375
+ const getGenericError = error => ({
376
+ name: "Error",
377
+ message: getGenericErrorMessage(error)
378
+ });
379
+ const getGenericErrorMessage = error => {
380
+ if (typeof error === "string") {
381
+ return error;
382
+ } else if (error instanceof Error) {
383
+ return error.message;
384
+ } else if (error && typeof error === "object" && "message" in error) {
385
+ return error.message;
386
+ }
387
+ return String(error) || "Unknown error";
385
388
  };
386
389
 
387
390
  exports.BigMath = BigMath;
@@ -389,33 +392,29 @@ exports.Deferred = Deferred;
389
392
  exports.MAX_DECIMALS_FORMAT = MAX_DECIMALS_FORMAT;
390
393
  exports.REGEX_HEX_STRING = REGEX_HEX_STRING;
391
394
  exports.addTrailingSlash = addTrailingSlash;
392
- exports.blake2Concat = blake2Concat;
393
395
  exports.classNames = classNames;
394
- exports.convertAddress = convertAddress;
395
- exports.decodeAnyAddress = decodeAnyAddress;
396
- exports.decodeSs58Format = decodeSs58Format;
397
- exports.encodeAnyAddress = encodeAnyAddress;
396
+ exports.cn = cn;
398
397
  exports.firstThenDebounce = firstThenDebounce;
399
398
  exports.formatDecimals = formatDecimals;
400
399
  exports.formatPrice = formatPrice;
400
+ exports.getLoadable$ = getLoadable$;
401
401
  exports.getSharedObservable = getSharedObservable;
402
402
  exports.hasOwnProperty = hasOwnProperty;
403
403
  exports.isAbortError = isAbortError;
404
- exports.isAddressEqual = isAddressEqual;
405
404
  exports.isArrayOf = isArrayOf;
406
405
  exports.isAscii = isAscii;
407
406
  exports.isBigInt = isBigInt;
408
407
  exports.isBooleanTrue = isBooleanTrue;
409
- exports.isEthereumAddress = isEthereumAddress;
410
408
  exports.isHexString = isHexString;
411
409
  exports.isNotNil = isNotNil;
410
+ exports.isPromise = isPromise;
411
+ exports.isSubject = isSubject;
412
412
  exports.isTruthy = isTruthy;
413
- exports.isValidSubstrateAddress = isValidSubstrateAddress;
414
413
  exports.keepAlive = keepAlive;
415
- exports.normalizeAddress = normalizeAddress;
416
414
  exports.planckToTokens = planckToTokens;
415
+ exports.replaySubjectFrom = replaySubjectFrom;
417
416
  exports.sleep = sleep;
417
+ exports.splitSubject = splitSubject;
418
418
  exports.throwAfter = throwAfter;
419
419
  exports.tokensToPlanck = tokensToPlanck;
420
- exports.twox64Concat = twox64Concat;
421
420
  exports.validateHexString = validateHexString;