UNPKG

@zenfs/core

Version:

A filesystem, anywhere

191 lines (190 loc) 5.84 kB
import { info } from 'kerium/log'; import { resolveMountConfig } from '../config.js'; import { FileSystem } from '../internal/filesystem.js'; import { Inode } from '../internal/inode.js'; import * as RPC from '../internal/rpc.js'; import { Async } from '../mixins/async.js'; import '../polyfills.js'; import { InMemory } from './memory.js'; export { RPC }; /** * PortFS lets you access an FS instance that is running in a port, or the other way around. * * Note that *direct* synchronous operations are not permitted on the PortFS, * regardless of the configuration option of the remote FS. * @category Internals * @internal */ export class PortFS extends Async(FileSystem) { channel; timeout; port; /** * @hidden */ _sync = InMemory.create({ label: 'tmpfs:port' }); /** * Constructs a new PortFS instance that connects with the FS running on `options.port`. */ constructor(channel, timeout = 250) { super(0x706f7274, 'portfs'); this.channel = channel; this.timeout = timeout; this.port = RPC.from(channel); RPC.attach(this.port, RPC.handleResponse); } rpc(method, ...args) { return RPC.request({ method, args }, { port: this.port, timeout: this.timeout, fs: this, }); } async ready() { await this.rpc('ready'); await super.ready(); } rename(oldPath, newPath) { return this.rpc('rename', oldPath, newPath); } async stat(path) { const result = await this.rpc('stat', path); return result instanceof Inode ? result : new Inode(result); } async touch(path, metadata) { const inode = metadata instanceof Inode ? metadata : new Inode(metadata); await this.rpc('touch', path, new Uint8Array(inode.buffer, inode.byteOffset, inode.byteLength)); } async sync() { await super.sync(); await this.rpc('sync'); } async createFile(path, options) { if (options instanceof Inode) options = options.toJSON(); const result = await this.rpc('createFile', path, options); return result instanceof Inode ? result : new Inode(result); } unlink(path) { return this.rpc('unlink', path); } rmdir(path) { return this.rpc('rmdir', path); } async mkdir(path, options) { if (options instanceof Inode) options = options.toJSON(); const result = await this.rpc('mkdir', path, options); return result instanceof Inode ? result : new Inode(result); } readdir(path) { return this.rpc('readdir', path); } exists(path) { return this.rpc('exists', path); } link(srcpath, dstpath) { return this.rpc('link', srcpath, dstpath); } async read(path, buffer, start, end) { buffer.set(await this.rpc('read', path, buffer, start, end)); } write(path, buffer, offset) { return this.rpc('write', path, buffer, offset); } } export function attachFS(channel, fs) { const port = RPC.from(channel); RPC.attach(port, request => RPC.handleRequest(port, fs, request)); } export function detachFS(channel, fs) { const port = RPC.from(channel); RPC.detach(port, request => RPC.handleRequest(port, fs, request)); } const _Port = { name: 'Port', options: { port: { type: [ EventTarget, function EventEmitter(e) { return typeof e == 'object' && 'on' in e; }, ], required: true, }, timeout: { type: 'number', required: false }, }, create(opt) { return new PortFS(opt.port, opt.timeout); }, }; /** * A backend for usage with ports and workers. See the examples below. * * #### Accessing an FS on a remote Worker from the main thread * * Main: * * ```ts * import { configure } from '@zenfs/core'; * import { Port } from '@zenfs/port'; * import { Worker } from 'node:worker_threads'; * * const worker = new Worker('worker.js'); * * await configure({ * mounts: { * '/worker': { * backend: Port, * port: worker, * }, * }, * }); * ``` * * Worker: * * ```ts * import { InMemory, resolveRemoteMount, attachFS } from '@zenfs/core'; * import { parentPort } from 'node:worker_threads'; * * await resolveRemoteMount(parentPort, { backend: InMemory, name: 'tmp' }); * ``` * * If you are using using web workers, you would use `self` instead of importing `parentPort` in the worker, and would not need to import `Worker` in the main thread. * * #### Using with multiple ports on the same thread * * ```ts * import { InMemory, fs, resolveMountConfig, resolveRemoteMount, Port } from '@zenfs/core'; * import { MessageChannel } from 'node:worker_threads'; * * const { port1: localPort, port2: remotePort } = new MessageChannel(); * * fs.mount('/remote', await resolveRemoteMount(remotePort, { backend: InMemory, name: 'tmp' })); * fs.mount('/port', await resolveMountConfig({ backend: Port, port: localPort })); * * const content = 'FS is in a port'; * * await fs.promises.writeFile('/port/test', content); * * fs.readFileSync('/remote/test', 'utf8'); // FS is in a port * await fs.promises.readFile('/port/test', 'utf8'); // FS is in a port * ``` * * @category Backends and Configuration */ export const Port = _Port; /** * @category Backends and Configuration */ export async function resolveRemoteMount(channel, config, _depth = 0) { const port = RPC.from(channel); const stopAndReplay = RPC.catchMessages(port); const fs = await resolveMountConfig(config, _depth); attachFS(port, fs); await stopAndReplay(fs); info('Resolved remote mount: ' + fs.toString()); return fs; }