Spaces:
Sleeping
Sleeping
| // @ts-check | |
| /** | |
| * @param {unknown} value | |
| */ | |
| const getType = (value) => { | |
| if (value === undefined) return 0 | |
| if (value === null) return 1 | |
| if (isBuffer(value)) return 10 | |
| const t = typeof value | |
| if (t === 'boolean') return 2 | |
| if (t === 'number') return 3 | |
| if (t === 'string') return 4 | |
| if (t === 'object') return 6 | |
| if (t === 'bigint') return 9 | |
| return -1 | |
| } | |
| /** | |
| * @param {unknown} value | |
| * @returns {value is Uint8Array} | |
| */ | |
| const isBuffer = (value) => { | |
| const BufferCtor = globalThis.Buffer | |
| if (typeof BufferCtor?.isBuffer === 'function') { | |
| return BufferCtor.isBuffer(value) | |
| } | |
| return value instanceof Uint8Array && value.constructor?.name === 'Buffer' | |
| } | |
| const RESPONSE_HEADER_SIZE = 16 | |
| const RESPONSE_PAYLOAD_SIZE = 10240 | |
| const RESPONSE_BUFFER_SIZE = RESPONSE_HEADER_SIZE + RESPONSE_PAYLOAD_SIZE | |
| /** | |
| * @param {Int32Array} sab | |
| * @param {Uint8Array} payload | |
| */ | |
| const writeResponsePayload = (sab, payload) => { | |
| if (payload.length > RESPONSE_PAYLOAD_SIZE) { | |
| throw new RangeError('payload overflow') | |
| } | |
| new Uint8Array(sab.buffer).set(payload, RESPONSE_HEADER_SIZE) | |
| } | |
| /** | |
| * @param {import('memfs').IFs} memfs | |
| * @param {any} value | |
| * @param {ReturnType<typeof getType>} type | |
| * @returns {Uint8Array} | |
| */ | |
| const encodeValue = (memfs, value, type) => { | |
| switch (type) { | |
| case 0: | |
| case 1: | |
| return new Uint8Array(0) | |
| case 2: { | |
| const view = new Int32Array(1) | |
| view[0] = value ? 1 : 0 | |
| return new Uint8Array(view.buffer) | |
| } | |
| case 3: { | |
| const view = new Float64Array(1) | |
| view[0] = value | |
| return new Uint8Array(view.buffer) | |
| } | |
| case 4: { | |
| const view = new TextEncoder().encode(value) | |
| return view | |
| } | |
| case 10: | |
| return new Uint8Array(value) | |
| case 6: { | |
| function storeConstructor(obj, memfs, processed = new WeakSet()) { | |
| if (!obj || typeof obj !== 'object') { | |
| return | |
| } | |
| if (processed.has(obj)) { | |
| return | |
| } | |
| processed.add(obj) | |
| const [entry] = | |
| Object.entries(memfs).filter(([_, v]) => v === obj.constructor)[0] ?? | |
| [] | |
| if (entry) { | |
| Object.defineProperty(obj, '__constructor__', { | |
| configurable: true, | |
| writable: true, | |
| enumerable: true, | |
| value: entry, | |
| }) | |
| } | |
| for (const value of Object.values(obj)) { | |
| storeConstructor(value, memfs, processed) | |
| } | |
| } | |
| storeConstructor(value, memfs) | |
| const json = JSON.stringify(value, (_, value) => { | |
| if (typeof value === 'bigint') { | |
| return `BigInt(${String(value)})` | |
| } | |
| if (value instanceof Error) { | |
| return { | |
| ...value, | |
| message: value.message, | |
| stack: value.stack, | |
| __error__: value.constructor.name, | |
| } | |
| } | |
| return value | |
| }) | |
| const view = new TextEncoder().encode(json) | |
| return view | |
| } | |
| case 9: { | |
| return new TextEncoder().encode(String(value)) | |
| } | |
| case -1: | |
| default: | |
| throw new Error('unsupported data') | |
| } | |
| } | |
| /** | |
| * @param {typeof import('memfs')} memfs | |
| * @param {Uint8Array} payload | |
| * @param {number} type | |
| * @returns {any} | |
| */ | |
| const decodeValue = (memfs, payload, type) => { | |
| if (type === 0) return undefined | |
| if (type === 1) return null | |
| if (type === 2) | |
| return Boolean(new Int32Array(payload.buffer, payload.byteOffset, 1)[0]) | |
| if (type === 3) | |
| return new Float64Array(payload.buffer, payload.byteOffset, 1)[0] | |
| if (type === 4) return new TextDecoder().decode(payload.slice()) | |
| if (type === 10) { | |
| const BufferCtor = globalThis.Buffer | |
| if (typeof BufferCtor?.from === 'function') { | |
| return BufferCtor.from(payload) | |
| } | |
| return payload.slice() | |
| } | |
| if (type === 6) { | |
| const obj = JSON.parse( | |
| new TextDecoder().decode(payload.slice()), | |
| (_key, value) => { | |
| if (typeof value === 'string') { | |
| const matched = value.match(/^BigInt\((-?\d+)\)$/) | |
| if (matched && matched[1]) { | |
| return BigInt(matched[1]) | |
| } | |
| } | |
| return value | |
| }, | |
| ) | |
| function loadConstructor(obj, memfs, processed = new WeakSet()) { | |
| if (!obj || typeof obj !== 'object') { | |
| return | |
| } | |
| if (processed.has(obj)) { | |
| return | |
| } | |
| processed.add(obj) | |
| if (obj.__constructor__) { | |
| const ctor = obj.__constructor__ | |
| delete obj.__constructor__ | |
| Object.setPrototypeOf(obj, memfs[ctor].prototype) | |
| } | |
| for (const value of Object.values(obj)) { | |
| loadConstructor(value, memfs, processed) | |
| } | |
| } | |
| loadConstructor(obj, memfs) | |
| if (obj.__error__) { | |
| const name = obj.__error__ | |
| const ErrorConstructor = globalThis[name] || Error | |
| delete obj.__error__ | |
| const err = new ErrorConstructor(obj.message) | |
| Object.defineProperty(err, 'stack', { | |
| configurable: true, | |
| enumerable: false, | |
| writable: true, | |
| value: err.stack, | |
| }) | |
| Object.defineProperty(err, Symbol.toStringTag, { | |
| configurable: true, | |
| enumerable: false, | |
| writable: true, | |
| value: name, | |
| }) | |
| for (const [k, v] of Object.entries(obj)) { | |
| if (k === 'message' || k === 'stack') continue | |
| err[k] = v | |
| } | |
| return err | |
| } | |
| return obj | |
| } | |
| if (type === 9) { | |
| return BigInt(new TextDecoder().decode(payload.slice())) | |
| } | |
| throw new Error('unsupported data') | |
| } | |
| /** | |
| * @param {import('memfs').IFs} fs | |
| * @returns {(e: { data: { __fs__: { sab: Int32Array, type: keyof import('memfs').IFs, payload: any[] } } }) => void} | |
| */ | |
| // oxlint-disable-next-line no-unused-vars -- fixed in an upcoming release | |
| export const createOnMessage = (fs) => | |
| function onMessage(e) { | |
| if (e.data.__fs__) { | |
| /** | |
| * 0..4 status(int32_t): 21(waiting) 0(success) 1(error) | |
| * 5..8 type(napi_valuetype): 0(undefined) 1(null) 2(boolean) 3(number) 4(string) 6(jsonstring) 9(bigint) 10(buffer) -1(unsupported) | |
| * 9..16 payload_size(uint32_t) <= 10240 | |
| * 16..16 + payload_size payload_content | |
| */ | |
| const { sab, type, payload } = e.data.__fs__ | |
| const fn = fs[type] | |
| try { | |
| const ret = fn.apply(fs, payload) | |
| const t = getType(ret) | |
| Atomics.store(sab, 1, t) | |
| const v = encodeValue(fs, ret, t) | |
| Atomics.store(sab, 2, v.length) | |
| writeResponsePayload(sab, v) | |
| Atomics.store(sab, 0, 0) // success | |
| } catch (/** @type {any} */ err) { | |
| let t = getType(err) | |
| let v | |
| try { | |
| if (t === -1) { | |
| throw new Error('unsupported data') | |
| } | |
| v = encodeValue(fs, err, t) | |
| } catch { | |
| const fallback = (() => { | |
| try { | |
| return String(err) | |
| } catch { | |
| return 'Unserializable thrown value' | |
| } | |
| })() | |
| t = getType(fallback) | |
| v = encodeValue(fs, fallback, t) | |
| } | |
| if (v.length > RESPONSE_PAYLOAD_SIZE) { | |
| const overflowErr = new RangeError('payload overflow') | |
| t = getType(overflowErr) | |
| v = encodeValue(fs, overflowErr, t) | |
| } | |
| Atomics.store(sab, 1, t) | |
| Atomics.store(sab, 2, v.length) | |
| writeResponsePayload(sab, v) | |
| Atomics.store(sab, 0, 1) // error | |
| } finally { | |
| Atomics.notify(sab, 0) | |
| } | |
| } | |
| } | |
| /** | |
| * @param {typeof import('memfs')} memfs | |
| */ | |
| export const createFsProxy = (memfs) => | |
| new Proxy( | |
| {}, | |
| { | |
| get(_target, p, _receiver) { | |
| /** | |
| * @param {any[]} args | |
| */ | |
| return function (...args) { | |
| const sab = new SharedArrayBuffer(RESPONSE_BUFFER_SIZE) | |
| const i32arr = new Int32Array(sab) | |
| Atomics.store(i32arr, 0, 21) | |
| postMessage({ | |
| __fs__: { | |
| sab: i32arr, | |
| type: p, | |
| payload: args, | |
| }, | |
| }) | |
| Atomics.wait(i32arr, 0, 21) | |
| const status = Atomics.load(i32arr, 0) | |
| const type = Atomics.load(i32arr, 1) | |
| const size = Atomics.load(i32arr, 2) | |
| const content = new Uint8Array(sab, RESPONSE_HEADER_SIZE, size) | |
| const value = decodeValue(memfs, content, type) | |
| if (status === 1) { | |
| throw value | |
| } | |
| return value | |
| } | |
| }, | |
| }, | |
| ) | |