@zenfs/core 1.3.6 → 1.4.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 (88) hide show
  1. package/dist/backends/memory.d.ts +4 -4
  2. package/dist/backends/memory.js +4 -4
  3. package/dist/backends/overlay.d.ts +5 -2
  4. package/dist/backends/overlay.js +7 -10
  5. package/dist/backends/port/fs.js +1 -4
  6. package/dist/config.js +4 -8
  7. package/dist/context.d.ts +32 -0
  8. package/dist/context.js +23 -0
  9. package/dist/credentials.d.ts +5 -5
  10. package/dist/credentials.js +10 -6
  11. package/dist/emulation/async.d.ts +90 -89
  12. package/dist/emulation/async.js +76 -75
  13. package/dist/emulation/dir.d.ts +3 -1
  14. package/dist/emulation/dir.js +6 -7
  15. package/dist/emulation/index.d.ts +1 -1
  16. package/dist/emulation/index.js +1 -1
  17. package/dist/emulation/promises.d.ts +50 -48
  18. package/dist/emulation/promises.js +78 -77
  19. package/dist/emulation/shared.d.ts +35 -8
  20. package/dist/emulation/shared.js +37 -11
  21. package/dist/emulation/sync.d.ts +63 -62
  22. package/dist/emulation/sync.js +72 -73
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +1 -0
  25. package/dist/stats.d.ts +2 -1
  26. package/dist/stats.js +5 -4
  27. package/package.json +3 -5
  28. package/scripts/test.js +78 -17
  29. package/tests/assignment.ts +1 -1
  30. package/tests/common/context.test.ts +19 -0
  31. package/tests/{devices.test.ts → common/devices.test.ts} +3 -3
  32. package/tests/{handle.test.ts → common/handle.test.ts} +1 -1
  33. package/tests/common/mounts.test.ts +36 -0
  34. package/tests/{mutex.test.ts → common/mutex.test.ts} +3 -3
  35. package/tests/common/path.test.ts +34 -0
  36. package/tests/common.ts +4 -3
  37. package/tests/fs/dir.test.ts +11 -11
  38. package/tests/fs/directory.test.ts +17 -17
  39. package/tests/fs/errors.test.ts +29 -39
  40. package/tests/fs/watch.test.ts +2 -2
  41. package/tests/setup/context.ts +9 -0
  42. package/tests/setup/cow+fetch.ts +1 -1
  43. package/tests/setup/memory.ts +1 -1
  44. package/tests/{setup/common.ts → setup.ts} +6 -5
  45. package/src/backends/backend.ts +0 -161
  46. package/src/backends/fetch.ts +0 -180
  47. package/src/backends/file_index.ts +0 -206
  48. package/src/backends/memory.ts +0 -45
  49. package/src/backends/overlay.ts +0 -560
  50. package/src/backends/port/fs.ts +0 -329
  51. package/src/backends/port/readme.md +0 -54
  52. package/src/backends/port/rpc.ts +0 -167
  53. package/src/backends/readme.md +0 -3
  54. package/src/backends/store/fs.ts +0 -667
  55. package/src/backends/store/readme.md +0 -9
  56. package/src/backends/store/simple.ts +0 -154
  57. package/src/backends/store/store.ts +0 -189
  58. package/src/config.ts +0 -227
  59. package/src/credentials.ts +0 -49
  60. package/src/devices.ts +0 -521
  61. package/src/emulation/async.ts +0 -834
  62. package/src/emulation/cache.ts +0 -86
  63. package/src/emulation/config.ts +0 -21
  64. package/src/emulation/constants.ts +0 -182
  65. package/src/emulation/dir.ts +0 -138
  66. package/src/emulation/index.ts +0 -8
  67. package/src/emulation/path.ts +0 -440
  68. package/src/emulation/promises.ts +0 -1140
  69. package/src/emulation/shared.ts +0 -172
  70. package/src/emulation/streams.ts +0 -34
  71. package/src/emulation/sync.ts +0 -863
  72. package/src/emulation/watchers.ts +0 -194
  73. package/src/error.ts +0 -307
  74. package/src/file.ts +0 -631
  75. package/src/filesystem.ts +0 -174
  76. package/src/index.ts +0 -35
  77. package/src/inode.ts +0 -128
  78. package/src/mixins/async.ts +0 -230
  79. package/src/mixins/index.ts +0 -5
  80. package/src/mixins/mutexed.ts +0 -257
  81. package/src/mixins/readonly.ts +0 -96
  82. package/src/mixins/shared.ts +0 -25
  83. package/src/mixins/sync.ts +0 -58
  84. package/src/polyfills.ts +0 -21
  85. package/src/stats.ts +0 -405
  86. package/src/utils.ts +0 -276
  87. package/tests/mounts.test.ts +0 -18
  88. package/tests/path.test.ts +0 -34
