Opaque Cursor Encoding And Decoding
A cursor codec that encodes and decodes opaque cursors so API clients never depend on internal offsets or key names.
Code
export type CursorDirection = "forward" | "backward";
export interface CursorFields {
readonly id: string;
readonly direction: CursorDirection;
}
export type CursorResult<T> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly error: string };
function toBase64Url(text: string): string {
return Buffer.from(text, "utf8").toString("base64url");
}
function fromBase64Url(encoded: string): string {
return Buffer.from(encoded, "base64url").toString("utf8");
}
export function encodeCursor(fields: CursorFields): string {
return toBase64Url(JSON.stringify(fields));
}
export function decodeCursor(cursor: string): CursorResult<CursorFields> {
if (typeof cursor !== "string" || cursor.length === 0) {
return { ok: false, error: "cursor must be a non-empty string" };
}
let parsed: unknown;
try {
parsed = JSON.parse(fromBase64Url(cursor));
} catch {
return { ok: false, error: "cursor is not valid base64url JSON" };
}
if (!parsed || typeof parsed !== "object") {
return { ok: false, error: "cursor payload must be an object" };
}
const record = parsed as Record<string, unknown>;
if (typeof record.id !== "string" || record.id.length === 0) {
return { ok: false, error: "cursor payload missing a string id" };
}
const direction = record.direction === undefined ? "forward" : record.direction;
if (direction !== "forward" && direction !== "backward") {
return { ok: false, error: `cursor direction must be forward or backward, got ${String(direction)}` };
}
return { ok: true, value: { id: record.id, direction } };
}Report a problem with this exampleOpens a pre-filled issue on GitHub.