UNPKG

sub-encoder

Version:

Generate sub encodings for key/value stores

282 lines (225 loc) 8.37 kB
const test = require('brittle') const Hypercore = require('hypercore') const Hyperbee = require('hyperbee') const IndexEncoder = require('index-encoder') const ram = require('random-access-memory') const b = require('b4a') const cenc = require('compact-encoding') const SEP = b.alloc(1) const SubEncoder = require('..') test('simple sub key encoding', t => { const enc = new SubEncoder() const sub1 = enc.sub('a') const sub2 = sub1.sub('b') const key = b.alloc(1).fill(1) const k1 = enc.encode(key) const k2 = sub1.encode(key) const k3 = sub2.encode(key) t.alike(k1, key) t.alike(k2, b.concat([b.from('a'), SEP, key])) t.alike(k3, b.concat([b.from('a'), SEP, b.from('b'), SEP, key])) t.alike(key, enc.decode(k1)) t.alike(key, sub1.decode(k2)) t.alike(key, sub2.decode(k3)) }) test('sub key encoding with hyperbee', async t => { const bee = new Hyperbee(new Hypercore(ram)) const enc = new SubEncoder() const sub1 = enc.sub('hello', { keyEncoding: 'utf-8' }) const sub2 = enc.sub('world') await bee.put('a', b.from('a'), { keyEncoding: sub1 }) await bee.put('b', b.from('b'), { keyEncoding: sub2 }) const n1 = await bee.get('a', { keyEncoding: sub1 }) const n2 = await bee.get('b', { keyEncoding: sub2 }) t.is(n1.key, 'a') t.alike(n1.value, b.from('a')) t.alike(n2.key, b.from('b')) t.alike(n2.value, b.from('b')) }) test('sub range encoding with hyperbee', async t => { const bee = new Hyperbee(new Hypercore(ram), { valueEncoding: 'utf-8' }) const enc = new SubEncoder(null, 'utf-8') const subA = enc.sub('sub-a', 'utf-8') const subB = enc.sub('sub-b', 'utf-8') const subC = enc.sub('sub-c', 'utf-8') await bee.put(enc.encode('d1'), 'd2') await bee.put(subA.encode('a1'), 'a1') await bee.put(subB.encode('b1'), 'b1') await bee.put(subB.encode('b2'), 'b2') await bee.put(subB.encode('b3'), 'b3') await bee.put(subC.encode('c1'), 'c1') { const range = { lt: 'sub' } const nodes = await collect(bee.createReadStream(range, { keyEncoding: enc })) t.is(nodes.length, 1) t.is(nodes[0].key, 'd1') } { const range = {} const nodes = await collect(bee.createReadStream(range, { keyEncoding: subA })) t.is(nodes.length, 1) t.is(nodes[0].key, 'a1') } { const range = { gt: 'b1', lt: 'b3' } const nodes = await collect(bee.createReadStream(range, { keyEncoding: subB })) t.is(nodes.length, 1) t.is(nodes[0].key, 'b2') } }) test('sub range encoding encodes non-undefined falsy values', async t => { const bee = new Hyperbee(new Hypercore(ram), { valueEncoding: 'utf-8' }) const enc = new SubEncoder() const preSub = enc.sub('sub-a') const keyEncoding = enc.sub('sub-b', cenc.lexint) const postSub = enc.sub('sub-c') await bee.put('what', 'ever', { keyEncoding: preSub }) await bee.put('what', 'ever', { keyEncoding: postSub }) await bee.put(0, 'entry0', { keyEncoding }) await bee.put(1, 'entry1', { keyEncoding }) { const range = { gt: 0 } const nodes = await collect(bee.createReadStream(range, { keyEncoding })) t.is(nodes.length, 1, 'gt works') t.is(nodes[0].key, 1) } { const range = { lt: 0 } const nodes = await collect(bee.createReadStream(range, { keyEncoding })) t.is(nodes.length, 0, 'lt works') } { const range = { lte: 0 } const nodes = await collect(bee.createReadStream(range, { keyEncoding })) t.is(nodes.length, 1, 'lte works') t.is(nodes[0].key, 0) } { // Note: also passes when processed as falsy value // would ideally have a value below 0 in the bee, but lexint does not support that const range = { gte: 0 } const nodes = await collect(bee.createReadStream(range, { keyEncoding })) t.is(nodes.length, 2, 'gte works') t.is(nodes[0].key, 0) } }) test('sub range diff encoding with hyperbee', async t => { const bee = new Hyperbee(new Hypercore(ram), { valueEncoding: 'utf-8' }) const enc = new SubEncoder(null, 'utf-8') const subA = enc.sub('sub-a', 'utf-8') const subB = enc.sub('sub-b', 'utf-8') const subC = enc.sub('sub-c', 'utf-8') await bee.put(enc.encode('d1'), 'd2') await bee.put(subA.encode('a1'), 'a1') await bee.put(subB.encode('b1'), 'b1') await bee.put(subB.encode('b2'), 'b2') await bee.put(subB.encode('b3'), 'b3') await bee.put(subC.encode('c1'), 'c1') { const range = { lt: 'sub' } const nodes = await collect(bee.createDiffStream(0, range, { keyEncoding: enc })) t.is(nodes.length, 1) t.is(nodes[0].left.key, 'd1') } { const range = {} const nodes = await collect(bee.createDiffStream(0, range, { keyEncoding: subA })) t.is(nodes.length, 1) t.is(nodes[0].left.key, 'a1') } { const range = { gt: 'b1', lt: 'b3' } const nodes = await collect(bee.createDiffStream(0, range, { keyEncoding: subB })) t.is(nodes.length, 1) t.is(nodes[0].left.key, 'b2') } }) test('supports the empty sub', async t => { const bee = new Hyperbee(new Hypercore(ram)) const enc = new SubEncoder() const sub1 = enc.sub('1', 'utf-8') const sub2 = enc.sub('2', 'utf-8') const sub3 = enc.sub() await bee.put('', b.from('a'), { keyEncoding: sub1 }) await bee.put('', b.from('b'), { keyEncoding: sub2 }) await bee.put(b.alloc(1), b.from('c'), { keyEncoding: sub3 }) const n3 = await collect(bee.createReadStream({ keyEncoding: sub3 })) t.is(n3.length, 1) t.alike(n3[0].key, b.alloc(1)) }) test('supports the empty sub on top of another sub', async t => { const bee = new Hyperbee(new Hypercore(ram)) const enc = new SubEncoder() const sub1 = enc.sub('1', 'utf-8') const subSubEmpty = sub1.sub() const subSub1 = sub1.sub('not empty', 'utf-8') await bee.put('', 'sub1 Entry', { keyEncoding: sub1 }) await bee.put('', 'subSub1 Entry', { keyEncoding: subSub1 }) await bee.put(b.alloc(1), 'EmptySubsub entry', { keyEncoding: subSubEmpty }) const n3 = await collect(bee.createReadStream({ keyEncoding: subSubEmpty })) t.is(n3.length, 1) t.alike(n3[0].key, b.alloc(1)) }) test('empty str is a valid prefix, causing no overlap', async t => { const bee = new Hyperbee(new Hypercore(ram)) const enc = new SubEncoder() const sub1 = enc.sub('', 'utf-8') const sub2 = enc.sub('other', 'utf-8') await bee.put('hey', 'ho', { keyEncoding: sub1 }) await bee.put('not', 'a part', { keyEncoding: sub2 }) const res = await collect(bee.createReadStream({ keyEncoding: sub1 })) t.is(res.length, 1) t.alike(res[0].key, 'hey') }) test('can read out the empty key in subs', async t => { const bee = new Hyperbee(new Hypercore(ram)) const enc = new SubEncoder() const sub1 = enc.sub('1', 'utf-8') const sub2 = enc.sub('2', 'utf-8') const sub3 = enc.sub('3', 'binary') await bee.put('', b.from('a'), { keyEncoding: sub1 }) await bee.put('', b.from('b'), { keyEncoding: sub2 }) await bee.put(b.alloc(1), b.from('c'), { keyEncoding: sub3 }) const n1 = await collect(bee.createReadStream({ keyEncoding: sub1 })) const n2 = await collect(bee.createReadStream({ keyEncoding: sub2 })) const n3 = await collect(bee.createReadStream({ keyEncoding: sub3 })) t.is(n1[0].key, '') t.is(n2[0].key, '') t.alike(n3[0].key, b.alloc(1)) }) test('sub + index + hyperbee combo', async t => { const root = new SubEncoder() const enc = { keyEncoding: root.sub(b.from([1]), { keyEncoding: new IndexEncoder([ IndexEncoder.UINT, IndexEncoder.STRING ]) }), valueEncoding: 'utf-8' } const bee = new Hyperbee(new Hypercore(ram)) await bee.put([1, 'a'], 'a', enc) await bee.put([1, 'b'], 'b', enc) await bee.put([2, 'aa'], 'aa', enc) await bee.put([2, 'bb'], 'bb', enc) await bee.put([3, 'aaa'], 'aaa', enc) await bee.put([3, 'bbb'], 'bbb', enc) const expectedKeys = [[2, 'aa'], [2, 'bb']] for await (const node of bee.createReadStream({ gt: [1], lt: [3] }, enc)) { t.alike(node.key, expectedKeys.shift()) } t.is(expectedKeys.length, 0) }) test('constructor-specified sub equivalent to calling .sub()', async t => { const directSub = new SubEncoder('mysub', 'utf-8') const roundaboutSub = (new SubEncoder()).sub('mysub', 'utf-8') t.alike(directSub, roundaboutSub) }) async function collect (ite) { const res = [] for await (const node of ite) { res.push(node) } return res }