function getUint8ArrayFromMemory(memory: WebAssembly.Memory, ptr: number, len: number): Uint8Array { ptr = ptr >>> 0; return new Uint8Array(memory.buffer).subarray(ptr, ptr + len); } function getStringFromMemory(memory: WebAssembly.Memory, ptr: number, len: number): string { const shared = getUint8ArrayFromMemory(memory, ptr, len); const copy = new Uint8Array(shared.length); copy.set(shared); const decoder = new TextDecoder('utf-8'); return decoder.decode(copy); } interface BrowserImports { is_web_worker(): boolean; lookup_file(ptr: number, len: number): number; read(handle: number, ptr: number, len: number, offset: number): number; write(handle: number, ptr: number, len: number, offset: number): number; sync(handle: number): number; truncate(handle: number, len: number): number; size(handle: number): number; } function panic(name): never { throw new Error(`method ${name} must be invoked only from the main thread`); } const MainDummyImports: BrowserImports = { is_web_worker: function (): boolean { return false; }, lookup_file: function (ptr: number, len: number): number { panic("lookup_file") }, read: function (handle: number, ptr: number, len: number, offset: number): number { panic("read") }, write: function (handle: number, ptr: number, len: number, offset: number): number { panic("write") }, sync: function (handle: number): number { panic("sync") }, truncate: function (handle: number, len: number): number { panic("truncate") }, size: function (handle: number): number { panic("size") } }; function workerImports(opfs: OpfsDirectory, memory: WebAssembly.Memory): BrowserImports { return { is_web_worker: function (): boolean { return true; }, lookup_file: function (ptr: number, len: number): number { try { const handle = opfs.lookupFileHandle(getStringFromMemory(memory, ptr, len)); return handle == null ? -404 : handle; } catch (e) { return -1; } }, read: function (handle: number, ptr: number, len: number, offset: number): number { try { return opfs.read(handle, getUint8ArrayFromMemory(memory, ptr, len), offset); } catch (e) { return -1; } }, write: function (handle: number, ptr: number, len: number, offset: number): number { try { return opfs.write(handle, getUint8ArrayFromMemory(memory, ptr, len), offset) } catch (e) { return -1; } }, sync: function (handle: number): number { try { opfs.sync(handle); return 0; } catch (e) { return -1; } }, truncate: function (handle: number, len: number): number { try { opfs.truncate(handle, len); return 0; } catch (e) { return -1; } }, size: function (handle: number): number { try { return opfs.size(handle); } catch (e) { return -1; } } } } class OpfsDirectory { fileByPath: Map; fileByHandle: Map; fileHandleNo: number; constructor() { this.fileByPath = new Map(); this.fileByHandle = new Map(); this.fileHandleNo = 0; } async registerFile(path: string) { if (this.fileByPath.has(path)) { return; } const opfsRoot = await navigator.storage.getDirectory(); const opfsHandle = await opfsRoot.getFileHandle(path, { create: true }); const opfsSync = await opfsHandle.createSyncAccessHandle(); this.fileHandleNo += 1; this.fileByPath.set(path, { handle: this.fileHandleNo, sync: opfsSync }); this.fileByHandle.set(this.fileHandleNo, opfsSync); } async unregisterFile(path: string) { const file = this.fileByPath.get(path); if (file == null) { return; } this.fileByPath.delete(path); this.fileByHandle.delete(file.handle); file.sync.close(); } lookupFileHandle(path: string): number | null { try { const file = this.fileByPath.get(path); if (file == null) { return null; } return file.handle; } catch (e) { console.error('lookupFile', path, e); throw e; } } read(handle: number, buffer: Uint8Array, offset: number): number { try { const file = this.fileByHandle.get(handle); const result = file.read(buffer, { at: Number(offset) }); return result; } catch (e) { console.error('read', handle, buffer.length, offset, e); throw e; } } write(handle: number, buffer: Uint8Array, offset: number): number { try { const file = this.fileByHandle.get(handle); const result = file.write(buffer, { at: Number(offset) }); return result; } catch (e) { console.error('write', handle, buffer.length, offset, e); throw e; } } sync(handle: number) { try { const file = this.fileByHandle.get(handle); file.flush(); } catch (e) { console.error('sync', handle, e); throw e; } } truncate(handle: number, size: number) { try { const file = this.fileByHandle.get(handle); const result = file.truncate(size); return result; } catch (e) { console.error('truncate', handle, size, e); throw e; } } size(handle: number): number { try { const file = this.fileByHandle.get(handle); const size = file.getSize() return size; } catch (e) { console.error('size', handle, e); throw e; } } } var workerRequestId = 0; function waitForWorkerResponse(worker: Worker, id: number): Promise { let waitResolve, waitReject; const callback = msg => { if (msg.data.id == id) { if (msg.data.error != null) { waitReject(msg.data.error) } else { waitResolve() } cleanup(); } }; const cleanup = () => worker.removeEventListener("message", callback); worker.addEventListener("message", callback); const result = new Promise((resolve, reject) => { waitResolve = resolve; waitReject = reject; }); return result; } function registerFileAtWorker(worker: Worker, path: string): Promise { workerRequestId += 1; const currentId = workerRequestId; const promise = waitForWorkerResponse(worker, currentId); worker.postMessage({ __turso__: "register", path: path, id: currentId }); return promise; } function unregisterFileAtWorker(worker: Worker, path: string): Promise { workerRequestId += 1; const currentId = workerRequestId; const promise = waitForWorkerResponse(worker, currentId); worker.postMessage({ __turso__: "unregister", path: path, id: currentId }); return promise; } export { OpfsDirectory, workerImports, MainDummyImports, waitForWorkerResponse, registerFileAtWorker, unregisterFileAtWorker }