@zenfs/core 2.5.8 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/backends/cow.d.ts +13 -0
  2. package/dist/backends/cow.js +48 -11
  3. package/dist/backends/single_buffer.d.ts +2 -2
  4. package/dist/backends/store/fs.d.ts +6 -6
  5. package/dist/backends/store/fs.js +10 -2
  6. package/dist/internal/devices.d.ts +6 -6
  7. package/dist/internal/index_fs.d.ts +4 -4
  8. package/dist/node/async.js +1 -1
  9. package/dist/node/dir.d.ts +3 -1
  10. package/dist/node/dir.js +23 -5
  11. package/dist/node/promises.d.ts +3 -1
  12. package/dist/node/promises.js +128 -89
  13. package/dist/node/streams.d.ts +4 -0
  14. package/dist/node/streams.js +9 -10
  15. package/dist/node/sync.js +71 -40
  16. package/dist/utils.d.ts +8 -1
  17. package/dist/utils.js +26 -7
  18. package/dist/vfs/async.js +224 -85
  19. package/dist/vfs/file.d.ts +13 -7
  20. package/dist/vfs/file.js +661 -357
  21. package/dist/vfs/shared.d.ts +1 -0
  22. package/dist/vfs/shared.js +19 -3
  23. package/dist/vfs/sync.js +214 -85
  24. package/dist/vfs/vcache.d.ts +66 -0
  25. package/dist/vfs/vcache.js +249 -0
  26. package/dist/vfs/vnode.d.ts +66 -0
  27. package/dist/vfs/vnode.js +200 -0
  28. package/dist/vfs/watchers.js +4 -2
  29. package/dist/vfs/xattr.js +2 -2
  30. package/eslint.shared.js +1 -1
  31. package/package.json +3 -3
  32. package/scripts/make-index.js +7 -1
  33. package/scripts/test.js +28 -21
  34. package/tests/backend/port.test.ts +3 -3
  35. package/tests/backend/store.test.ts +49 -0
  36. package/tests/common.ts +62 -0
  37. package/tests/fetch/fetch.ts +6 -0
  38. package/tests/fetch/run.sh +2 -2
  39. package/tests/fetch/server.js +3 -3
  40. package/tests/fs/append.test.ts +5 -5
  41. package/tests/fs/concurrency.test.ts +52 -0
  42. package/tests/fs/dir.test.ts +13 -15
  43. package/tests/fs/directory.test.ts +32 -31
  44. package/tests/fs/errors.test.ts +151 -31
  45. package/tests/fs/exists.test.ts +4 -4
  46. package/tests/fs/links.test.ts +4 -11
  47. package/tests/fs/open.test.ts +4 -4
  48. package/tests/fs/permissions.test.ts +15 -15
  49. package/tests/fs/read.test.ts +9 -9
  50. package/tests/fs/readFile.test.ts +9 -9
  51. package/tests/fs/rename.test.ts +8 -8
  52. package/tests/fs/scaling.test.ts +4 -4
  53. package/tests/fs/stat.test.ts +20 -20
  54. package/tests/fs/streams.test.ts +9 -6
  55. package/tests/fs/tempdir.test.ts +33 -10
  56. package/tests/fs/times.test.ts +11 -11
  57. package/tests/fs/truncate.test.ts +6 -6
  58. package/tests/fs/watch.test.ts +107 -53
  59. package/tests/fs/write.test.ts +10 -10
  60. package/tests/fs/xattr.test.ts +3 -3
  61. package/tests/native/run.sh +3 -0
  62. package/tests/native/setup.ts +230 -0
  63. package/types/readable-stream.d.ts +2 -2
package/dist/vfs/async.js CHANGED
@@ -1,3 +1,55 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
1
53
  import { rethrow, setUVMessage, UV } from 'kerium';
2
54
  import { decodeUTF8 } from 'utilium';
3
55
  import * as constants from '../constants.js';
