@zenfs/core 1.11.3 → 2.0.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.
- package/dist/backends/backend.d.ts +19 -15
- package/dist/backends/backend.js +31 -15
- package/dist/backends/cow.d.ts +20 -30
- package/dist/backends/cow.js +52 -142
- package/dist/backends/fetch.d.ts +1 -0
- package/dist/backends/fetch.js +3 -1
- package/dist/backends/index.d.ts +1 -1
- package/dist/backends/index.js +1 -1
- package/dist/backends/memory.d.ts +5 -7
- package/dist/backends/memory.js +2 -3
- package/dist/backends/passthrough.d.ts +19 -22
- package/dist/backends/passthrough.js +85 -160
- package/dist/backends/port.d.ts +207 -0
- package/dist/backends/port.js +297 -0
- package/dist/backends/single_buffer.d.ts +11 -5
- package/dist/backends/single_buffer.js +18 -12
- package/dist/backends/store/fs.d.ts +11 -27
- package/dist/backends/store/fs.js +67 -91
- package/dist/backends/store/store.d.ts +7 -12
- package/dist/config.d.ts +1 -10
- package/dist/config.js +7 -8
- package/dist/context.d.ts +8 -21
- package/dist/context.js +33 -10
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal/contexts.d.ts +63 -0
- package/dist/internal/contexts.js +15 -0
- package/dist/internal/credentials.d.ts +2 -11
- package/dist/internal/credentials.js +0 -19
- package/dist/internal/devices.d.ts +18 -80
- package/dist/internal/devices.js +76 -279
- package/dist/internal/file_index.js +3 -3
- package/dist/internal/filesystem.d.ts +31 -89
- package/dist/internal/filesystem.js +21 -20
- package/dist/internal/index.d.ts +0 -1
- package/dist/internal/index.js +0 -1
- package/dist/internal/index_fs.d.ts +12 -30
- package/dist/internal/index_fs.js +23 -55
- package/dist/internal/inode.d.ts +147 -9
- package/dist/internal/inode.js +333 -25
- package/dist/internal/log.d.ts +19 -13
- package/dist/internal/log.js +81 -80
- package/dist/mixins/async.js +26 -90
- package/dist/mixins/mutexed.d.ts +17 -16
- package/dist/mixins/mutexed.js +29 -31
- package/dist/mixins/readonly.d.ts +7 -6
- package/dist/mixins/readonly.js +6 -0
- package/dist/mixins/sync.js +8 -8
- package/dist/{vfs/path.d.ts → path.d.ts} +3 -4
- package/dist/{vfs/path.js → path.js} +6 -9
- package/dist/readline.d.ts +134 -0
- package/dist/readline.js +623 -0
- package/dist/utils.d.ts +4 -35
- package/dist/utils.js +8 -73
- package/dist/vfs/acl.d.ts +42 -0
- package/dist/vfs/acl.js +249 -0
- package/dist/vfs/async.d.ts +7 -21
- package/dist/vfs/async.js +19 -19
- package/dist/vfs/config.d.ts +6 -18
- package/dist/vfs/config.js +8 -18
- package/dist/vfs/dir.d.ts +3 -3
- package/dist/vfs/dir.js +9 -8
- package/dist/vfs/file.d.ts +106 -0
- package/dist/vfs/file.js +235 -0
- package/dist/vfs/flags.d.ts +19 -0
- package/dist/vfs/flags.js +62 -0
- package/dist/vfs/index.d.ts +4 -10
- package/dist/vfs/index.js +4 -13
- package/dist/vfs/ioctl.d.ts +87 -0
- package/dist/vfs/ioctl.js +304 -0
- package/dist/vfs/promises.d.ts +78 -16
- package/dist/vfs/promises.js +273 -122
- package/dist/vfs/shared.d.ts +7 -26
- package/dist/vfs/shared.js +25 -53
- package/dist/{stats.d.ts → vfs/stats.d.ts} +14 -28
- package/dist/{stats.js → vfs/stats.js} +11 -66
- package/dist/vfs/streams.d.ts +1 -0
- package/dist/vfs/streams.js +24 -19
- package/dist/vfs/sync.d.ts +4 -3
- package/dist/vfs/sync.js +143 -128
- package/dist/vfs/watchers.d.ts +2 -2
- package/dist/vfs/watchers.js +6 -6
- package/dist/vfs/xattr.d.ts +116 -0
- package/dist/vfs/xattr.js +218 -0
- package/package.json +3 -3
- package/readme.md +1 -1
- package/tests/backend/config.worker.js +4 -1
- package/tests/backend/fetch.test.ts +3 -0
- package/tests/backend/port.test.ts +21 -35
- package/tests/backend/remote.worker.js +4 -1
- package/tests/backend/single-buffer.test.ts +24 -0
- package/tests/common/context.test.ts +1 -1
- package/tests/common/handle.test.ts +17 -12
- package/tests/common/path.test.ts +1 -1
- package/tests/common/readline.test.ts +104 -0
- package/tests/common.ts +4 -19
- package/tests/fetch/fetch.ts +1 -1
- package/tests/fs/links.test.ts +1 -1
- package/tests/fs/permissions.test.ts +7 -6
- package/tests/fs/readFile.test.ts +3 -3
- package/tests/fs/stat.test.ts +6 -6
- package/tests/fs/streams.test.ts +2 -11
- package/tests/fs/times.test.ts +1 -1
- package/tests/fs/xattr.test.ts +85 -0
- package/tests/logs.js +22 -0
- package/tests/setup/context.ts +1 -1
- package/tests/setup/index.ts +3 -3
- package/tests/setup/port.ts +1 -1
- package/dist/backends/port/fs.d.ts +0 -84
- package/dist/backends/port/fs.js +0 -151
- package/dist/backends/port/rpc.d.ts +0 -77
- package/dist/backends/port/rpc.js +0 -100
- package/dist/backends/store/simple.d.ts +0 -20
- package/dist/backends/store/simple.js +0 -13
- package/dist/internal/file.d.ts +0 -351
- package/dist/internal/file.js +0 -739
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { BufferEncodingOption, ObjectEncodingOptions } from 'node:fs';
|
|
2
|
+
import type { V_Context } from '../context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extended attribute name with namespace prefix.
|
|
5
|
+
* Format is namespace.attributename where namespace is one of:
|
|
6
|
+
* - user: User attributes
|
|
7
|
+
* - trusted: Trusted attributes (privileged)
|
|
8
|
+
* - system: System attributes
|
|
9
|
+
* - security: Security attributes
|
|
10
|
+
*
|
|
11
|
+
* Note: Currently only the 'user' namespace is supported.
|
|
12
|
+
*/
|
|
13
|
+
export type Name = `${'user' | 'trusted' | 'system' | 'security'}.${string}`;
|
|
14
|
+
/**
|
|
15
|
+
* Options for xattr operations.
|
|
16
|
+
*/
|
|
17
|
+
export interface Options {
|
|
18
|
+
/**
|
|
19
|
+
* If true, don't follow symlinks.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
noFollow?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Encoding for attribute values.
|
|
25
|
+
* If 'buffer' or undefined, the value is returned as a Buffer.
|
|
26
|
+
* Otherwise, the value is returned as a string using the specified encoding.
|
|
27
|
+
* @default undefined
|
|
28
|
+
*/
|
|
29
|
+
encoding?: BufferEncoding | 'buffer';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Options for setting extended attributes.
|
|
33
|
+
* Extends the base Options with additional flags for create/replace behavior.
|
|
34
|
+
*/
|
|
35
|
+
export interface SetOptions extends Options {
|
|
36
|
+
/**
|
|
37
|
+
* If true, fail if the attribute already exists.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
create?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* If true, fail if the attribute does not exist.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
replace?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gets the value of an extended attribute.
|
|
49
|
+
*
|
|
50
|
+
* @param path Path to the file
|
|
51
|
+
* @param name Name of the attribute to get
|
|
52
|
+
* @param opt Options for the operation
|
|
53
|
+
* @returns A buffer containing the attribute value when encoding is 'buffer' or undefined, or a string when a string encoding is specified
|
|
54
|
+
*/
|
|
55
|
+
export declare function get(this: V_Context, path: string, name: Name, opt?: Options & (BufferEncodingOption | {
|
|
56
|
+
encoding?: null;
|
|
57
|
+
})): Promise<Uint8Array>;
|
|
58
|
+
export declare function get(this: V_Context, path: string, name: Name, opt: Options & ObjectEncodingOptions): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Synchronously gets the value of an extended attribute.
|
|
61
|
+
*
|
|
62
|
+
* @param path Path to the file
|
|
63
|
+
* @param name Name of the attribute to get
|
|
64
|
+
* @param opt Options for the operation
|
|
65
|
+
* @returns A buffer containing the attribute value when encoding is 'buffer' or undefined, or a string when a string encoding is specified
|
|
66
|
+
*/
|
|
67
|
+
export declare function getSync(this: V_Context, path: string, name: Name, opt?: Options & (BufferEncodingOption | {
|
|
68
|
+
encoding?: null;
|
|
69
|
+
})): Uint8Array;
|
|
70
|
+
export declare function getSync(this: V_Context, path: string, name: Name, opt: Options & ObjectEncodingOptions): string;
|
|
71
|
+
/**
|
|
72
|
+
* Sets the value of an extended attribute.
|
|
73
|
+
*
|
|
74
|
+
* @param path Path to the file
|
|
75
|
+
* @param name Name of the attribute to set
|
|
76
|
+
* @param value Value to set
|
|
77
|
+
* @param opt Options for the operation
|
|
78
|
+
*/
|
|
79
|
+
export declare function set(this: V_Context, path: string, name: Name, value: string | Uint8Array, opt?: SetOptions): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Synchronously sets the value of an extended attribute.
|
|
82
|
+
*
|
|
83
|
+
* @param path Path to the file
|
|
84
|
+
* @param name Name of the attribute to set
|
|
85
|
+
* @param value Value to set
|
|
86
|
+
* @param opt Options for the operation
|
|
87
|
+
*/
|
|
88
|
+
export declare function setSync(this: V_Context, path: string, name: Name, value: string | Uint8Array, opt?: SetOptions): void;
|
|
89
|
+
/**
|
|
90
|
+
* Removes an extended attribute from a file.
|
|
91
|
+
*
|
|
92
|
+
* @param path Path to the file
|
|
93
|
+
* @param name Name of the attribute to remove
|
|
94
|
+
*/
|
|
95
|
+
export declare function remove(this: V_Context, path: string, name: Name): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Synchronously removes an extended attribute from a file.
|
|
98
|
+
*
|
|
99
|
+
* @param path Path to the file
|
|
100
|
+
* @param name Name of the attribute to remove
|
|
101
|
+
*/
|
|
102
|
+
export declare function removeSync(this: V_Context, path: string, name: Name): void;
|
|
103
|
+
/**
|
|
104
|
+
* Lists all extended attributes of a file.
|
|
105
|
+
*
|
|
106
|
+
* @param path Path to the file
|
|
107
|
+
* @returns Array of attribute names
|
|
108
|
+
*/
|
|
109
|
+
export declare function list(this: V_Context, path: string): Promise<Name[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Synchronously lists all extended attributes of a file.
|
|
112
|
+
*
|
|
113
|
+
* @param path Path to the file
|
|
114
|
+
* @returns Array of attribute names
|
|
115
|
+
*/
|
|
116
|
+
export declare function listSync(this: V_Context, path: string): Name[];
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { pick } from 'utilium';
|
|
3
|
+
import { Errno, ErrnoError } from '../internal/error.js';
|
|
4
|
+
import { Attributes, hasAccess } from '../internal/inode.js';
|
|
5
|
+
import { normalizePath } from '../utils.js';
|
|
6
|
+
import { checkAccess } from './config.js';
|
|
7
|
+
import { R_OK, W_OK } from './constants.js';
|
|
8
|
+
import { fixError, resolveMount } from './shared.js';
|
|
9
|
+
const _allowedRestrictedNames = [];
|
|
10
|
+
/**
|
|
11
|
+
* Check permission for the attribute name.
|
|
12
|
+
* For now, only attributes in the 'user' namespace are supported.
|
|
13
|
+
* @throws EPERM for attributes in namespaces other than 'user'
|
|
14
|
+
*/
|
|
15
|
+
function checkName($, name, path, syscall) {
|
|
16
|
+
if (!name.startsWith('user.') && !_allowedRestrictedNames.includes(name))
|
|
17
|
+
throw new ErrnoError(Errno.EPERM, 'Only attributes in the user namespace are supported', path, syscall);
|
|
18
|
+
}
|
|
19
|
+
export async function get(path, name, opt = {}) {
|
|
20
|
+
var _a;
|
|
21
|
+
path = normalizePath(path);
|
|
22
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
23
|
+
checkName(this, name, path, 'xattr.get');
|
|
24
|
+
try {
|
|
25
|
+
const inode = await fs.stat(resolved);
|
|
26
|
+
if (checkAccess && !hasAccess(this, inode, R_OK)) {
|
|
27
|
+
throw ErrnoError.With('EACCES', resolved, 'xattr.get');
|
|
28
|
+
}
|
|
29
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
30
|
+
const attr = inode.attributes.get(name);
|
|
31
|
+
if (!attr)
|
|
32
|
+
throw ErrnoError.With('ENODATA', resolved, 'xattr.get');
|
|
33
|
+
const buffer = Buffer.from(attr.value);
|
|
34
|
+
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
throw fixError(e, { [resolved]: path });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function getSync(path, name, opt = {}) {
|
|
41
|
+
var _a;
|
|
42
|
+
path = normalizePath(path);
|
|
43
|
+
checkName(this, name, path, 'xattr.get');
|
|
44
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
45
|
+
try {
|
|
46
|
+
const inode = fs.statSync(resolved);
|
|
47
|
+
if (checkAccess && !hasAccess(this, inode, R_OK)) {
|
|
48
|
+
throw ErrnoError.With('EACCES', resolved, 'xattr.get');
|
|
49
|
+
}
|
|
50
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
51
|
+
const attr = inode.attributes.get(name);
|
|
52
|
+
if (!attr)
|
|
53
|
+
throw ErrnoError.With('ENODATA', resolved, 'xattr.get');
|
|
54
|
+
const buffer = Buffer.from(attr.value);
|
|
55
|
+
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
throw fixError(e, { [resolved]: path });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sets the value of an extended attribute.
|
|
63
|
+
*
|
|
64
|
+
* @param path Path to the file
|
|
65
|
+
* @param name Name of the attribute to set
|
|
66
|
+
* @param value Value to set
|
|
67
|
+
* @param opt Options for the operation
|
|
68
|
+
*/
|
|
69
|
+
export async function set(path, name, value, opt = {}) {
|
|
70
|
+
var _a;
|
|
71
|
+
path = normalizePath(path);
|
|
72
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
73
|
+
checkName(this, name, path, 'xattr.set');
|
|
74
|
+
try {
|
|
75
|
+
const inode = await fs.stat(resolved);
|
|
76
|
+
if (checkAccess && !hasAccess(this, inode, W_OK)) {
|
|
77
|
+
throw ErrnoError.With('EACCES', resolved, 'xattr.set');
|
|
78
|
+
}
|
|
79
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
80
|
+
const attr = inode.attributes.get(name);
|
|
81
|
+
if (opt.create && attr) {
|
|
82
|
+
throw ErrnoError.With('EEXIST', resolved, 'xattr.set');
|
|
83
|
+
}
|
|
84
|
+
if (opt.replace && !attr) {
|
|
85
|
+
throw ErrnoError.With('ENODATA', resolved, 'xattr.set');
|
|
86
|
+
}
|
|
87
|
+
inode.attributes.set(name, Buffer.from(value));
|
|
88
|
+
await fs.touch(resolved, pick(inode, 'attributes'));
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
throw fixError(e, { [resolved]: path });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Synchronously sets the value of an extended attribute.
|
|
96
|
+
*
|
|
97
|
+
* @param path Path to the file
|
|
98
|
+
* @param name Name of the attribute to set
|
|
99
|
+
* @param value Value to set
|
|
100
|
+
* @param opt Options for the operation
|
|
101
|
+
*/
|
|
102
|
+
export function setSync(path, name, value, opt = {}) {
|
|
103
|
+
var _a;
|
|
104
|
+
path = normalizePath(path);
|
|
105
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
106
|
+
checkName(this, name, path, 'xattr.set');
|
|
107
|
+
try {
|
|
108
|
+
const inode = fs.statSync(resolved);
|
|
109
|
+
if (checkAccess && !hasAccess(this, inode, W_OK)) {
|
|
110
|
+
throw ErrnoError.With('EACCES', resolved, 'xattr.set');
|
|
111
|
+
}
|
|
112
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
113
|
+
const attr = inode.attributes.get(name);
|
|
114
|
+
if (opt.create && attr) {
|
|
115
|
+
throw ErrnoError.With('EEXIST', resolved, 'xattr.set');
|
|
116
|
+
}
|
|
117
|
+
if (opt.replace && !attr) {
|
|
118
|
+
throw ErrnoError.With('ENODATA', resolved, 'xattr.set');
|
|
119
|
+
}
|
|
120
|
+
inode.attributes.set(name, Buffer.from(value));
|
|
121
|
+
fs.touchSync(resolved, pick(inode, 'attributes'));
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
throw fixError(e, { [resolved]: path });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Removes an extended attribute from a file.
|
|
129
|
+
*
|
|
130
|
+
* @param path Path to the file
|
|
131
|
+
* @param name Name of the attribute to remove
|
|
132
|
+
*/
|
|
133
|
+
export async function remove(path, name) {
|
|
134
|
+
var _a;
|
|
135
|
+
path = normalizePath(path);
|
|
136
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
137
|
+
checkName(this, name, path, 'xattr.remove');
|
|
138
|
+
try {
|
|
139
|
+
const inode = await fs.stat(resolved);
|
|
140
|
+
if (checkAccess && !hasAccess(this, inode, W_OK)) {
|
|
141
|
+
throw ErrnoError.With('EACCES', resolved, 'xattr.remove');
|
|
142
|
+
}
|
|
143
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
144
|
+
const attr = inode.attributes.get(name);
|
|
145
|
+
if (!attr)
|
|
146
|
+
throw ErrnoError.With('ENODATA', resolved, 'xattr.remove');
|
|
147
|
+
inode.attributes.remove(name);
|
|
148
|
+
await fs.touch(resolved, pick(inode, 'attributes'));
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
throw fixError(e, { [resolved]: path });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Synchronously removes an extended attribute from a file.
|
|
156
|
+
*
|
|
157
|
+
* @param path Path to the file
|
|
158
|
+
* @param name Name of the attribute to remove
|
|
159
|
+
*/
|
|
160
|
+
export function removeSync(path, name) {
|
|
161
|
+
var _a;
|
|
162
|
+
path = normalizePath(path);
|
|
163
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
164
|
+
checkName(this, name, path, 'xattr.remove');
|
|
165
|
+
try {
|
|
166
|
+
const inode = fs.statSync(resolved);
|
|
167
|
+
if (checkAccess && !hasAccess(this, inode, W_OK)) {
|
|
168
|
+
throw ErrnoError.With('EACCES', resolved, 'xattr.remove');
|
|
169
|
+
}
|
|
170
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
171
|
+
const attr = inode.attributes.get(name);
|
|
172
|
+
if (!attr)
|
|
173
|
+
throw ErrnoError.With('ENODATA', resolved, 'xattr.remove');
|
|
174
|
+
inode.attributes.remove(name);
|
|
175
|
+
fs.touchSync(resolved, pick(inode, 'attributes'));
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
throw fixError(e, { [resolved]: path });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Lists all extended attributes of a file.
|
|
183
|
+
*
|
|
184
|
+
* @param path Path to the file
|
|
185
|
+
* @returns Array of attribute names
|
|
186
|
+
*/
|
|
187
|
+
export async function list(path) {
|
|
188
|
+
path = normalizePath(path);
|
|
189
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
190
|
+
try {
|
|
191
|
+
const inode = await fs.stat(resolved);
|
|
192
|
+
if (!inode.attributes)
|
|
193
|
+
return [];
|
|
194
|
+
return inode.attributes.keys();
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
throw fixError(e, { [resolved]: path });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Synchronously lists all extended attributes of a file.
|
|
202
|
+
*
|
|
203
|
+
* @param path Path to the file
|
|
204
|
+
* @returns Array of attribute names
|
|
205
|
+
*/
|
|
206
|
+
export function listSync(path) {
|
|
207
|
+
path = normalizePath(path);
|
|
208
|
+
const { fs, path: resolved } = resolveMount(path, this);
|
|
209
|
+
try {
|
|
210
|
+
const inode = fs.statSync(resolved);
|
|
211
|
+
if (!inode.attributes)
|
|
212
|
+
return [];
|
|
213
|
+
return inode.attributes.keys();
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
throw fixError(e, { [resolved]: path });
|
|
217
|
+
}
|
|
218
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -67,11 +67,11 @@
|
|
|
67
67
|
"prepublishOnly": "npm run build"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@types/node": "^22.10.1",
|
|
70
|
+
"@types/node": "^22.10.1 <22.13.7",
|
|
71
71
|
"buffer": "^6.0.3",
|
|
72
72
|
"eventemitter3": "^5.0.1",
|
|
73
73
|
"readable-stream": "^4.5.2",
|
|
74
|
-
"utilium": "^1.
|
|
74
|
+
"utilium": "^1.8.0"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@eslint/js": "^9.8.0",
|
package/readme.md
CHANGED
|
@@ -7,7 +7,7 @@ ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http
|
|
|
7
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
|
+
- `CopyOnWrite`: Use readable and writable file systems with ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write)).
|
|
11
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
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { parentPort } from 'node:worker_threads';
|
|
2
|
-
import { resolveRemoteMount } from '../../dist/backends/port
|
|
2
|
+
import { resolveRemoteMount } from '../../dist/backends/port.js';
|
|
3
3
|
import { InMemory } from '../../dist/backends/memory.js';
|
|
4
|
+
import { setupLogs } from '../logs.js';
|
|
5
|
+
|
|
6
|
+
setupLogs('<config>');
|
|
4
7
|
|
|
5
8
|
await resolveRemoteMount(parentPort, { backend: InMemory });
|
|
@@ -4,6 +4,9 @@ import { suite, test } from 'node:test';
|
|
|
4
4
|
import { Worker } from 'node:worker_threads';
|
|
5
5
|
import { Fetch, configureSingle, fs, mounts, type FetchFS } from '../../dist/index.js';
|
|
6
6
|
import { baseUrl, defaultEntries, indexPath, whenServerReady } from '../fetch/config.js';
|
|
7
|
+
import { setupLogs } from '../logs.js';
|
|
8
|
+
|
|
9
|
+
setupLogs();
|
|
7
10
|
|
|
8
11
|
const server = new Worker(join(import.meta.dirname, '../fetch/server.js'));
|
|
9
12
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
import { MessageChannel, Worker } from 'node:worker_threads';
|
|
4
|
-
import { Port, attachFS } from '../../dist/backends/port
|
|
5
|
-
import { waitOnline } from '../../dist/backends/port/rpc.js';
|
|
4
|
+
import { Port, attachFS, waitOnline } from '../../dist/backends/port.js';
|
|
6
5
|
import type { InMemoryStore, StoreFS } from '../../dist/index.js';
|
|
7
|
-
import {
|
|
6
|
+
import { InMemory, configure, configureSingle, fs, resolveMountConfig } from '../../dist/index.js';
|
|
7
|
+
import { setupLogs } from '../logs.js';
|
|
8
|
+
setupLogs();
|
|
8
9
|
|
|
9
10
|
// Tests a mis-configured `Port` using a MessageChannel
|
|
10
11
|
|
|
@@ -12,36 +13,21 @@ const timeoutChannel = new MessageChannel();
|
|
|
12
13
|
timeoutChannel.port2.unref();
|
|
13
14
|
|
|
14
15
|
await suite('Timeout', { timeout: 1000 }, () => {
|
|
15
|
-
test('Misconfiguration',
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} catch (e) {
|
|
25
|
-
assert(e instanceof ErrnoError);
|
|
26
|
-
error = e;
|
|
27
|
-
}
|
|
28
|
-
assert(error! instanceof ErrnoError);
|
|
29
|
-
assert.equal(error.code, 'EIO');
|
|
30
|
-
assert(error.message.includes('RPC Failed'));
|
|
16
|
+
test('Misconfiguration', () => {
|
|
17
|
+
const configured = configure({
|
|
18
|
+
mounts: {
|
|
19
|
+
'/tmp-timeout': { backend: InMemory, label: 'tmp' },
|
|
20
|
+
'/port': { backend: Port, port: timeoutChannel.port1, timeout: 100 },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
|
|
31
25
|
});
|
|
32
26
|
|
|
33
|
-
test('Remote not attached',
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await fs.promises.writeFile('/test', 'anything');
|
|
38
|
-
} catch (e) {
|
|
39
|
-
assert(e instanceof ErrnoError);
|
|
40
|
-
error = e;
|
|
41
|
-
}
|
|
42
|
-
assert(error! instanceof ErrnoError);
|
|
43
|
-
assert.equal(error.code, 'EIO');
|
|
44
|
-
assert(error.message.includes('RPC Failed'));
|
|
27
|
+
test('Remote not attached', () => {
|
|
28
|
+
const configured = configureSingle({ backend: Port, port: timeoutChannel.port1, timeout: 100 });
|
|
29
|
+
|
|
30
|
+
assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
|
|
45
31
|
});
|
|
46
32
|
});
|
|
47
33
|
|
|
@@ -56,7 +42,7 @@ await suite('Remote FS with resolveRemoteMount', () => {
|
|
|
56
42
|
const content = 'FS is in a port';
|
|
57
43
|
|
|
58
44
|
test('Configuration', async () => {
|
|
59
|
-
await configureSingle({ backend: Port, port: configPort, timeout:
|
|
45
|
+
await configureSingle({ backend: Port, port: configPort, timeout: 500 });
|
|
60
46
|
});
|
|
61
47
|
|
|
62
48
|
test('Write', async () => {
|
|
@@ -79,9 +65,9 @@ let tmpfs: StoreFS<InMemoryStore>;
|
|
|
79
65
|
|
|
80
66
|
await suite('FS with MessageChannel', () => {
|
|
81
67
|
test('configuration', async () => {
|
|
82
|
-
tmpfs = await resolveMountConfig({ backend: InMemory,
|
|
68
|
+
tmpfs = await resolveMountConfig({ backend: InMemory, label: 'tmp' });
|
|
83
69
|
attachFS(channel.port2, tmpfs);
|
|
84
|
-
await configureSingle({ backend: Port, port: channel.port1, disableAsyncCache: true, timeout:
|
|
70
|
+
await configureSingle({ backend: Port, port: channel.port1, disableAsyncCache: true, timeout: 500 });
|
|
85
71
|
});
|
|
86
72
|
|
|
87
73
|
test('write', async () => {
|
|
@@ -116,7 +102,7 @@ await suite('Remote FS', () => {
|
|
|
116
102
|
const content = 'FS is in a port';
|
|
117
103
|
|
|
118
104
|
test('Configuration', async () => {
|
|
119
|
-
await configureSingle({ backend: Port, port: remotePort, timeout:
|
|
105
|
+
await configureSingle({ backend: Port, port: remotePort, timeout: 500 });
|
|
120
106
|
});
|
|
121
107
|
|
|
122
108
|
test('Write', async () => {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { parentPort } from 'node:worker_threads';
|
|
2
|
-
import { attachFS } from '../../dist/backends/port
|
|
2
|
+
import { attachFS } from '../../dist/backends/port.js';
|
|
3
3
|
import { mounts } from '../../dist/index.js';
|
|
4
|
+
import { setupLogs } from '../logs.js';
|
|
5
|
+
|
|
6
|
+
setupLogs('<remote>');
|
|
4
7
|
|
|
5
8
|
attachFS(parentPort, mounts.get('/'));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { test, suite } from 'node:test';
|
|
2
|
+
import { fs, mount, resolveMountConfig, SingleBuffer, umount } from '../../dist/index.js';
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
|
|
5
|
+
await suite('SingleBuffer', () => {
|
|
6
|
+
test('should be able to restore filesystem (with same metadata) from original buffer', async () => {
|
|
7
|
+
const buffer = new ArrayBuffer(0x100000);
|
|
8
|
+
|
|
9
|
+
umount('/');
|
|
10
|
+
const writable = await resolveMountConfig({ backend: SingleBuffer, buffer });
|
|
11
|
+
mount('/', writable);
|
|
12
|
+
|
|
13
|
+
fs.writeFileSync('/example.ts', 'console.log("hello world")', 'utf-8');
|
|
14
|
+
const stats = fs.statSync('/example.ts');
|
|
15
|
+
|
|
16
|
+
umount('/');
|
|
17
|
+
const snapshot = await resolveMountConfig({ backend: SingleBuffer, buffer });
|
|
18
|
+
mount('/', snapshot);
|
|
19
|
+
|
|
20
|
+
const snapshotStats = fs.statSync('/example.ts');
|
|
21
|
+
|
|
22
|
+
assert.deepEqual(snapshotStats, stats);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -5,7 +5,7 @@ import * as fs from '../../dist/vfs/index.js';
|
|
|
5
5
|
import { canary } from 'utilium';
|
|
6
6
|
|
|
7
7
|
fs.mkdirSync('/ctx');
|
|
8
|
-
const { fs: ctx } = bindContext('/ctx');
|
|
8
|
+
const { fs: ctx } = bindContext({ root: '/ctx' });
|
|
9
9
|
|
|
10
10
|
suite('Context', () => {
|
|
11
11
|
test('create a file', () => {
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
+
import { wait } from 'utilium';
|
|
3
4
|
import { constants, type FileHandle, open } from '../../dist/vfs/promises.js';
|
|
4
5
|
|
|
5
6
|
const content = 'The cake is a lie',
|
|
6
7
|
appended = '\nAnother lie';
|
|
7
8
|
|
|
8
|
-
await
|
|
9
|
-
let handle: FileHandle;
|
|
10
|
-
const filePath = './test.txt';
|
|
11
|
-
|
|
12
|
-
test('open', async () => {
|
|
13
|
-
handle = await open(filePath, 'w+');
|
|
14
|
-
});
|
|
9
|
+
await using handle: FileHandle = await open('./test.txt', 'ws+');
|
|
15
10
|
|
|
11
|
+
await suite('FileHandle', () => {
|
|
16
12
|
test('writeFile', async () => {
|
|
17
13
|
await handle.writeFile(content);
|
|
18
14
|
await handle.sync();
|
|
19
15
|
});
|
|
20
16
|
|
|
21
17
|
test('readFile', async () => {
|
|
22
|
-
assert(
|
|
18
|
+
assert.equal(await handle.readFile('utf8'), content);
|
|
23
19
|
});
|
|
24
20
|
|
|
25
21
|
test('appendFile', async () => {
|
|
@@ -27,12 +23,12 @@ await suite('FileHandle', () => {
|
|
|
27
23
|
});
|
|
28
24
|
|
|
29
25
|
test('readFile after appendFile', async () => {
|
|
30
|
-
assert(
|
|
26
|
+
assert.equal(await handle.readFile({ encoding: 'utf8' }), content + appended);
|
|
31
27
|
});
|
|
32
28
|
|
|
33
29
|
test('truncate', async () => {
|
|
34
30
|
await handle.truncate(5);
|
|
35
|
-
assert(
|
|
31
|
+
assert.equal(await handle.readFile({ encoding: 'utf8' }), content.slice(0, 5));
|
|
36
32
|
});
|
|
37
33
|
|
|
38
34
|
test('stat', async () => {
|
|
@@ -54,7 +50,16 @@ await suite('FileHandle', () => {
|
|
|
54
50
|
assert.equal(stats.gid, 5678);
|
|
55
51
|
});
|
|
56
52
|
|
|
57
|
-
test('
|
|
58
|
-
await handle.
|
|
53
|
+
test('readLines', async () => {
|
|
54
|
+
await handle.writeFile('first line\nsecond line\nthird line');
|
|
55
|
+
|
|
56
|
+
await using rl = handle.readLines();
|
|
57
|
+
|
|
58
|
+
const lines: string[] = [];
|
|
59
|
+
rl.on('line', (line: string) => lines.push(line));
|
|
60
|
+
|
|
61
|
+
await wait(50);
|
|
62
|
+
|
|
63
|
+
assert.deepEqual(lines, ['first line', 'second line', 'third line']);
|
|
59
64
|
});
|
|
60
65
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
-
import { basename, dirname, extname, join, normalize, resolve } from '../../dist/
|
|
3
|
+
import { basename, dirname, extname, join, normalize, resolve } from '../../dist/path.js';
|
|
4
4
|
import * as fs from '../../dist/vfs/index.js';
|
|
5
5
|
|
|
6
6
|
suite('Path emulation', () => {
|