initial commit
This commit is contained in:
@@ -66,39 +66,47 @@ const getPageCached = (page) => {
|
||||
return promise;
|
||||
};
|
||||
|
||||
export const fetchBookingsInRange = async (start, end) => {
|
||||
const firstPage = await getPageCached(1);
|
||||
/**
|
||||
* Drain pages up to the budget and return every row, unfiltered.
|
||||
*
|
||||
* `makeStop` receives page 1's rows once and returns the per-page early-stop
|
||||
* predicate, so a range scan can bail as soon as it has read past its window
|
||||
* while an unfiltered drain simply reads to the budget.
|
||||
*
|
||||
* `fetchPage` is injectable because the page cache below is the wrong layer for
|
||||
* a caller that already has one. A TanStack-managed screen sets its own refetch
|
||||
* interval, and serving it from a 20s module-level cache would silently cap how
|
||||
* fresh that screen can ever be.
|
||||
*/
|
||||
const drainPages = async (makeStop, fetchPage = getPageCached) => {
|
||||
const firstPage = await fetchPage(1);
|
||||
const total = firstPage.total;
|
||||
const pageCount = Math.max(1, Math.ceil(total / BULK_PAGESIZE));
|
||||
const pageSize = Math.max(1, firstPage.rows?.length || 100);
|
||||
const pageCount = Math.max(1, Math.ceil(total / pageSize));
|
||||
const budget = Math.min(pageCount, MAX_PAGES);
|
||||
|
||||
const collected = [...firstPage.rows];
|
||||
const descending = isDescendingByCreatedAt(firstPage.rows);
|
||||
const collected = [...(firstPage.rows || [])];
|
||||
const shouldStop = makeStop ? makeStop(firstPage.rows) : () => false;
|
||||
|
||||
/* Newest-first and this page already ends before the window opens → every
|
||||
later page is older still, so there is nothing left to find. */
|
||||
const pageEndsBeforeRange = (rows) => {
|
||||
if (!descending || !rows.length) return false;
|
||||
const oldest = parseDoormileTimestamp(rows[rows.length - 1].createdat);
|
||||
return oldest.isValid() && oldest.format('YYYY-MM-DD') < start;
|
||||
};
|
||||
|
||||
let stoppedEarly = pageEndsBeforeRange(firstPage.rows);
|
||||
let stoppedEarly = shouldStop(firstPage.rows) || collected.length >= total;
|
||||
let lastPageFetched = 1;
|
||||
|
||||
for (let page = 2; page <= budget && !stoppedEarly; page += 1) {
|
||||
const next = await getPageCached(page);
|
||||
const next = await fetchPage(page);
|
||||
lastPageFetched = page;
|
||||
if (!next.rows.length) {
|
||||
if (!next.rows?.length) {
|
||||
stoppedEarly = true;
|
||||
break;
|
||||
}
|
||||
collected.push(...next.rows);
|
||||
stoppedEarly = pageEndsBeforeRange(next.rows);
|
||||
if (collected.length >= total) {
|
||||
break;
|
||||
}
|
||||
stoppedEarly = shouldStop(next.rows);
|
||||
}
|
||||
|
||||
return {
|
||||
rows: collected.filter((booking) => inRange(booking, start, end)),
|
||||
rows: collected,
|
||||
/* Truncated only if the budget ran out with pages still unread AND the scan
|
||||
did not stop early because it had already passed the window. */
|
||||
truncated: !stoppedEarly && pageCount > budget,
|
||||
@@ -108,6 +116,35 @@ export const fetchBookingsInRange = async (start, end) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchBookingsInRange = async (start, end) => {
|
||||
const scan = await drainPages((firstRows) => {
|
||||
const descending = isDescendingByCreatedAt(firstRows);
|
||||
/* Newest-first and this page already ends before the window opens → every
|
||||
later page is older still, so there is nothing left to find. */
|
||||
return (rows) => {
|
||||
if (!descending || !rows.length) return false;
|
||||
const oldest = parseDoormileTimestamp(rows[rows.length - 1].createdat);
|
||||
return oldest.isValid() && oldest.format('YYYY-MM-DD') < start;
|
||||
};
|
||||
});
|
||||
|
||||
/* `scanned` deliberately stays the pre-filter count — it describes how much of
|
||||
the account was read, which is what `truncated` has to be judged against. */
|
||||
return { ...scan, rows: scan.rows.filter((booking) => inRange(booking, start, end)) };
|
||||
};
|
||||
|
||||
/**
|
||||
* Every booking the account has, up to the page budget, with **no date filter**.
|
||||
*
|
||||
* For screens that show the whole list rather than a window. Deliberately not
|
||||
* `fetchBookingsInRange` with sentinel bounds: that path still runs `inRange`,
|
||||
* which drops any row whose `createdat` will not parse. Dropping a row from a
|
||||
* date-scoped answer is defensible; dropping it from "every order" is not — the
|
||||
* row exists and the operator has to be able to see it.
|
||||
*/
|
||||
export const drainBookings = ({ cached = true } = {}) =>
|
||||
drainPages(undefined, cached ? getPageCached : (page) => getBookingsPage(page, BULK_PAGESIZE));
|
||||
|
||||
export const fetchBookingsForDay = (day) => fetchBookingsInRange(day, day);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user