@@ -10,6 +62,7 @@ import { Dirent, ifToDt } from './dir.js';
10
62
  import { Handle } from './file.js';
11
63
  import * as flags from './flags.js';
12
64
  import { resolveMount } from './shared.js';
65
+ import { cacheOf, lockPath } from './vcache.js';
13
66
  import { emitChange } from './watchers.js';
14
67
  /**
15
68
  * Resolves the mount and real path for a path.
@@ -20,15 +73,15 @@ export async function resolve($, path, preserveSymlinks, extra) {
20
73
  path = resolvePath.call($, path);
21
74
  if (preserveSymlinks) {
22
75
  const resolved = resolveMount(path, $, extra);
23
- const stats = await resolved.fs.stat(resolved.path).catch(() => undefined);
76
+ const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? (await resolved.fs.stat(resolved.path).catch(() => undefined));
24
77
  return { ...resolved, fullPath: path, stats };
25
78
  }
26
79
  /* Try to resolve it directly. If this works,
27
80
  that means we don't need to perform any resolution for parent directories. */
28
81
  try {
29
82
  const resolved = resolveMount(path, $);
30
- // Stat it to make sure it exists
31
- const stats = await resolved.fs.stat(resolved.path);
83
+ // Stat it to make sure it exists. The vnode cache takes precedence since it may have unsynced changes
84
+ const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? (await resolved.fs.stat(resolved.path));
32
85
  if (!isSymbolicLink(stats)) {
33
86
  return { ...resolved, fullPath: path, stats };
34
87
  }
@@ -42,11 +95,12 @@ export async function resolve($, path, preserveSymlinks, extra) {
42
95
  const realDir = dir == '/' ? '/' : (await resolve($, dir, false, extra)).fullPath;
43
96
  const maybePath = join(realDir, base);
44
97
  const resolved = resolveMount(maybePath, $);
45
- const stats = await resolved.fs.stat(resolved.path).catch((e) => {
46
- if (e.code == 'ENOENT')
47
- return;
48
- throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath, ...extra }));
49
- });
98
+ const stats = cacheOf(resolved.fs).get(resolved.path)?.inode
99
+ ?? (await resolved.fs.stat(resolved.path).catch((e) => {
100
+ if (e.code == 'ENOENT')
101
+ return;
102
+ throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath, ...extra }));
103
+ }));
50
104
  if (!stats)
51
105
  return { ...resolved, fullPath: path };
52
106
  if (!isSymbolicLink(stats)) {
@@ -63,33 +117,53 @@ export async function open($, path, opt) {
63
117
  path = normalizePath(path);
64
118
  const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
65
119
  const $ex = { syscall: 'open', path };
66
- const { fs, path: resolved, stats } = await resolve($, path, opt.preserveSymlinks, $ex);
120
+ // eslint-disable-next-line prefer-const
121
+ let { fs, path: resolved, stats } = await resolve($, path, opt.preserveSymlinks, $ex);
67
122
  if (!stats) {
68
- if (!(flag & constants.O_CREAT))
69
- throw UV('ENOENT', $ex);
70
- // Create the file
71
- const parentStats = await fs.stat(dirname(resolved));
72
- if (checkAccess && !hasAccess($, parentStats, constants.W_OK))
73
- throw UV('EACCES', 'open', dirname(path));
74
- if (!isDirectory(parentStats))
75
- throw UV('ENOTDIR', 'open', dirname(path));
76
- if (!opt.allowDirectory && mode & constants.S_IFDIR)
77
- throw UV('EISDIR', 'open', path);
78
- const { euid: uid, egid: gid } = contextOf($).credentials;
79
- const inode = await fs.createFile(resolved, {
80
- mode,
81
- uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
82
- gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
83
- });
84
- return new Handle($, path, fs, resolved, flag, inode);
123
+ const env_1 = { stack: [], error: void 0, hasError: false };
124
+ try {
125
+ if (!(flag & constants.O_CREAT))
126
+ throw UV('ENOENT', $ex);
127
+ // Create the file
128
+ const parentPath = dirname(resolved);
129
+ const parentStats = await fs.stat(parentPath);
130
+ if (checkAccess && !hasAccess($, parentStats, constants.W_OK))
131
+ throw UV('EACCES', 'open', dirname(path));
132
+ if (!isDirectory(parentStats))
133
+ throw UV('ENOTDIR', 'open', dirname(path));
134
+ if (!opt.allowDirectory && mode & constants.S_IFDIR)
135
+ throw UV('EISDIR', 'open', path);
136
+ // Serialize entry creation with other operations on the parent directory
137
+ const _ = __addDisposableResource(env_1, await lockPath(fs, parentPath, 'rw', parentStats), false);
138
+ // Another task may have created the file while we waited for the lock
139
+ stats = cacheOf(fs).get(resolved)?.inode ?? (await fs.stat(resolved).catch(() => undefined));
140
+ if (!stats) {
141
+ const { euid: uid, egid: gid } = contextOf($).credentials;
142
+ const inode = await fs.createFile(resolved, {
143
+ mode,
144
+ uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
145
+ gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
146
+ });
147
+ // A new entry in the parent directory, which is a 'rename' event
148
+ emitChange($, 'rename', path);
149
+ return new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, inode));
150
+ }
151
+ }
152
+ catch (e_1) {
153
+ env_1.error = e_1;
154
+ env_1.hasError = true;
155
+ }
156
+ finally {
157
+ __disposeResources(env_1);
158
+ }
85
159
  }
86
160
  if (checkAccess && !hasAccess($, stats, flags.toMode(flag)))
87
161
  throw UV('EACCES', $ex);
88
162
  if (flag & constants.O_EXCL)
89
163
  throw UV('EEXIST', $ex);
90
- const handle = new Handle($, path, fs, resolved, flag, stats);
91
164
  if (!opt.allowDirectory && mode & constants.S_IFDIR)
92
165
  throw UV('EISDIR', 'open', path);
166
+ const handle = new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, stats));
93
167
  if (flag & constants.O_TRUNC)
94
168
  await handle.truncate(0);
95
169
  return handle;
