@zenfs/core
Version:
A filesystem, anywhere
191 lines (190 loc) • 5.84 kB
JavaScript
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;
}