forked from baron/baron-sso
테넌트 목록 조회 cursor기반으로 재구성. 사용자 metadata 미사용 필드 제거
This commit is contained in:
106
common/core/pagination/cursorFetchCore.ts
Normal file
106
common/core/pagination/cursorFetchCore.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
export type CursorPageResponse<TItem> = {
|
||||
items: TItem[];
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
total?: number;
|
||||
cursor?: string;
|
||||
nextCursor?: string;
|
||||
next_cursor?: string;
|
||||
};
|
||||
|
||||
export type CursorFetchParams = Record<
|
||||
string,
|
||||
string | number | boolean | null | undefined
|
||||
>;
|
||||
|
||||
export type CursorFetchRequest = {
|
||||
baseUrl: string;
|
||||
path: string;
|
||||
pageSize?: number;
|
||||
params?: CursorFetchParams;
|
||||
headers?: Record<string, string>;
|
||||
credentials?: RequestCredentials;
|
||||
maxPages?: number;
|
||||
};
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string) {
|
||||
const value = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
||||
return new URL(value, globalThis.location?.origin ?? "http://localhost");
|
||||
}
|
||||
|
||||
function buildCursorFetchUrl(
|
||||
request: Required<Pick<CursorFetchRequest, "baseUrl" | "path">> &
|
||||
Pick<CursorFetchRequest, "params">,
|
||||
pageSize: number,
|
||||
cursor: string | undefined,
|
||||
) {
|
||||
const path = request.path.replace(/^\/+/, "");
|
||||
const url = new URL(path, normalizeBaseUrl(request.baseUrl));
|
||||
|
||||
for (const [key, value] of Object.entries(request.params ?? {})) {
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
url.searchParams.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
url.searchParams.set("limit", String(pageSize));
|
||||
url.searchParams.set("offset", "0");
|
||||
if (cursor) {
|
||||
url.searchParams.set("cursor", cursor);
|
||||
} else {
|
||||
url.searchParams.delete("cursor");
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function readNextCursor<TItem>(page: CursorPageResponse<TItem>) {
|
||||
return page.nextCursor || page.next_cursor || undefined;
|
||||
}
|
||||
|
||||
export async function fetchAllCursorPagesMainThread<TItem>({
|
||||
pageSize = 100,
|
||||
credentials = "same-origin",
|
||||
maxPages = 1000,
|
||||
...request
|
||||
}: CursorFetchRequest): Promise<CursorPageResponse<TItem>> {
|
||||
const items: TItem[] = [];
|
||||
let cursor: string | undefined;
|
||||
|
||||
for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {
|
||||
const url = buildCursorFetchUrl(request, pageSize, cursor);
|
||||
const response = await fetch(url, {
|
||||
headers: request.headers,
|
||||
credentials,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Cursor page request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const page = (await response.json()) as CursorPageResponse<TItem>;
|
||||
items.push(...page.items);
|
||||
|
||||
const nextCursor = readNextCursor(page);
|
||||
if (!nextCursor) {
|
||||
return {
|
||||
...page,
|
||||
items,
|
||||
limit: pageSize,
|
||||
offset: 0,
|
||||
total: items.length,
|
||||
cursor,
|
||||
nextCursor: undefined,
|
||||
next_cursor: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (nextCursor === cursor) {
|
||||
throw new Error("Cursor page request returned the same next cursor");
|
||||
}
|
||||
|
||||
cursor = nextCursor;
|
||||
}
|
||||
|
||||
throw new Error(`Cursor page request exceeded ${maxPages} pages`);
|
||||
}
|
||||
Reference in New Issue
Block a user