Give the paging loop, Link-header parser and FieldConfig one home each, rename buildPluginChrome to buildPageChrome, and delete a group from its parents
CI / full-gate (push) Successful in 3m24s
CI / full-gate (push) Successful in 3m24s
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// *through* us with. Built-in `fetch` only, no SDK dep (AGENTS.md); `fetchImpl`-injectable
|
||||
// like the kratos/keto clients. We authenticate the user (login) and grant scopes (consent);
|
||||
// Hydra mints the tokens.
|
||||
import { nextPageToken } from "./link-header.ts";
|
||||
|
||||
export interface OAuth2Client {
|
||||
client_id?: string;
|
||||
@@ -101,13 +102,6 @@ export interface HydraAdmin {
|
||||
rejectLoginRequest(challenge: string, body: RejectRequest): Promise<Completed>;
|
||||
}
|
||||
|
||||
// Hydra paginates with a Link header; pull the page_token of rel="next" (the href is relative, so
|
||||
// resolve against a throwaway base to read the query param). Mirrors kratos-admin's helper.
|
||||
function nextPageToken(link: string | null): string | null {
|
||||
const href = link?.match(/<([^>]+)>\s*;\s*rel="next"/)?.[1];
|
||||
return href ? new URL(href, "http://hydra").searchParams.get("page_token") : null;
|
||||
}
|
||||
|
||||
export function createHydraAdmin(config: { baseUrl: string; fetchImpl?: typeof fetch }): HydraAdmin {
|
||||
const base = config.baseUrl.replace(/\/+$/, "");
|
||||
const http = config.fetchImpl ?? fetch;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// completion projects Keto permissions into (README). Built-in `fetch` only, no SDK dep (AGENTS.md);
|
||||
// `fetchImpl`-injectable, reuses kratos-public.ts's `KratosError` (branch on `.status`).
|
||||
import { KratosError } from "./kratos-public.ts";
|
||||
import { nextPageToken } from "./link-header.ts";
|
||||
|
||||
export interface Identity {
|
||||
id: string;
|
||||
@@ -41,13 +42,6 @@ export interface KratosAdmin {
|
||||
updateMetadataPublic(id: string, metadata: unknown): Promise<Identity>;
|
||||
}
|
||||
|
||||
// Kratos paginates with a Link header; pull the page_token of rel="next" (the href is a
|
||||
// relative path, so resolve it against a throwaway base just to read the query param).
|
||||
function nextPageToken(link: string | null): string | null {
|
||||
const href = link?.match(/<([^>]+)>\s*;\s*rel="next"/)?.[1];
|
||||
return href ? new URL(href, "http://kratos").searchParams.get("page_token") : null;
|
||||
}
|
||||
|
||||
export function createKratosAdmin(config: { baseUrl: string; fetchImpl?: typeof fetch }): KratosAdmin {
|
||||
const base = config.baseUrl.replace(/\/+$/, "");
|
||||
const http = config.fetchImpl ?? fetch;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { nextPageToken } from "./link-header.ts";
|
||||
|
||||
test("nextPageToken reads the page_token of the rel=next entry among several", () => {
|
||||
const link = `</admin/identities?page_size=2&page_token=NEXT>; rel="next",</admin/identities?page_size=2>; rel="first"`;
|
||||
assert.equal(nextPageToken(link), "NEXT");
|
||||
});
|
||||
|
||||
test("nextPageToken is null with no header, no next entry, or a next href without a page_token", () => {
|
||||
assert.equal(nextPageToken(null), null);
|
||||
assert.equal(nextPageToken(`</admin/clients?page_size=2>; rel="first"`), null);
|
||||
assert.equal(nextPageToken(`</admin/clients?page_size=2>; rel="next"`), null);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// Kratos and Hydra both paginate with an RFC 8288 `Link` header. Returns the `page_token` of its
|
||||
// `rel="next"` entry, or null on the last page. The href is a relative path, so it is resolved
|
||||
// against a throwaway base just to read the query parameter.
|
||||
export function nextPageToken(link: string | null): string | null {
|
||||
const href = link?.match(/<([^>]+)>\s*;\s*rel="next"/)?.[1];
|
||||
return href ? new URL(href, "http://link-header.invalid").searchParams.get("page_token") : null;
|
||||
}
|
||||
Reference in New Issue
Block a user