Created
October 20, 2020 20:35
-
-
Save cjea/98b10dd232c15cff69d8e18e3346ec6c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
/** | |
* An example implementation of cursor-based pagination. | |
* The "database" is a singly-linked list of foods. | |
* The server uses the cursor to find the last returned record, so | |
* it can begin the next page after the cursor. | |
* | |
* The cursor is base64 encoded JSON with "type" and "id" fields. | |
* The "type" allows servers to tag pagination strategies, and change | |
* implementations later without users knowing about it. | |
* A "version" key would also make sense. | |
*/ | |
let records = `{"1":{"id":"1","name":"eggs","tastiness":7,"next":"2"},"2":{"id":"2","name":"toast","tastiness":5,"next":"3"},"3":{"id":"3","name":"kale","tastiness":3,"next":"4"},"4":{"id":"4","name":"rice","tastiness":4,"next":"5"},"5":{"id":"5","name":"burrito","tastiness":9,"next":"6"},"6":{"id":"6","name":"hot pot","tastiness":8,"next":"7"},"7":{"id":"7","name":"reuben","tastiness":10,"next":"8"},"8":{"id":"8","name":"potato salad","tastiness":8,"next":null}}`; | |
Database.prototype = { | |
fetch(id) { | |
return this.records[id]; | |
}, | |
ids() { | |
return Object.keys(this.records).sort(); | |
}, | |
fetchFrom(id, limit, filters = []) { | |
let first = id === "undefined" ? this.ids()[0] : this.fetch(id).next; | |
let current = this.fetch(first); | |
let results = []; | |
while (current && results.length < limit) { | |
if (filters.every((f) => f(current))) results.push(current); | |
current = this.fetch(current.next); | |
} | |
return results; | |
}, | |
}; | |
function Database(records) { | |
this.records = records; | |
} | |
Server.prototype = { | |
encodeCursor(id) { | |
let str = `{"type":"direct", "id":"${id}"}`; | |
return Buffer.from(str).toString("base64"); | |
}, | |
fetchFrom(id, limit, filters = []) { | |
let data = this.db.fetchFrom(id, limit, filters); | |
if (data.length === 0) return { data, afterCursor: null }; | |
let afterCursor = this.encodeCursor(data[data.length - 1].id); | |
return { data, afterCursor }; | |
}, | |
fetch(limit, filters = [], cursor = this.encodeCursor()) { | |
let data = JSON.parse(Buffer.from(cursor, "base64")); | |
switch (data.type) { | |
case "offset": | |
return this.fetchOffset(data.offset, limit, filters); | |
case "direct": | |
return this.fetchFrom(data.id, limit, filters); | |
default: | |
console.error(`unexpected cursor type: ${data.type}`); | |
} | |
}, | |
}; | |
function Server(db, defaultCursor = "direct") { | |
this.db = db; | |
this.defaultCursor = defaultCursor; | |
} | |
const PAGE_SIZE = 3; | |
const FILTERS = [(x) => x.tastiness > 3]; | |
let server = new Server(new Database(JSON.parse(records))); | |
let currentPage = server.fetch(PAGE_SIZE, FILTERS); | |
let i = 1; | |
while (currentPage.afterCursor) { | |
console.log(`PAGE ${i++}`); | |
console.log(`--------`); | |
console.log(currentPage); | |
currentPage = server.fetch(PAGE_SIZE, FILTERS, currentPage.afterCursor); | |
} | |
console.log(currentPage); // the last page whose cursor is `null` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment