diff --git a/README.md b/README.md index 5b6010e..7f50fac 100644 --- a/README.md +++ b/README.md @@ -392,7 +392,7 @@ The optional `options` object may contain: The `keyEncoding` and `valueEncoding` options are forwarded to the `AbstractLevel` constructor and work the same, as if a new, separate database was created. They default to `'utf8'` regardless of the encodings configured on `db`. Other options are forwarded too but `abstract-level` has no relevant options at the time of writing. For example, setting the `createIfMissing` option will have no effect. Why is that? -Like regular databases, sublevels open themselves but they do not affect the state of the parent database. This means a sublevel can be individually closed and (re)opened. If the sublevel is created while the parent database is opening, it will wait for that to finish. If the parent database is closed, then opening the sublevel will fail and subsequent operations on the sublevel will yield errors with code [`LEVEL_DATABASE_NOT_OPEN`](#errors). +Like regular databases, sublevels open themselves, but they do not affect the state of the parent database. This means a sublevel can be individually closed and (re)opened. If the sublevel is created while the parent database is opening, it will wait for that to finish. Closing the parent database will automatically close the sublevel, along with other resources like iterators. Lastly, the `name` argument can be an array as a shortcut to create nested sublevels. Those are normally created like so: diff --git a/lib/abstract-sublevel.js b/lib/abstract-sublevel.js index 52fc1b4..6b102b1 100644 --- a/lib/abstract-sublevel.js +++ b/lib/abstract-sublevel.js @@ -33,7 +33,6 @@ module.exports = function ({ AbstractLevel }) { } } - // TODO: add autoClose option, which if true, does parent.attachResource(this) constructor (db, name, options) { // Don't forward AbstractSublevel options to AbstractLevel const { separator, manifest, ...forward } = AbstractSublevel.defaults(options) @@ -130,7 +129,14 @@ module.exports = function ({ AbstractLevel }) { async _open (options) { // The parent db must open itself or be (re)opened by the user because // a sublevel should not initiate state changes on the rest of the db. - return this.#parent.open({ passive: true }) + await this.#parent.open({ passive: true }) + + // Close sublevel when parent is closed + this.#parent.attachResource(this) + } + + async _close () { + this.#parent.detachResource(this) } async _put (key, value, options) { diff --git a/test/self/sublevel-test.js b/test/self/sublevel-test.js index cb82b43..b390542 100644 --- a/test/self/sublevel-test.js +++ b/test/self/sublevel-test.js @@ -358,6 +358,27 @@ test('opening & closing sublevel', function (t) { await sub.close() }) + t.test('sublevel is closed by parent', async function (t) { + t.plan(4) + + const db = new NoopLevel() + await db.open() + const sub = db.sublevel('test') + + await db.open() + await sub.open() + + const promise = db.close() + + t.is(db.status, 'closing') + t.is(sub.status, 'closing') + + await promise + + t.is(db.status, 'closed') + t.is(sub.status, 'closed') + }) + t.test('sublevel rejects operations if parent db is closed', async function (t) { t.plan(6) @@ -401,7 +422,8 @@ test('opening & closing sublevel', function (t) { const promises = [ db.close().then(async function () { - // TODO: implement autoClose option (see AbstractSublevel) + // Ideally it'd be 'closed' but it's still 'opening' at this point. + // TODO: use a signal to abort the open() to transition to 'closed' faster // t.is(sub.status, 'closed') t.is(db.status, 'closed')