@zenfs/core 0.5.2 → 0.5.4

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.
@@ -4,41 +4,31 @@ import { FileSystem, type FileSystemMetadata } from '../filesystem.js';
4
4
  import { type Ino } from '../inode.js';
5
5
  import { type Stats } from '../stats.js';
6
6
  /**
7
- * Represents an *asynchronous* key-value store.
7
+ * Represents an asynchronous key-value store.
8
8
  */
9
9
  export interface AsyncStore {
10
10
  /**
11
- * The name of the key-value store.
11
+ * The name of the store.
12
12
  */
13
13
  name: string;
14
14
  /**
15
- * Empties the key-value store completely.
15
+ * Empties the store completely.
16
16
  */
17
17
  clear(): Promise<void>;
18
18
  /**
19
- * Begins a read-write transaction.
19
+ * Begins a transaction.
20
20
  */
21
- beginTransaction(type: 'readwrite'): AsyncRWTransaction;
22
- /**
23
- * Begins a read-only transaction.
24
- */
25
- beginTransaction(type: 'readonly'): AsyncROTransaction;
26
- beginTransaction(type: string): AsyncROTransaction;
21
+ beginTransaction(): AsyncTransaction;
27
22
  }
28
23
  /**
29
- * Represents an asynchronous read-only transaction.
24
+ * Represents an asynchronous transaction.
30
25
  */
