@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
package/dist/FileIndex.js CHANGED
@@ -10,65 +10,63 @@ import * as path from './emulation/path.js';
10
10
  export class FileIndex {
11
11
  /**
12
12
  * Static method for constructing indices from a JSON listing.
13
- * @param listing Directory listing generated by tools/XHRIndexer.coffee
13
+ * @param listing Directory listing generated by tools
14
14
  * @return A new FileIndex object.
15
15
  */
16
16
  static fromListing(listing) {
17
- const idx = new FileIndex();
17
+ const index = new FileIndex();
18
18
  // Add a root DirNode.
19
19
  const rootInode = new IndexDirInode();
20
- idx._index['/'] = rootInode;
21
- const queue = [['', listing, rootInode]];
20
+ index._index.set('/', rootInode);
21
+ const queue = [{ pwd: '', tree: listing, parent: rootInode }];
22
22
  while (queue.length > 0) {
23
23
  let inode;
24
- const next = queue.pop();
25
- const pwd = next[0];
26
- const tree = next[1];
27
- const parent = next[2];
24
+ const { tree, pwd, parent } = queue.pop();
28
25
  for (const node in tree) {
29
- if (Object.prototype.hasOwnProperty.call(tree, node)) {
30
- const children = tree[node];
31
- const name = `${pwd}/${node}`;
32
- if (children) {
33
- idx._index[name] = inode = new IndexDirInode();
34
- queue.push([name, children, inode]);
35
- }
36
- else {
37
- // This inode doesn't have correct size information, noted with -1.
38
- inode = new IndexFileInode(new Stats(FileType.FILE, -1, 0x16d));
39
- }
40
- if (parent) {
41
- parent._ls[node] = inode;
42
- }
26
+ if (!Object.hasOwn(tree, node)) {
27
+ continue;
43
28
  }
29
+ const children = tree[node];
30
+ if (children) {
31
+ const path = `${pwd}/${node}`;
32
+ inode = new IndexDirInode();
33
+ index._index.set(path, inode);
34
+ queue.push({ pwd: path, tree: children, parent: inode });
35
+ }
36
+ else {
37
+ // This inode doesn't have correct size information, noted with -1.
38
+ inode = new IndexFileInode(new Stats(FileType.FILE, -1, 0o555));
39
+ }
40
+ if (!parent) {
41
+ continue;
42
+ }
43
+ parent._listing.set(node, inode);
44
44
  }
45
45
  }
46
- return idx;
46
+ return index;
47
47
  }
48
48
  /**
49
49
  * Constructs a new FileIndex.
50
50
  */
51
51
  constructor() {
52
+ // Maps directory paths to directory inodes, which contain files.
53
+ this._index = new Map();
52
54
  // _index is a single-level key,value store that maps *directory* paths to
53
55
  // DirInodes. File information is only contained in DirInodes themselves.
54
- this._index = {};
55
56
  // Create the root directory.
56
57
  this.addPath('/', new IndexDirInode());
57
58
  }
58
59
  /**
59
60
  * Runs the given function over all files in the index.
60
61
  */
61
- fileIterator(cb) {
62
- for (const path in this._index) {
63
- if (Object.prototype.hasOwnProperty.call(this._index, path)) {
64
- const dir = this._index[path];
65
- const files = dir.getListing();
66
- for (const file of files) {
67
- const item = dir.getItem(file);
68
- if (isIndexFileInode(item)) {
69
- cb(item.getData());
70
- }
62
+ forEachFile(cb) {
63
+ for (const dir of this._index.values()) {
64
+ for (const file of dir.listing) {
65
+ const item = dir.get(file);
66
+ if (!item?.isFile()) {
67
+ continue;
71
68
  }
69
+ cb(item.data);
72
70
  }
73
71
  }
74
72
  }
@@ -92,15 +90,15 @@ export class FileIndex {
92
90
  throw new Error('Path must be absolute, got: ' + path);
93
91
  }
94
92
  // Check if it already exists.
95
- if (Object.prototype.hasOwnProperty.call(this._index, path)) {
96
- return this._index[path] === inode;
93
+ if (this._index.has(path)) {
94
+ return this._index.get(path) === inode;
97
95
  }
98
- const splitPath = this._split_path(path);
96
+ const splitPath = this.splitPath(path);
99
97
  const dirpath = splitPath[0];
100
98
  const itemname = splitPath[1];
101
99
  // Try to add to its parent directory first.
102
- let parent = this._index[dirpath];
103
- if (parent === undefined && path !== '/') {
100
+ let parent = this._index.get(dirpath);
101
+ if (!parent && path !== '/') {
104
102
  // Create parent.
105
103
  parent = new IndexDirInode();
106
104
  if (!this.addPath(dirpath, parent)) {
@@ -109,7 +107,7 @@ export class FileIndex {
109
107
  }
110
108
  // Add myself to my parent.
111
109
  if (path !== '/') {
112
- if (!parent.addItem(itemname, inode)) {
110
+ if (!parent.add(itemname, inode)) {
113
111
  return false;
114
112
  }
115
113
  }
@@ -137,13 +135,13 @@ export class FileIndex {
137
135
  const parentPath = itemNameMark === 0 ? '/' : path.substring(0, itemNameMark);
138
136
  const itemName = path.substring(itemNameMark + 1);
139
137
  // Try to add to its parent directory first.
140
- let parent = this._index[parentPath];
141
- if (parent === undefined) {
138
+ let parent = this._index.get(parentPath);
139
+ if (!parent) {
142
140
  // Create parent.
143
141
  parent = new IndexDirInode();
144
142
  this.addPathFast(parentPath, parent);
145
143
  }
146
- if (!parent.addItem(itemName, inode)) {
144
+ if (!parent.add(itemName, inode)) {
147
145
  return false;
148
146
  }
149
147
  // If adding a directory, add to the index as well.
@@ -158,90 +156,81 @@ export class FileIndex {
158
156
  * or null if it did not exist.
159
157
  */
160
158
  removePath(path) {
161
- const splitPath = this._split_path(path);
159
+ const splitPath = this.splitPath(path);
162
160
  const dirpath = splitPath[0];
163
161
  const itemname = splitPath[1];
164
162
  // Try to remove it from its parent directory first.
165
163
  const parent = this._index[dirpath];
166
- if (parent === undefined) {
167
- return null;
164
+ if (!parent) {
165
+ return;
168
166
  }
169
167
  // Remove myself from my parent.
170
- const inode = parent.remItem(itemname);
171
- if (inode === null) {
172
- return null;
168
+ const inode = parent.remove(itemname);
169
+ if (!inode) {
170
+ return;
173
171
  }
174
172
  // If I'm a directory, remove myself from the index, and remove my children.
175
- if (isIndexDirInode(inode)) {
176
- const children = inode.getListing();
177
- for (const child of children) {
178
- this.removePath(path + '/' + child);
179
- }
180
- // Remove the directory from the index, unless it's the root.
181
- if (path !== '/') {
182
- delete this._index[path];
183
- }
173
+ if (!isIndexDirInode(inode)) {
174
+ return inode;
175
+ }
176
+ const children = inode.listing;
177
+ for (const child of children) {
178
+ this.removePath(path + '/' + child);
179
+ }
180
+ // Remove the directory from the index, unless it's the root.
181
+ if (path !== '/') {
182
+ this._index.delete(path);
184
183
  }
185
- return inode;
186
184
  }
187
185
  /**
188
186
  * Retrieves the directory listing of the given path.
189
187
  * @return An array of files in the given path, or 'null' if it does not exist.
190
188
  */
191
189
  ls(path) {
192
- const item = this._index[path];
193
- if (item === undefined) {
194
- return null;
195
- }
196
- return item.getListing();
190
+ return this._index.get(path)?.listing;
197
191
  }
198
192
  /**
199
193
  * Returns the inode of the given item.
200
194
  * @return Returns null if the item does not exist.
201
195
  */
202
196
  getInode(path) {
203
- const splitPath = this._split_path(path);
204
- const dirpath = splitPath[0];
205
- const itemname = splitPath[1];
197
+ const [dirpath, itemname] = this.splitPath(path);
206
198
  // Retrieve from its parent directory.
207
- const parent = this._index[dirpath];
208
- if (parent === undefined) {
209
- return null;
210
- }
199
+ const parent = this._index.get(dirpath);
211
200
  // Root case
212
201
  if (dirpath === path) {
213
202
  return parent;
214
203
  }
215
- return parent.getItem(itemname);
204
+ return parent?.get(itemname);
216
205
  }
217
206
  /**
218
207
  * Split into a (directory path, item name) pair
219
208
  */
220
- _split_path(p) {
209
+ splitPath(p) {
221
210
  const dirpath = path.dirname(p);
222
211
  const itemname = p.slice(dirpath.length + (dirpath === '/' ? 0 : 1));
223
212
  return [dirpath, itemname];
224
213
  }
225
214
  }
226
215
  /**
227
- * Inode for a file. Stores an arbitrary (filesystem-specific) data payload.
216
+ * Generic interface for file/directory inodes.
217
+ * Note that Stats objects are what we use for file inodes.
228
218
  */
229
- export class IndexFileInode {
219
+ export class IndexInode {
230
220
  constructor(data) {
231
221
  this.data = data;
232
222
  }
223
+ }
224
+ /**
225
+ * Inode for a file. Stores an arbitrary (filesystem-specific) data payload.
226
+ */
227
+ export class IndexFileInode extends IndexInode {
233
228
  isFile() {
234
229
  return true;
235
230
  }
236
231
  isDir() {
237
232
  return false;
238
233
  }
239
- getData() {
240
- return this.data;
241
- }
242
- setData(data) {
243
- this.data = data;
244
- }
245
234
  toStats() {
246
235
  return new Stats(FileType.FILE, 4096, 0o666);
247
236
  }
@@ -249,13 +238,13 @@ export class IndexFileInode {
249
238
  /**
250
239
  * Inode for a directory. Currently only contains the directory listing.
251
240
  */
252
- export class IndexDirInode {
253
- /**
254
- * Constructs an inode for a directory.
255
- */
256
- constructor(data = null) {
257
- this.data = data;
258
- this._ls = {};
241
+ export class IndexDirInode extends IndexInode {
242
+ constructor() {
243
+ super(...arguments);
244
+ /**
245
+ * @internal
246
+ */
247
+ this._listing = new Map();
259
248
  }
260
249
  isFile() {
261
250
  return false;
@@ -263,39 +252,34 @@ export class IndexDirInode {
263
252
  isDir() {
264
253
  return true;
265
254
  }
266
- getData() {
267
- return this.data;
268
- }
269
255
  /**
270
256
  * Return a Stats object for this inode.
271
- * @todo Should probably remove this at some point. This isn't the
272
- * responsibility of the FileIndex.
257
+ * @todo Should probably remove this at some point. This isn't the responsibility of the FileIndex.
273
258
  */
274
- getStats() {
259
+ get stats() {
275
260
  return new Stats(FileType.DIRECTORY, 4096, 0o555);
276
261
  }
277
262
  /**
278
263
  * Alias of getStats()
279
- * @todo Remove this at some point. This isn't the
280
- * responsibility of the FileIndex.
264
+ * @todo Remove this at some point. This isn't the responsibility of the FileIndex.
281
265
  */
282
266
  toStats() {
283
- return this.getStats();
267
+ return this.stats;
284
268
  }
285
269
  /**
286
270
  * Returns the directory listing for this directory. Paths in the directory are
287
271
  * relative to the directory's path.
288
272
  * @return The directory listing for this directory.
289
273
  */
290
- getListing() {
291
- return Object.keys(this._ls);
274
+ get listing() {
275
+ return Object.keys(this._listing);
292
276
  }
293
277
  /**
294
278
  * Returns the inode for the indicated item, or null if it does not exist.
295
279
  * @param p Name of item in this directory.
296
280
  */
297
- getItem(p) {
298
- const item = this._ls[p];
281
+ get(p) {
282
+ const item = this._listing[p];
299
283
  return item ? item : null;
300
284
  }
301
285
  /**
@@ -306,11 +290,11 @@ export class IndexDirInode {
306
290
  * item to add to the directory inode.
307
291
  * @return True if it was added, false if it already existed.
308
292
  */
309
- addItem(p, inode) {
310
- if (p in this._ls) {
293
+ add(p, inode) {
294
+ if (p in this._listing) {
311
295
  return false;
312
296
  }
313
- this._ls[p] = inode;
297
+ this._listing[p] = inode;
314
298
  return true;
315
299
  }
316
300
  /**
@@ -319,12 +303,12 @@ export class IndexDirInode {
319
303
  * @return Returns the item
320
304
  * removed, or null if the item did not exist.
321
305
  */
322
- remItem(p) {
323
- const item = this._ls[p];
306
+ remove(p) {
307
+ const item = this._listing[p];
324
308
  if (item === undefined) {
325
309
  return null;
326
310
  }
327
- delete this._ls[p];
311
+ delete this._listing[p];
328
312
  return item;
329
313
  }
330
314
  }
@@ -332,11 +316,11 @@ export class IndexDirInode {
332
316
  * @hidden
333
317
  */
334
318
  export function isIndexFileInode(inode) {
335
- return !!inode && inode.isFile();
319
+ return inode?.isFile();
336
320
  }
337
321
  /**
338
322
  * @hidden
339
323
  */
340
324
  export function isIndexDirInode(inode) {
341
- return !!inode && inode.isDir();
325
+ return inode?.isDir();
342
326
  }
@@ -1,8 +1,18 @@
1
- import { type FileSystem, SynchronousFileSystem, FileSystemMetadata } from '../filesystem.js';
1
+ import { type FileSystem, SyncFileSystem, FileSystemMetadata } from '../filesystem.js';
2
2
  import { File, FileFlag, PreloadFile } from '../file.js';
3
3
  import { Stats } from '../stats.js';
4
4
  import { Cred } from '../cred.js';
5
- import { type BackendOptions } from './backend.js';
5
+ import type { Backend } from './backend.js';
6
+ /**
7
+ * We define our own file to interpose on syncSync() for mirroring purposes.
8
+ */
9
+ declare class MirrorFile extends PreloadFile<AsyncMirrorFS> {
10
+ constructor(fs: AsyncMirrorFS, path: string, flag: FileFlag, stat: Stats, data: Uint8Array);
11
+ sync(): Promise<void>;
12
+ syncSync(): void;
13
+ close(): Promise<void>;
14
+ closeSync(): void;
15
+ }
6
16
  export declare namespace AsyncMirror {
7
17
  /**
8
18
  * Configuration options for the AsyncMirror file system.
@@ -30,40 +40,8 @@ export declare namespace AsyncMirror {
30
40
  * The two stores will be kept in sync. The most common use-case is to pair a synchronous
31
41
  * in-memory filesystem with an asynchronous backing store.
32
42
  *
33
- * Example: Mirroring an IndexedDB file system to an in memory file system. Now, you can use
34
- * IndexedDB synchronously.
35
- *
36
- * ```javascript
37
- * ZenFS.configure({
38
- * fs: "AsyncMirror",
39
- * options: {
40
- * sync: { fs: "InMemory" },
41
- * async: { fs: "IndexedDB" }
42
- * }
43
- * }, function(e) {
44
- * // ZenFS is initialized and ready-to-use!
45
- * });
46
- * ```
47
- *
48
- * Or, alternatively:
49
- *
50
- * ```javascript
51
- * ZenFS.Backend.IndexedDB.Create(function(e, idbfs) {
52
- * ZenFS.Backend.InMemory.Create(function(e, inMemory) {
53
- * ZenFS.Backend.AsyncMirror({
54
- * sync: inMemory, async: idbfs
55
- * }, function(e, mirrored) {
56
- * ZenFS.initialize(mirrored);
57
- * });
58
- * });
59
- * });
60
- * ```
61
43
  */
62
- export declare class AsyncMirror extends SynchronousFileSystem {
63
- static readonly Name = "AsyncMirror";
64
- static Create: any;
65
- static readonly Options: BackendOptions;
66
- static isAvailable(): boolean;
44
+ export declare class AsyncMirrorFS extends SyncFileSystem {
67
45
  /**
68
46
  * Queue of pending asynchronous operations.
69
47
  */
@@ -72,7 +50,8 @@ export declare class AsyncMirror extends SynchronousFileSystem {
72
50
  private _sync;
73
51
  private _async;
74
52
  private _isInitialized;
75
- private _initializeCallbacks;
53
+ private _ready;
54
+ ready(): Promise<this>;
76
55
  /**
77
56
  *
78
57
  * Mirrors the synchronous file system into the asynchronous file system.
@@ -82,21 +61,41 @@ export declare class AsyncMirror extends SynchronousFileSystem {
82
61
  */
83
62
  constructor({ sync, async }: AsyncMirror.Options);
84
63
  get metadata(): FileSystemMetadata;
85
- _syncSync(fd: PreloadFile<AsyncMirror>): void;
64
+ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
65
+ openFileSync(path: string, flag: FileFlag, cred: Cred): File;
66
+ createFileSync(path: string, flag: FileFlag, mode: number, cred: Cred): MirrorFile;
67
+ linkSync(srcpath: string, dstpath: string, cred: Cred): void;
86
68
  renameSync(oldPath: string, newPath: string, cred: Cred): void;
87
69
  statSync(p: string, cred: Cred): Stats;
88
- openSync(p: string, flag: FileFlag, mode: number, cred: Cred): File;
89
70
  unlinkSync(p: string, cred: Cred): void;
90
71
  rmdirSync(p: string, cred: Cred): void;
91
72
  mkdirSync(p: string, mode: number, cred: Cred): void;
92
73
  readdirSync(p: string, cred: Cred): string[];
93
74
  existsSync(p: string, cred: Cred): boolean;
94
- chmodSync(p: string, mode: number, cred: Cred): void;
95
- chownSync(p: string, new_uid: number, new_gid: number, cred: Cred): void;
96
- utimesSync(p: string, atime: Date, mtime: Date, cred: Cred): void;
75
+ /**
76
+ * @internal
77
+ */
78
+ protected crossCopyDirectory(p: string, mode: number): Promise<void>;
79
+ /**
80
+ * @internal
81
+ */
82
+ protected crossCopyFile(p: string, mode: number): Promise<void>;
83
+ /**
84
+ * @internal
85
+ */
86
+ protected crossCopy(p: string): Promise<void>;
97
87
  /**
98
88
  * Called once to load up files from async storage into sync storage.
99
89
  */
100
- private _initialize;
101
- private enqueueOp;
90
+ protected _initialize(): Promise<void>;
91
+ /**
92
+ * @internal
93
+ */
94
+ private _next;
95
+ /**
96
+ * @internal
97
+ */
98
+ private enqueue;
102
99
  }
100
+ export declare const AsyncMirror: Backend;
101
+ export {};