diff --git a/README.md b/README.md index ab7d39b..35bd714 100644 --- a/README.md +++ b/README.md @@ -782,7 +782,7 @@ A value iterator has the same interface as `iterator` except that its methods yi ### `sublevel` -A sublevel is an instance of the `AbstractSublevel` class, which extends `AbstractLevel` and thus has the same API as documented above. Sublevels have a few additional properties. +A sublevel is an instance of the `AbstractSublevel` class, which extends `AbstractLevel` and thus has the same API as documented above. Sublevels have a few additional properties and methods. #### `sublevel.prefix` @@ -820,6 +820,24 @@ console.log(example.db === db) // true console.log(nested.db === db) // true ``` +#### `sublevel.path([local])` + +Get the path of this sublevel, which is its prefix without separators. If `local` is true, exclude path of parent database. If false (the default) then recurse to form a fully-qualified path that travels from the root database to this sublevel. + +```js +const example = db.sublevel('example') +const nested = example.sublevel('nested') +const foo = db.sublevel(['example', 'nested', 'foo']) + +// Get global or local path +console.log(nested.path()) // ['example', 'nested'] +console.log(nested.path(true)) // ['nested'] + +// Has no intermediary sublevels, so the local option has no effect +console.log(foo.path()) // ['example', 'nested', 'foo'] +console.log(foo.path(true)) // ['example', 'nested', 'foo'] +``` + ### Hooks **Hooks are experimental and subject to change without notice.** diff --git a/lib/abstract-sublevel.js b/lib/abstract-sublevel.js index b5b0ffe..fa75014 100644 --- a/lib/abstract-sublevel.js +++ b/lib/abstract-sublevel.js @@ -10,6 +10,8 @@ const { const kGlobalPrefix = Symbol('prefix') const kLocalPrefix = Symbol('localPrefix') +const kLocalPath = Symbol('localPath') +const kGlobalPath = Symbol('globalPath') const kGlobalUpperBound = Symbol('upperBound') const kPrefixRange = Symbol('prefixRange') const kRoot = Symbol('root') @@ -61,6 +63,8 @@ module.exports = function ({ AbstractLevel }) { // to change, until we add some form of preread or postread hooks. this[kRoot] = root this[kParent] = db + this[kLocalPath] = names + this[kGlobalPath] = db.prefix ? db.path().concat(names) : names this[kGlobalPrefix] = new MultiFormat(globalPrefix) this[kGlobalUpperBound] = new MultiFormat(globalUpperBound) this[kLocalPrefix] = new MultiFormat(localPrefix) @@ -120,6 +124,10 @@ module.exports = function ({ AbstractLevel }) { return this[kParent] } + path (local = false) { + return local ? this[kLocalPath] : this[kGlobalPath] + } + 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. diff --git a/test/self/sublevel-test.js b/test/self/sublevel-test.js index 5d25d30..d4f8063 100644 --- a/test/self/sublevel-test.js +++ b/test/self/sublevel-test.js @@ -75,26 +75,36 @@ test('sublevel prefix and options', function (t) { t.test('empty prefix', function (t) { const sub = new NoopLevel().sublevel('') t.is(sub.prefix, '!!') + t.same(sub.path(), ['']) t.end() }) t.test('prefix without options', function (t) { const sub = new NoopLevel().sublevel('prefix') t.is(sub.prefix, '!prefix!') + t.same(sub.path(), ['prefix']) t.end() }) t.test('prefix and separator option', function (t) { const sub = new NoopLevel().sublevel('prefix', { separator: '%' }) t.is(sub.prefix, '%prefix%') + t.same(sub.path(), ['prefix']) t.end() }) t.test('array name', function (t) { const sub = new NoopLevel().sublevel(['a', 'b']) - t.is(sub.prefix, '!a!!b!') const alt = new NoopLevel().sublevel('a').sublevel('b') + + t.is(sub.prefix, '!a!!b!') + t.same(sub.path(), ['a', 'b']) + t.same(sub.path(true), ['a', 'b']) + t.is(alt.prefix, sub.prefix) + t.same(alt.path(), ['a', 'b']) + t.same(alt.path(true), ['b']) + t.end() }) @@ -109,37 +119,51 @@ test('sublevel prefix and options', function (t) { t.test('array name with single element', function (t) { const sub = new NoopLevel().sublevel(['a']) t.is(sub.prefix, '!a!') + t.same(sub.path(), ['a']) + const alt = new NoopLevel().sublevel('a') t.is(alt.prefix, sub.prefix) + t.same(sub.path(), alt.path()) + t.end() }) t.test('array name and separator option', function (t) { const sub = new NoopLevel().sublevel(['a', 'b'], { separator: '%' }) t.is(sub.prefix, '%a%%b%') + t.same(sub.path(), ['a', 'b']) + const alt = new NoopLevel().sublevel('a', { separator: '%' }).sublevel('b', { separator: '%' }) t.is(alt.prefix, sub.prefix) + t.same(alt.path(), ['a', 'b']) + t.end() }) t.test('separator is trimmed from prefix', function (t) { const sub1 = new NoopLevel().sublevel('!prefix') t.is(sub1.prefix, '!prefix!') + t.same(sub1.path(), ['prefix']) const sub2 = new NoopLevel().sublevel('prefix!') t.is(sub2.prefix, '!prefix!') + t.same(sub2.path(), ['prefix']) const sub3 = new NoopLevel().sublevel('!!prefix!!') t.is(sub3.prefix, '!prefix!') + t.same(sub3.path(), ['prefix']) const sub4 = new NoopLevel().sublevel('@prefix@', { separator: '@' }) t.is(sub4.prefix, '@prefix@') + t.same(sub4.path(), ['prefix']) const sub5 = new NoopLevel().sublevel(['!!!a', 'b!!!']) t.is(sub5.prefix, '!a!!b!') + t.same(sub5.path(), ['a', 'b']) const sub6 = new NoopLevel().sublevel(['a@@@', '@@@b'], { separator: '@' }) t.is(sub6.prefix, '@a@@b@') + t.same(sub6.path(), ['a', 'b']) t.end() }) @@ -147,8 +171,12 @@ test('sublevel prefix and options', function (t) { t.test('repeated separator can not result in empty prefix', function (t) { const sub1 = new NoopLevel().sublevel('!!!!') t.is(sub1.prefix, '!!') + t.same(sub1.path(), ['']) + const sub2 = new NoopLevel().sublevel(['!!!!', '!!!!']) t.is(sub2.prefix, '!!!!') + t.same(sub2.path(), ['', '']) + t.end() }) diff --git a/types/abstract-sublevel.d.ts b/types/abstract-sublevel.d.ts index 345344a..014fd6e 100644 --- a/types/abstract-sublevel.d.ts +++ b/types/abstract-sublevel.d.ts @@ -26,6 +26,15 @@ declare class AbstractSublevel */ get prefix (): string + /** + * Get the path of the sublevel, which is its prefix without separators. + * + * @param local If true, exclude path of parent database. If false (the default) then + * recurse to form a fully-qualified path that travels from the root database to this + * sublevel. + */ + path (local?: boolean | undefined): string[] + /** * Parent database. A read-only property. */