@zenfs/core 0.5.12 → 0.7.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/config.d.ts CHANGED
@@ -1,23 +1,27 @@
1
- import type { Backend, BackendConfig } from './backends/backend.js';
1
+ import type { Backend, BackendConfiguration } from './backends/backend.js';
2
2
  import { FileSystem } from './filesystem.js';
3
3
  /**
4
- * Initializes ZenFS with the given file systems.
4
+ * Configuration for a specific mount point
5
5
  */
6
- export declare function initialize(mounts: {
7
- [point: string]: FileSystem;
8
- }, uid?: number, gid?: number): void;
6
+ export type MountConfiguration<FS extends FileSystem = FileSystem> = FS | BackendConfiguration<FS> | Backend<FS>;
9
7
  /**
10
- * Defines a mapping of mount points to their configurations
8
+ * Retrieve a file system with the given configuration.
9
+ * @param config A BackendConfig object.
11
10
  */
12
- export interface ConfigMapping {
13
- [mountPoint: string]: FileSystem | BackendConfig | Backend;
14
- }
11
+ export declare function resolveMountConfig<FS extends FileSystem>(config: MountConfiguration<FS>, _depth?: number): Promise<FS>;
15
12
  /**
16
- * A configuration for ZenFS
13
+ *A mapping of mount points to their configurations
17
14
  */
18
- export type Configuration = FileSystem | BackendConfig | ConfigMapping;
15
+ export type MappingConfiguration = Partial<{
16
+ uid: number;
17
+ gid: number;
18
+ }> & Record<string, FileSystem | BackendConfiguration | Backend>;
19
+ /**
20
+ * Configuration for the file systems
21
+ */
22
+ export type Configuration = MountConfiguration | MappingConfiguration;
19
23
  /**
20
24
  * Creates filesystems with the given configuration, and initializes ZenFS with it.
21
- * See the Configuration type for more info on the configuration object.
25
+ * @see Configuration for more info on the configuration object.
22
26
  */
23
27
  export declare function configure(config: Configuration): Promise<void>;
package/dist/config.js CHANGED
@@ -1,35 +1,66 @@
1
- import { isBackend, resolveBackend } from './backends/backend.js';
1
+ import { ApiError, ErrorCode } from './ApiError.js';
2
+ import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
2
3
  import * as fs from './emulation/index.js';
3
4
  import { setCred } from './emulation/shared.js';
4
5
  import { FileSystem } from './filesystem.js';
6
+ function isMountConfig(arg) {
7
+ return isBackendConfig(arg) || isBackend(arg) || arg instanceof FileSystem;
8
+ }
5
9
  /**
6
- * Initializes ZenFS with the given file systems.
10
+ * Retrieve a file system with the given configuration.
11
+ * @param config A BackendConfig object.
7
12
  */
