@talismn/util 0.4.1 → 0.5.0

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.
@@ -0,0 +1,13 @@
1
+ import { Observable } from "rxjs";
2
+ /**
3
+ * When using react-rxjs hooks and state observables, the options are used as weak map keys.
4
+ * This means that if the options object is recreated on each render, the observable will be recreated as well.
5
+ * 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
6
+ *
7
+ * @param namespace
8
+ * @param args
9
+ * @param createObservable
10
+ * @param serializer
11
+ * @returns
12
+ */
13
+ export declare const getSharedObservable: <Args, Output, ObsOutput = Observable<Output>>(namespace: string, args: Args, createObservable: (args: Args) => ObsOutput, serializer?: (args: Args) => string) => ObsOutput;
@@ -28,3 +28,7 @@ export * from "./isAddressEqual";
28
28
  export * from "./isAscii";
29
29
  export * from "./isNotNil";
30
30
  export * from "./isTruthy";
31
+ export * from "./isAbortError";
32
+ export * from "./getSharedObservable";
33
+ export * from "./keepAlive";
34
+ export * from "./isHexString";
@@ -0,0 +1 @@
1
+ export declare const isAbortError: (error: unknown) => boolean;
@@ -0,0 +1,3 @@
1
+ export type HexString = `0x${string}`;
2
+ export declare const REGEX_HEX_STRING: RegExp;
3
+ export declare const isHexString: (value: unknown) => value is HexString;
@@ -0,0 +1,17 @@
1
+ import type { OperatorFunction } from "rxjs";
2
+ /**
3
+ * An RxJS operator that keeps the source observable alive for a specified duration
4
+ * after all subscribers have unsubscribed. This prevents expensive re-subscriptions
5
+ * when subscribers come and go frequently.
6
+ *
7
+ * @param keepAliveMs - Duration in milliseconds to keep the source alive after last unsubscription
8
+ * @returns MonoTypeOperatorFunction that can be used in pipe()
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const data$ = expensive_api_call$.pipe(
13
+ * keepAlive(3000) // Keep alive for 3 seconds
14
+ * );
15
+ * ```
16
+ */
17
+ export declare const keepAlive: <T>(timeout: number) => OperatorFunction<T, T>;
@@ -282,13 +282,13 @@ const validateHexString = str => {
282
282
  throw new Error("Expected a hex string");
283
283
  };
284
284
 
285
- const CACHE = new Map();
285
+ const CACHE$1 = new Map();
286
286
 
287
287
  // Normalize an address in a way that it can be compared to other addresses that have also been normalized
