Files
cloudflare-domain-manager/apps/api/src/lib/cloudflare/kv-service.ts
T
DenozordecandCursor 4c4908558b
quality / changes (push) Successful in 9s
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
CD / update-wiki (push) Successful in 6s
quality / web (push) Successful in 1m4s
quality / api (push) Successful in 54s
CD / quality (push) Successful in 2m17s
CD / publish (push) Successful in 2m21s
feat(health): деплоить probe-Worker из CFDM и опрашивать цели с edge
Worker сам ходит на origin по Cron Trigger; CFDM кладёт цели в KV и забирает результаты без POST /probe.

Co-authored-by: Cursor <[email protected]>
2026-08-19 17:22:07 +07:00

101 lines
3.3 KiB
TypeScript

import { CF_API_BASE, handleCfResponse, handleCfSuccess, mapCloudflareFailure } from "./http.js";
export interface CfKvNamespace {
id: string;
title: string;
}
export function createKvAdapter(token: string) {
return {
async listNamespaces(accountId: string): Promise<CfKvNamespace[]> {
const all: CfKvNamespace[] = [];
let page = 1;
while (true) {
const url = new URL(
`${CF_API_BASE}/accounts/${accountId}/storage/kv/namespaces`,
);
url.searchParams.set("per_page", "100");
url.searchParams.set("page", String(page));
const response = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(30_000),
});
if (response.status >= 500 || response.status === 429) {
throw mapCloudflareFailure("kv_list", response.status, String(response.status));
}
const batch = await handleCfResponse<CfKvNamespace[]>(response, "kv_list");
all.push(...batch);
if (batch.length < 100) break;
page += 1;
}
return all;
},
async createNamespace(accountId: string, title: string): Promise<CfKvNamespace> {
const response = await fetch(
`${CF_API_BASE}/accounts/${accountId}/storage/kv/namespaces`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ title }),
signal: AbortSignal.timeout(30_000),
},
);
if (response.status >= 500 || response.status === 429) {
throw mapCloudflareFailure("kv_create", response.status, String(response.status));
}
return handleCfResponse<CfKvNamespace>(response, "kv_create");
},
async getValue(
accountId: string,
namespaceId: string,
key: string,
): Promise<string | null> {
const response = await fetch(
`${CF_API_BASE}/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(key)}`,
{
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(30_000),
},
);
if (response.status === 404) return null;
if (response.status >= 500 || response.status === 429) {
throw mapCloudflareFailure("kv_get", response.status, String(response.status));
}
if (!response.ok) {
const text = await response.text().catch(() => "");
throw mapCloudflareFailure("kv_get", response.status, text.slice(0, 180));
}
return response.text();
},
async putValue(
accountId: string,
namespaceId: string,
key: string,
value: string,
): Promise<void> {
const response = await fetch(
`${CF_API_BASE}/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(key)}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "text/plain",
},
body: value,
signal: AbortSignal.timeout(30_000),
},
);
if (response.status >= 500 || response.status === 429) {
throw mapCloudflareFailure("kv_put", response.status, String(response.status));
}
await handleCfSuccess(response, "kv_put");
},
};
}