Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose path of sublevel #78

Merged
merged 2 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
- [`sublevel.prefix`](#sublevelprefix)
- [`sublevel.parent`](#sublevelparent)
- [`sublevel.db`](#subleveldb)
- [`sublevel.path([local])`](#sublevelpathlocal)
- [Hooks](#hooks)
- [`hook = db.hooks.prewrite`](#hook--dbhooksprewrite)
- [Example](#example)
Expand Down Expand Up @@ -782,7 +783,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`

Expand Down Expand Up @@ -820,6 +821,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.**
Expand Down
1 change: 1 addition & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This document describes breaking changes and how to upgrade. For a complete list
- [1.2. Not found](#12-not-found)
- [1.3. Not ready](#13-not-ready)
- [1.4. Hooks](#14-hooks)
- [1.5. Open before creating a chained batch](#15-open-before-creating-a-chained-batch)
- [2. Private API](#2-private-api)
- [2.1. Promises all the way](#21-promises-all-the-way)
- [2.2. Ticks](#22-ticks)
Expand Down
8 changes: 8 additions & 0 deletions lib/abstract-sublevel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
30 changes: 29 additions & 1 deletion test/self/sublevel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})

Expand All @@ -109,46 +119,64 @@ 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()
})

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()
})

Expand Down
9 changes: 9 additions & 0 deletions types/abstract-sublevel.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ declare class AbstractSublevel<TDatabase, TFormat, KDefault, VDefault>
*/
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.
*/
Expand Down
Loading