@zenfs/core 0.1.0 → 0.2.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 (44) hide show
  1. package/dist/ApiError.d.ts +51 -14
  2. package/dist/ApiError.js +60 -34
  3. package/dist/FileIndex.d.ts +32 -35
  4. package/dist/FileIndex.js +93 -109
  5. package/dist/backends/AsyncMirror.d.ts +42 -43
  6. package/dist/backends/AsyncMirror.js +146 -133
  7. package/dist/backends/AsyncStore.d.ts +29 -28
  8. package/dist/backends/AsyncStore.js +139 -189
  9. package/dist/backends/InMemory.d.ts +16 -13
  10. package/dist/backends/InMemory.js +29 -14
  11. package/dist/backends/Locked.d.ts +8 -28
  12. package/dist/backends/Locked.js +44 -148
  13. package/dist/backends/OverlayFS.d.ts +26 -34
  14. package/dist/backends/OverlayFS.js +208 -371
  15. package/dist/backends/SyncStore.d.ts +54 -72
  16. package/dist/backends/SyncStore.js +159 -161
  17. package/dist/backends/backend.d.ts +45 -29
  18. package/dist/backends/backend.js +83 -13
  19. package/dist/backends/index.d.ts +6 -7
  20. package/dist/backends/index.js +5 -6
  21. package/dist/browser.min.js +5 -7
  22. package/dist/browser.min.js.map +4 -4
  23. package/dist/emulation/callbacks.d.ts +36 -67
  24. package/dist/emulation/callbacks.js +90 -46
  25. package/dist/emulation/constants.js +1 -1
  26. package/dist/emulation/promises.d.ts +228 -129
  27. package/dist/emulation/promises.js +414 -172
  28. package/dist/emulation/shared.d.ts +10 -10
  29. package/dist/emulation/shared.js +18 -20
  30. package/dist/emulation/sync.d.ts +25 -25
  31. package/dist/emulation/sync.js +187 -73
  32. package/dist/file.d.ts +166 -170
  33. package/dist/file.js +199 -218
  34. package/dist/filesystem.d.ts +68 -241
  35. package/dist/filesystem.js +59 -383
  36. package/dist/index.d.ts +7 -44
  37. package/dist/index.js +13 -52
  38. package/dist/inode.d.ts +37 -28
  39. package/dist/inode.js +123 -65
  40. package/dist/stats.d.ts +21 -19
  41. package/dist/stats.js +35 -56
  42. package/dist/utils.d.ts +26 -9
  43. package/dist/utils.js +73 -102
  44. package/package.json +4 -3
@@ -1,10 +1,8 @@
1
- var _a;
2
- import { SynchronousFileSystem } from '../filesystem.js';
1
+ import { SyncFileSystem } from '../filesystem.js';
3
2
  import { ApiError, ErrorCode } from '../ApiError.js';
4
3
  import { FileFlag, PreloadFile } from '../file.js';
5
4
  import { join } from '../emulation/path.js';
6
5
  import { Cred } from '../cred.js';
7
- import { CreateBackend } from './backend.js';
8
6
  /**
9
7
  * We define our own file to interpose on syncSync() for mirroring purposes.
10
8
  */
