@provex/utils 1.11.0 → 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/dist/buy/browsePanelDerivations.d.ts +212 -0
- package/dist/buy/browsePanelDerivations.js +269 -0
- package/dist/buy/browsePanelDerivations.test.d.ts +9 -0
- package/dist/buy/browsePanelDerivations.test.js +552 -0
- package/dist/buy/fillabilityNotice.d.ts +214 -0
- package/dist/buy/fillabilityNotice.js +162 -0
- package/dist/buy/fillabilityNotice.test.d.ts +8 -0
- package/dist/buy/fillabilityNotice.test.js +246 -0
- package/dist/buy/formatCurrencyBaseUnits.d.ts +30 -0
- package/dist/buy/formatCurrencyBaseUnits.js +34 -0
- package/dist/buy/formatCurrencyBaseUnits.test.d.ts +7 -0
- package/dist/buy/formatCurrencyBaseUnits.test.js +34 -0
- package/dist/buy/fulfillTransactionHash.d.ts +29 -0
- package/dist/buy/fulfillTransactionHash.js +27 -0
- package/dist/buy/fulfillTransactionHash.test.d.ts +7 -0
- package/dist/buy/fulfillTransactionHash.test.js +46 -0
- package/dist/buy/index.d.ts +17 -0
- package/dist/buy/index.js +17 -0
- package/dist/buy/orderCap.d.ts +29 -0
- package/dist/buy/orderCap.js +35 -0
- package/dist/buy/orderCap.test.d.ts +8 -0
- package/dist/buy/orderCap.test.js +35 -0
- package/dist/buy/postFeeAmount.d.ts +39 -0
- package/dist/buy/postFeeAmount.js +36 -0
- package/dist/buy/postFeeAmount.test.d.ts +9 -0
- package/dist/buy/postFeeAmount.test.js +82 -0
- package/dist/buy/providerSelectionMode.d.ts +5 -0
- package/dist/buy/providerSelectionMode.js +4 -0
- package/dist/buy/settlementReconciliation.d.ts +48 -0
- package/dist/buy/settlementReconciliation.js +65 -0
- package/dist/buy/settlementReconciliation.test.d.ts +9 -0
- package/dist/buy/settlementReconciliation.test.js +164 -0
- package/dist/buy/submitStepDerivations.d.ts +122 -0
- package/dist/buy/submitStepDerivations.js +140 -0
- package/dist/buy/submitStepDerivations.test.d.ts +1 -0
- package/dist/buy/submitStepDerivations.test.js +305 -0
- package/dist/buy/verifiableBanks.d.ts +29 -0
- package/dist/buy/verifiableBanks.js +28 -0
- package/dist/buy/verifiableBanks.test.d.ts +4 -0
- package/dist/buy/verifiableBanks.test.js +31 -0
- package/dist/consent.js +0 -2
- package/dist/consent.storage.test.d.ts +8 -0
- package/dist/consent.storage.test.js +111 -0
- package/dist/consent.test.js +60 -1
- package/dist/contracts.js +3 -1
- package/dist/contracts.test.js +17 -1
- package/dist/conversionRates.test.js +18 -0
- package/dist/currencies.d.ts +6 -0
- package/dist/currencies.js +1 -1
- package/dist/currencies.test.js +29 -2
- package/dist/fees.js +0 -3
- package/dist/gating.test.js +16 -1
- package/dist/ids.test.js +48 -0
- package/dist/payment.d.ts +50 -0
- package/dist/payment.js +72 -51
- package/dist/payment.lookup.test.d.ts +5 -0
- package/dist/payment.lookup.test.js +169 -0
- package/dist/payment.providerLists.test.d.ts +12 -0
- package/dist/payment.providerLists.test.js +40 -0
- package/dist/payment.sendLinks.test.d.ts +9 -0
- package/dist/payment.sendLinks.test.js +91 -0
- package/dist/peer-types.d.ts +5 -78
- package/dist/peer-types.js +5 -31
- package/dist/peer-types.test.js +1 -31
- package/dist/profit.d.ts +8 -0
- package/dist/profit.js +16 -3
- package/dist/profit.test.js +12 -1
- package/dist/reputation.js +4 -6
- package/dist/reputation.test.js +8 -0
- package/dist/time.test.js +26 -5
- package/dist/tokens.d.ts +2 -2
- package/dist/transports.d.ts +53 -2
- package/dist/transports.debug.test.d.ts +9 -0
- package/dist/transports.debug.test.js +95 -0
- package/dist/transports.js +26 -10
- package/dist/transports.test.js +5 -1
- package/dist/units.d.ts +19 -0
- package/dist/units.js +21 -0
- package/dist/units.test.js +31 -1
- package/package.json +6 -2
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the RPC call counter, the ranking probe and the
|
|
3
|
+
* environment-dependent default strategy.
|
|
4
|
+
*
|
|
5
|
+
* The counter exists so a developer can open a browser console, set
|
|
6
|
+
* `window.__RPC_DEBUG = true`, and see which JSON-RPC methods a page is
|
|
7
|
+
* hammering. It must stay silent otherwise and must never fail a request.
|
|
8
|
+
*/
|
|
9
|
+
import { afterEach, describe, it, mock } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { base } from 'viem/chains';
|
|
12
|
+
import { createChainTransport, defaultChainTransportStrategy, logRpcRequest, pingBlockNumber, } from './transports.js';
|
|
13
|
+
const windowHost = globalThis;
|
|
14
|
+
const jsonRequest = (method) => new Request('https://rpc.example', { method: 'POST', body: JSON.stringify({ jsonrpc: '2.0', id: 1, method }) });
|
|
15
|
+
/** Let the fire-and-forget body read finish. */
|
|
16
|
+
const settle = () => new Promise((resolve) => setTimeout(resolve, 10));
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
delete windowHost.window;
|
|
19
|
+
mock.restoreAll();
|
|
20
|
+
});
|
|
21
|
+
describe('logRpcRequest', () => {
|
|
22
|
+
it('stays silent when the debug flag is not set, so production consoles stay clean', async () => {
|
|
23
|
+
const debug = mock.method(console, 'debug', () => { });
|
|
24
|
+
logRpcRequest(jsonRequest('eth_chainId'));
|
|
25
|
+
await settle();
|
|
26
|
+
assert.equal(debug.mock.callCount(), 0);
|
|
27
|
+
});
|
|
28
|
+
it('stays silent when window exists but the flag is not exactly true', async () => {
|
|
29
|
+
windowHost.window = { __RPC_DEBUG: undefined };
|
|
30
|
+
const debug = mock.method(console, 'debug', () => { });
|
|
31
|
+
logRpcRequest(jsonRequest('eth_chainId'));
|
|
32
|
+
await settle();
|
|
33
|
+
assert.equal(debug.mock.callCount(), 0);
|
|
34
|
+
});
|
|
35
|
+
it('logs each method with a running per-method count when the flag is set', async () => {
|
|
36
|
+
windowHost.window = { __RPC_DEBUG: true };
|
|
37
|
+
const debug = mock.method(console, 'debug', () => { });
|
|
38
|
+
logRpcRequest(jsonRequest('eth_getLogs'));
|
|
39
|
+
logRpcRequest(jsonRequest('eth_getLogs'));
|
|
40
|
+
await settle();
|
|
41
|
+
assert.equal(debug.mock.callCount(), 2);
|
|
42
|
+
const lastLine = String(debug.mock.calls[1].arguments[0]);
|
|
43
|
+
assert.match(lastLine, /^\[RPC\] eth_getLogs \| 2 calls in [\d.]+s \| window: /);
|
|
44
|
+
assert.match(lastLine, /"eth_getLogs":2/);
|
|
45
|
+
});
|
|
46
|
+
it('leaves the original request body readable, because viem still has to send it', async () => {
|
|
47
|
+
const request = jsonRequest('eth_chainId');
|
|
48
|
+
logRpcRequest(request);
|
|
49
|
+
await settle();
|
|
50
|
+
assert.equal(request.bodyUsed, false);
|
|
51
|
+
assert.equal((await request.json()).method, 'eth_chainId');
|
|
52
|
+
});
|
|
53
|
+
it('ignores a body that is not JSON instead of failing the request', async () => {
|
|
54
|
+
windowHost.window = { __RPC_DEBUG: true };
|
|
55
|
+
const debug = mock.method(console, 'debug', () => { });
|
|
56
|
+
const request = new Request('https://rpc.example', { method: 'POST', body: 'not json' });
|
|
57
|
+
assert.doesNotThrow(() => logRpcRequest(request));
|
|
58
|
+
await settle();
|
|
59
|
+
assert.equal(debug.mock.callCount(), 0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('pingBlockNumber', () => {
|
|
63
|
+
it('probes with eth_blockNumber and returns its answer', async () => {
|
|
64
|
+
const calls = [];
|
|
65
|
+
const result = await pingBlockNumber({
|
|
66
|
+
transport: {
|
|
67
|
+
request: async (args) => {
|
|
68
|
+
calls.push(args);
|
|
69
|
+
return '0x10';
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
assert.deepEqual(calls, [{ method: 'eth_blockNumber', params: [] }]);
|
|
74
|
+
assert.equal(result, '0x10');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('defaultChainTransportStrategy', () => {
|
|
78
|
+
it('is ordered in Node, because ranking would keep the process alive forever', () => {
|
|
79
|
+
assert.equal(defaultChainTransportStrategy(), 'ordered');
|
|
80
|
+
});
|
|
81
|
+
it('is ranked in a browser, where the page owns the client for its lifetime', () => {
|
|
82
|
+
windowHost.window = {};
|
|
83
|
+
assert.equal(defaultChainTransportStrategy(), 'ranked');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('createChainTransport with an explicit ranked strategy', () => {
|
|
87
|
+
it('builds a factory without starting the ranking poller until a client uses it', () => {
|
|
88
|
+
// The poller starts when viem calls the factory with a chain, not here.
|
|
89
|
+
const transport = createChainTransport(8453, {
|
|
90
|
+
userUrls: [base.rpcUrls.default.http[0], 'https://other.example'],
|
|
91
|
+
strategy: 'ranked',
|
|
92
|
+
});
|
|
93
|
+
assert.equal(typeof transport, 'function');
|
|
94
|
+
});
|
|
95
|
+
});
|
package/dist/transports.js
CHANGED
|
@@ -46,6 +46,27 @@ const _rpcLog = (method) => {
|
|
|
46
46
|
const elapsed = ((now - _rpcLastWindow) / 1000).toFixed(1);
|
|
47
47
|
console.debug(`[RPC] ${method} | ${total} calls in ${elapsed}s | window: ${JSON.stringify(_rpcCounts)}`);
|
|
48
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Count one outgoing JSON-RPC request for the debug log.
|
|
51
|
+
*
|
|
52
|
+
* Reads the method from a clone, so the real request body stays unread. A body
|
|
53
|
+
* that is not JSON is ignored: a debug counter must never fail a request.
|
|
54
|
+
*
|
|
55
|
+
* @param req - The request viem is about to send.
|
|
56
|
+
*/
|
|
57
|
+
export const logRpcRequest = (req) => {
|
|
58
|
+
void req.clone().json().then((body) => _rpcLog(body.method)).catch(() => { });
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Latency probe for ranked transports: one `eth_blockNumber` call.
|
|
62
|
+
*
|
|
63
|
+
* It measures real query latency, not just the TCP round trip, and avoids the
|
|
64
|
+
* noisy `net_listening` entries viem's default probe leaves in server logs.
|
|
65
|
+
*
|
|
66
|
+
* @param transport - The transport instance viem is ranking.
|
|
67
|
+
* @returns The block number response, which viem times and discards.
|
|
68
|
+
*/
|
|
69
|
+
export const pingBlockNumber = (transport) => transport.transport.request({ method: 'eth_blockNumber', params: [] });
|
|
49
70
|
/**
|
|
50
71
|
* Provex-curated fallback RPC URLs per chain, applied after caller overrides
|
|
51
72
|
* but before the viem chain default.
|
|
@@ -135,7 +156,7 @@ export const parseRpcUrlList = (raw) => {
|
|
|
135
156
|
* browser (a long-running Node service) can still pass `strategy: 'ranked'`
|
|
136
157
|
* explicitly.
|
|
137
158
|
*/
|
|
138
|
-
const
|
|
159
|
+
export const defaultChainTransportStrategy = () => typeof window === 'undefined' ? 'ordered' : 'ranked';
|
|
139
160
|
/**
|
|
140
161
|
* Resolve the ordered RPC URL list for a chain: caller overrides, then the
|
|
141
162
|
* curated fallback list, then the viem chain default. Exposed for testing
|
|
@@ -162,7 +183,7 @@ export const resolveChainRpcUrls = (chainId, options = {}) => {
|
|
|
162
183
|
throw new Error(`resolveChainRpcUrls: no chain definition for chainId ${chainId}`);
|
|
163
184
|
}
|
|
164
185
|
const userUrls = options.userUrls ?? [];
|
|
165
|
-
const curatedFallbacks = FALLBACK_RPC_URLS[chainId]
|
|
186
|
+
const curatedFallbacks = FALLBACK_RPC_URLS[chainId];
|
|
166
187
|
const defaultUrl = chain.rpcUrls.default.http[0];
|
|
167
188
|
const ordered = [...userUrls, ...curatedFallbacks, defaultUrl].filter((url) => Boolean(url));
|
|
168
189
|
return [...new Set(ordered)];
|
|
@@ -197,12 +218,7 @@ export const resolveChainRpcUrls = (chainId, options = {}) => {
|
|
|
197
218
|
*/
|
|
198
219
|
export const createChainTransport = (chainId, options = {}) => {
|
|
199
220
|
const urls = resolveChainRpcUrls(chainId, options);
|
|
200
|
-
const transports = urls.map((url) => http(url, {
|
|
201
|
-
retryCount: 2,
|
|
202
|
-
onFetchRequest: (req) => {
|
|
203
|
-
void req.clone().json().then((body) => _rpcLog(body.method ?? '?')).catch(() => { });
|
|
204
|
-
},
|
|
205
|
-
}));
|
|
221
|
+
const transports = urls.map((url) => http(url, { retryCount: 2, onFetchRequest: logRpcRequest }));
|
|
206
222
|
// Single-URL chains: return the transport directly. fallback([single]) adds
|
|
207
223
|
// overhead with no benefit — there's nothing to fall back to, and retryCount
|
|
208
224
|
// is already set on the http() call above.
|
|
@@ -215,7 +231,7 @@ export const createChainTransport = (chainId, options = {}) => {
|
|
|
215
231
|
// a later entry is metered, because ranking optimises for latency and a paid
|
|
216
232
|
// endpoint is usually the fastest one in the list, so it would steer traffic
|
|
217
233
|
// onto exactly the endpoint the ordering was written to protect.
|
|
218
|
-
if ((options.strategy ??
|
|
234
|
+
if ((options.strategy ?? defaultChainTransportStrategy()) === 'ordered') {
|
|
219
235
|
return fallback(transports);
|
|
220
236
|
}
|
|
221
237
|
// Multi-URL chains: rank-enabled fallback so the fastest healthy endpoint
|
|
@@ -230,7 +246,7 @@ export const createChainTransport = (chainId, options = {}) => {
|
|
|
230
246
|
rank: {
|
|
231
247
|
interval: 60_000,
|
|
232
248
|
sampleCount: 3,
|
|
233
|
-
ping:
|
|
249
|
+
ping: pingBlockNumber,
|
|
234
250
|
},
|
|
235
251
|
});
|
|
236
252
|
};
|
package/dist/transports.test.js
CHANGED
|
@@ -56,6 +56,9 @@ describe('transports', () => {
|
|
|
56
56
|
it('trims padding so a human-formatted env list is not silently rejected', () => {
|
|
57
57
|
assert.deepEqual(parseRpcUrlList(' https://a.example , https://b.example '), ['https://a.example', 'https://b.example']);
|
|
58
58
|
});
|
|
59
|
+
it('skips the empty slots a stray or trailing comma leaves behind', () => {
|
|
60
|
+
assert.deepEqual(parseRpcUrlList('https://a.example,,https://b.example,'), ['https://a.example', 'https://b.example']);
|
|
61
|
+
});
|
|
59
62
|
it('drops malformed entries instead of handing them to the transport layer', () => {
|
|
60
63
|
assert.deepEqual(parseRpcUrlList('not-a-url, https://good.example, also bad'), ['https://good.example']);
|
|
61
64
|
});
|
|
@@ -167,7 +170,8 @@ describe('process liveness — the ranking poller has no off switch', () => {
|
|
|
167
170
|
// must still terminate; when it did not, three test files hung for their
|
|
168
171
|
// full timeout with every assertion passing, and
|
|
169
172
|
// `packages/scripts/src/lookup-hash.ts` grew a hand-added `process.exit(0)`.
|
|
170
|
-
|
|
173
|
+
// Generous timeout: a loaded machine can take over a minute to start the child.
|
|
174
|
+
assert.equal(buildsAndExits(undefined, 120_000), true, 'building a default transport left the process alive');
|
|
171
175
|
});
|
|
172
176
|
it('does not exit when ranking is explicitly requested', () => {
|
|
173
177
|
// The counterweight. Without this, the assertion above would still pass if
|
package/dist/units.d.ts
CHANGED
|
@@ -99,6 +99,25 @@ export declare const parseUnits: (amount: string, options: Pick<FormatRateDispla
|
|
|
99
99
|
* ```
|
|
100
100
|
*/
|
|
101
101
|
export declare function formatUnits(amount: bigint, options: FormatRateDisplayOptions): string;
|
|
102
|
+
/**
|
|
103
|
+
* Format a bigint amount as text for an editable field.
|
|
104
|
+
*
|
|
105
|
+
* Unlike {@link formatUnits} this never inserts a thousands delimiter, so the
|
|
106
|
+
* text is legal in an `<input type="number">` and re-parses to the same bigint.
|
|
107
|
+
* It keeps full precision unless `truncateDecimals` is given.
|
|
108
|
+
*
|
|
109
|
+
* @param amount - Amount in base units
|
|
110
|
+
* @param options - Same options as {@link formatUnits}; `delimiter` is not accepted
|
|
111
|
+
* @returns Plain decimal text such as `'1234.5'`
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* formatUnitsForInput(1_234_500000n, { decimals: 6 }) // '1234.5'
|
|
116
|
+
* formatUnitsForInput(10_123456n, { decimals: 6, padDecimals: 2 }) // '10.123456'
|
|
117
|
+
* formatUnitsForInput(500_000000n, { decimals: 6, padDecimals: 2 }) // '500.00'
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare function formatUnitsForInput(amount: bigint, options: Omit<FormatRateDisplayOptions, 'delimiter'>): string;
|
|
102
121
|
/**
|
|
103
122
|
* Format a token amount for display with 2 decimal places.
|
|
104
123
|
* Convenience wrapper around formatUnits with truncateDecimals: 2.
|
package/dist/units.js
CHANGED
|
@@ -120,6 +120,27 @@ export function formatUnits(amount, options) {
|
|
|
120
120
|
const numberString = `${integerPart}.${finalFractionalPart}`;
|
|
121
121
|
return addThousandsSeparator(numberString, delimiter);
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Format a bigint amount as text for an editable field.
|
|
125
|
+
*
|
|
126
|
+
* Unlike {@link formatUnits} this never inserts a thousands delimiter, so the
|
|
127
|
+
* text is legal in an `<input type="number">` and re-parses to the same bigint.
|
|
128
|
+
* It keeps full precision unless `truncateDecimals` is given.
|
|
129
|
+
*
|
|
130
|
+
* @param amount - Amount in base units
|
|
131
|
+
* @param options - Same options as {@link formatUnits}; `delimiter` is not accepted
|
|
132
|
+
* @returns Plain decimal text such as `'1234.5'`
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* formatUnitsForInput(1_234_500000n, { decimals: 6 }) // '1234.5'
|
|
137
|
+
* formatUnitsForInput(10_123456n, { decimals: 6, padDecimals: 2 }) // '10.123456'
|
|
138
|
+
* formatUnitsForInput(500_000000n, { decimals: 6, padDecimals: 2 }) // '500.00'
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export function formatUnitsForInput(amount, options) {
|
|
142
|
+
return formatUnits(amount, { ...options, delimiter: '' });
|
|
143
|
+
}
|
|
123
144
|
/**
|
|
124
145
|
* Format a token amount for display with 2 decimal places.
|
|
125
146
|
* Convenience wrapper around formatUnits with truncateDecimals: 2.
|
package/dist/units.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { oneEther, addThousandsSeparator, parseUnits, formatUnits, formatTokenAmountForDisplay, parseIntegerSafe, timestampToDate, formatAmount, abbreviateNumber, formatWithAbbreviation, numericAbbreviations, } from './units.js';
|
|
3
|
+
import { oneEther, addThousandsSeparator, parseUnits, formatUnits, formatUnitsForInput, formatTokenAmountForDisplay, parseIntegerSafe, timestampToDate, formatAmount, abbreviateNumber, formatWithAbbreviation, numericAbbreviations, } from './units.js';
|
|
4
4
|
describe('abbreviateNumber', () => {
|
|
5
5
|
describe('with decimals: 0 (whole numbers)', () => {
|
|
6
6
|
it('should abbreviate thousands with k suffix', () => {
|
|
@@ -338,3 +338,33 @@ describe('formatAmount', () => {
|
|
|
338
338
|
assert.equal(result, '1.2345');
|
|
339
339
|
});
|
|
340
340
|
});
|
|
341
|
+
// A value written into a number input must contain no thousands delimiter and
|
|
342
|
+
// must re-parse to the exact bigint, or the field shows empty / changes the amount.
|
|
343
|
+
describe('formatUnitsForInput', () => {
|
|
344
|
+
const cases = [
|
|
345
|
+
['below one thousand', 999500000n, { decimals: 6 }, '999.5'],
|
|
346
|
+
['one thousand or more has no comma', 1234500000n, { decimals: 6 }, '1234.5'],
|
|
347
|
+
['millions have no comma', 12345678900000n, { decimals: 6 }, '12345678.9'],
|
|
348
|
+
['sub-cent precision is kept', 10123456n, { decimals: 6, padDecimals: 2 }, '10.123456'],
|
|
349
|
+
['pads to the requested places', 500000000n, { decimals: 6, padDecimals: 2 }, '500.00'],
|
|
350
|
+
['an 18-decimal rate of 1234.5', 1234500000000000000000n, { decimals: 18, padDecimals: 2 }, '1234.50'],
|
|
351
|
+
['zero', 0n, { decimals: 6 }, '0'],
|
|
352
|
+
];
|
|
353
|
+
for (const [name, amount, options, expected] of cases) {
|
|
354
|
+
it(`${name}: ${expected}`, () => {
|
|
355
|
+
assert.equal(formatUnitsForInput(amount, options), expected);
|
|
356
|
+
assert.equal(parseUnits(expected, { decimals: options.decimals }), amount);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
describe('abbreviateNumber below the first suffix', () => {
|
|
361
|
+
it('keeps a non-zero fraction so 999.5 is not shown as 999', () => {
|
|
362
|
+
assert.equal(abbreviateNumber(9995n, { decimals: 1 }), '999.5');
|
|
363
|
+
});
|
|
364
|
+
it('keeps the sign on a negative fractional value', () => {
|
|
365
|
+
assert.equal(abbreviateNumber(-9995n, { decimals: 1 }), '-999.5');
|
|
366
|
+
});
|
|
367
|
+
it('drops an all-zero fraction', () => {
|
|
368
|
+
assert.equal(abbreviateNumber(5000n, { decimals: 1 }), '500');
|
|
369
|
+
});
|
|
370
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@provex/utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -120,13 +120,17 @@
|
|
|
120
120
|
"./consent": {
|
|
121
121
|
"import": "./dist/consent.js",
|
|
122
122
|
"types": "./dist/consent.d.ts"
|
|
123
|
+
},
|
|
124
|
+
"./buy": {
|
|
125
|
+
"import": "./dist/buy/index.js",
|
|
126
|
+
"types": "./dist/buy/index.d.ts"
|
|
123
127
|
}
|
|
124
128
|
},
|
|
125
129
|
"scripts": {
|
|
126
130
|
"build": "tsx scripts/convert-svg && tsc && cp -r src/data dist/",
|
|
127
131
|
"test": "node --import tsx --test 'src/**/*.test.ts'",
|
|
128
132
|
"test:watch": "node --import tsx --test --watch 'src/**/*.test.ts'",
|
|
129
|
-
"test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/data/**' --test-coverage-exclude='src/flags/**' --test-coverage-exclude='src/flags.ts' --test-coverage-exclude='src/global.d.ts' 'src/**/*.test.ts'"
|
|
133
|
+
"test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-lines=100 --test-coverage-branches=100 --test-coverage-functions=100 --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/data/**' --test-coverage-exclude='src/flags/**' --test-coverage-exclude='src/flags.ts' --test-coverage-exclude='src/global.d.ts' 'src/**/*.test.ts'"
|
|
130
134
|
},
|
|
131
135
|
"keywords": [],
|
|
132
136
|
"author": "",
|