Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(immutable-arraybuffer): update to recent spec #2688

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 53 additions & 9 deletions packages/immutable-arraybuffer/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/* global globalThis */

const { setPrototypeOf, getOwnPropertyDescriptor } = Object;
const { setPrototypeOf, getOwnPropertyDescriptors } = Object;
const { apply } = Reflect;
const { prototype: arrayBufferPrototype } = ArrayBuffer;

const {
slice,
// @ts-expect-error At the time of this writing, the `ArrayBuffer` type built
// TODO used to be a-ts-expect-error, but my local IDE's TS server
// seems to use a more recent definition of the `ArrayBuffer` type.
// @ts-ignore At the time of this writing, the `ArrayBuffer` type built
// into TypeScript does not know about the recent standard `transfer` method.
// Indeed, the `transfer` method is absent from Node <= 20.
transfer,
Expand Down Expand Up @@ -99,8 +101,13 @@ class ImmutableArrayBufferInternal {
return true;
}

slice(begin = undefined, end = undefined) {
return arrayBufferSlice(this.#buffer, begin, end);
slice(start = undefined, end = undefined) {
return arrayBufferSlice(this.#buffer, start, end);
}

sliceToImmutable(start = undefined, end = undefined) {
// eslint-disable-next-line no-use-before-define
return sliceBufferToImmutable(this.#buffer, start, end);
}

resize(_newByteLength = undefined) {
Expand Down Expand Up @@ -129,17 +136,36 @@ const immutableArrayBufferPrototype = ImmutableArrayBufferInternal.prototype;
delete immutableArrayBufferPrototype.constructor;

const {
// @ts-expect-error We know it is there.
get: isImmutableGetter,
} = getOwnPropertyDescriptor(immutableArrayBufferPrototype, 'immutable');
slice: { value: sliceOfImmutable },
immutable: { get: isImmutableGetter },
} = getOwnPropertyDescriptors(immutableArrayBufferPrototype);

setPrototypeOf(immutableArrayBufferPrototype, arrayBufferPrototype);

export const transferBufferToImmutable = buffer =>
new ImmutableArrayBufferInternal(buffer);
export const transferBufferToImmutable = (buffer, newLength = undefined) => {
if (newLength !== undefined) {
if (transfer) {
buffer = apply(transfer, buffer, [newLength]);
} else {
buffer = arrayBufferTransfer(buffer);
const oldLength = buffer.byteLength;
// eslint-disable-next-line @endo/restrict-comparison-operands
if (newLength <= oldLength) {
buffer = arrayBufferSlice(buffer, 0, newLength);
} else {
const oldTA = new Uint8Array(buffer);
const newTA = new Uint8Array(newLength);
newTA.set(oldTA);
buffer = newTA.buffer;
}
}
}
return new ImmutableArrayBufferInternal(buffer);
};

export const isBufferImmutable = buffer => {
try {
// @ts-expect-error Getter should be typed as this-sensitive
return apply(isImmutableGetter, buffer, []);
} catch (err) {
if (err instanceof TypeError) {
Expand All @@ -150,3 +176,21 @@ export const isBufferImmutable = buffer => {
throw err;
}
};

const sliceBuffer = (buffer, start = undefined, end = undefined) => {
try {
// @ts-expect-error We know it is really there
return apply(sliceOfImmutable, buffer, [start, end]);
} catch (err) {
if (err instanceof TypeError) {
return arrayBufferSlice(buffer, start, end);
}
throw err;
}
};

export const sliceBufferToImmutable = (
buffer,
start = undefined,
end = undefined,
) => transferBufferToImmutable(sliceBuffer(buffer, start, end));
13 changes: 10 additions & 3 deletions packages/immutable-arraybuffer/shim.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { transferBufferToImmutable, isBufferImmutable } from './index.js';
import {
transferBufferToImmutable,
isBufferImmutable,
sliceBufferToImmutable,
} from './index.js';

const { getOwnPropertyDescriptors, defineProperties } = Object;
const { prototype: arrayBufferPrototype } = ArrayBuffer;

const arrayBufferMethods = {
transferToImmutable() {
return transferBufferToImmutable(this);
transferToImmutable(newLength = undefined) {
return transferBufferToImmutable(this, newLength);
},
sliceToImmutable(start = undefined, end = undefined) {
return sliceBufferToImmutable(this, start, end);
},
get immutable() {
return isBufferImmutable(this);
Expand Down
82 changes: 81 additions & 1 deletion packages/immutable-arraybuffer/test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import test from 'ava';
import {
transferBufferToImmutable,
// isBufferImmutable,
isBufferImmutable,
sliceBufferToImmutable,
} from '../index.js';

const { isFrozen, getPrototypeOf } = Object;
Expand Down Expand Up @@ -124,3 +125,82 @@ test('TypedArray on Immutable ArrayBuffer ponyfill limitations', t => {
const ta3 = new Uint8Array(iab);
t.is(ta3.byteLength, 0);
});

const testTransfer = t => {
const ta12 = new Uint8Array([3, 4, 5]);
const ab12 = ta12.buffer;
t.is(ab12.byteLength, 3);
t.deepEqual([...ta12], [3, 4, 5]);

const ab2 = ab12.transfer(5);
t.false(isBufferImmutable(ab2));
t.is(ab2.byteLength, 5);
t.is(ab12.byteLength, 0);
const ta2 = new Uint8Array(ab2);
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);

const ta13 = new Uint8Array([3, 4, 5]);
const ab13 = ta13.buffer;

const ab3 = ab13.transfer(2);
t.false(isBufferImmutable(ab3));
t.is(ab3.byteLength, 2);
t.is(ab13.byteLength, 0);
const ta3 = new Uint8Array(ab3);
t.deepEqual([...ta3], [3, 4]);
};

{
// `transfer` is absent in Node <= 20. Present in Node >= 22
const maybeTest = 'transfer' in ArrayBuffer.prototype ? test : test.skip;
maybeTest('Standard buf.transfer(newLength) behavior baseline', testTransfer);
}

test('Analogous transferBufferToImmutable(buf, newLength) ponyfill', t => {
const ta12 = new Uint8Array([3, 4, 5]);
const ab12 = ta12.buffer;
t.is(ab12.byteLength, 3);
t.deepEqual([...ta12], [3, 4, 5]);

const ab2 = transferBufferToImmutable(ab12, 5);
t.true(isBufferImmutable(ab2));
t.is(ab2.byteLength, 5);
t.is(ab12.byteLength, 0);
// slice needed due to ponyfill limitations.
const ta2 = new Uint8Array(ab2.slice());
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);

const ta13 = new Uint8Array([3, 4, 5]);
const ab13 = ta13.buffer;

const ab3 = transferBufferToImmutable(ab13, 2);
t.true(isBufferImmutable(ab3));
t.is(ab3.byteLength, 2);
t.is(ab13.byteLength, 0);
// slice needed due to ponyfill limitations.
const ta3 = new Uint8Array(ab3.slice());
t.deepEqual([...ta3], [3, 4]);
});

test('sliceBufferToImmutable ponyfill', t => {
const ta12 = new Uint8Array([3, 4, 5]);
const ab12 = ta12.buffer;
t.is(ab12.byteLength, 3);
t.deepEqual([...ta12], [3, 4, 5]);

const ab2 = sliceBufferToImmutable(ab12, 1, 5);
t.true(isBufferImmutable(ab2));
t.is(ab2.byteLength, 2);
t.is(ab12.byteLength, 3);
// slice needed due to ponyfill limitations.
const ta2 = new Uint8Array(ab2.slice());
t.deepEqual([...ta2], [4, 5]);

const ab3 = sliceBufferToImmutable(ab2, 1, 2);
t.true(isBufferImmutable(ab3));
t.is(ab3.byteLength, 1);
t.is(ab2.byteLength, 2);
// slice needed due to ponyfill limitations.
const ta3 = new Uint8Array(ab3.slice());
t.deepEqual([...ta3], [5]);
});
79 changes: 79 additions & 0 deletions packages/immutable-arraybuffer/test/shim.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,82 @@ test('TypedArray on Immutable ArrayBuffer shim limitations', t => {
const ta3 = new Uint8Array(iab);
t.is(ta3.byteLength, 0);
});

const testTransfer = t => {
const ta12 = new Uint8Array([3, 4, 5]);
const ab12 = ta12.buffer;
t.is(ab12.byteLength, 3);
t.deepEqual([...ta12], [3, 4, 5]);

const ab2 = ab12.transfer(5);
t.false(ab2.immutable);
t.is(ab2.byteLength, 5);
t.is(ab12.byteLength, 0);
const ta2 = new Uint8Array(ab2);
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);

const ta13 = new Uint8Array([3, 4, 5]);
const ab13 = ta13.buffer;

const ab3 = ab13.transfer(2);
t.false(ab3.immutable);
t.is(ab3.byteLength, 2);
t.is(ab13.byteLength, 0);
const ta3 = new Uint8Array(ab3);
t.deepEqual([...ta3], [3, 4]);
};

{
// `transfer` is absent in Node <= 20. Present in Node >= 22
const maybeTest = 'transfer' in ArrayBuffer.prototype ? test : test.skip;
maybeTest('Standard buf.transfer(newLength) behavior baseline', testTransfer);
}

test('Analogous buf.transferToImmutable(newLength) shim', t => {
const ta12 = new Uint8Array([3, 4, 5]);
const ab12 = ta12.buffer;
t.is(ab12.byteLength, 3);
t.deepEqual([...ta12], [3, 4, 5]);

const ab2 = ab12.transferToImmutable(5);
t.true(ab2.immutable);
t.is(ab2.byteLength, 5);
t.is(ab12.byteLength, 0);
// slice needed due to ponyfill limitations.
const ta2 = new Uint8Array(ab2.slice());
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);

const ta13 = new Uint8Array([3, 4, 5]);
const ab13 = ta13.buffer;

const ab3 = ab13.transferToImmutable(2);
t.true(ab3.immutable);
t.is(ab3.byteLength, 2);
t.is(ab13.byteLength, 0);
// slice needed due to ponyfill limitations.
const ta3 = new Uint8Array(ab3.slice());
t.deepEqual([...ta3], [3, 4]);
});

test('sliceToImmutable shim', t => {
const ta12 = new Uint8Array([3, 4, 5]);
const ab12 = ta12.buffer;
t.is(ab12.byteLength, 3);
t.deepEqual([...ta12], [3, 4, 5]);

const ab2 = ab12.sliceToImmutable(1, 5);
t.true(ab2.immutable);
t.is(ab2.byteLength, 2);
t.is(ab12.byteLength, 3);
// slice needed due to ponyfill limitations.
const ta2 = new Uint8Array(ab2.slice());
t.deepEqual([...ta2], [4, 5]);

const ab3 = ab2.sliceToImmutable(1, 2);
t.true(ab3.immutable);
t.is(ab3.byteLength, 1);
t.is(ab2.byteLength, 2);
// slice needed due to ponyfill limitations.
const ta3 = new Uint8Array(ab3.slice());
t.deepEqual([...ta3], [5]);
});
Loading