Skip to content

Commit

Permalink
fix(immutable-arraybuffer): update to recent spec
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Jan 13, 2025
1 parent e02b0f6 commit 16fbf27
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 13 deletions.
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
86 changes: 85 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,86 @@ test('TypedArray on Immutable ArrayBuffer ponyfill limitations', t => {
const ta3 = new Uint8Array(iab);
t.is(ta3.byteLength, 0);
});

test('Standard buf.transfer(newLength) behavior baseline', t => {
if (!('transfer' in ArrayBuffer.prototype)) {
// Will happen on Node <= 20
const msg =
'skip when platform does not yet implement ArrayBuffer.prototype.transfer';
t.pass(msg);
t.log(msg);
return;
}
// Will happen on Node >= 22
t.log('ArrayBuffer.prototype.transfer exists');
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]);
});

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]);
});
83 changes: 83 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,86 @@ test('TypedArray on Immutable ArrayBuffer shim limitations', t => {
const ta3 = new Uint8Array(iab);
t.is(ta3.byteLength, 0);
});

test('Standard buf.transfer(newLength) behavior baseline', t => {
if (!('transfer' in ArrayBuffer.prototype)) {
// Will happen on Node <= 20
const msg =
'skip when platform does not yet implement ArrayBuffer.prototype.transfer';
t.pass(msg);
t.log(msg);
return;
}
// Will happen on Node >= 22
t.log('ArrayBuffer.prototype.transfer exists');
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]);
});

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]);
});

0 comments on commit 16fbf27

Please sign in to comment.