@zenfs/core 0.9.2 → 0.9.3

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.
@@ -0,0 +1,546 @@
1
+ import type { ExtractProperties } from 'utilium';
2
+ import { ApiError, ErrorCode } from './ApiError.js';
3
+ import { rootCred, type Cred } from './cred.js';
4
+ import { join } from './emulation/path.js';
5
+ import { PreloadFile, parseFlag, type File } from './file.js';
6
+ import type { Stats } from './stats.js';
7
+
8
+ export type FileContents = Uint8Array | string;
9
+
10
+ /**
11
+ * Metadata about a FileSystem
12
+ */
13
+ export interface FileSystemMetadata {
14
+ /**
15
+ * The name of the FS
16
+ */
17
+ name: string;
18
+
19
+ /**
20
+ * Wheter the FS is readonly or not
21
+ */
22
+ readonly: boolean;
23
+
24
+ /**
25
+ * The total space
26
+ */
27
+ totalSpace: number;
28
+
29
+ /**
30
+ * The available space
31
+ */
32
+ freeSpace: number;
33
+ }
34
+
35
+ /**
36
+ * Structure for a filesystem. All ZenFS backends must extend this.
37
+ *
38
+ * This class includes some default implementations
39
+ *
40
+ * Assume the following about arguments passed to each API method:
41
+ *
42
+ * - Every path is an absolute path. `.`, `..`, and other items are resolved into an absolute form.
43
+ * - All arguments are present. Any optional arguments at the Node API level have been passed in with their default values.
44
+ */
45
+ export abstract class FileSystem {
46
+ /**
47
+ * Get metadata about the current file syste,
48
+ */
49
+ public metadata(): FileSystemMetadata {
50
+ return {
51
+ name: this.constructor.name,
52
+ readonly: false,
53
+ totalSpace: 0,
54
+ freeSpace: 0,
55
+ };
56
+ }
57
+
58
+ public constructor(options?: object) {
59
+ // unused
60
+ }
61
+
62
+ public abstract ready(): Promise<this>;
63
+
64
+ /**
65
+ * Asynchronous rename. No arguments other than a possible exception
66
+ * are given to the completion callback.
67
+ */
68
+ public abstract rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
69
+ /**
70
+ * Synchronous rename.
71
+ */
72
+ public abstract renameSync(oldPath: string, newPath: string, cred: Cred): void;
73
+
74
+ /**
75
+ * Asynchronous `stat`.
76
+ */
77
+ public abstract stat(path: string, cred: Cred): Promise<Stats>;
78
+
79
+ /**
80
+ * Synchronous `stat`.
81
+ */
82
+ public abstract statSync(path: string, cred: Cred): Stats;
83
+
84
+ /**
85
+ * Opens the file at path p with the given flag. The file must exist.
86
+ * @param p The path to open.
87
+ * @param flag The flag to use when opening the file.
88
+ */
89
+ public abstract openFile(path: string, flag: string, cred: Cred): Promise<File>;
90
+
91
+ /**
92
+ * Opens the file at path p with the given flag. The file must exist.
93
+ * @param p The path to open.
94
+ * @param flag The flag to use when opening the file.
95
+ * @return A File object corresponding to the opened file.
96
+ */
97
+ public abstract openFileSync(path: string, flag: string, cred: Cred): File;
98
+
99
+ /**
100
+ * Create the file at path p with the given mode. Then, open it with the given
101
+ * flag.
102
+ */
103
+ public abstract createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>;
104
+
105
+ /**
106
+ * Create the file at path p with the given mode. Then, open it with the given
107
+ * flag.
108
+ */
109
+ public abstract createFileSync(path: string, flag: string, mode: number, cred: Cred): File;
110
+
111
+ /**
112
+ * Asynchronous `unlink`.
113
+ */
114
+ public abstract unlink(path: string, cred: Cred): Promise<void>;
115
+ /**
116
+ * Synchronous `unlink`.
117
+ */
118
+ public abstract unlinkSync(path: string, cred: Cred): void;
119
+ // Directory operations
120
+ /**
121
+ * Asynchronous `rmdir`.
122
+ */
123
+ public abstract rmdir(path: string, cred: Cred): Promise<void>;
124
+ /**
125
+ * Synchronous `rmdir`.
126
+ */
127
+ public abstract rmdirSync(path: string, cred: Cred): void;
128
+ /**
129
+ * Asynchronous `mkdir`.
130
+ * @param mode Mode to make the directory using. Can be ignored if
131
+ * the filesystem doesn't support permissions.
132
+ */
133
+ public abstract mkdir(path: string, mode: number, cred: Cred): Promise<void>;
134
+ /**
135
+ * Synchronous `mkdir`.
136
+ * @param mode Mode to make the directory using. Can be ignored if
137
+ * the filesystem doesn't support permissions.
138
+ */
139
+ public abstract mkdirSync(path: string, mode: number, cred: Cred): void;
140
+ /**
141
+ * Asynchronous `readdir`. Reads the contents of a directory.
142
+ *
143
+ * The callback gets two arguments `(err, files)` where `files` is an array of
144
+ * the names of the files in the directory excluding `'.'` and `'..'`.
145
+ */
146
+ public abstract readdir(path: string, cred: Cred): Promise<string[]>;
147
+ /**
148
+ * Synchronous `readdir`. Reads the contents of a directory.
149
+ */
150
+ public abstract readdirSync(path: string, cred: Cred): string[];
151
+
152
+ /**
153
+ * Test whether or not the given path exists by checking with the file system.
154
+ */
155
+ public async exists(path: string, cred: Cred): Promise<boolean> {
156
+ try {
157
+ await this.stat(path, cred);
158
+ return true;
159
+ } catch (e) {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Test whether or not the given path exists by checking with the file system.
166
+ */
167
+ public existsSync(path: string, cred: Cred): boolean {
168
+ try {
169
+ this.statSync(path, cred);
170
+ return true;
171
+ } catch (e) {
172
+ return false;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Asynchronous `link`.
178
+ */
179
+ public abstract link(srcpath: string, dstpath: string, cred: Cred): Promise<void>;
180
+
181
+ /**
182
+ * Synchronous `link`.
183
+ */
184
+ public abstract linkSync(srcpath: string, dstpath: string, cred: Cred): void;
185
+
186
+ /**
187
+ * Synchronize the data and stats for path asynchronously
188
+ */
189
+ public abstract sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
190
+
191
+ /**
192
+ * Synchronize the data and stats for path synchronously
193
+ */
194
+ public abstract syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
195
+ }
196
+
197
+ /**
198
+ * @internal
199
+ */
200
+ declare abstract class SyncFileSystem extends FileSystem {
201
+ metadata(): FileSystemMetadata;
202
+ ready(): Promise<this>;
203
+ exists(path: string, cred: Cred): Promise<boolean>;
204
+ rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
205
+ stat(path: string, cred: Cred): Promise<Stats>;
206
+ createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>;
207
+ openFile(path: string, flag: string, cred: Cred): Promise<File>;
208
+ unlink(path: string, cred: Cred): Promise<void>;
209
+ rmdir(path: string, cred: Cred): Promise<void>;
210
+ mkdir(path: string, mode: number, cred: Cred): Promise<void>;
211
+ readdir(path: string, cred: Cred): Promise<string[]>;
212
+ link(srcpath: string, dstpath: string, cred: Cred): Promise<void>;
213
+ sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
214
+ }
215
+
216
+ /**
217
+ * Implements the asynchronous API in terms of the synchronous API.
218
+ */
219
+ export function Sync<T extends abstract new (...args) => FileSystem>(FS: T): (abstract new (...args) => SyncFileSystem) & T {
220
+ abstract class _SyncFileSystem extends FS implements SyncFileSystem {
221
+ public async ready(): Promise<this> {
222
+ return this;
223
+ }
224
+
225
+ public async exists(path: string, cred: Cred): Promise<boolean> {
226
+ return this.existsSync(path, cred);
227
+ }
228
+
229
+ public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> {
230
+ return this.renameSync(oldPath, newPath, cred);
231
+ }
232
+
233
+ public async stat(path: string, cred: Cred): Promise<Stats> {
234
+ return this.statSync(path, cred);
235
+ }
236
+
237
+ public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> {
238
+ return this.createFileSync(path, flag, mode, cred);
239
+ }
240
+
241
+ public async openFile(path: string, flag: string, cred: Cred): Promise<File> {
242
+ return this.openFileSync(path, flag, cred);
243
+ }
244
+
245
+ public async unlink(path: string, cred: Cred): Promise<void> {
246
+ return this.unlinkSync(path, cred);
247
+ }
248
+
249
+ public async rmdir(path: string, cred: Cred): Promise<void> {
250
+ return this.rmdirSync(path, cred);
251
+ }
252
+
253
+ public async mkdir(path: string, mode: number, cred: Cred): Promise<void> {
254
+ return this.mkdirSync(path, mode, cred);
255
+ }
256
+
257
+ public async readdir(path: string, cred: Cred): Promise<string[]> {
258
+ return this.readdirSync(path, cred);
259
+ }
260
+
261
+ public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> {
262
+ return this.linkSync(srcpath, dstpath, cred);
263
+ }
264
+
265
+ public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
266
+ return this.syncSync(path, data, stats);
267
+ }
268
+ }
269
+ return _SyncFileSystem;
270
+ }
271
+
272
+ /**
273
+ * @internal
274
+ */
275
+ declare abstract class AsyncFileSystem extends FileSystem {
276
+ /**
277
+ * @hidden
278
+ */
279
+ abstract _sync: FileSystem;
280
+ metadata(): FileSystemMetadata;
281
+ ready(): Promise<this>;
282
+ renameSync(oldPath: string, newPath: string, cred: Cred): void;
283
+ statSync(path: string, cred: Cred): Stats;
284
+ createFileSync(path: string, flag: string, mode: number, cred: Cred): File;
285
+ openFileSync(path: string, flag: string, cred: Cred): File;
286
+ unlinkSync(path: string, cred: Cred): void;
287
+ rmdirSync(path: string, cred: Cred): void;
288
+ mkdirSync(path: string, mode: number, cred: Cred): void;
289
+ readdirSync(path: string, cred: Cred): string[];
290
+ linkSync(srcpath: string, dstpath: string, cred: Cred): void;
291
+ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
292
+ }
293
+
294
+ type AsyncMethods = ExtractProperties<FileSystem, (...args) => Promise<unknown>>;
295
+
296
+ /**
297
+ * @internal
298
+ */
299
+ type AsyncOperation = {
300
+ [K in keyof AsyncMethods]: [K, ...Parameters<FileSystem[K]>];
301
+ }[keyof AsyncMethods];
302
+
303
+ /**
304
+ * Async() implements synchronous methods on an asynchronous file system
305
+ *
306
+ * Implementing classes must define a protected _sync property for the synchronous file system used as a cache.
307
+ * by:
308
+ *
309
+ * - Performing operations over the in-memory copy, while asynchronously pipelining them
310
+ * to the backing store.
311
+ * - During application loading, the contents of the async file system can be reloaded into
312
+ * the synchronous store, if desired.
313
+ *
314
+ */
315
+ export function Async<T extends abstract new (...args) => FileSystem>(FS: T): (abstract new (...args) => AsyncFileSystem) & T {
316
+ abstract class _AsyncFileSystem extends FS implements AsyncFileSystem {
317
+ /**
318
+ * Queue of pending asynchronous operations.
319
+ */
320
+ private _queue: AsyncOperation[] = [];
321
+ private _queueRunning: boolean = false;
322
+ private _isInitialized: boolean = false;
323
+
324
+ abstract _sync: FileSystem;
325
+
326
+ public async ready(): Promise<this> {
327
+ await this._sync.ready();
328
+ if (this._isInitialized) {
329
+ return this;
330
+ }
331
+
332
+ try {
333
+ await this.crossCopy('/');
334
+ this._isInitialized = true;
335
+ } catch (e) {
336
+ this._isInitialized = false;
337
+ throw e;
338
+ }
339
+ return this;
340
+ }
341
+
342
+ public renameSync(oldPath: string, newPath: string, cred: Cred): void {
343
+ this._sync.renameSync(oldPath, newPath, cred);
344
+ this.queue('rename', oldPath, newPath, cred);
345
+ }
346
+
347
+ public statSync(p: string, cred: Cred): Stats {
348
+ return this._sync.statSync(p, cred);
349
+ }
350
+
351
+ public createFileSync(path: string, flag: string, mode: number, cred: Cred): PreloadFile<this> {
352
+ const file = this._sync.createFileSync(path, flag, mode, cred);
353
+ this.queue('createFile', path, flag, mode, cred);
354
+ const stats = file.statSync();
355
+ const buffer = new Uint8Array(stats.size);
356
+ file.readSync(buffer);
357
+ return new PreloadFile(this, path, flag, stats, buffer);
358
+ }
359
+
360
+ public openFileSync(path: string, flag: string, cred: Cred): File {
361
+ return this._sync.openFileSync(path, flag, cred);
362
+ }
363
+
364
+ public unlinkSync(p: string, cred: Cred): void {
365
+ this._sync.unlinkSync(p, cred);
366
+ this.queue('unlink', p, cred);
367
+ }
368
+
369
+ public rmdirSync(p: string, cred: Cred): void {
370
+ this._sync.rmdirSync(p, cred);
371
+ this.queue('rmdir', p, cred);
372
+ }
373
+
374
+ public mkdirSync(p: string, mode: number, cred: Cred): void {
375
+ this._sync.mkdirSync(p, mode, cred);
376
+ this.queue('mkdir', p, mode, cred);
377
+ }
378
+
379
+ public readdirSync(p: string, cred: Cred): string[] {
380
+ return this._sync.readdirSync(p, cred);
381
+ }
382
+
383
+ public linkSync(srcpath: string, dstpath: string, cred: Cred): void {
384
+ this._sync.linkSync(srcpath, dstpath, cred);
385
+ this.queue('link', srcpath, dstpath, cred);
386
+ }
387
+
388
+ public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
389
+ this._sync.syncSync(path, data, stats);
390
+ this.queue('sync', path, data, stats);
391
+ }
392
+
393
+ public existsSync(p: string, cred: Cred): boolean {
394
+ return this._sync.existsSync(p, cred);
395
+ }
396
+
397
+ /**
398
+ * @internal
399
+ */
400
+ protected async crossCopy(p: string): Promise<void> {
401
+ const stats = await this.stat(p, rootCred);
402
+ if (stats.isDirectory()) {
403
+ if (p !== '/') {
404
+ const stats = await this.stat(p, rootCred);
405
+ this._sync.mkdirSync(p, stats.mode, stats.cred());
406
+ }
407
+ const files = await this.readdir(p, rootCred);
408
+ for (const file of files) {
409
+ await this.crossCopy(join(p, file));
410
+ }
411
+ } else {
412
+ const asyncFile = await this.openFile(p, parseFlag('r'), rootCred);
413
+ const syncFile = this._sync.createFileSync(p, parseFlag('w'), stats.mode, rootCred);
414
+ try {
415
+ const { size } = await asyncFile.stat();
416
+ const buffer = new Uint8Array(size);
417
+ await asyncFile.read(buffer);
418
+ syncFile.writeSync(buffer);
419
+ } finally {
420
+ await asyncFile.close();
421
+ syncFile.closeSync();
422
+ }
423
+ }
424
+ }
425
+
426
+ /**
427
+ * @internal
428
+ */
429
+ private async _next(): Promise<void> {
430
+ if (this._queue.length == 0) {
431
+ this._queueRunning = false;
432
+ return;
433
+ }
434
+
435
+ const [method, ...args] = this._queue.shift()!;
436
+ // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
437
+ await this[method](...args);
438
+ await this._next();
439
+ }
440
+
441
+ /**
442
+ * @internal
443
+ */
444
+ private queue(...op: AsyncOperation) {
445
+ this._queue.push(op);
446
+ if (this._queueRunning) {
447
+ return;
448
+ }
449
+
450
+ this._queueRunning = true;
451
+ this._next();
452
+ }
453
+ }
454
+
455
+ return _AsyncFileSystem;
456
+ }
457
+
458
+ /**
459
+ * @internal
460
+ */
461
+ declare abstract class ReadonlyFileSystem extends FileSystem {
462
+ metadata(): FileSystemMetadata;
463
+ rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
464
+ renameSync(oldPath: string, newPath: string, cred: Cred): void;
465
+ createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>;
466
+ createFileSync(path: string, flag: string, mode: number, cred: Cred): File;
467
+ unlink(path: string, cred: Cred): Promise<void>;
468
+ unlinkSync(path: string, cred: Cred): void;
469
+ rmdir(path: string, cred: Cred): Promise<void>;
470
+ rmdirSync(path: string, cred: Cred): void;
471
+ mkdir(path: string, mode: number, cred: Cred): Promise<void>;
472
+ mkdirSync(path: string, mode: number, cred: Cred): void;
473
+ link(srcpath: string, dstpath: string, cred: Cred): Promise<void>;
474
+ linkSync(srcpath: string, dstpath: string, cred: Cred): void;
475
+ sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
476
+ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
477
+ }
478
+
479
+ /**
480
+ * Implements the non-readonly methods to throw `EROFS`
481
+ */
482
+ export function Readonly<T extends abstract new (...args) => FileSystem>(FS: T): (abstract new (...args) => ReadonlyFileSystem) & T {
483
+ abstract class _ReadonlyFileSystem extends FS implements ReadonlyFileSystem {
484
+ public metadata(): FileSystemMetadata {
485
+ return { ...super.metadata(), readonly: true };
486
+ }
487
+ /* eslint-disable @typescript-eslint/no-unused-vars */
488
+ public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> {
489
+ throw new ApiError(ErrorCode.EROFS);
490
+ }
491
+
492
+ public renameSync(oldPath: string, newPath: string, cred: Cred): void {
493
+ throw new ApiError(ErrorCode.EROFS);
494
+ }
495
+
496
+ public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> {
497
+ throw new ApiError(ErrorCode.EROFS);
498
+ }
499
+
500
+ public createFileSync(path: string, flag: string, mode: number, cred: Cred): File {
501
+ throw new ApiError(ErrorCode.EROFS);
502
+ }
503
+
504
+ public async unlink(path: string, cred: Cred): Promise<void> {
505
+ throw new ApiError(ErrorCode.EROFS);
506
+ }
507
+
508
+ public unlinkSync(path: string, cred: Cred): void {
509
+ throw new ApiError(ErrorCode.EROFS);
510
+ }
511
+
512
+ public async rmdir(path: string, cred: Cred): Promise<void> {
513
+ throw new ApiError(ErrorCode.EROFS);
514
+ }
515
+
516
+ public rmdirSync(path: string, cred: Cred): void {
517
+ throw new ApiError(ErrorCode.EROFS);
518
+ }
519
+
520
+ public async mkdir(path: string, mode: number, cred: Cred): Promise<void> {
521
+ throw new ApiError(ErrorCode.EROFS);
522
+ }
523
+
524
+ public mkdirSync(path: string, mode: number, cred: Cred): void {
525
+ throw new ApiError(ErrorCode.EROFS);
526
+ }
527
+
528
+ public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> {
529
+ throw new ApiError(ErrorCode.EROFS);
530
+ }
531
+
532
+ public linkSync(srcpath: string, dstpath: string, cred: Cred): void {
533
+ throw new ApiError(ErrorCode.EROFS);
534
+ }
535
+
536
+ public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
537
+ throw new ApiError(ErrorCode.EROFS);
538
+ }
539
+
540
+ public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
541
+ throw new ApiError(ErrorCode.EROFS);
542
+ }
543
+ /* eslint-enable @typescript-eslint/no-unused-vars */
544
+ }
545
+ return _ReadonlyFileSystem;
546
+ }
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ export * from './ApiError.js';
2
+ export * from './backends/AsyncStore.js';
3
+ export * from './backends/InMemory.js';
4
+ export * from './backends/Index.js';
5
+ export * from './backends/Locked.js';
6
+ export * from './backends/Overlay.js';
7
+ export * from './backends/SyncStore.js';
8
+ export * from './backends/backend.js';
9
+ export * from './config.js';
10
+ export * from './cred.js';
11
+ export * from './file.js';
12
+ export * from './filesystem.js';
13
+ export * from './inode.js';
14
+ export * from './mutex.js';
15
+ export * from './stats.js';
16
+ export * from './utils.js';
17
+
18
+ export * from './emulation/index.js';
19
+ import * as fs from './emulation/index.js';
20
+ export { fs };
21
+ export default fs;