import type { BackupConfig } from "./config.js"; import type { Manifest } from "./manifest.js"; import { type SnapshotStore } from "./snapshot-store.js"; import { type DrillCheck } from "./drill.js"; import { type StorageRestoreSummary } from "./storage-restore.js"; import { type OrphanedForeignKey } from "./orphaned-foreign-keys.js"; import { type RestorePhase, type RestoreRecordStore } from "./restore-record.js"; /** 归档可读性要求:pg_restore 必须 ≥ max(写入工具版本, 源服务端版本)——格式跟随写入工具。 */ export declare function requiredRestoreToolMajor(manifest: Manifest): number; export interface RestoreResult { snapshot: string; /** dry-run:只做只读预检,未发生任何写入 */ dryRun?: boolean; restoredToDatabase: boolean; /** 表级验证结果(与演练同一套 verifyRestored);未恢复数据库时不存在 */ databaseChecks?: DrillCheck[]; /** * 验证总裁决(= databaseChecks 全 pass)。库级调用方(worker)必须看它, * 不能只看 restoredToDatabase——"恢复完成"与"恢复通过验证"是两个事实。 */ databaseVerified?: boolean; /** 因快照自带孤儿行而以 NOT VALID 建成的外键(数据照样全部恢复);没有时不出现 */ orphanedForeignKeys?: OrphanedForeignKey[]; /** Storage 回传摘要(提供了目标 Storage 凭据时);本地下载模式不存在 */ storageSummary?: StorageRestoreSummary; storageFilesWritten: number; storageDir?: string; /** * 快照里有 Auth 用户时才有(2.10.0 起):这次恢复有没有把它们写进目标,写了多少,恢复后要注意什么。 * 不含 Auth 的快照没有这一项,输出和以前一样 */ auth?: RestoreAuthResult; } export interface RestoreAuthResult { /** true:Auth 用户、identities 和数据库在同一个事务里写进了目标 */ restored: boolean; /** 没写的原因(--skip-auth、读不到 auth.dump) */ reason?: string; users?: number; identities?: number; /** 补建了的触发器 */ triggers?: string[]; /** 恢复后登录会受影响的用户、要手工加回的授权 */ notes?: string[]; /** 没有自动补建、要手工处理的触发器和策略 */ manual?: string[]; } /** * restore 无源库配置时的占位连接串(纯 flag 驱动、没有 backupdrill.config.json 的场景)。 * 同源阻断门对它不生效——没有可比对的源;有真实源配置时阻断门照常工作。 */ export declare const NO_SOURCE_DATABASE = "postgresql://unused"; export { projectRefOf } from "./restore-engine.js"; /** * 两个连接串是否指向同一数据库租户(纯函数,可测)。 * Supabase 双方以项目 ref 为准(直连/pooler 形态互认);其余退回 host+user 比对 * ——只比 host 不够:pooler 主机是区域级共享的,租户身份在用户名里。 * 任一解析失败 → false(pg 侧会给出自己的连接错误,这里不误伤)。 */ export declare function sameDatabaseTarget(a: string, b: string): boolean; /** * 从目标 Project URL(https://.supabase.co)提取 ref。 */ export declare function refFromSupabaseUrl(supabaseUrl: string): string | null; /** * 目标 Storage URL 的严格校验:必须是 https 且恰为 .supabase.co 源(无路径/查询)。 * service-role key 会作为请求头发往这个地址——校验必须发生在**第一次 fetch 之前**, * 否则任意主机(或明文 http)都能收到 key(评审第 8 轮)。返回规范化 origin。 */ export declare function validateStorageTargetOrigin(supabaseUrl: string): string; export { assertNoHostOverride } from "./restore-engine.js"; /** * 备份配置能导出的全部源项目 ref:数据库连接串 + Storage 读取端点 * (https://.storage.supabase.co/...)。storage-only 恢复没有目标库连接串, * 同源阻断必须同样覆盖"目标 Storage = 源项目"的组合(评审第 8 轮)。 */ export declare function sourceProjectRefs(config: BackupConfig, manifest?: Manifest): Set; /** * 请求与快照内容必须相交(评审第 15 轮,纯函数可测):什么都不会做的调用 * 不得走到"Restore complete"——自动化会把 no-op 记成一次成功恢复。 */ export declare function assertRequestApplies(manifest: Manifest, wantsDatabase: boolean, wantsStorageUpload: boolean): void; /** * 正式执行的确认门(PRD §5.3.3):用户必须键入目标 project ref(或非 Supabase 目标的 * 主机名)与实际连接目标一致。误把生产源当目标是恢复流程里代价最不对称的失误, * 这道门要求人把目标身份亲手打一遍。 */ export declare function assertConfirmedTarget(confirmTarget: string | undefined, targetDatabaseUrl?: string, targetSupabaseUrl?: string): void; /** * 空目标门的拒绝文案。"只重试 Storage"那句只对同时恢复 Storage 的运行有意义:库已写完、 * Storage 中途断了,重跑时才会在这道门上被拦。纯数据库恢复(托管恢复只做数据库)带上它, * 用户会以为自己漏了一步,其实只需要换一个空项目。 */ export declare function nonEmptyTargetMessage(schemas: string[], objectCount: number, alsoRestoringStorage: boolean): string; /** * 单事务恢复特有的失败:每张表连同它的索引、序列、TOAST 表在提交前都占着一个锁槽。槽数 = * max_locks_per_transaction × (max_connections + …),Supabase 最小规格(max_connections 60)实测 * 900 张表能过、1200 张表报 out of shared memory(2026-10-23,PG 17)。分区多的库会撞上。 * Supabase 只能用 CLI 调这个参数(会重启数据库),照官方文档的命令给出来。 */ export declare function restoreFailureAdvice(failure: string): string; /** * 把一份快照恢复出来:数据库经统一引擎还原进目标(与演练同一条 pg_restore 路径与 * 错误分类),恢复后跑同一套表级验证;Storage 提供目标凭据时回传+对账(P0-D), * 否则下载到本地目录。这是 US-5 恢复向导的 CLI 形态。 */ export declare function runRestore(config: BackupConfig, opts: { targetDatabaseUrl?: string; targetSupabaseUrl?: string; targetServiceRoleKey?: string; confirmTarget?: string; dryRun?: boolean; /** 源身份不可考(v1 快照 + 无配置)时的显式风险认知——没有它一律拒绝写入 */ acknowledgeUnverifiedSource?: boolean; /** * 跳过 Storage 阶段(既不回传也不本地下载)。用于把恢复拆成可独立重跑的两段 * (hosted 检查点重试):DB 阶段传此=true 只恢复库;Storage 阶段单独用 * targetSupabaseUrl 跑。不影响 CLI 默认行为。 */ skipStorage?: boolean; snapshot?: string; storageDir?: string; /** 快照从哪读;省略 = config.storage 指的 S3 兼容桶 */ store?: SnapshotStore; /** * 不写快照里的 Auth 用户,数据库按 2.9 的方式恢复(逃生口:新路径在某个目标上出问题时照样能恢复; * 也用于用户已经自己把 Auth 用户复制到目标上的情况) */ skipAuth?: boolean; /** * 带 Auth 的恢复把事务号、目标、进度记在这里(写 COMMIT 之前先记),供 restore --status / --finish 用。 * CLI 传本机文件(fileRestoreRecords);托管恢复传自己的实现。不传就不记 */ restoreRecords?: RestoreRecordStore; }): Promise; export interface RestoreStatus { /** 给人看的目标说明 */ target: string; /** 没有记录时为 false,其余字段都没有 */ recorded: boolean; snapshot?: string; xid?: string; phase?: RestorePhase; /** 目标对这个事务的回答;目标不是记录里那个库、或者查不了时是 unknown */ transaction?: "committed" | "aborted" | "in progress" | "unknown"; /** 下一步该做什么,一句话 */ next: string; } /** * restore --status:只读。读这台机器上对这个目标的恢复记录,核对目标还是那个库,再用事务号问它提交了没有 */ export declare function restoreStatus(opts: { targetDatabaseUrl: string; restoreRecords: RestoreRecordStore; /** 连接加查询的总时限:目标不回应时到点断开,答"查不清",不一直挂着 */ timeoutMs?: number; }): Promise; /** * restore --finish:只做提交之后的步骤(补授权、补外键),和一次正常恢复用同一段代码、同一套规则(孤儿行建成 * NOT VALID 等)。只对记录里"已提交、后面的步骤没做完"的那次恢复做;目标不是那个库就拒绝。可以重复执行: * 授权本来就是幂等的,已经加上的外键定义一样就跳过,不一样就停下报告 */ export declare function finishRestore(config: BackupConfig, opts: { targetDatabaseUrl: string; confirmTarget?: string; restoreRecords: RestoreRecordStore; snapshot?: string; store?: SnapshotStore; }): Promise;