@zenfs/core 1.7.2 → 1.8.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 (62) hide show
  1. package/dist/backends/backend.js +3 -4
  2. package/dist/backends/fetch.d.ts +17 -18
  3. package/dist/backends/fetch.js +95 -58
  4. package/dist/backends/index.d.ts +2 -1
  5. package/dist/backends/index.js +2 -1
  6. package/dist/backends/memory.d.ts +1 -1
  7. package/dist/backends/overlay.d.ts +7 -2
  8. package/dist/backends/overlay.js +32 -9
  9. package/dist/backends/passthrough.d.ts +4 -0
  10. package/dist/backends/passthrough.js +128 -0
  11. package/dist/backends/port/fs.d.ts +9 -44
  12. package/dist/backends/port/fs.js +93 -116
  13. package/dist/backends/port/rpc.d.ts +8 -5
  14. package/dist/backends/port/rpc.js +9 -7
  15. package/dist/backends/store/file_index.d.ts +38 -0
  16. package/dist/backends/store/file_index.js +76 -0
  17. package/dist/backends/store/fs.d.ts +55 -34
  18. package/dist/backends/store/fs.js +417 -233
  19. package/dist/backends/store/index_fs.d.ts +34 -0
  20. package/dist/backends/store/index_fs.js +67 -0
  21. package/dist/backends/store/inode.d.ts +26 -8
  22. package/dist/backends/store/inode.js +92 -91
  23. package/dist/backends/store/simple.d.ts +20 -20
  24. package/dist/backends/store/simple.js +3 -4
  25. package/dist/backends/store/store.d.ts +12 -12
  26. package/dist/backends/store/store.js +4 -6
  27. package/dist/devices.d.ts +11 -10
  28. package/dist/devices.js +15 -11
  29. package/dist/file.d.ts +111 -7
  30. package/dist/file.js +319 -71
  31. package/dist/filesystem.d.ts +22 -4
  32. package/dist/mixins/mutexed.d.ts +7 -2
  33. package/dist/mixins/mutexed.js +56 -0
  34. package/dist/mixins/sync.d.ts +1 -1
  35. package/dist/stats.d.ts +12 -6
  36. package/dist/stats.js +14 -6
  37. package/dist/utils.d.ts +17 -3
  38. package/dist/utils.js +32 -10
  39. package/dist/vfs/constants.d.ts +2 -2
  40. package/dist/vfs/constants.js +2 -2
  41. package/dist/vfs/dir.js +3 -1
  42. package/dist/vfs/index.js +4 -1
  43. package/dist/vfs/promises.js +31 -11
  44. package/dist/vfs/shared.js +2 -0
  45. package/dist/vfs/sync.js +25 -13
  46. package/dist/vfs/types.d.ts +15 -0
  47. package/package.json +2 -3
  48. package/readme.md +2 -2
  49. package/scripts/test.js +73 -11
  50. package/tests/common/mutex.test.ts +1 -1
  51. package/tests/fetch/run.sh +16 -0
  52. package/tests/fetch/server.ts +49 -0
  53. package/tests/fetch/setup.ts +13 -0
  54. package/tests/fs/read.test.ts +10 -10
  55. package/tests/fs/times.test.ts +2 -2
  56. package/tests/setup/index.ts +38 -0
  57. package/tests/setup/port.ts +15 -0
  58. package/dist/backends/file_index.d.ts +0 -63
  59. package/dist/backends/file_index.js +0 -163
  60. package/tests/common/async.test.ts +0 -31
  61. package/tests/setup/cow+fetch.ts +0 -45
  62. /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
package/dist/stats.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type * as Node from 'node:fs';
2
2
  import type { V_Context } from './context.js';
3
3
  import * as c from './vfs/constants.js';
4
+ import type { InodeFields, InodeLike } from './backends/store/inode.js';
4
5
  /**
5
6
  * Indicates the type of a file. Applied to 'mode'.
6
7
  */
@@ -91,8 +92,10 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
91
92
  gid: T;
