@zenfs/core 0.9.3 → 0.9.5

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.
@@ -39,7 +39,7 @@ export declare abstract class FileSystem {
39
39
  */
40
40
  metadata(): FileSystemMetadata;
41
41
  constructor(options?: object);
42
- abstract ready(): Promise<this>;
42
+ ready(): Promise<this>;
43
43
  /**
44
44
  * Asynchronous rename. No arguments other than a possible exception
45
45
  * are given to the completion callback.
@@ -174,6 +174,7 @@ declare abstract class AsyncFileSystem extends FileSystem {
174
174
  * @hidden
175
175
  */
176
176
  abstract _sync: FileSystem;
177
+ queueDone(): Promise<void>;
177
178
  metadata(): FileSystemMetadata;
178
179
  ready(): Promise<this>;
179
180
  renameSync(oldPath: string, newPath: string, cred: Cred): void;
@@ -27,6 +27,9 @@ export class FileSystem {
27
27
  constructor(options) {
28
28
  // unused
29
29
  }
30
+ async ready() {
31
+ return this;
32
+ }
30
33
  /**
31
34
  * Test whether or not the given path exists by checking with the file system.
32
35
  */
@@ -57,9 +60,6 @@ export class FileSystem {
57
60
  */
58
61
  export function Sync(FS) {
59
62
  class _SyncFileSystem extends FS {
60
- async ready() {
61
- return this;
62
- }
63
63
  async exists(path, cred) {
64
64
  return this.existsSync(path, cred);
65
65
  }
@@ -116,11 +116,20 @@ export function Async(FS) {
116
116
  * Queue of pending asynchronous operations.
117
117
  */
118
118
  this._queue = [];
119
- this._queueRunning = false;
120
119
  this._isInitialized = false;
121
120
  }
121
+ get _queueRunning() {
122
+ return !!this._queue.length;
123
+ }
124
+ queueDone() {
125
+ return new Promise(resolve => {
126
+ const check = () => (this._queueRunning ? setTimeout(check) : resolve());
127
+ check();
128
+ });
129
+ }
122
130
  async ready() {
123
131
  await this._sync.ready();
132
+ await super.ready();
124
133
  if (this._isInitialized) {
125
134
  return this;
126
135
  }
@@ -195,12 +204,11 @@ export function Async(FS) {
195
204
  }
196
205
  else {
197
206
  const asyncFile = await this.openFile(p, parseFlag('r'), rootCred);
198
- const syncFile = this._sync.createFileSync(p, parseFlag('w'), stats.mode, rootCred);
207
+ const syncFile = this._sync.createFileSync(p, parseFlag('w'), stats.mode, stats.cred());
199
208
  try {
200
- const { size } = await asyncFile.stat();
201
- const buffer = new Uint8Array(size);
209
+ const buffer = new Uint8Array(stats.size);
202
210
  await asyncFile.read(buffer);
203
- syncFile.writeSync(buffer);
211
+ syncFile.writeSync(buffer, 0, stats.size);
204
212
  }
205
213
  finally {
206
214
  await asyncFile.close();
@@ -212,8 +220,7 @@ export function Async(FS) {
212
220
  * @internal
213
221
  */
214
222
  async _next() {
215
- if (this._queue.length == 0) {
216
- this._queueRunning = false;
223
+ if (!this._queueRunning) {
217
224
  return;
218
225
  }
219
226
  const [method, ...args] = this._queue.shift();
@@ -226,10 +233,6 @@ export function Async(FS) {
226
233
  */
227
234
  queue(...op) {
228
235
  this._queue.push(op);
229
- if (this._queueRunning) {
230
- return;
231
- }
232
- this._queueRunning = true;
233
236
  this._next();
234
237
  }
235
238
  }
package/dist/stats.d.ts CHANGED
@@ -47,6 +47,10 @@ export interface StatsLike {
47
47
  * the id of the group that owns the file
48
48
  */
49
49
  gid: number | bigint;
50
+ /**
51
+ * the ino
52
+ */
53
+ ino: number | bigint;
50
54
  }
51
55
  /**
52
56
  * Provides information about a particular entry in the file system.
@@ -127,7 +131,7 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
127
131
  /**
128
132
  * Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
129
133
  */
130
- constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode }?: Partial<StatsLike>);
134
+ constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino }?: Partial<StatsLike>);
131
135
  /**
132
136
  * @returns true if this item is a file.
133
137
  */
package/dist/stats.js CHANGED
@@ -49,7 +49,7 @@ export class StatsCommon {
49
49
  /**
50
50
  * Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
51
51
  */
52
- constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode } = {}) {
52
+ constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino } = {}) {
53
53
  /**
54
54
  * ID of device containing file
55
55
  */
@@ -91,6 +91,7 @@ export class StatsCommon {
91
91
  this.uid = resolveT(uid, 0);
92
92
  this.gid = resolveT(gid, 0);
93
93
  this.size = this._convert(size);
94
+ this.ino = this._convert(ino);
94
95
  const itemType = Number(mode) & S_IFMT || FileType.FILE;
95
96
  if (mode) {
96
97
  this.mode = this._convert(mode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "A filesystem in your browser",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.ts",
@@ -132,10 +132,15 @@ export interface AsyncStoreOptions {
132
132
  */
133
133
  export class AsyncStoreFS extends Async(FileSystem) {
134
134
  protected store: AsyncStore;
135
- private _cache?: LRUCache<string, Ino>;
135
+ protected _cache?: LRUCache<string, Ino>;
136
+ private _initialized: boolean = false;
136
137
  _sync: FileSystem;
137
138
 
138
- public async ready() {
139
+ public async ready(): Promise<this> {
140
+ if (this._initialized) {
141
+ return this;
142
+ }
143
+ this._initialized = true;
139
144
  if (this._options.lruCacheSize > 0) {
140
145
  this._cache = new LRUCache(this._options.lruCacheSize);
141
146
  }
@@ -165,7 +170,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
165
170
  this._cache.reset();
166
171
  }
167
172
  await this.store.clear();
168
- // INVARIANT: Root always exists.
173
+ // Root always exists.
169
174
  await this.makeRootDirectory();
170
175
  }
171
176
 
@@ -173,6 +178,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
173
178
  * @todo Make rename compatible with the cache.
174
179
  */
175
180
  public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> {
181
+ await this.queueDone();
176
182
  const c = this._cache;
177
183
  if (this._cache) {
178
184
  // Clear and disable cache during renaming process.
@@ -255,6 +261,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
255
261
  }
256
262
 
257
263
  public async stat(p: string, cred: Cred): Promise<Stats> {
264
+ await this.queueDone();
258
265
  const tx = this.store.beginTransaction();
259
266
  const inode = await this.findINode(tx, p);
260
267
  if (!inode) {
@@ -268,6 +275,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
268
275
  }
269
276
 
270
277
  public async createFile(p: string, flag: string, mode: number, cred: Cred): Promise<PreloadFile<this>> {
278
+ await this.queueDone();
271
279
  const tx = this.store.beginTransaction(),
272
280
  data = new Uint8Array(0),
273
281
  newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
@@ -276,6 +284,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
276
284
  }
277
285
 
278
286
  public async openFile(p: string, flag: string, cred: Cred): Promise<PreloadFile<this>> {
287
+ await this.queueDone();
279
288
  const tx = this.store.beginTransaction(),
280
289
  node = await this.findINode(tx, p),
281
290
  data = await tx.get(node.ino);
@@ -289,10 +298,12 @@ export class AsyncStoreFS extends Async(FileSystem) {
289
298
  }
290
299
 
291
300
  public async unlink(p: string, cred: Cred): Promise<void> {
301
+ await this.queueDone();
292
302
  return this.removeEntry(p, false, cred);
293
303
  }
294
304
 
295
305
  public async rmdir(p: string, cred: Cred): Promise<void> {
306
+ await this.queueDone();
296
307
  // Check first if directory is empty.
297
308
  const list = await this.readdir(p, cred);
298
309
  if (list.length > 0) {
@@ -302,12 +313,14 @@ export class AsyncStoreFS extends Async(FileSystem) {
302
313
  }
303
314
 
304
315
  public async mkdir(p: string, mode: number, cred: Cred): Promise<void> {
316
+ await this.queueDone();
305
317
  const tx = this.store.beginTransaction(),
306
318
  data = encode('{}');
307
319
  await this.commitNewFile(tx, p, FileType.DIRECTORY, mode, cred, data);
308
320
  }
309
321
 
310
322
  public async readdir(p: string, cred: Cred): Promise<string[]> {
323
+ await this.queueDone();
311
324
  const tx = this.store.beginTransaction();
312
325
  const node = await this.findINode(tx, p);
313
326
  if (!node.toStats().hasAccess(R_OK, cred)) {
@@ -321,6 +334,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
321
334
  * @todo Ensure mtime updates properly, and use that to determine if a data update is required.
322
335
  */
323
336
  public async sync(p: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
337
+ await this.queueDone();
324
338
  const tx = this.store.beginTransaction(),
325
339
  // We use the _findInode helper because we actually need the INode id.
326
340
  fileInodeId = await this._findINode(tx, dirname(p), basename(p)),
@@ -342,6 +356,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
342
356
  }
343
357
 
344
358
  public async link(existing: string, newpath: string, cred: Cred): Promise<void> {
359
+ await this.queueDone();
345
360
  const tx = this.store.beginTransaction(),
346
361
  existingDir: string = dirname(existing),
347
362
  existingDirNode = await this.findINode(tx, existingDir);
@@ -382,14 +397,13 @@ export class AsyncStoreFS extends Async(FileSystem) {
382
397
  */
383
398
  private async makeRootDirectory(): Promise<void> {
384
399
  const tx = this.store.beginTransaction();
385
- if ((await tx.get(rootIno)) === undefined) {
400
+ if (!(await tx.get(rootIno))) {
386
401
  // Create new inode. o777, owned by root:root
387
- const dirInode = new Inode();
388
- dirInode.mode = 0o777 | FileType.DIRECTORY;
389
- // If the root doesn't exist, the first random ID shouldn't exist,
390
- // either.
391
- await tx.put(dirInode.ino, encode('{}'), false);
392
- await tx.put(rootIno, dirInode.data, false);
402
+ const inode = new Inode();
403
+ inode.mode = 0o777 | FileType.DIRECTORY;
404
+ // If the root doesn't exist, the first random ID shouldn't exist either.
405
+ await tx.put(inode.ino, encode('{}'), false);
406
+ await tx.put(rootIno, inode.data, false);
393
407
  await tx.commit();
394
408
  }
395
409
  }
package/src/filesystem.ts CHANGED
@@ -59,7 +59,9 @@ export abstract class FileSystem {
59
59
  // unused
60
60
  }
61
61
 
62
- public abstract ready(): Promise<this>;
62
+ public async ready(): Promise<this> {
63
+ return this;
64
+ }
63
65
 
64
66
  /**
65
67
  * Asynchronous rename. No arguments other than a possible exception
@@ -218,10 +220,6 @@ declare abstract class SyncFileSystem extends FileSystem {
218
220
  */
219
221
  export function Sync<T extends abstract new (...args) => FileSystem>(FS: T): (abstract new (...args) => SyncFileSystem) & T {
220
222
  abstract class _SyncFileSystem extends FS implements SyncFileSystem {
221
- public async ready(): Promise<this> {
222
- return this;
223
- }
224
-
225
223
  public async exists(path: string, cred: Cred): Promise<boolean> {
226
224
  return this.existsSync(path, cred);
227
225
  }
@@ -277,6 +275,7 @@ declare abstract class AsyncFileSystem extends FileSystem {
277
275
  * @hidden
278
276
  */
279
277
  abstract _sync: FileSystem;
278
+ queueDone(): Promise<void>;
280
279
  metadata(): FileSystemMetadata;
281
280
  ready(): Promise<this>;
282
281
  renameSync(oldPath: string, newPath: string, cred: Cred): void;
@@ -318,13 +317,24 @@ export function Async<T extends abstract new (...args) => FileSystem>(FS: T): (a
318
317
  * Queue of pending asynchronous operations.
319
318
  */
320
319
  private _queue: AsyncOperation[] = [];
321
- private _queueRunning: boolean = false;
320
+ private get _queueRunning(): boolean {
321
+ return !!this._queue.length;
322
+ }
323
+
324
+ public queueDone(): Promise<void> {
325
+ return new Promise(resolve => {
326
+ const check = () => (this._queueRunning ? setTimeout(check) : resolve());
327
+ check();
328
+ });
329
+ }
330
+
322
331
  private _isInitialized: boolean = false;
323
332
 
324
333
  abstract _sync: FileSystem;
325
334
 
326
335
  public async ready(): Promise<this> {
327
336
  await this._sync.ready();
337
+ await super.ready();
328
338
  if (this._isInitialized) {
329
339
  return this;
330
340
  }
@@ -410,12 +420,11 @@ export function Async<T extends abstract new (...args) => FileSystem>(FS: T): (a
410
420
  }
411
421
  } else {
412
422
  const asyncFile = await this.openFile(p, parseFlag('r'), rootCred);
413
- const syncFile = this._sync.createFileSync(p, parseFlag('w'), stats.mode, rootCred);
423
+ const syncFile = this._sync.createFileSync(p, parseFlag('w'), stats.mode, stats.cred());
414
424
  try {
415
- const { size } = await asyncFile.stat();
416
- const buffer = new Uint8Array(size);
425
+ const buffer = new Uint8Array(stats.size);
417
426
  await asyncFile.read(buffer);
418
- syncFile.writeSync(buffer);
427
+ syncFile.writeSync(buffer, 0, stats.size);
419
428
  } finally {
420
429
  await asyncFile.close();
421
430
  syncFile.closeSync();
@@ -427,12 +436,11 @@ export function Async<T extends abstract new (...args) => FileSystem>(FS: T): (a
427
436
  * @internal
428
437
  */
429
438
  private async _next(): Promise<void> {
430
- if (this._queue.length == 0) {
431
- this._queueRunning = false;
439
+ if (!this._queueRunning) {
432
440
  return;
433
441
  }
434
442
 
435
- const [method, ...args] = this._queue.shift()!;
443
+ const [method, ...args] = this._queue.shift();
436
444
  // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
437
445
  await this[method](...args);
438
446
  await this._next();
@@ -443,11 +451,6 @@ export function Async<T extends abstract new (...args) => FileSystem>(FS: T): (a
443
451
  */
444
452
  private queue(...op: AsyncOperation) {
445
453
  this._queue.push(op);
446
- if (this._queueRunning) {
447
- return;
448
- }
449
-
450
- this._queueRunning = true;
451
454
  this._next();
452
455
  }
453
456
  }
package/src/stats.ts CHANGED
@@ -49,6 +49,10 @@ export interface StatsLike {
49
49
  * the id of the group that owns the file
50
50
  */
51
51
  gid: number | bigint;
52
+ /**
53
+ * the ino
54
+ */
55
+ ino: number | bigint;
52
56
  }
53
57
 
54
58
  /**
@@ -179,7 +183,7 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
179
183
  /**
180
184
  * Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
181
185
  */
182
- constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode }: Partial<StatsLike> = {}) {
186
+ constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino }: Partial<StatsLike> = {}) {
183
187
  const currentTime = Date.now();
184
188
  const resolveT = (val: number | bigint, _default: number) => <T>(typeof val == this._typename ? val : this._convert(typeof val == this._typename_inverse ? val : _default));
185
189
  this.atimeMs = resolveT(atimeMs, currentTime);
@@ -189,6 +193,7 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
189
193
  this.uid = resolveT(uid, 0);
190
194
  this.gid = resolveT(gid, 0);
191
195
  this.size = this._convert(size);
196
+ this.ino = this._convert(ino);
192
197
  const itemType: FileType = Number(mode) & S_IFMT || FileType.FILE;
193
198
 
194
199
  if (mode) {