@@ -12,12 +10,18 @@ class MirrorFile extends PreloadFile {
12
10
  constructor(fs, path, flag, stat, data) {
13
11
  super(fs, path, flag, stat, data);
14
12
  }
13
+ async sync() {
14
+ this.syncSync();
15
+ }
15
16
  syncSync() {
16
17
  if (this.isDirty()) {
17
- this._fs._syncSync(this);
18
+ this.fs.syncSync(this.path, this._buffer, this.stats);
18
19
  this.resetDirty();
19
20
  }
20
21
  }
22
+ async close() {
23
+ this.closeSync();
24
+ }
21
25
  closeSync() {
22
26
  this.syncSync();
23
27
  }
@@ -34,38 +38,11 @@ class MirrorFile extends PreloadFile {
34
38
  * The two stores will be kept in sync. The most common use-case is to pair a synchronous
35
39
  * in-memory filesystem with an asynchronous backing store.
36
40
  *
37
- * Example: Mirroring an IndexedDB file system to an in memory file system. Now, you can use
38
- * IndexedDB synchronously.
39
- *
40
- * ```javascript
41
- * ZenFS.configure({
42
- * fs: "AsyncMirror",
43
- * options: {
44
- * sync: { fs: "InMemory" },
45
- * async: { fs: "IndexedDB" }
46
- * }
47
- * }, function(e) {
48
- * // ZenFS is initialized and ready-to-use!
49
- * });
50
- * ```
51
- *
52
- * Or, alternatively:
53
- *
54
- * ```javascript
55
- * ZenFS.Backend.IndexedDB.Create(function(e, idbfs) {
56
- * ZenFS.Backend.InMemory.Create(function(e, inMemory) {
57
- * ZenFS.Backend.AsyncMirror({
58
- * sync: inMemory, async: idbfs
59
- * }, function(e, mirrored) {
60
- * ZenFS.initialize(mirrored);
61
- * });
62
- * });
63
- * });
64
- * ```
65
41
  */
66
- export class AsyncMirror extends SynchronousFileSystem {
67
- static isAvailable() {
68
- return true;
42
+ export class AsyncMirrorFS extends SyncFileSystem {
43
+ async ready() {
44
+ await this._ready;
45
+ return this;
69
46
  }
70
47
  /**
71
48
  *
@@ -82,7 +59,6 @@ export class AsyncMirror extends SynchronousFileSystem {
82
59
  this._queue = [];
83
60
  this._queueRunning = false;
84
61
  this._isInitialized = false;
85
- this._initializeCallbacks = [];
86
62
  this._sync = sync;
87
63
  this._async = async;
88
64
  this._ready = this._initialize();
@@ -90,22 +66,51 @@ export class AsyncMirror extends SynchronousFileSystem {
90
66
  get metadata() {
91
67
  return {
92
68
  ...super.metadata,
93
- name: AsyncMirror.Name,
69
+ name: AsyncMirrorFS.name,
94
70
  synchronous: true,
95
71
  supportsProperties: this._sync.metadata.supportsProperties && this._async.metadata.supportsProperties,
96
72
  };
97
73
  }
98
- _syncSync(fd) {
99
- const stats = fd.getStats();
100
- this._sync.writeFileSync(fd.getPath(), fd.getBuffer(), FileFlag.getFileFlag('w'), stats.mode, stats.getCred(0, 0));
101
- this.enqueueOp({
74
+ /*public _syncSync(file: PreloadFile<AsyncMirror>, cred: Cred) {
75
+ const sync = this._sync.openFileSync(file.path, FileFlag.FromString('w'), cred);
76
+ sync.writeSync(file.buffer);
77
+
78
+ this.enqueue({
102
79
  apiMethod: 'writeFile',
103
- arguments: [fd.getPath(), fd.getBuffer(), fd.getFlag(), stats.mode, stats.getCred(0, 0)],
80
+ arguments: [file.path, file.buffer, file.flag, file.stats.mode, cred],
81
+ });
82
+ }*/
83
+ syncSync(path, data, stats) {
84
+ this._sync.syncSync(path, data, stats);
85
+ this.enqueue({
86
+ apiMethod: 'sync',
87
+ arguments: [path, data, stats],
88
+ });
89
+ }
90
+ openFileSync(path, flag, cred) {
91
+ return this._sync.openFileSync(path, flag, cred);
92
+ }
93
+ createFileSync(path, flag, mode, cred) {
94
+ const file = this._sync.createFileSync(path, flag, mode, cred);
95
+ this.enqueue({
96
+ apiMethod: 'createFile',
97
+ arguments: [path, flag, mode, cred],
98
+ });
99
+ const stats = file.statSync();
100
+ const buffer = new Uint8Array(stats.size);
101
+ file.readSync(buffer);
102
+ return new MirrorFile(this, path, flag, stats, buffer);
103
+ }
104
+ linkSync(srcpath, dstpath, cred) {
105
+ this._sync.linkSync(srcpath, dstpath, cred);
106
+ this.enqueue({
107
+ apiMethod: 'link',
108
+ arguments: [srcpath, dstpath, cred],
104
109
  });
105
110
  }
106
111
  renameSync(oldPath, newPath, cred) {
107
112
  this._sync.renameSync(oldPath, newPath, cred);
108
- this.enqueueOp({
113
+ this.enqueue({
109
114
  apiMethod: 'rename',
110
115
  arguments: [oldPath, newPath, cred],
111
116
  });
@@ -113,29 +118,23 @@ export class AsyncMirror extends SynchronousFileSystem {
113
118
  statSync(p, cred) {
114
119
  return this._sync.statSync(p, cred);
115
120
  }
116
- openSync(p, flag, mode, cred) {
117
- // Sanity check: Is this open/close permitted?
118
- const fd = this._sync.openSync(p, flag, mode, cred);
119
- fd.closeSync();
120
- return new MirrorFile(this, p, flag, this._sync.statSync(p, cred), this._sync.readFileSync(p, FileFlag.getFileFlag('r'), cred));
121
- }
122
121
  unlinkSync(p, cred) {
123
122
  this._sync.unlinkSync(p, cred);
124
- this.enqueueOp({
123
+ this.enqueue({
125
124
  apiMethod: 'unlink',
126
125
  arguments: [p, cred],
127
126
  });
128
127
  }
129
128
  rmdirSync(p, cred) {
130
129
  this._sync.rmdirSync(p, cred);
131
- this.enqueueOp({
130
+ this.enqueue({
132
131
  apiMethod: 'rmdir',
133
132
  arguments: [p, cred],
134
133
  });
135
134
  }
136
135
  mkdirSync(p, mode, cred) {
137
136
  this._sync.mkdirSync(p, mode, cred);
138
- this.enqueueOp({
137
+ this.enqueue({
139
138
  apiMethod: 'mkdir',
140
139
  arguments: [p, mode, cred],
141
140
  });
@@ -146,101 +145,115 @@ export class AsyncMirror extends SynchronousFileSystem {
146
145
  existsSync(p, cred) {
147
146
  return this._sync.existsSync(p, cred);
148
147
  }
149
- chmodSync(p, mode, cred) {
150
- this._sync.chmodSync(p, mode, cred);
151
- this.enqueueOp({
152
- apiMethod: 'chmod',
153
- arguments: [p, mode, cred],
154
- });
148
+ /**
149
+ * @internal
150
+ */
151
+ async crossCopyDirectory(p, mode) {
152
+ if (p !== '/') {
153
+ const stats = await this._async.stat(p, Cred.Root);
154
+ this._sync.mkdirSync(p, mode, stats.getCred());
155
+ }
156
+ const files = await this._async.readdir(p, Cred.Root);
157
+ for (const file of files) {
158
+ await this.crossCopy(join(p, file));
159
+ }
155
160
  }
156
- chownSync(p, new_uid, new_gid, cred) {
157
- this._sync.chownSync(p, new_uid, new_gid, cred);
158
- this.enqueueOp({
159
- apiMethod: 'chown',
160
- arguments: [p, new_uid, new_gid, cred],
161
- });
161
+ /**
162
+ * @internal
163
+ */
164
+ async crossCopyFile(p, mode) {
165
+ const asyncFile = await this._async.openFile(p, FileFlag.FromString('r'), Cred.Root);
166
+ const syncFile = this._sync.createFileSync(p, FileFlag.FromString('w'), mode, Cred.Root);
167
+ try {
168
+ const { size } = await asyncFile.stat();
169
+ const buffer = new Uint8Array(size);
170
+ await asyncFile.read(buffer);
171
+ syncFile.writeSync(buffer);
172
+ }
173
+ finally {
174
+ await asyncFile.close();
175
+ syncFile.closeSync();
176
+ }
162
177
  }
163
- utimesSync(p, atime, mtime, cred) {
164
- this._sync.utimesSync(p, atime, mtime, cred);
165
- this.enqueueOp({
166
- apiMethod: 'utimes',
167
- arguments: [p, atime, mtime, cred],
168
- });
178
+ /**
179
+ * @internal
180
+ */
181
+ async crossCopy(p) {
182
+ const stats = await this._async.stat(p, Cred.Root);
183
+ if (stats.isDirectory()) {
184
+ await this.crossCopyDirectory(p, stats.mode);
185
+ }
186
+ else {
187
+ await this.crossCopyFile(p, stats.mode);
188
+ }
169
189
  }
170
190
  /**
171
191
  * Called once to load up files from async storage into sync storage.
172
192
  */
173
193
  async _initialize() {
174
- if (!this._isInitialized) {
175
- // First call triggers initialization, the rest wait.
176
- const copyDirectory = async (p, mode) => {
177
- if (p !== '/') {
178
- const stats = await this._async.stat(p, Cred.Root);
179
- this._sync.mkdirSync(p, mode, stats.getCred());
180
- }
181
- const files = await this._async.readdir(p, Cred.Root);
182
- for (const file of files) {
183
- await copyItem(join(p, file));
184
- }
185
- }, copyFile = async (p, mode) => {
186
- const data = await this._async.readFile(p, FileFlag.getFileFlag('r'), Cred.Root);
187
- this._sync.writeFileSync(p, data, FileFlag.getFileFlag('w'), mode, Cred.Root);
188
- }, copyItem = async (p) => {
189
- const stats = await this._async.stat(p, Cred.Root);
190
- if (stats.isDirectory()) {
191
- await copyDirectory(p, stats.mode);
192
- }
193
- else {
194
- await copyFile(p, stats.mode);
195
- }
196
- };
197
- try {
198
- await copyDirectory('/', 0);
199
- this._isInitialized = true;
200
- }
201
- catch (e) {
202
- this._isInitialized = false;
203
- throw e;
204
- }
194
+ if (this._isInitialized) {
195
+ return;
205
196
  }
206
- return this;
197
+ try {
198
+ await this.crossCopy('/');
199
+ this._isInitialized = true;
200
+ }
201
+ catch (e) {
202
+ this._isInitialized = false;
203
+ throw e;
204
+ }
205
+ }
206
+ /**
207
+ * @internal
208
+ */
209
+ async _next() {
210
+ if (this._queue.length == 0) {
211
+ this._queueRunning = false;
212
+ return;
213
+ }
214
+ const op = this._queue.shift();
215
+ try {
216
+ // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
217
+ await this._async[op.apiMethod](...op.arguments);
218
+ }
219
+ catch (e) {
220
+ throw new ApiError(ErrorCode.EIO, 'AsyncMirror desync: ' + e);
221
+ }
222
+ await this._next();
207
223
  }
208
- enqueueOp(op) {
224
+ /**
225
+ * @internal
226
+ */
227
+ enqueue(op) {
209
228
  this._queue.push(op);
210
- if (!this._queueRunning) {
211
- this._queueRunning = true;
212
- const doNextOp = (err) => {
213
- if (err) {
214
- throw new Error(`WARNING: File system has desynchronized. Received following error: ${err}\n$`);
215
- }
216
- if (this._queue.length > 0) {
217
- const op = this._queue.shift();
218
- op.arguments.push(doNextOp);
219
- this._async[op.apiMethod].apply(this._async, op.arguments);
220
- }
221
- else {
222
- this._queueRunning = false;
223
- }
224
- };
225
- doNextOp();
229
+ if (this._queueRunning) {
230
+ return;
226
231
  }
232
+ this._queueRunning = true;
233
+ this._next();
227
234
  }
228
235
  }
229
- _a = AsyncMirror;
230
- AsyncMirror.Name = 'AsyncMirror';
231
- AsyncMirror.Create = CreateBackend.bind(_a);
232
- AsyncMirror.Options = {
233
- sync: {
234
- type: 'object',
235
- description: 'The synchronous file system to mirror the asynchronous file system to.',
236
- validator: async (v) => {
237
- if (!v?.metadata.synchronous) {
238
- throw new ApiError(ErrorCode.EINVAL, `'sync' option must be a file system that supports synchronous operations`);
239
- }
236
+ export const AsyncMirror = {
237
+ name: 'AsyncMirror',
238
+ options: {
239
+ sync: {
240
+ type: 'object',
241
+ description: 'The synchronous file system to mirror the asynchronous file system to.',
242
+ validator: async (v) => {
243
+ if (!v?.metadata.synchronous) {
244
+ throw new ApiError(ErrorCode.EINVAL, `'sync' option must be a file system that supports synchronous operations`);
245
+ }
246
+ },
240
247
  },
248
+ async: {
249
+ type: 'object',
250
+ description: 'The asynchronous file system to mirror.',
251
+ },
252
+ },
253
+ isAvailable() {
254
+ return true;
241
255
  },
242
- async: {
243
- type: 'object',
244
- description: 'The asynchronous file system to mirror.',
256
+ create(options) {
257
+ return new AsyncMirrorFS(options);
245
258
  },
246
259
  };
@@ -1,15 +1,16 @@
1
1
  import { Cred } from '../cred.js';
2
2
  import { PreloadFile, File, FileFlag } from '../file.js';
3
- import { BaseFileSystem } from '../filesystem.js';
3
+ import { AsyncFileSystem } from '../filesystem.js';
4
+ import { type Ino } from '../inode.js';
4
5
  import { Stats } from '../stats.js';
5
6
  /**
6
7
  * Represents an *asynchronous* key-value store.
7
8
  */
8
- export interface AsyncKeyValueStore {
9
+ export interface AsyncStore {
9
10
  /**
10
11
  * The name of the key-value store.
11
12
  */
12
- name(): string;
13
+ name: string;
13
14
  /**
14
15
  * Empties the key-value store completely.
15
16
  */
@@ -17,27 +18,27 @@ export interface AsyncKeyValueStore {
17
18
  /**
18
19
  * Begins a read-write transaction.
19
20
  */
20
- beginTransaction(type: 'readwrite'): AsyncKeyValueRWTransaction;
21
+ beginTransaction(type: 'readwrite'): AsyncRWTransaction;
21
22
  /**
22
23
  * Begins a read-only transaction.
23
24
  */
24
- beginTransaction(type: 'readonly'): AsyncKeyValueROTransaction;
25
- beginTransaction(type: string): AsyncKeyValueROTransaction;
25
+ beginTransaction(type: 'readonly'): AsyncROTransaction;
26
+ beginTransaction(type: string): AsyncROTransaction;
26
27
  }
27
28
  /**
28
29
  * Represents an asynchronous read-only transaction.
29
30
  */
30
- export interface AsyncKeyValueROTransaction {
31
+ export interface AsyncROTransaction {
31
32
  /**
32
33
  * Retrieves the data at the given key.
33
34
  * @param key The key to look under for data.
34
35
  */
35
- get(key: string): Promise<Uint8Array>;
36
+ get(key: Ino): Promise<Uint8Array>;
36
37
  }
37
38
  /**
38
39
  * Represents an asynchronous read-write transaction.
39
40
  */
40
- export interface AsyncKeyValueRWTransaction extends AsyncKeyValueROTransaction {
41
+ export interface AsyncRWTransaction extends AsyncROTransaction {
41
42
  /**
42
43
  * Adds the data to the store under the given key. Overwrites any existing
43
44
  * data.
@@ -46,12 +47,12 @@ export interface AsyncKeyValueRWTransaction extends AsyncKeyValueROTransaction {
46
47
  * @param overwrite If 'true', overwrite any existing data. If 'false',
47
48
  * avoids writing the data if the key exists.
48
49
  */
49
- put(key: string, data: Uint8Array, overwrite: boolean): Promise<boolean>;
50
+ put(key: Ino, data: Uint8Array, overwrite: boolean): Promise<boolean>;
50
51
  /**
51
52
  * Deletes the data at the given key.
52
53
  * @param key The key to delete from the store.
53
54
  */
54
- del(key: string): Promise<void>;
55
+ remove(key: Ino): Promise<void>;
55
56
  /**
56
57
  * Commits the transaction.
57
58
  */
@@ -61,35 +62,32 @@ export interface AsyncKeyValueRWTransaction extends AsyncKeyValueROTransaction {
61
62
  */
62
63
  abort(): Promise<void>;
63
64
  }
64
- export declare class AsyncKeyValueFile extends PreloadFile<AsyncKeyValueFileSystem> implements File {
65
- constructor(_fs: AsyncKeyValueFileSystem, _path: string, _flag: FileFlag, _stat: Stats, contents?: Uint8Array);
65
+ export declare class AsyncFile extends PreloadFile<AsyncStoreFileSystem> {
66
+ constructor(_fs: AsyncStoreFileSystem, _path: string, _flag: FileFlag, _stat: Stats, contents?: Uint8Array);
66
67
  sync(): Promise<void>;
68
+ syncSync(): void;
67
69
  close(): Promise<void>;
70
+ closeSync(): void;
68
71
  }
69
72
  /**
70
73
  * An "Asynchronous key-value file system". Stores data to/retrieves data from
71
74
  * an underlying asynchronous key-value store.
72
75
  */
73
- export declare class AsyncKeyValueFileSystem extends BaseFileSystem {
74
- static isAvailable(): boolean;
75
- protected store: AsyncKeyValueStore;
76
- private _cache;
76
+ export declare class AsyncStoreFileSystem extends AsyncFileSystem {
77
+ protected store: AsyncStore;
78
+ private _cache?;
79
+ protected _ready: Promise<this>;
80
+ ready(): Promise<this>;
77
81
  constructor(cacheSize: number);
78
82
  /**
79
83
  * Initializes the file system. Typically called by subclasses' async
80
84
  * constructors.
81
85
  */
82
- init(store: AsyncKeyValueStore): Promise<void>;
83
- getName(): string;
84
- isReadOnly(): boolean;
85
- supportsSymlinks(): boolean;
86
- supportsProps(): boolean;
87
- supportsSynch(): boolean;
86
+ init(store: AsyncStore): Promise<void>;
88
87
  /**
89
88
  * Delete all contents stored in the file system.
90
89
  */
91
90
  empty(): Promise<void>;
92
- access(p: string, mode: number, cred: Cred): Promise<void>;
93
91
  /**
94
92
  * @todo Make rename compatible with the cache.
95
93
  */
@@ -101,9 +99,12 @@ export declare class AsyncKeyValueFileSystem extends BaseFileSystem {
101
99
  rmdir(p: string, cred: Cred): Promise<void>;
102
100
  mkdir(p: string, mode: number, cred: Cred): Promise<void>;
103
101
  readdir(p: string, cred: Cred): Promise<string[]>;
104
- chmod(p: string, mode: number, cred: Cred): Promise<void>;
105
- chown(p: string, new_uid: number, new_gid: number, cred: Cred): Promise<void>;
106
- _sync(p: string, data: Uint8Array, stats: Stats): Promise<void>;
102
+ /**
103
+ * Updated the inode and data node at the given path
104
+ * @todo Ensure mtime updates properly, and use that to determine if a data update is required.
105
+ */
106
+ sync(p: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
107
+ link(existing: string, newpath: string, cred: Cred): Promise<void>;
107
108
  /**
108
109
  * Checks if the root directory exists. Creates it if it doesn't.
109
110
  */
@@ -135,7 +136,7 @@ export declare class AsyncKeyValueFileSystem extends BaseFileSystem {
135
136
  private getDirListing;
136
137
  /**
137
138
  * Adds a new node under a random ID. Retries 5 times before giving up in
138
- * the exceedingly unlikely chance that we try to reuse a random GUID.
139
+ * the exceedingly unlikely chance that we try to reuse a random ino.
139
140
  */
140
141
  private addNewNode;
141
142
  /**