@@ -115,15 +189,28 @@ export async function mkdir(path, options = {}) {
115
189
  const { mode = 0o777, recursive } = options;
116
190
  const { fs, path: resolved } = resolveMount(path, this, { syscall: 'mkdir' });
117
191
  const __create = async (path, resolved, parent) => {
118
- if (checkAccess && !hasAccess(this, parent, constants.W_OK))
119
- throw UV('EACCES', 'mkdir', path);
120
- const inode = await fs.mkdir(resolved, {
121
- mode,
122
- uid: parent.mode & constants.S_ISUID ? parent.uid : uid,
123
- gid: parent.mode & constants.S_ISGID ? parent.gid : gid,
124
- });
125
- emitChange(this, 'rename', path);
126
- return inode;
192
+ const env_2 = { stack: [], error: void 0, hasError: false };
193
+ try {
194
+ if (checkAccess && !hasAccess(this, parent, constants.W_OK))
195
+ throw UV('EACCES', 'mkdir', path);
196
+ const _ = __addDisposableResource(env_2, await lockPath(fs, dirname(resolved), 'rw', parent), false);
197
+ const inode = await fs
198
+ .mkdir(resolved, {
199
+ mode,
200
+ uid: parent.mode & constants.S_ISUID ? parent.uid : uid,
201
+ gid: parent.mode & constants.S_ISGID ? parent.gid : gid,
202
+ })
203
+ .catch(rethrow({ syscall: 'mkdir', path }));
204
+ emitChange(this, 'rename', path);
205
+ return inode;
206
+ }
207
+ catch (e_2) {
208
+ env_2.error = e_2;
209
+ env_2.hasError = true;
210
+ }
211
+ finally {
212
+ __disposeResources(env_2);
213
+ }
127
214
  };
128
215
  if (!recursive) {
129
216
  await __create(path, resolved, await fs.stat(dirname(resolved)));
@@ -144,7 +231,8 @@ export async function mkdir(path, options = {}) {
144
231
  }
145
232
  export async function readdir(path, options = {}) {
146
233
  path = normalizePath(path);
147
- const $ex = { syscall: 'readdir', path };
234
+ // Node reports `readdir` failures as `scandir`
235
+ const $ex = { syscall: 'scandir', path };
148
236
  const { fs, path: resolved, stats } = await resolve(this, path, false, $ex);
149
237
  if (!stats)
150
238
  throw UV('ENOENT', $ex);
@@ -152,7 +240,21 @@ export async function readdir(path, options = {}) {
152
240
  throw UV('EACCES', $ex);
153
241
  if (!isDirectory(stats))
154
242
  throw UV('ENOTDIR', $ex);
155
- const entries = await fs.readdir(resolved);
243
+ let entries;
244
+ {
245
+ const env_3 = { stack: [], error: void 0, hasError: false };
246
+ try {
247
+ const _ = __addDisposableResource(env_3, await lockPath(fs, resolved, 'ro', stats), false);
248
+ entries = await fs.readdir(resolved);
249
+ }
250
+ catch (e_3) {
251
+ env_3.error = e_3;
252
+ env_3.hasError = true;
253
+ }
254
+ finally {
255
+ __disposeResources(env_3);
256
+ }
257
+ }
156
258
  const values = [];
157
259
  const addEntry = async (entry) => {
158
260
  const entryStats = await fs.stat(join(resolved, entry)).catch((e) => {
@@ -178,55 +280,91 @@ export async function readdir(path, options = {}) {
178
280
  return values;
179
281
  }
180
282
  export async function rename(oldPath, newPath) {
181
- oldPath = normalizePath(oldPath);
182
- newPath = normalizePath(newPath);
183
- const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
184
- const src = await resolve(this, oldPath, true, $ex);
185
- const dst = resolveMount(newPath, this, $ex);
186
- if (src.fs.uuid !== dst.fs.uuid)
187
- throw UV('EXDEV', $ex);
188
- if (dst.path.startsWith(src.path + '/'))
189
- throw UV('EBUSY', $ex);
190
- if (!src.stats)
191
- throw UV('ENOENT', $ex);
192
- const fs = src.fs;
193
- const oldParent = await fs.stat(dirname(src.path));
194
- const newParent = await fs.stat(dirname(dst.path));
195
- const newStats = await fs.stat(dst.path).catch((e) => {
196
- if (e.code == 'ENOENT')
197
- return null;
198
- throw e;
199
- });
200
- if (checkAccess && (!hasAccess(this, oldParent, constants.R_OK) || !hasAccess(this, newParent, constants.W_OK)))
201
- throw UV('EACCES', $ex);
202
- if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
203
- throw UV('EISDIR', $ex);
204
- if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
205
- throw UV('ENOTDIR', $ex);
206
- await src.fs.rename(src.path, dst.path);
207
- emitChange(this, 'rename', oldPath);
208
- emitChange(this, 'change', newPath);
283
+ const env_4 = { stack: [], error: void 0, hasError: false };
284
+ try {
285
+ oldPath = normalizePath(oldPath);
286
+ newPath = normalizePath(newPath);
287
+ const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
288
+ const src = await resolve(this, oldPath, true, $ex);
289
+ const dst = resolveMount(newPath, this, $ex);
290
+ if (src.fs.uuid !== dst.fs.uuid)
291
+ throw UV('EXDEV', $ex);
292
+ // A directory can not be moved inside itself
293
+ if (dst.path.startsWith(src.path + '/'))
294
+ throw UV('EINVAL', $ex);
295
+ if (!src.stats)
296
+ throw UV('ENOENT', $ex);
297
+ const fs = src.fs;
298
+ const oldParent = await fs.stat(dirname(src.path));
299
+ const newParent = await fs.stat(dirname(dst.path));
300
+ const newStats = await fs.stat(dst.path).catch((e) => {
301
+ if (e.code == 'ENOENT')
302
+ return null;
303
+ throw e;
304
+ });
305
+ if (checkAccess && (!hasAccess(this, oldParent, constants.R_OK) || !hasAccess(this, newParent, constants.W_OK)))
306
+ throw UV('EACCES', $ex);
307
+ if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
308
+ throw UV('EISDIR', $ex);
309
+ if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
310
+ throw UV('ENOTDIR', $ex);
311
+ const srcDir = dirname(src.path);
312
+ const dstDir = dirname(dst.path);
313
+ // Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
314
+ const parents = [
315
+ [srcDir, oldParent],
316
+ [dstDir, newParent],
317
+ ];
318
+ if (oldParent.ino > newParent.ino)
319
+ parents.reverse();
320
+ const _first = __addDisposableResource(env_4, await lockPath(fs, parents[0][0], 'rw', parents[0][1]), false);
321
+ const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : await lockPath(fs, parents[1][0], 'rw', parents[1][1]), false);
322
+ await src.fs.rename(src.path, dst.path);
323
+ cacheOf(fs).rename(src.path, dst.path);
324
+ // Both names change which entries exist, so both are 'rename' events
325
+ emitChange(this, 'rename', oldPath);
326
+ emitChange(this, 'rename', newPath);
327
+ }
328
+ catch (e_4) {
329
+ env_4.error = e_4;
330
+ env_4.hasError = true;
331
+ }
332
+ finally {
333
+ __disposeResources(env_4);
334
+ }
209
335
  }
210
336
  export async function link(target, link) {
211
- target = normalizePath(target);
212
- link = normalizePath(link);
213
- const $ex = { syscall: 'link', path: link, dest: target };
214
- const { fs, path: resolved } = resolveMount(target, this, $ex);
215
- const dst = resolveMount(link, this, $ex);
216
- if (fs.uuid != dst.fs.uuid)
217
- throw UV('EXDEV', $ex);
218
- const stats = await fs.stat(resolved);
219
- if (checkAccess) {
220
- if (!hasAccess(this, stats, constants.R_OK))
221
- throw UV('EACCES', $ex);
222
- const dirStats = await fs.stat(dirname(resolved));
223
- if (!hasAccess(this, dirStats, constants.R_OK))
224
- throw UV('EACCES', $ex);
225
- const destStats = await fs.stat(dirname(dst.path));
226
- if (!hasAccess(this, destStats, constants.W_OK))
227
- throw UV('EACCES', $ex);
337
+ const env_5 = { stack: [], error: void 0, hasError: false };
338
+ try {
339
+ target = normalizePath(target);
340
+ link = normalizePath(link);
341
+ const $ex = { syscall: 'link', path: target, dest: link };
342
+ const { fs, path: resolved } = resolveMount(target, this, $ex);
343
+ const dst = resolveMount(link, this, $ex);
344
+ if (fs.uuid != dst.fs.uuid)
345
+ throw UV('EXDEV', $ex);
346
+ const stats = await fs.stat(resolved);
347
+ if (checkAccess) {
348
+ if (!hasAccess(this, stats, constants.R_OK))
349
+ throw UV('EACCES', $ex);
350
+ const dirStats = await fs.stat(dirname(resolved));
351
+ if (!hasAccess(this, dirStats, constants.R_OK))
352
+ throw UV('EACCES', $ex);
353
+ const destStats = await fs.stat(dirname(dst.path));
354
+ if (!hasAccess(this, destStats, constants.W_OK))
355
+ throw UV('EACCES', $ex);
356
+ }
357
+ const _ = __addDisposableResource(env_5, await lockPath(fs, dirname(dst.path), 'rw'), false);
358
+ await fs.link(resolved, dst.path);
359
+ cacheOf(fs).link(resolved, dst.path);
360
+ }
361
+ catch (e_5) {
362
+ env_5.error = e_5;
363
+ env_5.hasError = true;
364
+ }
365
+ finally {
366
+ __disposeResources(env_5);
228
367
  }
229
- return await fs.link(resolved, dst.path);
230
368
  }
231
369
  export async function stat(path, lstat) {
232
370
  path = normalizePath.call(this, path);
@@ -237,7 +375,8 @@ export async function stat(path, lstat) {
237
375
  else {
238
376
  const { base, dir } = parse(path);
239
377
  const { fs, path: parent } = await resolve(this, dir, false, extra);
240
- stats = await fs.stat(base ? join(parent, base) : parent).catch(rethrow(extra));
378
+ const target = base ? join(parent, base) : parent;
379
+ stats = cacheOf(fs).get(target)?.inode ?? (await fs.stat(target).catch(rethrow(extra)));
241
380
  }
242
381
  if (!stats)
243
382
  throw UV('ENOENT', extra);
@@ -2,6 +2,7 @@ import type { V_Context } from '../context.js';
2
2
  import type { FileSystem, StreamOptions } from '../internal/filesystem.js';
3
3
  import { type InodeLike } from '../internal/inode.js';
4
4
  import '../polyfills.js';
5
+ import type { VNode } from './vnode.js';
5
6
  /** @hidden */
6
7
  export interface FileReadResult<T extends ArrayBufferView> {
7
8
  bytesRead: number;
@@ -13,11 +14,9 @@ export interface FileReadResult<T extends ArrayBufferView> {
13
14
  export declare class Handle {
14
15
  readonly context: V_Context;
15
16
  readonly path: string;
16
- readonly fs: FileSystem;
17
17
  readonly internalPath: string;
18
18
  readonly flag: number;
19
- readonly inode: InodeLike;
20
- protected _buffer?: Uint8Array;
19
+ readonly vnode: VNode;
21
20
  /**
22
21
  * Current position
23
22
  */
@@ -33,20 +32,24 @@ export declare class Handle {
33
32
  */
34
33
  get position(): number;
35
34
  set position(value: number);
35
+ /**
36
+ * The inode for the file. This is shared between all handles for the file via the vnode.
37
+ */
38
+ get inode(): InodeLike;
39
+ get fs(): FileSystem;
36
40
  /**
37
41
  * Whether the file has changes which have not been written to the FS
38
42
  */
39
- protected dirty: boolean;
43
+ protected get dirty(): boolean;
40
44
  /**
41
45
  * Whether the file is open or closed
42
46
  */
43
47
  protected closed: boolean;
44
48
  get isClosed(): boolean;
45
49
  /**
46
- * Creates a file with `path` and, optionally, the given contents.
47
- * Note that, if contents is specified, it will be mutated by the file.
50
+ * Creates a file handle for the vnode.
48
51
  */
49
- constructor(context: V_Context, path: string, fs: FileSystem, internalPath: string, flag: number, inode: InodeLike);
52
+ constructor(context: V_Context, path: string, internalPath: string, flag: number, vnode: VNode);
50
53
  protected get _isSync(): boolean;
51
54
  [Symbol.dispose](): void;
52
55
  syncSync(): void;
@@ -127,10 +130,13 @@ export declare class Handle {
127
130
  utimes(atime: number, mtime: number): Promise<void>;
128
131
  /**
129
132
  * Create a stream for reading the file.
133
+ * @todo Don't repeat ourselves with the implementation in `FileSystem`
130
134
  */
131
135
  streamRead(options: StreamOptions): ReadableStream;
132
136
  /**
133
137
  * Create a stream for writing the file.
138
+ * Chunks are written to the vnode's cache, then synced when the stream is closed.
139
+ * @todo Don't repeat ourselves with the implementation in `FileSystem`
134
140
  */
135
141
  streamWrite(options: StreamOptions): WritableStream;
136
142
  }