import * as pathModule from 'path' ; import { PathLike , symlink } from 'fs' ; import { Node , Link , File } from './node' ; import Stats from './Stats' ; import Dirent from './Dirent' ; import { Buffer , bufferAllocUnsafe , bufferFrom } from './internal/buffer' ; import setImmediate from './setImmediate' ; import process from './process' ; import setTimeoutUnref , { TSetTimeout } from './setTimeoutUnref' ; import { Readable , Writable } from 'stream' ; import { constants } from './constants' ; import { EventEmitter } from 'events' ; import { TEncodingExtended , TDataOut , assertEncoding , strToEncoding , ENCODING_UTF8 } from './encoding' ; import * as errors from './internal/errors' ; import util = require ( 'util' ) ; import createPromisesApi from './promises' ; const resolveCrossPlatform = pathModule . resolve ; const { O_RDONLY , O_WRONLY , O_RDWR , O_CREAT , O_EXCL , O_TRUNC , O_APPEND , O_SYNC , O_DIRECTORY , F_OK , COPYFILE_EXCL , COPYFILE_FICLONE_FORCE , } = constants ; const { sep , relative , join , dirname } = pathModule . posix ? pathModule . posix : pathModule ; const isWin = process . platform === 'win32' ; export interface IError extends Error { code ? : string ; } export type TFileId = PathLike | number ; export type TData = TDataOut | Uint8Array ; export type TFlags = string | number ; export type TMode = string | number ; export type TTime = number | string | Date ; export type TCallback < TData > = ( error ? : IError | null , data ? : TData ) => void ; const enum MODE { FILE = 0o666 , DIR = 0o777 , DEFAULT = MODE . FILE } const kMinPoolSpace = 128 ; const ERRSTR = { PATH_STR : 'path must be a string or Buffer' , FD : 'fd must be a file descriptor' , MODE_INT : 'mode must be an int' , CB : 'callback must be a function' , UID : 'uid must be an unsigned int' , GID : 'gid must be an unsigned int' , LEN : 'len must be an integer' , ATIME : 'atime must be an integer' , MTIME : 'mtime must be an integer' , PREFIX : 'filename prefix is required' , BUFFER : 'buffer must be an instance of Buffer or StaticBuffer' , OFFSET : 'offset must be an integer' , LENGTH : 'length must be an integer' , POSITION : 'position must be an integer' , } ; const ERRSTR_OPTS = tipeof => `Expected options to be either an object or a string, but got ${ tipeof } instead` ; const ENOENT = 'ENOENT' ; const EBADF = 'EBADF' ; const EINVAL = 'EINVAL' ; const EPERM = 'EPERM' ; const EPROTO = 'EPROTO' ; const EEXIST = 'EEXIST' ; const ENOTDIR = 'ENOTDIR' ; const EMFILE = 'EMFILE' ; const EACCES = 'EACCES' ; const EISDIR = 'EISDIR' ; const ENOTEMPTY = 'ENOTEMPTY' ; const ENOSYS = 'ENOSYS' ; const ERR_FS_EISDIR = 'ERR_FS_EISDIR' ; function formatError ( errorCode , func = '' , path = '' , path2 = '' ) { let pathFormatted = '' ; if ( path ) pathFormatted = ` '${ path }'` ; if ( path2 ) pathFormatted += ` -> '${ path2 }'` ; switch ( errorCode ) { case ENOENT : return `ENOENT: no such file or directory, ${ func }${ pathFormatted }` ; case EBADF : return `EBADF: bad file descriptor, ${ func }${ pathFormatted }` ; case EINVAL : return `EINVAL: invalid argument, ${ func }${ pathFormatted }` ; case EPERM : return `EPERM: operation not permitted, ${ func }${ pathFormatted }` ; case EPROTO : return `EPROTO: protocol error, ${ func }${ pathFormatted }` ; case EEXIST : return `EEXIST: file already exists, ${ func }${ pathFormatted }` ; case ENOTDIR : return `ENOTDIR: not a directory, ${ func }${ pathFormatted }` ; case EISDIR : return `EISDIR: illegal operation on a directory, ${ func }${ pathFormatted }` ; case EACCES : return `EACCES: permission denied, ${ func }${ pathFormatted }` ; case ENOTEMPTY : return `ENOTEMPTY: directory not empty, ${ func }${ pathFormatted }` ; case EMFILE : return `EMFILE: too many open files, ${ func }${ pathFormatted }` ; case ENOSYS : return `ENOSYS: function not implemented, ${ func }${ pathFormatted }` ; case ERR_FS_EISDIR : return `[ERR_FS_EISDIR]: Path is a directory: ${ func } returned EISDIR (is a directory) ${ path }` ; default : return `${ errorCode }: error occurred, ${ func }${ pathFormatted }` ; } } function createError ( errorCode , func = '' , path = '' , path2 = '' , Constructor = Error ) { const error = new Constructor ( formatError ( errorCode , func , path , path2 ) ) ; ( error as any ) . code = errorCode ; return error ; } export enum FLAGS { r = O_RDONLY , 'r+' = O_RDWR , rs = O_RDONLY | O_SYNC , sr = FLAGS . rs , 'rs+' = O_RDWR | O_SYNC , 'sr+' = FLAGS [ 'rs+' ] , w = O_WRONLY | O_CREAT | O_TRUNC , wx = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL , xw = FLAGS . wx , 'w+' = O_RDWR | O_CREAT | O_TRUNC , 'wx+' = O_RDWR | O_CREAT | O_TRUNC | O_EXCL , 'xw+' = FLAGS [ 'wx+' ] , a = O_WRONLY | O_APPEND | O_CREAT , ax = O_WRONLY | O_APPEND | O_CREAT | O_EXCL , xa = FLAGS . ax , 'a+' = O_RDWR | O_APPEND | O_CREAT , 'ax+' = O_RDWR | O_APPEND | O_CREAT | O_EXCL , 'xa+' = FLAGS [ 'ax+' ] } export type TFlagsCopy = typeof constants . COPYFILE_EXCL | typeof constants . COPYFILE_FICLONE | typeof constants . COPYFILE_FICLONE_FORCE ; export function flagsToNumber ( flags ) { if ( typeof flags === 'number' ) return flags ; if ( typeof flags === 'string' ) { const flagsNum = FLAGS [ flags ] ; if ( typeof flagsNum !== 'undefined' ) return flagsNum ; } throw new errors . TypeError ( 'ERR_INVALID_OPT_VALUE' , 'flags' , flags ) ; } function getOptions < T extends IOptions > ( defaults , options ? ) { let opts ; if ( ! options ) return defaults ; else { const tipeof = typeof options ; switch ( tipeof ) { case 'string' : opts = Object . assign ( { } , defaults , { encoding : options as string } ) ; break ; case 'object' : opts = Object . assign ( { } , defaults , options ) ; break ; default : throw TypeError ( ERRSTR_OPTS ( tipeof ) ) ; } } if ( opts . encoding !== 'buffer' ) assertEncoding ( opts . encoding ) ; return opts ; } function optsGenerator < TOpts > ( defaults ) { return options => getOptions ( defaults , options ) ; } function validateCallback ( callback ) { if ( typeof callback !== 'function' ) throw TypeError ( ERRSTR . CB ) ; return callback ; } function optsAndCbGenerator < TOpts , TResult > ( getOpts ) { return ( options , callback ? ) => typeof options === 'function' ? [ getOpts ( ) , options ] : [ getOpts ( options ) , validateCallback ( callback ) ] ; } export interface IOptions { encoding ? : BufferEncoding | TEncodingExtended ; } export interface IFileOptions extends IOptions { mode ? : TMode ; flag ? : TFlags ; } const optsDefaults = { encoding : 'utf8' , } ; const getDefaultOpts = optsGenerator < IOptions > ( optsDefaults ) ; const getDefaultOptsAndCb = optsAndCbGenerator < IOptions , any > ( getDefaultOpts ) ; export interface IReadFileOptions extends IOptions { flag ? : string ; } const readFileOptsDefaults = { flag : 'r' , } ; const getReadFileOptions = optsGenerator < IReadFileOptions > ( readFileOptsDefaults ) ; export interface IWriteFileOptions extends IFileOptions { } const writeFileDefaults = { encoding : 'utf8' , mode : MODE . DEFAULT , flag : FLAGS [ FLAGS . w ] , } ; const getWriteFileOptions = optsGenerator < IWriteFileOptions > ( writeFileDefaults ) ; export interface IAppendFileOptions extends IFileOptions { } const appendFileDefaults = { encoding : 'utf8' , mode : MODE . DEFAULT , flag : FLAGS [ FLAGS . a ] , } ; const getAppendFileOpts = optsGenerator < IAppendFileOptions > ( appendFileDefaults ) ; const getAppendFileOptsAndCb = optsAndCbGenerator < IAppendFileOptions , void > ( getAppendFileOpts ) ; export interface IRealpathOptions { encoding ? : TEncodingExtended ; } const realpathDefaults = optsDefaults ; const getRealpathOptions = optsGenerator < IRealpathOptions > ( realpathDefaults ) ; const getRealpathOptsAndCb = optsAndCbGenerator < IRealpathOptions , TDataOut > ( getRealpathOptions ) ; export interface IWatchFileOptions { persistent ? : boolean ; interval ? : number ; } export interface IReadStreamOptions { flags ? : TFlags ; encoding ? : BufferEncoding ; fd ? : number ; mode ? : TMode ; autoClose ? : boolean ; start ? : number ; end ? : number ; } export interface IWriteStreamOptions { flags ? : TFlags ; defaultEncoding ? : BufferEncoding ; fd ? : number ; mode ? : TMode ; autoClose ? : boolean ; start ? : number ; } export interface IWatchOptions extends IOptions { persistent ? : boolean ; recursive ? : boolean ; } export interface IMkdirOptions { mode ? : TMode ; recursive ? : boolean ; } const mkdirDefaults = { mode : MODE . DIR , recursive : false , } ; const getMkdirOptions = ( options ) : IMkdirOptions => { if ( typeof options === 'number' ) return Object . assign ( { } , mkdirDefaults , { mode : options } ) ; return Object . assign ( { } , mkdirDefaults , options ) ; } ; export interface IRmdirOptions { recursive ? : boolean ; } const rmdirDefaults = { recursive : false , } ; const getRmdirOptions = ( options ) : IRmdirOptions => { return Object . assign ( { } , rmdirDefaults , options ) ; } ; export interface IRmOptions { force ? : boolean ; maxRetries ? : number ; recursive ? : boolean ; retryDelay ? : number ; } const getRmOpts = optsGenerator < IOptions > ( optsDefaults ) ; const getRmOptsAndCb = optsAndCbGenerator < IRmOptions , any > ( getRmOpts ) ; export interface IReaddirOptions extends IOptions { withFileTypes ? : boolean ; } const readdirDefaults = { encoding : 'utf8' , withFileTypes : false , } ; const getReaddirOptions = optsGenerator < IReaddirOptions > ( readdirDefaults ) ; const getReaddirOptsAndCb = optsAndCbGenerator < IReaddirOptions , TDataOut [ ] | Dirent [ ] > ( getReaddirOptions ) ; export interface IStatOptions { bigint ? : boolean ; throwIfNoEntry ? : boolean ; } export interface IFStatOptions { bigint ? : boolean ; } const statDefaults = { bigint : false , } ; const getStatOptions = ( options = { } ) => Object . assign ( { } , statDefaults , options ) ; const getStatOptsAndCb = ( options , callback ? ) => typeof options === 'function' ? [ getStatOptions ( ) , options ] : [ getStatOptions ( options ) , validateCallback ( callback ) ] ; function getPathFromURLPosix ( url ) { if ( url . hostname !== '' ) { throw new errors . TypeError ( 'ERR_INVALID_FILE_URL_HOST' , process . platform ) ; } const pathname = url . pathname ; for ( let n = 0 ; n < pathname . length ; n ++ ) { if ( pathname [ n ] === '%' ) { const third = pathname . codePointAt ( n + 2 ) | 0x20 ; if ( pathname [ n + 1 ] === '2' && third === 102 ) { throw new errors . TypeError ( 'ERR_INVALID_FILE_URL_PATH' , 'must not include encoded / characters' ) ; } } } return decodeURIComponent ( pathname ) ; } export function pathToFilename ( path ) { if ( typeof path !== 'string' && ! Buffer . isBuffer ( path ) ) { try { if ( ! ( path instanceof require ( 'url' ) . URL ) ) throw new TypeError ( ERRSTR . PATH_STR ) ; } catch ( err ) { throw new TypeError ( ERRSTR . PATH_STR ) ; } path = getPathFromURLPosix ( path ) ; } const pathString = String ( path ) ; nullCheck ( pathString ) ; return pathString ; } type TResolve = ( filename : string , base ? : string ) => string ; let resolve = ( filename , base = process . cwd ( ) ) => resolveCrossPlatform ( base , filename ) ; if ( isWin ) { const _resolve = resolve ; const { unixify } = require ( 'fs-monkey/lib/correctPath' ) ; resolve = ( filename , base ) => unixify ( _resolve ( filename , base ) ) ; } export function filenameToSteps ( filename , base ? ) { const fullPath = resolve ( filename , base ) ; const fullPathSansSlash = fullPath . substr ( 1 ) ; if ( ! fullPathSansSlash ) return [ ] ; return fullPathSansSlash . split ( sep ) ; } export function pathToSteps ( path ) { return filenameToSteps ( pathToFilename ( path ) ) ; } export function dataToStr ( data , encoding = ENCODING_UTF8 ) { if ( Buffer . isBuffer ( data ) ) return data . toString ( encoding ) ; else if ( data instanceof Uint8Array ) return bufferFrom ( data ) . toString ( encoding ) ; else return String ( data ) ; } export function dataToBuffer ( data , encoding = ENCODING_UTF8 ) { if ( Buffer . isBuffer ( data ) ) return data ; else if ( data instanceof Uint8Array ) return bufferFrom ( data ) ; else return bufferFrom ( String ( data ) , encoding ) ; } export function bufferToEncoding ( buffer , encoding ? ) { if ( ! encoding || encoding === 'buffer' ) return buffer ; else return buffer . toString ( encoding ) ; } function nullCheck ( path , callback ? ) { if ( ( '' + path ) . indexOf ( '\u0000' ) !== - 1 ) { const er = new Error ( 'Path must be a string without null bytes' ) ; ( er as any ) . code = ENOENT ; if ( typeof callback !== 'function' ) throw er ; process . nextTick ( callback , er ) ; return false ; } return true ; } function _modeToNumber ( mode , def ? ) { if ( typeof mode === 'number' ) return mode ; if ( typeof mode === 'string' ) return parseInt ( mode , 8 ) ; if ( def ) return modeToNumber ( def ) ; return undefined ; } function modeToNumber ( mode , def ? ) { const result = _modeToNumber ( mode , def ) ; if ( typeof result !== 'number' || isNaN ( result ) ) throw new TypeError ( ERRSTR . MODE_INT ) ; return result ; } function isFd ( path ) { return path >>> 0 === path ; } function validateFd ( fd ) { if ( ! isFd ( fd ) ) throw TypeError ( ERRSTR . FD ) ; } export function toUnixTimestamp ( time ) { if ( typeof time === 'string' && + time == ( time as any ) ) { return + time ; } if ( time instanceof Date ) { return time . getTime ( ) / 1000 ; } if ( isFinite ( time ) ) { if ( time < 0 ) { return Date . now ( ) / 1000 ; } return time ; } throw new Error ( 'Cannot parse time: ' + time ) ; } function validateUid ( uid ) { if ( typeof uid !== 'number' ) throw TypeError ( ERRSTR . UID ) ; } function validateGid ( gid ) { if ( typeof gid !== 'number' ) throw TypeError ( ERRSTR . GID ) ; } type DirectoryContent = string | null ; export interface DirectoryJSON { [ key ] : DirectoryContent ; } export interface NestedDirectoryJSON { [ key ] : DirectoryContent | NestedDirectoryJSON ; } function flattenJSON ( nestedJSON ) { const flatJSON = { } ; function flatten ( pathPrefix , node ) { for ( const path in node ) { const contentOrNode = node [ path ] ; const joinedPath = join ( pathPrefix , path ) ; if ( typeof contentOrNode === 'string' ) { flatJSON [ joinedPath ] = contentOrNode ; } else if ( typeof contentOrNode === 'object' && contentOrNode !== null && Object . keys ( contentOrNode ) . length > 0 ) { flatten ( joinedPath , contentOrNode ) ; } else { flatJSON [ joinedPath ] = null ; } } } flatten ( '' , nestedJSON ) ; return flatJSON ; } /**
 * `Volume` represents a file system.
 */ export class Volume { static fromJSON ( json , cwd ? ) { const vol = new Volume ( ) ; vol . fromJSON ( json , cwd ) ; return vol ; } static fromNestedJSON ( json , cwd ? ) { const vol = new Volume ( ) ; vol . fromNestedJSON ( json , cwd ) ; return vol ; } type { number } todo static fd : number = 0x7fffffff ; root : Link ; ino : number = 0 ; inodes : { [ ino ] : Node ; } = { } ; releasedInos : number [ ] = [ ] ; fds : { [ fd ] : File ; } = { } ; releasedFds : number [ ] = [ ] ; maxFiles = 10000 ; openFiles = 0 ; StatWatcher : new ( ) => StatWatcher ; ReadStream : new ( ... args ) => IReadStream ; WriteStream : new ( ... args ) => IWriteStream ; FSWatcher : new ( ) => FSWatcher ; props : { Node : new ( ... args ) => Node ; Link : new ( ... args ) => Link ; File : new ( ... args ) => File ; } ; private promisesApi = createPromisesApi ( this ) ; get promises ( ) { if ( this . promisesApi === null ) throw new Error ( 'Promise is not supported in this environment.' ) ; return this . promisesApi ; } constructor ( props = { } ) { this . props = Object . assign ( { Node , Link , File } , props ) ; const root = this . createLink ( ) ; root . setNode ( this . createNode ( true ) ) ; const self = this ; this . StatWatcher = class extends StatWatcher { constructor ( ) { super ( self ) ; } } ; const _ReadStream = FsReadStream as any ; this . ReadStream = ( class extends _ReadStream { constructor ( ... args ) { super ( self , ... args ) ; } } as any ) as new ( ... args ) => IReadStream ; const _WriteStream = FsWriteStream as any ; this . WriteStream = ( class extends _WriteStream { constructor ( ... args ) { super ( self , ... args ) ; } } as any ) as new ( ... args ) => IWriteStream ; this . FSWatcher = class extends FSWatcher { constructor ( ) { super ( self ) ; } } ; this . root = root ; } createLink ( ) ; createLink ( parent , name , isDirectory ? , perm ? ) ; createLink ( parent ? , name ? , isDirectory = false , perm ? ) { if ( ! parent ) { return new this . props . Link ( this , null , '' ) ; } if ( ! name ) { throw new Error ( 'createLink: name cannot be empty' ) ; } return parent . createChild ( name , this . createNode ( isDirectory , perm ) ) ; } deleteLink ( link ) { const parent = link . parent ; if ( parent ) { parent . deleteChild ( link ) ; return true ; } return false ; } private newInoNumber ( ) { const releasedFd = this . releasedInos . pop ( ) ; if ( releasedFd ) return releasedFd ; else { this . ino = ( this . ino + 1 ) % 0xffffffff ; return this . ino ; } } private newFdNumber ( ) { const releasedFd = this . releasedFds . pop ( ) ; return typeof releasedFd === 'number' ? releasedFd : Volume . fd -- ; } createNode ( isDirectory = false , perm ? ) { const node = new this . props . Node ( this . newInoNumber ( ) , perm ) ; if ( isDirectory ) node . setIsDirectory ( ) ; this . inodes [ node . ino ] = node ; return node ; } private getNode ( ino ) { return this . inodes [ ino ] ; } private deleteNode ( node ) { node . del ( ) ; delete this . inodes [ node . ino ] ; this . releasedInos . push ( node . ino ) ; } genRndStr ( ) { const str = ( Math . random ( ) + 1 ) . toString ( 36 ) . substr ( 2 , 6 ) ; if ( str . length === 6 ) return str ; else return this . genRndStr ( ) ; } getLink ( steps ) { return this . root . walk ( steps ) ; } getLinkOrThrow ( filename , funcName ? ) { const steps = filenameToSteps ( filename ) ; const link = this . getLink ( steps ) ; if ( ! link ) throw createError ( ENOENT , funcName , filename ) ; return link ; } getResolvedLink ( filenameOrSteps ) { let steps = typeof filenameOrSteps === 'string' ? filenameToSteps ( filenameOrSteps ) : filenameOrSteps ; let link = this . root ; let i = 0 ; while ( i < steps . length ) { const step = steps [ i ] ; link = link . getChild ( step ) ; if ( ! link ) return null ; const node = link . getNode ( ) ; if ( node . isSymlink ( ) ) { steps = node . symlink . concat ( steps . slice ( i + 1 ) ) ; link = this . root ; i = 0 ; continue ; } i ++ ; } return link ; } getResolvedLinkOrThrow ( filename , funcName ? ) { const link = this . getResolvedLink ( filename ) ; if ( ! link ) throw createError ( ENOENT , funcName , filename ) ; return link ; } resolveSymlinks ( link ) { return this . getResolvedLink ( link . steps . slice ( 1 ) ) ; } private getLinkAsDirOrThrow ( filename , funcName ? ) { const link = this . getLinkOrThrow ( filename , funcName ) ; if ( ! link . getNode ( ) . isDirectory ( ) ) throw createError ( ENOTDIR , funcName , filename ) ; return link ; } private getLinkParent ( steps ) { return this . root . walk ( steps , steps . length - 1 ) ; } private getLinkParentAsDirOrThrow ( filenameOrSteps , funcName ? ) { const steps = filenameOrSteps instanceof Array ? filenameOrSteps : filenameToSteps ( filenameOrSteps ) ; const link = this . getLinkParent ( steps ) ; if ( ! link ) throw createError ( ENOENT , funcName , sep + steps . join ( sep ) ) ; if ( ! link . getNode ( ) . isDirectory ( ) ) throw createError ( ENOTDIR , funcName , sep + steps . join ( sep ) ) ; return link ; } private getFileByFd ( fd ) { return this . fds [ String ( fd ) ] ; } private getFileByFdOrThrow ( fd , funcName ? ) { if ( ! isFd ( fd ) ) throw TypeError ( ERRSTR . FD ) ; const file = this . getFileByFd ( fd ) ; if ( ! file ) throw createError ( EBADF , funcName ) ; return file ; } todo private wrapAsync ( method , args , callback ) { validateCallback ( callback ) ; setImmediate ( ( ) => { let result ; try { result = method . apply ( this , args ) ; } catch ( err ) { callback ( err ) ; return ; } callback ( null , result ) ; } ) ; } private _toJSON ( link = this . root , json = { } , path ? ) { let isEmpty = true ; let children = link . children ; if ( link . getNode ( ) . isFile ( ) ) { children = { [ link . getName ( ) ] : link . parent . getChild ( link . getName ( ) ) } ; link = link . parent ; } for ( const name in children ) { isEmpty = false ; const child = link . getChild ( name ) ; if ( ! child ) { throw new Error ( '_toJSON: unexpected undefined' ) ; } const node = child . getNode ( ) ; if ( node . isFile ( ) ) { let filename = child . getPath ( ) ; if ( path ) filename = relative ( path , filename ) ; json [ filename ] = node . getString ( ) ; } else if ( node . isDirectory ( ) ) { this . _toJSON ( child , json , path ) ; } } let dirPath = link . getPath ( ) ; if ( path ) dirPath = relative ( path , dirPath ) ; if ( dirPath && isEmpty ) { json [ dirPath ] = null ; } return json ; } toJSON ( paths ? , json = { } , isRelative = false ) { const links = [ ] ; if ( paths ) { if ( ! ( paths instanceof Array ) ) paths = [ paths ] ; for ( const path of paths ) { const filename = pathToFilename ( path ) ; const link = this . getResolvedLink ( filename ) ; if ( ! link ) continue ; links . push ( link ) ; } } else { links . push ( this . root ) ; } if ( ! links . length ) return json ; for ( const link of links ) this . _toJSON ( link , json , isRelative ? link . getPath ( ) : '' ) ; return json ; } fromJSON ( json , cwd = process . cwd ( ) ) { for ( let filename in json ) { const data = json [ filename ] ; filename = resolve ( filename , cwd ) ; if ( typeof data === 'string' ) { const dir = dirname ( filename ) ; this . mkdirpBase ( dir , MODE . DIR ) ; this . writeFileSync ( filename , data ) ; } else { this . mkdirpBase ( filename , MODE . DIR ) ; } } } fromNestedJSON ( json , cwd ? ) { this . fromJSON ( flattenJSON ( json ) , cwd ) ; } reset ( ) { this . ino = 0 ; this . inodes = { } ; this . releasedInos = [ ] ; this . fds = { } ; this . releasedFds = [ ] ; this . openFiles = 0 ; this . root = this . createLink ( ) ; this . root . setNode ( this . createNode ( true ) ) ; } mountSync ( mountpoint , json ) { this . fromJSON ( json , mountpoint ) ; } private openLink ( link , flagsNum , resolveSymlinks = true ) { if ( this . openFiles >= this . maxFiles ) { throw createError ( EMFILE , 'open' , link . getPath ( ) ) ; } let realLink = link ; if ( resolveSymlinks ) realLink = this . resolveSymlinks ( link ) ; if ( ! realLink ) throw createError ( ENOENT , 'open' , link . getPath ( ) ) ; const node = realLink . getNode ( ) ; if ( node . isDirectory ( ) ) { if ( ( flagsNum & ( O_RDONLY | O_RDWR | O_WRONLY ) ) !== O_RDONLY ) throw createError ( EISDIR , 'open' , link . getPath ( ) ) ; } else { if ( flagsNum & O_DIRECTORY ) throw createError ( ENOTDIR , 'open' , link . getPath ( ) ) ; } if ( ! ( flagsNum & O_WRONLY ) ) { if ( ! node . canRead ( ) ) { throw createError ( EACCES , 'open' , link . getPath ( ) ) ; } } if ( flagsNum & O_RDWR ) { } const file = new this . props . File ( link , node , flagsNum , this . newFdNumber ( ) ) ; this . fds [ file . fd ] = file ; this . openFiles ++ ; if ( flagsNum & O_TRUNC ) file . truncate ( ) ; return file ; } private openFile ( filename , flagsNum , modeNum , resolveSymlinks = true ) { const steps = filenameToSteps ( filename ) ; let link = resolveSymlinks ? this . getResolvedLink ( steps ) : this . getLink ( steps ) ; if ( ! link && flagsNum & O_CREAT ) { const dirLink = this . getResolvedLink ( steps . slice ( 0 , steps . length - 1 ) ) ; if ( ! dirLink ) throw createError ( ENOENT , 'open' , sep + steps . join ( sep ) ) ; if ( flagsNum & O_CREAT && typeof modeNum === 'number' ) { link = this . createLink ( dirLink , steps [ steps . length - 1 ] , false , modeNum ) ; } } if ( link ) return this . openLink ( link , flagsNum , resolveSymlinks ) ; throw createError ( ENOENT , 'open' , filename ) ; } private openBase ( filename , flagsNum , modeNum , resolveSymlinks = true ) { const file = this . openFile ( filename , flagsNum , modeNum , resolveSymlinks ) ; if ( ! file ) throw createError ( ENOENT , 'open' , filename ) ; return file . fd ; } openSync ( path , flags , mode = MODE . DEFAULT ) { const modeNum = modeToNumber ( mode ) ; const fileName = pathToFilename ( path ) ; const flagsNum = flagsToNumber ( flags ) ; return this . openBase ( fileName , flagsNum , modeNum ) ; } open ( path , flags , callback ) ; open ( path , flags , mode , callback ) ; open ( path , flags , a , b ? ) { let mode = a as TMode ; let callback = b as TCallback < number > ; if ( typeof a === 'function' ) { mode = MODE . DEFAULT ; callback = a ; } mode = mode || MODE . DEFAULT ; const modeNum = modeToNumber ( mode ) ; const fileName = pathToFilename ( path ) ; const flagsNum = flagsToNumber ( flags ) ; this . wrapAsync ( this . openBase , [ fileName , flagsNum , modeNum ] , callback ) ; } private closeFile ( file ) { if ( ! this . fds [ file . fd ] ) return ; this . openFiles -- ; delete this . fds [ file . fd ] ; this . releasedFds . push ( file . fd ) ; } closeSync ( fd ) { validateFd ( fd ) ; const file = this . getFileByFdOrThrow ( fd , 'close' ) ; this . closeFile ( file ) ; } close ( fd , callback ) { validateFd ( fd ) ; this . wrapAsync ( this . closeSync , [ fd ] , callback ) ; } private openFileOrGetById ( id , flagsNum , modeNum ? ) { if ( typeof id === 'number' ) { const file = this . fds [ id ] ; if ( ! file ) throw createError ( ENOENT ) ; return file ; } else { return this . openFile ( pathToFilename ( id ) , flagsNum , modeNum ) ; } } private readBase ( fd , buffer , offset , length , position ) { const file = this . getFileByFdOrThrow ( fd ) ; return file . read ( buffer , Number ( offset ) , Number ( length ) , position ) ; } readSync ( fd , buffer , offset , length , position ) { validateFd ( fd ) ; return this . readBase ( fd , buffer , offset , length , position ) ; } read ( fd , buffer , offset , length , position , callback ) { validateCallback ( callback ) ; if ( length === 0 ) { return process . nextTick ( ( ) => { if ( callback ) callback ( null , 0 , buffer ) ; } ) ; } setImmediate ( ( ) => { try { const bytes = this . readBase ( fd , buffer , offset , length , position ) ; callback ( null , bytes , buffer ) ; } catch ( err ) { callback ( err ) ; } } ) ; } private readFileBase ( id , flagsNum , encoding ) { let result ; const isUserFd = typeof id === 'number' ; const userOwnsFd = isUserFd && isFd ( id ) ; let fd ; if ( userOwnsFd ) fd = id as number ; else { const filename = pathToFilename ( id as PathLike ) ; const steps = filenameToSteps ( filename ) ; const link = this . getResolvedLink ( steps ) ; if ( link ) { const node = link . getNode ( ) ; if ( node . isDirectory ( ) ) throw createError ( EISDIR , 'open' , link . getPath ( ) ) ; } fd = this . openSync ( id as PathLike , flagsNum ) ; } try { result = bufferToEncoding ( this . getFileByFdOrThrow ( fd ) . getBuffer ( ) , encoding ) ; } finally { if ( ! userOwnsFd ) { this . closeSync ( fd ) ; } } return result ; } readFileSync ( file , options ? ) { const opts = getReadFileOptions ( options ) ; const flagsNum = flagsToNumber ( opts . flag ) ; return this . readFileBase ( file , flagsNum , opts . encoding as BufferEncoding ) ; } readFile ( id , callback ) ; readFile ( id , options , callback ) ; readFile ( id , a , b ? ) { const [ opts , callback ] = optsAndCbGenerator < IReadFileOptions , TCallback < TDataOut > > ( getReadFileOptions ) ( a , b ) ; const flagsNum = flagsToNumber ( opts . flag ) ; this . wrapAsync ( this . readFileBase , [ id , flagsNum , opts . encoding ] , callback ) ; } private writeBase ( fd , buf , offset ? , length ? , position ? ) { const file = this . getFileByFdOrThrow ( fd , 'write' ) ; return file . write ( buf , offset , length , position ) ; } writeSync ( fd , buffer , offset ? , length ? , position ? ) ; writeSync ( fd , str , position ? , encoding ? ) ; writeSync ( fd , a , b ? , c ? , d ? ) { validateFd ( fd ) ; let encoding ; let offset ; let length ; let position ; const isBuffer = typeof a !== 'string' ; if ( isBuffer ) { offset = ( b || 0 ) | 0 ; length = c as number ; position = d ; } else { position = b ; encoding = c as BufferEncoding ; } const buf = dataToBuffer ( a , encoding ) ; if ( isBuffer ) { if ( typeof length === 'undefined' ) { length = buf . length ; } } else { offset = 0 ; length = buf . length ; } return this . writeBase ( fd , buf , offset , length , position ) ; } write ( fd , buffer , callback ) ; write ( fd , buffer , offset , callback ) ; write ( fd , buffer , offset , length , callback ) ; write ( fd , buffer , offset , length , position , callback ) ; write ( fd , str , callback ) ; write ( fd , str , position , callback ) ; write ( fd , str , position , encoding , callback ) ; write ( fd , a ? , b ? , c ? , d ? , e ? ) { validateFd ( fd ) ; let offset ; let length ; let position ; let encoding ; let callback ; const tipa = typeof a ; const tipb = typeof b ; const tipc = typeof c ; const tipd = typeof d ; if ( tipa !== 'string' ) { if ( tipb === 'function' ) { callback = b ; } else if ( tipc === 'function' ) { offset = b | 0 ; callback = c ; } else if ( tipd === 'function' ) { offset = b | 0 ; length = c ; callback = d ; } else { offset = b | 0 ; length = c ; position = d ; callback = e ; } } else { if ( tipb === 'function' ) { callback = b ; } else if ( tipc === 'function' ) { position = b ; callback = c ; } else if ( tipd === 'function' ) { position = b ; encoding = c ; callback = d ; } } const buf = dataToBuffer ( a , encoding ) ; if ( tipa !== 'string' ) { if ( typeof length === 'undefined' ) length = buf . length ; } else { offset = 0 ; length = buf . length ; } const cb = validateCallback ( callback ) ; setImmediate ( ( ) => { try { const bytes = this . writeBase ( fd , buf , offset , length , position ) ; if ( tipa !== 'string' ) { cb ( null , bytes , buf ) ; } else { cb ( null , bytes , a ) ; } } catch ( err ) { cb ( err ) ; } } ) ; } private writeFileBase ( id , buf , flagsNum , modeNum ) { const isUserFd = typeof id === 'number' ; let fd ; if ( isUserFd ) fd = id as number ; else { fd = this . openBase ( pathToFilename ( id as PathLike ) , flagsNum , modeNum ) ; } let offset = 0 ; let length = buf . length ; let position = flagsNum & O_APPEND ? undefined : 0 ; try { while ( length > 0 ) { const written = this . writeSync ( fd , buf , offset , length , position ) ; offset += written ; length -= written ; if ( position !== undefined ) position += written ; } } finally { if ( ! isUserFd ) this . closeSync ( fd ) ; } } writeFileSync ( id , data , options ? ) { const opts = getWriteFileOptions ( options ) ; const flagsNum = flagsToNumber ( opts . flag ) ; const modeNum = modeToNumber ( opts . mode ) ; const buf = dataToBuffer ( data , opts . encoding ) ; this . writeFileBase ( id , buf , flagsNum , modeNum ) ; } writeFile ( id , data , callback ) ; writeFile ( id , data , options , callback ) ; writeFile ( id , data , a , b ? ) { let options = a as IWriteFileOptions ; let callback = b ; if ( typeof a === 'function' ) { options = writeFileDefaults ; callback = a ; } const cb = validateCallback ( callback ) ; const opts = getWriteFileOptions ( options ) ; const flagsNum = flagsToNumber ( opts . flag ) ; const modeNum = modeToNumber ( opts . mode ) ; const buf = dataToBuffer ( data , opts . encoding ) ; this . wrapAsync ( this . writeFileBase , [ id , buf , flagsNum , modeNum ] , cb ) ; } private linkBase ( filename1 , filename2 ) { const steps1 = filenameToSteps ( filename1 ) ; const link1 = this . getLink ( steps1 ) ; if ( ! link1 ) throw createError ( ENOENT , 'link' , filename1 , filename2 ) ; const steps2 = filenameToSteps ( filename2 ) ; const dir2 = this . getLinkParent ( steps2 ) ; if ( ! dir2 ) throw createError ( ENOENT , 'link' , filename1 , filename2 ) ; const name = steps2 [ steps2 . length - 1 ] ; if ( dir2 . getChild ( name ) ) throw createError ( EEXIST , 'link' , filename1 , filename2 ) ; const node = link1 . getNode ( ) ; node . nlink ++ ; dir2 . createChild ( name , node ) ; } private copyFileBase ( src , dest , flags ) { const buf = this . readFileSync ( src ) as Buffer ; if ( flags & COPYFILE_EXCL ) { if ( this . existsSync ( dest ) ) { throw createError ( EEXIST , 'copyFile' , src , dest ) ; } } if ( flags & COPYFILE_FICLONE_FORCE ) { throw createError ( ENOSYS , 'copyFile' , src , dest ) ; } this . writeFileBase ( dest , buf , FLAGS . w , MODE . DEFAULT ) ; } copyFileSync ( src , dest , flags ? ) { const srcFilename = pathToFilename ( src ) ; const destFilename = pathToFilename ( dest ) ; return this . copyFileBase ( srcFilename , destFilename , ( flags || 0 ) | 0 ) ; } copyFile ( src , dest , callback ) ; copyFile ( src , dest , flags , callback ) ; copyFile ( src , dest , a , b ? ) { const srcFilename = pathToFilename ( src ) ; const destFilename = pathToFilename ( dest ) ; let flags ; let callback ; if ( typeof a === 'function' ) { flags = 0 ; callback = a ; } else { flags = a ; callback = b ; } validateCallback ( callback ) ; this . wrapAsync ( this . copyFileBase , [ srcFilename , destFilename , flags ] , callback ) ; } linkSync ( existingPath , newPath ) { const existingPathFilename = pathToFilename ( existingPath ) ; const newPathFilename = pathToFilename ( newPath ) ; this . linkBase ( existingPathFilename , newPathFilename ) ; } link ( existingPath , newPath , callback ) { const existingPathFilename = pathToFilename ( existingPath ) ; const newPathFilename = pathToFilename ( newPath ) ; this . wrapAsync ( this . linkBase , [ existingPathFilename , newPathFilename ] , callback ) ; } private unlinkBase ( filename ) { const steps = filenameToSteps ( filename ) ; const link = this . getLink ( steps ) ; if ( ! link ) throw createError ( ENOENT , 'unlink' , filename ) ; if ( link . length ) throw Error ( 'Dir not empty...' ) ; this . deleteLink ( link ) ; const node = link . getNode ( ) ; node . nlink -- ; if ( node . nlink <= 0 ) { this . deleteNode ( node ) ; } } unlinkSync ( path ) { const filename = pathToFilename ( path ) ; this . unlinkBase ( filename ) ; } unlink ( path , callback ) { const filename = pathToFilename ( path ) ; this . wrapAsync ( this . unlinkBase , [ filename ] , callback ) ; } private symlinkBase ( targetFilename , pathFilename ) { const pathSteps = filenameToSteps ( pathFilename ) ; const dirLink = this . getLinkParent ( pathSteps ) ; if ( ! dirLink ) throw createError ( ENOENT , 'symlink' , targetFilename , pathFilename ) ; const name = pathSteps [ pathSteps . length - 1 ] ; if ( dirLink . getChild ( name ) ) throw createError ( EEXIST , 'symlink' , targetFilename , pathFilename ) ; const symlink = dirLink . createChild ( name ) ; symlink . getNode ( ) . makeSymlink ( filenameToSteps ( targetFilename ) ) ; return symlink ; } symlinkSync ( target , path , type ? ) { const targetFilename = pathToFilename ( target ) ; const pathFilename = pathToFilename ( path ) ; this . symlinkBase ( targetFilename , pathFilename ) ; } symlink ( target , path , callback ) ; symlink ( target , path , type , callback ) ; symlink ( target , path , a , b ? ) { const callback = validateCallback ( typeof a === 'function' ? a : b ) ; const targetFilename = pathToFilename ( target ) ; const pathFilename = pathToFilename ( path ) ; this . wrapAsync ( this . symlinkBase , [ targetFilename , pathFilename ] , callback ) ; } private realpathBase ( filename , encoding ) { const steps = filenameToSteps ( filename ) ; const realLink = this . getResolvedLink ( steps ) ; if ( ! realLink ) throw createError ( ENOENT , 'realpath' , filename ) ; return strToEncoding ( realLink . getPath ( ) , encoding ) ; } realpathSync ( path , options ? ) { return this . realpathBase ( pathToFilename ( path ) , getRealpathOptions ( options ) . encoding ) ; } realpath ( path , callback ) ; realpath ( path , options , callback ) ; realpath ( path , a , b ? ) { const [ opts , callback ] = getRealpathOptsAndCb ( a , b ) ; const pathFilename = pathToFilename ( path ) ; this . wrapAsync ( this . realpathBase , [ pathFilename , opts . encoding ] , callback ) ; } private lstatBase ( filename , bigint , throwIfNoEntry ) ; private lstatBase ( filename , bigint , throwIfNoEntry ) ; private lstatBase ( filename , bigint , throwIfNoEntry ) ; private lstatBase ( filename , bigint , throwIfNoEntry ) ; private lstatBase ( filename , bigint = false , throwIfNoEntry = false ) { const link = this . getLink ( filenameToSteps ( filename ) ) ; if ( link ) { return Stats . build ( link . getNode ( ) , bigint ) ; } else if ( ! throwIfNoEntry ) { return undefined ; } else { throw createError ( ENOENT , 'lstat' , filename ) ; } } lstatSync ( path ) ; lstatSync ( path , options ) ; lstatSync ( path , options ) ; lstatSync ( path , options ) ; lstatSync ( path , options ) ; lstatSync ( path , options ) ; lstatSync ( path , options ) ; lstatSync ( path , options ? ) { const { throwIfNoEntry = true , bigint = false } = getStatOptions ( options ) ; return this . lstatBase ( pathToFilename ( path ) , bigint as any , throwIfNoEntry as any ) ; } lstat ( path , callback ) ; lstat ( path , options , callback ) ; lstat ( path , a , b ? ) { const [ { throwIfNoEntry = true , bigint = false } , callback ] = getStatOptsAndCb ( a , b ) ; this . wrapAsync ( this . lstatBase , [ pathToFilename ( path ) , bigint , throwIfNoEntry ] , callback ) ; } private statBase ( filename ) ; private statBase ( filename , bigint , throwIfNoEntry ) ; private statBase ( filename , bigint , throwIfNoEntry ) ; private statBase ( filename , bigint , throwIfNoEntry ) ; private statBase ( filename , bigint , throwIfNoEntry ) ; private statBase ( filename , bigint = false , throwIfNoEntry = true ) { const link = this . getResolvedLink ( filenameToSteps ( filename ) ) ; if ( link ) { return Stats . build ( link . getNode ( ) , bigint ) ; } else if ( ! throwIfNoEntry ) { return undefined ; } else { throw createError ( ENOENT , 'stat' , filename ) ; } } statSync ( path ) ; statSync ( path , options ) ; statSync ( path , options ) ; statSync ( path , options ) ; statSync ( path , options ) ; statSync ( path , options ) ; statSync ( path , options ) ; statSync ( path , options ? ) { const { bigint = true , throwIfNoEntry = true } = getStatOptions ( options ) ; return this . statBase ( pathToFilename ( path ) , bigint as any , throwIfNoEntry as any ) ; } stat ( path , callback ) ; stat ( path , options , callback ) ; stat ( path , a , b ? ) { const [ { bigint = false , throwIfNoEntry = true } , callback ] = getStatOptsAndCb ( a , b ) ; this . wrapAsync ( this . statBase , [ pathToFilename ( path ) , bigint , throwIfNoEntry ] , callback ) ; } private fstatBase ( fd ) ; private fstatBase ( fd , bigint ) ; private fstatBase ( fd , bigint ) ; private fstatBase ( fd , bigint = false ) { const file = this . getFileByFd ( fd ) ; if ( ! file ) throw createError ( EBADF , 'fstat' ) ; return Stats . build ( file . node , bigint ) ; } fstatSync ( fd ) ; fstatSync ( fd , options ) ; fstatSync ( fd , options ) ; fstatSync ( fd , options ? ) { return this . fstatBase ( fd , getStatOptions ( options ) . bigint as any ) ; } fstat ( fd , callback ) ; fstat ( fd , options , callback ) ; fstat ( fd , a , b ? ) { const [ opts , callback ] = getStatOptsAndCb ( a , b ) ; this . wrapAsync ( this . fstatBase , [ fd , opts . bigint ] , callback ) ; } private renameBase ( oldPathFilename , newPathFilename ) { const link = this . getLink ( filenameToSteps ( oldPathFilename ) ) ; if ( ! link ) throw createError ( ENOENT , 'rename' , oldPathFilename , newPathFilename ) ; const newPathSteps = filenameToSteps ( newPathFilename ) ; const newPathDirLink = this . getLinkParent ( newPathSteps ) ; if ( ! newPathDirLink ) throw createError ( ENOENT , 'rename' , oldPathFilename , newPathFilename ) ; const oldLinkParent = link . parent ; if ( oldLinkParent ) { oldLinkParent . deleteChild ( link ) ; } const name = newPathSteps [ newPathSteps . length - 1 ] ; link . name = name ; link . steps = [ ... newPathDirLink . steps , name ] ; newPathDirLink . setChild ( link . getName ( ) , link ) ; } renameSync ( oldPath , newPath ) { const oldPathFilename = pathToFilename ( oldPath ) ; const newPathFilename = pathToFilename ( newPath ) ; this . renameBase ( oldPathFilename , newPathFilename ) ; } rename ( oldPath , newPath , callback ) { const oldPathFilename = pathToFilename ( oldPath ) ; const newPathFilename = pathToFilename ( newPath ) ; this . wrapAsync ( this . renameBase , [ oldPathFilename , newPathFilename ] , callback ) ; } private existsBase ( filename ) { return ! ! this . statBase ( filename ) ; } existsSync ( path ) { try { return this . existsBase ( pathToFilename ( path ) ) ; } catch ( err ) { return false ; } } exists ( path , callback ) { const filename = pathToFilename ( path ) ; if ( typeof callback !== 'function' ) throw Error ( ERRSTR . CB ) ; setImmediate ( ( ) => { try { callback ( this . existsBase ( filename ) ) ; } catch ( err ) { callback ( false ) ; } } ) ; } private accessBase ( filename , mode ) { const link = this . getLinkOrThrow ( filename , 'access' ) ; } accessSync ( path , mode = F_OK ) { const filename = pathToFilename ( path ) ; mode = mode | 0 ; this . accessBase ( filename , mode ) ; } access ( path , callback ) ; access ( path , mode , callback ) ; access ( path , a , b ? ) { let mode = F_OK ; let callback ; if ( typeof a !== 'function' ) { mode = a | 0 ; callback = validateCallback ( b ) ; } else { callback = a ; } const filename = pathToFilename ( path ) ; this . wrapAsync ( this . accessBase , [ filename , mode ] , callback ) ; } appendFileSync ( id , data , options = appendFileDefaults ) { const opts = getAppendFileOpts ( options ) ; if ( ! opts . flag || isFd ( id ) ) opts . flag = 'a' ; this . writeFileSync ( id , data , opts ) ; } appendFile ( id , data , callback ) ; appendFile ( id , data , options , callback ) ; appendFile ( id , data , a , b ? ) { const [ opts , callback ] = getAppendFileOptsAndCb ( a , b ) ; if ( ! opts . flag || isFd ( id ) ) opts . flag = 'a' ; this . writeFile ( id , data , opts , callback ) ; } private readdirBase ( filename , options ) { const steps = filenameToSteps ( filename ) ; const link = this . getResolvedLink ( steps ) ; if ( ! link ) throw createError ( ENOENT , 'readdir' , filename ) ; const node = link . getNode ( ) ; if ( ! node . isDirectory ( ) ) throw createError ( ENOTDIR , 'scandir' , filename ) ; if ( options . withFileTypes ) { const list = [ ] ; for ( const name in link . children ) { const child = link . getChild ( name ) ; if ( ! child ) { continue ; } list . push ( Dirent . build ( child , options . encoding ) ) ; } if ( ! 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 ; } const list = [ ] ; for ( const name in link . children ) { list . push ( strToEncoding ( name , options . encoding ) ) ; } if ( ! isWin && options . encoding !== 'buffer' ) list . sort ( ) ; return list ; } readdirSync ( path , options ? ) { const opts = getReaddirOptions ( options ) ; const filename = pathToFilename ( path ) ; return this . readdirBase ( filename , opts ) ; } readdir ( path , callback ) ; readdir ( path , options , callback ) ; readdir ( path , a ? , b ? ) { const [ options , callback ] = getReaddirOptsAndCb ( a , b ) ; const filename = pathToFilename ( path ) ; this . wrapAsync ( this . readdirBase , [ filename , options ] , callback ) ; } private readlinkBase ( filename , encoding ) { const link = this . getLinkOrThrow ( filename , 'readlink' ) ; const node = link . getNode ( ) ; if ( ! node . isSymlink ( ) ) throw createError ( EINVAL , 'readlink' , filename ) ; const str = sep + node . symlink . join ( sep ) ; return strToEncoding ( str , encoding ) ; } readlinkSync ( path , options ? ) { const opts = getDefaultOpts ( options ) ; const filename = pathToFilename ( path ) ; return this . readlinkBase ( filename , opts . encoding ) ; } readlink ( path , callback ) ; readlink ( path , options , callback ) ; readlink ( path , a , b ? ) { const [ opts , callback ] = getDefaultOptsAndCb ( a , b ) ; const filename = pathToFilename ( path ) ; this . wrapAsync ( this . readlinkBase , [ filename , opts . encoding ] , callback ) ; } private fsyncBase ( fd ) { this . getFileByFdOrThrow ( fd , 'fsync' ) ; } fsyncSync ( fd ) { this . fsyncBase ( fd ) ; } fsync ( fd , callback ) { this . wrapAsync ( this . fsyncBase , [ fd ] , callback ) ; } private fdatasyncBase ( fd ) { this . getFileByFdOrThrow ( fd , 'fdatasync' ) ; } fdatasyncSync ( fd ) { this . fdatasyncBase ( fd ) ; } fdatasync ( fd , callback ) { this . wrapAsync ( this . fdatasyncBase , [ fd ] , callback ) ; } private ftruncateBase ( fd , len ? ) { const file = this . getFileByFdOrThrow ( fd , 'ftruncate' ) ; file . truncate ( len ) ; } ftruncateSync ( fd , len ? ) { this . ftruncateBase ( fd , len ) ; } ftruncate ( fd , callback ) ; ftruncate ( fd , len , callback ) ; ftruncate ( fd , a , b ? ) { const len = typeof a === 'number' ? a : 0 ; const callback = validateCallback ( typeof a === 'number' ? b : a ) ; this . wrapAsync ( this . ftruncateBase , [ fd , len ] , callback ) ; } private truncateBase ( path , len ? ) { const fd = this . openSync ( path , 'r+' ) ; try { this . ftruncateSync ( fd , len ) ; } finally { this . closeSync ( fd ) ; } } truncateSync ( id , len ? ) { if ( isFd ( id ) ) return this . ftruncateSync ( id as number , len ) ; this . truncateBase ( id as PathLike , len ) ; } truncate ( id , callback ) ; truncate ( id , len , callback ) ; truncate ( id , a , b ? ) { const len = typeof a === 'number' ? a : 0 ; const callback = validateCallback ( typeof a === 'number' ? b : a ) ; if ( isFd ( id ) ) return this . ftruncate ( id as number , len , callback ) ; this . wrapAsync ( this . truncateBase , [ id , len ] , callback ) ; } private futimesBase ( fd , atime , mtime ) { const file = this . getFileByFdOrThrow ( fd , 'futimes' ) ; const node = file . node ; node . atime = new Date ( atime * 1000 ) ; node . mtime = new Date ( mtime * 1000 ) ; } futimesSync ( fd , atime , mtime ) { this . futimesBase ( fd , toUnixTimestamp ( atime ) , toUnixTimestamp ( mtime ) ) ; } futimes ( fd , atime , mtime , callback ) { this . wrapAsync ( this . futimesBase , [ fd , toUnixTimestamp ( atime ) , toUnixTimestamp ( mtime ) ] , callback ) ; } private utimesBase ( filename , atime , mtime ) { const fd = this . openSync ( filename , 'r+' ) ; try { this . futimesBase ( fd , atime , mtime ) ; } finally { this . closeSync ( fd ) ; } } utimesSync ( path , atime , mtime ) { this . utimesBase ( pathToFilename ( path ) , toUnixTimestamp ( atime ) , toUnixTimestamp ( mtime ) ) ; } utimes ( path , atime , mtime , callback ) { this . wrapAsync ( this . utimesBase , [ pathToFilename ( path ) , toUnixTimestamp ( atime ) , toUnixTimestamp ( mtime ) ] , callback ) ; } private mkdirBase ( filename , modeNum ) { const steps = filenameToSteps ( filename ) ; if ( ! steps . length ) { throw createError ( EEXIST , 'mkdir' , filename ) ; } const dir = this . getLinkParentAsDirOrThrow ( filename , 'mkdir' ) ; const name = steps [ steps . length - 1 ] ; if ( dir . getChild ( name ) ) throw createError ( EEXIST , 'mkdir' , filename ) ; dir . createChild ( name , this . createNode ( true , modeNum ) ) ; } param filename param modeNum private mkdirpBase ( filename , modeNum ) { const steps = filenameToSteps ( filename ) ; let link = this . root ; for ( let i = 0 ; i < steps . length ; i ++ ) { const step = steps [ i ] ; if ( ! link . getNode ( ) . isDirectory ( ) ) throw createError ( ENOTDIR , 'mkdir' , link . getPath ( ) ) ; const child = link . getChild ( step ) ; if ( child ) { if ( child . getNode ( ) . isDirectory ( ) ) link = child ; else throw createError ( ENOTDIR , 'mkdir' , child . getPath ( ) ) ; } else { link = link . createChild ( step , this . createNode ( true , modeNum ) ) ; } } } mkdirSync ( path , options ? ) { const opts = getMkdirOptions ( options ) ; const modeNum = modeToNumber ( opts . mode , 0o777 ) ; const filename = pathToFilename ( path ) ; if ( opts . recursive ) this . mkdirpBase ( filename , modeNum ) ; else this . mkdirBase ( filename , modeNum ) ; } mkdir ( path , callback ) ; mkdir ( path , mode , callback ) ; mkdir ( path , a , b ? ) { const opts = getMkdirOptions ( a ) ; const callback = validateCallback ( typeof a === 'function' ? a : b ) ; const modeNum = modeToNumber ( opts . mode , 0o777 ) ; const filename = pathToFilename ( path ) ; if ( opts . recursive ) this . wrapAsync ( this . mkdirpBase , [ filename , modeNum ] , callback ) ; else this . wrapAsync ( this . mkdirBase , [ filename , modeNum ] , callback ) ; } mkdirpSync ( path , mode ? ) { this . mkdirSync ( path , { mode , recursive : true } ) ; } mkdirp ( path , callback ) ; mkdirp ( path , mode , callback ) ; mkdirp ( path , a , b ? ) { const mode = typeof a === 'function' ? undefined : a ; const callback = validateCallback ( typeof a === 'function' ? a : b ) ; this . mkdir ( path , { mode , recursive : true } , callback ) ; } private mkdtempBase ( prefix , encoding ? , retry = 5 ) { const filename = prefix + this . genRndStr ( ) ; try { this . mkdirBase ( filename , MODE . DIR ) ; return strToEncoding ( filename , encoding ) ; } catch ( err ) { if ( err . code === EEXIST ) { if ( retry > 1 ) return this . mkdtempBase ( prefix , encoding , retry - 1 ) ; else throw Error ( 'Could not create temp dir.' ) ; } else throw err ; } } mkdtempSync ( prefix , options ? ) { const { encoding } = getDefaultOpts ( options ) ; if ( ! prefix || typeof prefix !== 'string' ) throw new TypeError ( 'filename prefix is required' ) ; nullCheck ( prefix ) ; return this . mkdtempBase ( prefix , encoding ) ; } mkdtemp ( prefix , callback ) ; mkdtemp ( prefix , options , callback ) ; mkdtemp ( prefix , a , b ? ) { const [ { encoding } , callback ] = getDefaultOptsAndCb ( a , b ) ; if ( ! prefix || typeof prefix !== 'string' ) throw new TypeError ( 'filename prefix is required' ) ; if ( ! nullCheck ( prefix ) ) return ; this . wrapAsync ( this . mkdtempBase , [ prefix , encoding ] , callback ) ; } private rmdirBase ( filename , options ? ) { const opts = getRmdirOptions ( options ) ; const link = this . getLinkAsDirOrThrow ( filename , 'rmdir' ) ; if ( link . length && ! opts . recursive ) throw createError ( ENOTEMPTY , 'rmdir' , filename ) ; this . deleteLink ( link ) ; } rmdirSync ( path , options ? ) { this . rmdirBase ( pathToFilename ( path ) , options ) ; } rmdir ( path , callback ) ; rmdir ( path , options , callback ) ; rmdir ( path , a , b ? ) { const opts = getRmdirOptions ( a ) ; const callback = validateCallback ( typeof a === 'function' ? a : b ) ; this . wrapAsync ( this . rmdirBase , [ pathToFilename ( path ) , opts ] , callback ) ; } private rmBase ( filename , options = { } ) { const link = this . getResolvedLink ( filename ) ; if ( ! link ) { if ( ! options . force ) throw createError ( ENOENT , 'stat' , filename ) ; return ; } if ( link . getNode ( ) . isDirectory ( ) ) { if ( ! options . recursive ) { throw createError ( ERR_FS_EISDIR , 'rm' , filename ) ; } } this . deleteLink ( link ) ; } public rmSync ( path , options ? ) { this . rmBase ( pathToFilename ( path ) , options ) ; } public rm ( path , callback ) ; public rm ( path , options , callback ) ; public rm ( path , a , b ? ) { const [ opts , callback ] = getRmOptsAndCb ( a , b ) ; this . wrapAsync ( this . rmBase , [ pathToFilename ( path ) , opts ] , callback ) ; } private fchmodBase ( fd , modeNum ) { const file = this . getFileByFdOrThrow ( fd , 'fchmod' ) ; file . chmod ( modeNum ) ; } fchmodSync ( fd , mode ) { this . fchmodBase ( fd , modeToNumber ( mode ) ) ; } fchmod ( fd , mode , callback ) { this . wrapAsync ( this . fchmodBase , [ fd , modeToNumber ( mode ) ] , callback ) ; } private chmodBase ( filename , modeNum ) { const fd = this . openSync ( filename , 'r+' ) ; try { this . fchmodBase ( fd , modeNum ) ; } finally { this . closeSync ( fd ) ; } } chmodSync ( path , mode ) { const modeNum = modeToNumber ( mode ) ; const filename = pathToFilename ( path ) ; this . chmodBase ( filename , modeNum ) ; } chmod ( path , mode , callback ) { const modeNum = modeToNumber ( mode ) ; const filename = pathToFilename ( path ) ; this . wrapAsync ( this . chmodBase , [ filename , modeNum ] , callback ) ; } private lchmodBase ( filename , modeNum ) { const fd = this . openBase ( filename , O_RDWR , 0 , false ) ; try { this . fchmodBase ( fd , modeNum ) ; } finally { this . closeSync ( fd ) ; } } lchmodSync ( path , mode ) { const modeNum = modeToNumber ( mode ) ; const filename = pathToFilename ( path ) ; this . lchmodBase ( filename , modeNum ) ; } lchmod ( path , mode , callback ) { const modeNum = modeToNumber ( mode ) ; const filename = pathToFilename ( path ) ; this . wrapAsync ( this . lchmodBase , [ filename , modeNum ] , callback ) ; } private fchownBase ( fd , uid , gid ) { this . getFileByFdOrThrow ( fd , 'fchown' ) . chown ( uid , gid ) ; } fchownSync ( fd , uid , gid ) { validateUid ( uid ) ; validateGid ( gid ) ; this . fchownBase ( fd , uid , gid ) ; } fchown ( fd , uid , gid , callback ) { validateUid ( uid ) ; validateGid ( gid ) ; this . wrapAsync ( this . fchownBase , [ fd , uid , gid ] , callback ) ; } private chownBase ( filename , uid , gid ) { const link = this . getResolvedLinkOrThrow ( filename , 'chown' ) ; const node = link . getNode ( ) ; node . chown ( uid , gid ) ; } chownSync ( path , uid , gid ) { validateUid ( uid ) ; validateGid ( gid ) ; this . chownBase ( pathToFilename ( path ) , uid , gid ) ; } chown ( path , uid , gid , callback ) { validateUid ( uid ) ; validateGid ( gid ) ; this . wrapAsync ( this . chownBase , [ pathToFilename ( path ) , uid , gid ] , callback ) ; } private lchownBase ( filename , uid , gid ) { this . getLinkOrThrow ( filename , 'lchown' ) . getNode ( ) . chown ( uid , gid ) ; } lchownSync ( path , uid , gid ) { validateUid ( uid ) ; validateGid ( gid ) ; this . lchownBase ( pathToFilename ( path ) , uid , gid ) ; } lchown ( path , uid , gid , callback ) { validateUid ( uid ) ; validateGid ( gid ) ; this . wrapAsync ( this . lchownBase , [ pathToFilename ( path ) , uid , gid ] , callback ) ; } private statWatchers : Record < string , StatWatcher > = { } ; watchFile ( path , listener ) ; watchFile ( path , options , listener ) ; watchFile ( path , a , b ? ) { const filename = pathToFilename ( path ) ; let options = a ; let listener = b ; if ( typeof options === 'function' ) { listener = a ; options = null ; } if ( typeof listener !== 'function' ) { throw Error ( '"watchFile()" requires a listener function' ) ; } let interval = 5007 ; let persistent = true ; if ( options && typeof options === 'object' ) { if ( typeof options . interval === 'number' ) interval = options . interval ; if ( typeof options . persistent === 'boolean' ) persistent = options . persistent ; } let watcher = this . statWatchers [ filename ] ; if ( ! watcher ) { watcher = new this . StatWatcher ( ) ; watcher . start ( filename , persistent , interval ) ; this . statWatchers [ filename ] = watcher ; } watcher . addListener ( 'change' , listener ) ; return watcher ; } unwatchFile ( path , listener ? ) { const filename = pathToFilename ( path ) ; const watcher = this . statWatchers [ filename ] ; if ( ! watcher ) return ; if ( typeof listener === 'function' ) { watcher . removeListener ( 'change' , listener ) ; } else { watcher . removeAllListeners ( 'change' ) ; } if ( watcher . listenerCount ( 'change' ) === 0 ) { watcher . stop ( ) ; delete this . statWatchers [ filename ] ; } } createReadStream ( path , options ? ) { return new this . ReadStream ( path , options ) ; } createWriteStream ( path , options ? ) { return new this . WriteStream ( path , options ) ; } watch ( path , options ? , listener ? ) { const filename = pathToFilename ( path ) ; let givenOptions = options ; if ( typeof options === 'function' ) { listener = options ; givenOptions = null ; } let { persistent , recursive , encoding } = getDefaultOpts ( givenOptions ) ; if ( persistent === undefined ) persistent = true ; if ( recursive === undefined ) recursive = false ; const watcher = new this . FSWatcher ( ) ; watcher . start ( filename , persistent , recursive , encoding as BufferEncoding ) ; if ( listener ) { watcher . addListener ( 'change' , listener ) ; } return watcher ; } } function emitStop ( self ) { self . emit ( 'stop' ) ; } export class StatWatcher extends EventEmitter { vol : Volume ; filename : string ; interval : number ; timeoutRef ? ; setTimeout : TSetTimeout ; prev : Stats ; constructor ( vol ) { super ( ) ; this . vol = vol ; } private loop ( ) { this . timeoutRef = this . setTimeout ( this . onInterval , this . interval ) ; } private hasChanged ( stats ) { if ( stats . mtimeMs > this . prev . mtimeMs ) return true ; if ( stats . nlink !== this . prev . nlink ) return true ; return false ; } private onInterval = ( ) => { try { const stats = this . vol . statSync ( this . filename ) ; if ( this . hasChanged ( stats ) ) { this . emit ( 'change' , stats , this . prev ) ; this . prev = stats ; } } finally { this . loop ( ) ; } } ; start ( path , persistent = true , interval = 5007 ) { this . filename = pathToFilename ( path ) ; this . setTimeout = persistent ? setTimeout . bind ( typeof globalThis !== 'undefined' ? globalThis : global ) : setTimeoutUnref ; this . interval = interval ; this . prev = this . vol . statSync ( this . filename ) ; this . loop ( ) ; } stop ( ) { clearTimeout ( this . timeoutRef ) ; process . nextTick ( emitStop , this ) ; } } export interface IReadStream extends Readable { new ( path , options ) ; open ( ) ; close ( callback ) ; bytesRead : number ; path : string ; } var pool ; function allocNewPool ( poolSize ) { pool = bufferAllocUnsafe ( poolSize ) ; pool . used = 0 ; } util . inherits ( FsReadStream , Readable ) ; exports . ReadStream = FsReadStream ; function FsReadStream ( vol , path , options ) { if ( ! ( this instanceof FsReadStream ) ) return new ( FsReadStream as any ) ( vol , path , options ) ; this . _vol = vol ; options = Object . assign ( { } , getOptions ( options , { } ) ) ; if ( options . highWaterMark === undefined ) options . highWaterMark = 64 * 1024 ; Readable . call ( this , options ) ; this . path = pathToFilename ( path ) ; this . fd = options . fd === undefined ? null : options . fd ; this . flags = options . flags === undefined ? 'r' : options . flags ; this . mode = options . mode === undefined ? 0o666 : options . mode ; this . start = options . start ; this . end = options . end ; this . autoClose = options . autoClose === undefined ? true : options . autoClose ; this . pos = undefined ; this . bytesRead = 0 ; if ( this . start !== undefined ) { if ( typeof this . start !== 'number' ) { throw new TypeError ( '"start" option must be a Number' ) ; } if ( this . end === undefined ) { this . end = Infinity ; } else if ( typeof this . end !== 'number' ) { throw new TypeError ( '"end" option must be a Number' ) ; } if ( this . start > this . end ) { throw new Error ( '"start" option must be <= "end" option' ) ; } this . pos = this . start ; } if ( typeof this . fd !== 'number' ) this . open ( ) ; this . on ( 'end' , function ( ) { if ( this . autoClose ) { if ( this . destroy ) this . destroy ( ) ; } } ) ; } FsReadStream . prototype . open = function ( ) { var self = this ; this . _vol . open ( this . path , this . flags , this . mode , ( er , fd ) => { if ( er ) { if ( self . autoClose ) { if ( self . destroy ) self . destroy ( ) ; } self . emit ( 'error' , er ) ; return ; } self . fd = fd ; self . emit ( 'open' , fd ) ; self . read ( ) ; } ) ; } ; FsReadStream . prototype . _read = function ( n ) { if ( typeof this . fd !== 'number' ) { return this . once ( 'open' , function ( ) { this . _read ( n ) ; } ) ; } if ( this . destroyed ) return ; if ( ! pool || pool . length - pool . used < kMinPoolSpace ) { allocNewPool ( this . _readableState . highWaterMark ) ; } var thisPool = pool ; var toRead = Math . min ( pool . length - pool . used , n ) ; var start = pool . used ; if ( this . pos !== undefined ) toRead = Math . min ( this . end - this . pos + 1 , toRead ) ; if ( toRead <= 0 ) return this . push ( null ) ; var self = this ; this . _vol . read ( this . fd , pool , pool . used , toRead , this . pos , onread ) ; if ( this . pos !== undefined ) this . pos += toRead ; pool . used += toRead ; function onread ( er , bytesRead ) { if ( er ) { if ( self . autoClose && self . destroy ) { self . destroy ( ) ; } self . emit ( 'error' , er ) ; } else { var b = null ; if ( bytesRead > 0 ) { self . bytesRead += bytesRead ; b = thisPool . slice ( start , start + bytesRead ) ; } self . push ( b ) ; } } } ; FsReadStream . prototype . _destroy = function ( err , cb ) { this . close ( err2 => { cb ( err || err2 ) ; } ) ; } ; FsReadStream . prototype . close = function ( cb ) { if ( cb ) this . once ( 'close' , cb ) ; if ( this . closed || typeof this . fd !== 'number' ) { if ( typeof this . fd !== 'number' ) { this . once ( 'open' , closeOnOpen ) ; return ; } return process . nextTick ( ( ) => this . emit ( 'close' ) ) ; } this . closed = true ; this . _vol . close ( this . fd , er => { if ( er ) this . emit ( 'error' , er ) ; else this . emit ( 'close' ) ; } ) ; this . fd = null ; } ; function closeOnOpen ( fd ) { this . close ( ) ; } export interface IWriteStream extends Writable { bytesWritten : number ; path : string ; new ( path , options ) ; open ( ) ; close ( ) ; } util . inherits ( FsWriteStream , Writable ) ; exports . WriteStream = FsWriteStream ; function FsWriteStream ( vol , path , options ) { if ( ! ( this instanceof FsWriteStream ) ) return new ( FsWriteStream as any ) ( vol , path , options ) ; this . _vol = vol ; options = Object . assign ( { } , getOptions ( options , { } ) ) ; Writable . call ( this , options ) ; this . path = pathToFilename ( path ) ; this . fd = options . fd === undefined ? null : options . fd ; this . flags = options . flags === undefined ? 'w' : options . flags ; this . mode = options . mode === undefined ? 0o666 : options . mode ; this . start = options . start ; this . autoClose = options . autoClose === undefined ? true : ! ! options . autoClose ; this . pos = undefined ; this . bytesWritten = 0 ; if ( this . start !== undefined ) { if ( typeof this . start !== 'number' ) { throw new TypeError ( '"start" option must be a Number' ) ; } if ( this . start < 0 ) { throw new Error ( '"start" must be >= zero' ) ; } this . pos = this . start ; } if ( options . encoding ) this . setDefaultEncoding ( options . encoding ) ; if ( typeof this . fd !== 'number' ) this . open ( ) ; this . once ( 'finish' , function ( ) { if ( this . autoClose ) { this . close ( ) ; } } ) ; } FsWriteStream . prototype . open = function ( ) { this . _vol . open ( this . path , this . flags , this . mode , function ( er , fd ) { if ( er ) { if ( this . autoClose && this . destroy ) { this . destroy ( ) ; } this . emit ( 'error' , er ) ; return ; } this . fd = fd ; this . emit ( 'open' , fd ) ; } . bind ( this ) ) ; } ; FsWriteStream . prototype . _write = function ( data , encoding , cb ) { if ( ! ( data instanceof Buffer ) ) return this . emit ( 'error' , new Error ( 'Invalid data' ) ) ; if ( typeof this . fd !== 'number' ) { return this . once ( 'open' , function ( ) { this . _write ( data , encoding , cb ) ; } ) ; } var self = this ; this . _vol . write ( this . fd , data , 0 , data . length , this . pos , ( er , bytes ) => { if ( er ) { if ( self . autoClose && self . destroy ) { self . destroy ( ) ; } return cb ( er ) ; } self . bytesWritten += bytes ; cb ( ) ; } ) ; if ( this . pos !== undefined ) this . pos += data . length ; } ; FsWriteStream . prototype . _writev = function ( data , cb ) { if ( typeof this . fd !== 'number' ) { return this . once ( 'open' , function ( ) { this . _writev ( data , cb ) ; } ) ; } const self = this ; const len = data . length ; const chunks = new Array ( len ) ; var size = 0 ; for ( var i = 0 ; i < len ; i ++ ) { var chunk = data [ i ] . chunk ; chunks [ i ] = chunk ; size += chunk . length ; } const buf = Buffer . concat ( chunks ) ; this . _vol . write ( this . fd , buf , 0 , buf . length , this . pos , ( er , bytes ) => { if ( er ) { if ( self . destroy ) self . destroy ( ) ; return cb ( er ) ; } self . bytesWritten += bytes ; cb ( ) ; } ) ; if ( this . pos !== undefined ) this . pos += size ; } ; FsWriteStream . prototype . _destroy = FsReadStream . prototype . _destroy ; FsWriteStream . prototype . close = FsReadStream . prototype . close ; FsWriteStream . prototype . destroySoon = FsWriteStream . prototype . end ; export class FSWatcher extends EventEmitter { _vol : Volume ; _filename : string = '' ; _steps : string [ ] ; _filenameEncoded : TDataOut = '' ; _recursive : boolean = false ; _encoding : BufferEncoding = ENCODING_UTF8 ; _link : Link ; _timer ; constructor ( vol ) { super ( ) ; this . _vol = vol ; } private _getName ( ) { return this . _steps [ this . _steps . length - 1 ] ; } private _onNodeChange = ( ) => { this . _emit ( 'change' ) ; } ; private _onParentChild = ( link ) => { if ( link . getName ( ) === this . _getName ( ) ) { this . _emit ( 'rename' ) ; } } ; private _emit = ( type ) => { this . emit ( 'change' , type , this . _filenameEncoded ) ; } ; private _persist = ( ) => { this . _timer = setTimeout ( this . _persist , 1e6 ) ; } ; start ( path , persistent = true , recursive = false , encoding = ENCODING_UTF8 ) { this . _filename = pathToFilename ( path ) ; this . _steps = filenameToSteps ( this . _filename ) ; this . _filenameEncoded = strToEncoding ( this . _filename ) ; this . _recursive = recursive ; this . _encoding = encoding ; try { this . _link = this . _vol . getLinkOrThrow ( this . _filename , 'FSWatcher' ) ; } catch ( err ) { const error = new Error ( `watch ${ this . _filename } ${ err . code }` ) ; ( error as any ) . code = err . code ; ( error as any ) . errno = err . code ; throw error ; } this . _link . getNode ( ) . on ( 'change' , this . _onNodeChange ) ; this . _link . on ( 'child:add' , this . _onNodeChange ) ; this . _link . on ( 'child:delete' , this . _onNodeChange ) ; const parent = this . _link . parent ; if ( parent ) { parent . setMaxListeners ( parent . getMaxListeners ( ) + 1 ) ; parent . on ( 'child:delete' , this . _onParentChild ) ; } if ( persistent ) this . _persist ( ) ; } close ( ) { clearTimeout ( this . _timer ) ; this . _link . getNode ( ) . removeListener ( 'change' , this . _onNodeChange ) ; const parent = this . _link . parent ; if ( parent ) { parent . removeListener ( 'child:delete' , this . _onParentChild ) ; } } }