Skip to content

Commit 8be1116

Browse files
committed
feat: typedCopyBytes
1 parent 8d54290 commit 8be1116

3 files changed

Lines changed: 97 additions & 1 deletion

File tree

array.d.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,48 @@ export type Uint32ArrayBuffer = ReturnType<typeof Uint32Array.from>;
4747
*/
4848
export type OutputFormat = 'uint8' | 'buffer';
4949

50+
/**
51+
* Output format for typed array copy conversions
52+
*/
53+
export type OutputCopyFormat = 'uint8' | 'arraybuffer' | 'buffer';
54+
5055
/**
5156
* Create a view of a TypedArray in the specified format (`'uint8'` or `'buffer'`)
5257
*
5358
* > [!IMPORTANT]
5459
* > Does not copy data, returns a view on the same underlying buffer
5560
*
61+
* > [!WARNING]
62+
* > Viewing `Uint16Array` (or other with `BYTES_PER_ELEMENT > 1`) as bytes
63+
* > is platform endianness-dependent.
64+
*
5665
* @param arr - The input TypedArray
5766
* @param format - The desired output format (`'uint8'` or `'buffer'`)
5867
* @returns A view on the same underlying buffer
5968
*/
6069
export function typedView(arr: ArrayBufferView, format: 'uint8'): Uint8Array;
6170
export function typedView(arr: ArrayBufferView, format: 'buffer'): Buffer;
6271
export function typedView(arr: ArrayBufferView, format: OutputFormat): Uint8Array | Buffer;
72+
73+
/**
74+
* Create a copy of TypedArray underlying bytes in the specified format (`'uint8'`, `'buffer'`, or `'arraybuffer'`)
75+
*
76+
* This does not copy _values_, but copies the underlying bytes.
77+
* The result is similar to that of `typedView()`, but this function provides a copy, not a view of the same memory.
78+
*
79+
* > [!WARNING]
80+
* > Copying underlying bytes from `Uint16Array` (or other with `BYTES_PER_ELEMENT > 1`)
81+
* > is platform endianness-dependent.
82+
*
83+
* > [!NOTE]
84+
* > Buffer might be pooled.
85+
* > Uint8Array return values are not pooled and match their underlying ArrayBuffer.
86+
*
87+
* @param arr - The input TypedArray
88+
* @param format - The desired output format (`'uint8'`, `'buffer'`, or `'arraybuffer'`)
89+
* @returns A copy of the underlying buffer
90+
*/
91+
export function typedCopyBytes(arr: ArrayBufferView, format: 'uint8'): Uint8Array;
92+
export function typedCopyBytes(arr: ArrayBufferView, format: 'arraybuffer'): ArrayBuffer;
93+
export function typedCopyBytes(arr: ArrayBufferView, format: 'buffer'): Buffer;
94+
export function typedCopyBytes(arr: ArrayBufferView, format: OutputCopyFormat): Uint8Array | ArrayBuffer | Buffer;

array.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,17 @@ export function typedView(arr, format) {
1515

1616
throw new TypeError('Unexpected format')
1717
}
18+
19+
export function typedCopyBytes(arr, format) {
20+
assertTypedArray(arr)
21+
switch (format) {
22+
case 'uint8':
23+
return Uint8Array.from(arr) // never pooled
24+
case 'buffer':
25+
return Buffer.from(arr)
26+
case 'arraybuffer':
27+
return Uint8Array.from(arr).buffer
28+
}
29+
30+
throw new TypeError('Unexpected format')
31+
}

tests/array.test.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { typedView } from '@exodus/bytes/array.js'
1+
import { typedView, typedCopyBytes } from '@exodus/bytes/array.js'
22
import { describe, test } from 'node:test'
33

44
const raw = [new Uint8Array(), new Uint8Array([0]), new Uint8Array([1]), new Uint8Array([255])]
@@ -40,3 +40,53 @@ describe('typedView', () => {
4040
}
4141
})
4242
})
43+
44+
describe('typedCopyBytes', () => {
45+
test('invalid input', (t) => {
46+
for (const input of [null, undefined, [], [1, 2], 'string']) {
47+
t.assert.throws(() => typedCopyBytes(input))
48+
for (const form of ['uint8', 'buffer']) {
49+
t.assert.throws(() => typedCopyBytes(input, form))
50+
}
51+
}
52+
})
53+
54+
test('uint8', (t) => {
55+
for (const { buffer, uint8 } of pool) {
56+
for (const arg of [buffer, uint8]) {
57+
const a = typedCopyBytes(arg, 'uint8')
58+
t.assert.deepStrictEqual(a, uint8)
59+
t.assert.notEqual(a, arg)
60+
t.assert.strictEqual(a.byteLength, arg.byteLength)
61+
// Non-pooled
62+
t.assert.notEqual(a.buffer, arg.buffer)
63+
t.assert.strictEqual(a.byteLength, a.buffer.byteLength)
64+
}
65+
}
66+
})
67+
68+
test('arraybuffer', (t) => {
69+
for (const { buffer, uint8 } of pool) {
70+
for (const arg of [buffer, uint8]) {
71+
const a = typedCopyBytes(arg, 'arraybuffer')
72+
t.assert.deepStrictEqual(a, Uint8Array.from(arg).buffer)
73+
t.assert.notEqual(a, arg)
74+
t.assert.strictEqual(a.byteLength, arg.byteLength)
75+
t.assert.notEqual(a, arg.buffer)
76+
}
77+
}
78+
})
79+
80+
test('buffer', (t) => {
81+
for (const { uint8, buffer } of pool) {
82+
for (const arg of [buffer, uint8]) {
83+
const a = typedCopyBytes(arg, 'buffer')
84+
t.assert.deepStrictEqual(a, buffer)
85+
t.assert.notEqual(a, arg)
86+
t.assert.strictEqual(a.byteLength, arg.byteLength)
87+
// Might be pooled
88+
t.assert.ok(a.buffer !== arg.buffer || a.byteOffset !== arg.byteOffset)
89+
}
90+
}
91+
})
92+
})

0 commit comments

Comments
 (0)