@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,104 +1,50 @@
1
1
  import { dirname, basename, join, resolve } from '../emulation/path.js';
2
2
  import { ApiError, ErrorCode } from '../ApiError.js';
3
3
  import { W_OK, R_OK } from '../emulation/constants.js';
4
- import { PreloadFile, FileFlag } from '../file.js';
5
- import { BaseFileSystem } from '../filesystem.js';
6
- import Inode from '../inode.js';
4
+ import { PreloadFile } from '../file.js';
5
+ import { AsyncFileSystem } from '../filesystem.js';
6
+ import { randomIno, Inode } from '../inode.js';
7
7
  import { FileType } from '../stats.js';
8
- import { ROOT_NODE_ID, randomUUID, encode, decode } from '../utils.js';
9
- class LRUNode {
10
- constructor(key, value) {
11
- this.key = key;
12
- this.value = value;
13
- this.prev = null;
14
- this.next = null;
15
- }
16
- }
17
- // Adapted from https://chrisrng.svbtle.com/lru-cache-in-javascript
8
+ import { encode, decodeDirListing, encodeDirListing } from '../utils.js';
9
+ import { rootIno } from '../inode.js';
10
+ /**
11
+ * Last Recently Used cache
12
+ */
18
13
  class LRUCache {
19
14
  constructor(limit) {
20
15
  this.limit = limit;
21
- this.size = 0;
22
- this.map = {};
23
- this.head = null;
24
- this.tail = null;
16
+ this.cache = [];
25
17
  }
26
- /**
27
- * Change or add a new value in the cache
28
- * We overwrite the entry if it already exists
29
- */
30
18
  set(key, value) {
31
- const node = new LRUNode(key, value);
32
- if (this.map[key]) {
33
- this.map[key].value = node.value;
34
- this.remove(node.key);
19
+ const existingIndex = this.cache.findIndex(node => node.key === key);
20
+ if (existingIndex != -1) {
21
+ this.cache.splice(existingIndex, 1);
35
22
  }
36
- else {
37
- if (this.size >= this.limit) {
38
- delete this.map[this.tail.key];
39
- this.size--;
40
- this.tail = this.tail.prev;
41
- this.tail.next = null;
42
- }
23
+ else if (this.cache.length >= this.limit) {
24
+ this.cache.shift();
43
25
  }
44
- this.setHead(node);
26
+ this.cache.push({ key, value });
45
27
  }
46
- /* Retrieve a single entry from the cache */
47
28
  get(key) {
48
- if (this.map[key]) {
49
- const value = this.map[key].value;
50
- const node = new LRUNode(key, value);
51
- this.remove(key);
52
- this.setHead(node);
53
- return value;
54
- }
55
- else {
56
- return null;
57
- }
58
- }
59
- /* Remove a single entry from the cache */
60
- remove(key) {
61
- const node = this.map[key];
29
+ const node = this.cache.find(n => n.key === key);
62
30
  if (!node) {
63
31
  return;
64
32
  }
65
- if (node.prev !== null) {
66
- node.prev.next = node.next;
67
- }
68
- else {
69
- this.head = node.next;
70
- }
71
- if (node.next !== null) {
72
- node.next.prev = node.prev;
73
- }
74
- else {
75
- this.tail = node.prev;
76
- }
77
- delete this.map[key];
78
- this.size--;
79
- }
80
- /* Resets the entire cache - Argument limit is optional to be reset */
81
- removeAll() {
82
- this.size = 0;
83
- this.map = {};
84
- this.head = null;
85
- this.tail = null;
33
+ // Move the accessed item to the end of the cache (most recently used)
34
+ this.set(key, node.value);
35
+ return node.value;
86
36
  }
87
- setHead(node) {
88
- node.next = this.head;
89
- node.prev = null;
90
- if (this.head !== null) {
91
- this.head.prev = node;
92
- }
93
- this.head = node;
94
- if (this.tail === null) {
95
- this.tail = node;
37
+ remove(key) {
38
+ const index = this.cache.findIndex(node => node.key === key);
39
+ if (index !== -1) {
40
+ this.cache.splice(index, 1);
96
41
  }
97
- this.size++;
98
- this.map[node.key] = node;
42
+ }
43
+ reset() {
44
+ this.cache = [];
99
45
  }
100
46
  }
101
- export class AsyncKeyValueFile extends PreloadFile {
47
+ export class AsyncFile extends PreloadFile {
102
48
  constructor(_fs, _path, _flag, _stat, contents) {
103
49
  super(_fs, _path, _flag, _stat, contents);
104
50
  }
@@ -106,24 +52,29 @@ export class AsyncKeyValueFile extends PreloadFile {
106
52
  if (!this.isDirty()) {
107
53
  return;
108
54
  }
109
- await this._fs._sync(this.getPath(), this.getBuffer(), this.getStats());
55
+ await this.fs.sync(this.path, this._buffer, this.stats);
110
56
  this.resetDirty();
111
57
  }
58
+ syncSync() {
59
+ throw new ApiError(ErrorCode.ENOTSUP);
60
+ }
112
61
  async close() {
113
62
  this.sync();
114
63
  }
64
+ closeSync() {
65
+ throw new ApiError(ErrorCode.ENOTSUP);
66
+ }
115
67
  }
116
68
  /**
117
69
  * An "Asynchronous key-value file system". Stores data to/retrieves data from
118
70
  * an underlying asynchronous key-value store.
119
71
  */
120
- export class AsyncKeyValueFileSystem extends BaseFileSystem {
121
- static isAvailable() {
122
- return true;
72
+ export class AsyncStoreFileSystem extends AsyncFileSystem {
73
+ ready() {
74
+ return this._ready;
123
75
  }
124
76
  constructor(cacheSize) {
125
77
  super();
126
- this._cache = null;
127
78
  if (cacheSize > 0) {
128
79
  this._cache = new LRUCache(cacheSize);
129
80
  }
@@ -137,42 +88,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
137
88
  // INVARIANT: Ensure that the root exists.
138
89
  await this.makeRootDirectory();
139
90
  }
140
- getName() {
141
- return this.store.name();
142
- }
143
- isReadOnly() {
144
- return false;
145
- }
146
- supportsSymlinks() {
147
- return false;
148
- }
149
- supportsProps() {
150
- return true;
151
- }
152
- supportsSynch() {
153
- return false;
154
- }
155
91
  /**
156
92
  * Delete all contents stored in the file system.
157
93
  */
158
94
  async empty() {
159
95
  if (this._cache) {
160
- this._cache.removeAll();
96
+ this._cache.reset();
161
97
  }
162
98
  await this.store.clear();
163
99
  // INVARIANT: Root always exists.
164
100
  await this.makeRootDirectory();
165
101
  }
166
- async access(p, mode, cred) {
167
- const tx = this.store.beginTransaction('readonly');
168
- const inode = await this.findINode(tx, p);
169
- if (!inode) {
170
- throw ApiError.ENOENT(p);
171
- }
172
- if (!inode.toStats().hasAccess(mode, cred)) {
173
- throw ApiError.EACCES(p);
174
- }
175
- }
176
102
  /**
177
103
  * @todo Make rename compatible with the cache.
178
104
  */
@@ -181,12 +107,12 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
181
107
  if (this._cache) {
182
108
  // Clear and disable cache during renaming process.
183
109
  this._cache = null;
184
- c.removeAll();
110
+ c.reset();
185
111
  }
186
112
  try {
187
113
  const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
188
114
  // Remove oldPath from parent's directory listing.
189
- oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldParent, oldDirNode);
115
+ oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
190
116
  if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
191
117
  throw ApiError.EACCES(oldPath);
192
118
  }
@@ -212,15 +138,15 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
212
138
  }
213
139
  else {
214
140
  newDirNode = await this.findINode(tx, newParent);
215
- newDirList = await this.getDirListing(tx, newParent, newDirNode);
141
+ newDirList = await this.getDirListing(tx, newDirNode, newParent);
216
142
  }
217
143
  if (newDirList[newName]) {
218
144
  // If it's a file, delete it.
219
- const newNameNode = await this.getINode(tx, newPath, newDirList[newName]);
220
- if (newNameNode.isFile()) {
145
+ const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
146
+ if (newNameNode.toStats().isFile()) {
221
147
  try {
222
- await tx.del(newNameNode.id);
223
- await tx.del(newDirList[newName]);
148
+ await tx.remove(newNameNode.ino);
149
+ await tx.remove(newDirList[newName]);
224
150
  }
225
151
  catch (e) {
226
152
  await tx.abort();
@@ -235,8 +161,8 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
235
161
  newDirList[newName] = nodeId;
236
162
  // Commit the two changed directory listings.
237
163
  try {
238
- await tx.put(oldDirNode.id, encode(JSON.stringify(oldDirList)), true);
239
- await tx.put(newDirNode.id, encode(JSON.stringify(newDirList)), true);
164
+ await tx.put(oldDirNode.ino, encodeDirListing(oldDirList), true);
165
+ await tx.put(newDirNode.ino, encodeDirListing(newDirList), true);
240
166
  }
241
167
  catch (e) {
242
168
  await tx.abort();
@@ -253,6 +179,9 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
253
179
  async stat(p, cred) {
254
180
  const tx = this.store.beginTransaction('readonly');
255
181
  const inode = await this.findINode(tx, p);
182
+ if (!inode) {
183
+ throw ApiError.ENOENT(p);
184
+ }
256
185
  const stats = inode.toStats();
257
186
  if (!stats.hasAccess(R_OK, cred)) {
258
187
  throw ApiError.EACCES(p);
@@ -262,17 +191,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
262
191
  async createFile(p, flag, mode, cred) {
263
192
  const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
264
193
  // Open the file.
265
- return new AsyncKeyValueFile(this, p, flag, newFile.toStats(), data);
194
+ return new AsyncFile(this, p, flag, newFile.toStats(), data);
266
195
  }
267
196
  async openFile(p, flag, cred) {
268
- const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.id);
269
- if (!node.toStats().hasAccess(flag.getMode(), cred)) {
197
+ const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.ino);
198
+ if (!node.toStats().hasAccess(flag.mode, cred)) {
270
199
  throw ApiError.EACCES(p);
271
200
  }
272
- if (data === undefined) {
201
+ if (!data) {
273
202
  throw ApiError.ENOENT(p);
274
203
  }
275
- return new AsyncKeyValueFile(this, p, flag, node.toStats(), data);
204
+ return new AsyncFile(this, p, flag, node.toStats(), data);
276
205
  }
277
206
  async unlink(p, cred) {
278
207
  return this.removeEntry(p, false, cred);
@@ -295,28 +224,22 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
295
224
  if (!node.toStats().hasAccess(R_OK, cred)) {
296
225
  throw ApiError.EACCES(p);
297
226
  }
298
- return Object.keys(await this.getDirListing(tx, p, node));
299
- }
300
- async chmod(p, mode, cred) {
301
- const fd = await this.openFile(p, FileFlag.getFileFlag('r+'), cred);
302
- await fd.chmod(mode);
227
+ return Object.keys(await this.getDirListing(tx, node, p));
303
228
  }
304
- async chown(p, new_uid, new_gid, cred) {
305
- const fd = await this.openFile(p, FileFlag.getFileFlag('r+'), cred);
306
- await fd.chown(new_uid, new_gid);
307
- }
308
- async _sync(p, data, stats) {
309
- // @todo Ensure mtime updates properly, and use that to determine if a data
310
- // update is required.
229
+ /**
230
+ * Updated the inode and data node at the given path
231
+ * @todo Ensure mtime updates properly, and use that to determine if a data update is required.
232
+ */
233
+ async sync(p, data, stats) {
311
234
  const tx = this.store.beginTransaction('readwrite'),
312
235
  // We use the _findInode helper because we actually need the INode id.
313
- fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx, p, fileInodeId), inodeChanged = fileInode.update(stats);
236
+ fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
314
237
  try {
315
238
  // Sync data.
316
- await tx.put(fileInode.id, data, true);
239
+ await tx.put(fileInode.ino, data, true);
317
240
  // Sync metadata.
318
241
  if (inodeChanged) {
319
- await tx.put(fileInodeId, fileInode.serialize(), true);
242
+ await tx.put(fileInodeId, fileInode.data, true);
320
243
  }
321
244
  }
322
245
  catch (e) {
@@ -325,20 +248,45 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
325
248
  }
326
249
  await tx.commit();
327
250
  }
251
+ async link(existing, newpath, cred) {
252
+ const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
253
+ if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
254
+ throw ApiError.EACCES(existingDir);
255
+ }
256
+ const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir);
257
+ if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
258
+ throw ApiError.EACCES(newDir);
259
+ }
260
+ const ino = await this._findINode(tx, existingDir, basename(existing));
261
+ const node = await this.getINode(tx, ino, existing);
262
+ if (!node.toStats().hasAccess(W_OK, cred)) {
263
+ throw ApiError.EACCES(newpath);
264
+ }
265
+ node.nlink++;
266
+ newListing[basename(newpath)] = ino;
267
+ try {
268
+ tx.put(ino, node.data, true);
269
+ tx.put(newDirNode.ino, encodeDirListing(newListing), true);
270
+ }
271
+ catch (e) {
272
+ tx.abort();
273
+ throw e;
274
+ }
275
+ tx.commit();
276
+ }
328
277
  /**
329
278
  * Checks if the root directory exists. Creates it if it doesn't.
330
279
  */
331
280
  async makeRootDirectory() {
332
281
  const tx = this.store.beginTransaction('readwrite');
333
- if ((await tx.get(ROOT_NODE_ID)) === undefined) {
334
- // Create new inode.
335
- const currTime = new Date().getTime(),
336
- // Mode 0666, owned by root:root
337
- dirInode = new Inode(randomUUID(), 4096, 511 | FileType.DIRECTORY, currTime, currTime, currTime, 0, 0);
282
+ if ((await tx.get(rootIno)) === undefined) {
283
+ // Create new inode. o777, owned by root:root
284
+ const dirInode = new Inode();
285
+ dirInode.mode = 0o777 | FileType.DIRECTORY;
338
286
  // If the root doesn't exist, the first random ID shouldn't exist,
339
287
  // either.
340
- await tx.put(dirInode.id, encode('{}'), false);
341
- await tx.put(ROOT_NODE_ID, dirInode.serialize(), false);
288
+ await tx.put(dirInode.ino, encode('{}'), false);
289
+ await tx.put(rootIno, dirInode.data, false);
342
290
  await tx.commit();
343
291
  }
344
292
  }
@@ -364,14 +312,14 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
364
312
  if (filename === '') {
365
313
  // BASE CASE #1: Return the root's ID.
366
314
  if (this._cache) {
367
- this._cache.set(currentPath, ROOT_NODE_ID);
315
+ this._cache.set(currentPath, rootIno);
368
316
  }
369
- return ROOT_NODE_ID;
317
+ return rootIno;
370
318
  }
371
319
  else {
372
320
  // BASE CASE #2: Find the item in the root node.
373
- const inode = await this.getINode(tx, parent, ROOT_NODE_ID);
374
- const dirList = await this.getDirListing(tx, parent, inode);
321
+ const inode = await this.getINode(tx, rootIno, parent);
322
+ const dirList = await this.getDirListing(tx, inode, parent);
375
323
  if (dirList[filename]) {
376
324
  const id = dirList[filename];
377
325
  if (this._cache) {
@@ -388,7 +336,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
388
336
  // Get the parent directory's INode, and find the file in its directory
389
337
  // listing.
390
338
  const inode = await this.findINode(tx, parent, visited);
391
- const dirList = await this.getDirListing(tx, parent, inode);
339
+ const dirList = await this.getDirListing(tx, inode, parent);
392
340
  if (dirList[filename]) {
393
341
  const id = dirList[filename];
394
342
  if (this._cache) {
@@ -408,7 +356,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
408
356
  */
409
357
  async findINode(tx, p, visited = new Set()) {
410
358
  const id = await this._findINode(tx, dirname(p), basename(p), visited);
411
- return this.getINode(tx, p, id);
359
+ return this.getINode(tx, id, p);
412
360
  }
413
361
  /**
414
362
  * Given the ID of a node, retrieves the corresponding Inode.
@@ -416,35 +364,35 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
416
364
  * @param p The corresponding path to the file (used for error messages).
417
365
  * @param id The ID to look up.
418
366
  */
419
- async getINode(tx, p, id) {
367
+ async getINode(tx, id, p) {
420
368
  const data = await tx.get(id);
421
369
  if (!data) {
422
370
  throw ApiError.ENOENT(p);
423
371
  }
424
- return Inode.Deserialize(data);
372
+ return new Inode(data.buffer);
425
373
  }
426
374
  /**
427
375
  * Given the Inode of a directory, retrieves the corresponding directory
428
376
  * listing.
429
377
  */
430
- async getDirListing(tx, p, inode) {
431
- if (!inode.isDirectory()) {
378
+ async getDirListing(tx, inode, p) {
379
+ if (!inode.toStats().isDirectory()) {
432
380
  throw ApiError.ENOTDIR(p);
433
381
  }
434
- const data = await tx.get(inode.id);
435
- try {
436
- return JSON.parse(decode(data));
437
- }
438
- catch (e) {
439
- // Occurs when data is undefined, or corresponds to something other
440
- // than a directory listing. The latter should never occur unless
441
- // the file system is corrupted.
382
+ const data = await tx.get(inode.ino);
383
+ if (!data) {
384
+ /*
385
+ Occurs when data is undefined, or corresponds to something other
386
+ than a directory listing. The latter should never occur unless
387
+ the file system is corrupted.
388
+ */
442
389
  throw ApiError.ENOENT(p);
443
390
  }
391
+ return decodeDirListing(data);
444
392
  }
445
393
  /**
446
394
  * Adds a new node under a random ID. Retries 5 times before giving up in
447
- * the exceedingly unlikely chance that we try to reuse a random GUID.
395
+ * the exceedingly unlikely chance that we try to reuse a random ino.
448
396
  */
449
397
  async addNewNode(tx, data) {
450
398
  let retries = 0;
@@ -455,13 +403,13 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
455
403
  }
456
404
  else {
457
405
  // Try again.
458
- const currId = randomUUID();
459
- const committed = await tx.put(currId, data, false);
406
+ const ino = randomIno();
407
+ const committed = await tx.put(ino, data, false);
460
408
  if (!committed) {
461
409
  return reroll();
462
410
  }
463
411
  else {
464
- return currId;
412
+ return ino;
465
413
  }
466
414
  }
467
415
  };
@@ -478,7 +426,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
478
426
  * @param data The data to store at the file's data node.
479
427
  */
480
428
  async commitNewFile(tx, p, type, mode, cred, data) {
481
- const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentDir, parentNode), currTime = new Date().getTime();
429
+ const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentNode, parentDir);
482
430
  //Check that the creater has correct access
483
431
  if (!parentNode.toStats().hasAccess(W_OK, cred)) {
484
432
  throw ApiError.EACCES(p);
@@ -496,15 +444,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
496
444
  }
497
445
  try {
498
446
  // Commit data.
499
- const dataId = await this.addNewNode(tx, data);
500
- const fileNode = new Inode(dataId, data.length, mode | type, currTime, currTime, currTime, cred.uid, cred.gid);
501
- // Commit file node.
502
- const fileNodeId = await this.addNewNode(tx, fileNode.serialize());
447
+ const inode = new Inode();
448
+ inode.ino = await this.addNewNode(tx, data);
449
+ inode.mode = mode | type;
450
+ inode.uid = cred.uid;
451
+ inode.gid = cred.gid;
452
+ inode.size = data.length;
503
453
  // Update and commit parent directory listing.
504
- dirListing[fname] = fileNodeId;
505
- await tx.put(parentNode.id, encode(JSON.stringify(dirListing)), true);
454
+ dirListing[fname] = await this.addNewNode(tx, inode.data);
455
+ await tx.put(parentNode.ino, encodeDirListing(dirListing), true);
506
456
  await tx.commit();
507
- return fileNode;
457
+ return inode;
508
458
  }
509
459
  catch (e) {
510
460
  tx.abort();
@@ -527,31 +477,31 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
527
477
  if (this._cache) {
528
478
  this._cache.remove(p);
529
479
  }
530
- const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx, parent, parentNode), fileName = basename(p);
480
+ 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);
531
481
  if (!parentListing[fileName]) {
532
482
  throw ApiError.ENOENT(p);
533
483
  }
534
- const fileNodeId = parentListing[fileName];
484
+ const fileIno = parentListing[fileName];
535
485
  // Get file inode.
536
- const fileNode = await this.getINode(tx, p, fileNodeId);
486
+ const fileNode = await this.getINode(tx, fileIno, p);
537
487
  if (!fileNode.toStats().hasAccess(W_OK, cred)) {
538
488
  throw ApiError.EACCES(p);
539
489
  }
540
490
  // Remove from directory listing of parent.
541
491
  delete parentListing[fileName];
542
- if (!isDir && fileNode.isDirectory()) {
492
+ if (!isDir && fileNode.toStats().isDirectory()) {
543
493
  throw ApiError.EISDIR(p);
544
494
  }
545
- else if (isDir && !fileNode.isDirectory()) {
495
+ if (isDir && !fileNode.toStats().isDirectory()) {
546
496
  throw ApiError.ENOTDIR(p);
547
497
  }
548
498
  try {
549
- // Delete data.
550
- await tx.del(fileNode.id);
551
- // Delete node.
552
- await tx.del(fileNodeId);
553
- // Update directory listing.
554
- await tx.put(parentNode.id, encode(JSON.stringify(parentListing)), true);
499
+ await tx.put(parentNode.ino, encodeDirListing(parentListing), true);
500
+ if (--fileNode.nlink < 1) {
501
+ // remove file
502
+ await tx.remove(fileNode.ino);
503
+ await tx.remove(fileIno);
504
+ }
555
505
  }
556
506
  catch (e) {
557
507
  await tx.abort();
@@ -1,24 +1,27 @@
1
- import { SyncKeyValueStore, SimpleSyncStore, SyncKeyValueRWTransaction, SyncKeyValueFileSystem } from './SyncStore.js';
2
- import { type BackendOptions } from './backend.js';
1
+ import type { Ino } from '../inode.js';
2
+ import { SyncStore, SimpleSyncStore, SyncRWTransaction, SyncStoreFileSystem } from './SyncStore.js';
3
+ import { type Backend } from './backend.js';
3
4
  /**
4
- * A simple in-memory key-value store backed by a JavaScript object.
5
+ * A simple in-memory store
5
6
  */
6
- export declare class InMemoryStore implements SyncKeyValueStore, SimpleSyncStore {
7
+ export declare class InMemoryStore implements SyncStore, SimpleSyncStore {
8
+ name: string;
7
9
  private store;
8
- name(): string;
10
+ constructor(name?: string);
9
11
  clear(): void;
10
- beginTransaction(type: string): SyncKeyValueRWTransaction;
11
- get(key: string): Uint8Array;
12
- put(key: string, data: Uint8Array, overwrite: boolean): boolean;
13
- del(key: string): void;
12
+ beginTransaction(): SyncRWTransaction;
13
+ get(key: Ino): Uint8Array;
14
+ put(key: Ino, data: Uint8Array, overwrite: boolean): boolean;
15
+ remove(key: Ino): void;
14
16
  }
17
+ export declare const InMemory: Backend;
15
18
  /**
16
19
  * A simple in-memory file system backed by an InMemoryStore.
17
20
  * Files are not persisted across page loads.
18
21
  */
19
- export declare class InMemoryFileSystem extends SyncKeyValueFileSystem {
20
- static readonly Name = "InMemory";
21
- static Create: any;
22
- static readonly Options: BackendOptions;
22
+ export declare class _InMemory extends SyncStoreFileSystem {
23
+ static isAvailable(): boolean;
24
+ static create: any;
25
+ static readonly options: {};
23
26
  constructor();
24
27
  }
@@ -1,20 +1,18 @@
1
1
  var _a;
2
- import { SimpleSyncRWTransaction, SyncKeyValueFileSystem } from './SyncStore.js';
3
- import { CreateBackend } from './backend.js';
2
+ import { SimpleSyncRWTransaction, SyncStoreFileSystem } from './SyncStore.js';
3
+ import { createBackend } from './backend.js';
4
4
  /**
5
- * A simple in-memory key-value store backed by a JavaScript object.
5
+ * A simple in-memory store
6
6
  */
7
7
  export class InMemoryStore {
8
- constructor() {
8
+ constructor(name = 'tmp') {
9
+ this.name = name;
9
10
  this.store = new Map();
10
11
  }
11
- name() {
12
- return InMemoryFileSystem.Name;
13
- }
14
12
  clear() {
15
13
  this.store.clear();
16
14
  }
17
- beginTransaction(type) {
15
+ beginTransaction() {
18
16
  return new SimpleSyncRWTransaction(this);
19
17
  }
20
18
  get(key) {
@@ -27,20 +25,37 @@ export class InMemoryStore {
27
25
  this.store.set(key, data);
28
26
  return true;
29
27
  }
30
- del(key) {
28
+ remove(key) {
31
29
  this.store.delete(key);
32
30
  }
33
31
  }
32
+ export const InMemory = {
33
+ name: 'InMemory',
34
+ isAvailable() {
35
+ return true;
36
+ },
37
+ options: {
38
+ name: {
39
+ type: 'string',
40
+ description: 'The name of the store',
41
+ },
42
+ },
43
+ create({ name }) {
44
+ return new SyncStoreFileSystem({ store: new InMemoryStore(name) });
45
+ },
46
+ };
34
47
  /**
35
48
  * A simple in-memory file system backed by an InMemoryStore.
36
49
  * Files are not persisted across page loads.
37
50
  */
38
- export class InMemoryFileSystem extends SyncKeyValueFileSystem {
51
+ export class _InMemory extends SyncStoreFileSystem {
52
+ static isAvailable() {
53
+ return true;
54
+ }
39
55
  constructor() {
40
56
  super({ store: new InMemoryStore() });
41
57
  }
42
58
  }
43
- _a = InMemoryFileSystem;
44
- InMemoryFileSystem.Name = 'InMemory';
45
- InMemoryFileSystem.Create = CreateBackend.bind(_a);
46
- InMemoryFileSystem.Options = {};
59
+ _a = _InMemory;
60
+ _InMemory.create = createBackend.bind(_a);
61
+ _InMemory.options = {};