UNPKG

@ezdevlol/memfs

Version:

In-memory file-system with Node's fs API.

806 lines (805 loc) 33.1 kB
import * as optHelpers from '../node/options'; import * as util from '../node/util'; import { Buffer } from '../internal/buffer'; import { FsPromises } from '../node/FsPromises'; import { pathToLocation, testDirectoryIsWritable } from './util'; import { ERRSTR } from '../node/constants'; import { strToEncoding } from '../encoding'; import { FsaNodeDirent } from './FsaNodeDirent'; import { constants } from '../constants'; import { FsaNodeStats } from './FsaNodeStats'; import queueMicrotask from '../queueMicrotask'; import { FsaNodeWriteStream } from './FsaNodeWriteStream'; import { FsaNodeReadStream } from './FsaNodeReadStream'; import { FsaNodeCore } from './FsaNodeCore'; import { FileHandle } from '../node/FileHandle'; const notSupported = () => { throw new Error('Method not supported by the File System Access API.'); }; const notImplemented = () => { throw new Error('Not implemented'); }; const noop = () => { }; /** * Constructs a Node.js `fs` API from a File System Access API * [`FileSystemDirectoryHandle` object](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle). */ export class FsaNodeFs extends FsaNodeCore { // ------------------------------------------------------------ FsPromisesApi promises = new FsPromises(this, FileHandle); // ------------------------------------------------------------ FsCallbackApi open = (path, flags, a, b) => { let mode = a; let callback = b; if (typeof a === 'function') { mode = 438 /* MODE.DEFAULT */; callback = a; } mode = mode || 438 /* MODE.DEFAULT */; const modeNum = util.modeToNumber(mode); const filename = util.pathToFilename(path); const flagsNum = util.flagsToNumber(flags); this.__open(filename, flagsNum, modeNum).then(openFile => callback(null, openFile.fd), error => callback(error)); }; close = (fd, callback) => { util.validateFd(fd); this.__close(fd).then(() => callback(null), error => callback(error)); }; read = (fd, buffer, offset, length, position, callback) => { util.validateCallback(callback); // This `if` branch is from Node.js if (length === 0) { return queueMicrotask(() => { if (callback) callback(null, 0, buffer); }); } (async () => { const openFile = await this.getFileByFd(fd, 'read'); const file = await openFile.file.getFile(); const src = await file.arrayBuffer(); const slice = new Uint8Array(src, Number(position), Number(length)); const dest = new Uint8Array(buffer.buffer, buffer.byteOffset + offset, slice.length); dest.set(slice, 0); return slice.length; })().then(bytesWritten => callback(null, bytesWritten, buffer), error => callback(error)); }; readFile = (id, a, b) => { const [opts, callback] = optHelpers.optsAndCbGenerator(optHelpers.getReadFileOptions)(a, b); const flagsNum = util.flagsToNumber(opts.flag); (async () => { let fd = typeof id === 'number' ? id : -1; const originalFd = fd; try { if (fd === -1) { const filename = util.pathToFilename(id); fd = (await this.__open(filename, flagsNum, 0)).fd; } const handle = await this.__getFileById(fd, 'readFile'); const file = await handle.getFile(); const buffer = Buffer.from(await file.arrayBuffer()); return util.bufferToEncoding(buffer, opts.encoding); } finally { try { const idWasFd = typeof originalFd === 'number' && originalFd >= 0; if (idWasFd) await this.__close(originalFd); } catch { } } })() .then(data => callback(null, data)) .catch(error => callback(error)); }; write = (fd, a, b, c, d, e) => { const [, asStr, buf, offset, length, position, cb] = util.getWriteArgs(fd, a, b, c, d, e); (async () => { const openFile = await this.getFileByFd(fd, 'write'); const data = buf.subarray(offset, offset + length); await openFile.write(data, position); return length; })().then(bytesWritten => cb(null, bytesWritten, asStr ? a : buf), error => cb(error)); }; writev = (fd, buffers, a, b) => { util.validateFd(fd); let position = null; let callback; if (typeof a === 'function') { callback = a; } else { position = Number(a); callback = b; } util.validateCallback(callback); (async () => { const openFile = await this.getFileByFd(fd, 'writev'); const length = buffers.length; let bytesWritten = 0; for (let i = 0; i < length; i++) { const data = buffers[i]; await openFile.write(data, position); bytesWritten += data.byteLength; position = null; } return bytesWritten; })().then(bytesWritten => callback(null, bytesWritten, buffers), error => callback(error)); }; writeFile = (id, data, a, b) => { let options = a; let callback = b; if (typeof a === 'function') { options = optHelpers.writeFileDefaults; callback = a; } const cb = util.validateCallback(callback); const opts = optHelpers.getWriteFileOptions(options); const flagsNum = util.flagsToNumber(opts.flag); const modeNum = util.modeToNumber(opts.mode); const buf = util.dataToBuffer(data, opts.encoding); (async () => { let fd = typeof id === 'number' ? id : -1; const originalFd = fd; try { if (fd === -1) { const filename = util.pathToFilename(id); fd = (await this.__open(filename, flagsNum, modeNum)).fd; } const file = await this.__getFileById(fd, 'writeFile'); const writable = await file.createWritable({ keepExistingData: false }); await writable.write(buf); await writable.close(); } finally { try { const idWasFd = typeof originalFd === 'number' && originalFd >= 0; if (idWasFd) await this.__close(originalFd); } catch { } } })().then(() => cb(null), error => cb(error)); }; copyFile = (src, dest, a, b) => { const srcFilename = util.pathToFilename(src); const destFilename = util.pathToFilename(dest); let flags; let callback; if (typeof a === 'function') { flags = 0; callback = a; } else { flags = a; callback = b; } util.validateCallback(callback); const [oldFolder, oldName] = pathToLocation(srcFilename); const [newFolder, newName] = pathToLocation(destFilename); (async () => { const oldFile = await this.getFile(oldFolder, oldName, 'copyFile'); const newDir = await this.getDir(newFolder, false, 'copyFile'); const newFile = await newDir.getFileHandle(newName, { create: true }); const writable = await newFile.createWritable({ keepExistingData: false }); const oldData = await oldFile.getFile(); await writable.write(await oldData.arrayBuffer()); await writable.close(); })().then(() => callback(null), error => callback(error)); }; /** * @todo There is a proposal for native "self remove" operation. * @see https://github.com/whatwg/fs/blob/main/proposals/Remove.md */ unlink = (path, callback) => { const filename = util.pathToFilename(path); const [folder, name] = pathToLocation(filename); this.getDir(folder, false, 'unlink') .then(dir => dir.removeEntry(name)) .then(() => callback(null), error => { if (error && typeof error === 'object') { switch (error.name) { case 'NotFoundError': { callback(util.createError('ENOENT', 'unlink', filename)); return; } case 'InvalidModificationError': { callback(util.createError('EISDIR', 'unlink', filename)); return; } } } callback(error); }); }; realpath = (path, a, b) => { const [opts, callback] = optHelpers.getRealpathOptsAndCb(a, b); let pathFilename = util.pathToFilename(path); if (pathFilename[0] !== "/" /* FsaToNodeConstants.Separator */) pathFilename = "/" /* FsaToNodeConstants.Separator */ + pathFilename; callback(null, strToEncoding(pathFilename, opts.encoding)); }; stat = (path, a, b) => { const [{ bigint = false, throwIfNoEntry = true }, callback] = optHelpers.getStatOptsAndCb(a, b); const filename = util.pathToFilename(path); const [folder, name] = pathToLocation(filename); (async () => { const handle = await this.getFileOrDir(folder, name, 'stat'); return await this.getHandleStats(bigint, handle); })().then(stats => callback(null, stats), error => callback(error)); }; lstat = this.stat; fstat = (fd, a, b) => { const [{ bigint = false, throwIfNoEntry = true }, callback] = optHelpers.getStatOptsAndCb(a, b); (async () => { const openFile = await this.getFileByFd(fd, 'fstat'); return await this.getHandleStats(bigint, openFile.file); })().then(stats => callback(null, stats), error => callback(error)); }; async getHandleStats(bigint, handle) { let size = 0; if (handle.kind === 'file') { const file = handle; const fileData = await file.getFile(); size = fileData.size; } const stats = new FsaNodeStats(bigint, bigint ? BigInt(size) : size, handle.kind); return stats; } /** * @todo There is a proposal for native move support. * @see https://github.com/whatwg/fs/blob/main/proposals/MovingNonOpfsFiles.md */ rename = (oldPath, newPath, callback) => { const oldPathFilename = util.pathToFilename(oldPath); const newPathFilename = util.pathToFilename(newPath); const [oldFolder, oldName] = pathToLocation(oldPathFilename); const [newFolder, newName] = pathToLocation(newPathFilename); (async () => { const oldFile = await this.getFile(oldFolder, oldName, 'rename'); const newDir = await this.getDir(newFolder, false, 'rename'); const newFile = await newDir.getFileHandle(newName, { create: true }); const writable = await newFile.createWritable({ keepExistingData: false }); const oldData = await oldFile.getFile(); await writable.write(await oldData.arrayBuffer()); await writable.close(); const oldDir = await this.getDir(oldFolder, false, 'rename'); await oldDir.removeEntry(oldName); })().then(() => callback(null), error => callback(error)); }; exists = (path, callback) => { const filename = util.pathToFilename(path); if (typeof callback !== 'function') throw Error(ERRSTR.CB); this.access(path, 0 /* AMODE.F_OK */, error => callback(!error)); }; access = (path, a, b) => { let mode = 0 /* AMODE.F_OK */; let callback; if (typeof a !== 'function') { mode = a | 0; // cast to number callback = util.validateCallback(b); } else { callback = a; } const filename = util.pathToFilename(path); const [folder, name] = pathToLocation(filename); (async () => { const node = folder.length || name ? await this.getFileOrDir(folder, name, 'access') : await this.root; const checkIfCanExecute = mode & 1 /* AMODE.X_OK */; if (checkIfCanExecute) throw util.createError('EACCESS', 'access', filename); const checkIfCanWrite = mode & 2 /* AMODE.W_OK */; switch (node.kind) { case 'file': { if (checkIfCanWrite) { try { const file = node; const writable = await file.createWritable(); await writable.close(); } catch { throw util.createError('EACCESS', 'access', filename); } } break; } case 'directory': { if (checkIfCanWrite) { const dir = node; const canWrite = await testDirectoryIsWritable(dir); if (!canWrite) throw util.createError('EACCESS', 'access', filename); } break; } default: { throw util.createError('EACCESS', 'access', filename); } } })().then(() => callback(null), error => callback(error)); }; appendFile = (id, data, a, b) => { const [opts, callback] = optHelpers.getAppendFileOptsAndCb(a, b); const buffer = util.dataToBuffer(data, opts.encoding); this.getFileByIdOrCreate(id, 'appendFile') .then(file => (async () => { const blob = await file.getFile(); const writable = await file.createWritable({ keepExistingData: true }); await writable.write({ type: 'write', data: buffer, position: blob.size, }); await writable.close(); })()) .then(() => callback(null), error => callback(error)); }; readdir = (path, a, b) => { const [options, callback] = optHelpers.getReaddirOptsAndCb(a, b); const filename = util.pathToFilename(path); const [folder, name] = pathToLocation(filename); if (name) folder.push(name); this.getDir(folder, false, 'readdir') .then(dir => (async () => { if (options.withFileTypes) { const list = []; for await (const [name, handle] of dir.entries()) { const dirent = new FsaNodeDirent(name, handle.kind); list.push(dirent); } if (!util.isWin && options.encoding !== 'buffer') list.sort((a, b) => { if (a.name < b.name) return -1; if (a.name > b.name) return 1; return 0; }); return list; } else { const list = []; for await (const key of dir.keys()) list.push(key); if (!util.isWin && options.encoding !== 'buffer') list.sort(); return list; } })()) .then(res => callback(null, res), err => callback(err)); }; readlink = (path, a, b) => { const [opts, callback] = optHelpers.getDefaultOptsAndCb(a, b); const filename = util.pathToFilename(path); const buffer = Buffer.from(filename); callback(null, util.bufferToEncoding(buffer, opts.encoding)); }; /** @todo Could this use `FileSystemSyncAccessHandle.flush` through a Worker thread? */ fsync = (fd, callback) => { callback(null); }; fdatasync = (fd, callback) => { callback(null); }; ftruncate = (fd, a, b) => { const len = typeof a === 'number' ? a : 0; const callback = util.validateCallback(typeof a === 'number' ? b : a); this.getFileByFdAsync(fd) .then(file => file.file.createWritable({ keepExistingData: true })) .then(writable => writable.truncate(len).then(() => writable.close())) .then(() => callback(null), error => callback(error)); }; truncate = (path, a, b) => { const len = typeof a === 'number' ? a : 0; const callback = util.validateCallback(typeof a === 'number' ? b : a); this.open(path, 'r+', (error, fd) => { if (error) callback(error); else { this.ftruncate(fd, len, error => { if (error) this.close(fd, () => callback(error)); else this.close(fd, callback); }); } }); }; futimes = (fd, atime, mtime, callback) => { callback(null); }; utimes = (path, atime, mtime, callback) => { callback(null); }; mkdir = (path, a, b) => { const opts = optHelpers.getMkdirOptions(a); const callback = util.validateCallback(typeof a === 'function' ? a : b); // const modeNum = modeToNumber(opts.mode, 0o777); const filename = util.pathToFilename(path); const [folder, name] = pathToLocation(filename); // TODO: need to throw if directory already exists this.getDir(folder, opts.recursive ?? false) .then(dir => dir.getDirectoryHandle(name, { create: true })) .then(() => callback(null), error => { if (error && typeof error === 'object') { switch (error.name) { case 'NotFoundError': { const err = util.createError('ENOENT', 'mkdir', folder.join('/')); callback(err); return; } } } callback(error); }); }; mkdtemp = (prefix, a, b) => { const [{ encoding }, callback] = optHelpers.getDefaultOptsAndCb(a, b); if (!prefix || typeof prefix !== 'string') throw new TypeError('filename prefix is required'); if (!util.nullCheck(prefix)) return; const filename = prefix + util.genRndStr6(); this.mkdir(filename, 511 /* MODE.DIR */, err => { if (err) callback(err); else callback(null, strToEncoding(filename, encoding)); }); }; rmAll(callback) { (async () => { const root = await this.root; for await (const name of root.keys()) { await root.removeEntry(name, { recursive: true }); } })().then(() => callback(null), error => callback(error)); } rmdir = (path, a, b) => { const options = optHelpers.getRmdirOptions(a); const callback = util.validateCallback(typeof a === 'function' ? a : b); const [folder, name] = pathToLocation(util.pathToFilename(path)); if (!name && options.recursive) return this.rmAll(callback); this.getDir(folder, false, 'rmdir') .then(dir => dir.getDirectoryHandle(name).then(() => dir)) .then(dir => dir.removeEntry(name, { recursive: options.recursive ?? false })) .then(() => callback(null), error => { if (error && typeof error === 'object') { switch (error.name) { case 'NotFoundError': { const err = util.createError('ENOENT', 'rmdir', folder.join('/')); callback(err); return; } case 'InvalidModificationError': { const err = util.createError('ENOTEMPTY', 'rmdir', folder.join('/')); callback(err); return; } } } callback(error); }); }; rm = (path, a, b) => { const [options, callback] = optHelpers.getRmOptsAndCb(a, b); const [folder, name] = pathToLocation(util.pathToFilename(path)); if (!name && options.recursive) return this.rmAll(callback); this.getDir(folder, false, 'rmdir') .then(dir => dir.removeEntry(name, { recursive: options.recursive ?? false })) .then(() => callback(null), error => { if (options.force) { callback(null); return; } if (error && typeof error === 'object') { switch (error.name) { case 'NotFoundError': { const err = util.createError('ENOENT', 'rmdir', folder.join('/')); callback(err); return; } case 'InvalidModificationError': { const err = util.createError('ENOTEMPTY', 'rmdir', folder.join('/')); callback(err); return; } } } callback(error); }); }; fchmod = (fd, mode, callback) => { callback(null); }; chmod = (path, mode, callback) => { callback(null); }; lchmod = (path, mode, callback) => { callback(null); }; fchown = (fd, uid, gid, callback) => { callback(null); }; chown = (path, uid, gid, callback) => { callback(null); }; lchown = (path, uid, gid, callback) => { callback(null); }; createWriteStream = (path, options) => { const defaults = { encoding: 'utf8', flags: 'w', autoClose: true, emitClose: true, }; const optionsObj = optHelpers.getOptions(defaults, options); const filename = util.pathToFilename(path); const flags = util.flagsToNumber(optionsObj.flags ?? 'w'); const fd = optionsObj.fd ? (typeof optionsObj.fd === 'number' ? optionsObj.fd : optionsObj.fd.fd) : 0; const handle = fd ? this.getFileByFdAsync(fd) : this.__open(filename, flags, 0); const stream = new FsaNodeWriteStream(handle, filename, optionsObj); if (optionsObj.autoClose) { stream.once('finish', () => { handle.then(file => this.close(file.fd, () => { })); }); stream.once('error', () => { handle.then(file => this.close(file.fd, () => { })); }); } return stream; }; createReadStream = (path, options) => { const defaults = { flags: 'r', fd: null, mode: 0o666, autoClose: true, emitClose: true, start: 0, end: Infinity, highWaterMark: 64 * 1024, fs: null, signal: null, }; const optionsObj = optHelpers.getOptions(defaults, options); const filename = util.pathToFilename(path); const flags = util.flagsToNumber(optionsObj.flags); const fd = optionsObj.fd ? (typeof optionsObj.fd === 'number' ? optionsObj.fd : optionsObj.fd.fd) : 0; const handle = fd ? this.getFileByFdAsync(fd) : this.__open(filename, flags, 0); const stream = new FsaNodeReadStream(this, handle, filename, optionsObj); return stream; }; cp = notImplemented; lutimes = notImplemented; openAsBlob = notImplemented; opendir = notImplemented; readv = notImplemented; statfs = notImplemented; /** * @todo Watchers could be implemented in the future on top of `FileSystemObserver`, * which is currently a proposal. * @see https://github.com/whatwg/fs/blob/main/proposals/FileSystemObserver.md */ watchFile = notSupported; unwatchFile = notSupported; watch = notSupported; symlink = notSupported; link = notSupported; // --------------------------------------------------------- FsSynchronousApi statSync = (path, options) => { const { bigint = true, throwIfNoEntry = true } = optHelpers.getStatOptions(options); const filename = util.pathToFilename(path); const location = pathToLocation(filename); const adapter = this.getSyncAdapter(); const res = adapter.call('stat', location); const stats = new FsaNodeStats(bigint, res.size ?? 0, res.kind); return stats; }; lstatSync = this.statSync; fstatSync = (fd, options) => { const filename = this.getFileName(fd); return this.statSync(filename, options); }; accessSync = (path, mode = 0 /* AMODE.F_OK */) => { const filename = util.pathToFilename(path); mode = mode | 0; const adapter = this.getSyncAdapter(); adapter.call('access', [filename, mode]); }; readFileSync = (id, options) => { const opts = optHelpers.getReadFileOptions(options); const flagsNum = util.flagsToNumber(opts.flag); const filename = this.getFileName(id); const adapter = this.getSyncAdapter(); const uint8 = adapter.call('readFile', [filename, opts]); const buffer = Buffer.from(uint8.buffer, uint8.byteOffset, uint8.byteLength); return util.bufferToEncoding(buffer, opts.encoding); }; writeFileSync = (id, data, options) => { const opts = optHelpers.getWriteFileOptions(options); const flagsNum = util.flagsToNumber(opts.flag); const modeNum = util.modeToNumber(opts.mode); const buf = util.dataToBuffer(data, opts.encoding); const filename = this.getFileName(id); const adapter = this.getSyncAdapter(); adapter.call('writeFile', [filename, util.bufToUint8(buf), opts]); }; appendFileSync = (id, data, options) => { const opts = optHelpers.getAppendFileOpts(options); if (!opts.flag || util.isFd(id)) opts.flag = 'a'; const filename = this.getFileName(id); const buf = util.dataToBuffer(data, opts.encoding); const adapter = this.getSyncAdapter(); adapter.call('appendFile', [filename, util.bufToUint8(buf), opts]); }; closeSync = (fd) => { util.validateFd(fd); const file = this.getFileByFd(fd, 'close'); file.close().catch(() => { }); this.fds.delete(fd); this.releasedFds.push(fd); }; existsSync = (path) => { try { this.statSync(path); return true; } catch { return false; } }; copyFileSync = (src, dest, flags) => { const srcFilename = util.pathToFilename(src); const destFilename = util.pathToFilename(dest); const adapter = this.getSyncAdapter(); adapter.call('copy', [srcFilename, destFilename, flags]); }; renameSync = (oldPath, newPath) => { const srcFilename = util.pathToFilename(oldPath); const destFilename = util.pathToFilename(newPath); const adapter = this.getSyncAdapter(); adapter.call('move', [srcFilename, destFilename]); }; rmdirSync = (path, opts) => { const filename = util.pathToFilename(path); const adapter = this.getSyncAdapter(); adapter.call('rmdir', [filename, opts]); }; rmSync = (path, options) => { const filename = util.pathToFilename(path); const adapter = this.getSyncAdapter(); adapter.call('rm', [filename, options]); }; mkdirSync = (path, options) => { const opts = optHelpers.getMkdirOptions(options); const modeNum = util.modeToNumber(opts.mode, 0o777); const filename = util.pathToFilename(path); return this.getSyncAdapter().call('mkdir', [filename, options]); }; mkdtempSync = (prefix, options) => { const { encoding } = optHelpers.getDefaultOpts(options); if (!prefix || typeof prefix !== 'string') throw new TypeError('filename prefix is required'); util.nullCheck(prefix); const result = this.getSyncAdapter().call('mkdtemp', [prefix, options]); return strToEncoding(result, encoding); }; readlinkSync = (path, options) => { const opts = optHelpers.getDefaultOpts(options); const filename = util.pathToFilename(path); const buffer = Buffer.from(filename); return util.bufferToEncoding(buffer, opts.encoding); }; truncateSync = (id, len) => { if (util.isFd(id)) return this.ftruncateSync(id, len); const filename = util.pathToFilename(id); this.getSyncAdapter().call('trunc', [filename, Number(len) || 0]); }; ftruncateSync = (fd, len) => { const filename = this.getFileName(fd); this.truncateSync(filename, len); }; unlinkSync = (path) => { const filename = util.pathToFilename(path); this.getSyncAdapter().call('unlink', [filename]); }; readdirSync = (path, options) => { const opts = optHelpers.getReaddirOptions(options); const filename = util.pathToFilename(path); const adapter = this.getSyncAdapter(); const list = adapter.call('readdir', [filename]); if (opts.withFileTypes) { const res = []; for (const entry of list) res.push(new FsaNodeDirent(entry.name, entry.kind)); return res; } else { const res = []; for (const entry of list) { const buffer = Buffer.from(entry.name); res.push(util.bufferToEncoding(buffer, opts.encoding)); } return res; } }; realpathSync = (path, options) => { let filename = util.pathToFilename(path); const { encoding } = optHelpers.getRealpathOptions(options); if (filename[0] !== "/" /* FsaToNodeConstants.Separator */) filename = "/" /* FsaToNodeConstants.Separator */ + filename; return strToEncoding(filename, encoding); }; readSync = (fd, buffer, offset, length, position) => { util.validateFd(fd); const filename = this.getFileName(fd); const adapter = this.getSyncAdapter(); const uint8 = adapter.call('read', [filename, position, length]); const dest = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); dest.set(uint8, offset); return uint8.length; }; writeSync = (fd, a, b, c, d) => { const [, buf, offset, length, position] = util.getWriteSyncArgs(fd, a, b, c, d); const filename = this.getFileName(fd); const data = new Uint8Array(buf.buffer, buf.byteOffset + offset, length); return this.getSyncAdapter().call('write', [filename, data, position || null]); }; openSync = (path, flags, mode = 438 /* MODE.DEFAULT */) => { const modeNum = util.modeToNumber(mode); const filename = util.pathToFilename(path); const flagsNum = util.flagsToNumber(flags); const adapter = this.getSyncAdapter(); const handle = adapter.call('open', [filename, flagsNum, modeNum]); const openFile = this.__open2(handle, filename, flagsNum, modeNum); return openFile.fd; }; writevSync = (fd, buffers, position) => { if (buffers.length === 0) return; this.writeSync(fd, buffers[0], 0, buffers[0].byteLength, position); for (let i = 1; i < buffers.length; i++) { this.writeSync(fd, buffers[i], 0, buffers[i].byteLength, null); } }; fdatasyncSync = noop; fsyncSync = noop; chmodSync = noop; chownSync = noop; fchmodSync = noop; fchownSync = noop; futimesSync = noop; lchmodSync = noop; lchownSync = noop; utimesSync = noop; lutimesSync = noop; cpSync = notImplemented; opendirSync = notImplemented; statfsSync = notImplemented; readvSync = notImplemented; symlinkSync = notSupported; linkSync = notSupported; // ---------------------------------------------------------- FsCommonObjects F_OK = constants.F_OK; R_OK = constants.R_OK; W_OK = constants.W_OK; X_OK = constants.X_OK; constants = constants; Dirent = FsaNodeDirent; Stats = (FsaNodeStats); WriteStream = FsaNodeWriteStream; ReadStream = FsaNodeReadStream; StatFs = 0; Dir = 0; StatsWatcher = 0; FSWatcher = 0; }