src / pagination.ts
/**
* Helpers that return large results in small, model-driven pages, plus a small
* LRU cache so paging a document does not re-hit OneNote on every page turn.
*/
export interface TextPage {
text: string;
offset: number;
chars_returned: number;
total_chars: number;
next_offset: number | null;
has_more: boolean;
}
export function sliceText(fullText: string, offset: number, maxChars: number): TextPage {
const total = fullText.length;
const start = Math.min(Math.max(offset | 0, 0), total);
const size = Math.max(maxChars | 0, 1);
const end = Math.min(total, start + size);
return {
text: fullText.slice(start, end),
offset: start,
chars_returned: end - start,
total_chars: total,
next_offset: end < total ? end : null,
has_more: end < total,
};
}
export interface ItemPage<T> {
items: T[];
offset: number;
count_returned: number;
total_count: number;
next_offset: number | null;
has_more: boolean;
}
export function sliceItems<T>(items: T[], offset: number, limit: number): ItemPage<T> {
const total = items.length;
const start = Math.min(Math.max(offset | 0, 0), total);
const size = Math.max(limit | 0, 1);
const end = Math.min(total, start + size);
return {
items: items.slice(start, end),
offset: start,
count_returned: end - start,
total_count: total,
next_offset: end < total ? end : null,
has_more: end < total,
};
}
export class DocCache<V> {
private readonly max: number;
private readonly map = new Map<string, V>();
constructor(max = 16) {
this.max = max;
}
get(key: string): V | undefined {
const value = this.map.get(key);
if (value !== undefined) {
this.map.delete(key);
this.map.set(key, value);
}
return value;
}
set(key: string, value: V): void {
if (this.map.has(key)) this.map.delete(key);
this.map.set(key, value);
while (this.map.size > this.max) {
const oldest = this.map.keys().next().value;
if (oldest === undefined) break;
this.map.delete(oldest);
}
}
}