@talismn/util 1.0.1 → 2.0.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.
- package/LICENSE +253 -674
- package/dist/index.d.mts +361 -92
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +360 -91
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +894 -438
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +817 -378
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -10
package/dist/index.mjs
CHANGED
|
@@ -1,414 +1,853 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import BigNumber from "bignumber.js";
|
|
2
|
+
import { isEqual } from "lodash-es";
|
|
3
|
+
import { BehaviorSubject, Observable, ReplaySubject, Subject, catchError, concat, concatMap, debounceTime, distinctUntilChanged, from, map, of, shareReplay, skip, startWith, switchMap, take, tap, timer } from "rxjs";
|
|
4
|
+
//#region src/configureBigNumber.ts
|
|
5
|
+
BigNumber.set({ STRICT: false });
|
|
6
|
+
var configureBigNumber_default = BigNumber;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/addTrailingSlash.ts
|
|
9
|
+
const addTrailingSlash = (url) => {
|
|
10
|
+
if (url.endsWith("/")) return url;
|
|
11
|
+
return `${url}/`;
|
|
7
12
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/assert.ts
|
|
15
|
+
/**
|
|
16
|
+
* @name assert
|
|
17
|
+
* @description Throws an Error with the given message if the condition is falsy.
|
|
18
|
+
* @example
|
|
19
|
+
* assert(chain, "Chain not found")
|
|
20
|
+
**/
|
|
21
|
+
function assert(condition, message) {
|
|
22
|
+
if (condition) return;
|
|
23
|
+
throw new Error(typeof message === "function" ? message() : message);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/BigMath.ts
|
|
27
|
+
/**
|
|
28
|
+
* Javascript's `Math` library for `BigInt`.
|
|
29
|
+
* Taken from https://stackoverflow.com/questions/51867270/is-there-a-library-similar-to-math-that-supports-javascript-bigint/64953280#64953280
|
|
30
|
+
*/
|
|
31
|
+
const BigMath = {
|
|
32
|
+
abs(x) {
|
|
33
|
+
return x < 0n ? -x : x;
|
|
34
|
+
},
|
|
35
|
+
sign(x) {
|
|
36
|
+
if (x === 0n) return 0n;
|
|
37
|
+
return x < 0n ? -1n : 1n;
|
|
38
|
+
},
|
|
39
|
+
min(value, ...values) {
|
|
40
|
+
for (const v of values) if (v < value) value = v;
|
|
41
|
+
return value;
|
|
42
|
+
},
|
|
43
|
+
max(value, ...values) {
|
|
44
|
+
for (const v of values) if (v > value) value = v;
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
31
47
|
};
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/isHexString.ts
|
|
50
|
+
const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
|
|
51
|
+
const isHexString = (value) => {
|
|
52
|
+
return typeof value === "string" && REGEX_HEX_STRING.test(value);
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/bytes.ts
|
|
56
|
+
/**
|
|
57
|
+
* Byte / hex primitives, drop-in equivalents of the `@polkadot/util` helpers we used to depend on.
|
|
58
|
+
* Semantics intentionally match polkadot-js for migration safety.
|
|
59
|
+
*/
|
|
60
|
+
const hexToU8a = (hex) => {
|
|
61
|
+
if (!hex) return /* @__PURE__ */ new Uint8Array();
|
|
62
|
+
const value = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
63
|
+
const padded = value.length % 2 ? `${value}0` : value;
|
|
64
|
+
const result = new Uint8Array(padded.length / 2);
|
|
65
|
+
for (let i = 0; i < result.length; i++) result[i] = parseInt(padded.substring(i * 2, i * 2 + 2), 16);
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
const u8aToHex = (value) => {
|
|
69
|
+
if (!value?.length) return "0x";
|
|
70
|
+
let hex = "";
|
|
71
|
+
for (const byte of value) hex += byte.toString(16).padStart(2, "0");
|
|
72
|
+
return `0x${hex}`;
|
|
73
|
+
};
|
|
74
|
+
const stringToU8a = (value) => value ? new TextEncoder().encode(value) : /* @__PURE__ */ new Uint8Array();
|
|
75
|
+
const u8aToString = (value) => value?.length ? new TextDecoder("utf-8").decode(value) : "";
|
|
76
|
+
const hexToString = (hex) => u8aToString(hexToU8a(hex));
|
|
77
|
+
const hexToNumber = (hex) => hex ? parseInt(hex, 16) : NaN;
|
|
78
|
+
const numberToU8a = (value) => {
|
|
79
|
+
let hex = (value ?? 0).toString(16);
|
|
80
|
+
if (hex.length % 2) hex = `0${hex}`;
|
|
81
|
+
return hexToU8a(hex);
|
|
82
|
+
};
|
|
83
|
+
/** Coerces hex strings, utf-8 strings, number arrays and buffers to Uint8Array (polkadot-js semantics) */
|
|
84
|
+
const u8aToU8a = (value) => {
|
|
85
|
+
if (!value) return /* @__PURE__ */ new Uint8Array();
|
|
86
|
+
if (value instanceof Uint8Array) return value;
|
|
87
|
+
if (Array.isArray(value)) return new Uint8Array(value);
|
|
88
|
+
return isHexString(value) ? hexToU8a(value) : stringToU8a(value);
|
|
89
|
+
};
|
|
90
|
+
const u8aConcat = (...items) => {
|
|
91
|
+
const chunks = items.map((item) => u8aToU8a(item));
|
|
92
|
+
const result = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.length, 0));
|
|
93
|
+
let offset = 0;
|
|
94
|
+
for (const chunk of chunks) {
|
|
95
|
+
result.set(chunk, offset);
|
|
96
|
+
offset += chunk.length;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
const u8aEq = (a, b) => {
|
|
101
|
+
const u8aa = u8aToU8a(a);
|
|
102
|
+
const u8ab = u8aToU8a(b);
|
|
103
|
+
if (u8aa.length !== u8ab.length) return false;
|
|
104
|
+
return u8aa.every((byte, i) => byte === u8ab[i]);
|
|
105
|
+
};
|
|
106
|
+
const u8aCmp = (a, b) => {
|
|
107
|
+
const u8aa = u8aToU8a(a);
|
|
108
|
+
const u8ab = u8aToU8a(b);
|
|
109
|
+
for (let i = 0; i < Math.max(u8aa.length, u8ab.length); i++) {
|
|
110
|
+
const byteA = u8aa[i] ?? 0;
|
|
111
|
+
const byteB = u8ab[i] ?? 0;
|
|
112
|
+
if (byteA !== byteB) return byteA < byteB ? -1 : 1;
|
|
113
|
+
}
|
|
114
|
+
return u8aa.length === u8ab.length ? 0 : u8aa.length < u8ab.length ? -1 : 1;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Tests if the input is printable ASCII (code points 32-126). Hex strings are decoded to bytes
|
|
118
|
+
* first, other strings are checked character-wise (polkadot-js `isAscii` semantics).
|
|
119
|
+
*/
|
|
120
|
+
const isAsciiPrintable = (value) => {
|
|
121
|
+
if (value == null) return false;
|
|
122
|
+
return (typeof value === "string" && !isHexString(value) ? stringToU8a(value) : u8aToU8a(value)).every((b) => b >= 32 && b <= 126);
|
|
123
|
+
};
|
|
124
|
+
const U8A_WRAP_PREFIX = stringToU8a("<Bytes>");
|
|
125
|
+
const U8A_WRAP_POSTFIX = stringToU8a("</Bytes>");
|
|
126
|
+
const u8aIsWrapped = (value) => value.length >= U8A_WRAP_PREFIX.length + U8A_WRAP_POSTFIX.length && u8aEq(value.subarray(0, U8A_WRAP_PREFIX.length), U8A_WRAP_PREFIX) && u8aEq(value.subarray(value.length - U8A_WRAP_POSTFIX.length), U8A_WRAP_POSTFIX);
|
|
127
|
+
/** Wraps bytes with `<Bytes>...</Bytes>` for raw-message signing, unless already wrapped */
|
|
128
|
+
const u8aWrapBytes = (value) => {
|
|
129
|
+
const u8a = u8aToU8a(value);
|
|
130
|
+
return u8aIsWrapped(u8a) ? u8a : u8aConcat(U8A_WRAP_PREFIX, u8a, U8A_WRAP_POSTFIX);
|
|
131
|
+
};
|
|
132
|
+
const u8aUnwrapBytes = (value) => {
|
|
133
|
+
const u8a = u8aToU8a(value);
|
|
134
|
+
return u8aIsWrapped(u8a) ? u8a.subarray(U8A_WRAP_PREFIX.length, u8a.length - U8A_WRAP_POSTFIX.length) : u8a;
|
|
135
|
+
};
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/jsActivityHook.ts
|
|
138
|
+
const reportJsActivity = (label, durationMs) => {
|
|
139
|
+
const record = globalThis.__recordJsActivity;
|
|
140
|
+
if (record) record(label, durationMs);
|
|
141
|
+
};
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/yieldToEventLoop.ts
|
|
144
|
+
const yieldToEventLoop = () => {
|
|
145
|
+
if (typeof MessageChannel !== "undefined") return new Promise((resolve) => {
|
|
146
|
+
const channel = new MessageChannel();
|
|
147
|
+
const port1 = channel.port1;
|
|
148
|
+
const port2 = channel.port2;
|
|
149
|
+
port1.onmessage = () => {
|
|
150
|
+
port1.close();
|
|
151
|
+
port2.close();
|
|
152
|
+
resolve();
|
|
153
|
+
};
|
|
154
|
+
port2.postMessage(null);
|
|
155
|
+
});
|
|
156
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
157
|
+
};
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/timeSlicer.ts
|
|
160
|
+
/**
|
|
161
|
+
* Default CPU budget for a single synchronous slice of chunked work.
|
|
162
|
+
* ~50-60% of a 60fps frame, leaving the rest for the host (user interactions, rendering).
|
|
163
|
+
*/
|
|
164
|
+
const DEFAULT_TIME_SLICE_BUDGET_MS = 10;
|
|
165
|
+
/**
|
|
166
|
+
* Creates an AbortError-style error without relying on DOMException (not available on Hermes).
|
|
167
|
+
* Recognized by `isAbortError` from this package.
|
|
168
|
+
*/
|
|
169
|
+
const newAbortError = () => Object.assign(/* @__PURE__ */ new Error("Aborted"), { name: "AbortError" });
|
|
170
|
+
const defaultNow = typeof performance !== "undefined" ? () => performance.now() : () => Date.now();
|
|
171
|
+
/**
|
|
172
|
+
* Tracks a time budget for cooperative scheduling: run synchronous work until the budget
|
|
173
|
+
* for the current slice is exhausted, then yield the thread back to the host event loop
|
|
174
|
+
* before continuing.
|
|
175
|
+
*
|
|
176
|
+
* A single slicer can be shared across multiple phases of work so the budget spans them.
|
|
177
|
+
*/
|
|
178
|
+
const createTimeSlicer = ({ budgetMs = 10, signal, now = defaultNow, label } = {}) => {
|
|
179
|
+
let sliceStart = now();
|
|
180
|
+
const throwIfAborted = () => {
|
|
181
|
+
if (signal?.aborted) throw newAbortError();
|
|
182
|
+
};
|
|
183
|
+
const shouldYield = () => now() - sliceStart >= budgetMs;
|
|
184
|
+
const doYield = async () => {
|
|
185
|
+
const elapsed = now() - sliceStart;
|
|
186
|
+
if (elapsed >= budgetMs * 3) reportJsActivity(`timeSlicer over-budget slice${label ? ` (${label})` : ""}`, elapsed);
|
|
187
|
+
await yieldToEventLoop();
|
|
188
|
+
sliceStart = now();
|
|
189
|
+
throwIfAborted();
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
signal,
|
|
193
|
+
shouldYield,
|
|
194
|
+
yield: doYield,
|
|
195
|
+
yieldIfNeeded: () => {
|
|
196
|
+
throwIfAborted();
|
|
197
|
+
return shouldYield() ? doYield() : void 0;
|
|
198
|
+
},
|
|
199
|
+
throwIfAborted
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/chunkedArray.ts
|
|
204
|
+
const getSlicer = (options) => options?.slicer ?? createTimeSlicer({
|
|
205
|
+
budgetMs: options?.budgetMs,
|
|
206
|
+
signal: options?.signal,
|
|
207
|
+
label: options?.label
|
|
208
|
+
});
|
|
209
|
+
/**
|
|
210
|
+
* `Array.prototype.forEach`, chunked: iterates synchronously until the time budget is
|
|
211
|
+
* exhausted, then yields the thread to the host event loop before continuing.
|
|
212
|
+
*
|
|
213
|
+
* Errors thrown by `fn` reject the promise; aborting rejects with an AbortError at the
|
|
214
|
+
* next between-items check point.
|
|
215
|
+
*/
|
|
216
|
+
const forEachWithYield = async (items, fn, options) => {
|
|
217
|
+
const slicer = getSlicer(options);
|
|
218
|
+
for (let i = 0; i < items.length; i++) {
|
|
219
|
+
const yielded = slicer.yieldIfNeeded();
|
|
220
|
+
if (yielded) await yielded;
|
|
221
|
+
fn(items[i], i);
|
|
222
|
+
}
|
|
223
|
+
const yielded = slicer.yieldIfNeeded();
|
|
224
|
+
if (yielded) await yielded;
|
|
225
|
+
};
|
|
226
|
+
/** `Array.prototype.map`, chunked (see forEachWithYield for semantics) */
|
|
227
|
+
const mapWithYield = async (items, fn, options) => {
|
|
228
|
+
const results = new Array(items.length);
|
|
229
|
+
await forEachWithYield(items, (item, i) => results[i] = fn(item, i), options);
|
|
230
|
+
return results;
|
|
231
|
+
};
|
|
232
|
+
/** lodash `keyBy`, chunked (see forEachWithYield for semantics) */
|
|
233
|
+
const keyByWithYield = async (items, keyFn, options) => {
|
|
234
|
+
const result = {};
|
|
235
|
+
await forEachWithYield(items, (item, i) => result[keyFn(item, i)] = item, options);
|
|
236
|
+
return result;
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Chunked equivalent of lodash `isEqual(a, b)` for arrays: `a === b` fast path, length
|
|
240
|
+
* check, then per-item `a[i] === b[i] || isItemEqual(a[i], b[i])` within the time budget.
|
|
241
|
+
* The result is identical to `isEqual(a, b)`.
|
|
242
|
+
*/
|
|
243
|
+
const arrayItemsEqualWithYield = async (a, b, options) => {
|
|
244
|
+
if (a === b) return true;
|
|
245
|
+
if (!a || !b) return false;
|
|
246
|
+
if (a.length !== b.length) return false;
|
|
247
|
+
const isItemEqual = options?.isItemEqual ?? isEqual;
|
|
248
|
+
const slicer = getSlicer(options);
|
|
249
|
+
for (let i = 0; i < a.length; i++) {
|
|
250
|
+
const yielded = slicer.yieldIfNeeded();
|
|
251
|
+
if (yielded) await yielded;
|
|
252
|
+
if (a[i] !== b[i] && !isItemEqual(a[i], b[i])) return false;
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
};
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/deferred.ts
|
|
258
|
+
/**
|
|
259
|
+
* In TypeScript, a deferred promise refers to a pattern that involves creating a promise that can be
|
|
260
|
+
* resolved or rejected at a later point in time, typically by code outside of the current function scope.
|
|
261
|
+
*
|
|
262
|
+
* This pattern is often used when dealing with asynchronous operations that involve multiple steps or when
|
|
263
|
+
* the result of an operation cannot be immediately determined.
|
|
264
|
+
*/
|
|
34
265
|
function Deferred() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
266
|
+
let resolve;
|
|
267
|
+
let reject;
|
|
268
|
+
let isPending = true;
|
|
269
|
+
let isResolved = false;
|
|
270
|
+
let isRejected = false;
|
|
271
|
+
return {
|
|
272
|
+
promise: new Promise((innerResolve, innerReject) => {
|
|
273
|
+
resolve = (value) => {
|
|
274
|
+
isPending = false;
|
|
275
|
+
isResolved = true;
|
|
276
|
+
innerResolve(value);
|
|
277
|
+
};
|
|
278
|
+
reject = (reason) => {
|
|
279
|
+
isPending = false;
|
|
280
|
+
isRejected = true;
|
|
281
|
+
innerReject(reason);
|
|
282
|
+
};
|
|
283
|
+
}),
|
|
284
|
+
resolve,
|
|
285
|
+
reject,
|
|
286
|
+
isPending: () => isPending,
|
|
287
|
+
isResolved: () => isResolved,
|
|
288
|
+
isRejected: () => isRejected
|
|
289
|
+
};
|
|
60
290
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/firstThenDebounce.ts
|
|
293
|
+
/**
|
|
294
|
+
* An rxjs operator which:
|
|
295
|
+
*
|
|
296
|
+
* 1. Emits the first value it receives from the source observable, then:
|
|
297
|
+
* 2. Debounces any future values by `timeout` ms.
|
|
298
|
+
*/
|
|
299
|
+
const firstThenDebounce = (timeout) => (source) => concat(source.pipe(take(1)), source.pipe(skip(1)).pipe(debounceTime(timeout)));
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/formatDecimals.ts
|
|
302
|
+
const MIN_DIGITS = 4;
|
|
303
|
+
const MAX_DECIMALS_FORMAT = 12;
|
|
304
|
+
/**
|
|
305
|
+
* Custom decimal number formatting for Talisman
|
|
306
|
+
* note that the NumberFormat().format() call is the ressource heavy part, it's not worth trying to optimize other parts
|
|
307
|
+
* @param num input number
|
|
308
|
+
* @param digits number of significant digits to display
|
|
309
|
+
* @param locale locale used to format the number
|
|
310
|
+
* @param options formatting options
|
|
311
|
+
* @returns the formatted value
|
|
312
|
+
*/
|
|
313
|
+
const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US") => {
|
|
314
|
+
if (num === null || num === void 0) return "";
|
|
315
|
+
if (digits < MIN_DIGITS) digits = MIN_DIGITS;
|
|
316
|
+
const value = new configureBigNumber_default(num);
|
|
317
|
+
const minDisplayVal = 1 / 10 ** digits;
|
|
318
|
+
if (value.gt(0) && value.lt(minDisplayVal)) return `< ${formatDecimals(minDisplayVal)}`;
|
|
319
|
+
const flooredValue = value.integerValue();
|
|
320
|
+
const intDigits = flooredValue.isEqualTo(0) ? 0 : flooredValue.toString().length;
|
|
321
|
+
let truncatedValue = value;
|
|
322
|
+
const excessFractionDigitsPow10 = new configureBigNumber_default(10).pow(digits > intDigits ? digits - intDigits : 0);
|
|
323
|
+
truncatedValue = truncatedValue.multipliedBy(excessFractionDigitsPow10).integerValue().dividedBy(excessFractionDigitsPow10);
|
|
324
|
+
const excessIntegerDigits = new configureBigNumber_default(intDigits > digits ? intDigits - digits : 0);
|
|
325
|
+
const excessIntegerDigitsPow10 = new configureBigNumber_default(10).pow(excessIntegerDigits);
|
|
326
|
+
if (excessIntegerDigits.gt(0)) truncatedValue = truncatedValue.dividedBy(excessIntegerDigitsPow10).integerValue().multipliedBy(excessIntegerDigitsPow10);
|
|
327
|
+
return Intl.NumberFormat(locale, {
|
|
328
|
+
notation: truncatedValue.abs().gt(9999) ? "compact" : "standard",
|
|
329
|
+
maximumSignificantDigits: Math.max(1, Math.min(digits + (truncatedValue.lt(1) ? 1 : 0), 21)),
|
|
330
|
+
...options
|
|
331
|
+
}).format(truncatedValue.toNumber());
|
|
95
332
|
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/formatPrice.ts
|
|
335
|
+
const formatPrice = (price, currency, compact) => {
|
|
336
|
+
return Intl.NumberFormat(void 0, {
|
|
337
|
+
style: "currency",
|
|
338
|
+
currency,
|
|
339
|
+
currencyDisplay: currency === "usd" ? "narrowSymbol" : "symbol",
|
|
340
|
+
minimumSignificantDigits: 3,
|
|
341
|
+
maximumSignificantDigits: compact ? price < 1 ? 3 : 4 : void 0,
|
|
342
|
+
roundingPriority: compact ? "auto" : "morePrecision",
|
|
343
|
+
notation: compact ? "compact" : "standard"
|
|
344
|
+
}).format(price);
|
|
108
345
|
};
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
import { catchError, from, map, of, startWith, switchMap, timer } from "rxjs";
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/getLoadable.ts
|
|
112
348
|
function getLoadable$(factory, options = {}) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
(error) => of({
|
|
123
|
-
status: "error",
|
|
124
|
-
error: getError ? getError(error) : getGenericError(error)
|
|
125
|
-
})
|
|
126
|
-
)
|
|
127
|
-
);
|
|
128
|
-
const source$ = refreshInterval ? timer(0, refreshInterval).pipe(switchMap(() => createLoadableStream())) : createLoadableStream();
|
|
129
|
-
return source$.pipe(
|
|
130
|
-
startWith({
|
|
131
|
-
status: "loading"
|
|
132
|
-
})
|
|
133
|
-
);
|
|
349
|
+
const { getError, refreshInterval } = options;
|
|
350
|
+
const createLoadableStream = () => from(factory()).pipe(map((data) => ({
|
|
351
|
+
status: "success",
|
|
352
|
+
data
|
|
353
|
+
})), catchError((error) => of({
|
|
354
|
+
status: "error",
|
|
355
|
+
error: getError ? getError(error) : getGenericError(error)
|
|
356
|
+
})));
|
|
357
|
+
return (refreshInterval ? timer(0, refreshInterval).pipe(switchMap(() => createLoadableStream())) : createLoadableStream()).pipe(startWith({ status: "loading" }));
|
|
134
358
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
359
|
+
const getGenericError = (error) => ({
|
|
360
|
+
name: "Error",
|
|
361
|
+
message: getGenericErrorMessage(error)
|
|
138
362
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
145
|
-
return error.message;
|
|
146
|
-
}
|
|
147
|
-
return String(error) || "Unknown error";
|
|
363
|
+
const getGenericErrorMessage = (error) => {
|
|
364
|
+
if (typeof error === "string") return error;
|
|
365
|
+
else if (error instanceof Error) return error.message;
|
|
366
|
+
else if (error && typeof error === "object" && "message" in error) return error.message;
|
|
367
|
+
return String(error) || "Unknown error";
|
|
148
368
|
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/getSharedObservable.ts
|
|
371
|
+
const CACHE = /* @__PURE__ */ new Map();
|
|
372
|
+
/**
|
|
373
|
+
* How long an entry survives with no subscribers before being dropped. Long enough
|
|
374
|
+
* for quick unmount/remount cycles (navigation, react-rxjs Subscribe boundaries) to
|
|
375
|
+
* reuse the entry, short enough that abandoned scopes (e.g. superseded balance
|
|
376
|
+
* subscriptions after networks/accounts change) don't accumulate forever.
|
|
377
|
+
*/
|
|
378
|
+
const CLEANUP_DELAY_MS = 6e4;
|
|
379
|
+
/**
|
|
380
|
+
* When using react-rxjs hooks and state observables, the options are used as weak map keys.
|
|
381
|
+
* This means that if the options object is recreated on each render, the observable will be recreated as well.
|
|
382
|
+
* 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
|
|
383
|
+
*
|
|
384
|
+
* Entries are reference-counted: an entry with no subscribers for CLEANUP_DELAY_MS is
|
|
385
|
+
* removed from the cache (its shareReplay source is already torn down by refCount at
|
|
386
|
+
* that point — only the replay buffer and the entry itself are dropped).
|
|
387
|
+
*
|
|
388
|
+
* @param namespace
|
|
389
|
+
* @param args
|
|
390
|
+
* @param createObservable
|
|
391
|
+
* @param serializer
|
|
392
|
+
* @returns
|
|
393
|
+
*/
|
|
394
|
+
const getSharedObservable = (namespace, args, createObservable, serializer = (args) => JSON.stringify(args)) => {
|
|
395
|
+
const cacheKey = `${namespace}:${serializer(args)}`;
|
|
396
|
+
const cached = CACHE.get(cacheKey);
|
|
397
|
+
if (cached) return cached.observable;
|
|
398
|
+
const sharedObs = createObservable(args).pipe(shareReplay({
|
|
399
|
+
bufferSize: 1,
|
|
400
|
+
refCount: true
|
|
401
|
+
}));
|
|
402
|
+
const scheduleCleanup = (entry) => {
|
|
403
|
+
if (entry.cleanupTimer) clearTimeout(entry.cleanupTimer);
|
|
404
|
+
entry.cleanupTimer = setTimeout(() => {
|
|
405
|
+
if (entry.refCount === 0 && CACHE.get(cacheKey) === entry) CACHE.delete(cacheKey);
|
|
406
|
+
}, CLEANUP_DELAY_MS);
|
|
407
|
+
};
|
|
408
|
+
const entry = {
|
|
409
|
+
observable: null,
|
|
410
|
+
refCount: 0,
|
|
411
|
+
cleanupTimer: null
|
|
412
|
+
};
|
|
413
|
+
entry.observable = new Observable((subscriber) => {
|
|
414
|
+
entry.refCount++;
|
|
415
|
+
if (entry.cleanupTimer) {
|
|
416
|
+
clearTimeout(entry.cleanupTimer);
|
|
417
|
+
entry.cleanupTimer = null;
|
|
418
|
+
}
|
|
419
|
+
const subscription = sharedObs.subscribe(subscriber);
|
|
420
|
+
return () => {
|
|
421
|
+
subscription.unsubscribe();
|
|
422
|
+
entry.refCount--;
|
|
423
|
+
if (entry.refCount === 0) scheduleCleanup(entry);
|
|
424
|
+
};
|
|
425
|
+
});
|
|
426
|
+
CACHE.set(cacheKey, entry);
|
|
427
|
+
scheduleCleanup(entry);
|
|
428
|
+
return entry.observable;
|
|
167
429
|
};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/getQuery.ts
|
|
432
|
+
/**
|
|
433
|
+
* Creates a shared observable for executing queries with caching, loading states, and automatic refresh capabilities.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* ```typescript
|
|
437
|
+
* const userQuery$ = getQuery$({
|
|
438
|
+
* namespace: 'users',
|
|
439
|
+
* args: { userId: 123 },
|
|
440
|
+
* queryFn: async ({ userId }) => fetchUser(userId),
|
|
441
|
+
* defaultValue: null,
|
|
442
|
+
* refreshInterval: 30000
|
|
443
|
+
* });
|
|
444
|
+
*
|
|
445
|
+
* userQuery$.subscribe(result => {
|
|
446
|
+
* if (result.status === 'loaded') {
|
|
447
|
+
* console.log(result.data);
|
|
448
|
+
* }
|
|
449
|
+
* });
|
|
450
|
+
* ```
|
|
451
|
+
*
|
|
452
|
+
* @deprecated use getLoadableQuery$ instead
|
|
453
|
+
*/
|
|
454
|
+
const getQuery$ = ({ namespace, args, queryFn, defaultValue, refreshInterval, serializer = (args) => JSON.stringify(args) }) => {
|
|
455
|
+
return getSharedObservable(namespace, args, () => new Observable((subscriber) => {
|
|
456
|
+
const controller = new AbortController();
|
|
457
|
+
const result = new BehaviorSubject({
|
|
458
|
+
status: "loading",
|
|
459
|
+
data: defaultValue,
|
|
460
|
+
error: void 0
|
|
461
|
+
});
|
|
462
|
+
const sub = result.pipe(distinctUntilChanged(isEqual)).subscribe(subscriber);
|
|
463
|
+
let timeout = null;
|
|
464
|
+
const run = () => {
|
|
465
|
+
if (controller.signal.aborted) return;
|
|
466
|
+
queryFn(args, controller.signal).then((data) => {
|
|
467
|
+
if (controller.signal.aborted) return;
|
|
468
|
+
result.next({
|
|
469
|
+
status: "loaded",
|
|
470
|
+
data,
|
|
471
|
+
error: void 0
|
|
472
|
+
});
|
|
473
|
+
}).catch((error) => {
|
|
474
|
+
if (controller.signal.aborted) return;
|
|
475
|
+
result.next({
|
|
476
|
+
status: "error",
|
|
477
|
+
data: void 0,
|
|
478
|
+
error
|
|
479
|
+
});
|
|
480
|
+
}).finally(() => {
|
|
481
|
+
if (controller.signal.aborted) return;
|
|
482
|
+
if (refreshInterval) timeout = setTimeout(run, refreshInterval);
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
run();
|
|
486
|
+
return () => {
|
|
487
|
+
sub.unsubscribe();
|
|
488
|
+
if (timeout) clearTimeout(timeout);
|
|
489
|
+
controller.abort(/* @__PURE__ */ new Error("getQuery$ unsubscribed"));
|
|
490
|
+
};
|
|
491
|
+
}).pipe(shareReplay({
|
|
492
|
+
refCount: true,
|
|
493
|
+
bufferSize: 1
|
|
494
|
+
})), serializer);
|
|
212
495
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region src/getLoadableQuery.ts
|
|
498
|
+
/**
|
|
499
|
+
* Thin wrapper around getQuery$ that returns Loadable<T> and optionally
|
|
500
|
+
* primes the stream with a loading state using the provided default value.
|
|
501
|
+
*
|
|
502
|
+
* TODO: consolidate with getQuery$
|
|
503
|
+
*/
|
|
504
|
+
const getLoadableQuery$ = (params) => {
|
|
505
|
+
const initial = params.defaultValue === void 0 ? [] : [{
|
|
506
|
+
status: "loading",
|
|
507
|
+
data: params.defaultValue
|
|
508
|
+
}];
|
|
509
|
+
return getQuery$(params).pipe(map((val) => {
|
|
510
|
+
switch (val.status) {
|
|
511
|
+
case "loading": return {
|
|
512
|
+
status: "loading",
|
|
513
|
+
data: val.data
|
|
514
|
+
};
|
|
515
|
+
case "loaded": return {
|
|
516
|
+
status: "success",
|
|
517
|
+
data: val.data
|
|
518
|
+
};
|
|
519
|
+
case "error": {
|
|
520
|
+
const err = val.error;
|
|
521
|
+
return {
|
|
522
|
+
status: "error",
|
|
523
|
+
error: {
|
|
524
|
+
name: err?.name ?? "QueryError",
|
|
525
|
+
message: err?.message ?? "Failed to execute query"
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}), startWith(...initial));
|
|
238
531
|
};
|
|
239
|
-
|
|
240
|
-
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/hasOwnProperty.ts
|
|
241
534
|
function hasOwnProperty(obj, prop) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
535
|
+
if (typeof obj !== "object") return false;
|
|
536
|
+
if (obj === null) return false;
|
|
537
|
+
return prop in obj;
|
|
245
538
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/isAbortError.ts
|
|
541
|
+
const isAbortError = (error) => {
|
|
542
|
+
return error instanceof Error && error.name === "AbortError";
|
|
250
543
|
};
|
|
251
|
-
|
|
252
|
-
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/isArrayOf.ts
|
|
253
546
|
function isArrayOf(array, func) {
|
|
254
|
-
|
|
255
|
-
|
|
547
|
+
if (array.length > 0 && array[0] instanceof func) return true;
|
|
548
|
+
return false;
|
|
256
549
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/isAscii.ts
|
|
552
|
+
const isAscii = (str) => {
|
|
553
|
+
return [...str].every((char) => char.charCodeAt(0) <= 127);
|
|
261
554
|
};
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/isBigInt.ts
|
|
557
|
+
const isBigInt = (value) => typeof value === "bigint";
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/isBooleanTrue.ts
|
|
560
|
+
const isBooleanTrue = (x) => !!x;
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/isErrorOfName.ts
|
|
563
|
+
/**
|
|
564
|
+
* Robust replacement for `error instanceof SomeErrorClass` when the class comes from a
|
|
565
|
+
* package that gets bundled into multiple chunks/contexts (e.g. viem, @solana/web3.js).
|
|
566
|
+
*
|
|
567
|
+
* `instanceof` returns `false` whenever the error originates from a different bundled copy of
|
|
568
|
+
* the module than the imported class reference — which silently broke Solana transfers (see
|
|
569
|
+
* apps/extension/src/__tests__/no-instanceof-bundled-class.test.ts). An error's `.name` is
|
|
570
|
+
* stable across copies, *provided the class sets it explicitly* (viem does:
|
|
571
|
+
* `super({ name: 'ContractFunctionExecutionError' })`). For classes that don't set `.name`,
|
|
572
|
+
* duck-type the shape instead.
|
|
573
|
+
*/
|
|
574
|
+
const isErrorOfName = (error, ...names) => error instanceof Error && names.includes(error.name);
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/isNotNil.ts
|
|
577
|
+
/**
|
|
578
|
+
* WARNING: This function only checks against null or undefined, it does not coerce the value.
|
|
579
|
+
* ie: false and 0 are considered not nil
|
|
580
|
+
* Use isTruthy instead for a regular coercion check.
|
|
581
|
+
*
|
|
582
|
+
* @param value
|
|
583
|
+
* @returns whether the value is neither null nor undefined
|
|
584
|
+
*/
|
|
585
|
+
const isNotNil = (value) => value !== null && value !== void 0;
|
|
586
|
+
//#endregion
|
|
587
|
+
//#region src/isPromise.ts
|
|
588
|
+
const isPromise = (value) => !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/isSubject.ts
|
|
591
|
+
/**
|
|
592
|
+
* Tests to see if an object is an RxJS {@link Subject}.
|
|
593
|
+
*/
|
|
283
594
|
function isSubject(object) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
595
|
+
if (!object) return false;
|
|
596
|
+
if (object instanceof Subject) return true;
|
|
597
|
+
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);
|
|
287
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Returns `true` if `value` is a function.
|
|
601
|
+
*/
|
|
288
602
|
function isFn(value) {
|
|
289
|
-
|
|
603
|
+
return typeof value === "function";
|
|
290
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* Returns `true` if `value` is a boolean.
|
|
607
|
+
*/
|
|
291
608
|
function isBool(value) {
|
|
292
|
-
|
|
609
|
+
return typeof value === "boolean";
|
|
293
610
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/isTruthy.ts
|
|
613
|
+
const isTruthy = (value) => Boolean(value);
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/keepAlive.ts
|
|
616
|
+
/**
|
|
617
|
+
* An RxJS operator that keeps the source observable alive for a specified duration
|
|
618
|
+
* after all subscribers have unsubscribed. This prevents expensive re-subscriptions
|
|
619
|
+
* when subscribers come and go frequently.
|
|
620
|
+
*
|
|
621
|
+
* @param keepAliveMs - Duration in milliseconds to keep the source alive after last unsubscription
|
|
622
|
+
* @returns MonoTypeOperatorFunction that can be used in pipe()
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```typescript
|
|
626
|
+
* const data$ = expensive_api_call$.pipe(
|
|
627
|
+
* keepAlive(3000) // Keep alive for 3 seconds
|
|
628
|
+
* );
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
const keepAlive = (timeout) => {
|
|
632
|
+
let release;
|
|
633
|
+
return (source) => source.pipe(tap({
|
|
634
|
+
subscribe: () => {
|
|
635
|
+
release = keepSourceSubscribed(source, timeout);
|
|
636
|
+
},
|
|
637
|
+
unsubscribe: () => {
|
|
638
|
+
release?.();
|
|
639
|
+
}
|
|
640
|
+
}), shareReplay({
|
|
641
|
+
refCount: true,
|
|
642
|
+
bufferSize: 1
|
|
643
|
+
}));
|
|
317
644
|
};
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
645
|
+
const keepSourceSubscribed = (observable, ms) => {
|
|
646
|
+
const sub = observable.subscribe();
|
|
647
|
+
return () => setTimeout(() => sub.unsubscribe(), ms);
|
|
648
|
+
};
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/lruMap.ts
|
|
651
|
+
/**
|
|
652
|
+
* A Map with a maximum size and least-recently-used eviction.
|
|
653
|
+
*
|
|
654
|
+
* Reads refresh recency; when an insert would exceed `maxSize`, the
|
|
655
|
+
* least-recently-used entry is evicted. Intended for long-lived in-memory
|
|
656
|
+
* caches that would otherwise grow without bound.
|
|
657
|
+
*/
|
|
658
|
+
var LruMap = class {
|
|
659
|
+
#map = /* @__PURE__ */ new Map();
|
|
660
|
+
#maxSize;
|
|
661
|
+
constructor(maxSize) {
|
|
662
|
+
if (maxSize < 1) throw new Error("LruMap maxSize must be >= 1");
|
|
663
|
+
this.#maxSize = maxSize;
|
|
664
|
+
}
|
|
665
|
+
get(key) {
|
|
666
|
+
if (!this.#map.has(key)) return void 0;
|
|
667
|
+
const value = this.#map.get(key);
|
|
668
|
+
this.#map.delete(key);
|
|
669
|
+
this.#map.set(key, value);
|
|
670
|
+
return value;
|
|
671
|
+
}
|
|
672
|
+
set(key, value) {
|
|
673
|
+
if (this.#map.has(key)) this.#map.delete(key);
|
|
674
|
+
else if (this.#map.size >= this.#maxSize) {
|
|
675
|
+
const oldest = this.#map.keys().next();
|
|
676
|
+
if (!oldest.done) this.#map.delete(oldest.value);
|
|
677
|
+
}
|
|
678
|
+
this.#map.set(key, value);
|
|
679
|
+
return this;
|
|
680
|
+
}
|
|
681
|
+
has(key) {
|
|
682
|
+
return this.#map.has(key);
|
|
683
|
+
}
|
|
684
|
+
delete(key) {
|
|
685
|
+
return this.#map.delete(key);
|
|
686
|
+
}
|
|
687
|
+
clear() {
|
|
688
|
+
this.#map.clear();
|
|
689
|
+
}
|
|
690
|
+
get size() {
|
|
691
|
+
return this.#map.size;
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/mapChunked.ts
|
|
696
|
+
const runChunkedProject = (project, options, value, index) => new Observable((subscriber) => {
|
|
697
|
+
const controller = new AbortController();
|
|
698
|
+
project(value, {
|
|
699
|
+
slicer: createTimeSlicer({
|
|
700
|
+
budgetMs: options?.budgetMs,
|
|
701
|
+
signal: controller.signal,
|
|
702
|
+
label: options?.label
|
|
703
|
+
}),
|
|
704
|
+
signal: controller.signal
|
|
705
|
+
}, index).then((result) => {
|
|
706
|
+
if (!controller.signal.aborted) subscriber.next(result);
|
|
707
|
+
subscriber.complete();
|
|
708
|
+
}, (error) => {
|
|
709
|
+
if (isAbortError(error)) subscriber.complete();
|
|
710
|
+
else subscriber.error(error);
|
|
711
|
+
});
|
|
712
|
+
return () => controller.abort();
|
|
713
|
+
});
|
|
714
|
+
/**
|
|
715
|
+
* `switchMap` for time-sliced (chunked) async work — latest-wins: a new upstream value or
|
|
716
|
+
* an unsubscription aborts the in-flight `project` via `ctx.signal` / the shared slicer,
|
|
717
|
+
* so cancelled work stops burning CPU at its next yield check point and its result is
|
|
718
|
+
* never emitted.
|
|
719
|
+
*
|
|
720
|
+
* Non-abort errors thrown by `project` error the stream (same as a throwing sync `map`).
|
|
721
|
+
*
|
|
722
|
+
* Note: pipelines using this operator already emit asynchronously (macrotask), so adding
|
|
723
|
+
* `observeOn(asyncScheduler)` downstream is redundant and only adds latency.
|
|
724
|
+
*/
|
|
725
|
+
const switchMapChunked = (project, options) => switchMap((value, index) => runChunkedProject(project, options, value, index));
|
|
726
|
+
/**
|
|
727
|
+
* `concatMap` variant of switchMapChunked — ordered: upstream values queue and are
|
|
728
|
+
* processed one at a time; a new value does NOT cancel in-flight work (unsubscription
|
|
729
|
+
* still aborts it).
|
|
730
|
+
*/
|
|
731
|
+
const concatMapChunked = (project, options) => concatMap((value, index) => runChunkedProject(project, options, value, index));
|
|
732
|
+
//#endregion
|
|
733
|
+
//#region src/planckToTokens.ts
|
|
321
734
|
function planckToTokens(planck, tokenDecimals) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const multiplier = base ** exponent;
|
|
326
|
-
return new BigNumber2(planck).multipliedBy(multiplier).toString(10);
|
|
735
|
+
if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
|
|
736
|
+
const multiplier = 10 ** (-1 * tokenDecimals);
|
|
737
|
+
return new configureBigNumber_default(planck).multipliedBy(multiplier).toString(10);
|
|
327
738
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
739
|
+
//#endregion
|
|
740
|
+
//#region src/replaySubjectFrom.ts
|
|
741
|
+
/**
|
|
742
|
+
* Turns a value into a {@link ReplaySubject} of size 1.
|
|
743
|
+
*
|
|
744
|
+
* If the value is already a {@link ReplaySubject}, it will be returned as-is.
|
|
745
|
+
*
|
|
746
|
+
* If the value is a {@link Promise}, it will be awaited,
|
|
747
|
+
* and the awaited value will be published into the {@link ReplaySubject} when it becomes available.
|
|
748
|
+
*
|
|
749
|
+
* For any other type of value, it will be immediately published into the {@link ReplaySubject}.
|
|
750
|
+
*/
|
|
751
|
+
const replaySubjectFrom = (initialValue) => {
|
|
752
|
+
if (initialValue instanceof ReplaySubject) return initialValue;
|
|
753
|
+
const subject = new ReplaySubject(1);
|
|
754
|
+
if (isPromise(initialValue)) {
|
|
755
|
+
initialValue.then((value) => subject.next(value), (error) => subject.error(error));
|
|
756
|
+
return subject;
|
|
757
|
+
}
|
|
758
|
+
subject.next(initialValue);
|
|
759
|
+
return subject;
|
|
343
760
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/sleep.ts
|
|
763
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
764
|
+
if (process.env.NODE_ENV === "test") resolve();
|
|
765
|
+
else setTimeout(resolve, ms);
|
|
349
766
|
});
|
|
350
|
-
|
|
351
|
-
|
|
767
|
+
//#endregion
|
|
768
|
+
//#region src/splitSubject.ts
|
|
769
|
+
/**
|
|
770
|
+
* Takes a subject and splits it into two parts:
|
|
771
|
+
*
|
|
772
|
+
* 1. A function to submit new values into the subject.
|
|
773
|
+
* 2. An observable for subscribing to new values from the subject.
|
|
774
|
+
*
|
|
775
|
+
* This can be helpful when, to avoid bugs, you want to expose only one
|
|
776
|
+
* of these parts to external code and keep the other part private.
|
|
777
|
+
*/
|
|
352
778
|
function splitSubject(subject) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return [next, observable];
|
|
779
|
+
const next = (value) => subject.next(value);
|
|
780
|
+
return [next, subject.asObservable()];
|
|
356
781
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/stripHexPrefix.ts
|
|
784
|
+
/**
|
|
785
|
+
* @name stripHexPrefix
|
|
786
|
+
* @description Removes a leading `0x` prefix from a string, if present.
|
|
787
|
+
* @param {string} str - string to strip
|
|
788
|
+
* @returns {string} - the string without its leading `0x` prefix
|
|
789
|
+
* @example
|
|
790
|
+
* stripHexPrefix("0x1234") // "1234"
|
|
791
|
+
* stripHexPrefix("1234") // "1234"
|
|
792
|
+
**/
|
|
793
|
+
const stripHexPrefix = (str) => str.startsWith("0x") ? str.slice(2) : str;
|
|
794
|
+
//#endregion
|
|
795
|
+
//#region src/throwAfter.ts
|
|
796
|
+
const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
|
|
797
|
+
//#endregion
|
|
798
|
+
//#region src/tokensToPlanck.ts
|
|
363
799
|
function tokensToPlanck(tokens, tokenDecimals) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const multiplier = base ** exponent;
|
|
368
|
-
return new BigNumber3(tokens).multipliedBy(multiplier).toString(10);
|
|
800
|
+
if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
|
|
801
|
+
const multiplier = 10 ** tokenDecimals;
|
|
802
|
+
return new configureBigNumber_default(tokens).multipliedBy(multiplier).toString(10);
|
|
369
803
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
804
|
+
//#endregion
|
|
805
|
+
//#region src/validateArrayWithYield.ts
|
|
806
|
+
/**
|
|
807
|
+
* Validates every item of an array within a cooperative time budget, yielding the thread
|
|
808
|
+
* to the host event loop whenever the budget for the current slice is exhausted.
|
|
809
|
+
*
|
|
810
|
+
* Like `z.array(schema).safeParse`, ALL items are validated and all failures collected
|
|
811
|
+
* (no fail-fast) — pass e.g. `(item) => SomeSchema.safeParse(item)` as `parseItem`.
|
|
812
|
+
* Zod-agnostic on purpose: this package has no zod dependency.
|
|
813
|
+
*/
|
|
814
|
+
const validateArrayWithYield = async (items, parseItem, options) => {
|
|
815
|
+
const data = new Array(items.length);
|
|
816
|
+
const errors = [];
|
|
817
|
+
await forEachWithYield(items, (item, index) => {
|
|
818
|
+
const result = parseItem(item, index);
|
|
819
|
+
if (result.success) data[index] = result.data;
|
|
820
|
+
else errors.push({
|
|
821
|
+
index,
|
|
822
|
+
error: result.error
|
|
823
|
+
});
|
|
824
|
+
}, options);
|
|
825
|
+
return errors.length ? {
|
|
826
|
+
success: false,
|
|
827
|
+
errors
|
|
828
|
+
} : {
|
|
829
|
+
success: true,
|
|
830
|
+
data
|
|
831
|
+
};
|
|
832
|
+
};
|
|
833
|
+
//#endregion
|
|
834
|
+
//#region src/validateHexString.ts
|
|
835
|
+
/**
|
|
836
|
+
* @name validateHexString
|
|
837
|
+
* @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
|
|
838
|
+
* @param {string} str - string to check
|
|
839
|
+
* @returns {`0x${string}`} - boolean
|
|
840
|
+
* @example
|
|
841
|
+
* validateHexString("0x1234") // "0x1234"
|
|
842
|
+
* validateHexString("1234") // Error: Expected a hex string
|
|
843
|
+
* validateHexString(1234) // Error: Expected a string
|
|
844
|
+
**/
|
|
845
|
+
const validateHexString = (str) => {
|
|
846
|
+
if (typeof str !== "string") throw new Error("Expected a string");
|
|
847
|
+
if (str.startsWith("0x")) return str;
|
|
848
|
+
throw new Error("Expected a hex string");
|
|
413
849
|
};
|
|
850
|
+
//#endregion
|
|
851
|
+
export { BigMath, DEFAULT_TIME_SLICE_BUDGET_MS, Deferred, LruMap, MAX_DECIMALS_FORMAT, REGEX_HEX_STRING, addTrailingSlash, arrayItemsEqualWithYield, assert, concatMapChunked, createTimeSlicer, firstThenDebounce, forEachWithYield, formatDecimals, formatPrice, getLoadable$, getLoadableQuery$, getQuery$, getSharedObservable, hasOwnProperty, hexToNumber, hexToString, hexToU8a, isAbortError, isArrayOf, isAscii, isAsciiPrintable, isBigInt, isBooleanTrue, isErrorOfName, isHexString, isNotNil, isPromise, isSubject, isTruthy, keepAlive, keyByWithYield, mapWithYield, newAbortError, numberToU8a, planckToTokens, replaySubjectFrom, reportJsActivity, sleep, splitSubject, stringToU8a, stripHexPrefix, switchMapChunked, throwAfter, tokensToPlanck, u8aCmp, u8aConcat, u8aEq, u8aToHex, u8aToString, u8aToU8a, u8aUnwrapBytes, u8aWrapBytes, validateArrayWithYield, validateHexString, yieldToEventLoop };
|
|
852
|
+
|
|
414
853
|
//# sourceMappingURL=index.mjs.map
|