8
- export function initialize(mounts, uid = 0, gid = 0) {
9
- setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
10
- fs.initialize(mounts);
13
+ export async function resolveMountConfig(config, _depth = 0) {
14
+ if (typeof config !== 'object' || config == null) {
15
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid options on mount configuration');
16
+ }
17
+ if (!isMountConfig(config)) {
18
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid mount configuration');
19
+ }
20
+ if (config instanceof FileSystem) {
21
+ return config;
22
+ }
23
+ if (isBackend(config)) {
24
+ config = { backend: config };
25
+ }
26
+ for (const [key, value] of Object.entries(config)) {
27
+ if (key == 'backend') {
28
+ continue;
29
+ }
30
+ if (!isMountConfig(value)) {
31
+ continue;
32
+ }
33
+ if (_depth > 10) {
34
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid configuration, too deep and possibly infinite');
35
+ }
36
+ config[key] = await resolveMountConfig(value, ++_depth);
37
+ }
38
+ const { backend } = config;
39
+ if (!(await backend.isAvailable())) {
40
+ throw new ApiError(ErrorCode.EPERM, 'Backend not available: ' + backend);
41
+ }
42
+ checkOptions(backend, config);
43
+ const mount = backend.create(config);
44
+ await mount.ready();
45
+ return mount;
11
46
  }
12
47
  /**
13
48
  * Creates filesystems with the given configuration, and initializes ZenFS with it.
14
- * See the Configuration type for more info on the configuration object.
49
+ * @see Configuration for more info on the configuration object.
15
50
  */
16
51
  export async function configure(config) {
17
- if ('backend' in config || config instanceof FileSystem) {
52
+ const uid = 'uid' in config ? +config.uid || 0 : 0;
53
+ const gid = 'gid' in config ? +config.gid || 0 : 0;
54
+ if (isMountConfig(config)) {
18
55
  // single FS
19
56
  config = { '/': config };
20
57
  }
21
- for (let [point, value] of Object.entries(config)) {
22
- if (typeof value == 'number') {
23
- //should never happen
58
+ for (const [point, value] of Object.entries(config)) {
59
+ if (point == 'uid' || point == 'gid' || typeof value == 'number') {
24
60
  continue;
25
61
  }
26
- if (value instanceof FileSystem) {
27
- continue;
28
- }
29
- if (isBackend(value)) {
30
- value = { backend: value };
31
- }
32
- config[point] = await resolveBackend(value);
62
+ config[point] = await resolveMountConfig(value);
33
63
  }
34
- initialize(config);
64
+ fs.mountMapping(config);
65
+ setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
35
66
  }
@@ -4,5 +4,5 @@ export * as promises from './promises.js';
4
4
  export * as constants from './constants.js';
5
5
  export * from './streams.js';
6
6
  export * from './dir.js';
7
- export { initialize, mounts, mount, umount, _toUnixTimestamp } from './shared.js';
7
+ export { mountMapping, mounts, mount, umount, _toUnixTimestamp } from './shared.js';
8
8
  export { Stats, BigIntStats, StatsFs } from '../stats.js';
@@ -4,5 +4,5 @@ export * as promises from './promises.js';
4
4
  export * as constants from './constants.js';
5
5
  export * from './streams.js';
6
6
  export * from './dir.js';
7
- export { initialize, mounts, mount, umount, _toUnixTimestamp } from './shared.js';
7
+ export { mountMapping, mounts, mount, umount, _toUnixTimestamp } from './shared.js';
8
8
  export { Stats, BigIntStats, StatsFs } from '../stats.js';
@@ -5,7 +5,7 @@ import { BigIntStats, FileType } from '../stats.js';
5
5
  import { F_OK } from './constants.js';
6
6
  import { Dirent } from './dir.js';
7
7
  import { dirname, join, parse } from './path.js';
8
- import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, normalizeMode, normalizeOptions, normalizePath, normalizeTime, resolveFS } from './shared.js';
8
+ import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, normalizeMode, normalizeOptions, normalizePath, normalizeTime, resolveMount } from './shared.js';
9
9
  export * as constants from './constants.js';
10
10
  export class FileHandle {
11
11
  constructor(
@@ -228,7 +228,7 @@ export class FileHandle {
228
228
  async function doOp(...[name, resolveSymlinks, rawPath, ...args]) {
229
229
  rawPath = normalizePath(rawPath);
230
230
  const _path = resolveSymlinks && (await exists(rawPath)) ? await realpath(rawPath) : rawPath;
231
- const { fs, path } = resolveFS(_path);
231
+ const { fs, path } = resolveMount(_path);
232
232
  try {
233
233
  // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
234
234
  return fs[name](path, ...args);
@@ -246,8 +246,8 @@ async function doOp(...[name, resolveSymlinks, rawPath, ...args]) {
246
246
  export async function rename(oldPath, newPath) {
247
247
  oldPath = normalizePath(oldPath);
248
248
  newPath = normalizePath(newPath);
249
- const src = resolveFS(oldPath);
250
- const dst = resolveFS(newPath);
249
+ const src = resolveMount(oldPath);
250
+ const dst = resolveMount(newPath);
251
251
  try {
252
252
  if (src.mountPoint == dst.mountPoint) {
253
253
  await src.fs.rename(src.path, dst.path, cred);
@@ -267,7 +267,7 @@ rename;
267
267
  */
268
268
  export async function exists(_path) {
269
269
  try {
270
- const { fs, path } = resolveFS(await realpath(_path));
270
+ const { fs, path } = resolveMount(await realpath(_path));
271
271
  return await fs.exists(path, cred);
272
272
  }
273
273
  catch (e) {
@@ -395,7 +395,7 @@ async function _readFile(fname, flag, resolveSymlinks) {
395
395
  }
396
396
  }
397
397
  export async function readFile(filename, _options) {
398
- const options = normalizeOptions(_options, null, 'r', null);
398
+ const options = normalizeOptions(_options, null, 'r', 0);
399
399
  const flag = parseFlag(options.flag);
400
400
  if (!isReadable(flag)) {
401
401
  throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
@@ -633,7 +633,7 @@ export async function realpath(path, options) {
633
633
  path = normalizePath(path);
634
634
  const { base, dir } = parse(path);
635
635
  const lpath = join(dir == '/' ? '/' : await realpath(dir), base);
636
- const { fs, path: resolvedPath, mountPoint } = resolveFS(lpath);
636
+ const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
637
637
  try {
638
638
  const stats = await fs.stat(resolvedPath, cred);
639
639
  if (!stats.isSymbolicLink()) {
@@ -70,7 +70,7 @@ export declare function umount(mountPoint: string): void;
70
70
  /**
71
71
  * Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
72
72
  */
73
- export declare function resolveFS(path: string): {
73
+ export declare function resolveMount(path: string): {
74
74
  fs: FileSystem;
75
75
  path: string;
76
76
  mountPoint: string;
@@ -84,7 +84,7 @@ export declare function fixPaths(text: string, paths: {
84
84
  export declare function fixError<E extends Error>(e: E, paths: {
85
85
  [from: string]: string;
86
86
  }): E;
87
- export declare function initialize(mountMapping: MountMapping): void;
87
+ export declare function mountMapping(mountMapping: MountMapping): void;
88
88
  /**
89
89
  * Types supports as path parameters.
90
90
  *
@@ -153,7 +153,7 @@ export function umount(mountPoint) {
153
153
  /**
154
154
  * Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
155
155
  */
156
- export function resolveFS(path) {
156
+ export function resolveMount(path) {
157
157
  path = normalizePath(path);
158
158
  const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // decending order of the string length
159
159
  for (const [mountPoint, fs] of sortedMounts) {
@@ -184,7 +184,7 @@ export function fixError(e, paths) {
184
184
  e.message = fixPaths(e.message, paths);
185
185
  return e;
186
186
  }
187
- export function initialize(mountMapping) {
187
+ export function mountMapping(mountMapping) {
188
188
  if ('/' in mountMapping) {
189
189
  umount('/');
190
190
  }
@@ -4,10 +4,10 @@ import { ActionType, isAppendable, isReadable, isWriteable, parseFlag, pathExist
4
4
  import { BigIntStats, FileType } from '../stats.js';
5
5
  import { Dirent } from './dir.js';
6
6
  import { dirname, join, parse } from './path.js';
7
- import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, normalizeMode, normalizeOptions, normalizePath, normalizeTime, resolveFS } from './shared.js';
7
+ import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, normalizeMode, normalizeOptions, normalizePath, normalizeTime, resolveMount } from './shared.js';
8
8
  function doOp(...[name, resolveSymlinks, path, ...args]) {
9
9
  path = normalizePath(path);
10
- const { fs, path: resolvedPath } = resolveFS(resolveSymlinks && existsSync(path) ? realpathSync(path) : path);
10
+ const { fs, path: resolvedPath } = resolveMount(resolveSymlinks && existsSync(path) ? realpathSync(path) : path);
11
11
  try {
12
12
  // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
13
13
  return fs[name](resolvedPath, ...args);
@@ -24,8 +24,8 @@ function doOp(...[name, resolveSymlinks, path, ...args]) {
24
24
  export function renameSync(oldPath, newPath) {
25
25
  oldPath = normalizePath(oldPath);
26
26
  newPath = normalizePath(newPath);
27
- const _old = resolveFS(oldPath);
28
- const _new = resolveFS(newPath);
27
+ const _old = resolveMount(oldPath);
28
+ const _new = resolveMount(newPath);
29
29
  const paths = { [_old.path]: oldPath, [_new.path]: newPath };
30
30
  try {
31
31
  if (_old === _new) {
@@ -46,7 +46,7 @@ renameSync;
46
46
  export function existsSync(path) {
47
47
  path = normalizePath(path);
48
48
  try {
49
- const { fs, path: resolvedPath } = resolveFS(realpathSync(path));
49
+ const { fs, path: resolvedPath } = resolveMount(realpathSync(path));
50
50
  return fs.existsSync(resolvedPath, cred);
51
51
  }
52
52
  catch (e) {
@@ -517,7 +517,7 @@ export function realpathSync(path, options) {
517
517
  path = normalizePath(path);
518
518
  const { base, dir } = parse(path);
519
519
  const lpath = join(dir == '/' ? '/' : realpathSync(dir), base);
520
- const { fs, path: resolvedPath, mountPoint } = resolveFS(lpath);
520
+ const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
521
521
  try {
522
522
  const stats = fs.statSync(resolvedPath, cred);
523
523
  if (!stats.isSymbolicLink()) {
package/dist/file.d.ts CHANGED
@@ -187,12 +187,9 @@ export declare abstract class File {
187
187
  * An implementation of the File interface that operates on a file that is
188
188
  * completely in-memory. PreloadFiles are backed by a Uint8Array.
189
189
  *
190
- * This is also an abstract class, as it lacks an implementation of 'sync' and
191
- * 'close'. Each filesystem that wishes to use this file representation must
192
- * extend this class and implement those two methods.
193
190
  * @todo 'close' lever that disables functionality once closed.
194
191
  */
195
- export declare abstract class PreloadFile<FS extends FileSystem> extends File {
192
+ export declare class PreloadFile<FS extends FileSystem> extends File {
196
193
  /**
197
194
  * The file system that created the file.
198
195
  */
@@ -246,6 +243,10 @@ export declare abstract class PreloadFile<FS extends FileSystem> extends File {
246
243
  * @param newPos new position
247
244
  */
248
245
  set position(newPos: number);
246
+ sync(): Promise<void>;
247
+ syncSync(): void;
248
+ close(): Promise<void>;
249
+ closeSync(): void;
249
250
  /**
250
251
  * Asynchronous `stat`.
251
252
  */
@@ -350,16 +351,6 @@ export declare abstract class PreloadFile<FS extends FileSystem> extends File {
350
351
  _setType(type: FileType): Promise<void>;
351
352
  _setTypeSync(type: FileType): void;
352
353
  }
353
- /**
354
- * For synchronous file systems
355
- */
356
- export declare class SyncFile<FS extends FileSystem> extends PreloadFile<FS> {
357
- constructor(_fs: FS, _path: string, _flag: string, _stat: Stats, contents?: Uint8Array);
358
- sync(): Promise<void>;
359
- syncSync(): void;
360
- close(): Promise<void>;
361
- closeSync(): void;
362
- }
363
354
  /**
364
355
  * For the filesystems which do not sync to anything..
365
356
  */
package/dist/file.js CHANGED
@@ -154,9 +154,6 @@ export class File {
154
154
  * An implementation of the File interface that operates on a file that is
155
155
  * completely in-memory. PreloadFiles are backed by a Uint8Array.
156
156
  *
157
- * This is also an abstract class, as it lacks an implementation of 'sync' and
158
- * 'close'. Each filesystem that wishes to use this file representation must
159
- * extend this class and implement those two methods.
160
157
  * @todo 'close' lever that disables functionality once closed.
161
158
  */
162
159
  export class PreloadFile extends File {
@@ -230,6 +227,26 @@ export class PreloadFile extends File {
230
227
  set position(newPos) {
231
228
  this._position = newPos;
232
229
  }
230
+ async sync() {
231
+ if (!this.isDirty()) {
232
+ return;
233
+ }
234
+ await this.fs.sync(this.path, this._buffer, this.stats);
235
+ this._dirty = false;
236
+ }
237
+ syncSync() {
238
+ if (!this.isDirty()) {
239
+ return;
240
+ }
241
+ this.fs.syncSync(this.path, this._buffer, this.stats);
242
+ this._dirty = false;
243
+ }
244
+ async close() {
245
+ await this.sync();
246
+ }
247
+ closeSync() {
248
+ this.syncSync();
249
+ }
233
250
  /**
234
251
  * Asynchronous `stat`.
235
252
  */
@@ -248,7 +265,7 @@ export class PreloadFile extends File {
248
265
  */
249
266
  truncate(len) {
250
267
  this.truncateSync(len);
251
- if (isSynchronous(this.flag) && !this.fs.metadata().synchronous) {
268
+ if (isSynchronous(this.flag)) {
252
269
  return this.sync();
253
270
  }
254
271
  }
@@ -266,7 +283,7 @@ export class PreloadFile extends File {
266
283
  const buf = new Uint8Array(len - this._buffer.length);
267
284
  // Write will set stats.size for us.
268
285
  this.writeSync(buf, 0, buf.length, this._buffer.length);
269
- if (isSynchronous(this.flag) && this.fs.metadata().synchronous) {
286
+ if (isSynchronous(this.flag)) {
270
287
  this.syncSync();
271
288
  }
272
289
  return;
@@ -274,7 +291,7 @@ export class PreloadFile extends File {
274
291
  this.stats.size = len;
275
292
  // Truncate buffer to 'len'.
276
293
  this._buffer = this._buffer.subarray(0, len);
277
- if (isSynchronous(this.flag) && this.fs.metadata().synchronous) {
294
+ if (isSynchronous(this.flag)) {
278
295
  this.syncSync();
279
296
  }
280
297
  }
@@ -445,29 +462,6 @@ export class PreloadFile extends File {
445
462
  this.syncSync();
446
463
  }
447
464
  }
448
- /**
449
- * For synchronous file systems
450
- */
451
- export class SyncFile extends PreloadFile {
452
- constructor(_fs, _path, _flag, _stat, contents) {
453
- super(_fs, _path, _flag, _stat, contents);
454
- }
455
- async sync() {
456
- this.syncSync();
457
- }
458
- syncSync() {
459
- if (this.isDirty()) {
460
- this.fs.syncSync(this.path, this._buffer, this.stats);
461
- this.resetDirty();
462
- }
463
- }
464
- async close() {
465
- this.closeSync();
466
- }
467
- closeSync() {
468
- this.syncSync();
469
- }
470
- }
471
465
  /**
472
466
  * For the filesystems which do not sync to anything..
473
467
  */
@@ -1,7 +1,7 @@
1
1
  import { ApiError } from './ApiError.js';
2
2
  import type { Stats } from './stats.js';
3
- import type { File } from './file.js';
4
- import type { Cred } from './cred.js';
3
+ import { type File } from './file.js';
4
+ import { type Cred } from './cred.js';
5
5
  export type NoArgCallback = (e?: ApiError) => unknown;
6
6
  export type TwoArgCallback<T> = (e?: ApiError, rv?: T) => unknown;
7
7
  export type ThreeArgCallback<T, U> = (e?: ApiError, arg1?: T, arg2?: U) => unknown;
@@ -18,10 +18,6 @@ export interface FileSystemMetadata {
18
18
  * Wheter the FS is readonly or not
19
19
  */
20
20
  readonly: boolean;
21
- /**
22
- * Does the FS support synchronous operations
23
- */
24
- synchronous: boolean;
25
21
  /**
26
22
  * Does the FS support properties
27
23
  */
@@ -46,6 +42,9 @@ export interface FileSystemMetadata {
46
42
  * - All arguments are present. Any optional arguments at the Node API level have been passed in with their default values.
47
43
  */
48
44
  export declare abstract class FileSystem {
45
+ /**
46
+ * Get metadata about the current file syste,
47
+ */
49
48
  metadata(): FileSystemMetadata;
50
49
  constructor(options?: object);
51
50
  abstract ready(): Promise<this>;
@@ -178,8 +177,13 @@ export declare function Sync<T extends abstract new (...args: any[]) => FileSyst
178
177
  /**
179
178
  * @internal
180
179
  */
181
- declare abstract class AsyncFileSystem {
180
+ declare abstract class AsyncFileSystem extends FileSystem {
181
+ /**
182
+ * @hidden
183
+ */
184
+ abstract _sync: FileSystem;
182
185
  metadata(): FileSystemMetadata;
186
+ ready(): Promise<this>;
183
187
  renameSync(oldPath: string, newPath: string, cred: Cred): void;
184
188
  statSync(path: string, cred: Cred): Stats;
185
189
  createFileSync(path: string, flag: string, mode: number, cred: Cred): File;
@@ -191,11 +195,23 @@ declare abstract class AsyncFileSystem {
191
195
  linkSync(srcpath: string, dstpath: string, cred: Cred): void;
192
196
  syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
193
197
  }
198
+ /**
199
+ * Async() implements synchronous methods on an asynchronous file system
200
+ *
201
+ * Implementing classes must define a protected _sync property for the synchronous file system used as a cache.
202
+ * by:
203
+ *
204
+ * - Performing operations over the in-memory copy, while asynchronously pipelining them
205
+ * to the backing store.
206
+ * - During application loading, the contents of the async file system can be reloaded into
207
+ * the synchronous store, if desired.
208
+ *
209
+ */
194
210
  export declare function Async<T extends abstract new (...args: any[]) => FileSystem>(FS: T): (abstract new (...args: any[]) => AsyncFileSystem) & T;
195
211
  /**
196
212
  * @internal
197
213
  */
198
- declare abstract class ReadonlyFileSystem {
214
+ declare abstract class ReadonlyFileSystem extends FileSystem {
199
215
  metadata(): FileSystemMetadata;
200
216
  rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
201
217
  renameSync(oldPath: string, newPath: string, cred: Cred): void;