@zenfs/core 0.1.0 → 0.2.1

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 +40 -29
  8. package/dist/backends/AsyncStore.js +144 -192
  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,73 +52,55 @@ 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
- constructor(cacheSize) {
76
+ constructor({ store, cacheSize }) {
125
77
  super();
126
- this._cache = null;
127
78
  if (cacheSize > 0) {
128
79
  this._cache = new LRUCache(cacheSize);
129
80
  }
81
+ this._ready = this._initialize(store);
130
82
  }
131
83
  /**
132
84
  * Initializes the file system. Typically called by subclasses' async
133
85
  * constructors.
134
86
  */
135
- async init(store) {
136
- this.store = store;
87
+ async _initialize(store) {
88
+ this.store = await store;
137
89
  // INVARIANT: Ensure that the root exists.
138
90
  await this.makeRootDirectory();
139
- }
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;
91
+ return this;
154
92
  }
155
93
  /**
156
94
  * Delete all contents stored in the file system.
157
95
  */
158
96
  async empty() {
159
97
  if (this._cache) {
160
- this._cache.removeAll();
98
+ this._cache.reset();
161
99
  }
162
100
  await this.store.clear();
163
101
  // INVARIANT: Root always exists.
164
102
  await this.makeRootDirectory();
165
103
  }
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
104
  /**
177
105
  * @todo Make rename compatible with the cache.
178
106
  */
@@ -181,12 +109,12 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
181
109
  if (this._cache) {
182
110
  // Clear and disable cache during renaming process.
183
111
  this._cache = null;
184
- c.removeAll();
112
+ c.reset();
185
113
  }
186
114
  try {
187
115
  const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
188
116
  // Remove oldPath from parent's directory listing.
189
- oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldParent, oldDirNode);
117
+ oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
190
118
  if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
191
119
  throw ApiError.EACCES(oldPath);
192
120
  }
@@ -212,15 +140,15 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
212
140
  }
213
141
  else {
214
142
  newDirNode = await this.findINode(tx, newParent);
215
- newDirList = await this.getDirListing(tx, newParent, newDirNode);
143
+ newDirList = await this.getDirListing(tx, newDirNode, newParent);
216
144
  }