92
93
  /**
93
94
  * Some file systems stash data on stats objects.
95
+ * @todo [BREAKING] Remove this
96
+ * @deprecated @hidden
94
97
  */
95
- fileData?: Uint8Array;
98
+ fileData?: unknown;
96
99
  /**
97
100
  * Time of last access, since epoch
98
101
  */
@@ -122,10 +125,12 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
122
125
  * For directories/symlinks, this is normally the size of the struct that represents the item.
123
126
  */
124
127
  size: T;
128
+ data?: number;
129
+ flags?: number;
125
130
  /**
126
131
  * Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
127
132
  */
128
- constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino }?: Partial<StatsLike>);
133
+ constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest }?: Partial<InodeLike>);
129
134
  isFile(): boolean;
130
135
  isDirectory(): boolean;
131
136
  isSymbolicLink(): boolean;
@@ -133,6 +138,7 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
133
138
  isBlockDevice(): boolean;
134
139
  isCharacterDevice(): boolean;
135
140
  isFIFO(): boolean;
141
+ toJSON(): StatsLike<T> & InodeFields;
136
142
  /**
137
143
  * Checks if a given user/group has access to this item
138
144
  * @param mode The requested access, combination of W_OK, R_OK, and X_OK
@@ -143,15 +149,15 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
143
149
  /**
144
150
  * Change the mode of the file.
145
151
  * We use this helper function to prevent messing up the type of the file.
146
- * @internal
147
- * @deprecated This will be removed in the next minor release since it is internal
152
+ * @internal @deprecated
153
+ * @todo [BREAKING] Remove
148
154
  */
149
155
  chmod(mode: number): void;
150
156
  /**
151
157
  * Change the owner user/group of the file.
152
158
  * This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
153
- * @internal
154
- * @deprecated This will be removed in the next minor release since it is internal
159
+ * @internal @deprecated
160
+ * @todo [BREAKING] Remove
155
161
  */
156
162
  chown(uid: number, gid: number): void;
157
163
  get atimeNs(): bigint;
package/dist/stats.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { credentials } from './credentials.js';
2
2
  import * as c from './vfs/constants.js';
3
+ import { pick } from 'utilium';
4
+ import { _inode_fields } from './backends/store/inode.js';
3
5
  const n1000 = BigInt(1000);
4
6
  /**
5
7
  * Provides information about a particular entry in the file system.
@@ -39,7 +41,7 @@ export class StatsCommon {
39
41
  /**
40
42
  * Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
41
43
  */
