Skip to content

Commit 8ad9853

Browse files
chafeyChris Hafey
andauthored
feat: add suport for entries() returning results in descending order (#46)
* feat: add suport for entries() returning results in descending order * updates based on review by @alanshaw --------- Co-authored-by: Chris Hafey <chafey@Chriss-MBP.localdomain>
1 parent 336de84 commit 8ad9853

File tree

3 files changed

+166
-1
lines changed

3 files changed

+166
-1
lines changed

src/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ export interface KeyUpperBoundRangeInclusiveOption {
8686
lte: string
8787
}
8888

89+
export interface KeyOrderOption {
90+
/** Order to return results: 'asc' for ascending (default), 'desc' for descending. */
91+
order: 'asc' | 'desc'
92+
}
93+
8994
export type EntriesOptions =
9095
| KeyPrefixOption
9196
| KeyRangeOption
97+
| KeyOrderOption
98+
| (KeyPrefixOption & KeyOrderOption)
99+
| (KeyRangeOption & KeyOrderOption)

src/index.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ const isKeyUpperBoundRangeInclusiveOption = options => 'lte' in options && Boole
294294
*/
295295
const isKeyUpperBoundRangeExclusiveOption = options => 'lt' in options && Boolean(options.lt)
296296

297+
/**
298+
* @param {API.EntriesOptions} [options]
299+
* @returns {options is API.KeyOrderOption}
300+
*/
301+
const isKeyOrderOption = options => {
302+
const opts = options ?? {}
303+
return 'order' in opts && (opts.order === 'asc' || opts.order === 'desc')
304+
}
305+
297306
/**
298307
* List entries in the bucket.
299308
*
@@ -305,6 +314,8 @@ const isKeyUpperBoundRangeExclusiveOption = options => 'lt' in options && Boolea
305314
export const entries = async function * (blocks, root, options) {
306315
const hasKeyPrefix = isKeyPrefixOption(options)
307316
const hasKeyRange = isKeyRangeOption(options)
317+
const hasKeyOrder = isKeyOrderOption(options)
318+
const isDescending = hasKeyOrder && options.order === 'desc'
308319
const hasKeyLowerBoundRange = hasKeyRange && isKeyLowerBoundRangeOption(options)
309320
const hasKeyLowerBoundRangeInclusive = hasKeyLowerBoundRange && isKeyLowerBoundRangeInclusiveOption(options)
310321
const hasKeyLowerBoundRangeExclusive = hasKeyLowerBoundRange && isKeyLowerBoundRangeExclusiveOption(options)
@@ -319,7 +330,12 @@ export const entries = async function * (blocks, root, options) {
319330
yield * (
320331
/** @returns {AsyncIterableIterator<API.ShardValueEntry>} */
321332
async function * ents (shard) {
322-
for (const entry of shard.value.entries) {
333+
const entriesLength = shard.value.entries.length
334+
335+
for (let idx = 0; idx < entriesLength; idx++) {
336+
// Calculate the actual index based on order
337+
const i = isDescending ? entriesLength - 1 - idx : idx
338+
const entry = shard.value.entries[i]
323339
const key = shard.value.prefix + entry[0]
324340

325341
// if array, this is a link to a shard

test/entries.test.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,145 @@ describe('entries', () => {
242242
assert.equal(results[i][0], key)
243243
}
244244
})
245+
246+
it('lists entries in ascending order (explicit)', async () => {
247+
const empty = await ShardBlock.create()
248+
const blocks = new Blockstore()
249+
await blocks.put(empty.cid, empty.bytes)
250+
251+
/** @type {Array<[string, API.UnknownLink]>} */
252+
const testdata = [
253+
['c', await randomCID(32)],
254+
['d', await randomCID(32)],
255+
['a', await randomCID(32)],
256+
['b', await randomCID(32)]
257+
]
258+
259+
/** @type {API.ShardLink} */
260+
let root = empty.cid
261+
for (const [k, v] of testdata) {
262+
const res = await put(blocks, root, k, v)
263+
for (const b of res.additions) {
264+
await blocks.put(b.cid, b.bytes)
265+
}
266+
root = res.root
267+
}
268+
269+
const results = []
270+
for await (const entry of entries(blocks, root, { order: 'asc' })) {
271+
results.push(entry)
272+
}
273+
274+
const sortedKeys = testdata.map(d => d[0]).sort()
275+
for (const [i, key] of sortedKeys.entries()) {
276+
assert.equal(results[i][0], key)
277+
}
278+
})
279+
280+
it('lists entries in descending order', async () => {
281+
const empty = await ShardBlock.create()
282+
const blocks = new Blockstore()
283+
await blocks.put(empty.cid, empty.bytes)
284+
285+
/** @type {Array<[string, API.UnknownLink]>} */
286+
const testdata = [
287+
['c', await randomCID(32)],
288+
['d', await randomCID(32)],
289+
['a', await randomCID(32)],
290+
['b', await randomCID(32)]
291+
]
292+
293+
/** @type {API.ShardLink} */
294+
let root = empty.cid
295+
for (const [k, v] of testdata) {
296+
const res = await put(blocks, root, k, v)
297+
for (const b of res.additions) {
298+
await blocks.put(b.cid, b.bytes)
299+
}
300+
root = res.root
301+
}
302+
303+
const results = []
304+
for await (const entry of entries(blocks, root, { order: 'desc' })) {
305+
results.push(entry)
306+
}
307+
308+
const sortedKeys = testdata.map(d => d[0]).sort().reverse()
309+
for (const [i, key] of sortedKeys.entries()) {
310+
assert.equal(results[i][0], key)
311+
}
312+
})
313+
314+
it('lists entries by prefix in descending order', async () => {
315+
const empty = await ShardBlock.create()
316+
const blocks = new Blockstore()
317+
await blocks.put(empty.cid, empty.bytes)
318+
319+
/** @type {Array<[string, API.UnknownLink]>} */
320+
const testdata = [
321+
['dccc', await randomCID(32)],
322+
['deee', await randomCID(32)],
323+
['dooo', await randomCID(32)],
324+
['daaa', await randomCID(32)],
325+
['beee', await randomCID(32)]
326+
]
327+
328+
/** @type {API.ShardLink} */
329+
let root = empty.cid
330+
for (const [k, v] of testdata) {
331+
const res = await put(blocks, root, k, v)
332+
for (const b of res.additions) {
333+
await blocks.put(b.cid, b.bytes)
334+
}
335+
root = res.root
336+
}
337+
338+
const prefix = 'd'
339+
const results = []
340+
for await (const entry of entries(blocks, root, { prefix, order: 'desc' })) {
341+
results.push(entry)
342+
}
343+
344+
const filteredAndSorted = testdata.map(d => d[0]).filter(k => k.startsWith(prefix)).sort().reverse()
345+
for (const [i, key] of filteredAndSorted.entries()) {
346+
assert.equal(results[i][0], key)
347+
}
348+
})
349+
350+
it('lists entries by range in descending order', async () => {
351+
const empty = await ShardBlock.create()
352+
const blocks = new Blockstore()
353+
await blocks.put(empty.cid, empty.bytes)
354+
355+
/** @type {Array<[string, API.UnknownLink]>} */
356+
const testdata = [
357+
['aaaa', await randomCID(32)],
358+
['cccc', await randomCID(32)],
359+
['deee', await randomCID(32)],
360+
['dooo', await randomCID(32)],
361+
['eeee', await randomCID(32)]
362+
]
363+
364+
/** @type {API.ShardLink} */
365+
let root = empty.cid
366+
for (const [k, v] of testdata) {
367+
const res = await put(blocks, root, k, v)
368+
for (const b of res.additions) {
369+
await blocks.put(b.cid, b.bytes)
370+
}
371+
root = res.root
372+
}
373+
374+
const gte = 'c'
375+
const lt = 'e'
376+
const results = []
377+
for await (const entry of entries(blocks, root, { gte, lt, order: 'desc' })) {
378+
results.push(entry)
379+
}
380+
381+
const filteredAndSorted = testdata.map(d => d[0]).filter(k => k >= gte && k < lt).sort().reverse()
382+
for (const [i, key] of filteredAndSorted.entries()) {
383+
assert.equal(results[i][0], key)
384+
}
385+
})
245386
})

0 commit comments

Comments
 (0)