288
288
  const normalizeAddress = address => {
289
289
  try {
290
- if (!CACHE.has(address)) CACHE.set(address, encodeAnyAddress(address));
291
- return CACHE.get(address);
290
+ if (!CACHE$1.has(address)) CACHE$1.set(address, encodeAnyAddress(address));
291
+ return CACHE$1.get(address);
292
292
  } catch (cause) {
293
293
  throw new Error(`Unable to normalize address: ${address}`, {
294
294
  cause
@@ -316,9 +316,78 @@ const isNotNil = value => value !== null && value !== undefined;
316
316
 
317
317
  const isTruthy = value => Boolean(value);
318
318
 
319
+ const isAbortError = error => {
320
+ return error instanceof Error && error.name === "AbortError";
321
+ };
322
+
323
+ const CACHE = new Map();
324
+
325
+ /**
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
335
+ */
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
+ };
347
+
348
+ /**
349
+ * An RxJS operator that keeps the source observable alive for a specified duration
350
+ * after all subscribers have unsubscribed. This prevents expensive re-subscriptions
351
+ * when subscribers come and go frequently.
352
+ *
353
+ * @param keepAliveMs - Duration in milliseconds to keep the source alive after last unsubscription
354
+ * @returns MonoTypeOperatorFunction that can be used in pipe()
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * const data$ = expensive_api_call$.pipe(
359
+ * keepAlive(3000) // Keep alive for 3 seconds
360
+ * );
361
+ * ```
362
+ */
363
+ const keepAlive = timeout => {
364
+ let release;
365
+ return source => source.pipe(rxjs.tap({
366
+ subscribe: () => {
367
+ release = keepSourceSubscribed(source, timeout);
368
+ },
369
+ unsubscribe: () => {
370
+ release?.();
371
+ }
372
+ }), rxjs.shareReplay({
373
+ refCount: true,
374
+ bufferSize: 1
375
+ }));
376
+ };
377
+ const keepSourceSubscribed = (observable, ms) => {
378
+ const sub = observable.subscribe();
379
+ return () => setTimeout(() => sub.unsubscribe(), ms);
380
+ };
381
+
382
+ const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
383
+ const isHexString = value => {
384
+ return typeof value === "string" && REGEX_HEX_STRING.test(value);
385
+ };
386
+
319
387
  exports.BigMath = BigMath;
320
388
  exports.Deferred = Deferred;
321
389
  exports.MAX_DECIMALS_FORMAT = MAX_DECIMALS_FORMAT;
390
+ exports.REGEX_HEX_STRING = REGEX_HEX_STRING;
322
391
  exports.addTrailingSlash = addTrailingSlash;
323
392
  exports.blake2Concat = blake2Concat;
324
393
  exports.classNames = classNames;
@@ -329,16 +398,20 @@ exports.encodeAnyAddress = encodeAnyAddress;
329
398
  exports.firstThenDebounce = firstThenDebounce;
330
399
  exports.formatDecimals = formatDecimals;
331
400
  exports.formatPrice = formatPrice;
401
+ exports.getSharedObservable = getSharedObservable;
332
402
  exports.hasOwnProperty = hasOwnProperty;
403
+ exports.isAbortError = isAbortError;
333
404
  exports.isAddressEqual = isAddressEqual;
334
405
  exports.isArrayOf = isArrayOf;
335
406
  exports.isAscii = isAscii;
336
407
  exports.isBigInt = isBigInt;
337
408
  exports.isBooleanTrue = isBooleanTrue;
338
409
  exports.isEthereumAddress = isEthereumAddress;
410
+ exports.isHexString = isHexString;
339
411
  exports.isNotNil = isNotNil;
340
412
  exports.isTruthy = isTruthy;
341
413
  exports.isValidSubstrateAddress = isValidSubstrateAddress;
414
+ exports.keepAlive = keepAlive;
342
415
  exports.normalizeAddress = normalizeAddress;
343
416
  exports.planckToTokens = planckToTokens;
344
417
  exports.sleep = sleep;
@@ -282,13 +282,13 @@ const validateHexString = str => {
282
282
  throw new Error("Expected a hex string");
283
283
  };
284
284
 
285
- const CACHE = new Map();
285
+ const CACHE$1 = new Map();
286
286
 
287
287
  // Normalize an address in a way that it can be compared to other addresses that have also been normalized
288
288
  const normalizeAddress = address => {
289
289
  try {
290
- if (!CACHE.has(address)) CACHE.set(address, encodeAnyAddress(address));
291
- return CACHE.get(address);
290
+ if (!CACHE$1.has(address)) CACHE$1.set(address, encodeAnyAddress(address));
291
+ return CACHE$1.get(address);
292
292
  } catch (cause) {
293
293
  throw new Error(`Unable to normalize address: ${address}`, {
294
294
  cause
@@ -316,9 +316,78 @@ const isNotNil = value => value !== null && value !== undefined;
316
316
 
317
317
  const isTruthy = value => Boolean(value);
318
318
 
319
+ const isAbortError = error => {
320
+ return error instanceof Error && error.name === "AbortError";
321
+ };
322
+
323
+ const CACHE = new Map();
324
+
325
+ /**
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
335
+ */
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
+ };
347
+
348
+ /**
349
+ * An RxJS operator that keeps the source observable alive for a specified duration
350
+ * after all subscribers have unsubscribed. This prevents expensive re-subscriptions
351
+ * when subscribers come and go frequently.
352
+ *
353
+ * @param keepAliveMs - Duration in milliseconds to keep the source alive after last unsubscription
354
+ * @returns MonoTypeOperatorFunction that can be used in pipe()
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * const data$ = expensive_api_call$.pipe(
359
+ * keepAlive(3000) // Keep alive for 3 seconds
360
+ * );
361
+ * ```
362
+ */
363
+ const keepAlive = timeout => {
364
+ let release;
365
+ return source => source.pipe(rxjs.tap({
366
+ subscribe: () => {
367
+ release = keepSourceSubscribed(source, timeout);
368
+ },
369
+ unsubscribe: () => {
370
+ release?.();
371
+ }
372
+ }), rxjs.shareReplay({
373
+ refCount: true,
374
+ bufferSize: 1
375
+ }));
376
+ };
377
+ const keepSourceSubscribed = (observable, ms) => {
378
+ const sub = observable.subscribe();
379
+ return () => setTimeout(() => sub.unsubscribe(), ms);
380
+ };
381
+
382
+ const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
383
+ const isHexString = value => {
384
+ return typeof value === "string" && REGEX_HEX_STRING.test(value);
385
+ };
386
+
319
387
  exports.BigMath = BigMath;
320
388
  exports.Deferred = Deferred;
321
389
  exports.MAX_DECIMALS_FORMAT = MAX_DECIMALS_FORMAT;
390
+ exports.REGEX_HEX_STRING = REGEX_HEX_STRING;
322
391
  exports.addTrailingSlash = addTrailingSlash;
323
392
  exports.blake2Concat = blake2Concat;
324
393
  exports.classNames = classNames;
@@ -329,16 +398,20 @@ exports.encodeAnyAddress = encodeAnyAddress;
329
398
  exports.firstThenDebounce = firstThenDebounce;
330
399
  exports.formatDecimals = formatDecimals;
331
400
  exports.formatPrice = formatPrice;
401
+ exports.getSharedObservable = getSharedObservable;
332
402
  exports.hasOwnProperty = hasOwnProperty;
403
+ exports.isAbortError = isAbortError;
333
404
  exports.isAddressEqual = isAddressEqual;
334
405
  exports.isArrayOf = isArrayOf;
335
406
  exports.isAscii = isAscii;
336
407
  exports.isBigInt = isBigInt;
337
408
  exports.isBooleanTrue = isBooleanTrue;
338
409
  exports.isEthereumAddress = isEthereumAddress;
410
+ exports.isHexString = isHexString;
339
411
  exports.isNotNil = isNotNil;
340
412
  exports.isTruthy = isTruthy;
341
413
  exports.isValidSubstrateAddress = isValidSubstrateAddress;
414
+ exports.keepAlive = keepAlive;
342
415
  exports.normalizeAddress = normalizeAddress;
343
416
  exports.planckToTokens = planckToTokens;
344
417
  exports.sleep = sleep;
@@ -2,7 +2,7 @@ import { u8aToHex, u8aConcat, u8aToU8a, hexToU8a } from '@polkadot/util';
2
2
  import { blake2AsU8a, isEthereumAddress as isEthereumAddress$1, ethereumEncode, decodeAddress as decodeAddress$1, base58Decode, checkAddressChecksum, xxhashAsU8a } from '@polkadot/util-crypto';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import { encodeAddress, decodeAddress } from '@polkadot/keyring';
5
- import { concat, take, skip, debounceTime } from 'rxjs';
5
+ import { concat, take, skip, debounceTime, shareReplay, tap } from 'rxjs';
6
6
  import BigNumber from 'bignumber.js';
7
7
 
8
8
  const addTrailingSlash = url => {
@@ -276,13 +276,13 @@ const validateHexString = str => {
276
276
  throw new Error("Expected a hex string");
277
277
  };
278
278
 
279
- const CACHE = new Map();
279
+ const CACHE$1 = new Map();
280
280
 
281
281
  // Normalize an address in a way that it can be compared to other addresses that have also been normalized
282
282
  const normalizeAddress = address => {
283
283
  try {
284
- if (!CACHE.has(address)) CACHE.set(address, encodeAnyAddress(address));
285
- return CACHE.get(address);
284
+ if (!CACHE$1.has(address)) CACHE$1.set(address, encodeAnyAddress(address));
285
+ return CACHE$1.get(address);
286
286
  } catch (cause) {
287
287
  throw new Error(`Unable to normalize address: ${address}`, {
288
288
  cause
@@ -310,4 +310,72 @@ const isNotNil = value => value !== null && value !== undefined;
310
310
 
311
311
  const isTruthy = value => Boolean(value);
312
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 };
313
+ const isAbortError = error => {
314
+ return error instanceof Error && error.name === "AbortError";
315
+ };
316
+
317
+ const CACHE = new Map();
318
+
319
+ /**
320
+ * When using react-rxjs hooks and state observables, the options are used as weak map keys.
321
+ * This means that if the options object is recreated on each render, the observable will be recreated as well.
322
+ * 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
323
+ *
324
+ * @param namespace
325
+ * @param args
326
+ * @param createObservable
327
+ * @param serializer
328
+ * @returns
329
+ */
330
+ const getSharedObservable = (namespace, args, createObservable, serializer = args => JSON.stringify(args)) => {
331
+ const cacheKey = `${namespace}:${serializer(args)}`;
332
+ if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
333
+ const obs = createObservable(args);
334
+ const sharedObs = obs.pipe(shareReplay({
335
+ bufferSize: 1,
336
+ refCount: true
337
+ }));
338
+ CACHE.set(cacheKey, sharedObs);
339
+ return sharedObs;
340
+ };
341
+
342
+ /**
343
+ * An RxJS operator that keeps the source observable alive for a specified duration
344
+ * after all subscribers have unsubscribed. This prevents expensive re-subscriptions
345
+ * when subscribers come and go frequently.
346
+ *
347
+ * @param keepAliveMs - Duration in milliseconds to keep the source alive after last unsubscription
348
+ * @returns MonoTypeOperatorFunction that can be used in pipe()
349
+ *
350
+ * @example
351
+ * ```typescript
352
+ * const data$ = expensive_api_call$.pipe(
353
+ * keepAlive(3000) // Keep alive for 3 seconds
354
+ * );
355
+ * ```
356
+ */
357
+ const keepAlive = timeout => {
358
+ let release;
359
+ return source => source.pipe(tap({
360
+ subscribe: () => {
361
+ release = keepSourceSubscribed(source, timeout);
362
+ },
363
+ unsubscribe: () => {
364
+ release?.();
365
+ }
366
+ }), shareReplay({
367
+ refCount: true,
368
+ bufferSize: 1
369
+ }));
370
+ };
371
+ const keepSourceSubscribed = (observable, ms) => {
372
+ const sub = observable.subscribe();
373
+ return () => setTimeout(() => sub.unsubscribe(), ms);
374
+ };
375
+
376
+ const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
377
+ const isHexString = value => {
378
+ return typeof value === "string" && REGEX_HEX_STRING.test(value);
379
+ };
380
+
381
+ export { BigMath, Deferred, MAX_DECIMALS_FORMAT, REGEX_HEX_STRING, addTrailingSlash, blake2Concat, classNames, convertAddress, decodeAnyAddress, decodeSs58Format, encodeAnyAddress, firstThenDebounce, formatDecimals, formatPrice, getSharedObservable, hasOwnProperty, isAbortError, isAddressEqual, isArrayOf, isAscii, isBigInt, isBooleanTrue, isEthereumAddress, isHexString, isNotNil, isTruthy, isValidSubstrateAddress, keepAlive, normalizeAddress, planckToTokens, sleep, throwAfter, tokensToPlanck, twox64Concat, validateHexString };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/util",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -26,16 +26,16 @@
26
26
  "tailwind-merge": "^2.5.4"
27
27
  },
28
28
  "devDependencies": {
29
- "@polkadot/keyring": "13.4.3",
30
- "@polkadot/util": "13.4.3",
31
- "@polkadot/util-crypto": "13.4.3",
29
+ "@polkadot/keyring": "13.5.1",
30
+ "@polkadot/util": "13.5.1",
31
+ "@polkadot/util-crypto": "13.5.1",
32
32
  "@types/jest": "^29.5.14",
33
33
  "eslint": "^8.57.1",
34
34
  "jest": "^29.7.0",
35
35
  "ts-jest": "^29.2.5",
36
36
  "typescript": "^5.6.3",
37
- "@talismn/eslint-config": "0.0.3",
38
- "@talismn/tsconfig": "0.0.2"
37
+ "@talismn/tsconfig": "0.0.2",
38
+ "@talismn/eslint-config": "0.0.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@polkadot/keyring": "*",