Files

35 lines
1.9 KiB
TypeScript

import type { Database, DeploymentSettings } from './deployment-config.ts';
export async function ensureDatabase(
settings: Pick<DeploymentSettings, 'accountId' | 'apiToken' | 'databaseName' | 'databaseId'>,
fetcher: typeof fetch = fetch,
): Promise<Database> {
const base = `https://api.cloudflare.com/client/v4/accounts/${settings.accountId}/d1/database`;
async function request<T>(suffix: string, method = 'GET', body?: unknown): Promise<T> {
const response = await fetcher(`${base}${suffix}`, {
method,
headers: { Authorization: `Bearer ${settings.apiToken}`, 'Content-Type': 'application/json' },
...(body ? { body: JSON.stringify(body) } : {}),
signal: AbortSignal.timeout(30_000),
});
// 不回显 API 响应体,防止远端诊断信息意外包含凭据或内部细节。
if (!response.ok) throw new Error(`D1 ${method} 请求失败(HTTP ${response.status})。请检查目标账户、数据库 ID 和 Token 的 D1 权限。`);
const payload = await response.json() as { success: boolean; result: T };
if (!payload.success) throw new Error(`D1 ${method} 请求未成功,请检查 Token 权限与账户资源配额。`);
return payload.result;
}
// 显式 ID 必须属于目标账户;不存在或无权限时直接失败,不能偷偷创建空库替代。
if (settings.databaseId) return await request<Database>(`/${settings.databaseId}`);
// 按名称复用并读取全部分页,避免已有数据库在后续页时误创建同名资源。
const pageSize = 100;
for (let page = 1; ; page++) {
const databases = await request<Database[]>(`?per_page=${pageSize}&page=${page}`);
const existing = databases.find((database) => database.name === settings.databaseName);
if (existing) return existing;
if (databases.length < pageSize) break;
}
return await request<Database>('', 'POST', { name: settings.databaseName });
}