Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336// Structured clone as bytes, for the shared queues under node:worker_threads ports: what// postMessage clones, written into a SharedArrayBuffer so the receiver can read it// without its event loop (receiveMessageOnPort). The browser has no structured-clone-// to-bytes API, so this is V8's algorithm over the types a message can hold. What only// a native postMessage can carry (SharedArrayBuffers, ports, host objects) throws// NeedsNative and the caller sends the whole message natively instead. Unclonable values// throw Node's DataCloneError: a DOMException named DataCloneError, code 25.
export class NeedsNative extends Error {}
/** worker_threads.markAsUncloneable: checked by serialize() below, so a marked value * fails to go on the shared-queue path. A message with transferables rides a native * postMessage instead (ports.ts), which has no way to ask this set first — marking a * value only guards the common (no-transferables) send. */export const uncloneable = new WeakSet<object>();
export function dataCloneError(shown: string): DOMException { return new DOMException(`${shown} could not be cloned.`, "DataCloneError");}
const enum Tag { Undefined, Null, True, False, Number, BigInt, Utf8, Utf16, Ref, Array, Hole, Object, Date, RegExp, Map, Set, ArrayBuffer, View, Error, BoxedNumber, BoxedString, BoxedBoolean, BoxedBigInt,}
const VIEWS = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array, DataView,] as const;type ViewCtor = (typeof VIEWS)[number];
/** V8 keeps an error's name only when it is one of these; any other becomes "Error". */const ERRORS: Record<string, ErrorConstructor> = { Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError,};
const enc = new TextEncoder();const dec = new TextDecoder();
class Writer { bytes = new Uint8Array(256); view = new DataView(this.bytes.buffer); at = 0;
reserve(n: number): void { if (this.at + n <= this.bytes.length) return; let size = this.bytes.length * 2; while (size < this.at + n) size *= 2; const next = new Uint8Array(size); next.set(this.bytes.subarray(0, this.at)); this.bytes = next; this.view = new DataView(next.buffer); } u8(n: number): void { this.reserve(1); this.bytes[this.at++] = n; } u32(n: number): void { this.reserve(4); this.view.setUint32(this.at, n, true); this.at += 4; } f64(n: number): void { this.reserve(8); this.view.setFloat64(this.at, n, true); this.at += 8; } raw(b: Uint8Array): void { this.u32(b.length); this.reserve(b.length); this.bytes.set(b, this.at); this.at += b.length; } str(s: string): void { if (s.isWellFormed()) { this.u8(Tag.Utf8); this.raw(enc.encode(s)); return; } // Lone surrogates survive a clone, and UTF-8 cannot hold them. this.u8(Tag.Utf16); this.u32(s.length); this.reserve(s.length * 2); for (let i = 0; i < s.length; i++, this.at += 2) this.view.setUint16(this.at, s.charCodeAt(i), true); }}
export interface SerializeOptions { /** Keep an error's own enumerable properties (`code`), as Node does for a worker's * uncaught error; a postMessage clone keeps only name, message, stack and cause. */ errorProps?: boolean;}
export function serialize(value: unknown, options: SerializeOptions = {}): Uint8Array { const w = new Writer(); const ids = new Map<object, number>();
const write = (v: unknown): void => { switch (typeof v) { case "undefined": return w.u8(Tag.Undefined); case "boolean": return w.u8(v ? Tag.True : Tag.False); case "number": w.u8(Tag.Number); return w.f64(v); case "bigint": w.u8(Tag.BigInt); return w.str(v.toString()); case "string": return w.str(v); case "symbol": throw dataCloneError(v.toString()); case "function": throw dataCloneError(String(v)); } if (v === null) return w.u8(Tag.Null); const o = v as object; const seen = ids.get(o); if (seen !== undefined) { w.u8(Tag.Ref); return w.u32(seen); } ids.set(o, ids.size); writeObject(o); };
const writeObject = (o: object): void => { if (Array.isArray(o)) { w.u8(Tag.Array); w.u32(o.length); for (let i = 0; i < o.length; i++) { if (i in o) write(o[i]); else w.u8(Tag.Hole); } return; } if (o instanceof ArrayBuffer) { w.u8(Tag.ArrayBuffer); return w.raw(new Uint8Array(o)); } if (ArrayBuffer.isView(o)) { if (!(o.buffer instanceof ArrayBuffer)) throw new NeedsNative(); const kind = VIEWS.findIndex((C) => o instanceof C); w.u8(Tag.View); w.u8(kind); return w.raw(new Uint8Array(o.buffer, o.byteOffset, o.byteLength)); } if (o instanceof Date) { w.u8(Tag.Date); return w.f64(o.getTime()); } if (o instanceof RegExp) { w.u8(Tag.RegExp); w.str(o.source); return w.str(o.flags); } if (o instanceof Map) { w.u8(Tag.Map); w.u32(o.size); for (const [k, val] of o) { write(k); write(val); } return; } if (o instanceof Set) { w.u8(Tag.Set); w.u32(o.size); for (const val of o) write(val); return; } if (o instanceof Error) { const e = o as Error & Record<string, unknown>; w.u8(Tag.Error); w.str(Object.hasOwn(ERRORS, e.name) ? e.name : "Error"); write(Object.hasOwn(e, "message") ? String(e.message) : undefined); write(typeof e.stack === "string" ? e.stack : undefined); write(Object.hasOwn(e, "cause") ? [e.cause] : undefined); const keys = options.errorProps ? Object.keys(e).filter((k) => k !== "message" && k !== "cause") : []; w.u32(keys.length); for (const k of keys) { w.str(k); write(e[k]); } return; } const tag = Object.prototype.toString.call(o); switch (tag) { case "[object Number]": w.u8(Tag.BoxedNumber); return w.f64(Number.prototype.valueOf.call(o)); case "[object String]": w.u8(Tag.BoxedString); return w.str(String.prototype.valueOf.call(o)); case "[object Boolean]": return w.u8(Tag.BoxedBoolean), w.u8(Boolean.prototype.valueOf.call(o) ? 1 : 0); case "[object BigInt]": w.u8(Tag.BoxedBigInt); return w.str(BigInt.prototype.valueOf.call(o).toString()); case "[object Promise]": case "[object WeakMap]": case "[object WeakSet]": case "[object WeakRef]": throw dataCloneError(`#<${tag.slice(8, -1)}>`); case "[object Object]": case "[object Arguments]": break; default: // A class instance with its own toStringTag is still an ordinary object to V8. if (!isOrdinary(o)) throw new NeedsNative(); } if (isNodePortLike(o)) throw new NeedsNative(); // markAsUncloneable only reaches a plain object this way — Node's own check is // on this same path, after Array/Map/Date/etc. have already returned above. if (uncloneable.has(o)) throw new DOMException("Cannot clone object of unsupported type.", "DataCloneError"); const keys = Object.keys(o); w.u8(Tag.Object); w.u32(keys.length); for (const k of keys) { w.str(k); write((o as Record<string, unknown>)[k]); } };
write(value); return w.bytes.slice(0, w.at);}
/** Objects the guest made, as opposed to host objects (Blob, MessagePort, ...). */function isOrdinary(o: object): boolean { for (let p = Object.getPrototypeOf(o); p !== null; p = Object.getPrototypeOf(p)) { if (p === Object.prototype) return true; const ctor = Object.getOwnPropertyDescriptor(p, "constructor")?.value; if (typeof ctor === "function" && /\{\s*\[native code\]\s*\}$/.test(Function.prototype.toString.call(ctor))) return false; } return true;}
/** Marks node:worker_threads ports (src/runtime/ports.ts), which only travel transferred. */export const kTransferOnly = Symbol("nib.transferOnly");function isNodePortLike(o: object): boolean { return kTransferOnly in o;}
export function deserialize(bytes: Uint8Array): unknown { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); let at = 0; const refs: unknown[] = [];
const u8 = (): number => bytes[at++]!; const u32 = (): number => { const n = view.getUint32(at, true); at += 4; return n; }; const f64 = (): number => { const n = view.getFloat64(at, true); at += 8; return n; }; const raw = (): Uint8Array => { const n = u32(); const out = bytes.slice(at, at + n); at += n; return out; }; const str = (): string => { const tag = u8(); if (tag === Tag.Utf8) return dec.decode(raw()); const n = u32(); let s = ""; for (let i = 0; i < n; i++, at += 2) s += String.fromCharCode(view.getUint16(at, true)); return s; }; const keep = <T>(v: T): T => { refs.push(v); return v; };
const read = (): unknown => { const tag = u8(); switch (tag) { case Tag.Undefined: return undefined; case Tag.Null: return null; case Tag.True: return true; case Tag.False: return false; case Tag.Number: return f64(); case Tag.BigInt: return BigInt(str()); case Tag.Utf8: case Tag.Utf16: at--; return str(); case Tag.Ref: return refs[u32()]; case Tag.Array: { const out = keep(new Array(u32())); for (let i = 0; i < out.length; i++) { if (bytes[at] === Tag.Hole) at++; else out[i] = read(); } return out; } case Tag.Object: { const out = keep({} as Record<string, unknown>); for (let n = u32(); n > 0; n--) { const k = str(); out[k] = read(); } return out; } case Tag.Date: return keep(new Date(f64())); case Tag.RegExp: { const source = str(); return keep(new RegExp(source, str())); } case Tag.Map: { const out = keep(new Map()); for (let n = u32(); n > 0; n--) { const k = read(); out.set(k, read()); } return out; } case Tag.Set: { const out = keep(new Set()); for (let n = u32(); n > 0; n--) out.add(read()); return out; } case Tag.ArrayBuffer: return keep(raw().buffer); case Tag.View: { const C: ViewCtor = VIEWS[u8()]!; const b = raw(); const elem = C === DataView ? 1 : (C as unknown as { BYTES_PER_ELEMENT: number }).BYTES_PER_ELEMENT; return keep(C === DataView ? new DataView(b.buffer) : new (C as Uint8ArrayConstructor)(b.buffer, 0, b.length / elem)); } case Tag.Error: { const Ctor = ERRORS[str()]!; const e = keep(Object.create(Ctor.prototype) as Error & Record<string, unknown>); const message = read(); if (message !== undefined) Object.defineProperty(e, "message", { value: message, writable: true, configurable: true }); const stack = read(); if (stack !== undefined) Object.defineProperty(e, "stack", { value: stack, writable: true, configurable: true }); const cause = read() as [unknown] | undefined; if (cause) Object.defineProperty(e, "cause", { value: cause[0], writable: true, configurable: true }); for (let n = u32(); n > 0; n--) { const k = str(); e[k] = read(); } return e; } case Tag.BoxedNumber: return keep(Object(f64())); case Tag.BoxedString: return keep(Object(str())); case Tag.BoxedBoolean: return keep(Object(u8() === 1)); case Tag.BoxedBigInt: return keep(Object(BigInt(str()))); default: throw new Error(`nib clone: bad tag ${tag}`); } }; return read();}