217
145
  if (newDirList[newName]) {
218
146
  // If it's a file, delete it.
219
- const newNameNode = await this.getINode(tx, newPath, newDirList[newName]);
220
- if (newNameNode.isFile()) {
147
+ const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
148
+ if (newNameNode.toStats().isFile()) {
221
149
  try {
222
- await tx.del(newNameNode.id);
223
- await tx.del(newDirList[newName]);
150
+ await tx.remove(newNameNode.ino);
151
+ await tx.remove(newDirList[newName]);
224
152
  }
225
153
  catch (e) {
226
154
  await tx.abort();
@@ -235,8 +163,8 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
235
163
  newDirList[newName] = nodeId;
236
164
  // Commit the two changed directory listings.
237
165
  try {
238
- await tx.put(oldDirNode.id, encode(JSON.stringify(oldDirList)), true);
239
- await tx.put(newDirNode.id, encode(JSON.stringify(newDirList)), true);
166
+ await tx.put(oldDirNode.ino, encodeDirListing(oldDirList), true);
167
+ await tx.put(newDirNode.ino, encodeDirListing(newDirList), true);
240
168
  }
241
169
  catch (e) {
242
170
  await tx.abort();
@@ -253,6 +181,9 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
253
181
  async stat(p, cred) {
254
182
  const tx = this.store.beginTransaction('readonly');
255
183
  const inode = await this.findINode(tx, p);
184
+ if (!inode) {
185
+ throw ApiError.ENOENT(p);
186
+ }
256
187
  const stats = inode.toStats();
257
188
  if (!stats.hasAccess(R_OK, cred)) {
258
189
  throw ApiError.EACCES(p);
@@ -262,17 +193,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
262
193
  async createFile(p, flag, mode, cred) {
263
194
  const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
264
195
  // Open the file.
265
- return new AsyncKeyValueFile(this, p, flag, newFile.toStats(), data);
196
+ return new AsyncFile(this, p, flag, newFile.toStats(), data);
266
197
  }
267
198
  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)) {
199
+ const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.ino);
200
+ if (!node.toStats().hasAccess(flag.mode, cred)) {
270
201
  throw ApiError.EACCES(p);
271
202
  }
272
- if (data === undefined) {
203
+ if (!data) {
273
204
  throw ApiError.ENOENT(p);
274
205
  }
275
- return new AsyncKeyValueFile(this, p, flag, node.toStats(), data);
206
+ return new AsyncFile(this, p, flag, node.toStats(), data);
276
207
  }
277
208
  async unlink(p, cred) {
278
209
  return this.removeEntry(p, false, cred);
@@ -295,28 +226,22 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
295
226
  if (!node.toStats().hasAccess(R_OK, cred)) {
296
227
  throw ApiError.EACCES(p);
297
228
  }
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);
229
+ return Object.keys(await this.getDirListing(tx, node, p));
303
230
  }
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.
231
+ /**
232
+ * Updated the inode and data node at the given path
233
+ * @todo Ensure mtime updates properly, and use that to determine if a data update is required.
234
+ */
235
+ async sync(p, data, stats) {
311
236
  const tx = this.store.beginTransaction('readwrite'),
312
237
  // 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);
238
+ fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
314
239
  try {
315
240
  // Sync data.
316
- await tx.put(fileInode.id, data, true);
241
+ await tx.put(fileInode.ino, data, true);
317
242
  // Sync metadata.
318
243
  if (inodeChanged) {
319
- await tx.put(fileInodeId, fileInode.serialize(), true);
244
+ await tx.put(fileInodeId, fileInode.data, true);
320
245
  }
321
246
  }
322
247
  catch (e) {
@@ -325,20 +250,45 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
325
250
  }
326
251
  await tx.commit();
327
252
  }
253
+ async link(existing, newpath, cred) {
254
+ const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
255
+ if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
256
+ throw ApiError.EACCES(existingDir);
257
+ }
258
+ const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir);
259
+ if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
260
+ throw ApiError.EACCES(newDir);
261
+ }
262
+ const ino = await this._findINode(tx, existingDir, basename(existing));
263
+ const node = await this.getINode(tx, ino, existing);
264
+ if (!node.toStats().hasAccess(W_OK, cred)) {
265
+ throw ApiError.EACCES(newpath);
266
+ }
267
+ node.nlink++;
268
+ newListing[basename(newpath)] = ino;
269
+ try {
270
+ tx.put(ino, node.data, true);
271
+ tx.put(newDirNode.ino, encodeDirListing(newListing), true);
272
+ }
273
+ catch (e) {
274
+ tx.abort();
275
+ throw e;
276
+ }
277
+ tx.commit();
278
+ }
328
279
  /**
329
280
  * Checks if the root directory exists. Creates it if it doesn't.
330
281
  */
331
282
  async makeRootDirectory() {
332
283
  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);
284
+ if ((await tx.get(rootIno)) === undefined) {
285
+ // Create new inode. o777, owned by root:root
286
+ const dirInode = new Inode();
287
+ dirInode.mode = 0o777 | FileType.DIRECTORY;
338
288
  // If the root doesn't exist, the first random ID shouldn't exist,
339
289
  // either.
340
- await tx.put(dirInode.id, encode('{}'), false);
341
- await tx.put(ROOT_NODE_ID, dirInode.serialize(), false);
290
+ await tx.put(dirInode.ino, encode('{}'), false);
291
+ await tx.put(rootIno, dirInode.data, false);
342
292
  await tx.commit();
343
293
  }
344
294
  }
@@ -364,14 +314,14 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
364
314
  if (filename === '') {
365
315
  // BASE CASE #1: Return the root's ID.
366
316
  if (this._cache) {
367
- this._cache.set(currentPath, ROOT_NODE_ID);
317
+ this._cache.set(currentPath, rootIno);
368
318
  }
369
- return ROOT_NODE_ID;
319
+ return rootIno;
370
320
  }