42
- constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino } = {}) {
44
+ constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest } = {}) {
43
45
  /**
44
46
  * ID of device containing file
45
47
  */
@@ -81,6 +83,7 @@ export class StatsCommon {
81
83
  if ((this.mode & c.S_IFMT) == 0) {
82
84
  this.mode = (this.mode | this._convert(c.S_IFREG));
83
85
  }
86
+ Object.assign(this, rest);
84
87
  }
85
88
  isFile() {
86
89
  return (this.mode & c.S_IFMT) === c.S_IFREG;
@@ -103,6 +106,9 @@ export class StatsCommon {
103
106
  isFIFO() {
104
107
  return (this.mode & c.S_IFMT) === c.S_IFIFO;
105
108
  }
109
+ toJSON() {
110
+ return pick(this, _inode_fields);
111
+ }
106
112
  /**
107
113
  * Checks if a given user/group has access to this item
108
114
  * @param mode The requested access, combination of W_OK, R_OK, and X_OK
@@ -142,11 +148,12 @@ export class StatsCommon {
142
148
  // Perform the access check
143
149
  return (perm & mode) === mode;
144
150
  }
151
+ /* node:coverage disable */
145
152
  /**
146
153
  * Change the mode of the file.
147
154
  * We use this helper function to prevent messing up the type of the file.
148
- * @internal
149
- * @deprecated This will be removed in the next minor release since it is internal
155
+ * @internal @deprecated
156
+ * @todo [BREAKING] Remove
150
157
  */
151
158
  chmod(mode) {
152
159
  this.mode = this._convert((this.mode & c.S_IFMT) | mode);
@@ -154,8 +161,8 @@ export class StatsCommon {
154
161
  /**
155
162
  * Change the owner user/group of the file.
156
163
  * This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
157
- * @internal
158
- * @deprecated This will be removed in the next minor release since it is internal
164
+ * @internal @deprecated
165
+ * @todo [BREAKING] Remove
159
166
  */
160
167
  chown(uid, gid) {
161
168
  uid = Number(uid);
@@ -167,6 +174,7 @@ export class StatsCommon {
167
174
  this.gid = this._convert(gid);
168
175
  }
169
176
  }
177
+ /* node:coverage enable */
170
178
  get atimeNs() {
171
179
  return BigInt(this.atimeMs) * n1000;
172
180
  }
@@ -184,7 +192,7 @@ export class StatsCommon {
184
192
  * @hidden @internal
185
193
  */
186
194
  export function _chown(stats, uid, gid) {
187
- if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
195
+ if (!isNaN(uid) && 0 <= uid && uid < c.size_max) {
188
196
  stats.uid = uid;
189
197
  }
190
198
  if (!isNaN(gid) && 0 <= gid && gid < 2 ** 32) {
package/dist/utils.d.ts CHANGED
@@ -32,15 +32,16 @@ export { /** @deprecated @hidden */ decodeUTF8 as decode };
32
32
  * Decodes a directory listing
33
33
  * @hidden
34
34
  */
35
- export declare function decodeDirListing(data: Uint8Array): Record<string, bigint>;
35
+ export declare function decodeDirListing(data: Uint8Array): Record<string, number>;
36
36
  /**
37
37
  * Encodes a directory listing
38
38
  * @hidden
39
39
  */
40
- export declare function encodeDirListing(data: Record<string, bigint>): Uint8Array;
40
+ export declare function encodeDirListing(data: Record<string, number>): Uint8Array;
41
41
  export type Callback<Args extends unknown[] = [], NoError = undefined | void> = (e: ErrnoError | NoError, ...args: OptionalTuple<Args>) => unknown;
42
42
  /**
43
43
  * Normalizes a mode
44
+ * @param def default
44
45
  * @internal
45
46
  */
46
47
  export declare function normalizeMode(mode: unknown, def?: number): number;
@@ -48,7 +49,7 @@ export declare function normalizeMode(mode: unknown, def?: number): number;
48
49
  * Normalizes a time
49
50
  * @internal
50
51
  */
51
- export declare function normalizeTime(time: string | number | Date): Date;
52
+ export declare function normalizeTime(time: string | number | Date): number;
52
53
  /**
53
54
  * Normalizes a path
54
55
  * @internal
@@ -75,3 +76,16 @@ export type Concrete<T extends ClassLike> = Pick<T, keyof T> & (new (...args: an
75
76
  * @internal
76
77
  */
77
78
  export declare function randomBigInt(): bigint;
79
+ /**
80
+ * Prevents infinite loops
81
+ * @internal
82
+ */
83
+ export declare function canary(path?: string, syscall?: string): () => void;
84
+ /**
85
+ * A wrapper for throwing things.
86
+ * Used in expressions.
87
+ * @todo Remove once `throw` is allowed in expressions
88
+ * @see https://github.com/tc39/proposal-throw-expressions
89
+ * @internal @hidden
90
+ */
91
+ export declare function _throw(e: unknown): never;
package/dist/utils.js CHANGED
@@ -34,7 +34,9 @@ export function encodeUTF8(input) {
34
34
  }
35
35
  return encoder.encode(input);
36
36
  }
37
+ /* node:coverage disable */
37
38
  export { /** @deprecated @hidden */ encodeUTF8 as encode };
39
+ /* node:coverage enable */
38
40
  const decoder = new TextDecoder();
39
41
  /**
40
42
  * Decodes a string from a buffer
@@ -46,38 +48,39 @@ export function decodeUTF8(input) {
46
48
  }
47
49
  return decoder.decode(input);
48
50
  }
51
+ /* node:coverage disable */
49
52
  export { /** @deprecated @hidden */ decodeUTF8 as decode };
53
+ /* node:coverage enable */
50
54
  /**
51
55
  * Decodes a directory listing
52
56
  * @hidden
53
57
  */
54
58
  export function decodeDirListing(data) {
55
- return JSON.parse(decodeUTF8(data), (k, v) => (k == '' ? v : BigInt(v)));
59
+ return JSON.parse(decodeUTF8(data), (k, v) => (k == '' ? v : typeof v == 'string' ? BigInt(v).toString(16).slice(0, Math.min(v.length, 8)) : v));
56
60
  }
57
61
  /**
58
62
  * Encodes a directory listing
59
63
  * @hidden
60
64
  */
61
65
  export function encodeDirListing(data) {
62
- return encodeUTF8(JSON.stringify(data, (k, v) => (k == '' ? v : v.toString())));
66
+ return encodeUTF8(JSON.stringify(data));
63
67
  }
64
68
  /**
65
69
  * Normalizes a mode
70
+ * @param def default
66
71
  * @internal
67
72
  */
68
73
  export function normalizeMode(mode, def) {
69
- if (typeof mode == 'number') {
74
+ if (typeof mode == 'number')
70
75
  return mode;
71
- }
72
76
  if (typeof mode == 'string') {
73
77
  const parsed = parseInt(mode, 8);
74
78
  if (!isNaN(parsed)) {
75
79
  return parsed;
76
80
  }
77
81
  }
78
- if (typeof def == 'number') {
82
+ if (typeof def == 'number')
79
83
  return def;
80
- }
81
84
  throw new ErrnoError(Errno.EINVAL, 'Invalid mode: ' + (mode === null || mode === void 0 ? void 0 : mode.toString()));
82
85
  }
83
86
  /**
@@ -85,11 +88,10 @@ export function normalizeMode(mode, def) {
85
88
  * @internal
86
89
  */
87
90
  export function normalizeTime(time) {
88
- if (time instanceof Date) {
89
- return time;
90
- }
91
+ if (time instanceof Date)
92
+ return time.getTime();
91
93
  try {
92
- return new Date(time);
94
+ return Number(time);
93
95
  }
94
96
  catch {
95
97
  throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
@@ -138,3 +140,23 @@ export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) {
138
140
  export function randomBigInt() {
139
141
  return BigInt('0x' + randomHex(8));
140
142
  }
143
+ /**
144
+ * Prevents infinite loops
145
+ * @internal
146
+ */
147
+ export function canary(path, syscall) {
148
+ const timeout = setTimeout(() => {
149
+ throw ErrnoError.With('EDEADLK', path, syscall);
150
+ }, 5000);
151
+ return () => clearTimeout(timeout);
152
+ }
153
+ /**
154
+ * A wrapper for throwing things.
155
+ * Used in expressions.
156
+ * @todo Remove once `throw` is allowed in expressions
157
+ * @see https://github.com/tc39/proposal-throw-expressions
158
+ * @internal @hidden
159
+ */
160
+ export function _throw(e) {
161
+ throw e;
162
+ }
@@ -111,7 +111,7 @@ export declare const S_IXOTH = 1;
111
111
  */
112
112
  export declare const UV_FS_O_FILEMAP = 0;
113
113
  /**
114
- * Max 32-bit integer
114
+ * Max 32-bit unsigned integer
115
115
  * @hidden
116
116
  */
117
- export declare const size_max: number;
117
+ export declare const size_max = 4294967295;
@@ -123,7 +123,7 @@ export const S_IXOTH = 1;
123
123
  */
124
124
  export const UV_FS_O_FILEMAP = 0;
125
125
  /**
126
- * Max 32-bit integer
126
+ * Max 32-bit unsigned integer
127
127
  * @hidden
128
128
  */
129
- export const size_max = 2 ** 32 - 1;
129
+ export const size_max = 0xffffffff; // 2^32 - 1
package/dist/vfs/dir.js CHANGED
@@ -66,7 +66,9 @@ export class Dir {
66
66
  async _read() {
67
67
  var _a, _b;
68
68
  this.checkClosed();
69
- (_a = this._entries) !== null && _a !== void 0 ? _a : (this._entries = await readdir.call(this.context, this.path, { withFileTypes: true }));
69
+ (_a = this._entries) !== null && _a !== void 0 ? _a : (this._entries = await readdir.call(this.context, this.path, {
70
+ withFileTypes: true,
71
+ }));
70
72
  if (!this._entries.length)
71
73
  return null;
72
74
  return (_b = this._entries.shift()) !== null && _b !== void 0 ? _b : null;
package/dist/vfs/index.js CHANGED
@@ -4,6 +4,7 @@ export * as constants from './constants.js';
4
4
  export * from './dir.js';
5
5
  export * as promises from './promises.js';
6
6
  export { chroot, mount, mountObject,
7
+ /* node:coverage disable */
7
8
  /**
8
9
  * The map of mount points.
9
10
  * Using `fs.mounts` instead of the `mounts` export is a security issue and not recommended.
@@ -11,6 +12,8 @@ export { chroot, mount, mountObject,
11
12
  * @todo [BREAKING] remove `fs.mounts` for security.
12
13
  * @deprecated Use the `mounts` export that isn't an `fs` property!
13
14
  */
14
- mounts, umount, } from './shared.js';
15
+ mounts,
16
+ /* node:coverage enable */
17
+ umount, } from './shared.js';
15
18
  export * from './streams.js';
16
19
  export * from './sync.js';
@@ -213,7 +213,10 @@ export class FileHandle {
213
213
  if (!('ReadableStream' in _gt)) {
214
214
  throw new ErrnoError(Errno.ENOSYS, 'ReadableStream is missing on globalThis');
215
215
  }
216
- return new _gt.ReadableStream({ start, type: options.type });
216
+ return new _gt.ReadableStream({
217
+ start,
218
+ type: options.type,
219
+ });
217
220
  }
218
221
  /**
219
222
  * @todo Implement
@@ -478,11 +481,11 @@ async function applySetId(file, uid, gid) {
478
481
  * Opens a file. This helper handles the complexity of file flags.
479
482
  * @internal
480
483
  */
481
- async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
484
+ async function _open(path, opt) {
482
485
  var _a;
483
486
  path = normalizePath(path);
484
- const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
485
- path = resolveSymlinks ? await realpath.call(this, path) : path;
487
+ const mode = normalizeMode(opt.mode, 0o644), flag = parseFlag(opt.flag);
488
+ path = opt.preserveSymlinks ? path : await realpath.call(this, path);
486
489
  const { fs, path: resolved } = resolveMount(path, this);
487
490
  const stats = await fs.stat(resolved).catch(() => null);
488
491
  if (!stats) {
@@ -527,7 +530,7 @@ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
527
530
  * @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
528
531
  */
529
532
  export async function open(path, flag = 'r', mode = 0o644) {
530
- return await _open.call(this, path, flag, mode, true);
533
+ return await _open.call(this, path, { flag, mode });
531
534
  }
532
535
  open;
533
536
  export async function readFile(path, _options) {
@@ -771,7 +774,7 @@ export async function symlink(target, path, type = 'file') {
771
774
  if (await exists.call(this, path)) {
772
775
  throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
773
776
  }
774
- const handle = __addDisposableResource(env_5, await _open.call(this, path, 'w+', 0o644, false), true);
777
+ const handle = __addDisposableResource(env_5, await _open.call(this, path, { flag: 'w+', mode: 0o644, preserveSymlinks: true }), true);
775
778
  await handle.writeFile(target.toString());
776
779
  await handle.file.chmod(constants.S_IFLNK);
777
780
  }
@@ -789,7 +792,7 @@ symlink;
789
792
  export async function readlink(path, options) {
790
793
  const env_6 = { stack: [], error: void 0, hasError: false };
791
794
  try {
792
- const handle = __addDisposableResource(env_6, await _open.call(this, normalizePath(path), 'r', 0o644, false), true);
795
+ const handle = __addDisposableResource(env_6, await _open.call(this, normalizePath(path), { flag: 'r', mode: 0o644, preserveSymlinks: true }), true);
793
796
  const value = await handle.readFile();
794
797
  const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
795
798
  // always defaults to utf-8 to avoid wrangler (cloudflare) worker "unknown encoding" exception
@@ -826,7 +829,12 @@ chown;
826
829
  export async function lchown(path, uid, gid) {
827
830
  const env_8 = { stack: [], error: void 0, hasError: false };
828
831
  try {
829
- const handle = __addDisposableResource(env_8, await _open.call(this, path, 'r+', 0o644, false), true);
832
+ const handle = __addDisposableResource(env_8, await _open.call(this, path, {
833
+ flag: 'r+',
834
+ mode: 0o644,
835
+ preserveSymlinks: true,
836
+ allowDirectory: true,
837
+ }), true);
830
838
  await handle.chown(uid, gid);
831
839
  }
832
840
  catch (e_8) {
@@ -860,7 +868,12 @@ chmod;
860
868
  export async function lchmod(path, mode) {
861
869
  const env_10 = { stack: [], error: void 0, hasError: false };
862
870
  try {
863
- const handle = __addDisposableResource(env_10, await _open.call(this, path, 'r+', 0o644, false), true);
871
+ const handle = __addDisposableResource(env_10, await _open.call(this, path, {
872
+ flag: 'r+',
873
+ mode: 0o644,
874
+ preserveSymlinks: true,
875
+ allowDirectory: true,
876
+ }), true);
864
877
  await handle.chmod(mode);
865
878
  }
866
879
  catch (e_10) {
@@ -900,7 +913,12 @@ utimes;
900
913
  export async function lutimes(path, atime, mtime) {
901
914
  const env_12 = { stack: [], error: void 0, hasError: false };
902
915
  try {
903
- const handle = __addDisposableResource(env_12, await _open.call(this, path, 'r+', 0o644, false), true);
916
+ const handle = __addDisposableResource(env_12, await _open.call(this, path, {
917
+ flag: 'r+',
918
+ mode: 0o644,
919
+ preserveSymlinks: true,
920
+ allowDirectory: true,
921
+ }), true);
904
922
  await handle.utimes(new Date(atime), new Date(mtime));
905
923
  }
906
924
  catch (e_12) {
@@ -1006,7 +1024,9 @@ export async function rm(path, options) {
1006
1024
  switch (stats.mode & constants.S_IFMT) {
1007
1025
  case constants.S_IFDIR:
1008
1026
  if (options === null || options === void 0 ? void 0 : options.recursive) {
1009
- for (const entry of await readdir.call(this, path, { _isIndirect: true })) {
1027
+ for (const entry of await readdir.call(this, path, {
1028
+ _isIndirect: true,
1029
+ })) {
1010
1030
  await rm.call(this, join(path, entry), { ...options, _isIndirect: true });
1011
1031
  }
1012
1032
  }
@@ -113,6 +113,7 @@ export function fixError(e, paths) {
113
113
  e.path = fixPaths(e.path, paths);
114
114
  return e;
115
115
  }
116
+ /* node:coverage disable */
116
117
  /**
117
118
  * @internal @deprecated
118
119
  */
@@ -124,6 +125,7 @@ export function mountObject(mounts) {
124
125
  mount(point, fs);
125
126
  }
126
127
  }
128
+ /* node:coverage enable */
127
129
  /**
128
130
  * @internal @hidden
129
131
  */
package/dist/vfs/sync.js CHANGED
@@ -134,7 +134,7 @@ lstatSync;
134
134
  export function truncateSync(path, len = 0) {
135
135
  const env_1 = { stack: [], error: void 0, hasError: false };
136
136
  try {
137
- const file = __addDisposableResource(env_1, _openSync.call(this, path, 'r+'), false);
137
+ const file = __addDisposableResource(env_1, _openSync.call(this, path, { flag: 'r+' }), false);
138
138
  len || (len = 0);
139
139
  if (len < 0) {
140
140
  throw new ErrnoError(Errno.EINVAL);
@@ -176,11 +176,11 @@ function applySetId(file, uid, gid) {
176
176
  file.chownSync(parent.mode & constants.S_ISUID ? parent.uid : uid, // manually apply setuid/setgid
177
177
  parent.mode & constants.S_ISGID ? parent.gid : gid);
178
178
  }
179
- function _openSync(path, _flag, _mode, resolveSymlinks = true) {
179
+ function _openSync(path, opt) {
180
180
  var _a;
181
181
  path = normalizePath(path);
182
- const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
183
- path = resolveSymlinks ? realpathSync.call(this, path) : path;
182
+ const mode = normalizeMode(opt.mode, 0o644), flag = parseFlag(opt.flag);
183
+ path = opt.preserveSymlinks ? path : realpathSync.call(this, path);
184
184
  const { fs, path: resolved } = resolveMount(path, this);
185
185
  let stats;
186
186
  try {
@@ -203,6 +203,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
203
203
  }
204
204
  const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : credentials;
205
205
  const file = fs.createFileSync(resolved, flag, mode, { uid, gid });
206
+ if (!opt.allowDirectory && mode & constants.S_IFDIR)
207
+ throw ErrnoError.With('EISDIR', path, '_open');
206
208
  applySetId(file, uid, gid);
207
209
  return file;
208
210
  }
@@ -216,6 +218,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
216
218
  if (isTruncating(flag)) {
217
219
  file.truncateSync(0);
218
220
  }
221
+ if (!opt.allowDirectory && stats.mode & constants.S_IFDIR)
222
+ throw ErrnoError.With('EISDIR', path, '_open');
219
223
  return file;
220
224
  }
221
225
  /**
@@ -223,7 +227,7 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
223
227
  * @see http://www.manpagez.com/man/2/open/
224
228
  */
225
229
  export function openSync(path, flag, mode = constants.F_OK) {
226
- return file2fd(_openSync.call(this, path, flag, mode, true));
230
+ return file2fd(_openSync.call(this, path, { flag, mode }));
227
231
  }
228
232
  openSync;
229
233
  /**
@@ -231,13 +235,13 @@ openSync;
231
235
  * @internal
232
236
  */
233
237
  export function lopenSync(path, flag, mode) {
234
- return file2fd(_openSync.call(this, path, flag, mode, false));
238
+ return file2fd(_openSync.call(this, path, { flag, mode, preserveSymlinks: true }));
235
239
  }
236
- function _readFileSync(fname, flag, resolveSymlinks) {
240
+ function _readFileSync(fname, flag, preserveSymlinks) {
237
241
  const env_2 = { stack: [], error: void 0, hasError: false };
238
242
  try {
239
243
  // Get file.
240
- const file = __addDisposableResource(env_2, _openSync.call(this, fname, flag, 0o644, resolveSymlinks), false);
244
+ const file = __addDisposableResource(env_2, _openSync.call(this, fname, { flag, mode: 0o644, preserveSymlinks }), false);
241
245
  const stat = file.statSync();
242
246
  // Allocate buffer.
243
247
  const data = new Uint8Array(stat.size);
@@ -258,7 +262,7 @@ export function readFileSync(path, _options = {}) {
258
262
  if (!isReadable(flag)) {
259
263
  throw new ErrnoError(Errno.EINVAL, 'Flag passed to readFile must allow for reading.');
260
264
  }
261
- const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag, true));
265
+ const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag, false));
262
266
  return options.encoding ? data.toString(options.encoding) : data;
263
267
  }
264
268
  readFileSync;
@@ -277,7 +281,11 @@ export function writeFileSync(path, data, _options = {}) {
277
281
  if (!encodedData) {
278
282
  throw new ErrnoError(Errno.EINVAL, 'Data not specified');
279
283
  }
280
- const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), flag, options.mode, true), false);
284
+ const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), {
285
+ flag,
286
+ mode: options.mode,
287
+ preserveSymlinks: true,
288
+ }), false);
281
289
  file.writeSync(encodedData, 0, encodedData.byteLength, 0);
282
290
  emitChange('change', path.toString());
283
291
  }
@@ -308,7 +316,11 @@ export function appendFileSync(filename, data, _options = {}) {
308
316
  throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
309
317
  }
310
318
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
311
- const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(), flag, options.mode, true), false);
319
+ const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(), {
320
+ flag,
321
+ mode: options.mode,
322
+ preserveSymlinks: true,
323
+ }), false);
312
324
  file.writeSync(encodedData, 0, encodedData.byteLength);
313
325
  }
314
326
  catch (e_4) {
@@ -560,12 +572,12 @@ export function symlinkSync(target, path, type = 'file') {
560
572
  throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
561
573
  }
562
574
  writeFileSync.call(this, path, target.toString());
563
- const file = _openSync.call(this, path, 'r+', 0o644, false);
575
+ const file = _openSync.call(this, path, { flag: 'r+', mode: 0o644, preserveSymlinks: true });
564
576
  file.chmodSync(constants.S_IFLNK);
565
577
  }
566
578
  symlinkSync;
567
579
  export function readlinkSync(path, options) {
568
- const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r', false));
580
+ const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r', true));
569
581
  const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
570
582
  if (encoding == 'buffer') {
571
583
  return value;
@@ -11,6 +11,21 @@ export interface InternalOptions {
11
11
  */
12
12
  _isIndirect?: boolean;
13
13
  }
14
+ /**
15
+ * @internal @hidden Used for the internal `_open` functions
16
+ */
17
+ export interface OpenOptions {
18
+ flag: fs.OpenMode;
19
+ mode?: fs.Mode | null;
20
+ /**
21
+ * If true, do not resolve symlinks
22
+ */
23
+ preserveSymlinks?: boolean;
24
+ /**
25
+ * If true, allows opening directories
26
+ */
27
+ allowDirectory?: boolean;
28
+ }
14
29
  export interface ReaddirOptions extends InternalOptions {
15
30
  withFileTypes?: boolean;
16
31
  recursive?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -57,8 +57,7 @@
57
57
  "format": "prettier --write .",
58
58
  "format:check": "prettier --check .",
59
59
  "lint": "eslint src tests",
60
- "test": "tsx --test --experimental-test-coverage",
61
- "pretest": "npm run build",
60
+ "test": "npx zenfs-test --clean && npx zenfs-test -abcfp && tests/fetch/run.sh && npx zenfs-test --report",
62
61
  "build": "tsc -p tsconfig.json",
63
62
  "build:docs": "typedoc",
64
63
  "dev": "npm run build -- --watch",
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ZenFS
2
2
 
3
- ZenFS is a file system that emulates the [NodeJS filesystem API](http://nodejs.org/api/fs.html). It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
3
+ ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http://nodejs.org/api/fs.html). It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
4
4
 
5
5
  ## Backends
6
6
 
@@ -8,7 +8,7 @@ ZenFS is modular and extensible. The core includes some built-in backends:
8
8
 
9
9
  - `InMemory`: Stores files in-memory. This is cleared when the runtime ends (e.g. a user navigating away from a web page or a Node process exiting)
10
10
  - `Overlay`: Use a read-only file system as read-write by overlaying a writable file system on top of it. ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write))
11
- - `Fetch`: Downloads files over HTTP with the `fetch` API (_readonly_)
11
+ - `Fetch`: Downloads files over HTTP with the `fetch` API
12
12
  - `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
13
13
  - `Passthrough`: Use an existing `node:fs` interface with ZenFS
14
14