Skip to content

Commit eb21a02

Browse files
authored
feat: reinitialize FS via 'init' and provide custom store names (#42)
1 parent d1c314c commit eb21a02

File tree

7 files changed

+100
-42
lines changed

7 files changed

+100
-42
lines changed

README.md

+30-9
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ While the mutex is being held by another thread, any fs operations will be stuck
4949
### `new FS(name, opts?)`
5050
First, create or open a "filesystem". (The name is used to determine the IndexedDb store name.)
5151

52-
```
52+
```js
5353
import FS from '@isomorphic-git/lightning-fs';
5454

5555
const fs = new FS("testfs")
@@ -59,11 +59,32 @@ const fs = new FS("testfs")
5959

6060
Options object:
6161

62-
| Param | Type [= default] | Description |
63-
| --------- | ------------------ | --------------------------------------------------------------------- |
64-
| `wipe` | boolean = false | Delete the database and start with an empty filesystem |
65-
| `url` | string = undefined | Let `readFile` requests fall back to an HTTP request to this base URL |
66-
| `urlauto` | boolean = false | Fall back to HTTP for every read of a missing file, even if unbacked |
62+
| Param | Type [= default] | Description |
63+
| --------------- | ------------------ | --------------------------------------------------------------------- |
64+
| `wipe` | boolean = false | Delete the database and start with an empty filesystem |
65+
| `url` | string = undefined | Let `readFile` requests fall back to an HTTP request to this base URL |
66+
| `urlauto` | boolean = false | Fall back to HTTP for every read of a missing file, even if unbacked |
67+
| `fileDbName` | string | Customize the database name |
68+
| `fileStoreName` | string | Customize the store name |
69+
| `lockDbName` | string | Customize the database name for the lock mutex |
70+
| `lockStoreName` | string | Customize the store name for the lock mutex |
71+
72+
#### Advanced usage
73+
74+
You can procrastinate initializing the FS object until later.
75+
And, if you're really adventurous, you can _re-initialize_ it with a different name to switch between IndexedDb databases.
76+
77+
```js
78+
import FS from '@isomorphic-git/lightning-fs';
79+
80+
const fs = new FS()
81+
82+
// Some time later...
83+
fs.init(name, options)
84+
85+
// Some time later...
86+
fs.init(different_name, different_options)
87+
```
6788

6889
### `fs.mkdir(filepath, opts?, cb)`
6990

@@ -157,9 +178,9 @@ Note that stat data is made automatically from the file `/.superblock.txt` if fo
157178

158179
Options object:
159180

160-
| Param | Type [= default] | Description |
161-
| ---------- | ------------------ | -------------------------------- |
162-
| `mode` | number = 0o666 | Posix mode permissions |
181+
| Param | Type [= default] | Description |
182+
| ------ | ---------------- | ---------------------- |
183+
| `mode` | number = 0o666 | Posix mode permissions |
163184

164185
### `fs.promises`
165186

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"semantic-release": "semantic-release"
1717
},
1818
"dependencies": {
19-
"@isomorphic-git/idb-keyval": "^3.3.1",
19+
"@isomorphic-git/idb-keyval": "3.3.2",
2020
"isomorphic-textencoder": "1.0.1",
2121
"just-debounce-it": "1.1.0",
2222
"just-once": "1.1.0"

src/IdbBackend.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
const idb = require("@isomorphic-git/idb-keyval");
22

33
module.exports = class IdbBackend {
4-
constructor(name) {
5-
this._database = name;
6-
this._store = new idb.Store(this._database, this._database + "_files");
4+
constructor(dbname, storename) {
5+
this._database = dbname;
6+
this._storename = storename;
7+
this._store = new idb.Store(this._database, this._storename);
78
}
89
saveSuperblock(superblock) {
910
return idb.set("!root", superblock, this._store);

src/Mutex.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ const idb = require("@isomorphic-git/idb-keyval");
33
const sleep = ms => new Promise(r => setTimeout(r, ms))
44

55
module.exports = class Mutex {
6-
constructor(name) {
6+
constructor(dbname, storename) {
77
this._id = Math.random()
8-
this._database = name
9-
this._store = new idb.Store(this._database + "_lock", this._database + "_lock")
8+
this._database = dbname
9+
this._storename = storename
10+
this._store = new idb.Store(this._database, this._storename)
1011
this._lock = null
1112
}
1213
async has ({ margin = 2000 } = {}) {

src/PromisifiedFS.js

+55-23
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,8 @@ function cleanParams2(oldFilepath, newFilepath) {
3434
}
3535

3636
module.exports = class PromisifiedFS {
37-
constructor(name, { wipe, url, urlauto } = {}) {
38-
this._name = name
39-
this._idb = new IdbBackend(name);
40-
this._mutex = navigator.locks ? new Mutex2(name) : new Mutex(name);
41-
this._cache = new CacheFS(name);
42-
this._opts = { wipe, url };
43-
this._needsWipe = !!wipe;
44-
this.saveSuperblock = debounce(() => {
45-
this._saveSuperblock();
46-
}, 500);
47-
if (url) {
48-
this._http = new HttpBackend(url)
49-
this._urlauto = !!urlauto
50-
}
51-
this._operations = new Set()
52-
37+
constructor(name, options) {
38+
this.init = this.init.bind(this)
5339
this.readFile = this._wrap(this.readFile, false)
5440
this.writeFile = this._wrap(this.writeFile, true)
5541
this.unlink = this._wrap(this.unlink, true)
@@ -63,17 +49,60 @@ module.exports = class PromisifiedFS {
6349
this.symlink = this._wrap(this.symlink, true)
6450
this.backFile = this._wrap(this.backFile, true)
6551

52+
this.saveSuperblock = debounce(() => {
53+
this._saveSuperblock();
54+
}, 500);
55+
6656
this._deactivationPromise = null
6757
this._deactivationTimeout = null
6858
this._activationPromise = null
59+
60+
this._operations = new Set()
61+
62+
if (name) {
63+
this.init(name, options)
64+
}
65+
}
66+
async init (...args) {
67+
if (this._initPromiseResolve) await this._initPromise;
68+
this._initPromise = this._init(...args)
69+
return this._initPromise
70+
}
71+
async _init (name, {
72+
wipe,
73+
url,
74+
urlauto,
75+
fileDbName = name,
76+
fileStoreName = name + "_files",
77+
lockDbName = name + "_lock",
78+
lockStoreName = name + "_lock",
79+
} = {}) {
80+
await this._gracefulShutdown()
81+
this._name = name
82+
this._idb = new IdbBackend(fileDbName, fileStoreName);
83+
this._mutex = navigator.locks ? new Mutex2(name) : new Mutex(lockDbName, lockStoreName);
84+
this._cache = new CacheFS(name);
85+
this._opts = { wipe, url };
86+
this._needsWipe = !!wipe;
87+
if (url) {
88+
this._http = new HttpBackend(url)
89+
this._urlauto = !!urlauto
90+
}
91+
if (this._initPromiseResolve) {
92+
this._initPromiseResolve();
93+
this._initPromiseResolve = null;
94+
}
6995
// The fs is initially activated when constructed (in order to wipe/save the superblock)
70-
// but there might not be any other fs operations needed until later. Therefore we
71-
// need to attempt to release the mutex
72-
this._activate().then(() => {
73-
if (this._operations.size === 0 && !this._deactivationTimeout) {
74-
this._deactivationTimeout = setTimeout(this._deactivate.bind(this), 100)
75-
}
76-
})
96+
// This is not awaited, because that would create a cycle.
97+
this.stat('/')
98+
}
99+
async _gracefulShutdown () {
100+
if (this._operations.size > 0) {
101+
this._isShuttingDown = true
102+
await new Promise(resolve => this._gracefulShutdownResolve = resolve);
103+
this._isShuttingDown = false
104+
this._gracefulShutdownResolve = null
105+
}
77106
}
78107
_wrap (fn, mutating) {
79108
let i = 0
@@ -97,6 +126,8 @@ module.exports = class PromisifiedFS {
97126
}
98127
}
99128
async _activate() {
129+
if (!this._initPromise) console.warn(new Error(`Attempted to use LightningFS ${this._name} before it was initialized.`))
130+
await this._initPromise
100131
if (this._deactivationTimeout) {
101132
clearTimeout(this._deactivationTimeout)
102133
this._deactivationTimeout = null
@@ -138,6 +169,7 @@ module.exports = class PromisifiedFS {
138169
if (this._activationPromise) await this._activationPromise
139170
if (!this._deactivationPromise) this._deactivationPromise = this.__deactivate()
140171
this._activationPromise = null
172+
if (this._gracefulShutdownResolve) this._gracefulShutdownResolve()
141173
return this._deactivationPromise
142174
}
143175
async __deactivate() {

src/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ module.exports = class FS {
2828
this.symlink = this.symlink.bind(this)
2929
this.backFile = this.backFile.bind(this)
3030
}
31+
init(name, options) {
32+
this.promises.init(name, options)
33+
}
3134
readFile(filepath, opts, cb) {
3235
const [resolve, reject] = wrapCallback(opts, cb);
3336
this.promises.readFile(filepath, opts).then(resolve).catch(reject)

0 commit comments

Comments
 (0)