import { ArrayPrototypePush as push, TypeError, WeakMap, WeakMapPrototypeGet as get, WeakMapPrototypeSet as set, } from "ext:core/primordials"; import { array } from "./idl.js"; interface TransferEntry { states: WeakMap; transferable: boolean; clone?: ((state: unknown, value: unknown) => object) | null; prepare?: (state: unknown, value: unknown) => unknown; validate?: (state: unknown, source: unknown, value?: unknown) => void; commit?: (state: unknown, source: unknown, value?: unknown) => void; rollback?: (state: unknown, source: unknown, value?: unknown) => void; } interface WireEntry { states?: WeakMap; name: string; transferable: boolean; accepts?: (value: unknown) => boolean; encode: (state: unknown, value: unknown, transfer?: unknown) => unknown; decode: (data: unknown) => object; prepare?: (state: unknown, value: unknown) => unknown; validate?: (state: unknown, source: unknown, value?: unknown) => void; commit?: (state: unknown, source: unknown, value?: unknown) => void; rollback?: (state: unknown, source: unknown, value?: unknown) => void; } type MarkedEntry = Omit & { states?: undefined }; interface PreparedTransfer { source: unknown; entry: TransferEntry | WireEntry | MarkedEntry; state: unknown; value: unknown; committed?: boolean; } interface WireCallbacks { name: string; encode: (state: State, value: object, prepared: Prepared) => Packet; decode: (data: NoInfer) => object; prepare?: (state: State, value: object) => Prepared; validate?: ( state: State, source: object, prepared: NoInfer, ) => void; commit?: (state: State, source: object, prepared: NoInfer) => void; rollback?: ( state: State, source: object, prepared: NoInfer, ) => void; } type WireRegistration = | ( & { states: WeakMap; accepts?: never } & WireCallbacks, Packet, Prepared> ) | ( & { states?: undefined; accepts: (value: unknown) => boolean } & WireCallbacks ); const types: TransferEntry[] = array(), marked = new WeakMap(), wireTypes: WireEntry[] = array(); export function registerWire< State = unknown, Packet = unknown, Prepared = undefined, >( entry: WireRegistration, ): void { push( wireTypes, { ...entry, transferable: !!entry.prepare } as unknown as WireEntry, ); } function wireType(value: unknown): WireEntry | undefined { // Newest first so specific types win (File over Blob). for (let i = wireTypes.length - 1; i >= 0; i--) { const entry = wireTypes[i]; if ( entry.states ? get(entry.states, value) !== undefined : entry.accepts!(value) ) return entry; } } export function decodeWire(packet: [string, unknown]): object { for (let i = 0; i < wireTypes.length; i++) { if (wireTypes[i].name === packet[0]) return wireTypes[i].decode(packet[1]); } throw new TypeError("Unknown serialized host object"); } let markedTransferable: MarkedEntry | undefined, structuredCloneImpl: | ((value: unknown, options?: unknown) => unknown) | undefined; export function setStructuredClone( callback: (value: unknown, options?: unknown) => unknown, ): void { structuredCloneImpl = callback; } export function cloneValue( value: unknown, options: unknown = undefined, ): unknown { if (structuredCloneImpl === undefined) { throw new TypeError("structuredClone is unavailable"); } return structuredCloneImpl(value, options); } export function registerSerializable( states: WeakMap, clone: (state: State, value: object) => object, ): void { push(types, { states, clone: clone as unknown as TransferEntry["clone"], transferable: false, } as unknown as TransferEntry); } export function registerUncloneable( states: WeakMap, transferable = false, ): void { push(types, { states, clone: null, transferable }); } export function registerTransferable( states: WeakMap, prepare: (state: State, value: object) => Prepared, validate: (state: State, source: object, value: Prepared) => void, commit: (state: State, source: object, value: Prepared) => void, ): void { push(types, { states, clone: null, transferable: true, prepare: prepare as unknown as TransferEntry["prepare"], validate: validate as unknown as TransferEntry["validate"], commit: commit as unknown as TransferEntry["commit"], } as unknown as TransferEntry); } export function registerMarkedTransferable( prepare: (state: unknown, value: object) => Prepared, validate: (state: unknown, source: object, value: Prepared) => void, commit: (state: unknown, source: object, value: Prepared) => void, ): void { markedTransferable = { transferable: true, prepare: prepare as unknown as TransferEntry["prepare"], validate: validate as unknown as TransferEntry["validate"], commit: commit as unknown as TransferEntry["commit"], } as unknown as MarkedEntry; } export function markUncloneable(value: object, transferable = false): void { set(marked, value, transferable ? 2 : 1); } function type(value: unknown): TransferEntry | undefined { // Derived interfaces register after their bases. for (let i = types.length - 1; i >= 0; i--) { if (get(types[i].states, value) !== undefined) return types[i]; } } export function classify(value: unknown): 0 | 1 | 2 { const kind = get(marked, value); if (kind) return kind; const entry = type(value); return entry ? (entry.transferable ? 2 : 1) : 0; } export function transferCallbacks( transfer: unknown[], dataCloneError: (message: string) => unknown, wire = false, ): { encodeHost(value: unknown): [name: string, data: unknown]; prepare(): void; validate(): void; commit(): void; cloneHost(value: unknown): object | undefined; clear(): void; } { let prepared: PreparedTransfer[] = array(); function find(value: unknown): PreparedTransfer | undefined { for (let i = 0; i < prepared.length; i++) { if (prepared[i].source === value) return prepared[i]; } } function prepare(): void { for (let i = 0; i < transfer.length; i++) { const value = transfer[i]; const wireEntry = wire ? wireType(value) : undefined; const entry = wireEntry?.prepare ? wireEntry : type(value) ?? (get(marked, value) === 2 ? markedTransferable : undefined); if (entry?.transferable && entry.prepare) { const state = entry.states ? get(entry.states, value) : undefined; push(prepared, { source: value, entry, state, value: entry.prepare(state, value), }); } else if (classify(value) === 2) { throw dataCloneError("not yet implemented: transferring this object"); } } } function validate(): void { for (let i = 0; i < prepared.length; i++) { const { source, entry, state, value } = prepared[i]; entry.validate!(state, source, value); } } function commit(): void { for (let i = 0; i < prepared.length; i++) { const { source, entry, state, value } = prepared[i]; entry.commit!(state, source, value); prepared[i].committed = true; } } function cloneHost(value: unknown): object | undefined { const record = find(value); if (record) return record.value as object; const entry = type(value); if (entry?.transferable || classify(value) === 2) { throw dataCloneError("Transferable value is not in the transfer list"); } return entry?.clone ? entry.clone(get(entry.states, value), value) : undefined; } function encodeHost(value: unknown): [string, unknown] { const record = find(value); if (!record && classify(value) === 2) { throw dataCloneError("Transferable value is not in the transfer list"); } const entry = wireType(value); if (!entry) { throw dataCloneError("This object cannot be sent between isolates"); } return [ entry.name, entry.encode( entry.states ? get(entry.states, value) : undefined, value, record?.value, ), ]; } return { encodeHost, prepare, validate, commit, cloneHost, clear() { for (let i = 0; i < prepared.length; i++) { const record = prepared[i]; if (!record.committed) { record.entry.rollback?.(record.state, record.source, record.value); } } prepared = array(); }, }; }