31
- export interface AsyncROTransaction {
26
+ export interface AsyncTransaction {
32
27
  /**
33
28
  * Retrieves the data at the given key.
34
29
  * @param key The key to look under for data.
35
30
  */
36
31
  get(key: Ino): Promise<Uint8Array>;
37
- }
38
- /**
39
- * Represents an asynchronous read-write transaction.
40
- */
41
- export interface AsyncRWTransaction extends AsyncROTransaction {
42
32
  /**
43
33
  * Adds the data to the store under the given key. Overwrites any existing
44
34
  * data.
@@ -123,7 +123,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
123
123
  c.reset();
124
124
  }
125
125
  try {
126
- const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
126
+ const tx = this.store.beginTransaction(), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
127
127
  // Remove oldPath from parent's directory listing.
128
128
  oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
129
129
  if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
@@ -190,7 +190,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
190
190
  }
191
191
  }
192
192
  async stat(p, cred) {
193
- const tx = this.store.beginTransaction('readonly');
193
+ const tx = this.store.beginTransaction();
194
194
  const inode = await this.findINode(tx, p);
195
195
  if (!inode) {
196
196
  throw ApiError.ENOENT(p);
@@ -202,12 +202,12 @@ export class AsyncStoreFS extends Async(FileSystem) {
202
202
  return stats;
203
203
  }
204
204
  async createFile(p, flag, mode, cred) {
205
- const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
205
+ const tx = this.store.beginTransaction(), data = new Uint8Array(0), newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
206
206
  // Open the file.
207
207
  return new AsyncFile(this, p, flag, newFile.toStats(), data);
208
208
  }
209
209
  async openFile(p, flag, cred) {
210
- const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.ino);
210
+ const tx = this.store.beginTransaction(), node = await this.findINode(tx, p), data = await tx.get(node.ino);
211
211
  if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
212
212
  throw ApiError.EACCES(p);
213
213
  }
@@ -228,11 +228,11 @@ export class AsyncStoreFS extends Async(FileSystem) {
228
228
  await this.removeEntry(p, true, cred);
229
229
  }
230
230
  async mkdir(p, mode, cred) {
231
- const tx = this.store.beginTransaction('readwrite'), data = encode('{}');
231
+ const tx = this.store.beginTransaction(), data = encode('{}');
232
232
  await this.commitNewFile(tx, p, FileType.DIRECTORY, mode, cred, data);
233
233
  }
234
234
  async readdir(p, cred) {
235
- const tx = this.store.beginTransaction('readonly');
235
+ const tx = this.store.beginTransaction();
236
236
  const node = await this.findINode(tx, p);
237
237
  if (!node.toStats().hasAccess(R_OK, cred)) {
238
238
  throw ApiError.EACCES(p);
@@ -244,7 +244,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
244
244
  * @todo Ensure mtime updates properly, and use that to determine if a data update is required.
245
245
  */
246
246
  async sync(p, data, stats) {
247
- const tx = this.store.beginTransaction('readwrite'),
247
+ const tx = this.store.beginTransaction(),
248
248
  // We use the _findInode helper because we actually need the INode id.
249
249
  fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
250
250
  try {
@@ -262,7 +262,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
262
262
  await tx.commit();
263
263
  }
264
264
  async link(existing, newpath, cred) {
265
- const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
265
+ const tx = this.store.beginTransaction(), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
266
266
  if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
267
267
  throw ApiError.EACCES(existingDir);
268
268
  }
@@ -291,7 +291,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
291
291
  * Checks if the root directory exists. Creates it if it doesn't.
292
292
  */
293
293
  async makeRootDirectory() {
294
- const tx = this.store.beginTransaction('readwrite');
294
+ const tx = this.store.beginTransaction();
295
295
  if ((await tx.get(rootIno)) === undefined) {
296
296
  // Create new inode. o777, owned by root:root
297
297
  const dirInode = new Inode();
@@ -407,26 +407,18 @@ export class AsyncStoreFS extends Async(FileSystem) {
407
407
  * Adds a new node under a random ID. Retries 5 times before giving up in
408
408
  * the exceedingly unlikely chance that we try to reuse a random ino.
409
409
  */
410
- async addNewNode(tx, data) {
411
- let retries = 0;
412
- const reroll = async () => {
413
- if (++retries === 5) {
414
- // Max retries hit. Return with an error.
415
- throw new ApiError(ErrorCode.EIO, 'Unable to commit data to key-value store.');
416
- }
417
- else {
418
- // Try again.
419
- const ino = randomIno();
420
- const committed = await tx.put(ino, data, false);
421
- if (!committed) {
422
- return reroll();
423
- }
424
- else {
425
- return ino;
426
- }
427
- }
428
- };
429
- return reroll();
410
+ async addNewNode(tx, data, _maxAttempts = 5) {
411
+ if (_maxAttempts <= 0) {
412
+ // Max retries hit. Return with an error.
413
+ throw new ApiError(ErrorCode.EIO, 'Unable to commit data to key-value store.');
414
+ }
415
+ // Make an attempt
416
+ const ino = randomIno();
417
+ const isCommited = await tx.put(ino, data, false);
418
+ if (!isCommited) {
419
+ return await this.addNewNode(tx, data, --_maxAttempts);
420
+ }
421
+ return ino;
430
422
  }
431
423
  /**
432
424
  * Commits a new file (well, a FILE or a DIRECTORY) to the file system with
@@ -490,7 +482,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
490
482
  if (this._cache) {
491
483
  this._cache.remove(p);
492
484
  }
493
- const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx, parentNode, parent), fileName = basename(p);
485
+ const tx = this.store.beginTransaction(), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx, parentNode, parent), fileName = basename(p);
494
486
  if (!parentListing[fileName]) {
495
487
  throw ApiError.ENOENT(p);
496
488
  }
@@ -1,6 +1,6 @@
1
1
  import type { Ino } from '../inode.js';
2
2
  import type { Backend } from './backend.js';
3
- import { SyncStore, SimpleSyncStore, SyncRWTransaction } from './SyncStore.js';
3
+ import { SyncStore, SimpleSyncStore, SyncTransaction } from './SyncStore.js';
4
4
  /**
5
5
  * A simple in-memory store
6
6
  */
@@ -9,7 +9,7 @@ export declare class InMemoryStore implements SyncStore, SimpleSyncStore {
9
9
  private store;
10
10
  constructor(name?: string);
11
11
  clear(): void;
12
- beginTransaction(): SyncRWTransaction;
12
+ beginTransaction(): SyncTransaction;
13
13
  get(key: Ino): Uint8Array;
14
14
  put(key: Ino, data: Uint8Array, overwrite: boolean): boolean;
15
15
  remove(key: Ino): void;
@@ -1,4 +1,4 @@
1
- import { SimpleSyncRWTransaction, SyncStoreFS } from './SyncStore.js';
1
+ import { SimpleSyncTransaction, SyncStoreFS } from './SyncStore.js';
2
2
  /**
3
3
  * A simple in-memory store
4
4
  */
@@ -11,7 +11,7 @@ export class InMemoryStore {
11
11
  this.store.clear();
12
12
  }
13
13
  beginTransaction() {
14
- return new SimpleSyncRWTransaction(this);
14
+ return new SimpleSyncTransaction(this);
15
15
  }
16
16
  get(key) {
17
17
  return this.store.get(key);
@@ -16,19 +16,14 @@ export interface SyncStore {
16
16
  */
17
17
  clear(): void;
18
18
  /**
19
- * Begins a new read-only transaction.
19
+ * Begins a new transaction.
20
20
  */
21
- beginTransaction(type: 'readonly'): SyncROTransaction;
22
- /**
23
- * Begins a new read-write transaction.
24
- */
25
- beginTransaction(type: 'readwrite'): SyncRWTransaction;
26
- beginTransaction(type: string): SyncROTransaction;
21
+ beginTransaction(): SyncTransaction;
27
22
  }
28
23
  /**
29
- * A read-only transaction for a synchronous key value store.
24
+ * A transaction for a synchronous key value store.
30
25
  */
31
- export interface SyncROTransaction {
26
+ export interface SyncTransaction {
32
27
  /**
33
28
  * Retrieves the data at the given key. Throws an ApiError if an error occurs
34
29
  * or if the key does not exist.
@@ -36,11 +31,6 @@ export interface SyncROTransaction {
36
31
  * @return The data stored under the key, or undefined if not present.
37
32
  */
38
33
  get(ino: Ino): Uint8Array | undefined;
39
- }
40
- /**
41
- * A read-write transaction for a synchronous key value store.
42
- */
43
- export interface SyncRWTransaction extends SyncROTransaction {
44
34
  /**
45
35
  * Adds the data to the store under the given key.
46
36
  * @param ino The key to add the data under.
@@ -76,7 +66,7 @@ export interface SimpleSyncStore {
76
66
  /**
77
67
  * A simple RW transaction for simple synchronous key-value stores.
78
68
  */
79
- export declare class SimpleSyncRWTransaction implements SyncRWTransaction {
69
+ export declare class SimpleSyncTransaction implements SyncTransaction {
80
70
  protected store: SimpleSyncStore;
81
71
  /**
82
72
  * Stores data in the keys we modify prior to modifying them.
@@ -186,25 +176,25 @@ export declare class SyncStoreFS extends SyncStoreFS_base {
186
176
  * the parent.
187
177
  * @return string The ID of the file's inode in the file system.
188
178
  */
189
- protected _findINode(tx: SyncROTransaction, parent: string, filename: string, visited?: Set<string>): Ino;
179
+ protected _findINode(tx: SyncTransaction, parent: string, filename: string, visited?: Set<string>): Ino;
190
180
  /**
191
181
  * Finds the Inode of the given path.
192
182
  * @param p The path to look up.
193
183
  * @return The Inode of the path p.
194
184
  * @todo memoize/cache
195
185
  */
196
- protected findINode(tx: SyncROTransaction, p: string): Inode;
186
+ protected findINode(tx: SyncTransaction, p: string): Inode;
197
187
  /**
198
188
  * Given the ID of a node, retrieves the corresponding Inode.
199
189
  * @param tx The transaction to use.
200
190
  * @param p The corresponding path to the file (used for error messages).
201
191
  * @param id The ID to look up.
202
192
  */
203
- protected getINode(tx: SyncROTransaction, id: Ino, p?: string): Inode;
193
+ protected getINode(tx: SyncTransaction, id: Ino, p?: string): Inode;
204
194
  /**
205
195
  * Given the Inode of a directory, retrieves the corresponding directory listing.
206
196
  */
207
- protected getDirListing(tx: SyncROTransaction, inode: Inode, p?: string): {
197
+ protected getDirListing(tx: SyncTransaction, inode: Inode, p?: string): {
208
198
  [fileName: string]: Ino;
209
199
  };
210
200
  /**
@@ -212,7 +202,7 @@ export declare class SyncStoreFS extends SyncStoreFS_base {
212
202
  * the exceedingly unlikely chance that we try to reuse a random GUID.
213
203
  * @return The GUID that the data was stored under.
214
204
  */
215
- protected addNewNode(tx: SyncRWTransaction, data: Uint8Array): Ino;
205
+ protected addNewNode(tx: SyncTransaction, data: Uint8Array, _maxAttempts?: number): Ino;
216
206
  /**
217
207
  * Commits a new file (well, a FILE or a DIRECTORY) to the file system with the given mode.
218
208
  * Note: This will commit the transaction.
@@ -10,7 +10,7 @@ import { rootIno } from '../inode.js';
10
10
  /**
11
11
  * A simple RW transaction for simple synchronous key-value stores.
12
12
  */
13
- export class SimpleSyncRWTransaction {
13
+ export class SimpleSyncTransaction {
14
14
  constructor(store) {
15
15
  this.store = store;
16
16
  /**
@@ -130,7 +130,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
130
130
  this.makeRootDirectory();
131
131
  }
132
132
  renameSync(oldPath, newPath, cred) {
133
- const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
133
+ const tx = this.store.beginTransaction(), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
134
134
  // Remove oldPath from parent's directory listing.
135
135
  oldDirNode = this.findINode(tx, oldParent), oldDirList = this.getDirListing(tx, oldDirNode, oldParent);
136
136
  if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
@@ -192,7 +192,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
192
192
  }
193
193
  statSync(p, cred) {
194
194
  // Get the inode to the item, convert it into a Stats object.
195
- const stats = this.findINode(this.store.beginTransaction('readonly'), p).toStats();
195
+ const stats = this.findINode(this.store.beginTransaction(), p).toStats();
196
196
  if (!stats.hasAccess(R_OK, cred)) {
197
197
  throw ApiError.EACCES(p);
198
198
  }
@@ -203,7 +203,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
203
203
  return this.openFileSync(p, flag, cred);
204
204
  }
205
205
  openFileSync(p, flag, cred) {
206
- const tx = this.store.beginTransaction('readonly'), node = this.findINode(tx, p), data = tx.get(node.ino);
206
+ const tx = this.store.beginTransaction(), node = this.findINode(tx, p), data = tx.get(node.ino);
207
207
  if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
208
208
  throw ApiError.EACCES(p);
209
209
  }
@@ -228,7 +228,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
228
228
  this.commitNewFile(p, FileType.DIRECTORY, mode, cred, encode('{}'));
229
229
  }
230
230
  readdirSync(p, cred) {
231
- const tx = this.store.beginTransaction('readonly');
231
+ const tx = this.store.beginTransaction();
232
232
  const node = this.findINode(tx, p);
233
233
  if (!node.toStats().hasAccess(R_OK, cred)) {
234
234
  throw ApiError.EACCES(p);
@@ -238,7 +238,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
238
238
  syncSync(p, data, stats) {
239
239
  // @todo Ensure mtime updates properly, and use that to determine if a data
240
240
  // update is required.
241
- const tx = this.store.beginTransaction('readwrite'),
241
+ const tx = this.store.beginTransaction(),
242
242
  // We use the _findInode helper because we actually need the INode id.
243
243
  fileInodeId = this._findINode(tx, dirname(p), basename(p)), fileInode = this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
244
244
  try {
@@ -256,7 +256,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
256
256
  tx.commit();
257
257
  }
258
258
  linkSync(existing, newpath, cred) {
259
- const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = this.findINode(tx, existingDir);
259
+ const tx = this.store.beginTransaction(), existingDir = dirname(existing), existingDirNode = this.findINode(tx, existingDir);
260
260
  if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
261
261
  throw ApiError.EACCES(existingDir);
262
262
  }
@@ -285,7 +285,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
285
285
  * Checks if the root directory exists. Creates it if it doesn't.
286
286
  */
287
287
  makeRootDirectory() {
288
- const tx = this.store.beginTransaction('readwrite');
288
+ const tx = this.store.beginTransaction();
289
289
  if (tx.get(rootIno)) {
290
290
  return;
291
291
  }
@@ -371,18 +371,13 @@ export class SyncStoreFS extends Sync(FileSystem) {
371
371
  * the exceedingly unlikely chance that we try to reuse a random GUID.
372
372
  * @return The GUID that the data was stored under.
373
373
  */
374
- addNewNode(tx, data) {
375
- const retries = 0;
376
- let ino;
377
- while (retries < 5) {
378
- try {
379
- ino = randomIno();
380
- tx.put(ino, data, false);
381
- return ino;
382
- }
383
- catch (e) {
384
- // Ignore and reroll.
374
+ addNewNode(tx, data, _maxAttempts = 5) {
375
+ for (let i = 0; i < _maxAttempts; i++) {
376
+ const ino = randomIno();
377
+ if (!tx.put(ino, data, false)) {
378
+ continue;
385
379
  }
380
+ return ino;
386
381
  }
387
382
  throw new ApiError(ErrorCode.EIO, 'Unable to commit data to key-value store.');
388
383
  }
@@ -396,7 +391,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
396
391
  * @return The Inode for the new file.
397
392
  */
398
393
  commitNewFile(p, type, mode, cred, data = new Uint8Array()) {
399
- const tx = this.store.beginTransaction('readwrite'), parentDir = dirname(p), fname = basename(p), parentNode = this.findINode(tx, parentDir), dirListing = this.getDirListing(tx, parentNode, parentDir);
394
+ const tx = this.store.beginTransaction(), parentDir = dirname(p), fname = basename(p), parentNode = this.findINode(tx, parentDir), dirListing = this.getDirListing(tx, parentNode, parentDir);
400
395
  //Check that the creater has correct access
401
396
  if (!parentNode.toStats().hasAccess(W_OK, cred)) {
402
397
  throw ApiError.EACCES(p);
@@ -438,7 +433,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
438
433
  * @todo Update mtime.
439
434
  */
440
435
  removeEntry(p, isDir, cred) {
441
- const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = this.findINode(tx, parent), parentListing = this.getDirListing(tx, parentNode, parent), fileName = basename(p), fileIno = parentListing[fileName];
436
+ const tx = this.store.beginTransaction(), parent = dirname(p), parentNode = this.findINode(tx, parent), parentListing = this.getDirListing(tx, parentNode, parent), fileName = basename(p), fileIno = parentListing[fileName];
442
437
  if (!fileIno) {
443
438
  throw ApiError.ENOENT(p);
444
439
  }