Skip to content

Commit 6ef63d5

Browse files
committed
feat: add promises API
Fixes #147
1 parent a8c8998 commit 6ef63d5

File tree

7 files changed

+949
-4
lines changed

7 files changed

+949
-4
lines changed

src/__tests__/index.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,7 @@ describe('memfs', () => {
3535
expect(typeof memfs[method]).toBe('function');
3636
}
3737
});
38+
it('Exports promises API', () => {
39+
expect(typeof memfs.promises).toBe('object');
40+
});
3841
});

src/__tests__/promises.test.ts

Lines changed: 629 additions & 0 deletions
Large diffs are not rendered by default.

src/__tests__/volume.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,12 @@ describe('volume', () => {
936936
}, 1);
937937
});
938938
});
939+
describe('.promises', () => {
940+
it('Have a promises property', () => {
941+
const vol = new Volume;
942+
expect(typeof vol.promises).toBe('object');
943+
});
944+
});
939945
});
940946
describe('StatWatcher', () => {
941947
it('.vol points to current volume', () => {

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Stats, Dirent} from './node';
22
import {Volume as _Volume, StatWatcher, FSWatcher, toUnixTimestamp, IReadStream, IWriteStream} from './volume';
33
import * as volume from './volume';
4+
import { IPromisesAPI } from './promises';
45
const {fsSyncMethods, fsAsyncMethods} = require('fs-monkey/lib/util/lists');
56
import {constants} from './constants';
67
const {F_OK, R_OK, W_OK, X_OK} = constants;
@@ -21,6 +22,7 @@ export interface IFs extends _Volume {
2122
FSWatcher: new () => FSWatcher,
2223
ReadStream: new (...args) => IReadStream,
2324
WriteStream: new (...args) => IWriteStream,
25+
promises: IPromisesAPI,
2426
_toUnixTimestamp,
2527
}
2628

@@ -39,6 +41,7 @@ export function createFsFromVolume(vol: _Volume): IFs {
3941
fs.FSWatcher = vol.FSWatcher;
4042
fs.WriteStream = vol.WriteStream;
4143
fs.ReadStream = vol.ReadStream;
44+
fs.promises = vol.promises;
4245

4346
fs._toUnixTimestamp = toUnixTimestamp;
4447

src/promises.ts

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
import {
2+
Volume,
3+
TFilePath,
4+
TData,
5+
TMode,
6+
TFlags,
7+
TFlagsCopy,
8+
TSymlinkType,
9+
TTime,
10+
IOptions,
11+
IAppendFileOptions,
12+
IMkdirOptions,
13+
IReaddirOptions,
14+
IReadFileOptions,
15+
IRealpathOptions,
16+
IWriteFileOptions,
17+
} from './volume';
18+
import { Stats, Dirent } from './node';
19+
import { TDataOut } from './encoding';
20+
21+
function promisify(
22+
vol: Volume,
23+
fn: string,
24+
getResult: (result: any) => any = input => input,
25+
): (...args) => Promise<any> {
26+
return (...args) => new Promise((resolve, reject) => {
27+
vol[fn].bind(vol)(...args, (error, result) => {
28+
if (error) return reject(error);
29+
return resolve(getResult(result));
30+
});
31+
});
32+
}
33+
34+
export type TFileHandleReadResult = {
35+
bytesRead: number,
36+
buffer: Buffer | Uint8Array,
37+
};
38+
39+
export type TFileHandleWriteResult = {
40+
bytesWritten: number,
41+
buffer: Buffer | Uint8Array,
42+
};
43+
44+
export interface IFileHandle {
45+
fd: number;
46+
appendFile(data: TData, options?: IAppendFileOptions | string): Promise<void>,
47+
chmod(mode: TMode): Promise<void>,
48+
chown(uid: number, gid: number): Promise<void>,
49+
close(): Promise<void>,
50+
datasync(): Promise<void>,
51+
read(
52+
buffer: Buffer | Uint8Array,
53+
offset: number,
54+
length: number,
55+
position: number,
56+
): Promise<TFileHandleReadResult>,
57+
readFile(options?: IReadFileOptions|string): Promise<TDataOut>,
58+
stat(): Promise<Stats>,
59+
truncate(len?: number): Promise<void>,
60+
utimes(atime: TTime, mtime: TTime): Promise<void>,
61+
write(
62+
buffer: Buffer | Uint8Array,
63+
offset?: number,
64+
length?: number,
65+
position?: number,
66+
): Promise<TFileHandleWriteResult>,
67+
writeFile(data: TData, options?: IWriteFileOptions): Promise<void>,
68+
}
69+
70+
export type TFileHandle = TFilePath | IFileHandle;
71+
72+
export interface IPromisesAPI {
73+
FileHandle,
74+
access(path: TFilePath, mode?: number): Promise<void>,
75+
appendFile(path: TFileHandle, data: TData, options?: IAppendFileOptions | string): Promise<void>,
76+
chmod(path: TFilePath, mode: TMode): Promise<void>,
77+
chown(path: TFilePath, uid: number, gid: number): Promise<void>,
78+
copyFile(src: TFilePath, dest: TFilePath, flags?: TFlagsCopy): Promise<void>,
79+
lchmod(path: TFilePath, mode: TMode): Promise<void>,
80+
lchown(path: TFilePath, uid: number, gid: number): Promise<void>,
81+
link(existingPath: TFilePath, newPath: TFilePath): Promise<void>,
82+
lstat(path: TFilePath): Promise<Stats>,
83+
mkdir(path: TFilePath, options?: TMode | IMkdirOptions): Promise<void>,
84+
mkdtemp(prefix: string, options?: IOptions): Promise<TDataOut>,
85+
open(path: TFilePath, flags: TFlags, mode?: TMode): Promise<FileHandle>,
86+
readdir(path: TFilePath, options?: IReaddirOptions | string): Promise<TDataOut[]|Dirent[]>,
87+
readFile(id: TFileHandle, options?: IReadFileOptions|string): Promise<TDataOut>,
88+
readlink(path: TFilePath, options?: IOptions): Promise<TDataOut>,
89+
realpath(path: TFilePath, options?: IRealpathOptions | string): Promise<TDataOut>,
90+
rename(oldPath: TFilePath, newPath: TFilePath): Promise<void>,
91+
rmdir(path: TFilePath): Promise<void>,
92+
stat(path: TFilePath): Promise<Stats>,
93+
symlink(target: TFilePath, path: TFilePath, type?: TSymlinkType): Promise<void>,
94+
truncate(path: TFilePath, len?: number): Promise<void>,
95+
unlink(path: TFilePath): Promise<void>,
96+
utimes(path: TFilePath, atime: TTime, mtime: TTime): Promise<void>,
97+
writeFile(id: TFileHandle, data: TData, options?: IWriteFileOptions): Promise<void>,
98+
}
99+
100+
export class FileHandle implements IFileHandle {
101+
private vol: Volume;
102+
103+
fd: number;
104+
105+
constructor(vol: Volume, fd: number) {
106+
this.vol = vol;
107+
this.fd = fd;
108+
}
109+
110+
appendFile(data: TData, options?: IAppendFileOptions | string): Promise<void> {
111+
return promisify(this.vol, 'appendFile')(this.fd, data, options);
112+
}
113+
114+
chmod(mode: TMode): Promise<void> {
115+
return promisify(this.vol, 'fchmod')(this.fd, mode);
116+
}
117+
118+
chown(uid: number, gid: number): Promise<void> {
119+
return promisify(this.vol, 'fchown')(this.fd, uid, gid);
120+
}
121+
122+
close(): Promise<void> {
123+
return promisify(this.vol, 'close')(this.fd);
124+
}
125+
126+
datasync(): Promise<void> {
127+
return promisify(this.vol, 'fdatasync')(this.fd);
128+
}
129+
130+
read(
131+
buffer: Buffer | Uint8Array,
132+
offset: number,
133+
length: number,
134+
position: number,
135+
): Promise<TFileHandleReadResult> {
136+
return promisify(this.vol, 'read', bytesRead => ({ bytesRead, buffer }))(
137+
this.fd,
138+
buffer,
139+
offset,
140+
length,
141+
position
142+
);
143+
}
144+
145+
readFile(options?: IReadFileOptions|string): Promise<TDataOut> {
146+
return promisify(this.vol, 'readFile')(this.fd, options);
147+
}
148+
149+
stat(): Promise<Stats> {
150+
return promisify(this.vol, 'fstat')(this.fd);
151+
}
152+
153+
sync(): Promise<void> {
154+
return promisify(this.vol, 'fsync')(this.fd);
155+
}
156+
157+
truncate(len?: number): Promise<void> {
158+
return promisify(this.vol, 'ftruncate')(this.fd, len);
159+
}
160+
161+
utimes(atime: TTime, mtime: TTime): Promise<void> {
162+
return promisify(this.vol, 'futimes')(this.fd, atime, mtime);
163+
}
164+
165+
write(
166+
buffer: Buffer | Uint8Array,
167+
offset?: number,
168+
length?: number,
169+
position?: number,
170+
): Promise<TFileHandleWriteResult> {
171+
return promisify(this.vol, 'write', bytesWritten => ({ bytesWritten, buffer }))(
172+
this.fd,
173+
buffer,
174+
offset,
175+
length,
176+
position,
177+
);
178+
}
179+
180+
writeFile(data: TData, options?: IWriteFileOptions): Promise<void> {
181+
return promisify(this.vol, 'writeFile')(this.fd, data, options);
182+
}
183+
}
184+
185+
export default function createPromisesApi(vol: Volume): IPromisesAPI {
186+
return {
187+
FileHandle,
188+
189+
access(path: TFilePath, mode?: number): Promise<void> {
190+
return promisify(vol, 'access')(path, mode);
191+
},
192+
193+
appendFile(path: TFileHandle, data: TData, options?: IAppendFileOptions | string): Promise<void> {
194+
return promisify(vol, 'appendFile')(
195+
path instanceof FileHandle ? path.fd : path as TFilePath,
196+
data,
197+
options,
198+
);
199+
},
200+
201+
chmod(path: TFilePath, mode: TMode): Promise<void> {
202+
return promisify(vol, 'chmod')(path, mode);
203+
},
204+
205+
chown(path: TFilePath, uid: number, gid: number): Promise<void> {
206+
return promisify(vol, 'chown')(path, uid, gid);
207+
},
208+
209+
copyFile(src: TFilePath, dest: TFilePath, flags?: TFlagsCopy): Promise<void> {
210+
return promisify(vol, 'copyFile')(src, dest, flags);
211+
},
212+
213+
lchmod(path: TFilePath, mode: TMode): Promise<void> {
214+
return promisify(vol, 'lchmod')(path, mode);
215+
},
216+
217+
lchown(path: TFilePath, uid: number, gid: number): Promise<void> {
218+
return promisify(vol, 'lchown')(path, uid, gid);
219+
},
220+
221+
link(existingPath: TFilePath, newPath: TFilePath): Promise<void> {
222+
return promisify(vol, 'link')(existingPath, newPath);
223+
},
224+
225+
lstat(path: TFilePath): Promise<Stats> {
226+
return promisify(vol, 'lstat')(path);
227+
},
228+
229+
mkdir(path: TFilePath, options?: TMode | IMkdirOptions): Promise<void> {
230+
return promisify(vol, 'mkdir')(path, options);
231+
},
232+
233+
mkdtemp(prefix: string, options?: IOptions): Promise<TDataOut> {
234+
return promisify(vol, 'mkdtemp')(prefix, options);
235+
},
236+
237+
open(path: TFilePath, flags: TFlags, mode?: TMode): Promise<FileHandle> {
238+
return promisify(vol, 'open', fd => new FileHandle(vol, fd))(path, flags, mode);
239+
},
240+
241+
readdir(path: TFilePath, options?: IReaddirOptions | string): Promise<TDataOut[]|Dirent[]> {
242+
return promisify(vol, 'readdir')(path, options);
243+
},
244+
245+
readFile(id: TFileHandle, options?: IReadFileOptions|string): Promise<TDataOut> {
246+
return promisify(vol, 'readFile')(
247+
id instanceof FileHandle ? id.fd : id as TFilePath,
248+
options,
249+
);
250+
},
251+
252+
readlink(path: TFilePath, options?: IOptions): Promise<TDataOut> {
253+
return promisify(vol, 'readlink')(path, options);
254+
},
255+
256+
realpath(path: TFilePath, options?: IRealpathOptions | string): Promise<TDataOut> {
257+
return promisify(vol, 'realpath')(path, options);
258+
},
259+
260+
rename(oldPath: TFilePath, newPath: TFilePath): Promise<void> {
261+
return promisify(vol, 'rename')(oldPath, newPath);
262+
},
263+
264+
rmdir(path: TFilePath): Promise<void> {
265+
return promisify(vol, 'rmdir')(path);
266+
},
267+
268+
stat(path: TFilePath): Promise<Stats> {
269+
return promisify(vol, 'stat')(path);
270+
},
271+
272+
symlink(target: TFilePath, path: TFilePath, type?: TSymlinkType): Promise<void> {
273+
return promisify(vol, 'symlink')(target, path, type);
274+
},
275+
276+
truncate(path: TFilePath, len?: number): Promise<void> {
277+
return promisify(vol, 'truncate')(path, len);
278+
},
279+
280+
unlink(path: TFilePath): Promise<void> {
281+
return promisify(vol, 'unlink')(path);
282+
},
283+
284+
utimes(path: TFilePath, atime: TTime, mtime: TTime): Promise<void> {
285+
return promisify(vol, 'utimes')(path, atime, mtime);
286+
},
287+
288+
writeFile(id: TFileHandle, data: TData, options?: IWriteFileOptions): Promise<void> {
289+
return promisify(vol, 'writeFile')(
290+
id instanceof FileHandle ? id.fd : id as TFilePath,
291+
data,
292+
options,
293+
);
294+
},
295+
};
296+
}

src/volume.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {TEncoding, TEncodingExtended, TDataOut, assertEncoding, strToEncoding, E
1212
import errors = require('./internal/errors');
1313
import extend = require('fast-extend');
1414
import util = require('util');
15+
import createPromisesApi from './promises';
1516

1617
const {O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_APPEND,
1718
O_DIRECTORY, O_NOATIME, O_NOFOLLOW, O_SYNC, O_DIRECT, O_NONBLOCK,
@@ -49,7 +50,7 @@ export type TTime = number | string | Date;
4950
export type TCallback<TData> = (error?: IError, data?: TData) => void;
5051
// type TCallbackWrite = (err?: IError, bytesWritten?: number, source?: Buffer) => void;
5152
// type TCallbackWriteStr = (err?: IError, written?: number, str?: string) => void;
52-
53+
export type TSymlinkType = 'file' | 'dir' | 'junction';
5354

5455
// ---------------------------------------- Constants
5556

@@ -562,6 +563,12 @@ export class Volume {
562563
File: new (...File) => File,
563564
};
564565

566+
private promisesApi = createPromisesApi(this);
567+
568+
get promises() {
569+
return this.promisesApi;
570+
}
571+
565572
constructor(props = {}) {
566573
this.props = extend({Node, Link, File}, props);
567574

@@ -1362,16 +1369,16 @@ export class Volume {
13621369
}
13631370

13641371
// `type` argument works only on Windows.
1365-
symlinkSync(target: TFilePath, path: TFilePath, type?: 'file' | 'dir' | 'junction') {
1372+
symlinkSync(target: TFilePath, path: TFilePath, type?: TSymlinkType) {
13661373
const targetFilename = pathToFilename(target);
13671374
const pathFilename = pathToFilename(path);
13681375
this.symlinkBase(targetFilename, pathFilename);
13691376
}
13701377

13711378
symlink(target: TFilePath, path: TFilePath, callback: TCallback<void>);
1372-
symlink(target: TFilePath, path: TFilePath, type: 'file' | 'dir' | 'junction', callback: TCallback<void>);
1379+
symlink(target: TFilePath, path: TFilePath, type: TSymlinkType, callback: TCallback<void>);
13731380
symlink(target: TFilePath, path: TFilePath, a, b?) {
1374-
const [type, callback] = getArgAndCb<'file' | 'dir' | 'junction', TCallback<void>>(a, b);
1381+
const [type, callback] = getArgAndCb<TSymlinkType, TCallback<void>>(a, b);
13751382
const targetFilename = pathToFilename(target);
13761383
const pathFilename = pathToFilename(path);
13771384
this.wrapAsync(this.symlinkBase, [targetFilename, pathFilename], callback);

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "es5",
4+
"lib": ["es5", "dom", "es2015.promise"],
45
"module": "commonjs",
56
"removeComments": false,
67
"noImplicitAny": false,

0 commit comments

Comments
 (0)