datastore-core
Version:
Wrapper implementation for interface-datastore
154 lines (131 loc) • 4.27 kB
text/typescript
import { DeleteFailedError, NotFoundError, PutFailedError } from 'interface-store'
import filter from 'it-filter'
import merge from 'it-merge'
import sort from 'it-sort'
import take from 'it-take'
import { BaseDatastore } from './base.js'
import type { Batch, Datastore, Key, KeyQuery, Pair, Query } from 'interface-datastore'
import type { AbortOptions } from 'interface-store'
/**
* A datastore that can combine multiple stores inside various
* key prefixes
*/
export class MountDatastore extends BaseDatastore {
private readonly mounts: Array<{ prefix: Key, datastore: Datastore }>
constructor (mounts: Array<{ prefix: Key, datastore: Datastore }>) {
super()
this.mounts = mounts.slice()
}
/**
* Lookup the matching datastore for the given key
*/
private _lookup (key: Key): { datastore: Datastore, mountpoint: Key } | undefined {
for (const mount of this.mounts) {
if (mount.prefix.toString() === key.toString() || mount.prefix.isAncestorOf(key)) {
return {
datastore: mount.datastore,
mountpoint: mount.prefix
}
}
}
}
async put (key: Key, value: Uint8Array, options?: AbortOptions): Promise<Key> {
const match = this._lookup(key)
if (match == null) {
throw new PutFailedError('No datastore mounted for this key')
}
await match.datastore.put(key, value, options)
return key
}
/**
* @param {Key} key
* @param {Options} [options]
*/
async get (key: Key, options: AbortOptions = {}): Promise<Uint8Array> {
const match = this._lookup(key)
if (match == null) {
throw new NotFoundError('No datastore mounted for this key')
}
return match.datastore.get(key, options)
}
async has (key: Key, options?: AbortOptions): Promise<boolean> {
const match = this._lookup(key)
if (match == null) {
return Promise.resolve(false)
}
return match.datastore.has(key, options)
}
async delete (key: Key, options?: AbortOptions): Promise<void> {
const match = this._lookup(key)
if (match == null) {
throw new DeleteFailedError('No datastore mounted for this key')
}
await match.datastore.delete(key, options)
}
batch (): Batch {
const batchMounts: Record<string, Batch> = {}
const lookup = (key: Key): { batch: Batch } => {
const match = this._lookup(key)
if (match == null) {
throw new Error('No datastore mounted for this key')
}
const m = match.mountpoint.toString()
if (batchMounts[m] == null) {
batchMounts[m] = match.datastore.batch()
}
return {
batch: batchMounts[m]
}
}
return {
put: (key, value) => {
const match = lookup(key)
match.batch.put(key, value)
},
delete: (key) => {
const match = lookup(key)
match.batch.delete(key)
},
commit: async (options) => {
await Promise.all(Object.keys(batchMounts).map(async p => { await batchMounts[p].commit(options) }))
}
}
}
query (q: Query, options?: AbortOptions): AsyncIterable<Pair> {
const qs = this.mounts.map(m => {
return m.datastore.query({
prefix: q.prefix,
filters: q.filters
}, options)
})
let it = merge(...qs)
if (q.filters != null) { q.filters.forEach(f => { it = filter(it, f) }) }
if (q.orders != null) { q.orders.forEach(o => { it = sort(it, o) }) }
if (q.offset != null) {
let i = 0
const offset = q.offset
it = filter(it, () => i++ >= offset)
}
if (q.limit != null) { it = take(it, q.limit) }
return it
}
queryKeys (q: KeyQuery, options?: AbortOptions): AsyncIterable<Key> {
const qs = this.mounts.map(m => {
return m.datastore.queryKeys({
prefix: q.prefix,
filters: q.filters
}, options)
})
/** @type AsyncIterable<Key> */
let it = merge(...qs)
if (q.filters != null) { q.filters.forEach(f => { it = filter(it, f) }) }
if (q.orders != null) { q.orders.forEach(o => { it = sort(it, o) }) }
if (q.offset != null) {
let i = 0
const offset = q.offset
it = filter(it, () => i++ >= offset)
}
if (q.limit != null) { it = take(it, q.limit) }
return it
}
}