Skip to content

transfer()

transfer(value) opts a clonable Transferable into move semantics: the value is added to the transfer list instead of being cloned, so ownership transfers to the peer and the value is detached locally.

const transfer: <T>(value: T) => T

transfer() is idempotent, and non-transferable inputs pass through.

Clonable Transferables (ArrayBuffer, typed-array views, ImageBitmap, VideoFrame, AudioData, …) are copied by structured clone by default. Wrapping one with transfer() moves it instead: the peer receives the original, and your local value is detached.

import {
const transfer: <T>(value: T) => T

Opt into transfer (move) semantics for a transferable value. Idempotent; non-transferable inputs pass through unchanged. Silently degrades to a copy when the platform/transport can't transfer the given type. Lies at the type level - runtime value is a TransferWrapper typed as T.

transfer
} from 'osra'
const
const buffer: ArrayBuffer
buffer
= new
var ArrayBuffer: ArrayBufferConstructor
new (byteLength: number, options?: {
maxByteLength?: number;
}) => ArrayBuffer (+2 overloads)
ArrayBuffer
(1_000_000)
await
const remote: {
process: (buffer: ArrayBuffer) => Promise<void>;
}
remote
.
process: (buffer: ArrayBuffer) => Promise<void>
process
(
transfer<ArrayBuffer>(value: ArrayBuffer): ArrayBuffer

Opt into transfer (move) semantics for a transferable value. Idempotent; non-transferable inputs pass through unchanged. Silently degrades to a copy when the platform/transport can't transfer the given type. Lies at the type level - runtime value is a TransferWrapper typed as T.

transfer
(
const buffer: ArrayBuffer
buffer
)) // buffer is detached locally

Moving large buffers avoids the copy entirely — see performance for when this matters.

On JSON transports there is no transfer list, so transfer() silently degrades to a copy.

Types that structured clone cannot copy are always moved, with or without transfer():

  • MessagePort
  • TransformStream
  • OffscreenCanvas
  • MediaSourceHandle
  • MediaStreamTrack
  • MIDIAccess
  • RTCDataChannel
  • WebTransport streams

A bare send still detaches them locally; transfer() adds nothing there.

ReadableStream and WritableStream are never transferred natively: their revivable modules claim them ahead of the transfer machinery and proxy them over a routed channel, chunk by chunk, on clone and JSON transports alike. Sending locks the source (a reader/writer is acquired) rather than detaching it, and transfer() is a no-op on them — see performance for the implications on large binary data. TransformStream has no proxy module, so it rides native structured-clone transfer (clone transports only).