splitwise
Version:
A TypeScript SDK for the Splitwise API.
95 lines • 4 kB
JavaScript
;
/**
* Pagination helper for Splitwise list endpoints.
*
* Splitwise uses a simple `limit`/`offset` scheme with no continuation tokens,
* so a "page" is exhausted when the server returns fewer rows than requested
* (or none at all). `PagedResult` wraps that loop in a value that is:
* - awaitable: `await result` resolves to the first page's array (sends the
* user's `limit` as-is so the server's default applies when omitted)
* - async-iterable: `for await (const item of result)` yields every item
* - page-iterable: `for await (const page of result.byPage())` yields arrays
*
* Iteration needs a known page size to detect end-of-data, so when iterating
* without an explicit `limit` the SDK uses `ITERATION_PAGE_SIZE` for batching.
*
* The first page fetched via `await` is cached, so repeated awaits don't
* re-hit the network. Iteration always starts a fresh sequence from the
* configured offset.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPagedResult = createPagedResult;
const ITERATION_PAGE_SIZE = 100;
const DEFAULT_OFFSET = 0;
function createPagedResult(http, path, unwrapKey, options) {
const userLimit = options?.limit;
const startOffset = options?.offset ?? DEFAULT_OFFSET;
const extraQuery = options?.query ?? {};
// Reject nonsensical limits early. Without this guard, `limit: 0` would
// produce an infinite loop in pageIterator (page.length < 0 is never true,
// so the termination check never fires and we just keep requesting empty
// pages from offset 0 forever).
if (userLimit !== undefined && (!Number.isInteger(userLimit) || userLimit <= 0)) {
throw new RangeError(`Pagination limit must be a positive integer, got ${userLimit}`);
}
if (!Number.isInteger(startOffset) || startOffset < 0) {
throw new RangeError(`Pagination offset must be a non-negative integer, got ${startOffset}`);
}
const overrides = {
...(options?.signal !== undefined && { signal: options.signal }),
...(options?.timeout !== undefined && { timeout: options.timeout }),
...(options?.maxRetries !== undefined && { maxRetries: options.maxRetries }),
...(options?.baseUrl !== undefined && { baseUrl: options.baseUrl }),
};
const fetchPage = (offset, limit) => http.get(path, {
query: {
...extraQuery,
...(limit !== undefined && { limit }),
offset,
},
unwrapKey,
...overrides,
});
// Cache the first page so repeated `await result` calls don't re-fetch.
// The await path sends the user's limit as-is (no client-side default).
let firstPagePromise = null;
const getFirstPage = () => {
if (firstPagePromise === null) {
firstPagePromise = fetchPage(startOffset, userLimit);
}
return firstPagePromise;
};
async function* pageIterator() {
// Iteration needs a known page size to detect end-of-data.
const pageSize = userLimit ?? ITERATION_PAGE_SIZE;
let offset = startOffset;
while (true) {
const page = await fetchPage(offset, pageSize);
if (page.length > 0) {
yield page;
}
// Stop when the server returns a short or empty page; in either case
// there's nothing more to fetch.
if (page.length < pageSize)
return;
offset += pageSize;
}
}
async function* itemIterator() {
for await (const page of pageIterator()) {
for (const item of page) {
yield item;
}
}
}
return {
then(onfulfilled, onrejected) {
return getFirstPage().then(onfulfilled, onrejected);
},
byPage() {
return { [Symbol.asyncIterator]: pageIterator };
},
[Symbol.asyncIterator]: itemIterator,
};
}
//# sourceMappingURL=pagination.js.map