Module

x/replicache/scan-iterator.test.ts

Realtime Sync for Any Backend Stack
Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
import {expect} from '@esm-bundle/chai';import {asyncIterableToArray} from './async-iterable-to-array.js';import type {ReadonlyEntry} from './btree/node.js';import type {IndexKey} from './db/index.js';import type {ReadonlyJSONValue} from './json.js';import type {ScanIndexOptions, ScanOptions} from './mod.js';import { fromKeyForIndexScan, GetIndexScanIterator, GetScanIterator, makeScanResult,} from './scan-iterator.js';
test('makeScanResult', async () => { function getTestScanIterator( entries: (readonly [key: string, value: ReadonlyJSONValue])[], ): GetScanIterator { return async function* (fromKey: string) { for (const [key, value] of entries) { if (key >= fromKey) { yield [key, value]; } } }; }
const t = async ( entries: ReadonlyEntry<ReadonlyJSONValue>[], options: ScanOptions, expectedEntries = entries, ) => { const iter = makeScanResult(options, getTestScanIterator(entries)); expect(await asyncIterableToArray(iter.entries())).to.deep.equal( expectedEntries, ); };
await t([], {}); await t( [ ['b', 1], ['d', 2], ], {}, );
// prefix await t( [ ['b', 1], ['d', 2], ], {prefix: 'a'}, [], ); await t( [ ['b', 1], ['d', 2], ], {prefix: 'b'}, [['b', 1]], ); await t( [ ['b', 1], ['d', 2], ], {prefix: 'c'}, [], ); await t( [ ['b', 1], ['d', 2], ], {prefix: 'd'}, [['d', 2]], ); await t( [ ['b', 1], ['d', 2], ], {prefix: 'e'}, [], );
// start await t( [ ['b', 1], ['d', 2], ], {start: {key: 'a'}}, ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'b'}}, ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'c'}}, [['d', 2]], ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'd'}}, [['d', 2]], ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'e'}}, [], );
// start exclusive await t( [ ['b', 1], ['d', 2], ], {start: {key: 'a', exclusive: true}}, ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'b', exclusive: true}}, [['d', 2]], ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'c', exclusive: true}}, [['d', 2]], ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'd', exclusive: true}}, [], ); await t( [ ['b', 1], ['d', 2], ], {start: {key: 'e', exclusive: true}}, [], );
// start & prefix await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'a', start: {key: 'aa'}}, [ ['ab', 1], ['ad', 2], ], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'a', start: {key: 'ab'}}, [ ['ab', 1], ['ad', 2], ], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'a', start: {key: 'ac'}}, [['ad', 2]], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'a', start: {key: 'ad'}}, [['ad', 2]], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'a', start: {key: 'ae'}}, [], );
await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'b', start: {key: 'aa'}}, [ ['bf', 3], ['bh', 4], ], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'b', start: {key: 'ab'}}, [ ['bf', 3], ['bh', 4], ], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'b', start: {key: 'ad'}}, [ ['bf', 3], ['bh', 4], ], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'b', start: {key: 'bf'}}, [ ['bf', 3], ['bh', 4], ], ); await t( [ ['ab', 1], ['ad', 2], ['bf', 3], ['bh', 4], ], {prefix: 'b', start: {key: 'bh'}}, [['bh', 4]], );});
test('makeScanResult with index', async () => { function getTestScanIterator( entries: (readonly [key: IndexKey, value: ReadonlyJSONValue])[], ): GetIndexScanIterator { return async function* (indexName, secondaryKey, primaryKey) { expect(indexName).to.equal('index'); for (const [key, value] of entries) { if (key[0] >= secondaryKey) { if (primaryKey === undefined || key[1] >= primaryKey) { yield [key, value]; } } } }; }
const t = async ( entries: (readonly [key: IndexKey, value: ReadonlyJSONValue])[], options: Omit<ScanIndexOptions, 'indexName'> = {}, expectedEntries = entries, ) => { const indexOptions = {indexName: 'index', ...options}; const iter = makeScanResult(indexOptions, getTestScanIterator(entries)); expect(await asyncIterableToArray(iter.entries())).to.deep.equal( expectedEntries, ); };
await t([]); await t([[['sb', 'pb'], 1]]); await t([ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ]);
// prefix is always on secondary await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 'sa'}, [], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 'sb'}, [[['sb', 'pb'], 1]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 'sc'}, [], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 'sd'}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 's'}, );
// start key await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sa'}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sa']}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sa', 'pa']}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sb'}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb']}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb', '']}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb', 'pb']}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb', 'pc']}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sc'}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sd'}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sd', 'pc']}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sd', 'pd']}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sd', 'pe']}}, [], );
// start exclusive await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sa', exclusive: true}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sa'], exclusive: true}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sa', 'pa'], exclusive: true}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sb', exclusive: true}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb'], exclusive: true}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb', ''], exclusive: true}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb', 'pb'], exclusive: true}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sb', 'pc'], exclusive: true}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sc', exclusive: true}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: 'sd', exclusive: true}}, [], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sd', 'pc'], exclusive: true}}, [[['sd', 'pd'], 2]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sd', 'pd'], exclusive: true}}, [], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {start: {key: ['sd', 'pe'], exclusive: true}}, [], );
// prefix and start await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 'sa', start: {key: 'sb'}}, [], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 's', start: {key: 'sb'}}, ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 'sb', start: {key: 'sb'}}, [[['sb', 'pb'], 1]], ); await t( [ [['sb', 'pb'], 1], [['sd', 'pd'], 2], ], {prefix: 'sd', start: {key: 'sb'}}, [[['sd', 'pd'], 2]], );});
test('fromKeyForIndexScan', () => { const t = ( options: Omit<ScanIndexOptions, 'indexName'>, expected: [secondary: string, primary?: string], ) => { const indexOptions = {indexName: 'i', ...options}; expect(fromKeyForIndexScan(indexOptions)).to.deep.equal(expected); };
t({}, ['', undefined]); t({prefix: 'a'}, ['a', undefined]); t({start: {key: 'a'}}, ['a']); t({start: {key: ['a']}}, ['a']); t({start: {key: ['a', undefined]}}, ['a', undefined]); t({start: {key: ['a', 'b']}}, ['a', 'b']);
t({prefix: 'b', start: {key: 'a'}}, ['b', undefined]); t({prefix: 'a', start: {key: 'b'}}, ['b']); t({prefix: 'a', start: {key: ['a']}}, ['a', undefined]); t({prefix: 'a', start: {key: ['a', '']}}, ['a', '']);});