@btc-vision/bitcoin 7.0.0-alpha.2 → 7.0.0-alpha.4
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/README.md +455 -155
- package/browser/chunks/WorkerSigningPool.sequential-DHha7j0b.js +113 -0
- package/browser/ecc/context.d.ts +22 -21
- package/browser/ecc/context.d.ts.map +1 -1
- package/browser/ecc/index.d.ts +1 -1
- package/browser/ecc/index.d.ts.map +1 -1
- package/browser/ecc/types.d.ts +10 -123
- package/browser/ecc/types.d.ts.map +1 -1
- package/browser/env.d.ts +13 -0
- package/browser/env.d.ts.map +1 -0
- package/browser/index.d.ts +3 -3
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +5790 -4295
- package/browser/io/index.d.ts +0 -1
- package/browser/io/index.d.ts.map +1 -1
- package/browser/payments/p2tr.d.ts.map +1 -1
- package/browser/psbt/types.d.ts +2 -68
- package/browser/psbt/types.d.ts.map +1 -1
- package/browser/psbt.d.ts +9 -11
- package/browser/psbt.d.ts.map +1 -1
- package/browser/types.d.ts +1 -1
- package/browser/types.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.d.ts +6 -0
- package/browser/workers/WorkerSigningPool.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.node.d.ts +6 -0
- package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -1
- package/browser/workers/WorkerSigningPool.sequential.d.ts +69 -0
- package/browser/workers/WorkerSigningPool.sequential.d.ts.map +1 -0
- package/browser/workers/WorkerSigningPool.worklet.d.ts +64 -0
- package/browser/workers/WorkerSigningPool.worklet.d.ts.map +1 -0
- package/browser/workers/index.d.ts +2 -2
- package/browser/workers/index.d.ts.map +1 -1
- package/browser/workers/index.react-native.d.ts +28 -0
- package/browser/workers/index.react-native.d.ts.map +1 -0
- package/browser/workers/psbt-parallel.d.ts +2 -3
- package/browser/workers/psbt-parallel.d.ts.map +1 -1
- package/browser/workers/types.d.ts +12 -0
- package/browser/workers/types.d.ts.map +1 -1
- package/build/ecc/context.d.ts +22 -21
- package/build/ecc/context.d.ts.map +1 -1
- package/build/ecc/context.js +19 -114
- package/build/ecc/context.js.map +1 -1
- package/build/ecc/index.d.ts +1 -1
- package/build/ecc/index.d.ts.map +1 -1
- package/build/ecc/types.d.ts +7 -126
- package/build/ecc/types.d.ts.map +1 -1
- package/build/ecc/types.js +4 -1
- package/build/ecc/types.js.map +1 -1
- package/build/env.d.ts +13 -0
- package/build/env.d.ts.map +1 -0
- package/build/env.js +198 -0
- package/build/env.js.map +1 -0
- package/build/index.d.ts +4 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/build/io/index.d.ts +0 -1
- package/build/io/index.d.ts.map +1 -1
- package/build/io/index.js +0 -2
- package/build/io/index.js.map +1 -1
- package/build/payments/p2tr.d.ts.map +1 -1
- package/build/payments/p2tr.js +2 -3
- package/build/payments/p2tr.js.map +1 -1
- package/build/psbt/types.d.ts +2 -68
- package/build/psbt/types.d.ts.map +1 -1
- package/build/psbt.d.ts +9 -11
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js +38 -53
- package/build/psbt.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/types.d.ts +1 -1
- package/build/types.d.ts.map +1 -1
- package/build/types.js +2 -16
- package/build/types.js.map +1 -1
- package/build/workers/WorkerSigningPool.d.ts +6 -0
- package/build/workers/WorkerSigningPool.d.ts.map +1 -1
- package/build/workers/WorkerSigningPool.js +8 -0
- package/build/workers/WorkerSigningPool.js.map +1 -1
- package/build/workers/WorkerSigningPool.node.d.ts +6 -0
- package/build/workers/WorkerSigningPool.node.d.ts.map +1 -1
- package/build/workers/WorkerSigningPool.node.js +9 -2
- package/build/workers/WorkerSigningPool.node.js.map +1 -1
- package/build/workers/WorkerSigningPool.sequential.d.ts +78 -0
- package/build/workers/WorkerSigningPool.sequential.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.sequential.js +160 -0
- package/build/workers/WorkerSigningPool.sequential.js.map +1 -0
- package/build/workers/WorkerSigningPool.worklet.d.ts +79 -0
- package/build/workers/WorkerSigningPool.worklet.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.worklet.js +388 -0
- package/build/workers/WorkerSigningPool.worklet.js.map +1 -0
- package/build/workers/index.d.ts +2 -2
- package/build/workers/index.d.ts.map +1 -1
- package/build/workers/index.js +9 -0
- package/build/workers/index.js.map +1 -1
- package/build/workers/index.react-native.d.ts +28 -0
- package/build/workers/index.react-native.d.ts.map +1 -0
- package/build/workers/index.react-native.js +67 -0
- package/build/workers/index.react-native.js.map +1 -0
- package/build/workers/psbt-parallel.d.ts +2 -3
- package/build/workers/psbt-parallel.d.ts.map +1 -1
- package/build/workers/psbt-parallel.js +4 -4
- package/build/workers/psbt-parallel.js.map +1 -1
- package/build/workers/types.d.ts +12 -0
- package/build/workers/types.d.ts.map +1 -1
- package/package.json +14 -4
- package/src/ecc/context.ts +26 -147
- package/src/ecc/index.ts +2 -2
- package/src/ecc/types.ts +7 -138
- package/src/env.ts +237 -0
- package/src/index.ts +2 -4
- package/src/io/index.ts +0 -3
- package/src/payments/p2tr.ts +2 -2
- package/src/psbt/types.ts +2 -84
- package/src/psbt.ts +63 -121
- package/src/types.ts +5 -28
- package/src/workers/WorkerSigningPool.node.ts +10 -2
- package/src/workers/WorkerSigningPool.sequential.ts +190 -0
- package/src/workers/WorkerSigningPool.ts +9 -0
- package/src/workers/WorkerSigningPool.worklet.ts +519 -0
- package/src/workers/index.react-native.ts +110 -0
- package/src/workers/index.ts +10 -1
- package/src/workers/psbt-parallel.ts +8 -8
- package/src/workers/types.ts +16 -0
- package/test/env.spec.ts +418 -0
- package/test/workers-pool.spec.ts +43 -0
- package/test/workers-sequential.spec.ts +669 -0
- package/test/workers-worklet.spec.ts +500 -0
- package/browser/io/MemoryPool.d.ts +0 -220
- package/browser/io/MemoryPool.d.ts.map +0 -1
- package/build/io/MemoryPool.d.ts +0 -220
- package/build/io/MemoryPool.d.ts.map +0 -1
- package/build/io/MemoryPool.js +0 -309
- package/build/io/MemoryPool.js.map +0 -1
- package/src/io/MemoryPool.ts +0 -343
|
@@ -32,9 +32,8 @@ import type { PsbtInput, TapKeySig, TapScriptSig } from 'bip174';
|
|
|
32
32
|
import type { PublicKey } from '../types.js';
|
|
33
33
|
import type { Psbt } from '../psbt.js';
|
|
34
34
|
import { Transaction } from '../transaction.js';
|
|
35
|
-
import type { ParallelSignerKeyPair, ParallelSigningResult, SigningTask, WorkerPoolConfig, } from './types.js';
|
|
35
|
+
import type { ParallelSignerKeyPair, ParallelSigningResult, SigningPoolLike, SigningTask, WorkerPoolConfig, } from './types.js';
|
|
36
36
|
import { SignatureType } from './types.js';
|
|
37
|
-
import { WorkerSigningPool } from './WorkerSigningPool.js';
|
|
38
37
|
import { toXOnly } from '../pubkey.js';
|
|
39
38
|
import { isTaprootInput, serializeTaprootSignature } from '../psbt/bip371.js';
|
|
40
39
|
import * as bscript from '../script.js';
|
|
@@ -108,17 +107,18 @@ export interface PsbtParallelKeyPair extends ParallelSignerKeyPair {
|
|
|
108
107
|
export async function signPsbtParallel(
|
|
109
108
|
psbt: Psbt,
|
|
110
109
|
keyPair: PsbtParallelKeyPair,
|
|
111
|
-
poolOrConfig?:
|
|
110
|
+
poolOrConfig?: SigningPoolLike | WorkerPoolConfig,
|
|
112
111
|
options: ParallelSignOptions = {},
|
|
113
112
|
): Promise<ParallelSigningResult> {
|
|
114
113
|
// Get or create pool
|
|
115
|
-
let pool:
|
|
114
|
+
let pool: SigningPoolLike;
|
|
116
115
|
let shouldShutdown = false;
|
|
117
116
|
|
|
118
|
-
if (poolOrConfig
|
|
117
|
+
if (poolOrConfig && 'signBatch' in poolOrConfig) {
|
|
119
118
|
pool = poolOrConfig;
|
|
120
119
|
} else {
|
|
121
|
-
|
|
120
|
+
const { WorkerSigningPool } = await import('./WorkerSigningPool.js');
|
|
121
|
+
pool = WorkerSigningPool.getInstance(poolOrConfig as WorkerPoolConfig | undefined);
|
|
122
122
|
if (!pool.isPreservingWorkers) {
|
|
123
123
|
shouldShutdown = true;
|
|
124
124
|
}
|
|
@@ -310,12 +310,12 @@ export function applySignaturesToPsbt(
|
|
|
310
310
|
} else {
|
|
311
311
|
// ECDSA signature
|
|
312
312
|
const encodedSig = bscript.signature.encode(
|
|
313
|
-
|
|
313
|
+
sigResult.signature,
|
|
314
314
|
input.sighashType ?? Transaction.SIGHASH_ALL,
|
|
315
315
|
);
|
|
316
316
|
const partialSig = [
|
|
317
317
|
{
|
|
318
|
-
pubkey:
|
|
318
|
+
pubkey: Uint8Array.from(pubkey),
|
|
319
319
|
signature: encodedSig,
|
|
320
320
|
},
|
|
321
321
|
];
|
package/src/workers/types.ts
CHANGED
|
@@ -400,6 +400,22 @@ export const WorkerState = {
|
|
|
400
400
|
|
|
401
401
|
export type WorkerState = (typeof WorkerState)[keyof typeof WorkerState];
|
|
402
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Minimum contract for any signing pool implementation.
|
|
405
|
+
*
|
|
406
|
+
* Implemented by WorkerSigningPool (browser), NodeWorkerSigningPool (Node.js),
|
|
407
|
+
* and SequentialSigningPool (React Native / fallback).
|
|
408
|
+
*/
|
|
409
|
+
export interface SigningPoolLike {
|
|
410
|
+
signBatch(
|
|
411
|
+
tasks: readonly SigningTask[],
|
|
412
|
+
keyPair: ParallelSignerKeyPair,
|
|
413
|
+
): Promise<ParallelSigningResult>;
|
|
414
|
+
initialize(): Promise<void>;
|
|
415
|
+
shutdown(): Promise<void>;
|
|
416
|
+
readonly isPreservingWorkers: boolean;
|
|
417
|
+
}
|
|
418
|
+
|
|
403
419
|
/**
|
|
404
420
|
* Internal worker wrapper for pool management.
|
|
405
421
|
*/
|
package/test/env.spec.ts
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('Runtime capability check — hard requirements', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.resetModules();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.unstubAllGlobals();
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should pass in a capable environment', async () => {
|
|
14
|
+
await expect(import('../src/env.js')).resolves.not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should throw when BigInt is missing', async () => {
|
|
18
|
+
vi.stubGlobal('BigInt', undefined);
|
|
19
|
+
|
|
20
|
+
await expect(import('../src/env.js')).rejects.toThrow('unsupported runtime');
|
|
21
|
+
await expect(import('../src/env.js')).rejects.toThrow('BigInt');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should throw when Uint8Array is missing', async () => {
|
|
25
|
+
vi.stubGlobal('Uint8Array', undefined);
|
|
26
|
+
|
|
27
|
+
await expect(import('../src/env.js')).rejects.toThrow('unsupported runtime');
|
|
28
|
+
await expect(import('../src/env.js')).rejects.toThrow('Uint8Array');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should throw when DataView.getBigInt64 is missing', async () => {
|
|
32
|
+
const orig = DataView.prototype.getBigInt64;
|
|
33
|
+
(DataView.prototype as Record<string, unknown>)['getBigInt64'] = undefined;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await import('../src/env.js');
|
|
37
|
+
expect.unreachable('should have thrown');
|
|
38
|
+
} catch (err) {
|
|
39
|
+
expect((err as Error).message).toContain('getBigInt64');
|
|
40
|
+
} finally {
|
|
41
|
+
DataView.prototype.getBigInt64 = orig;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should report all missing capabilities at once', async () => {
|
|
46
|
+
vi.stubGlobal('BigInt', undefined);
|
|
47
|
+
vi.stubGlobal('Uint8Array', undefined);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await import('../src/env.js');
|
|
51
|
+
expect.unreachable('should have thrown');
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const msg = (err as Error).message;
|
|
54
|
+
expect(msg).toContain('BigInt');
|
|
55
|
+
expect(msg).toContain('Uint8Array');
|
|
56
|
+
expect(msg).toContain('cannot be polyfilled');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Polyfill — TextEncoder / TextDecoder', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
vi.resetModules();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.unstubAllGlobals();
|
|
68
|
+
vi.restoreAllMocks();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should polyfill TextEncoder when missing', async () => {
|
|
72
|
+
vi.stubGlobal('TextEncoder', undefined);
|
|
73
|
+
|
|
74
|
+
await import('../src/env.js');
|
|
75
|
+
|
|
76
|
+
const encoder = new globalThis.TextEncoder();
|
|
77
|
+
const result = encoder.encode('hello');
|
|
78
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
79
|
+
expect(Array.from(result)).toEqual([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should encode multi-byte UTF-8 correctly', async () => {
|
|
83
|
+
vi.stubGlobal('TextEncoder', undefined);
|
|
84
|
+
|
|
85
|
+
await import('../src/env.js');
|
|
86
|
+
|
|
87
|
+
const encoder = new globalThis.TextEncoder();
|
|
88
|
+
// 2-byte: é (U+00E9)
|
|
89
|
+
const twoB = encoder.encode('é');
|
|
90
|
+
expect(Array.from(twoB)).toEqual([0xc3, 0xa9]);
|
|
91
|
+
|
|
92
|
+
// 3-byte: € (U+20AC)
|
|
93
|
+
const threeB = encoder.encode('€');
|
|
94
|
+
expect(Array.from(threeB)).toEqual([0xe2, 0x82, 0xac]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should encode surrogate pairs (4-byte UTF-8)', async () => {
|
|
98
|
+
vi.stubGlobal('TextEncoder', undefined);
|
|
99
|
+
|
|
100
|
+
await import('../src/env.js');
|
|
101
|
+
|
|
102
|
+
const encoder = new globalThis.TextEncoder();
|
|
103
|
+
// U+1F600 (😀) = F0 9F 98 80
|
|
104
|
+
const emoji = encoder.encode('😀');
|
|
105
|
+
expect(Array.from(emoji)).toEqual([0xf0, 0x9f, 0x98, 0x80]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should polyfill TextDecoder when missing', async () => {
|
|
109
|
+
vi.stubGlobal('TextDecoder', undefined);
|
|
110
|
+
|
|
111
|
+
await import('../src/env.js');
|
|
112
|
+
|
|
113
|
+
const decoder = new globalThis.TextDecoder();
|
|
114
|
+
const result = decoder.decode(new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]));
|
|
115
|
+
expect(result).toBe('hello');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should decode multi-byte UTF-8 correctly', async () => {
|
|
119
|
+
vi.stubGlobal('TextDecoder', undefined);
|
|
120
|
+
|
|
121
|
+
await import('../src/env.js');
|
|
122
|
+
|
|
123
|
+
const decoder = new globalThis.TextDecoder();
|
|
124
|
+
expect(decoder.decode(new Uint8Array([0xc3, 0xa9]))).toBe('é');
|
|
125
|
+
expect(decoder.decode(new Uint8Array([0xe2, 0x82, 0xac]))).toBe('€');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should decode surrogate pairs (4-byte UTF-8)', async () => {
|
|
129
|
+
vi.stubGlobal('TextDecoder', undefined);
|
|
130
|
+
|
|
131
|
+
await import('../src/env.js');
|
|
132
|
+
|
|
133
|
+
const decoder = new globalThis.TextDecoder();
|
|
134
|
+
expect(decoder.decode(new Uint8Array([0xf0, 0x9f, 0x98, 0x80]))).toBe('😀');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should round-trip encode/decode', async () => {
|
|
138
|
+
vi.stubGlobal('TextEncoder', undefined);
|
|
139
|
+
vi.stubGlobal('TextDecoder', undefined);
|
|
140
|
+
|
|
141
|
+
await import('../src/env.js');
|
|
142
|
+
|
|
143
|
+
const input = 'Hello, 世界! 🚀';
|
|
144
|
+
const encoder = new globalThis.TextEncoder();
|
|
145
|
+
const decoder = new globalThis.TextDecoder();
|
|
146
|
+
expect(decoder.decode(encoder.encode(input))).toBe(input);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should not replace native TextEncoder', async () => {
|
|
150
|
+
const NativeTextEncoder = globalThis.TextEncoder;
|
|
151
|
+
|
|
152
|
+
await import('../src/env.js');
|
|
153
|
+
|
|
154
|
+
expect(globalThis.TextEncoder).toBe(NativeTextEncoder);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('Polyfill — Map', () => {
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
vi.resetModules();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
afterEach(() => {
|
|
164
|
+
vi.unstubAllGlobals();
|
|
165
|
+
vi.restoreAllMocks();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should polyfill Map when missing', async () => {
|
|
169
|
+
vi.stubGlobal('Map', undefined);
|
|
170
|
+
|
|
171
|
+
await import('../src/env.js');
|
|
172
|
+
|
|
173
|
+
const map = new globalThis.Map<string, number>();
|
|
174
|
+
map.set('a', 1);
|
|
175
|
+
map.set('b', 2);
|
|
176
|
+
expect(map.size).toBe(2);
|
|
177
|
+
expect(map.get('a')).toBe(1);
|
|
178
|
+
expect(map.has('b')).toBe(true);
|
|
179
|
+
expect(map.has('c')).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should support delete and clear', async () => {
|
|
183
|
+
vi.stubGlobal('Map', undefined);
|
|
184
|
+
|
|
185
|
+
await import('../src/env.js');
|
|
186
|
+
|
|
187
|
+
const map = new globalThis.Map<string, number>();
|
|
188
|
+
map.set('x', 10);
|
|
189
|
+
map.set('y', 20);
|
|
190
|
+
expect(map.delete('x')).toBe(true);
|
|
191
|
+
expect(map.delete('x')).toBe(false);
|
|
192
|
+
expect(map.size).toBe(1);
|
|
193
|
+
|
|
194
|
+
map.clear();
|
|
195
|
+
expect(map.size).toBe(0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should support iteration', async () => {
|
|
199
|
+
vi.stubGlobal('Map', undefined);
|
|
200
|
+
|
|
201
|
+
await import('../src/env.js');
|
|
202
|
+
|
|
203
|
+
const map = new globalThis.Map<string, number>([
|
|
204
|
+
['a', 1],
|
|
205
|
+
['b', 2],
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
const keys = [...map.keys()];
|
|
209
|
+
const values = [...map.values()];
|
|
210
|
+
const entries = [...map.entries()];
|
|
211
|
+
|
|
212
|
+
expect(keys).toEqual(['a', 'b']);
|
|
213
|
+
expect(values).toEqual([1, 2]);
|
|
214
|
+
expect(entries).toEqual([
|
|
215
|
+
['a', 1],
|
|
216
|
+
['b', 2],
|
|
217
|
+
]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should support forEach', async () => {
|
|
221
|
+
vi.stubGlobal('Map', undefined);
|
|
222
|
+
|
|
223
|
+
await import('../src/env.js');
|
|
224
|
+
|
|
225
|
+
const map = new globalThis.Map([['k', 'v']]);
|
|
226
|
+
const collected: string[] = [];
|
|
227
|
+
map.forEach((val, key) => collected.push(`${key}=${val}`));
|
|
228
|
+
expect(collected).toEqual(['k=v']);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should overwrite existing keys', async () => {
|
|
232
|
+
vi.stubGlobal('Map', undefined);
|
|
233
|
+
|
|
234
|
+
await import('../src/env.js');
|
|
235
|
+
|
|
236
|
+
const map = new globalThis.Map<string, number>();
|
|
237
|
+
map.set('a', 1);
|
|
238
|
+
map.set('a', 2);
|
|
239
|
+
expect(map.size).toBe(1);
|
|
240
|
+
expect(map.get('a')).toBe(2);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should not replace native Map', async () => {
|
|
244
|
+
const NativeMap = globalThis.Map;
|
|
245
|
+
|
|
246
|
+
await import('../src/env.js');
|
|
247
|
+
|
|
248
|
+
expect(globalThis.Map).toBe(NativeMap);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('Polyfill — Promise.allSettled', () => {
|
|
253
|
+
beforeEach(() => {
|
|
254
|
+
vi.resetModules();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
afterEach(() => {
|
|
258
|
+
vi.unstubAllGlobals();
|
|
259
|
+
vi.restoreAllMocks();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should polyfill Promise.allSettled when missing', async () => {
|
|
263
|
+
const orig = Promise.allSettled;
|
|
264
|
+
(Promise as Record<string, unknown>)['allSettled'] = undefined;
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
await import('../src/env.js');
|
|
268
|
+
|
|
269
|
+
const results = await Promise.allSettled([
|
|
270
|
+
Promise.resolve(1),
|
|
271
|
+
Promise.reject(new Error('fail')),
|
|
272
|
+
Promise.resolve(3),
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
expect(results).toHaveLength(3);
|
|
276
|
+
expect(results[0]).toEqual({ status: 'fulfilled', value: 1 });
|
|
277
|
+
expect(results[1]!.status).toBe('rejected');
|
|
278
|
+
expect((results[1] as PromiseRejectedResult).reason).toBeInstanceOf(Error);
|
|
279
|
+
expect(results[2]).toEqual({ status: 'fulfilled', value: 3 });
|
|
280
|
+
} finally {
|
|
281
|
+
Promise.allSettled = orig;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should not replace native Promise.allSettled', async () => {
|
|
286
|
+
const native = Promise.allSettled;
|
|
287
|
+
|
|
288
|
+
await import('../src/env.js');
|
|
289
|
+
|
|
290
|
+
expect(Promise.allSettled).toBe(native);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('Polyfill — structuredClone', () => {
|
|
295
|
+
beforeEach(() => {
|
|
296
|
+
vi.resetModules();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
afterEach(() => {
|
|
300
|
+
vi.unstubAllGlobals();
|
|
301
|
+
vi.restoreAllMocks();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should polyfill structuredClone when missing', async () => {
|
|
305
|
+
vi.stubGlobal('structuredClone', undefined);
|
|
306
|
+
|
|
307
|
+
await import('../src/env.js');
|
|
308
|
+
|
|
309
|
+
const original = { a: 1, b: { c: 2 } };
|
|
310
|
+
const cloned = globalThis.structuredClone(original);
|
|
311
|
+
|
|
312
|
+
expect(cloned).toEqual(original);
|
|
313
|
+
expect(cloned).not.toBe(original);
|
|
314
|
+
expect(cloned.b).not.toBe(original.b);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should deep clone nested objects', async () => {
|
|
318
|
+
vi.stubGlobal('structuredClone', undefined);
|
|
319
|
+
|
|
320
|
+
await import('../src/env.js');
|
|
321
|
+
|
|
322
|
+
const original = { x: [1, 2, { y: 3 }] };
|
|
323
|
+
const cloned = globalThis.structuredClone(original);
|
|
324
|
+
|
|
325
|
+
cloned.x[0] = 99;
|
|
326
|
+
(cloned.x[2] as { y: number }).y = 99;
|
|
327
|
+
|
|
328
|
+
expect(original.x[0]).toBe(1);
|
|
329
|
+
expect((original.x[2] as { y: number }).y).toBe(3);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should handle primitives and null', async () => {
|
|
333
|
+
vi.stubGlobal('structuredClone', undefined);
|
|
334
|
+
|
|
335
|
+
await import('../src/env.js');
|
|
336
|
+
|
|
337
|
+
expect(globalThis.structuredClone(42)).toBe(42);
|
|
338
|
+
expect(globalThis.structuredClone('hello')).toBe('hello');
|
|
339
|
+
expect(globalThis.structuredClone(true)).toBe(true);
|
|
340
|
+
expect(globalThis.structuredClone(null)).toBe(null);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should not replace native structuredClone', async () => {
|
|
344
|
+
const native = globalThis.structuredClone;
|
|
345
|
+
|
|
346
|
+
await import('../src/env.js');
|
|
347
|
+
|
|
348
|
+
expect(globalThis.structuredClone).toBe(native);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('Polyfill — Symbol.dispose / Symbol.asyncDispose', () => {
|
|
353
|
+
beforeEach(() => {
|
|
354
|
+
vi.resetModules();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
afterEach(() => {
|
|
358
|
+
vi.unstubAllGlobals();
|
|
359
|
+
vi.restoreAllMocks();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should ensure Symbol.dispose is defined after import', async () => {
|
|
363
|
+
await import('../src/env.js');
|
|
364
|
+
expect(typeof Symbol.dispose).toBe('symbol');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should ensure Symbol.asyncDispose is defined after import', async () => {
|
|
368
|
+
await import('../src/env.js');
|
|
369
|
+
expect(typeof Symbol.asyncDispose).toBe('symbol');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should polyfill dispose on a target that lacks it', () => {
|
|
373
|
+
const target: Record<string, symbol | undefined> = { dispose: undefined };
|
|
374
|
+
if (typeof target['dispose'] === 'undefined') {
|
|
375
|
+
target['dispose'] = Symbol.for('Symbol.dispose');
|
|
376
|
+
}
|
|
377
|
+
expect(target['dispose']).toBe(Symbol.for('Symbol.dispose'));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should polyfill asyncDispose on a target that lacks it', () => {
|
|
381
|
+
const target: Record<string, symbol | undefined> = { asyncDispose: undefined };
|
|
382
|
+
if (typeof target['asyncDispose'] === 'undefined') {
|
|
383
|
+
target['asyncDispose'] = Symbol.for('Symbol.asyncDispose');
|
|
384
|
+
}
|
|
385
|
+
expect(target['asyncDispose']).toBe(Symbol.for('Symbol.asyncDispose'));
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('should not overwrite an existing dispose symbol', () => {
|
|
389
|
+
const existing = Symbol('existing');
|
|
390
|
+
const target: Record<string, symbol> = { dispose: existing };
|
|
391
|
+
if (typeof target['dispose'] === 'undefined') {
|
|
392
|
+
target['dispose'] = Symbol.for('Symbol.dispose');
|
|
393
|
+
}
|
|
394
|
+
expect(target['dispose']).toBe(existing);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should not overwrite an existing asyncDispose symbol', () => {
|
|
398
|
+
const existing = Symbol('existing');
|
|
399
|
+
const target: Record<string, symbol> = { asyncDispose: existing };
|
|
400
|
+
if (typeof target['asyncDispose'] === 'undefined') {
|
|
401
|
+
target['asyncDispose'] = Symbol.for('Symbol.asyncDispose');
|
|
402
|
+
}
|
|
403
|
+
expect(target['asyncDispose']).toBe(existing);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should produce a usable symbol for async method dispatch', async () => {
|
|
407
|
+
const sym = Symbol.for('Symbol.asyncDispose');
|
|
408
|
+
const obj = {
|
|
409
|
+
disposed: false,
|
|
410
|
+
async [sym]() {
|
|
411
|
+
this.disposed = true;
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
await obj[sym]();
|
|
416
|
+
expect(obj.disposed).toBe(true);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
@@ -606,6 +606,49 @@ describe('WorkerSigningPool', () => {
|
|
|
606
606
|
});
|
|
607
607
|
});
|
|
608
608
|
|
|
609
|
+
describe('Symbol.asyncDispose', () => {
|
|
610
|
+
it('should have Symbol.asyncDispose method', () => {
|
|
611
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 1 });
|
|
612
|
+
expect(typeof pool[Symbol.asyncDispose]).toBe('function');
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it('should terminate workers when disposed', async () => {
|
|
616
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
617
|
+
await pool.initialize();
|
|
618
|
+
expect(pool.workerCount).toBe(2);
|
|
619
|
+
|
|
620
|
+
await pool[Symbol.asyncDispose]();
|
|
621
|
+
expect(pool.workerCount).toBe(0);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('should be safe to call after shutdown', async () => {
|
|
625
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
626
|
+
await pool.initialize();
|
|
627
|
+
|
|
628
|
+
await pool.shutdown();
|
|
629
|
+
await pool[Symbol.asyncDispose](); // Should not throw
|
|
630
|
+
|
|
631
|
+
expect(pool.workerCount).toBe(0);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it('should clean up with await using', async () => {
|
|
635
|
+
WorkerSigningPool.resetInstance();
|
|
636
|
+
|
|
637
|
+
let poolRef: InstanceType<typeof WorkerSigningPool> | undefined;
|
|
638
|
+
|
|
639
|
+
// Scoped block — pool is disposed when the block exits
|
|
640
|
+
{
|
|
641
|
+
await using pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
642
|
+
await pool.initialize();
|
|
643
|
+
expect(pool.workerCount).toBe(2);
|
|
644
|
+
poolRef = pool;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// After scope exit, dispose has been called
|
|
648
|
+
expect(poolRef!.workerCount).toBe(0);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
609
652
|
describe('Worker Pool Without Preservation', () => {
|
|
610
653
|
it('should terminate workers after batch when not preserving', async () => {
|
|
611
654
|
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|