@talismn/util 0.4.2 → 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.
- package/dist/declarations/src/Prettify.d.ts +3 -0
- package/dist/declarations/src/classNames.d.ts +1 -0
- package/dist/declarations/src/getLoadable.d.ts +25 -0
- package/dist/declarations/src/getSharedObservable.d.ts +13 -0
- package/dist/declarations/src/index.d.ts +13 -13
- package/dist/declarations/src/isAbortError.d.ts +1 -0
- package/dist/declarations/src/isHexString.d.ts +3 -0
- package/dist/declarations/src/isPromise.d.ts +1 -0
- package/dist/declarations/src/isSubject.d.ts +5 -0
- package/dist/declarations/src/keepAlive.d.ts +17 -0
- package/dist/declarations/src/replaySubjectFrom.d.ts +12 -0
- package/dist/declarations/src/splitSubject.d.ts +11 -0
- package/dist/declarations/src/validateHexString.d.ts +2 -3
- package/dist/talismn-util.cjs.dev.js +189 -117
- package/dist/talismn-util.cjs.prod.js +189 -117
- package/dist/talismn-util.esm.js +180 -109
- package/package.json +3 -9
- package/dist/declarations/src/blake2Concat.d.ts +0 -1
- package/dist/declarations/src/convertAddress.d.ts +0 -7
- package/dist/declarations/src/decodeAnyAddress.d.ts +0 -1
- package/dist/declarations/src/decodeSs58Format.d.ts +0 -1
- package/dist/declarations/src/encodeAnyAddress.d.ts +0 -1
- package/dist/declarations/src/isAddressEqual.d.ts +0 -1
- package/dist/declarations/src/isEthereumAddress.d.ts +0 -1
- package/dist/declarations/src/isValidSubstrateAddress.d.ts +0 -7
- package/dist/declarations/src/normalizeAddress.d.ts +0 -1
- package/dist/declarations/src/twox64Concat.d.ts +0 -1
package/dist/talismn-util.esm.js
CHANGED
@@ -1,8 +1,5 @@
|
|
1
|
-
import { u8aToHex, u8aConcat, u8aToU8a, hexToU8a } from '@polkadot/util';
|
2
|
-
import { blake2AsU8a, isEthereumAddress as isEthereumAddress$1, ethereumEncode, decodeAddress as decodeAddress$1, base58Decode, checkAddressChecksum, xxhashAsU8a } from '@polkadot/util-crypto';
|
3
1
|
import { twMerge } from 'tailwind-merge';
|
4
|
-
import {
|
5
|
-
import { concat, take, skip, debounceTime } from 'rxjs';
|
2
|
+
import { concat, take, skip, debounceTime, shareReplay, Subject, tap, ReplaySubject, timer, switchMap, from, startWith, map, catchError, of } from 'rxjs';
|
6
3
|
import BigNumber from 'bignumber.js';
|
7
4
|
|
8
5
|
const addTrailingSlash = url => {
|
@@ -39,54 +36,8 @@ const BigMath = {
|
|
39
36
|
}
|
40
37
|
};
|
41
38
|
|
42
|
-
const bitLength$1 = 128;
|
43
|
-
function blake2Concat(input) {
|
44
|
-
return u8aToHex(u8aConcat(blake2AsU8a(input, bitLength$1), u8aToU8a(input)));
|
45
|
-
}
|
46
|
-
|
47
39
|
const classNames = twMerge;
|
48
|
-
|
49
|
-
function encodeAnyAddress(key, ss58Format) {
|
50
|
-
try {
|
51
|
-
return encodeAddress(key, ss58Format);
|
52
|
-
} catch (error) {
|
53
|
-
if (typeof key !== "string") throw error;
|
54
|
-
if (!isEthereumAddress$1(key)) throw error;
|
55
|
-
return ethereumEncode(key);
|
56
|
-
}
|
57
|
-
}
|
58
|
-
|
59
|
-
/**
|
60
|
-
*
|
61
|
-
* @param address substrate SS58 address
|
62
|
-
* @param prefix prefix used to format the address
|
63
|
-
* @returns address encoded with supplied prefix
|
64
|
-
*/
|
65
|
-
const convertAddress = (address, prefix) => {
|
66
|
-
return encodeAnyAddress(address, prefix ?? undefined);
|
67
|
-
};
|
68
|
-
|
69
|
-
function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
|
70
|
-
try {
|
71
|
-
return decodeAddress(encoded, ignoreChecksum, ss58Format);
|
72
|
-
} catch (error) {
|
73
|
-
if (typeof encoded !== "string") throw error;
|
74
|
-
if (!isEthereumAddress$1(encoded)) throw error;
|
75
|
-
return hexToU8a(encoded.slice("0x".length));
|
76
|
-
}
|
77
|
-
}
|
78
|
-
|
79
|
-
const decodeSs58Format = address => {
|
80
|
-
if (!address) return;
|
81
|
-
try {
|
82
|
-
decodeAddress$1(address);
|
83
|
-
const decoded = base58Decode(address);
|
84
|
-
const [,,, ss58Format] = checkAddressChecksum(decoded);
|
85
|
-
return ss58Format;
|
86
|
-
} catch {
|
87
|
-
return; // invalid address
|
88
|
-
}
|
89
|
-
};
|
40
|
+
const cn = twMerge;
|
90
41
|
|
91
42
|
/**
|
92
43
|
* In TypeScript, a deferred promise refers to a pattern that involves creating a promise that can be
|
@@ -189,43 +140,129 @@ const formatPrice = (price, currency, compact) => {
|
|
189
140
|
}).format(price);
|
190
141
|
};
|
191
142
|
|
143
|
+
const CACHE = new Map();
|
144
|
+
|
145
|
+
/**
|
146
|
+
* When using react-rxjs hooks and state observables, the options are used as weak map keys.
|
147
|
+
* This means that if the options object is recreated on each render, the observable will be recreated as well.
|
148
|
+
* 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
|
149
|
+
*
|
150
|
+
* @param namespace
|
151
|
+
* @param args
|
152
|
+
* @param createObservable
|
153
|
+
* @param serializer
|
154
|
+
* @returns
|
155
|
+
*/
|
156
|
+
const getSharedObservable = (namespace, args, createObservable, serializer = args => JSON.stringify(args)) => {
|
157
|
+
const cacheKey = `${namespace}:${serializer(args)}`;
|
158
|
+
if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
|
159
|
+
const obs = createObservable(args);
|
160
|
+
const sharedObs = obs.pipe(shareReplay({
|
161
|
+
bufferSize: 1,
|
162
|
+
refCount: true
|
163
|
+
}));
|
164
|
+
CACHE.set(cacheKey, sharedObs);
|
165
|
+
return sharedObs;
|
166
|
+
};
|
167
|
+
|
192
168
|
function hasOwnProperty(obj, prop) {
|
193
169
|
if (typeof obj !== "object") return false;
|
194
170
|
if (obj === null) return false;
|
195
171
|
return prop in obj;
|
196
172
|
}
|
197
173
|
|
174
|
+
const isAbortError = error => {
|
175
|
+
return error instanceof Error && error.name === "AbortError";
|
176
|
+
};
|
177
|
+
|
198
178
|
function isArrayOf(array, func) {
|
199
179
|
if (array.length > 0 && array[0] instanceof func) return true;
|
200
180
|
return false;
|
201
181
|
}
|
202
182
|
|
183
|
+
const isAscii = str => {
|
184
|
+
return [...str].every(char => char.charCodeAt(0) <= 127);
|
185
|
+
};
|
186
|
+
|
203
187
|
const isBigInt = value => typeof value === "bigint";
|
204
188
|
|
205
189
|
const isBooleanTrue = x => !!x;
|
206
190
|
|
207
|
-
const
|
191
|
+
const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
|
192
|
+
const isHexString = value => {
|
193
|
+
return typeof value === "string" && REGEX_HEX_STRING.test(value);
|
194
|
+
};
|
208
195
|
|
209
196
|
/**
|
210
|
-
*
|
211
|
-
*
|
212
|
-
*
|
213
|
-
*
|
197
|
+
* WARNING: This function only checks against null or undefined, it does not coerce the value.
|
198
|
+
* ie: false and 0 are considered not nil
|
199
|
+
* Use isTruthy instead for a regular coercion check.
|
200
|
+
*
|
201
|
+
* @param value
|
202
|
+
* @returns whether the value is neither null nor undefined
|
214
203
|
*/
|
215
|
-
const
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
204
|
+
const isNotNil = value => value !== null && value !== undefined;
|
205
|
+
|
206
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
207
|
+
const isPromise = value => !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
208
|
+
|
209
|
+
/**
|
210
|
+
* Tests to see if an object is an RxJS {@link Subject}.
|
211
|
+
*/
|
212
|
+
function isSubject(object) {
|
213
|
+
if (!object) return false;
|
214
|
+
if (object instanceof Subject) return true;
|
215
|
+
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);
|
216
|
+
}
|
217
|
+
|
218
|
+
/**
|
219
|
+
* Returns `true` if `value` is a function.
|
220
|
+
*/
|
221
|
+
function isFn(value) {
|
222
|
+
return typeof value === "function";
|
223
|
+
}
|
224
|
+
|
225
|
+
/**
|
226
|
+
* Returns `true` if `value` is a boolean.
|
227
|
+
*/
|
228
|
+
function isBool(value) {
|
229
|
+
return typeof value === "boolean";
|
230
|
+
}
|
231
|
+
|
232
|
+
const isTruthy = value => Boolean(value);
|
233
|
+
|
234
|
+
/**
|
235
|
+
* An RxJS operator that keeps the source observable alive for a specified duration
|
236
|
+
* after all subscribers have unsubscribed. This prevents expensive re-subscriptions
|
237
|
+
* when subscribers come and go frequently.
|
238
|
+
*
|
239
|
+
* @param keepAliveMs - Duration in milliseconds to keep the source alive after last unsubscription
|
240
|
+
* @returns MonoTypeOperatorFunction that can be used in pipe()
|
241
|
+
*
|
242
|
+
* @example
|
243
|
+
* ```typescript
|
244
|
+
* const data$ = expensive_api_call$.pipe(
|
245
|
+
* keepAlive(3000) // Keep alive for 3 seconds
|
246
|
+
* );
|
247
|
+
* ```
|
248
|
+
*/
|
249
|
+
const keepAlive = timeout => {
|
250
|
+
let release;
|
251
|
+
return source => source.pipe(tap({
|
252
|
+
subscribe: () => {
|
253
|
+
release = keepSourceSubscribed(source, timeout);
|
254
|
+
},
|
255
|
+
unsubscribe: () => {
|
256
|
+
release?.();
|
257
|
+
}
|
258
|
+
}), shareReplay({
|
259
|
+
refCount: true,
|
260
|
+
bufferSize: 1
|
261
|
+
}));
|
262
|
+
};
|
263
|
+
const keepSourceSubscribed = (observable, ms) => {
|
264
|
+
const sub = observable.subscribe();
|
265
|
+
return () => setTimeout(() => sub.unsubscribe(), ms);
|
229
266
|
};
|
230
267
|
|
231
268
|
function planckToTokens(planck, tokenDecimals) {
|
@@ -236,10 +273,50 @@ function planckToTokens(planck, tokenDecimals) {
|
|
236
273
|
return new BigNumber(planck).multipliedBy(multiplier).toString(10);
|
237
274
|
}
|
238
275
|
|
276
|
+
/**
|
277
|
+
* Turns a value into a {@link ReplaySubject} of size 1.
|
278
|
+
*
|
279
|
+
* If the value is already a {@link ReplaySubject}, it will be returned as-is.
|
280
|
+
*
|
281
|
+
* If the value is a {@link Promise}, it will be awaited,
|
282
|
+
* and the awaited value will be published into the {@link ReplaySubject} when it becomes available.
|
283
|
+
*
|
284
|
+
* For any other type of value, it will be immediately published into the {@link ReplaySubject}.
|
285
|
+
*/
|
286
|
+
const replaySubjectFrom = initialValue => {
|
287
|
+
if (initialValue instanceof ReplaySubject) return initialValue;
|
288
|
+
const subject = new ReplaySubject(1);
|
289
|
+
|
290
|
+
// if initialValue is a promise, await it and then call `subject.next()` with the awaited value
|
291
|
+
if (isPromise(initialValue)) {
|
292
|
+
initialValue.then(value => subject.next(value), error => subject.error(error));
|
293
|
+
return subject;
|
294
|
+
}
|
295
|
+
|
296
|
+
// if initialValue is not a promise, immediately call `subject.next()` with the value
|
297
|
+
subject.next(initialValue);
|
298
|
+
return subject;
|
299
|
+
};
|
300
|
+
|
239
301
|
const sleep = ms => new Promise(resolve => {
|
240
302
|
if (process.env.NODE_ENV === "test") resolve();else setTimeout(resolve, ms);
|
241
303
|
});
|
242
304
|
|
305
|
+
/**
|
306
|
+
* Takes a subject and splits it into two parts:
|
307
|
+
*
|
308
|
+
* 1. A function to submit new values into the subject.
|
309
|
+
* 2. An observable for subscribing to new values from the subject.
|
310
|
+
*
|
311
|
+
* This can be helpful when, to avoid bugs, you want to expose only one
|
312
|
+
* of these parts to external code and keep the other part private.
|
313
|
+
*/
|
314
|
+
function splitSubject(subject) {
|
315
|
+
const next = value => subject.next(value);
|
316
|
+
const observable = subject.asObservable();
|
317
|
+
return [next, observable];
|
318
|
+
}
|
319
|
+
|
243
320
|
const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
|
244
321
|
|
245
322
|
function tokensToPlanck(tokens, tokenDecimals) {
|
@@ -250,17 +327,11 @@ function tokensToPlanck(tokens, tokenDecimals) {
|
|
250
327
|
return new BigNumber(tokens).multipliedBy(multiplier).toString(10);
|
251
328
|
}
|
252
329
|
|
253
|
-
const bitLength = 64;
|
254
|
-
function twox64Concat(input) {
|
255
|
-
const inputAsU8a = typeof input === "string" ? input : new Uint8Array(input);
|
256
|
-
return u8aToHex(u8aConcat(xxhashAsU8a(inputAsU8a, bitLength), u8aToU8a(inputAsU8a)));
|
257
|
-
}
|
258
|
-
|
259
330
|
/**
|
260
331
|
* @name validateHexString
|
261
332
|
* @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
|
262
333
|
* @param {string} str - string to check
|
263
|
-
* @returns {
|
334
|
+
* @returns {`0x${string}`} - boolean
|
264
335
|
* @example
|
265
336
|
* validateHexString("0x1234") // "0x1234"
|
266
337
|
* validateHexString("1234") // Error: Expected a hex string
|
@@ -276,38 +347,38 @@ const validateHexString = str => {
|
|
276
347
|
throw new Error("Expected a hex string");
|
277
348
|
};
|
278
349
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
const
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
350
|
+
// Designed to be serializable as it can be sent to the frontend
|
351
|
+
|
352
|
+
function getLoadable$(factory, options = {}) {
|
353
|
+
const {
|
354
|
+
getError,
|
355
|
+
refreshInterval
|
356
|
+
} = options;
|
357
|
+
const createLoadableStream = () => from(factory()).pipe(map(data => ({
|
358
|
+
status: "success",
|
359
|
+
data
|
360
|
+
})), catchError(error => of({
|
361
|
+
status: "error",
|
362
|
+
error: getError ? getError(error) : getGenericError(error)
|
363
|
+
})));
|
364
|
+
const source$ = refreshInterval ? timer(0, refreshInterval).pipe(switchMap(() => createLoadableStream())) : createLoadableStream();
|
365
|
+
return source$.pipe(startWith({
|
366
|
+
status: "loading"
|
367
|
+
}));
|
368
|
+
}
|
369
|
+
const getGenericError = error => ({
|
370
|
+
name: "Error",
|
371
|
+
message: getGenericErrorMessage(error)
|
372
|
+
});
|
373
|
+
const getGenericErrorMessage = error => {
|
374
|
+
if (typeof error === "string") {
|
375
|
+
return error;
|
376
|
+
} else if (error instanceof Error) {
|
377
|
+
return error.message;
|
378
|
+
} else if (error && typeof error === "object" && "message" in error) {
|
379
|
+
return error.message;
|
290
380
|
}
|
381
|
+
return String(error) || "Unknown error";
|
291
382
|
};
|
292
383
|
|
293
|
-
|
294
|
-
return normalizeAddress(address1) === normalizeAddress(address2);
|
295
|
-
};
|
296
|
-
|
297
|
-
const isAscii = str => {
|
298
|
-
return [...str].every(char => char.charCodeAt(0) <= 127);
|
299
|
-
};
|
300
|
-
|
301
|
-
/**
|
302
|
-
* WARNING: This function only checks against null or undefined, it does not coerce the value.
|
303
|
-
* ie: false and 0 are considered not nil
|
304
|
-
* Use isTruthy instead for a regular coercion check.
|
305
|
-
*
|
306
|
-
* @param value
|
307
|
-
* @returns whether the value is neither null nor undefined
|
308
|
-
*/
|
309
|
-
const isNotNil = value => value !== null && value !== undefined;
|
310
|
-
|
311
|
-
const isTruthy = value => Boolean(value);
|
312
|
-
|
313
|
-
export { BigMath, Deferred, MAX_DECIMALS_FORMAT, addTrailingSlash, blake2Concat, classNames, convertAddress, decodeAnyAddress, decodeSs58Format, encodeAnyAddress, firstThenDebounce, formatDecimals, formatPrice, hasOwnProperty, isAddressEqual, isArrayOf, isAscii, isBigInt, isBooleanTrue, isEthereumAddress, isNotNil, isTruthy, isValidSubstrateAddress, normalizeAddress, planckToTokens, sleep, throwAfter, tokensToPlanck, twox64Concat, validateHexString };
|
384
|
+
export { BigMath, Deferred, MAX_DECIMALS_FORMAT, REGEX_HEX_STRING, addTrailingSlash, classNames, cn, firstThenDebounce, formatDecimals, formatPrice, getLoadable$, getSharedObservable, hasOwnProperty, isAbortError, isArrayOf, isAscii, isBigInt, isBooleanTrue, isHexString, isNotNil, isPromise, isSubject, isTruthy, keepAlive, planckToTokens, replaySubjectFrom, sleep, splitSubject, throwAfter, tokensToPlanck, validateHexString };
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@talismn/util",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.5.1",
|
4
4
|
"author": "Talisman",
|
5
5
|
"homepage": "https://talisman.xyz",
|
6
6
|
"license": "GPL-3.0-or-later",
|
@@ -26,21 +26,15 @@
|
|
26
26
|
"tailwind-merge": "^2.5.4"
|
27
27
|
},
|
28
28
|
"devDependencies": {
|
29
|
-
"@polkadot/keyring": "13.5.1",
|
30
|
-
"@polkadot/util": "13.5.1",
|
31
|
-
"@polkadot/util-crypto": "13.5.1",
|
32
29
|
"@types/jest": "^29.5.14",
|
33
30
|
"eslint": "^8.57.1",
|
34
31
|
"jest": "^29.7.0",
|
35
32
|
"ts-jest": "^29.2.5",
|
36
33
|
"typescript": "^5.6.3",
|
37
|
-
"@talismn/
|
38
|
-
"@talismn/
|
34
|
+
"@talismn/tsconfig": "0.0.2",
|
35
|
+
"@talismn/eslint-config": "0.0.3"
|
39
36
|
},
|
40
37
|
"peerDependencies": {
|
41
|
-
"@polkadot/keyring": "*",
|
42
|
-
"@polkadot/util": "*",
|
43
|
-
"@polkadot/util-crypto": "*",
|
44
38
|
"rxjs": ">= 7.8.1"
|
45
39
|
},
|
46
40
|
"eslintConfig": {
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare function blake2Concat(input: Uint8Array): `0x${string}`;
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare function decodeAnyAddress(encoded?: string | Uint8Array | null | undefined, ignoreChecksum?: boolean | undefined, ss58Format?: number | undefined): Uint8Array;
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare const decodeSs58Format: (address?: string) => number | undefined;
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare function encodeAnyAddress(key: string | Uint8Array, ss58Format?: number | undefined): string;
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare const isAddressEqual: (address1: string, address2: string) => boolean;
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare const isEthereumAddress: (address: string | undefined | null) => address is `0x${string}`;
|
@@ -1,7 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Similar to isValidAddress but will not call isEthereumAddress under the hood
|
3
|
-
* @param address
|
4
|
-
* @param prefix if supplied, the method will also check the prefix
|
5
|
-
* @returns true if valid substrate (SS58) address, false otherwise
|
6
|
-
*/
|
7
|
-
export declare const isValidSubstrateAddress: (address: string, prefix?: number | null) => boolean;
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare const normalizeAddress: (address: string) => string;
|
@@ -1 +0,0 @@
|
|
1
|
-
export declare function twox64Concat(input: string | Buffer | Uint8Array): `0x${string}`;
|