@endo/immutable-arraybuffer
Advanced tools
Comparing version
@@ -1,4 +0,2 @@ | ||
export function transferBufferToImmutable(buffer: ArrayBuffer, newLength?: number): ArrayBuffer; | ||
export function isBufferImmutable(buffer: any): boolean; | ||
export function sliceBufferToImmutable(buffer: ArrayBuffer, start?: number, end?: number): ArrayBuffer; | ||
export * from "./src/immutable-arraybuffer-pony.js"; | ||
//# sourceMappingURL=index.d.ts.map |
235
index.js
@@ -1,234 +0,1 @@ | ||
/* global globalThis */ | ||
const { setPrototypeOf, getOwnPropertyDescriptors, defineProperties } = Object; | ||
const { apply } = Reflect; | ||
const { prototype: arrayBufferPrototype } = ArrayBuffer; | ||
// Capture structuredClone before it could be scuttled. | ||
const { structuredClone: originalStructuredCloneMaybe } = globalThis; | ||
const { | ||
slice, | ||
// 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, | ||
} = arrayBufferPrototype; | ||
/** | ||
* Enforces that `arrayBuffer` is a genuine `ArrayBuffer` exotic object. | ||
* | ||
* @param {ArrayBuffer} arrayBuffer | ||
* @param {number} [start] | ||
* @param {number} [end] | ||
* @returns {ArrayBuffer} | ||
*/ | ||
const arrayBufferSlice = (arrayBuffer, start = undefined, end = undefined) => | ||
apply(slice, arrayBuffer, [start, end]); | ||
/** | ||
* Enforces that `arrayBuffer` is a genuine `ArrayBuffer` exotic object. | ||
* Return a new fresh `ArrayBuffer` exotic object, where the contents of the | ||
* original `arrayBuffer` has been moved into the new one, and the original | ||
* `arrayBuffer` has been detached. | ||
* | ||
* @param {ArrayBuffer} arrayBuffer | ||
* @returns {ArrayBuffer} | ||
*/ | ||
let arrayBufferTransfer; | ||
if (transfer) { | ||
arrayBufferTransfer = arrayBuffer => apply(transfer, arrayBuffer, []); | ||
} else if (originalStructuredCloneMaybe) { | ||
arrayBufferTransfer = arrayBuffer => { | ||
// Hopefully, a zero-length slice is cheap, but still enforces that | ||
// `arrayBuffer` is a genuine `ArrayBuffer` exotic object. | ||
arrayBufferSlice(arrayBuffer, 0, 0); | ||
return originalStructuredCloneMaybe(arrayBuffer, { | ||
transfer: [arrayBuffer], | ||
}); | ||
}; | ||
} else { | ||
// Indeed, Node <= 16 has neither. | ||
throw TypeError( | ||
`Can only emulate immutable ArrayBuffer on a platform with either "structuredClone" or "ArrayBuffer.prototype.transfer"`, | ||
); | ||
} | ||
/** | ||
* This class only exists as an artifact of this ponyfill and shim, | ||
* as a convenience for imperfectly emulating the | ||
* *Immutable ArrayBuffer* proposal, which would not have this class. | ||
* In the proposal, | ||
* `transferToImmutable` makes a new `ArrayBuffer` that inherits directly from | ||
* `ArrayBuffer.prototype` as you'd expect. In the ponyfill and shim, | ||
* `transferToImmutable` makes a normal object that inherits directly from | ||
* `immutableArrayBufferPrototype`, which has been surgically | ||
* altered to inherit directly from `ArrayBuffer.prototype`. The constructor is | ||
* captured for use internal to this module, and is made otherwise inaccessible. | ||
* Therefore, `immutableArrayBufferPrototype` and all its methods | ||
* and accessor functions effectively become hidden intrinsics. | ||
* They are not encapsulated. Rather, they are trivially discoverable if you | ||
* know how, but are not discoverable merely by enumerating naming paths. | ||
*/ | ||
class ImmutableArrayBufferInternal { | ||
/** @type {ArrayBuffer} */ | ||
#buffer; | ||
constructor(buffer) { | ||
// This constructor is deleted from the prototype below. | ||
this.#buffer = arrayBufferTransfer(buffer); | ||
} | ||
get byteLength() { | ||
return this.#buffer.byteLength; | ||
} | ||
get detached() { | ||
this.#buffer; // shim brand check | ||
return false; | ||
} | ||
get maxByteLength() { | ||
// Not underlying maxByteLength, which is irrelevant | ||
return this.#buffer.byteLength; | ||
} | ||
get resizable() { | ||
this.#buffer; // shim brand check | ||
return false; | ||
} | ||
get immutable() { | ||
this.#buffer; // shim brand check | ||
return true; | ||
} | ||
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) { | ||
this.#buffer; // shim brand check | ||
throw TypeError('Cannot resize an immutable ArrayBuffer'); | ||
} | ||
transfer(_newLength = undefined) { | ||
this.#buffer; // shim brand check | ||
throw TypeError('Cannot detach an immutable ArrayBuffer'); | ||
} | ||
transferToFixedLength(_newLength = undefined) { | ||
this.#buffer; // shim brand check | ||
throw TypeError('Cannot detach an immutable ArrayBuffer'); | ||
} | ||
transferToImmutable(_newLength = undefined) { | ||
this.#buffer; // shim brand check | ||
throw TypeError('Cannot detach an immutable ArrayBuffer'); | ||
} | ||
} | ||
const immutableArrayBufferPrototype = ImmutableArrayBufferInternal.prototype; | ||
// @ts-expect-error can only delete optionals | ||
delete immutableArrayBufferPrototype.constructor; | ||
const { | ||
slice: { value: sliceOfImmutable }, | ||
immutable: { get: isImmutableGetter }, | ||
} = getOwnPropertyDescriptors(immutableArrayBufferPrototype); | ||
setPrototypeOf(immutableArrayBufferPrototype, arrayBufferPrototype); | ||
// See https://github.com/endojs/endo/tree/master/packages/immutable-arraybuffer#purposeful-violation | ||
defineProperties(immutableArrayBufferPrototype, { | ||
[Symbol.toStringTag]: { | ||
value: 'ImmutableArrayBuffer', | ||
writable: false, | ||
enumerable: false, | ||
configurable: true, | ||
}, | ||
}); | ||
/** | ||
* Transfer the contents to a new Immutable ArrayBuffer | ||
* | ||
* @param {ArrayBuffer} buffer The original buffer. | ||
* @param {number} [newLength] The start index. | ||
* @returns {ArrayBuffer} | ||
*/ | ||
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; | ||
} | ||
} | ||
} | ||
const result = new ImmutableArrayBufferInternal(buffer); | ||
return /** @type {ArrayBuffer} */ (/** @type {unknown} */ (result)); | ||
}; | ||
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) { | ||
// Enforce that `buffer` is a genuine ArrayBuffer before returning. | ||
arrayBufferSlice(buffer, 0, 0); | ||
return false; | ||
} | ||
throw err; | ||
} | ||
}; | ||
/** | ||
* Enforces that `arrayBuffer` is a genuine `ArrayBuffer` exotic object. | ||
* | ||
* @param {ArrayBuffer} buffer | ||
* @param {number} [start] | ||
* @param {number} [end] | ||
* @returns {ArrayBuffer} | ||
*/ | ||
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; | ||
} | ||
}; | ||
/** | ||
* Creates an immutable slice of the given buffer. | ||
* | ||
* @param {ArrayBuffer} buffer The original buffer. | ||
* @param {number} [start] The start index. | ||
* @param {number} [end] The end index. | ||
* @returns {ArrayBuffer} The sliced immutable ArrayBuffer. | ||
*/ | ||
export const sliceBufferToImmutable = ( | ||
buffer, | ||
start = undefined, | ||
end = undefined, | ||
) => transferBufferToImmutable(sliceBuffer(buffer, start, end)); | ||
export * from './src/immutable-arraybuffer-pony.js'; |
{ | ||
"name": "@endo/immutable-arraybuffer", | ||
"version": "1.1.1", | ||
"version": "1.1.2", | ||
"description": "Immutable ArrayBuffer (the shim!)", | ||
@@ -28,3 +28,2 @@ "keywords": [ | ||
"./shim.js": "./shim.js", | ||
"./shim-hermes.js": "./shim-hermes.js", | ||
"./package.json": "./package.json" | ||
@@ -46,3 +45,9 @@ }, | ||
"ava": "^6.1.3", | ||
"babel-eslint": "^10.1.0", | ||
"c8": "^7.14.0", | ||
"eslint": "^8.57.1", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"eslint-plugin-eslint-comments": "^3.2.0", | ||
"eslint-plugin-import": "^2.31.0", | ||
"tsd": "^0.31.2", | ||
@@ -67,3 +72,3 @@ "typescript": "~5.8.3" | ||
"extends": [ | ||
"plugin:@endo/internal" | ||
"plugin:@endo/ses" | ||
] | ||
@@ -77,3 +82,3 @@ }, | ||
}, | ||
"gitHead": "03b92fc383da5d8bb4ea993b90149a0db5799d0b" | ||
"gitHead": "9815aea9541f241389d2135c6097a7442bdffa17" | ||
} |
59
shim.js
@@ -1,58 +0,1 @@ | ||
import { | ||
transferBufferToImmutable, | ||
isBufferImmutable, | ||
sliceBufferToImmutable, | ||
} from './index.js'; | ||
const { getOwnPropertyDescriptors, defineProperties } = Object; | ||
const { prototype: arrayBufferPrototype } = ArrayBuffer; | ||
const arrayBufferMethods = { | ||
/** | ||
* Transfer the contents to a new Immutable ArrayBuffer | ||
* | ||
* @this {ArrayBuffer} buffer The original buffer. | ||
* @param {number} [newLength] The start index. | ||
* @returns {ArrayBuffer} The sliced immutable ArrayBuffer. | ||
*/ | ||
transferToImmutable(newLength = undefined) { | ||
return transferBufferToImmutable(this, newLength); | ||
}, | ||
/** | ||
* Creates an immutable slice of the given buffer. | ||
* | ||
* @this {ArrayBuffer} buffer The original buffer. | ||
* @param {number} [start] The start index. | ||
* @param {number} [end] The end index. | ||
* @returns {ArrayBuffer} The sliced immutable ArrayBuffer. | ||
*/ | ||
sliceToImmutable(start = undefined, end = undefined) { | ||
return sliceBufferToImmutable(this, start, end); | ||
}, | ||
get immutable() { | ||
return isBufferImmutable(this); | ||
}, | ||
}; | ||
if ('sliceToImmutable' in arrayBufferPrototype) { | ||
// Modern shim practice frowns on conditional installation, at least for | ||
// proposals prior to stage 3. This is so changes to the proposal since | ||
// an old shim was distributed don't need to worry about the proposal | ||
// breaking old code depending on the old shim. Thus, if we detect that | ||
// we're about to overwrite a prior installation, we simply issue this | ||
// warning and continue. | ||
// | ||
// TODO, if the primordials are frozen after the prior implementation, such as | ||
// by `lockdown`, then this precludes overwriting as expected. However, for | ||
// this case, the following warning text will be confusing. | ||
console.warn( | ||
'About to overwrite a prior implementation of "sliceToImmutable"', | ||
); | ||
} | ||
defineProperties( | ||
arrayBufferPrototype, | ||
getOwnPropertyDescriptors(arrayBufferMethods), | ||
); | ||
import './src/immutable-arraybuffer-shim.js'; |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
39926
-7.93%10
150%333
-21.09%1
Infinity%