371
321
  else {
372
322
  // 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);
323
+ const inode = await this.getINode(tx, rootIno, parent);
324
+ const dirList = await this.getDirListing(tx, inode, parent);
375
325
  if (dirList[filename]) {
376
326
  const id = dirList[filename];
377
327
  if (this._cache) {
@@ -388,7 +338,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
388
338
  // Get the parent directory's INode, and find the file in its directory
389
339
  // listing.
390
340
  const inode = await this.findINode(tx, parent, visited);
391
- const dirList = await this.getDirListing(tx, parent, inode);
341
+ const dirList = await this.getDirListing(tx, inode, parent);
392
342
  if (dirList[filename]) {
393
343
  const id = dirList[filename];
394
344
  if (this._cache) {
@@ -408,7 +358,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
408
358
  */
409
359
  async findINode(tx, p, visited = new Set()) {
410
360
  const id = await this._findINode(tx, dirname(p), basename(p), visited);
411
- return this.getINode(tx, p, id);
361
+ return this.getINode(tx, id, p);
412
362
  }
413
363
  /**
414
364
  * Given the ID of a node, retrieves the corresponding Inode.
@@ -416,35 +366,35 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
416
366
  * @param p The corresponding path to the file (used for error messages).
417
367
  * @param id The ID to look up.
418
368
  */
419
- async getINode(tx, p, id) {
369
+ async getINode(tx, id, p) {
420
370
  const data = await tx.get(id);
421
371
  if (!data) {
422
372
  throw ApiError.ENOENT(p);
423
373
  }
424
- return Inode.Deserialize(data);
374
+ return new Inode(data.buffer);
425
375
  }
426
376
  /**
427
377
  * Given the Inode of a directory, retrieves the corresponding directory
428
378
  * listing.
429
379
  */
430
- async getDirListing(tx, p, inode) {
431
- if (!inode.isDirectory()) {
380
+ async getDirListing(tx, inode, p) {
381
+ if (!inode.toStats().isDirectory()) {
432
382
  throw ApiError.ENOTDIR(p);
433
383
  }
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.
384
+ const data = await tx.get(inode.ino);
385
+ if (!data) {
386
+ /*
387
+ Occurs when data is undefined, or corresponds to something other
388
+ than a directory listing. The latter should never occur unless
389
+ the file system is corrupted.
390
+ */
442
391
  throw ApiError.ENOENT(p);
443
392
  }
393
+ return decodeDirListing(data);
444
394
  }
445
395
  /**
446
396
  * 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.
397
+ * the exceedingly unlikely chance that we try to reuse a random ino.
448
398
  */
449
399
  async addNewNode(tx, data) {
450
400
  let retries = 0;
@@ -455,13 +405,13 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
455
405
  }
456
406
  else {
457
407
  // Try again.
458
- const currId = randomUUID();
459
- const committed = await tx.put(currId, data, false);
408
+ const ino = randomIno();
409
+ const committed = await tx.put(ino, data, false);
460
410
  if (!committed) {
461
411
  return reroll();
462
412
  }
463
413
  else {
464
- return currId;
414
+ return ino;
465
415
  }
466
416
  }
467
417
  };
@@ -478,7 +428,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
478
428
  * @param data The data to store at the file's data node.
479
429
  */
480
430
  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();
431
+ const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentNode, parentDir);
482
432
  //Check that the creater has correct access
483
433
  if (!parentNode.toStats().hasAccess(W_OK, cred)) {
484
434
  throw ApiError.EACCES(p);
@@ -496,15 +446,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
496
446
  }
497
447
  try {
498
448
  // 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());
449
+ const inode = new Inode();
450
+ inode.ino = await this.addNewNode(tx, data);
451
+ inode.mode = mode | type;
452
+ inode.uid = cred.uid;
453
+ inode.gid = cred.gid;
454
+ inode.size = data.length;
503
455
  // Update and commit parent directory listing.
504
- dirListing[fname] = fileNodeId;
505
- await tx.put(parentNode.id, encode(JSON.stringify(dirListing)), true);
456
+ dirListing[fname] = await this.addNewNode(tx, inode.data);
457
+ await tx.put(parentNode.ino, encodeDirListing(dirListing), true);
506
458
  await tx.commit();
507
- return fileNode;
459
+ return inode;
508
460
  }
509
461
  catch (e) {
510
462
  tx.abort();
@@ -527,31 +479,31 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
527
479
  if (this._cache) {
528
480
  this._cache.remove(p);
529
481
  }
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);
482
+ 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
483
  if (!parentListing[fileName]) {
532
484
  throw ApiError.ENOENT(p);
533
485
  }
534
- const fileNodeId = parentListing[fileName];
486
+ const fileIno = parentListing[fileName];
535
487
  // Get file inode.
536
- const fileNode = await this.getINode(tx, p, fileNodeId);
488
+ const fileNode = await this.getINode(tx, fileIno, p);
537
489
  if (!fileNode.toStats().hasAccess(W_OK, cred)) {
538
490
  throw ApiError.EACCES(p);
539
491
  }
540
492
  // Remove from directory listing of parent.
541
493
  delete parentListing[fileName];
542
- if (!isDir && fileNode.isDirectory()) {
494
+ if (!isDir && fileNode.toStats().isDirectory()) {
543
495
  throw ApiError.EISDIR(p);
544
496
  }
545
- else if (isDir && !fileNode.isDirectory()) {
497
+ if (isDir && !fileNode.toStats().isDirectory()) {
546
498
  throw ApiError.ENOTDIR(p);
547
499
  }
548
500
  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);
501
+ await tx.put(parentNode.ino, encodeDirListing(parentListing), true);
502
+ if (--fileNode.nlink < 1) {
503
+ // remove file
504
+ await tx.remove(fileNode.ino);
505
+ await tx.remove(fileIno);
506
+ }
555
507
  }
556
508
  catch (e) {
557
509
  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
  }