@@ -1,329 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { FileReadResult } from 'node:fs/promises';
3
- import type { ExtractProperties } from 'utilium';
4
- import { resolveMountConfig, type MountConfiguration } from '../../config.js';
5
- import { Errno, ErrnoError } from '../../error.js';
6
- import { File } from '../../file.js';
7
- import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
8
- import { Async } from '../../mixins/async.js';
9
- import { Stats } from '../../stats.js';
10
- import type { Backend, FilesystemOf } from '../backend.js';
11
- import { InMemory } from '../memory.js';
12
- import * as RPC from './rpc.js';
13
-
14
- type FileMethods = Omit<ExtractProperties<File, (...args: any[]) => Promise<any>>, typeof Symbol.asyncDispose>;
15
- type FileMethod = keyof FileMethods;
16
- /** @internal */
17
- export interface FileRequest<TMethod extends FileMethod = FileMethod> extends RPC.Request {
18
- fd: number;
19
- scope: 'file';
20
- method: TMethod;
21
- args: Parameters<FileMethods[TMethod]>;
22
- }
23
-
24
- export class PortFile extends File {
25
- public constructor(
26
- public fs: PortFS,
27
- public readonly fd: number,
28
- path: string,
29
- public position: number
30
- ) {
31
- super(fs, path);
32
- }
33
-
34
- public rpc<const T extends FileMethod>(method: T, ...args: Parameters<FileMethods[T]>): Promise<Awaited<ReturnType<FileMethods[T]>>> {
35
- return RPC.request<FileRequest<T>, Awaited<ReturnType<FileMethods[T]>>>(
36
- {
37
- scope: 'file',
38
- fd: this.fd,
39
- method,
40
- args,
41
- },
42
- this.fs.options
43
- );
44
- }
45
-
46
- protected _throwNoSync(syscall: string): never {
47
- throw new ErrnoError(Errno.ENOTSUP, 'Synchronous operations not supported on PortFile', this.path, syscall);
48
- }
49
-
50
- public async stat(): Promise<Stats> {
51
- return new Stats(await this.rpc('stat'));
52
- }
53
-
54
- public statSync(): Stats {
55
- this._throwNoSync('stat');
56
- }
57
-
58
- public truncate(len: number): Promise<void> {
59
- return this.rpc('truncate', len);
60
- }
61
-
62
- public truncateSync(): void {
63
- this._throwNoSync('truncate');
64
- }
65
-
66
- public write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number> {
67
- return this.rpc('write', buffer, offset, length, position);
68
- }
69
-
70
- public writeSync(): number {
71
- this._throwNoSync('write');
72
- }
73
-
74
- public async read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<FileReadResult<TBuffer>> {
75
- const result = await this.rpc('read', buffer, offset, length, position);
76
- return result as FileReadResult<TBuffer>;
77
- }
78
-
79
- public readSync(): number {
80
- this._throwNoSync('read');
81
- }
82
-
83
- public chown(uid: number, gid: number): Promise<void> {
84
- return this.rpc('chown', uid, gid);
85
- }
86
-
87
- public chownSync(): void {
88
- this._throwNoSync('chown');
89
- }
90
-
91
- public chmod(mode: number): Promise<void> {
92
- return this.rpc('chmod', mode);
93
- }
94
-
95
- public chmodSync(): void {
96
- this._throwNoSync('chmod');
97
- }
98
-
99
- public utimes(atime: Date, mtime: Date): Promise<void> {
100
- return this.rpc('utimes', atime, mtime);
101
- }
102
-
103
- public utimesSync(): void {
104
- this._throwNoSync('utimes');
105
- }
106
-
107
- public _setTypeSync(): void {
108
- this._throwNoSync('_setType');
109
- }
110
-
111
- public close(): Promise<void> {
112
- return this.rpc('close');
113
- }
114
-
115
- public closeSync(): void {
116
- this._throwNoSync('close');
117
- }
118
-
119
- public sync(): Promise<void> {
120
- return this.rpc('sync');
121
- }
122
-
123
- public syncSync(): void {
124
- this._throwNoSync('sync');
125
- }
126
- }
127
-
128
- type FSMethods = ExtractProperties<FileSystem, (...args: any[]) => Promise<any> | FileSystemMetadata>;
129
- type FSMethod = keyof FSMethods;
130
- /** @internal */
131
- export interface FSRequest<TMethod extends FSMethod = FSMethod> extends RPC.Request {
132
- scope: 'fs';
133
- method: TMethod;
134
- args: Parameters<FSMethods[TMethod]>;
135
- }
136
-
137
- /**
138
- * PortFS lets you access an FS instance that is running in a port, or the other way around.
139
- *
140
- * Note that *direct* synchronous operations are not permitted on the PortFS,
141
- * regardless of the configuration option of the remote FS.
142
- */
143
- export class PortFS extends Async(FileSystem) {
144
- public readonly port: RPC.Port;
145
-
146
- /**
147
- * @hidden
148
- */
149
- _sync = InMemory.create({ name: 'port-tmpfs' });
150
-
151
- /**
152
- * Constructs a new PortFS instance that connects with the FS running on `options.port`.
153
- */
154
- public constructor(public readonly options: RPC.Options) {
155
- super();
156
- this.port = options.port;
157
- RPC.attach<RPC.Response>(this.port, RPC.handleResponse);
158
- }
159
-
160
- public metadata(): FileSystemMetadata {
161
- return {
162
- ...super.metadata(),
163
- name: 'PortFS',
164
- };
165
- }
166
-
167
- protected rpc<const T extends FSMethod>(method: T, ...args: Parameters<FSMethods[T]>): Promise<Awaited<ReturnType<FSMethods[T]>>> {
168
- return RPC.request<FSRequest<T>, Awaited<ReturnType<FSMethods[T]>>>(
169
- {
170
- scope: 'fs',
171
- method,
172
- args,
173
- },
174
- { ...this.options, fs: this }
175
- );
176
- }
177
-
178
- public async ready(): Promise<void> {
179
- await this.rpc('ready');
180
- await super.ready();
181
- }
182
-
183
- public rename(oldPath: string, newPath: string): Promise<void> {
184
- return this.rpc('rename', oldPath, newPath);
185
- }
186
-
187
- public async stat(path: string): Promise<Stats> {
188
- return new Stats(await this.rpc('stat', path));
189
- }
190
-
191
- public sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
192
- return this.rpc('sync', path, data, stats);
193
- }
194
-
195
- public openFile(path: string, flag: string): Promise<File> {
196
- return this.rpc('openFile', path, flag);
197
- }
198
-
199
- public createFile(path: string, flag: string, mode: number): Promise<File> {
200
- return this.rpc('createFile', path, flag, mode);
201
- }
202
-
203
- public unlink(path: string): Promise<void> {
204
- return this.rpc('unlink', path);
205
- }
206
-
207
- public rmdir(path: string): Promise<void> {
208
- return this.rpc('rmdir', path);
209
- }
210
-
211
- public mkdir(path: string, mode: number): Promise<void> {
212
- return this.rpc('mkdir', path, mode);
213
- }
214
-
215
- public readdir(path: string): Promise<string[]> {
216
- return this.rpc('readdir', path);
217
- }
218
-
219
- public exists(path: string): Promise<boolean> {
220
- return this.rpc('exists', path);
221
- }
222
-
223
- public link(srcpath: string, dstpath: string): Promise<void> {
224
- return this.rpc('link', srcpath, dstpath);
225
- }
226
- }
227
-
228
- let nextFd = 0;
229
-
230
- const descriptors: Map<number, File> = new Map();
231
-
232
- /** @internal */
233
- export type FileOrFSRequest = FSRequest | FileRequest;
234
-
235
- /** @internal */
236
- export async function handleRequest(port: RPC.Port, fs: FileSystem, request: FileOrFSRequest): Promise<void> {
237
- if (!RPC.isMessage(request)) {
238
- return;
239
- }
240
- const { method, args, id, scope, stack } = request;
241
-
242
- let value,
243
- error: boolean = false;
244
-
245
- try {
246
- switch (scope) {
247
- case 'fs':
248
- // @ts-expect-error 2556
249
- value = await fs[method](...args);
250
- if (value instanceof File) {
251
- descriptors.set(++nextFd, value);
252
- value = {
253
- fd: nextFd,
254
- path: value.path,
255
- position: value.position,
256
- };
257
- }
258
- break;
259
- case 'file': {
260
- const { fd } = request;
261
- if (!descriptors.has(fd)) {
262
- throw new ErrnoError(Errno.EBADF);
263
- }
264
- // @ts-expect-error 2556
265
- value = await descriptors.get(fd)![method](...args);
266
- if (method == 'close') {
267
- descriptors.delete(fd);
268
- }
269
- break;
270
- }
271
- default:
272
- return;
273
- }
274
- } catch (e) {
275
- value = e instanceof ErrnoError ? e.toJSON() : (e as object).toString();
276
- error = true;
277
- }
278
-
279
- port.postMessage({ _zenfs: true, scope, id, error, method, stack, value });
280
- }
281
-
282
- export function attachFS(port: RPC.Port, fs: FileSystem): void {
283
- RPC.attach<FileOrFSRequest>(port, request => handleRequest(port, fs, request));
284
- }
285
-
286
- export function detachFS(port: RPC.Port, fs: FileSystem): void {
287
- RPC.detach<FileOrFSRequest>(port, request => handleRequest(port, fs, request));
288
- }
289
-
290
- const _Port = {
291
- name: 'Port',
292
-
293
- options: {
294
- port: {
295
- type: 'object',
296
- required: true,
297
- validator(port: RPC.Port) {
298
- // Check for a `postMessage` function.
299
- if (typeof port?.postMessage != 'function') {
300
- throw new ErrnoError(Errno.EINVAL, 'option must be a port.');
301
- }
302
- },
303
- },
304
- timeout: {
305
- type: 'number',
306
- required: false,
307
- },
308
- },
309
-
310
- isAvailable(): boolean {
311
- return true;
312
- },
313
-
314
- create(options: RPC.Options) {
315
- return new PortFS(options);
316
- },
317
- } satisfies Backend<PortFS, RPC.Options>;
318
- type _Port = typeof _Port;
319
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
320
- export interface Port extends _Port {}
321
- export const Port: Port = _Port;
322
-
323
- export async function resolveRemoteMount<T extends Backend>(port: RPC.Port, config: MountConfiguration<T>, _depth = 0): Promise<FilesystemOf<T>> {
324
- const stopAndReplay = RPC.catchMessages(port);
325
- const fs = await resolveMountConfig(config, _depth);
326
- attachFS(port, fs);
327
- stopAndReplay(fs);
328
- return fs;
329
- }
@@ -1,54 +0,0 @@
1
- # Port Backend
2
-
3
- A backend for usage with ports and workers. See the examples below.
4
-
5
- #### Accessing an FS on a remote Worker from the main thread
6
-
7
- Main:
8
-
9
- ```ts
10
- import { configure } from '@zenfs/core';
11
- import { Port } from '@zenfs/port';
12
- import { Worker } from 'node:worker_threads';
13
-
14
- const worker = new Worker('worker.js');
15
-
16
- await configure({
17
- mounts: {
18
- '/worker': {
19
- backend: Port,
20
- port: worker,
21
- },
22
- },
23
- });
24
- ```
25
-
26
- Worker:
27
-
28
- ```ts
29
- import { InMemory, resolveRemoteMount, attachFS } from '@zenfs/core';
30
- import { parentPort } from 'node:worker_threads';
31
-
32
- await resolveRemoteMount(parentPort, { backend: InMemory, name: 'tmp' });
33
- ```
34
-
35
- If you are using using web workers, you would use `self` instead of importing `parentPort` in the worker, and would not need to import `Worker` in the main thread.
36
-
37
- #### Using with multiple ports on the same thread
38
-
39
- ```ts
40
- import { InMemory, fs, resolveMountConfig, resolveRemoteMount, Port } from '@zenfs/core';
41
- import { MessageChannel } from 'node:worker_threads';
42
-
43
- const { port1: localPort, port2: remotePort } = new MessageChannel();
44
-
45
- fs.mount('/remote', await resolveRemoteMount(remotePort, { backend: InMemory, name: 'tmp' }));
46
- fs.mount('/port', await resolveMountConfig({ backend: Port, port: localPort }));
47
-
48
- const content = 'FS is in a port';
49
-
50
- await fs.promises.writeFile('/port/test', content);
51
-
52
- fs.readFileSync('/remote/test', 'utf8'); // FS is in a port
53
- await fs.promises.readFile('/port/test', 'utf8'); // FS is in a port
54
- ```
@@ -1,167 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { Errno, ErrnoError, type ErrnoErrorJSON } from '../../error.js';
3
- import type { FileSystem } from '../../filesystem.js';
4
- import type { Backend, FilesystemOf } from '../backend.js';
5
- import { handleRequest, PortFile, type PortFS } from './fs.js';
6
- import type { FileOrFSRequest } from './fs.js';
7
-
8
- type _MessageEvent<T = any> = T | { data: T };
9
-
10
- /** @internal */
11
- export interface Port {
12
- postMessage(value: unknown): void;
13
- on?(event: 'message', listener: (value: unknown) => void): this;
14
- off?(event: 'message', listener: (value: unknown) => void): this;
15
- addEventListener?(type: 'message', listener: (ev: _MessageEvent) => void): void;
16
- removeEventListener?(type: 'message', listener: (ev: _MessageEvent) => void): void;
17
- }
18
-
19
- export interface Options {
20
- /**
21
- * The target port that you want to connect to, or the current port if in a port context.
22
- */
23
- port: Port;
24
- /**
25
- * How long to wait for a request to complete
26
- */
27
- timeout?: number;
28
- }
29
-
30
- /**
31
- * An RPC message
32
- */
33
- export interface Message {
34
- _zenfs: true;
35
- scope: 'fs' | 'file';
36
- id: string;
37
- method: string;
38
- stack: string;
39
- }
40
-
41
- export interface Request extends Message {
42
- args: unknown[];
43
- }
44
-
45
- interface _ResponseWithError extends Message {
46
- error: true;
47
- value: ErrnoErrorJSON | string;
48
- }
49
-
50
- interface _ResponseWithValue<T> extends Message {
51
- error: false;
52
- value: Awaited<T> extends File ? FileData : Awaited<T>;
53
- }
54
-
55
- export type Response<T = unknown> = _ResponseWithError | _ResponseWithValue<T>;
56
-
57
- export interface FileData {
58
- fd: number;
59
- path: string;
60
- position: number;
61
- }
62
-
63
- function isFileData(value: unknown): value is FileData {
64
- return typeof value == 'object' && value != null && 'fd' in value && 'path' in value && 'position' in value;
65
- }
66
-
67
- export { FileData as File };
68
-
69
- // general types
70
-
71
- export function isMessage(arg: unknown): arg is Message {
72
- return typeof arg == 'object' && arg != null && '_zenfs' in arg && !!arg._zenfs;
73
- }
74
-
75
- type _Executor = Parameters<ConstructorParameters<typeof Promise<any>>[0]>;
76
-
77
- export interface Executor {
78
- resolve: _Executor[0];
79
- reject: _Executor[1];
80
- fs?: PortFS;
81
- }
82
-
83
- const executors: Map<string, Executor> = new Map();
84
-
85
- export function request<const TRequest extends Request, TValue>(
86
- request: Omit<TRequest, 'id' | 'stack' | '_zenfs'>,
87
- { port, timeout = 1000, fs }: Partial<Options> & { fs?: PortFS } = {}
88
- ): Promise<TValue> {
89
- const stack = '\n' + new Error().stack!.slice('Error:'.length);
90
- if (!port) {
91
- throw ErrnoError.With('EINVAL');
92
- }
93
- return new Promise<TValue>((resolve, reject) => {
94
- const id = Math.random().toString(16).slice(10);
95
- executors.set(id, { resolve, reject, fs });
96
- port.postMessage({ ...request, _zenfs: true, id, stack });
97
- const _ = setTimeout(() => {
98
- const error = new ErrnoError(Errno.EIO, 'RPC Failed');
99
- error.stack += stack;
100
- reject(error);
101
- if (typeof _ == 'object') _.unref();
102
- }, timeout);
103
- });
104
- }
105
-
106
- export function handleResponse<const TResponse extends Response>(response: TResponse): void {
107
- if (!isMessage(response)) {
108
- return;
109
- }
110
- const { id, value, error, stack } = response;
111
- if (!executors.has(id)) {
112
- const error = new ErrnoError(Errno.EIO, 'Invalid RPC id:' + id);
113
- error.stack += stack;
114
- throw error;
115
- }
116
- const { resolve, reject, fs } = executors.get(id)!;
117
- if (error) {
118
- const e = typeof value == 'string' ? new Error(value) : ErrnoError.fromJSON(value);
119
- e.stack += stack;
120
- reject(e);
121
- executors.delete(id);
122
- return;
123
- }
124
-
125
- if (isFileData(value)) {
126
- const { fd, path, position } = value;
127
- const file = new PortFile(fs!, fd, path, position);
128
- resolve(file);
129
- executors.delete(id);
130
- return;
131
- }
132
-
133
- resolve(value);
134
- executors.delete(id);
135
- return;
136
- }
137
-
138
- export function attach<T extends Message>(port: Port, handler: (message: T) => unknown) {
139
- if (!port) {
140
- throw ErrnoError.With('EINVAL');
141
- }
142
- port['on' in port ? 'on' : 'addEventListener']!('message', (message: T | _MessageEvent<T>) => {
143
- handler('data' in message ? message.data : message);
144
- });
145
- }
146
-
147
- export function detach<T extends Message>(port: Port, handler: (message: T) => unknown) {
148
- if (!port) {
149
- throw ErrnoError.With('EINVAL');
150
- }
151
- port['off' in port ? 'off' : 'removeEventListener']!('message', (message: T | _MessageEvent<T>) => {
152
- handler('data' in message ? message.data : message);
153
- });
154
- }
155
-
156
- export function catchMessages<T extends Backend>(port: Port): (fs: FilesystemOf<T>) => void {
157
- const events: _MessageEvent[] = [];
158
- const handler = events.push.bind(events);
159
- attach(port, handler);
160
- return function (fs: FileSystem) {
161
- detach(port, handler);
162
- for (const event of events) {
163
- const request = ('data' in event ? event.data : event) as FileOrFSRequest;
164
- void handleRequest(port, fs, request);
165
- }
166
- };
167
- }
@@ -1,3 +0,0 @@
1
- ## IndexFS
2
-
3
- The `IndexFS` class is a base class for other backends that uses an `Index` to store stats for files and directories. The `Index` class inherits from `Map` and stores information about files and directories in a file system.