import { type GrantedObject } from "./grants.js"; /** * manifest.json —— 每次快照的自描述清单。 * 云版演练靠它做校验(表数量、行数对比、体积异常告警);开源侧至少让用户能核对 * "这次到底备了什么、有多大、校验和是多少"。 */ export interface TableStat { schema: string; name: string; estimatedRows: number; primaryKeyColumns?: string[]; exactRows?: number; sampleHash?: string; sampleAlgorithm?: string; freshnessColumn?: string; freshnessValue?: string; } /** * 源 bucket 的可恢复属性(v2,读自源库 storage.buckets——S3 兼容端点拿不到这些)。 * 值为 null = 源端就没设置;字段整体缺失 = 当次备份没捕获到(如 storage schema 无读权限), * 恢复端遇缺失须如实标 "not captured",不得猜测(PRD §5.1.3)。 */ export interface BucketAttrs { name: string; public?: boolean | null; fileSizeLimit?: number | null; allowedMimeTypes?: string[] | null; } /** * 源库安装的非内置扩展(pg_extension,排除必装的 plpgsql)。 * --schema=public 的转储不含 CREATE EXTENSION,但表定义会引用扩展类型 * (如 extensions.vector)——沙箱恢复前必须按这份清单把扩展装进**同名 schema**, * dump 里的限定名才解析得到(Supabase 惯例装在 "extensions" schema)。 */ export interface ExtensionInfo { name: string; version: string; schema: string; } /** * manifest.json 的格式版本。**这是留在客户桶里、可能躺很多年的持久产物**——将来改结构时, * 读取端要靠它分支判断,而不是去猜 toolVersion。 * * 约定: * - 缺失该字段的 manifest = 版本 1(1.0 之前写出的旧快照) * - 读取端遇到**更高**的版本必须明确报错,而不是按旧结构硬解析出错误结果 * ——对备份工具来说,"旧程序静默误读新归档"是最不能接受的失败方式。 * * 版本史: * - 1:初始格式。 * - 2(恢复闭环 PRD §5.1):storage 增加 buckets 属性清单(public/大小/MIME 限制), * files 增加 contentType/cacheControl/metadata 等恢复所需元数据;tables 预留 * 验证升级字段(本期不生成)。全部为**可加字段**——v2 读取端兼容 v1 快照。 * - (仍是 2)2.9.0 加 database.grants:可加字段,旧读取端忽略它、照旧恢复,不升版本, * 免得还没升级的 CLI 和 worker 拒读新快照。 * - (仍是 2)2.10.0 加 auth:同样是可加字段。旧读取端忽略它和 auth.dump,演练和恢复与之前完全一样。 */ export declare const MANIFEST_SCHEMA_VERSION = 2; export interface Manifest { schemaVersion?: number; tool: "backupdrill-cli"; toolVersion: string; createdAt: string; projectName: string; sourceProjectRef?: string; database: { serverVersion: string; pgDumpVersion: string; schemas: string[]; tableCount: number; estimatedRowTotal: number; tables: TableStat[]; extensions?: ExtensionInfo[]; grants?: GrantedObject[]; }; dump: { key: string; format: "custom"; bytes: number; sha256: string; }; auth?: AuthSection; storage: null | { buckets?: BucketAttrs[]; fileCount: number; totalBytes: number; files: StorageFile[]; }; } /** * Auth 用户备份的结果。只有 included 的快照带 dump 和统计;其余状态说明为什么没备上。 * - included:auth.dump 在快照里,行数可以合法为 0 * - skipped:默认开启但做不了(无权读取、没有 auth schema、存储不支持、schemas 可能已含 auth、太大),不算失败 * - failed:权限够、却在导出或上传时出错;只有显式要求时备份才因此失败 */ export declare const AUTH_STATUSES: readonly ["included", "skipped", "failed"]; export declare const AUTH_SKIP_REASONS: readonly ["no_permission", "no_auth_schema", "unsupported_destination", "schemas_include_auth", "schemas_not_plain", "too_large", "snapshot_unavailable"]; export interface AuthTriggerInfo { table: string; name: string; enabled: "O" | "D" | "R" | "A"; functionSchema: string; functionName: string; /** 触发器依赖的全部函数所在 schema(触发函数 + WHEN 条件里调用的) */ dependencySchemas?: string[]; } export interface AuthPolicyInfo { table: string; name: string; /** 策略表达式里引用的函数所在的非平台 schema;不在备份里的,恢复时建不出来 */ functionSchemas: string[]; } export interface AuthSection { status: (typeof AUTH_STATUSES)[number]; /** skipped 时是 AUTH_SKIP_REASONS 之一;failed 时是一句不带行值的说明 */ reason?: string; dump?: { key: string; format: "custom"; bytes: number; sha256: string; }; /** 两张表在共享快照里的精确行数;演练恢复后拿它核对 */ rows?: { users: number; identities: number; }; /** 密码哈希分类计数:none = 没有密码(OAuth、魔法链接、匿名);encrypted = 用项目密钥加密过,换项目后不能用原密码登录 */ passwordHashes?: { none: number; portable: number; encrypted: number; unrecognized: number; }; anonymousUsers?: number; ssoUsers?: number; /** 有已验证 MFA 因子的用户数;读不到时为 null */ mfaUsers?: number | null; triggers?: AuthTriggerInfo[]; policies?: AuthPolicyInfo[]; /** * 两张表上的授权(aclexplode),新项目由平台另设一套;用户自己加的(例如给 authenticated 授 SELECT) * 恢复时拿它比对、列成手工步骤。2.10.0 起写入;缺席 = 当时没记 */ grants?: Array<{ table: string; grantee: string; privilege: string; }>; } export interface StorageFile { bucket: string; key: string; bytes: number; sha256: string; contentType?: string; cacheControl?: string; contentEncoding?: string; metadata?: Record; lastModified?: string; } /** * 解析桶里的 manifest.json,并做**向前兼容**校验:比本程序更新的格式一律拒绝, * 明确告诉用户升级,绝不按旧结构硬解析(那会静默产出错误的演练/恢复结果)。 * 版本关之后再做结构校验(assertManifestShape)——两类失败要给出不同的处置指引。 */ export declare function parseManifest(json: string): Manifest;