Level / abstract-level

Abstract class for a lexicographically sorted key-value database.
MIT License
128 stars 8 forks source link

Expose name and/or local prefix of sublevel #61

Closed vweevers closed 9 months ago

vweevers commented 1 year ago

Context

I'm working on a rewrite of level-ttl, to make it use hooks and find out what gaps we in that API. Say a plugin like that wants to add methods to a db and also to (deeply) nested sublevels that the user might create. For optimal performance, the plugin wishes to bypass intermediate sublevels on its own operations. E.g.:

module.exports = function ttlPlugin (db) {
  // Skipping encodings etc for brevity
  const main = db.sublevel('main')
  const expiryIndex = db.sublevel('expiryIndex')

  db.hooks.prewrite.add(function (op, batch) {
    const expiry = Date.now() + op.ttl
    batch.add({ type: 'put', sublevel: expiryIndex, key: op.key, value: expiry })
  })

  const addMethods = (sub, path) => {
    // Bypass intermediate sublevels
    const proxy = db.sublevel(['expiryIndex', ...path])

    sub.getTTL = async (key) => {
      const expiry = await proxy.get(key)
      return expiry - Date.now()
    }

    // Offer the same API on nested sublevels
    sub.hooks.newsub.add((child) => {
      // Here we need the name or prefix of the child sublevel
      addMethods(child, [...path, child.xxx])
    })
  }

  addMethods(main, ['main'])
  return main
}

Usage:

const db = new Level()
const cache = ttlPlugin(db)
const html = cache.sublevel(['templates', 'html'])
await html.put('my-template', '<html></html>', { ttl: 100 })
const ttl = await html.getTTL('my-template') // 100 (or less)

Problem

There's no suitable value for child.xxx atm. We don't expose the name(s) of a sublevel (e.g. 'templates' and 'html') and even if we did, the sublevel might be using custom separators. We do expose the prefix of a sublevel (e.g. !templates!!html!). But passing this along to .sublevel() would only work one level deep, because the AbstractSublevel constructor trims separators from e.g. .sublevel('!templates!) resulting in a valid name, but .sublevel('!templates!!html!') results in a name that's 'templates!!html'. Which is then rejected by:

https://github.com/Level/abstract-level/blob/69c5ce7350e6d77eaf07158e23d77f4b84459e46/lib/abstract-sublevel.js#L47-L50

vweevers commented 1 year ago

A simple solution is to change the hook signature from:

db.hooks.newsub.add(function (sub, opts) {})

To:

db.hooks.newsub.add(function (sub, name, opts) {})

The plugin can then do:

db.hooks.newsub.add(function (sub, name, opts) {
  if (opts.separator !== '!') {
    throw new Error('I do not support custom separators, sorry')
  }

  // ..
})
vweevers commented 9 months ago

Done in https://github.com/Level/abstract-level/pull/78 (didn't auto-close because it's not on the default branch).