From ae8e1f2841c04ae1710d6851e0ab1703d6121df9 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 20 Aug 2026 15:17:31 -0500 Subject: [PATCH 01/12] feat(config): add config diff classification core (CLI-2156) Adds the pure comparison engine for supabase config diff: a managed-surface table (defined by the v2 project-config translation, so unmapped schema paths are unmanaged by construction), a change-set classifier with update / remote_only / local_only classes, order-insensitive type-aware equality, byte-size canonicalization, masked-secret transparency, and env-var name threading through the interpolation pipeline onto value origins. Co-Authored-By: Claude Fable 5 --- packages/config/src/config-diff.auth.ts | 461 +++++++++++++++++++ packages/config/src/config-diff.managed.ts | 200 ++++++++ packages/config/src/config-diff.read.ts | 151 ++++++ packages/config/src/config-diff.ts | 288 ++++++++++++ packages/config/src/config-diff.unit.test.ts | 297 ++++++++++++ packages/config/src/index.ts | 14 + packages/config/src/io.ts | 18 +- packages/config/src/lib/env.ts | 30 +- 8 files changed, 1444 insertions(+), 15 deletions(-) create mode 100644 packages/config/src/config-diff.auth.ts create mode 100644 packages/config/src/config-diff.managed.ts create mode 100644 packages/config/src/config-diff.read.ts create mode 100644 packages/config/src/config-diff.ts create mode 100644 packages/config/src/config-diff.unit.test.ts diff --git a/packages/config/src/config-diff.auth.ts b/packages/config/src/config-diff.auth.ts new file mode 100644 index 0000000000..1286ea2a7e --- /dev/null +++ b/packages/config/src/config-diff.auth.ts @@ -0,0 +1,461 @@ +import type { ManagedConfigProperty, RemoteProjectConfig } from "./config-diff.ts"; +import { + coerceRemoteScalar, + isRemoteRecord, + managedScalar, + managedStringList, + remoteValueAt, + type RemoteScalarKind, +} from "./config-diff.read.ts"; + +/** + * The auth portion of the managed surface (`config-diff.managed.ts`). The v2 + * `auth` block is a flat record keyed by lowercased GoTrue setting name — the + * same wire keys as the v1 `AuthConfigResponse`. Each entry maps one wire key + * to its `auth.*` config.toml path, mirroring the Go CLI's + * `FromRemoteAuthConfig` (`pkg/config/auth.go`): the same inversions + * (`disable_signup`, `mailer_autoconfirm`), duration conversions (wire + * seconds/hours to Go-style duration strings), and enum renames + * (`password_required_characters`) apply. + * + * Deliberately unmanaged: local-only fields the API never reports + * (`auth.enabled`, JWT key material, template `content_path`s, + * `auth.external.*.redirect_uri`), `auth.third_party.*` (not part of the + * gotrue config record), `auth.sms.test_otp` (a record-valued map, not a + * leaf), and wire keys with no local schema path (`passkey_enabled`, + * `webauthn_rp_*`, `external_figma_*`, SAML, OAuth server flags). + */ + +function readAuthValue(remote: RemoteProjectConfig, key: string): unknown { + return remoteValueAt(remote, "auth", [key]); +} + +function authScalar( + path: string, + remoteKey: string, + kind: RemoteScalarKind, +): ManagedConfigProperty { + return managedScalar({ path, block: "auth", remotePath: [remoteKey], kind }); +} + +function authSecret(path: string, remoteKey: string): ManagedConfigProperty { + return managedScalar({ + path, + block: "auth", + remotePath: [remoteKey], + kind: "string", + secret: true, + }); +} + +/** + * Inverted booleans: Go reads `EnableSignup = !DisableSignup` and + * `EnableConfirmations = !MailerAutoconfirm`. Only an actual boolean is + * negated; anything else (including "not returned") passes through so drift + * against an unexpected wire shape is reported rather than swallowed. + */ +function readNegatedBoolean(remoteKey: string) { + return (remote: RemoteProjectConfig): unknown => { + const value = coerceRemoteScalar(readAuthValue(remote, remoteKey), "boolean"); + return typeof value === "boolean" ? !value : value; + }; +} + +const GO_DURATION_UNIT_SECONDS = new Map([ + ["ns", 1e-9], + ["us", 1e-6], + ["µs", 1e-6], + ["ms", 1e-3], + ["s", 1], + ["m", 60], + ["h", 3600], +]); + +/** + * Canonicalizes Go-style duration strings (`"1h30m"`, `"5s"`, `"0"`) to + * seconds for comparison, matching `time.ParseDuration` for the non-negative + * durations the schema uses. Unparseable strings pass through so they still + * compare (and report) as-is. + */ +function normalizeGoDuration(value: unknown): unknown { + if (typeof value !== "string") { + return value; + } + const trimmed = value.trim(); + if (trimmed === "0") { + return 0; + } + const component = /(\d+(?:\.\d*)?|\.\d+)(ns|us|µs|ms|s|m|h)/y; + let total = 0; + let index = 0; + while (index < trimmed.length) { + component.lastIndex = index; + const match = component.exec(trimmed); + if (match === null) { + return value; + } + total += Number(match[1]) * (GO_DURATION_UNIT_SECONDS.get(match[2]!) ?? 0); + index = component.lastIndex; + } + return index > 0 ? total : value; +} + +/** + * A local Go-duration string fed by a wire number of seconds or hours (e.g. + * `smtp_max_frequency` seconds, `sessions_timebox` hours). The remote value is + * rendered as `""` and both sides normalize through + * {@link normalizeGoDuration}, so `"1h30m"` still equals a wire `1.5` hours. + */ +function authDuration(path: string, remoteKey: string, unit: "s" | "h"): ManagedConfigProperty { + return { + path, + block: "auth", + normalize: normalizeGoDuration, + read: (remote) => { + const value = coerceRemoteScalar(readAuthValue(remote, remoteKey), "number"); + return typeof value === "number" ? `${value}${unit}` : value; + }, + }; +} + +/** + * `password_required_characters` reports a character-class string; the local + * schema stores an enum name (Go's `NewPasswordRequirement`). Unknown wire + * values pass through unmapped so they surface as drift. + */ +const PASSWORD_REQUIREMENTS_BY_REQUIRED_CHARACTERS = new Map([ + ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", "letters_digits"], + [ + "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", + "lower_upper_letters_digits", + ], + [ + "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~", + "lower_upper_letters_digits_symbols", + ], +]); + +// -- Core / site -------------------------------------------------------------- + +const CORE_PROPERTIES: ReadonlyArray = [ + authScalar("auth.site_url", "site_url", "string"), + managedStringList({ + path: "auth.additional_redirect_urls", + block: "auth", + remotePath: ["uri_allow_list"], + }), + authScalar("auth.jwt_expiry", "jwt_exp", "number"), + authScalar("auth.enable_refresh_token_rotation", "refresh_token_rotation_enabled", "boolean"), + authScalar( + "auth.refresh_token_reuse_interval", + "security_refresh_token_reuse_interval", + "number", + ), + authScalar("auth.enable_manual_linking", "security_manual_linking_enabled", "boolean"), + // Go: `a.EnableSignup = !DisableSignup` (auth.go:454). + { path: "auth.enable_signup", block: "auth", read: readNegatedBoolean("disable_signup") }, + authScalar("auth.enable_anonymous_sign_ins", "external_anonymous_users_enabled", "boolean"), + authScalar("auth.minimum_password_length", "password_min_length", "number"), + { + path: "auth.password_requirements", + block: "auth", + read: (remote) => { + const value = coerceRemoteScalar( + readAuthValue(remote, "password_required_characters"), + "string", + ); + if (typeof value !== "string") { + return value; + } + return PASSWORD_REQUIREMENTS_BY_REQUIRED_CHARACTERS.get(value) ?? value; + }, + }, +]; + +// -- Email -------------------------------------------------------------------- + +const EMAIL_TEMPLATE_NAMES = [ + "invite", + "confirmation", + "recovery", + "magic_link", + "email_change", + "reauthentication", +]; + +const EMAIL_NOTIFICATION_NAMES = [ + "password_changed", + "email_changed", + "phone_changed", + "identity_linked", + "identity_unlinked", + "mfa_factor_enrolled", + "mfa_factor_unenrolled", +]; + +const EMAIL_PROPERTIES: ReadonlyArray = [ + authScalar("auth.email.enable_signup", "external_email_enabled", "boolean"), + authScalar("auth.email.double_confirm_changes", "mailer_secure_email_change_enabled", "boolean"), + // Go: `e.EnableConfirmations = !MailerAutoconfirm` (auth.go:825). + { + path: "auth.email.enable_confirmations", + block: "auth", + read: readNegatedBoolean("mailer_autoconfirm"), + }, + authScalar( + "auth.email.secure_password_change", + "security_update_password_require_reauthentication", + "boolean", + ), + authDuration("auth.email.max_frequency", "smtp_max_frequency", "s"), + authScalar("auth.email.otp_length", "mailer_otp_length", "number"), + authScalar("auth.email.otp_expiry", "mailer_otp_exp", "number"), + // Go derives enablement from `smtp_host` presence: the platform clears every + // SMTP field when custom SMTP is off (auth.go:1115: `Enabled = SmtpHost != nil`). + { + path: "auth.email.smtp.enabled", + block: "auth", + read: (remote) => { + if (!isRemoteRecord(remote.auth)) { + return undefined; + } + return readAuthValue(remote, "smtp_host") !== undefined; + }, + }, + authScalar("auth.email.smtp.host", "smtp_host", "string"), + // The wire reports the port as a string; the local schema types it a number. + authScalar("auth.email.smtp.port", "smtp_port", "number"), + authScalar("auth.email.smtp.user", "smtp_user", "string"), + authSecret("auth.email.smtp.pass", "smtp_pass"), + authScalar("auth.email.smtp.admin_email", "smtp_admin_email", "string"), + authScalar("auth.email.smtp.sender_name", "smtp_sender_name", "string"), + // Template subjects only: local templates store bodies as `content_path` + // files, which the wire never reports. + ...EMAIL_TEMPLATE_NAMES.map((name) => + authScalar(`auth.email.template.${name}.subject`, `mailer_subjects_${name}`, "string"), + ), + ...EMAIL_NOTIFICATION_NAMES.flatMap((name) => [ + authScalar( + `auth.email.notification.${name}.enabled`, + `mailer_notifications_${name}_enabled`, + "boolean", + ), + authScalar( + `auth.email.notification.${name}.subject`, + `mailer_subjects_${name}_notification`, + "string", + ), + ]), +]; + +// -- SMS ---------------------------------------------------------------------- + +/** + * The wire reports a single `sms_provider`; Go fans it out to per-provider + * `enabled` flags (auth.go:1207-1213). An empty provider reads as "not + * returned" because Go leaves the local flags untouched in that case. + */ +function readSmsProviderEnabled(provider: string) { + return (remote: RemoteProjectConfig): unknown => { + const value = coerceRemoteScalar(readAuthValue(remote, "sms_provider"), "string"); + if (typeof value !== "string") { + return value; + } + return value === "" ? undefined : value === provider; + }; +} + +const SMS_PROVIDER_IDS = ["twilio", "twilio_verify", "messagebird", "textlocal", "vonage"]; + +const SMS_PROPERTIES: ReadonlyArray = [ + authScalar("auth.sms.enable_signup", "external_phone_enabled", "boolean"), + authScalar("auth.sms.enable_confirmations", "sms_autoconfirm", "boolean"), + authScalar("auth.sms.template", "sms_template", "string"), + authDuration("auth.sms.max_frequency", "sms_max_frequency", "s"), + ...SMS_PROVIDER_IDS.map((provider): ManagedConfigProperty => ({ + path: `auth.sms.${provider}.enabled`, + block: "auth", + read: readSmsProviderEnabled(provider), + })), + authScalar("auth.sms.twilio.account_sid", "sms_twilio_account_sid", "string"), + authScalar("auth.sms.twilio.message_service_sid", "sms_twilio_message_service_sid", "string"), + authSecret("auth.sms.twilio.auth_token", "sms_twilio_auth_token"), + authScalar("auth.sms.twilio_verify.account_sid", "sms_twilio_verify_account_sid", "string"), + authScalar( + "auth.sms.twilio_verify.message_service_sid", + "sms_twilio_verify_message_service_sid", + "string", + ), + authSecret("auth.sms.twilio_verify.auth_token", "sms_twilio_verify_auth_token"), + authScalar("auth.sms.messagebird.originator", "sms_messagebird_originator", "string"), + authSecret("auth.sms.messagebird.access_key", "sms_messagebird_access_key"), + authScalar("auth.sms.textlocal.sender", "sms_textlocal_sender", "string"), + authSecret("auth.sms.textlocal.api_key", "sms_textlocal_api_key"), + authScalar("auth.sms.vonage.from", "sms_vonage_from", "string"), + authScalar("auth.sms.vonage.api_key", "sms_vonage_api_key", "string"), + authSecret("auth.sms.vonage.api_secret", "sms_vonage_api_secret"), +]; + +// -- MFA ---------------------------------------------------------------------- + +const MFA_PROPERTIES: ReadonlyArray = [ + authScalar("auth.mfa.max_enrolled_factors", "mfa_max_enrolled_factors", "number"), + authScalar("auth.mfa.totp.enroll_enabled", "mfa_totp_enroll_enabled", "boolean"), + authScalar("auth.mfa.totp.verify_enabled", "mfa_totp_verify_enabled", "boolean"), + authScalar("auth.mfa.phone.enroll_enabled", "mfa_phone_enroll_enabled", "boolean"), + authScalar("auth.mfa.phone.verify_enabled", "mfa_phone_verify_enabled", "boolean"), + authScalar("auth.mfa.phone.otp_length", "mfa_phone_otp_length", "number"), + authScalar("auth.mfa.phone.template", "mfa_phone_template", "string"), + authDuration("auth.mfa.phone.max_frequency", "mfa_phone_max_frequency", "s"), + authScalar("auth.mfa.web_authn.enroll_enabled", "mfa_web_authn_enroll_enabled", "boolean"), + authScalar("auth.mfa.web_authn.verify_enabled", "mfa_web_authn_verify_enabled", "boolean"), +]; + +// -- External OAuth providers --------------------------------------------------- + +interface OAuthProviderSpec { + readonly id: string; + /** Wire reports `external__url` (azure, gitlab, keycloak, workos). */ + readonly url?: boolean; + /** Wire reports `external__email_optional` (every provider but workos). */ + readonly emailOptional?: boolean; + /** Wire splits extra client ids into `external__additional_client_ids`. */ + readonly additionalClientIds?: boolean; + /** Wire reports `external__skip_nonce_check` (google only). */ + readonly skipNonceCheck?: boolean; +} + +/** + * The providers the local schema declares (`auth/providers.ts`), in schema + * order. Go also maps `figma`, which the local schema does not model. The + * local `redirect_uri` field (and `url`/`skip_nonce_check` on providers whose + * wire block omits them) has no remote counterpart and stays unmanaged. + */ +const OAUTH_PROVIDERS: ReadonlyArray = [ + { id: "apple", additionalClientIds: true, emailOptional: true }, + { id: "azure", url: true, emailOptional: true }, + { id: "bitbucket", emailOptional: true }, + { id: "discord", emailOptional: true }, + { id: "facebook", emailOptional: true }, + { id: "github", emailOptional: true }, + { id: "gitlab", url: true, emailOptional: true }, + { id: "google", additionalClientIds: true, skipNonceCheck: true, emailOptional: true }, + { id: "kakao", emailOptional: true }, + { id: "keycloak", url: true, emailOptional: true }, + { id: "linkedin_oidc", emailOptional: true }, + { id: "notion", emailOptional: true }, + { id: "twitch", emailOptional: true }, + { id: "twitter", emailOptional: true }, + { id: "x", emailOptional: true }, + { id: "slack_oidc", emailOptional: true }, + { id: "spotify", emailOptional: true }, + { id: "workos", url: true }, + { id: "zoom", emailOptional: true }, +]; + +function oauthProviderEntries(spec: OAuthProviderSpec): ReadonlyArray { + const prefix = `auth.external.${spec.id}`; + const wire = `external_${spec.id}`; + const entries: Array = [ + authScalar(`${prefix}.enabled`, `${wire}_enabled`, "boolean"), + ]; + if (spec.additionalClientIds === true) { + // Go folds `additional_client_ids` back into the comma-joined local + // `client_id` (auth.go:1415-1417, 1516-1518). + entries.push({ + path: `${prefix}.client_id`, + block: "auth", + read: (remote) => { + const clientId = coerceRemoteScalar(readAuthValue(remote, `${wire}_client_id`), "string"); + const additional = coerceRemoteScalar( + readAuthValue(remote, `${wire}_additional_client_ids`), + "string", + ); + if (typeof clientId !== "string" || typeof additional !== "string" || additional === "") { + return clientId; + } + return `${clientId},${additional}`; + }, + }); + } else { + entries.push(authScalar(`${prefix}.client_id`, `${wire}_client_id`, "string")); + } + entries.push(authSecret(`${prefix}.secret`, `${wire}_secret`)); + if (spec.url === true) { + entries.push(authScalar(`${prefix}.url`, `${wire}_url`, "string")); + } + if (spec.skipNonceCheck === true) { + entries.push(authScalar(`${prefix}.skip_nonce_check`, `${wire}_skip_nonce_check`, "boolean")); + } + if (spec.emailOptional === true) { + entries.push(authScalar(`${prefix}.email_optional`, `${wire}_email_optional`, "boolean")); + } + return entries; +} + +const EXTERNAL_PROPERTIES: ReadonlyArray = + OAUTH_PROVIDERS.flatMap(oauthProviderEntries); + +// -- Sessions ------------------------------------------------------------------- + +const SESSION_PROPERTIES: ReadonlyArray = [ + authDuration("auth.sessions.timebox", "sessions_timebox", "h"), + authDuration("auth.sessions.inactivity_timeout", "sessions_inactivity_timeout", "h"), +]; + +// -- Rate limits ---------------------------------------------------------------- + +const RATE_LIMIT_PROPERTIES: ReadonlyArray = [ + authScalar("auth.rate_limit.email_sent", "rate_limit_email_sent", "number"), + authScalar("auth.rate_limit.sms_sent", "rate_limit_sms_sent", "number"), + authScalar("auth.rate_limit.anonymous_users", "rate_limit_anonymous_users", "number"), + authScalar("auth.rate_limit.token_refresh", "rate_limit_token_refresh", "number"), + authScalar("auth.rate_limit.sign_in_sign_ups", "rate_limit_otp", "number"), + authScalar("auth.rate_limit.token_verifications", "rate_limit_verify", "number"), + authScalar("auth.rate_limit.web3", "rate_limit_web3", "number"), +]; + +// -- Captcha -------------------------------------------------------------------- + +const CAPTCHA_PROPERTIES: ReadonlyArray = [ + authScalar("auth.captcha.enabled", "security_captcha_enabled", "boolean"), + authScalar("auth.captcha.provider", "security_captcha_provider", "string"), + authSecret("auth.captcha.secret", "security_captcha_secret"), +]; + +// -- Web3 ----------------------------------------------------------------------- + +const WEB3_PROPERTIES: ReadonlyArray = [ + authScalar("auth.web3.solana.enabled", "external_web3_solana_enabled", "boolean"), + authScalar("auth.web3.ethereum.enabled", "external_web3_ethereum_enabled", "boolean"), +]; + +// -- Hooks ---------------------------------------------------------------------- + +const HOOK_NAMES = [ + "mfa_verification_attempt", + "password_verification_attempt", + "custom_access_token", + "send_sms", + "send_email", + "before_user_created", +]; + +const HOOK_PROPERTIES: ReadonlyArray = HOOK_NAMES.flatMap((name) => [ + authScalar(`auth.hook.${name}.enabled`, `hook_${name}_enabled`, "boolean"), + authScalar(`auth.hook.${name}.uri`, `hook_${name}_uri`, "string"), + authSecret(`auth.hook.${name}.secrets`, `hook_${name}_secrets`), +]); + +export const AUTH_MANAGED_CONFIG_PROPERTIES: ReadonlyArray = [ + ...CORE_PROPERTIES, + ...EMAIL_PROPERTIES, + ...SMS_PROPERTIES, + ...MFA_PROPERTIES, + ...EXTERNAL_PROPERTIES, + ...SESSION_PROPERTIES, + ...RATE_LIMIT_PROPERTIES, + ...CAPTCHA_PROPERTIES, + ...WEB3_PROPERTIES, + ...HOOK_PROPERTIES, +]; diff --git a/packages/config/src/config-diff.managed.ts b/packages/config/src/config-diff.managed.ts new file mode 100644 index 0000000000..a246f5647d --- /dev/null +++ b/packages/config/src/config-diff.managed.ts @@ -0,0 +1,200 @@ +import type { ManagedConfigProperty } from "./config-diff.ts"; +import { AUTH_MANAGED_CONFIG_PROPERTIES } from "./config-diff.auth.ts"; +import { + isRemoteRecord, + managedScalar, + managedStringList, + normalizeByteSize, + remoteValueAt, + type RemoteScalarKind, +} from "./config-diff.read.ts"; + +/** + * The managed surface: every local schema path the v2 project-config resource + * can report, with its reader. A local path with no entry here is unmanaged by + * construction — `[studio]`, `[local_smtp]`, ports, image pins, TLS material, + * `db.migrations`/`db.seed`, `storage.buckets` content, and the entire local + * `[realtime]` section (its local fields — `enabled`, `ip_version`, + * `max_header_length` — configure the local container only; none of the v2 + * `realtime` block's platform limits have a config.toml counterpart). + */ + +const API_PROPERTIES: ReadonlyArray = [ + managedStringList({ path: "api.schemas", block: "api", remotePath: ["db_schema"] }), + managedStringList({ + path: "api.extra_search_path", + block: "api", + remotePath: ["db_extra_search_path"], + }), + managedScalar({ path: "api.max_rows", block: "api", remotePath: ["max_rows"], kind: "number" }), +]; + +/** + * `db.settings.*` ↔ `database.postgres_settings.*`. The wire block carries + * more settings than the local schema declares; only locally-representable + * ones are managed. Kinds mirror `db.ts`'s `settings` struct. + */ +const POSTGRES_SETTINGS: ReadonlyArray = [ + ["effective_cache_size", "string"], + ["logical_decoding_work_mem", "string"], + ["maintenance_work_mem", "string"], + ["max_connections", "number"], + ["max_locks_per_transaction", "number"], + ["max_parallel_maintenance_workers", "number"], + ["max_parallel_workers", "number"], + ["max_parallel_workers_per_gather", "number"], + ["max_replication_slots", "number"], + ["max_slot_wal_keep_size", "string"], + ["max_standby_archive_delay", "string"], + ["max_standby_streaming_delay", "string"], + ["max_wal_size", "string"], + ["max_wal_senders", "number"], + ["max_worker_processes", "number"], + ["session_replication_role", "string"], + ["shared_buffers", "string"], + ["statement_timeout", "string"], + ["track_activity_query_size", "string"], + ["track_commit_timestamp", "boolean"], + ["wal_keep_size", "string"], + ["wal_sender_timeout", "string"], + ["work_mem", "string"], +]; + +function readAllowedCidrs(kind: "v4" | "v6") { + return (remote: Parameters[0]): unknown => { + const entries = remoteValueAt(remote, "database", ["network_restrictions", "allowed_cidrs"]); + if (!Array.isArray(entries)) { + return undefined; + } + return entries + .filter(isRemoteRecord) + .filter((entry) => entry["type"] === kind) + .map((entry) => entry["address"]) + .filter((address): address is string => typeof address === "string"); + }; +} + +const DATABASE_PROPERTIES: ReadonlyArray = [ + managedScalar({ + path: "db.ssl_enforcement.enabled", + block: "database", + remotePath: ["ssl_enforced"], + kind: "boolean", + }), + { + path: "db.network_restrictions.allowed_cidrs", + block: "database", + read: readAllowedCidrs("v4"), + }, + { + path: "db.network_restrictions.allowed_cidrs_v6", + block: "database", + read: readAllowedCidrs("v6"), + }, + ...POSTGRES_SETTINGS.map(([name, kind]) => + managedScalar({ + path: `db.settings.${name}`, + block: "database", + remotePath: ["postgres_settings", name], + kind, + }), + ), +]; + +const POOLER_PROPERTIES: ReadonlyArray = [ + managedScalar({ + path: "db.pooler.pool_mode", + block: "pooler", + remotePath: ["pool_mode"], + kind: "string", + }), + managedScalar({ + path: "db.pooler.default_pool_size", + block: "pooler", + remotePath: ["default_pool_size"], + kind: "number", + }), + managedScalar({ + path: "db.pooler.max_client_conn", + block: "pooler", + remotePath: ["max_client_conn"], + kind: "number", + }), +]; + +const STORAGE_PROPERTIES: ReadonlyArray = [ + managedScalar({ + path: "storage.file_size_limit", + block: "storage", + remotePath: ["file_size_limit"], + kind: "string", + normalize: normalizeByteSize, + }), + managedScalar({ + path: "storage.image_transformation.enabled", + block: "storage", + remotePath: ["features", "image_transformation", "enabled"], + kind: "boolean", + }), + managedScalar({ + path: "storage.s3_protocol.enabled", + block: "storage", + remotePath: ["features", "s3_protocol", "enabled"], + kind: "boolean", + }), + managedScalar({ + path: "storage.analytics.enabled", + block: "storage", + remotePath: ["features", "iceberg_catalog", "enabled"], + kind: "boolean", + }), + managedScalar({ + path: "storage.analytics.max_namespaces", + block: "storage", + remotePath: ["features", "iceberg_catalog", "max_namespaces"], + kind: "number", + }), + managedScalar({ + path: "storage.analytics.max_tables", + block: "storage", + remotePath: ["features", "iceberg_catalog", "max_tables"], + kind: "number", + }), + managedScalar({ + path: "storage.analytics.max_catalogs", + block: "storage", + remotePath: ["features", "iceberg_catalog", "max_catalogs"], + kind: "number", + }), + managedScalar({ + path: "storage.vector.enabled", + block: "storage", + remotePath: ["features", "vector_buckets", "enabled"], + kind: "boolean", + }), + managedScalar({ + path: "storage.vector.max_buckets", + block: "storage", + remotePath: ["features", "vector_buckets", "max_buckets"], + kind: "number", + }), + managedScalar({ + path: "storage.vector.max_indexes", + block: "storage", + remotePath: ["features", "vector_buckets", "max_indexes"], + kind: "number", + }), +]; + +export const MANAGED_CONFIG_PROPERTIES: ReadonlyArray = [ + ...API_PROPERTIES, + ...AUTH_MANAGED_CONFIG_PROPERTIES, + ...DATABASE_PROPERTIES, + ...POOLER_PROPERTIES, + ...STORAGE_PROPERTIES, +]; + +/** Dotted local schema paths of the managed surface. */ +export const MANAGED_CONFIG_PATHS: ReadonlySet = new Set( + MANAGED_CONFIG_PROPERTIES.map((property) => property.path), +); diff --git a/packages/config/src/config-diff.read.ts b/packages/config/src/config-diff.read.ts new file mode 100644 index 0000000000..18d82d7b62 --- /dev/null +++ b/packages/config/src/config-diff.read.ts @@ -0,0 +1,151 @@ +import type { + ManagedConfigProperty, + RemoteConfigBlock, + RemoteProjectConfig, +} from "./config-diff.ts"; + +/** + * Reader/constructor helpers for the managed-surface table + * (`config-diff.managed.ts`, `config-diff.auth.ts`). Every reader descends the + * loosely-typed v2 response with runtime guards and coerces the wire value to + * the local schema's type, so the classifier compares like with like. + */ + +export function isRemoteRecord(value: unknown): value is Readonly> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/** + * Reads a nested value from a response block. `undefined` means "not + * returned"; an explicit `null` also reads as not returned (the API uses it + * for "no value set", e.g. `api.db_pool`). + */ +export function remoteValueAt( + remote: RemoteProjectConfig, + block: RemoteConfigBlock, + segments: ReadonlyArray, +): unknown { + let current: unknown = remote[block]; + for (const segment of segments) { + if (!isRemoteRecord(current) || !Object.hasOwn(current, segment)) { + return undefined; + } + current = current[segment]; + } + return current === null ? undefined : current; +} + +export type RemoteScalarKind = "string" | "number" | "boolean"; + +const REMOTE_BOOL_TRUE = new Set(["true", "1"]); +const REMOTE_BOOL_FALSE = new Set(["false", "0"]); + +/** + * Coerces a wire scalar to the local schema's primitive kind. Unconvertible + * values pass through unchanged so drift against an unexpected wire shape is + * reported rather than swallowed. + */ +export function coerceRemoteScalar(value: unknown, kind: RemoteScalarKind): unknown { + if (value === undefined) { + return undefined; + } + switch (kind) { + case "number": { + if (typeof value === "string" && value.trim() !== "") { + const parsed = Number(value.trim()); + return Number.isFinite(parsed) ? parsed : value; + } + return value; + } + case "boolean": { + if (typeof value === "string") { + const lowered = value.trim().toLowerCase(); + if (REMOTE_BOOL_TRUE.has(lowered)) return true; + if (REMOTE_BOOL_FALSE.has(lowered)) return false; + } + return value; + } + case "string": { + if (typeof value === "number" || typeof value === "boolean") { + return String(value); + } + return value; + } + } +} + +export interface ManagedScalarOptions { + /** Dotted local schema path. */ + readonly path: string; + readonly block: RemoteConfigBlock; + /** Segments below the block, e.g. `["postgres_settings", "work_mem"]`. */ + readonly remotePath: ReadonlyArray; + readonly kind: RemoteScalarKind; + readonly secret?: boolean; + readonly normalize?: (value: unknown) => unknown; +} + +export function managedScalar(options: ManagedScalarOptions): ManagedConfigProperty { + return { + path: options.path, + block: options.block, + ...(options.secret === true ? { secret: true } : {}), + ...(options.normalize === undefined ? {} : { normalize: options.normalize }), + read: (remote) => + coerceRemoteScalar(remoteValueAt(remote, options.block, options.remotePath), options.kind), + }; +} + +export interface ManagedListOptions { + readonly path: string; + readonly block: RemoteConfigBlock; + readonly remotePath: ReadonlyArray; + readonly secret?: boolean; +} + +/** + * A local string-array property the wire reports either as a comma-joined + * string (e.g. PostgREST's `db_schema`) or as an actual array. + */ +export function managedStringList(options: ManagedListOptions): ManagedConfigProperty { + return { + path: options.path, + block: options.block, + ...(options.secret === true ? { secret: true } : {}), + read: (remote) => { + const value = remoteValueAt(remote, options.block, options.remotePath); + if (typeof value === "string") { + return value === "" + ? [] + : value + .split(",") + .map((element) => element.trim()) + .filter((element) => element !== ""); + } + if (Array.isArray(value)) { + return value; + } + return undefined; + }, + }; +} + +/** + * Canonicalizes byte-size values for comparison: the wire reports byte + * counts (`52428800`) where the file writes human-readable sizes (`"50MiB"`). + * 1024-based and case-insensitive with an optional `b`/`ib` suffix, matching + * Go's `units.RAMInBytes` semantics used by the original config loader. + * Unparseable strings pass through so they still compare (and report) as-is. + */ +export function normalizeByteSize(value: unknown): unknown { + if (typeof value !== "string") { + return value; + } + const match = /^\s*(\d*\.?\d+)\s*([kmgtp]?)(?:i?b)?\s*$/i.exec(value); + if (match === null) { + return value; + } + const magnitude = Number(match[1]); + const exponent = { "": 0, k: 1, m: 2, g: 3, t: 4, p: 5 }[match[2]!.toLowerCase()] ?? 0; + return Math.floor(magnitude * 1024 ** exponent); +} diff --git a/packages/config/src/config-diff.ts b/packages/config/src/config-diff.ts new file mode 100644 index 0000000000..426d83df34 --- /dev/null +++ b/packages/config/src/config-diff.ts @@ -0,0 +1,288 @@ +import type { BaseProjectConfig } from "./sparse.ts"; +import { getDefaultProjectConfig } from "./sparse.ts"; +import { MANAGED_CONFIG_PROPERTIES } from "./config-diff.managed.ts"; + +/** + * Config drift classification between a local project config and the + * effective remote configuration reported by the Management API + * (`GET /v2/projects/{ref}/config`). Pure and synchronous: fetching the + * response, resolving the target, and rendering output are the caller's job + * (`supabase config diff`, and `config pull` after it). See ADR 0019. + */ + +/** The per-service blocks of the v2 project-config resource. */ +export type RemoteConfigBlock = "api" | "auth" | "database" | "pooler" | "realtime" | "storage"; + +export const REMOTE_CONFIG_BLOCKS: ReadonlyArray = [ + "api", + "auth", + "database", + "pooler", + "realtime", + "storage", +]; + +/** + * Structural shape of the v2 response's `data.attributes`. Deliberately loose + * (`Record` per block): the wire format is owned by the + * Management API and may grow keys at any time, and every read below descends + * with runtime guards. This package must not import `@supabase/api` — the + * caller passes whatever the generated client decoded. + */ +export interface RemoteProjectConfig { + readonly api?: Readonly> | undefined; + readonly auth?: Readonly> | undefined; + readonly database?: Readonly> | undefined; + readonly pooler?: Readonly> | undefined; + readonly realtime?: Readonly> | undefined; + readonly storage?: Readonly> | undefined; +} + +/** + * One remotely-managed local schema property. The managed surface is *defined* + * by the table of these entries (`config-diff.managed.ts`): a schema path with + * no entry is unmanaged by construction and never appears in a change set. + */ +export interface ManagedConfigProperty { + /** Dotted local schema path, e.g. `"api.max_rows"`. Always a leaf. */ + readonly path: string; + /** Which v2 block reports this property. */ + readonly block: RemoteConfigBlock; + /** + * Secret-valued: the platform reports an HMAC (or omits the value), never + * plaintext. The property is "present, unknown" — excluded from comparison + * and surfaced via {@link ConfigChangeSet.masked} instead. + */ + readonly secret?: boolean; + /** + * Reads this property's value from the response, coerced to the local + * schema's type. `undefined` means the response did not carry it. + */ + readonly read: (remote: RemoteProjectConfig) => unknown; + /** + * Canonicalizes a value before equality on both sides (e.g. byte-size + * strings to byte counts). Reported values stay un-normalized. + */ + readonly normalize?: (value: unknown) => unknown; +} + +export type ConfigChangeClass = "update" | "remote_only" | "local_only"; + +export interface ConfigChange { + /** Dotted local schema path. */ + readonly path: string; + /** + * `update`: declared locally and returned remotely, values differ. + * `remote_only`: returned remotely, not declared in the file, and differing + * from the schema default. `local_only`: declared in the file but the + * response did not account for it. + */ + readonly class: ConfigChangeClass; + /** Effective local value; `undefined` when the file does not declare it. */ + readonly local: unknown; + /** Remote value; `undefined` when the response did not return it. */ + readonly remote: unknown; + /** Environment variable a local `env()` reference resolved from, if any. */ + readonly envVariable?: string | undefined; +} + +export interface ConfigChangeCounts { + readonly update: number; + readonly remote_only: number; + readonly local_only: number; +} + +export interface ConfigChangeSet { + /** Reportable differences, ordered by path. */ + readonly changes: ReadonlyArray; + /** + * Managed secret paths the file sets a value for. These were never compared + * (the platform masks them), so a clean `changes` list is still only a + * partial claim — callers must surface this. + */ + readonly masked: ReadonlyArray; + /** Blocks the response actually carried, ordered per {@link REMOTE_CONFIG_BLOCKS}. */ + readonly scope: ReadonlyArray; + readonly counts: ConfigChangeCounts; +} + +export interface DiffProjectConfigOptions { + /** + * The *effective* local config: decoded with defaults filled, `env()` + * resolved, and — when the target is a branch with a matching `[remotes.*]` + * block — merged per ADR 0018. + */ + readonly local: BaseProjectConfig; + /** + * The raw (pre-decode, post-merge) document the config was loaded from. + * Declares which paths the file actually sets — the decoded config cannot, + * because decoding materializes every default. + */ + readonly declared: Readonly>; + readonly remote: RemoteProjectConfig; + /** + * Baseline for `remote_only` suppression: a remote value equal to this + * config's value at the same path is not drift. Defaults to the current + * schema's default config. + */ + readonly defaults?: BaseProjectConfig; + /** Dotted local path → environment variable name, for `env()` reporting. */ + readonly envReferences?: ReadonlyMap; +} + +function isPlainRecord(value: unknown): value is Readonly> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/** Walks a dotted path through records with own-key checks only. */ +function valueAtPath(root: unknown, path: string): unknown { + let current: unknown = root; + for (const segment of path.split(".")) { + if (!isPlainRecord(current) || !Object.hasOwn(current, segment)) { + return undefined; + } + current = current[segment]; + } + return current; +} + +function isDeclaredAtPath(root: Readonly>, path: string): boolean { + let current: unknown = root; + const segments = path.split("."); + for (const [index, segment] of segments.entries()) { + if (!isPlainRecord(current) || !Object.hasOwn(current, segment)) { + return false; + } + if (index < segments.length - 1) { + current = current[segment]; + } + } + return true; +} + +function scalarEqual(a: unknown, b: unknown): boolean { + if (a === b) { + return true; + } + // Type-aware comparison: the response may carry "8080" where the schema + // types the property as a number (or vice versa) — that is not drift. + if (typeof a === "string" && typeof b === "number") { + const parsed = Number(a.trim()); + return a.trim() !== "" && Number.isFinite(parsed) && parsed === b; + } + if (typeof a === "number" && typeof b === "string") { + return scalarEqual(b, a); + } + if (typeof a === "string" && typeof b === "boolean") { + return a.trim().toLowerCase() === String(b); + } + if (typeof a === "boolean" && typeof b === "string") { + return scalarEqual(b, a); + } + return false; +} + +function canonicalArrayElement(value: unknown): string { + if (typeof value === "string") { + return `s:${value}`; + } + if (typeof value === "number" || typeof value === "boolean") { + // Scalars fold to their string form so "1" and 1 compare equal, matching + // the scalar type-awareness above. + return `s:${String(value)}`; + } + return `j:${JSON.stringify(value)}`; +} + +/** + * Order-insensitive, type-aware value equality: arrays compare as multisets + * (`additional_redirect_urls` in a different order is not a difference), and + * scalars tolerate string/number and string/boolean representation skew. + */ +export function isEqualConfigValue(a: unknown, b: unknown): boolean { + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + const left = a.map(canonicalArrayElement).sort(); + const right = b.map(canonicalArrayElement).sort(); + return left.every((element, index) => element === right[index]); + } + return scalarEqual(a, b); +} + +/** + * Classifies every managed property into the change set. Pure: no I/O, no + * dependency on command flags or output formatting. + */ +export function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChangeSet { + const defaults = options.defaults ?? getDefaultProjectConfig(); + const changes: Array = []; + const masked: Array = []; + + for (const property of MANAGED_CONFIG_PROPERTIES) { + const declared = isDeclaredAtPath(options.declared, property.path); + + if (property.secret === true) { + if (declared) { + masked.push(property.path); + } + continue; + } + + const remoteValue = property.read(options.remote); + const localValue = valueAtPath(options.local, property.path); + const normalize = property.normalize ?? ((value: unknown) => value); + const envVariable = options.envReferences?.get(property.path); + + if (remoteValue !== undefined && declared) { + if (!isEqualConfigValue(normalize(localValue), normalize(remoteValue))) { + changes.push({ + path: property.path, + class: "update", + local: localValue, + remote: remoteValue, + ...(envVariable === undefined ? {} : { envVariable }), + }); + } + continue; + } + + if (remoteValue !== undefined) { + const defaultValue = valueAtPath(defaults, property.path); + if (!isEqualConfigValue(normalize(defaultValue), normalize(remoteValue))) { + changes.push({ + path: property.path, + class: "remote_only", + local: undefined, + remote: remoteValue, + }); + } + continue; + } + + if (declared) { + changes.push({ + path: property.path, + class: "local_only", + local: localValue, + remote: undefined, + ...(envVariable === undefined ? {} : { envVariable }), + }); + } + } + + changes.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); + masked.sort(); + + return { + changes, + masked, + scope: REMOTE_CONFIG_BLOCKS.filter((block) => isPlainRecord(options.remote[block])), + counts: { + update: changes.filter((change) => change.class === "update").length, + remote_only: changes.filter((change) => change.class === "remote_only").length, + local_only: changes.filter((change) => change.class === "local_only").length, + }, + }; +} diff --git a/packages/config/src/config-diff.unit.test.ts b/packages/config/src/config-diff.unit.test.ts new file mode 100644 index 0000000000..2363236be0 --- /dev/null +++ b/packages/config/src/config-diff.unit.test.ts @@ -0,0 +1,297 @@ +import { describe, expect, test } from "vitest"; +import { Schema } from "effect"; +import { ProjectConfigSchema } from "./base.ts"; +import { + diffProjectConfig, + isEqualConfigValue, + type ConfigChange, + type DiffProjectConfigOptions, + type RemoteProjectConfig, +} from "./config-diff.ts"; +import { MANAGED_CONFIG_PATHS, MANAGED_CONFIG_PROPERTIES } from "./config-diff.managed.ts"; +import { normalizeByteSize } from "./config-diff.read.ts"; + +const decodeProjectConfig = Schema.decodeUnknownSync(ProjectConfigSchema); + +/** + * Builds the diff input the way the command layer does: `declared` is the raw + * document (key presence), `local` is its decoded effective config. + */ +function diffWith( + declared: Record, + remote: RemoteProjectConfig, + extra?: Partial, +) { + return diffProjectConfig({ + local: decodeProjectConfig(declared), + declared, + remote, + ...extra, + }); +} + +function changeAt(changes: ReadonlyArray, path: string): ConfigChange | undefined { + return changes.find((change) => change.path === path); +} + +describe("managed surface", () => { + test("declares no duplicate paths", () => { + expect(MANAGED_CONFIG_PATHS.size).toBe(MANAGED_CONFIG_PROPERTIES.length); + }); + + test("every managed path resolves to a real schema path in the default config", () => { + const defaults: unknown = decodeProjectConfig({}); + for (const path of MANAGED_CONFIG_PATHS) { + let current: unknown = defaults; + for (const segment of path.split(".")) { + if (typeof current !== "object" || current === null) { + throw new Error(`managed path ${path} leaves the schema at ${segment}`); + } + // Optional-key subtrees (db.settings, storage.image_transformation, + // auth provider entries…) are absent from the default config; their + // presence in the schema is asserted by the entries' unit coverage + // below instead. + if (!Object.hasOwn(current, segment)) { + current = undefined; + break; + } + current = (current as Record)[segment]; + } + } + }); + + test("local-only sections are unmanaged by construction", () => { + for (const prefix of ["studio.", "local_smtp.", "edge_runtime.", "analytics.", "realtime."]) { + for (const path of MANAGED_CONFIG_PATHS) { + expect(path.startsWith(prefix)).toBe(false); + } + } + expect(MANAGED_CONFIG_PATHS.has("api.port")).toBe(false); + expect(MANAGED_CONFIG_PATHS.has("db.port")).toBe(false); + }); +}); + +describe("diffProjectConfig classification", () => { + test("declared value differing from remote is an update", () => { + const result = diffWith( + { api: { max_rows: 500 } }, + { api: { max_rows: 1000, db_schema: "public,graphql_public" } }, + ); + const change = changeAt(result.changes, "api.max_rows"); + expect(change).toMatchObject({ class: "update", local: 500, remote: 1000 }); + expect(result.counts.update).toBe(1); + }); + + test("declared value equal to remote is not a difference", () => { + const result = diffWith({ api: { max_rows: 500 } }, { api: { max_rows: 500 } }); + expect(result.changes).toEqual([]); + expect(result.counts).toEqual({ update: 0, remote_only: 0, local_only: 0 }); + }); + + test("remote value at the schema default is suppressed when undeclared", () => { + const result = diffWith({}, { api: { max_rows: 1000 } }); + expect(changeAt(result.changes, "api.max_rows")).toBeUndefined(); + }); + + test("remote value off the schema default is remote_only when undeclared", () => { + const result = diffWith({}, { api: { max_rows: 250 } }); + const change = changeAt(result.changes, "api.max_rows"); + expect(change).toMatchObject({ class: "remote_only", local: undefined, remote: 250 }); + }); + + test("declared value the response does not carry is local_only", () => { + const result = diffWith( + { api: { max_rows: 500 } }, + // api block present but without max_rows, and no other blocks at all. + { api: { db_schema: "public" } }, + ); + const change = changeAt(result.changes, "api.max_rows"); + expect(change).toMatchObject({ class: "local_only", local: 500, remote: undefined }); + }); + + test("a wholly absent block turns its declared properties local_only", () => { + const result = diffWith({ db: { settings: { max_connections: 120 } } }, {}); + expect(changeAt(result.changes, "db.settings.max_connections")).toMatchObject({ + class: "local_only", + local: 120, + }); + expect(result.scope).toEqual([]); + }); + + test("unmanaged declared properties are never reported", () => { + const result = diffWith( + { + studio: { port: 55555 }, + api: { port: 4321 }, + realtime: { max_header_length: 8192 }, + local_smtp: { enabled: true }, + }, + { api: {}, realtime: { max_concurrent_users: 5 } }, + ); + expect(result.changes).toEqual([]); + }); + + test("array comparison ignores element order", () => { + const result = diffWith( + { api: { schemas: ["graphql_public", "public"] } }, + { api: { db_schema: "public,graphql_public" } }, + ); + expect(result.changes).toEqual([]); + }); + + test("comma-joined remote strings trim around separators", () => { + const result = diffWith( + { api: { extra_search_path: ["public", "extensions"] } }, + { api: { db_extra_search_path: "public, extensions" } }, + ); + expect(result.changes).toEqual([]); + }); + + test("scalar comparison is type-aware across string/number and string/boolean", () => { + const result = diffWith( + { + db: { + settings: { max_connections: 120, track_commit_timestamp: true }, + }, + }, + { + database: { + postgres_settings: { max_connections: "120", track_commit_timestamp: "true" }, + }, + }, + ); + expect(result.changes).toEqual([]); + }); + + test("byte-size values compare canonically across representations", () => { + const equal = diffWith( + { storage: { file_size_limit: "50MiB" } }, + { storage: { file_size_limit: 52428800 } }, + ); + expect(equal.changes).toEqual([]); + + const differing = diffWith( + { storage: { file_size_limit: "50MiB" } }, + { storage: { file_size_limit: 1048576 } }, + ); + // The reader coerces the wire's byte count to the local schema's string + // kind before comparison, so the reported remote value is the coerced form. + expect(changeAt(differing.changes, "storage.file_size_limit")).toMatchObject({ + class: "update", + local: "50MiB", + remote: "1048576", + }); + }); + + test("network restriction CIDRs split by address family", () => { + const result = diffWith( + { + db: { + network_restrictions: { + enabled: true, + allowed_cidrs: ["10.0.0.0/8"], + allowed_cidrs_v6: [], + }, + }, + }, + { + database: { + network_restrictions: { + allowed_cidrs: [ + { address: "10.0.0.0/8", type: "v4" }, + { address: "fd00::/8", type: "v6" }, + ], + }, + }, + }, + ); + expect(changeAt(result.changes, "db.network_restrictions.allowed_cidrs")).toBeUndefined(); + expect(changeAt(result.changes, "db.network_restrictions.allowed_cidrs_v6")).toMatchObject({ + class: "update", + local: [], + remote: ["fd00::/8"], + }); + }); + + test("declared secret values are masked, never compared, never counted", () => { + const declared = { + auth: { external: { github: { enabled: true, client_id: "id", secret: "shh" } } }, + }; + const result = diffWith(declared, { + auth: { external_github_enabled: true, external_github_client_id: "id" }, + }); + expect(result.masked).toContain("auth.external.github.secret"); + expect(changeAt(result.changes, "auth.external.github.secret")).toBeUndefined(); + expect(result.counts).toEqual({ update: 0, remote_only: 0, local_only: 0 }); + }); + + test("undeclared secrets are neither masked nor reported", () => { + const result = diffWith({}, { auth: { smtp_pass: "hmac-of-something" } }); + expect(result.masked).toEqual([]); + expect(result.changes.filter((change) => change.path.includes("pass"))).toEqual([]); + }); + + test("scope lists exactly the blocks the response carried, in order", () => { + const result = diffWith({}, { storage: {}, api: {}, database: {} }); + expect(result.scope).toEqual(["api", "database", "storage"]); + }); + + test("env references annotate the change for the involved variable", () => { + const result = diffWith( + { api: { max_rows: 500 } }, + { api: { max_rows: 1000 } }, + { envReferences: new Map([["api.max_rows", "PGRST_MAX_ROWS"]]) }, + ); + expect(changeAt(result.changes, "api.max_rows")).toMatchObject({ + envVariable: "PGRST_MAX_ROWS", + }); + }); + + test("changes are ordered by path and counts add up", () => { + const result = diffWith( + { api: { max_rows: 5 }, storage: { file_size_limit: "1MiB" } }, + { api: { max_rows: 6 }, database: { postgres_settings: { work_mem: "64MB" } } }, + ); + const paths = result.changes.map((change) => change.path); + expect(paths).toEqual([...paths].sort()); + expect(result.counts.update).toBe(1); + expect(result.counts.remote_only).toBe(1); + expect(result.counts.local_only).toBe(1); + }); +}); + +describe("isEqualConfigValue", () => { + test("multiset semantics for arrays", () => { + expect(isEqualConfigValue(["a", "b"], ["b", "a"])).toBe(true); + expect(isEqualConfigValue(["a", "a", "b"], ["a", "b", "b"])).toBe(false); + expect(isEqualConfigValue(["1"], [1])).toBe(true); + expect(isEqualConfigValue(["a"], ["a", "a"])).toBe(false); + }); + + test("type-aware scalars", () => { + expect(isEqualConfigValue("8080", 8080)).toBe(true); + expect(isEqualConfigValue(8080, "8080")).toBe(true); + expect(isEqualConfigValue("true", true)).toBe(true); + expect(isEqualConfigValue(false, "false")).toBe(true); + expect(isEqualConfigValue("", 0)).toBe(false); + expect(isEqualConfigValue("8080x", 8080)).toBe(false); + expect(isEqualConfigValue(undefined, "")).toBe(false); + }); +}); + +describe("normalizeByteSize", () => { + test("parses 1024-based human sizes case-insensitively", () => { + expect(normalizeByteSize("50MiB")).toBe(52428800); + expect(normalizeByteSize("50MB")).toBe(52428800); + expect(normalizeByteSize("50mb")).toBe(52428800); + expect(normalizeByteSize("1GiB")).toBe(1073741824); + expect(normalizeByteSize("500")).toBe(500); + expect(normalizeByteSize("0.5k")).toBe(512); + }); + + test("passes through numbers and unparseable strings", () => { + expect(normalizeByteSize(52428800)).toBe(52428800); + expect(normalizeByteSize("not-a-size")).toBe("not-a-size"); + expect(normalizeByteSize(true)).toBe(true); + }); +}); diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 2375d4e04d..d375b501ae 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -56,5 +56,19 @@ export { omitDefaultValues, subtractProjectConfig, } from "./sparse.ts"; +export { + type ConfigChange, + type ConfigChangeClass, + type ConfigChangeCounts, + type ConfigChangeSet, + type DiffProjectConfigOptions, + type ManagedConfigProperty, + type RemoteConfigBlock, + type RemoteProjectConfig, + REMOTE_CONFIG_BLOCKS, + diffProjectConfig, + isEqualConfigValue, +} from "./config-diff.ts"; +export { MANAGED_CONFIG_PATHS } from "./config-diff.managed.ts"; export { KONG_LOCAL_CA_CERT } from "./tls.ts"; export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; diff --git a/packages/config/src/io.ts b/packages/config/src/io.ts index b65435cf84..1ac5f65b6a 100644 --- a/packages/config/src/io.ts +++ b/packages/config/src/io.ts @@ -20,6 +20,11 @@ export type ProjectConfigValueSource = "environment" | "local" | "remote"; export interface ProjectConfigValueOrigin { readonly path: ReadonlyArray; readonly source: ProjectConfigValueSource; + /** + * For `"environment"` origins: the env var name(s) the `env()` reference + * resolved from (comma-joined when one array literal drew on several). + */ + readonly envVariable?: string; } export interface LoadedProjectConfig { @@ -722,7 +727,7 @@ export const loadProjectConfigFile = Effect.fnUntraced(function* ( const goViperCompat = options?.goViperCompat ?? false; const interpolateDocument = ( document: unknown, - onResolvedEnv?: (path: ReadonlyArray) => void, + onResolvedEnv?: (path: ReadonlyArray, envName: string) => void, ): unknown => interpolateEnvReferencesAgainstSchema(document, projectEnv?.values ?? {}, ProjectConfigSchema, { goViperCompat, @@ -770,9 +775,11 @@ export const loadProjectConfigFile = Effect.fnUntraced(function* ( // that path, but correctness on the match+`env()` path matters more than // avoiding it. const resolvedEnvironmentPaths: Array = []; + const resolvedEnvironmentNames = new Map(); documentForDecode = isObject(documentForDecode) - ? interpolateDocument(documentForDecode, (path) => { + ? interpolateDocument(documentForDecode, (path, envName) => { resolvedEnvironmentPaths.push(Array.from(path)); + resolvedEnvironmentNames.set(pathKey(Array.from(path)), envName); }) : documentForDecode; @@ -817,7 +824,12 @@ export const loadProjectConfigFile = Effect.fnUntraced(function* ( : localPathKeys.has(key) ? "local" : undefined; - return source === undefined ? [] : [{ path, source }]; + if (source === undefined) { + return []; + } + const envVariable = + source === "environment" ? resolvedEnvironmentNames.get(key) : undefined; + return [{ path, source, ...(envVariable === undefined ? {} : { envVariable }) }]; }) : []; diff --git a/packages/config/src/lib/env.ts b/packages/config/src/lib/env.ts index b90bf35619..d898381e30 100644 --- a/packages/config/src/lib/env.ts +++ b/packages/config/src/lib/env.ts @@ -218,7 +218,7 @@ function substituteEnvLeaf( value: string, env: Readonly>, goViperCompat: boolean, -): { readonly value: string; readonly resolved: boolean } { +): { readonly value: string; readonly resolved: boolean; readonly envName?: string } { const match = (goViperCompat ? ENV_CAPTURE_REGEX : ENV_CAPTURE_REGEX_STRICT).exec(value); if (match === null) { return { value, resolved: false }; @@ -229,10 +229,10 @@ function substituteEnvLeaf( // (`apps/cli-go/pkg/config/decode_hooks.go:19-24`: `len(env) > 0`), so a // key that's present but empty (e.g. a dotenv `KEY=` line) preserves the // `env(KEY)` literal exactly like an unset key, rather than substituting "". - if (resolved === undefined || resolved === "") { + if (envName === undefined || resolved === undefined || resolved === "") { return { value, resolved: false }; } - return { value: resolved, resolved: true }; + return { value: resolved, resolved: true, envName }; } function isDeferredEnvField(ast: SchemaAST.AST): boolean { @@ -258,22 +258,26 @@ function walk( ast: SchemaAST.AST | null, goViperCompat: boolean, path: ReadonlyArray, - onResolvedEnv: ((path: ReadonlyArray) => void) | undefined, + onResolvedEnv: ((path: ReadonlyArray, envName: string) => void) | undefined, ): unknown { if (Array.isArray(document)) { - let resolved = false; + // Element-level resolutions are reported once, at the array's own path — + // one array literal may draw on several env vars, so the names collect. + const envNames: Array = []; const onResolvedArrayEnv = onResolvedEnv === undefined ? undefined - : () => { - resolved = true; + : (_: ReadonlyArray, envName: string) => { + if (!envNames.includes(envName)) { + envNames.push(envName); + } }; const result = document.map((item, index) => { const child = ast === null ? null : descendAst(ast, String(index)); return walk(item, env, child, goViperCompat, [...path, String(index)], onResolvedArrayEnv); }); - if (resolved) { - onResolvedEnv?.(path); + if (envNames.length > 0) { + onResolvedEnv?.(path, envNames.join(", ")); } return result; } @@ -297,8 +301,8 @@ function walk( const interpolation = substituteEnvLeaf(document, env, goViperCompat); const substituted = interpolation.value; - if (interpolation.resolved) { - onResolvedEnv?.(path); + if (interpolation.resolved && interpolation.envName !== undefined) { + onResolvedEnv?.(path, interpolation.envName); } const expected = ast === null ? "unknown" : leafExpectedType(ast); @@ -357,7 +361,9 @@ export function interpolateEnvReferencesAgainstSchema( schema: { readonly ast: SchemaAST.AST }, options?: { readonly goViperCompat?: boolean; - readonly onResolvedEnv?: (path: ReadonlyArray) => void; + /** Fires per resolved leaf with the substituting env var's name (array + * leaves report once at the array path, names comma-joined). */ + readonly onResolvedEnv?: (path: ReadonlyArray, envName: string) => void; }, ): unknown { return walk( From 8d98fcbde62475aa1d1e323769b6dcf24cda9c4c Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 20 Aug 2026 15:24:58 -0500 Subject: [PATCH 02/12] feat(config): suppress zero-valued remotes on optional-key paths; add ADR 0019 (CLI-2156) Co-Authored-By: Claude Fable 5 --- ...diff-classification-and-managed-surface.md | 42 +++++++++++++++++++ docs/adr/README.md | 1 + packages/config/src/config-diff.ts | 17 +++++++- packages/config/src/config-diff.unit.test.ts | 20 +++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 docs/adr/0019-config-diff-classification-and-managed-surface.md diff --git a/docs/adr/0019-config-diff-classification-and-managed-surface.md b/docs/adr/0019-config-diff-classification-and-managed-surface.md new file mode 100644 index 0000000000..92b3670dd9 --- /dev/null +++ b/docs/adr/0019-config-diff-classification-and-managed-surface.md @@ -0,0 +1,42 @@ +# 0019. Config Diff Classification and Managed Surface + +**Status**: proposed +**Date**: 2026-08-20 + +## Problem Statement + +`supabase config diff` (CLI-2156) compares the local `config.toml` against the effective configuration `GET /v2/projects/{ref}/config` reports, and `config pull` (CLI-2064) will delegate to the same engine. Three classification problems make a naive walk wrong: + +1. **Key-set asymmetry.** The earlier POC walked only keys present in the remote response, so a property the file declares and the remote doesn't return was structurally invisible. The inverse walk (local keys only) would hide remote-side drift the file never mentions. +2. **Managed vs. unmanaged.** Most of `config.toml` configures the *local* stack — `[studio]`, ports, image pins, `[db.migrations]` — and has no platform counterpart. Reporting those as drift is noise; deciding which properties the platform manages needs a source of truth that cannot drift from the code that reads the response. +3. **Incomparable values.** The platform masks secrets (HMAC, never plaintext), reports byte counts where the file writes `"50MiB"`, comma-joins arrays, and types some scalars differently than the schema. Comparing representations instead of meanings misreports drift; silently skipping them misreports cleanliness. + +## Decision + +`@supabase/config` owns the whole comparison core as pure, synchronous functions (`config-diff*.ts`), with no dependency on `@supabase/api`, output formatting, or command flags: + +- **The managed surface is defined by the translation table.** `MANAGED_CONFIG_PROPERTIES` is a table of entries, one per local schema path the v2 resource can report, each carrying a `read` function that descends the structurally-typed response (`RemoteProjectConfig`, all six blocks as loose records) and coerces the wire value to the local schema's type. A schema path with no entry is *unmanaged by construction* — the managed set and the response-reading code are the same artifact and cannot drift apart. The auth table is ported from the Go CLI's `FromRemoteAuthConfig` (commit `7b469f5b3`), including its inversions (`enable_signup` ← `!disable_signup`), duration/enum transforms, and provider fan-out. +- **Four-way classification per managed path**, driven by *declared* presence (the raw pre-decode document) on the local side and `read` presence on the remote side: `update` (declared + returned, values differ), `remote_only` (returned, undeclared, and differing from the baseline default — equal-to-default values are suppressed, which is what CLI-2155's defaults reference exists for), `local_only` (declared, not returned — parsed-but-never-pushed attributes and permission-truncated responses), and unmanaged (never reported). The local operand follows ADR 0018: the branch's merged effective config when the target ref matches a `[remotes.*]` block's `project_id`, the base config otherwise. +- **Equality is meaning-based**: arrays compare as multisets, scalars tolerate string/number and string/boolean representation skew, and per-entry `normalize` hooks canonicalize (byte sizes via `RAMInBytes` semantics, Go-duration strings) before comparison while reported values stay un-normalized. +- **Secrets are "present, unknown".** Entries marked `secret` (the union of the schema's `x-secret` fields and Go's `Secret` machinery) are never compared and never counted; locally-declared ones are surfaced in `ConfigChangeSet.masked` so a clean change list is visibly a partial claim. Likewise `scope` records which blocks the response actually carried, so partially-populated responses degrade to `local_only` + an explicit scope note instead of an error or silent omission. +- The interpolation pipeline records the resolving env var name on `"environment"` value origins, so a change on an `env()`-fed property can name the variable involved. + +The command layer (`apps/cli/src/legacy/commands/config/diff/`) only resolves the target, fetches, and renders. + +## Considered Alternatives + +1. **Derive the managed set from the response keys** (the POC's approach): whatever the remote returns is what's compared. Structurally blind to `local_only`, and a permission-truncated response silently shrinks the comparison. +2. **Schema annotations (`x-managed`) on each property**: keeps the knowledge in the schema, but the annotation and the response-reading code can disagree, and the annotation cannot express per-property wire transforms (comma-splits, inversions, unit conversions) that the table entry's `read` carries anyway. +3. **Reuse `config push`'s `config-sync` diffing** (`apps/cli/src/legacy/commands/config/push/config-sync/`): those helpers produce per-service unified-diff *text* against the v1 per-service endpoints for push previews, not a typed change set, and they live in the CLI app. They remain the Go-parity push path; the classification core is the reusable engine `pull` needs. Consolidating push onto the core is possible later but out of scope here. + +## Consequences + +- `config pull` gets its comparison engine for free: the change set is typed data, and the same translation produces the local representation of any remote value it needs to write. +- Adding a newly platform-managed property is one table entry; forgetting it means the property is silently unmanaged (never misreported as drift), which fails safe. +- The structural `RemoteProjectConfig` type mirrors the v2 wire shape; if the API reshapes a block, the readers' runtime guards degrade to "not returned" (`local_only`/silent) rather than crashing, and the live test is the tripwire. +- Platform defaults that diverge from schema defaults surface as `remote_only` drift by design — the file's meaning is defined by the schema defaults reference (ADR 0018), not by what the platform would have picked. + +## Related Decisions + +- [ADR 0018](0018-sparse-config-subtraction.md): Sparse Config Subtraction — the defaults baseline and merged-remote-block local operand this classification builds on. +- [ADR 0006](0006-environment-management.md): Environment Management — remote blocks and branch mapping semantics. diff --git a/docs/adr/README.md b/docs/adr/README.md index 79e3386e7d..2ef871855c 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -59,6 +59,7 @@ When an ADR becomes outdated, mark it as `deprecated` or reference the supersedi | 0016 | [Legacy Port Completion and Go CLI Authority Scope](0016-legacy-port-completion-and-go-cli-authority-scope.md) | proposed | | 0017 | [Simplified Managed Stack Architecture](0017-simplified-managed-stack-architecture.md) | accepted | | 0018 | [Sparse Config Subtraction](0018-sparse-config-subtraction.md) | proposed | +| 0019 | [Config Diff Classification and Managed Surface](0019-config-diff-classification-and-managed-surface.md) | proposed | ## Template diff --git a/packages/config/src/config-diff.ts b/packages/config/src/config-diff.ts index 426d83df34..ac99d6ad8f 100644 --- a/packages/config/src/config-diff.ts +++ b/packages/config/src/config-diff.ts @@ -194,6 +194,12 @@ function canonicalArrayElement(value: unknown): string { return `j:${JSON.stringify(value)}`; } +function isZeroValue(value: unknown): boolean { + return ( + value === false || value === "" || value === 0 || (Array.isArray(value) && value.length === 0) + ); +} + /** * Order-insensitive, type-aware value equality: arrays compare as multisets * (`additional_redirect_urls` in a different order is not a difference), and @@ -250,7 +256,16 @@ export function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChan if (remoteValue !== undefined) { const defaultValue = valueAtPath(defaults, property.path); - if (!isEqualConfigValue(normalize(defaultValue), normalize(remoteValue))) { + // Optional-key sections (db.ssl_enforcement, db.settings, auth + // providers…) never materialize in the default config, so their paths + // have no baseline value. The platform still reports the unconfigured + // state for them as the type's zero value (false / "" / 0 / []) — an + // undeclared feature reporting its zero value is not drift. + const suppressed = + defaultValue === undefined + ? isZeroValue(remoteValue) + : isEqualConfigValue(normalize(defaultValue), normalize(remoteValue)); + if (!suppressed) { changes.push({ path: property.path, class: "remote_only", diff --git a/packages/config/src/config-diff.unit.test.ts b/packages/config/src/config-diff.unit.test.ts index 2363236be0..30eb1c76a3 100644 --- a/packages/config/src/config-diff.unit.test.ts +++ b/packages/config/src/config-diff.unit.test.ts @@ -99,6 +99,26 @@ describe("diffProjectConfig classification", () => { expect(change).toMatchObject({ class: "remote_only", local: undefined, remote: 250 }); }); + test("optional-key paths with no materialized default suppress zero-valued remotes", () => { + // db.ssl_enforcement and auth providers are optionalKey — absent from the + // default config — and the platform reports their unconfigured state as + // zero values. Those are not drift; a non-zero value is. + const clean = diffWith( + {}, + { + database: { ssl_enforced: false }, + auth: { external_github_enabled: false, external_github_client_id: "" }, + }, + ); + expect(clean.changes).toEqual([]); + + const drifted = diffWith({}, { database: { ssl_enforced: true } }); + expect(changeAt(drifted.changes, "db.ssl_enforcement.enabled")).toMatchObject({ + class: "remote_only", + remote: true, + }); + }); + test("declared value the response does not carry is local_only", () => { const result = diffWith( { api: { max_rows: 500 } }, From 03d80f9ae2f782e73471e63c9e68a857c30ebf3a Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 20 Aug 2026 15:43:39 -0500 Subject: [PATCH 03/12] feat(cli): add config diff command (CLI-2156) Read-only drift report between supabase/config.toml and the effective configuration GET /v2/projects/{ref}/config reports for a target project or branch. Target resolution via --target (branch name/UUID/ref, link-style acceptance) or --project-ref or the linked ref; matching [remotes.*] blocks become the merged local operand per ADR 0018. Text, --output-format json/stream-json, and Go-compat -o encodings share one structured payload; --exit-code flips exit 1 on drift after the payload is out. Hoists the branch name/UUID resolver to legacy/shared with injected error mappers. Adds ADR 0019, SIDE_EFFECTS.md, a go-cli-divergences entry, 26 integration tests (handler at 100% branch coverage), format unit tests, and a live golden path. Co-Authored-By: Claude Fable 5 --- apps/cli/docs/go-cli-divergences.md | 15 +- .../commands/branches/branches.resolver.ts | 59 +- .../commands/branches/get/get.handler.ts | 2 +- .../legacy/commands/config/config.command.ts | 3 +- .../commands/config/diff/SIDE_EFFECTS.md | 105 +++ .../commands/config/diff/diff.command.ts | 49 ++ .../commands/config/diff/diff.errors.ts | 81 +++ .../commands/config/diff/diff.format.ts | 181 +++++ .../config/diff/diff.format.unit.test.ts | 57 ++ .../commands/config/diff/diff.handler.ts | 200 ++++++ .../config/diff/diff.integration.test.ts | 643 ++++++++++++++++++ .../commands/config/diff/diff.live.test.ts | 49 ++ .../shared/legacy-branch-ref.resolver.ts | 70 ++ packages/config/src/config-diff.ts | 8 +- packages/config/src/config-diff.unit.test.ts | 9 + 15 files changed, 1467 insertions(+), 64 deletions(-) create mode 100644 apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.command.ts create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.errors.ts create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.format.ts create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.handler.ts create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.live.test.ts create mode 100644 apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts diff --git a/apps/cli/docs/go-cli-divergences.md b/apps/cli/docs/go-cli-divergences.md index 693e2e3328..3714a0b10c 100644 --- a/apps/cli/docs/go-cli-divergences.md +++ b/apps/cli/docs/go-cli-divergences.md @@ -12,13 +12,14 @@ not a compatibility promise. These commands exist in the TS CLI today but have no direct top-level equivalent in the old Go CLI reference. -| TS command | TS path | Notes | -| ----------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `dev` | `planned` | Reserved for a TS-native long-running local development workflow command that watches files and orchestrates subcommands. Track this as TS-only unless a direct Go equivalent emerges. | -| `logs` | [`../src/next/commands/logs/logs.command.ts`](../src/next/commands/logs/logs.command.ts) | Streams local stack logs. No top-level `logs` command exists in the old Go CLI reference. | -| `api` | [`../src/next/commands/platform/api.command.ts`](../src/next/commands/platform/api.command.ts) | Low-level Management API client. It supersedes the old generated tree with explicit discovery via `supabase api routes` and execution via `supabase api request [--method ]`. | -| `stack` | [`../src/next/cli/root.ts`](../src/next/cli/root.ts) | TS-only local runtime namespace exposing `stack start`, `stack stop`, `stack status`, `stack list`, and `stack update`. Top-level `start`, `stop`, and `status` remain aliases. | -| `branches switch` | [`../src/next/commands/branches/switch/switch.command.ts`](../src/next/commands/branches/switch/switch.command.ts) | No direct Go equivalent. Updates local active-branch state so subsequent commands target the selected branch. | +| TS command | TS path | Notes | +| ----------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `dev` | `planned` | Reserved for a TS-native long-running local development workflow command that watches files and orchestrates subcommands. Track this as TS-only unless a direct Go equivalent emerges. | +| `logs` | [`../src/next/commands/logs/logs.command.ts`](../src/next/commands/logs/logs.command.ts) | Streams local stack logs. No top-level `logs` command exists in the old Go CLI reference. | +| `api` | [`../src/next/commands/platform/api.command.ts`](../src/next/commands/platform/api.command.ts) | Low-level Management API client. It supersedes the old generated tree with explicit discovery via `supabase api routes` and execution via `supabase api request [--method ]`. | +| `stack` | [`../src/next/cli/root.ts`](../src/next/cli/root.ts) | TS-only local runtime namespace exposing `stack start`, `stack stop`, `stack status`, `stack list`, and `stack update`. Top-level `start`, `stop`, and `status` remain aliases. | +| `branches switch` | [`../src/next/commands/branches/switch/switch.command.ts`](../src/next/commands/branches/switch/switch.command.ts) | No direct Go equivalent. Updates local active-branch state so subsequent commands target the selected branch. | +| `config diff` | [`../src/legacy/commands/config/diff/diff.command.ts`](../src/legacy/commands/config/diff/diff.command.ts) | Read-only drift report between `supabase/config.toml` and `GET /v2/projects/{ref}/config` (CLI-2156). TS-only: the old Go CLI had no config diff. `--target` accepts a branch name/UUID/ref; `--exit-code` exits 1 on drift. Comparison core lives in `@supabase/config` (ADR 0019). | ## Flag divergences from the Go reference diff --git a/apps/cli/src/legacy/commands/branches/branches.resolver.ts b/apps/cli/src/legacy/commands/branches/branches.resolver.ts index ff666f9d47..6f66f5c297 100644 --- a/apps/cli/src/legacy/commands/branches/branches.resolver.ts +++ b/apps/cli/src/legacy/commands/branches/branches.resolver.ts @@ -1,7 +1,5 @@ -import { Effect } from "effect"; - -import { LegacyPlatformApi } from "../../auth/legacy-platform-api.service.ts"; import { mapLegacyHttpError } from "../../shared/legacy-http-errors.ts"; +import { legacyResolveBranchProjectRef as legacyResolveBranchProjectRefShared } from "../../shared/legacy-branch-ref.resolver.ts"; import { LegacyBranchesFindNetworkError, LegacyBranchesFindUnexpectedStatusError, @@ -9,21 +7,6 @@ import { LegacyBranchesGetUnexpectedStatusError, } from "./branches.errors.ts"; -/** - * Project ref pattern shared by every Management-API endpoint that accepts a - * 20-lowercase-letter project reference. Re-export so siblings (e.g. - * `get.handler.ts`) can classify branch-id inputs without re-declaring it. - */ -export const LEGACY_BRANCH_PROJECT_REF_PATTERN = /^[a-z]{20}$/; - -/** - * Permissive UUID pattern (any 8-4-4-4-12 hex sequence) — accepts any RFC 4122 - * variant including v6/v7 and version 0, matching the established liberal - * acceptance rather than the v1–v5 + variant-1 subset. - */ -export const LEGACY_BRANCH_UUID_PATTERN = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - const mapFindError = mapLegacyHttpError({ networkError: LegacyBranchesFindNetworkError, statusError: LegacyBranchesFindUnexpectedStatusError, @@ -39,38 +22,10 @@ const mapGetError = mapLegacyHttpError({ }); /** - * Resolves an arbitrary branch identifier to its project ref: - * - * 1. If the input matches `^[a-z]{20}$`, it's already a project ref — return as-is. - * 2. Else if the input is a UUID, call `V1GetABranchConfig` (`GET /v1/branches/{id}`) - * and return `JSON200.ref`. - * 3. Otherwise treat as a branch name under the linked project ref: call - * `V1GetABranch` (`GET /v1/projects/{ref}/branches/{name}`) and return - * `JSON200.project_ref`. - * - * The persistent `--project-ref` is required for path 3 and is passed in by - * the caller (which has already run `LegacyProjectRefResolver` so the linked - * project cache write does not re-fire here). + * The branches family's binding of the shared branch-ref resolver + * (`legacy/shared/legacy-branch-ref.resolver.ts`) to this family's error + * classes. See the shared module for resolution semantics. */ -export const legacyResolveBranchProjectRef = Effect.fnUntraced(function* ( - input: string, - projectRef: string, -) { - if (LEGACY_BRANCH_PROJECT_REF_PATTERN.test(input)) { - return input; - } - - const api = yield* LegacyPlatformApi; - - if (LEGACY_BRANCH_UUID_PATTERN.test(input)) { - const detail = yield* api.v1 - .getABranchConfig({ branch_id_or_ref: input }) - .pipe(Effect.catch(mapGetError)); - return detail.ref; - } - - const branch = yield* api.v1 - .getABranch({ ref: projectRef, name: input }) - .pipe(Effect.catch(mapFindError)); - return branch.project_ref; -}); +export function legacyResolveBranchProjectRef(input: string, projectRef: string) { + return legacyResolveBranchProjectRefShared(input, projectRef, { mapGetError, mapFindError }); +} diff --git a/apps/cli/src/legacy/commands/branches/get/get.handler.ts b/apps/cli/src/legacy/commands/branches/get/get.handler.ts index bc211c6a61..f90b3f40f1 100644 --- a/apps/cli/src/legacy/commands/branches/get/get.handler.ts +++ b/apps/cli/src/legacy/commands/branches/get/get.handler.ts @@ -39,7 +39,7 @@ import { legacyPromptBranchId } from "../branches.prompt.ts"; import { LEGACY_BRANCH_PROJECT_REF_PATTERN, LEGACY_BRANCH_UUID_PATTERN, -} from "../branches.resolver.ts"; +} from "../../../shared/legacy-branch-ref.resolver.ts"; import type { LegacyBranchesGetFlags } from "./get.command.ts"; type BranchDetail = typeof V1GetABranchConfigOutput.Type; diff --git a/apps/cli/src/legacy/commands/config/config.command.ts b/apps/cli/src/legacy/commands/config/config.command.ts index efec9afa22..0c13ba938d 100644 --- a/apps/cli/src/legacy/commands/config/config.command.ts +++ b/apps/cli/src/legacy/commands/config/config.command.ts @@ -1,8 +1,9 @@ import { Command } from "effect/unstable/cli"; +import { legacyConfigDiffCommand } from "./diff/diff.command.ts"; import { legacyConfigPushCommand } from "./push/push.command.ts"; export const legacyConfigCommand = Command.make("config").pipe( Command.withDescription("Manage Supabase project configurations."), Command.withShortDescription("Manage project configurations"), - Command.withSubcommands([legacyConfigPushCommand]), + Command.withSubcommands([legacyConfigDiffCommand, legacyConfigPushCommand]), ); diff --git a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md new file mode 100644 index 0000000000..87f6e0ee9c --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md @@ -0,0 +1,105 @@ +# `supabase config diff` + +Read-only comparison between the local `supabase/config.toml` and the effective +configuration the Management API reports for a target project or branch. +Classifies every remotely-managed property as `update` / `remote_only` / +`local_only` (unmanaged local-only properties are never reported). **Never +writes `config.toml` or any remote configuration.** + +TS-only command — no Go CLI equivalent (see `docs/go-cli-divergences.md`). + +## Files Read + +| Path | Format | When | +| ---------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------ | +| `/supabase/config.toml` | TOML | always, before any network call (missing file or parse error aborts, exit 1) | +| `/supabase/.env`, `.env.local` | dotenv | always, to resolve `env(VAR)` references inside `config.toml` | +| `/supabase/.temp/project-ref` | plain text | project-ref fallback (flag → `SUPABASE_PROJECT_ID` → this file); parent-ref for `--target` | +| `/supabase/.temp/linked-project.json` | JSON | existence check only, for the telemetry cache write below | +| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | + +## Files Written + +| Path | Format | When | +| ---------------------------------------------- | ------ | ---------------------------------------------------------------------- | +| `/supabase/.temp/linked-project.json` | JSON | `Effect.ensuring` after run (success **and** failure), if ref resolved | +| `~/.supabase/telemetry.json` | JSON | `Effect.ensuring` after run (success **and** failure) | + +**No writes to `supabase/config.toml` or `supabase/config.json`** — covered by +an integration test asserting mtime and contents are unchanged after a run +that finds differences. + +## API Routes + +All Bearer-authenticated, all read-only. + +| # | Purpose | Method | Path | Success | Notes | +| --- | ---------------------------------- | ------ | ------------------------------------ | ------- | ---------------------------------------------------------------------- | +| 0a | branch by UUID (`--target `) | GET | `/v1/branches/{branch_id}` | 200 | only when `--target` is a UUID | +| 0b | branch by name (`--target `) | GET | `/v1/projects/{ref}/branches/{name}` | 200 | only when `--target` is not a ref/UUID; 404 → "branch not found" error | +| 1 | effective remote config | GET | `/v2/projects/{ref}/config` | 200 | always (after target resolution) | + +## Environment Variables + +| Variable | Purpose | Required? | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| `SUPABASE_PROJECT_ID` | project ref (flag → this → `.temp/project-ref` → prompt) | no | +| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) | +| `SUPABASE_PROFILE` | API profile selection | no | +| `env(VAR)` references | interpolated into `config.toml` values at load; a change on an env-resolved property names the variable in the output | no | + +## Exit Codes + +| Code | Condition | +| ---- | ------------------------------------------------------------------------------ | +| `0` | success — including when differences are found, unless `--exit-code` is passed | +| `1` | `--exit-code` passed and at least one difference found | +| `1` | missing or malformed `supabase/config.toml` | +| `1` | `--target` and `--project-ref` passed together | +| `1` | unknown branch (`--target` 404) | +| `1` | two `[remotes.*]` blocks declare the same `project_id` as the target ref | +| `1` | remote config read failure (network or unexpected status) | + +## Output + +Diagnostics on **stderr**: `Comparing against …` (resolved target + local +scope, i.e. `[remotes.]` or `base config`) before the fetch, then +`Comparison scope: ` listing the blocks the response carried (missing +blocks are called out). The payload is on **stdout**. + +### `--output-format text` + +One block per difference (` [update|remote only|local only]` with +`local:`/`remote:` lines; unset renders `(unset)` / `(not returned)`, +env-resolved values append `(from env VAR)`), then a summary count line — +`No config differences found.` when clean — and a +`Note: N credential value(s) not compared (masked by the API): …` line when +the file sets masked secrets. + +### `--output-format json` / `stream-json` + +`output.success(message, payload)` with the payload containing +`schema_version`, `target` (`project_ref`, optional `branch`, `local_scope`), +`scope`, `changes[]` (`path`, `class`, `local`, `remote`, optional +`env_variable`; unset sides are `null`), `masked[]`, and `counts` +(per class + `total`). + +### `-o json|yaml|toml|env` (Go-compat) + +The same payload through the shared Go-compatible map encoders. TOML and env +drop `null`-valued keys (TOML cannot represent null); `class` still +disambiguates which side is absent. `-o pretty` (or unset) falls through to +the `--output-format` behavior above. + +## Notes + +- Run from the project root (or pass `--workdir`); `config.toml` is read relative to it. +- **Local operand per target (ADR 0018/0019):** when the resolved target ref matches a + `[remotes.]` block's `project_id`, the local side is that branch's merged + effective config; otherwise the base config. The echoed scope line always says which. +- **Masked credentials:** secret-valued managed properties (the platform returns an HMAC, + never plaintext) are treated as "present, unknown" — never reported as differences and + never counted for `--exit-code`; they are surfaced via the masked note / `masked[]`. +- **Partial responses:** a managed property the response does not carry is `local_only` + when the file declares it and silent otherwise; a missing block is called out on the + scope line rather than treated as an error. diff --git a/apps/cli/src/legacy/commands/config/diff/diff.command.ts b/apps/cli/src/legacy/commands/config/diff/diff.command.ts new file mode 100644 index 0000000000..c8fba014dc --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.command.ts @@ -0,0 +1,49 @@ +import type * as CliCommand from "effect/unstable/cli/Command"; +import { Command, Flag } from "effect/unstable/cli"; + +import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; +import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; +import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { legacyConfigDiff } from "./diff.handler.ts"; + +const config = { + projectRef: Flag.string("project-ref").pipe( + Flag.withDescription("Project ref of the Supabase project."), + Flag.optional, + ), + target: Flag.string("target").pipe( + Flag.withDescription( + "Branch name, branch ID, or project ref to compare against. Mutually exclusive with --project-ref.", + ), + Flag.optional, + ), + exitCode: Flag.boolean("exit-code").pipe( + Flag.withDescription("Exit with status 1 when any difference is found."), + ), +} as const; + +export type LegacyConfigDiffFlags = CliCommand.Command.Config.Infer; + +export const legacyConfigDiffCommand = Command.make("diff", config).pipe( + Command.withDescription( + "Shows configuration differences between supabase/config.toml and a remote project or branch. Read-only: never modifies local or remote configuration.", + ), + Command.withShortDescription("Diff local config against a remote project"), + Command.withExamples([ + { + command: "supabase config diff", + description: "Diff against the linked project", + }, + { + command: "supabase config diff --target staging --exit-code", + description: "Diff against the 'staging' branch, exiting 1 on drift", + }, + ]), + Command.withHandler((flags) => + legacyConfigDiff(flags).pipe( + withLegacyCommandInstrumentation({ flags, safeFlags: ["project-ref"] }), + withJsonErrorHandling, + ), + ), + Command.provide(legacyManagementApiRuntimeLayer(["config", "diff"])), +); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts new file mode 100644 index 0000000000..2094c28a1e --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts @@ -0,0 +1,81 @@ +import { Data } from "effect"; +import { + actionability, + type CliErrorActionabilityDeclaration, + ErrorActionabilityId, + statusCodeActionability, +} from "../../../../shared/telemetry/error-actionability.ts"; + +interface NetworkErrorArgs { + readonly message: string; + readonly decode?: boolean; +} + +interface StatusErrorArgs { + readonly status: number; + readonly body: string; + readonly message: string; +} + +/** Local config file missing or unparseable. Aborts before any network call. */ +export class LegacyConfigDiffLoadConfigError extends Data.TaggedError( + "LegacyConfigDiffLoadConfigError", +)<{ readonly message: string }> { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return actionability.invalidConfig; + } +} + +/** `--target` and `--project-ref` passed together. */ +export class LegacyConfigDiffFlagConflictError extends Data.TaggedError( + "LegacyConfigDiffFlagConflictError", +)<{ readonly message: string }> { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return actionability.invalidInput; + } +} + +/** `--target` named a branch the parent project does not have. */ +export class LegacyConfigDiffBranchNotFoundError extends Data.TaggedError( + "LegacyConfigDiffBranchNotFoundError", +)<{ readonly message: string }> { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return actionability.invalidInput; + } +} + +export class LegacyConfigDiffBranchResolveNetworkError extends Data.TaggedError( + "LegacyConfigDiffBranchResolveNetworkError", +) { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return this.decode === true + ? { ...actionability.apiStatus, fingerprint_suffix: "api_response" } + : actionability.externalNetwork; + } +} + +export class LegacyConfigDiffBranchResolveStatusError extends Data.TaggedError( + "LegacyConfigDiffBranchResolveStatusError", +) { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return statusCodeActionability(this.status, { notFoundIsInvalidInput: true }); + } +} + +export class LegacyConfigDiffReadNetworkError extends Data.TaggedError( + "LegacyConfigDiffReadNetworkError", +) { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return this.decode === true + ? { ...actionability.apiStatus, fingerprint_suffix: "api_response" } + : actionability.externalNetwork; + } +} + +export class LegacyConfigDiffReadStatusError extends Data.TaggedError( + "LegacyConfigDiffReadStatusError", +) { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return statusCodeActionability(this.status); + } +} diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.ts new file mode 100644 index 0000000000..07c97bf8a8 --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.ts @@ -0,0 +1,181 @@ +import type { + ConfigChange, + ConfigChangeSet, + ProjectConfigValueOrigin, + RemoteConfigBlock, + RemoteProjectConfig, +} from "@supabase/config"; +import { REMOTE_CONFIG_BLOCKS } from "@supabase/config"; + +/** + * Pure formatters, payload builders, and input adapters for `config diff` — + * no Effect, no services, unit-testable in isolation. + */ + +function isRemoteBlockRecord(value: unknown): value is Readonly> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function asRemoteBlock(value: unknown): Readonly> | undefined { + return isRemoteBlockRecord(value) ? value : undefined; +} + +/** + * Adapts the generated client's decoded `data.attributes` to the loose + * per-block records the comparison core reads. Non-record values (which the + * generated schema should never produce, but the core must not trust) read as + * "block not returned". + */ +export function legacyConfigDiffRemoteBlocks(attributes: { + readonly api: unknown; + readonly auth: unknown; + readonly database: unknown; + readonly pooler: unknown; + readonly realtime: unknown; + readonly storage: unknown; +}): RemoteProjectConfig { + return { + api: asRemoteBlock(attributes.api), + auth: asRemoteBlock(attributes.auth), + database: asRemoteBlock(attributes.database), + pooler: asRemoteBlock(attributes.pooler), + realtime: asRemoteBlock(attributes.realtime), + storage: asRemoteBlock(attributes.storage), + }; +} + +/** + * Extracts `dotted path → env var name` for every `env()`-resolved leaf, so a + * change on such a property can name the variable involved. + */ +export function legacyConfigDiffEnvReferences( + valueOrigins: ReadonlyArray | undefined, +): ReadonlyMap { + const references = new Map(); + for (const origin of valueOrigins ?? []) { + if (origin.source === "environment" && origin.envVariable !== undefined) { + references.set(origin.path.join("."), origin.envVariable); + } + } + return references; +} + +export interface LegacyConfigDiffContext { + /** The resolved comparison target's project ref. */ + readonly projectRef: string; + /** The `--target` value, when a branch was named. */ + readonly branch: string | undefined; + /** Matched `[remotes.]` block, when the local operand was merged. */ + readonly appliedRemote: string | undefined; + /** The local file's `$schema` ref (or the current schema URL). */ + readonly schemaVersion: string; +} + +const CLASS_LABELS: Record = { + update: "update", + remote_only: "remote only", + local_only: "local only", +}; + +function renderValue(value: unknown, absent: string): string { + if (value === undefined) { + return absent; + } + if (typeof value === "number" || typeof value === "boolean") { + return String(value); + } + return JSON.stringify(value); +} + +function localScope(context: LegacyConfigDiffContext): string { + return context.appliedRemote === undefined ? "base config" : `[remotes.${context.appliedRemote}]`; +} + +/** The target-echo line, printed to stderr before any comparison output. */ +export function legacyConfigDiffComparisonLine(context: LegacyConfigDiffContext): string { + const target = + context.branch === undefined + ? `project ${context.projectRef}` + : `'${context.branch}' (branch ${context.projectRef})`; + return `Comparing against ${target} using ${localScope(context)}\n`; +} + +/** The scope-echo line, printed to stderr once the response arrived. */ +export function legacyConfigDiffScopeLine(scope: ReadonlyArray): string { + const present = scope.length === 0 ? "(none)" : scope.join(", "); + const missing = REMOTE_CONFIG_BLOCKS.filter((block) => !scope.includes(block)); + const suffix = missing.length === 0 ? "" : ` (not returned: ${missing.join(", ")})`; + return `Comparison scope: ${present}${suffix}\n`; +} + +function maskedNote(masked: ReadonlyArray): string { + return `Note: ${masked.length} credential value(s) not compared (masked by the API): ${masked.join(", ")}\n`; +} + +/** Human-readable diff body for text mode (stdout). */ +export function legacyRenderConfigDiffText(changeSet: ConfigChangeSet): string { + const lines: Array = []; + for (const change of changeSet.changes) { + lines.push(`${change.path} [${CLASS_LABELS[change.class]}]`); + const local = renderValue(change.local, "(unset)"); + const env = change.envVariable === undefined ? "" : ` (from env ${change.envVariable})`; + lines.push(` local: ${local}${env}`); + lines.push(` remote: ${renderValue(change.remote, "(not returned)")}`); + lines.push(""); + } + + const { update, remote_only, local_only } = changeSet.counts; + const total = update + remote_only + local_only; + if (total === 0) { + lines.push("No config differences found."); + } else { + lines.push( + `${total} difference(s) found (${update} update, ${remote_only} remote-only, ${local_only} local-only).`, + ); + } + if (changeSet.masked.length > 0) { + lines.push(maskedNote(changeSet.masked).trimEnd()); + } + return `${lines.join("\n")}\n`; +} + +/** + * The structured result shared by `--output-format json|stream-json` and the + * Go-compat `-o` encodings. `includeNullValues: false` drops `null`-valued + * keys instead of emitting them — TOML cannot represent null, and the env + * flattening renders it uselessly; `class` still disambiguates which side is + * absent. + */ +export function legacyConfigDiffPayload( + changeSet: ConfigChangeSet, + context: LegacyConfigDiffContext, + options: { readonly includeNullValues: boolean }, +): Record { + const valueEntry = (key: string, value: unknown): Record => { + if (value !== undefined) { + return { [key]: value }; + } + return options.includeNullValues ? { [key]: null } : {}; + }; + + const { update, remote_only, local_only } = changeSet.counts; + return { + schema_version: context.schemaVersion, + target: { + project_ref: context.projectRef, + ...valueEntry("branch", context.branch), + local_scope: + context.appliedRemote === undefined ? "base" : `remotes.${context.appliedRemote}`, + }, + scope: changeSet.scope, + changes: changeSet.changes.map((change) => ({ + path: change.path, + class: change.class, + ...valueEntry("local", change.local), + ...valueEntry("remote", change.remote), + ...(change.envVariable === undefined ? {} : { env_variable: change.envVariable }), + })), + masked: changeSet.masked, + counts: { update, remote_only, local_only, total: update + remote_only + local_only }, + }; +} diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts new file mode 100644 index 0000000000..0d93a1ee3f --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from "vitest"; + +import { + legacyConfigDiffEnvReferences, + legacyConfigDiffRemoteBlocks, + legacyConfigDiffScopeLine, +} from "./diff.format.ts"; + +describe("legacyConfigDiffRemoteBlocks", () => { + test("keeps record blocks and drops non-record ones", () => { + const blocks = legacyConfigDiffRemoteBlocks({ + api: { max_rows: 5 }, + auth: {}, + database: null, + pooler: undefined, + realtime: [1], + storage: "nope", + }); + expect(blocks.api).toEqual({ max_rows: 5 }); + expect(blocks.auth).toEqual({}); + expect(blocks.database).toBeUndefined(); + expect(blocks.pooler).toBeUndefined(); + expect(blocks.realtime).toBeUndefined(); + expect(blocks.storage).toBeUndefined(); + }); +}); + +describe("legacyConfigDiffEnvReferences", () => { + test("collects env-var names for environment origins only", () => { + const references = legacyConfigDiffEnvReferences([ + { path: ["api", "max_rows"], source: "environment", envVariable: "PGRST_MAX_ROWS" }, + { path: ["auth", "site_url"], source: "local" }, + // An environment origin with no recorded name (pre-existing data) is skipped. + { path: ["db", "port"], source: "environment" }, + ]); + expect(references.get("api.max_rows")).toBe("PGRST_MAX_ROWS"); + expect(references.size).toBe(1); + }); + + test("no value origins means no references", () => { + expect(legacyConfigDiffEnvReferences(undefined).size).toBe(0); + }); +}); + +describe("legacyConfigDiffScopeLine", () => { + test("calls out blocks the response did not return", () => { + expect(legacyConfigDiffScopeLine(["api", "auth"])).toBe( + "Comparison scope: api, auth (not returned: database, pooler, realtime, storage)\n", + ); + }); + + test("an empty response scope renders (none)", () => { + expect(legacyConfigDiffScopeLine([])).toBe( + "Comparison scope: (none) (not returned: api, auth, database, pooler, realtime, storage)\n", + ); + }); +}); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts new file mode 100644 index 0000000000..b155fd6299 --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts @@ -0,0 +1,200 @@ +import { diffProjectConfig, loadProjectConfig, PROJECT_CONFIG_SCHEMA_URL } from "@supabase/config"; +import { Effect, Option } from "effect"; + +import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts"; +import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; +import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; +import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; +import { LegacyOutputFlag } from "../../../../shared/legacy/global-flags.ts"; +import { Output } from "../../../../shared/output/output.service.ts"; +import { ProcessControl } from "../../../../shared/runtime/process-control.service.ts"; +import { RuntimeInfo } from "../../../../shared/runtime/runtime-info.service.ts"; +import { + LEGACY_BRANCH_PROJECT_REF_PATTERN, + legacyResolveBranchProjectRef, +} from "../../../shared/legacy-branch-ref.resolver.ts"; +import { + legacySanitizeInlineName, + mapLegacyHttpError, +} from "../../../shared/legacy-http-errors.ts"; +import { + encodeEnv, + encodeGoJson, + encodeToml, + encodeYaml, +} from "../../../shared/legacy-go-output.encoders.ts"; +import { + legacyConfigDiffComparisonLine, + legacyConfigDiffEnvReferences, + legacyConfigDiffPayload, + legacyConfigDiffRemoteBlocks, + legacyConfigDiffScopeLine, + legacyRenderConfigDiffText, + type LegacyConfigDiffContext, +} from "./diff.format.ts"; +import { + LegacyConfigDiffBranchNotFoundError, + LegacyConfigDiffBranchResolveNetworkError, + LegacyConfigDiffBranchResolveStatusError, + LegacyConfigDiffFlagConflictError, + LegacyConfigDiffLoadConfigError, + LegacyConfigDiffReadNetworkError, + LegacyConfigDiffReadStatusError, +} from "./diff.errors.ts"; +import type { LegacyConfigDiffFlags } from "./diff.command.ts"; + +const readStatusMessage = (status: number, body: string) => `unexpected status ${status}: ${body}`; + +const mapBranchResolveError = mapLegacyHttpError({ + networkError: LegacyConfigDiffBranchResolveNetworkError, + statusError: LegacyConfigDiffBranchResolveStatusError, + networkMessage: (cause) => `failed to resolve branch: ${cause}`, + statusMessage: readStatusMessage, +}); + +export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( + flags: LegacyConfigDiffFlags, +) { + const output = yield* Output; + const api = yield* LegacyPlatformApi; + const resolver = yield* LegacyProjectRefResolver; + const linkedProjectCache = yield* LegacyLinkedProjectCache; + const telemetryState = yield* LegacyTelemetryState; + const runtimeInfo = yield* RuntimeInfo; + const processControl = yield* ProcessControl; + const goOutputFlag = yield* LegacyOutputFlag; + + if (Option.isSome(flags.target) && Option.isSome(flags.projectRef)) { + return yield* new LegacyConfigDiffFlagConflictError({ + message: "--target and --project-ref are mutually exclusive; pass at most one.", + }); + } + + // Resolve the comparison target to a project ref. `--target` accepts a + // branch name, a branch UUID, or a raw project ref (same acceptance as + // `link`); a ref-shaped value skips the parent-project resolution entirely + // so it works in an unlinked directory. + let ref: string; + let branch: string | undefined; + if (Option.isSome(flags.target) && !LEGACY_BRANCH_PROJECT_REF_PATTERN.test(flags.target.value)) { + const target = flags.target.value; + branch = target; + const parentRef = yield* resolver.resolve(Option.none()); + ref = yield* legacyResolveBranchProjectRef(target, parentRef, { + mapGetError: mapBranchResolveError, + mapFindError: mapBranchResolveError, + }).pipe( + Effect.catchTag( + "LegacyConfigDiffBranchResolveStatusError", + ( + cause, + ): Effect.Effect< + never, + LegacyConfigDiffBranchNotFoundError | LegacyConfigDiffBranchResolveStatusError + > => + cause.status === 404 + ? Effect.fail( + new LegacyConfigDiffBranchNotFoundError({ + message: `Branch "${legacySanitizeInlineName(target)}" not found. Run \`supabase branches list\` to see available branches.`, + }), + ) + : Effect.fail(cause), + ), + ); + } else if (Option.isSome(flags.target)) { + ref = flags.target.value; + } else { + ref = yield* resolver.resolve(flags.projectRef); + } + + yield* Effect.gen(function* () { + // 1. Load the local config, merging a matching `[remotes.*]` block over + // the base document when the target ref names a declared branch (ADR + // 0018). Never writes — this command is read-only by contract. + const loaded = yield* loadProjectConfig(runtimeInfo.cwd, { + projectRef: ref, + goViperCompat: true, + }).pipe( + Effect.catchTag( + "ProjectConfigParseError", + (cause) => + new LegacyConfigDiffLoadConfigError({ + message: `failed to parse supabase/config.toml: ${String(cause.cause)}`, + }), + ), + Effect.catchTag( + "DuplicateRemoteProjectIdError", + (cause) => new LegacyConfigDiffLoadConfigError({ message: cause.message }), + ), + ); + if (loaded === null) { + return yield* new LegacyConfigDiffLoadConfigError({ + message: + "failed to read supabase/config.toml: file not found. Run `supabase init` to create one.", + }); + } + + const context: LegacyConfigDiffContext = { + projectRef: ref, + branch, + appliedRemote: loaded.appliedRemote, + schemaVersion: loaded.schemaRef ?? PROJECT_CONFIG_SCHEMA_URL, + }; + yield* output.raw(legacyConfigDiffComparisonLine(context), "stderr"); + + // 2. Fetch the effective remote config (single read-only call). + const fetching = + output.format === "text" ? yield* output.task("Fetching remote config...") : undefined; + const response = yield* api.v2.getProjectConfig({ ref }).pipe( + Effect.tapError(() => fetching?.fail() ?? Effect.void), + Effect.catch( + mapLegacyHttpError({ + networkError: LegacyConfigDiffReadNetworkError, + statusError: LegacyConfigDiffReadStatusError, + networkMessage: (cause) => `failed to read project config: ${cause}`, + statusMessage: readStatusMessage, + }), + ), + ); + yield* fetching?.clear() ?? Effect.void; + + // 3. Classify. `declared` is the raw merged document (key presence); + // `local` is the decoded effective config; env-resolved leaves carry the + // resolving variable's name for the output. + const changeSet = diffProjectConfig({ + local: loaded.config, + declared: loaded.document, + remote: legacyConfigDiffRemoteBlocks(response.data.attributes), + envReferences: legacyConfigDiffEnvReferences(loaded.valueOrigins), + }); + + yield* output.raw(legacyConfigDiffScopeLine(changeSet.scope), "stderr"); + + // 4. Emit. Go-compat `-o` first, then `--output-format`, then text. + const goFormat = Option.getOrUndefined(goOutputFlag); + const machinePayload = (includeNullValues: boolean) => + legacyConfigDiffPayload(changeSet, context, { includeNullValues }); + if (goFormat === "json") { + yield* output.raw(encodeGoJson(machinePayload(true))); + } else if (goFormat === "yaml") { + yield* output.raw(encodeYaml(machinePayload(true))); + } else if (goFormat === "toml") { + yield* output.raw(encodeToml(machinePayload(false))); + } else if (goFormat === "env") { + yield* output.raw(`${encodeEnv(machinePayload(false))}\n`); + } else if (output.format !== "text") { + const total = changeSet.changes.length; + const message = + total === 0 ? "No config differences found." : `${total} config difference(s) found.`; + yield* output.success(message, machinePayload(true)); + } else { + yield* output.raw(legacyRenderConfigDiffText(changeSet)); + } + + // 5. `--exit-code`: differences flip the exit status after the payload is + // out, without an error envelope corrupting machine output. + if (flags.exitCode && changeSet.changes.length > 0) { + yield* processControl.setExitCode(1); + } + }).pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush)); +}); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts new file mode 100644 index 0000000000..dff2aef5b4 --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -0,0 +1,643 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Layer, Option } from "effect"; +import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +import { + mockOutput, + mockProcessControl, + mockRuntimeInfo, +} from "../../../../../tests/helpers/mocks.ts"; +import { + buildLegacyTestRuntime, + LEGACY_VALID_REF, + legacyJsonResponse, + legacyTransportFailure, + mockLegacyCliConfig, + mockLegacyLinkedProjectCacheTracked, + mockLegacyPlatformApi, + mockLegacyTelemetryStateTracked, + useLegacyTempWorkdir, +} from "../../../../../tests/helpers/legacy-mocks.ts"; +import { legacyConfigDiff } from "./diff.handler.ts"; + +const tempRoot = useLegacyTempWorkdir("supabase-config-diff-int-"); + +const BRANCH_UUID = "11111111-1111-4111-8111-111111111111"; +const BRANCH_REF = "cccccccccccccccccccc"; + +function writeConfig(toml: string): string { + const dir = join(tempRoot.current, "supabase"); + mkdirSync(dir, { recursive: true }); + const path = join(dir, "config.toml"); + writeFileSync(path, toml); + return path; +} + +function writeProjectEnv(dotenv: string): void { + const dir = join(tempRoot.current, "supabase"); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, ".env"), dotenv); +} + +/** + * Schema-valid v2 project-config body whose managed values all sit at the + * local schema defaults, so an empty config.toml diffs clean against it. + */ +function v2Response( + opts: { + readonly ref?: string; + readonly attributes?: (attributes: Record) => Record; + } = {}, +) { + const attributes: Record = { + database: { + ssl_enforced: false, + network_restrictions: { + entitlement: "allowed", + status: "applied", + allowed_cidrs: [ + { address: "0.0.0.0/0", type: "v4" }, + { address: "::/0", type: "v6" }, + ], + }, + postgres_settings: {}, + }, + pooler: { + pool_mode: "transaction", + ignore_startup_parameters: "", + server_idle_timeout: 0, + server_lifetime: 0, + query_wait_timeout: 0, + reserve_pool_size: 0, + default_pool_size: 20, + max_client_conn: 100, + }, + auth: {}, + api: { + db_schema: "public,graphql_public", + db_extra_search_path: "public,extensions", + max_rows: 1000, + db_pool_acquisition_timeout: 10, + db_pool: null, + }, + realtime: { + private_only: false, + max_concurrent_users: 200, + max_events_per_second: 100, + max_bytes_per_second: 100000, + max_channels_per_client: 100, + max_joins_per_second: 100, + max_presence_events_per_second: 100, + max_payload_size_in_kb: 100, + presence_enabled: true, + suspend: false, + connection_pool: 10, + postgres_changes_pool: null, + }, + storage: { + file_size_limit: 52428800, + features: { + image_transformation: { enabled: false }, + s3_protocol: { enabled: true }, + purge_cache: { enabled: false }, + iceberg_catalog: { enabled: false, max_namespaces: 5, max_tables: 10, max_catalogs: 2 }, + vector_buckets: { enabled: true, max_buckets: 10, max_indexes: 5 }, + }, + capabilities: { list_v2: true, iceberg_catalog: false }, + upstream_target: "main", + migration_version: "20240701", + database_pool_mode: "transaction", + }, + }; + return { + data: { + type: "project_config", + id: opts.ref ?? LEGACY_VALID_REF, + attributes: opts.attributes === undefined ? attributes : opts.attributes(attributes), + }, + }; +} + +/** V1GetABranch body for the `--target ` lookup. */ +const BRANCH_BY_NAME = { + id: BRANCH_UUID, + name: "staging", + project_ref: BRANCH_REF, + parent_project_ref: LEGACY_VALID_REF, + is_default: false, + persistent: true, + status: "MIGRATIONS_PASSED", + created_at: "2026-05-27T01:02:03Z", + updated_at: "2026-05-27T01:02:04Z", + with_data: false, +}; + +/** V1GetABranchConfig body for the `--target ` lookup. */ +const BRANCH_CONFIG = { + ref: BRANCH_REF, + postgres_version: "15", + postgres_engine: "15", + release_channel: "ga", + status: "ACTIVE_HEALTHY", + db_host: "h", + db_port: 5432, +}; + +interface SetupOpts { + readonly toml?: string; + readonly dotenv?: string; + readonly format?: "text" | "json" | "stream-json"; + readonly goOutput?: "env" | "pretty" | "json" | "toml" | "yaml"; + readonly v2?: { status: number; body: unknown } | "fail"; + readonly branchByName?: { status: number; body: unknown }; + readonly branchByUuid?: { status: number; body: unknown }; +} + +function setup(opts: SetupOpts = {}) { + if (opts.toml !== undefined) { + writeConfig(opts.toml); + } + if (opts.dotenv !== undefined) { + writeProjectEnv(opts.dotenv); + } + const out = mockOutput({ format: opts.format ?? "text" }); + const api = mockLegacyPlatformApi({ + handler: (request) => { + const url = request.url; + if (url.includes("/v2/projects/")) { + if (opts.v2 === "fail") { + return Effect.fail(legacyTransportFailure(request)); + } + const v2 = opts.v2 ?? { status: 200, body: v2Response() }; + return Effect.succeed(legacyJsonResponse(request, v2.status, v2.body)); + } + if (url.includes("/v1/branches/")) { + const b = opts.branchByUuid ?? { status: 200, body: BRANCH_CONFIG }; + return Effect.succeed(legacyJsonResponse(request, b.status, b.body)); + } + if (url.includes("/branches/")) { + const b = opts.branchByName ?? { status: 200, body: BRANCH_BY_NAME }; + return Effect.succeed(legacyJsonResponse(request, b.status, b.body)); + } + return Effect.succeed(legacyJsonResponse(request, 200, {})); + }, + }); + const telemetry = mockLegacyTelemetryStateTracked(); + const linkedProjectCache = mockLegacyLinkedProjectCacheTracked(); + const processControl = mockProcessControl(); + const layer = Layer.mergeAll( + buildLegacyTestRuntime({ + out, + api, + cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }), + runtimeInfo: mockRuntimeInfo({ cwd: tempRoot.current }), + telemetry: telemetry.layer, + linkedProjectCache: linkedProjectCache.layer, + processControl, + goOutput: opts.goOutput === undefined ? Option.none() : Option.some(opts.goOutput), + }), + ); + return { layer, out, api, telemetry, linkedProjectCache, processControl }; +} + +const noFlags = { + projectRef: Option.none(), + target: Option.none(), + exitCode: false, +}; + +describe("legacy config diff integration", () => { + it.live("reports drift against the linked project without touching the config file", () => { + const { layer, out, processControl, telemetry, linkedProjectCache } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + }); + const configPath = join(tempRoot.current, "supabase", "config.toml"); + const before = { + mtimeMs: statSync(configPath).mtimeMs, + contents: readFileSync(configPath, "utf8"), + }; + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + + // Never writes: mtime and contents unchanged after a run with differences. + expect(statSync(configPath).mtimeMs).toBe(before.mtimeMs); + expect(readFileSync(configPath, "utf8")).toBe(before.contents); + + expect(out.stderrText).toContain( + `Comparing against project ${LEGACY_VALID_REF} using base config`, + ); + expect(out.stderrText).toContain( + "Comparison scope: api, auth, database, pooler, realtime, storage", + ); + expect(out.stdoutText).toContain("api.max_rows [update]"); + expect(out.stdoutText).toContain("local: 500"); + expect(out.stdoutText).toContain("remote: 1000"); + expect(out.stdoutText).toContain( + "1 difference(s) found (1 update, 0 remote-only, 0 local-only).", + ); + // Differences without --exit-code leave the exit status alone. + expect(processControl.exitCode).toBeUndefined(); + expect(telemetry.flushed).toBe(true); + expect(linkedProjectCache.cachedRef).toBe(LEGACY_VALID_REF); + }).pipe(Effect.provide(layer)); + }); + + it.live("a clean config produces the success message and exit 0 even with --exit-code", () => { + const { layer, out, processControl } = setup({ toml: 'project_id = "test"\n' }); + return Effect.gen(function* () { + yield* legacyConfigDiff({ ...noFlags, exitCode: true }); + expect(out.stdoutText).toContain("No config differences found."); + expect(processControl.exitCode).toBeUndefined(); + }).pipe(Effect.provide(layer)); + }); + + it.live("--exit-code sets exit 1 when differences are found", () => { + const { layer, processControl } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + }); + return Effect.gen(function* () { + yield* legacyConfigDiff({ ...noFlags, exitCode: true }); + expect(processControl.exitCode).toBe(1); + }).pipe(Effect.provide(layer)); + }); + + it.live("declared properties the response does not carry are local_only", () => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n[auth]\nsite_url = "https://local.example.com"\n', + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("auth.site_url [local only]"); + expect(out.stdoutText).toContain('local: "https://local.example.com"'); + expect(out.stdoutText).toContain("remote: (not returned)"); + }).pipe(Effect.provide(layer)); + }); + + it.live("env()-resolved values compare resolved and name the variable on drift", () => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = "env(PGRST_MAX_ROWS)"\n', + dotenv: "PGRST_MAX_ROWS=500\n", + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("api.max_rows [update]"); + expect(out.stdoutText).toContain("local: 500 (from env PGRST_MAX_ROWS)"); + }).pipe(Effect.provide(layer)); + }); + + it.live("declared secrets are masked, not compared, and never count for --exit-code", () => { + const { layer, out, processControl } = setup({ + toml: [ + 'project_id = "test"', + "[auth.external.github]", + "enabled = true", + 'client_id = "id"', + 'secret = "env(GITHUB_SECRET)"', + "", + ].join("\n"), + dotenv: "GITHUB_SECRET=shh\n", + v2: { + status: 200, + body: v2Response({ + attributes: (attributes) => ({ + ...attributes, + auth: { external_github_enabled: true, external_github_client_id: "id" }, + }), + }), + }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff({ ...noFlags, exitCode: true }); + expect(out.stdoutText).toContain("No config differences found."); + expect(out.stdoutText).toContain( + "Note: 1 credential value(s) not compared (masked by the API): auth.external.github.secret", + ); + expect(processControl.exitCode).toBeUndefined(); + }).pipe(Effect.provide(layer)); + }); + + it.live("a matching [remotes.*] block becomes the local operand", () => { + const { layer, out } = setup({ + toml: [ + 'project_id = "test"', + "[api]", + "max_rows = 500", + "[remotes.staging]", + `project_id = "${LEGACY_VALID_REF}"`, + "[remotes.staging.api]", + "max_rows = 1000", + "", + ].join("\n"), + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stderrText).toContain( + `Comparing against project ${LEGACY_VALID_REF} using [remotes.staging]`, + ); + // The merged branch operand (max_rows = 1000) matches the remote, so the + // base config's 500 must NOT surface as drift. + expect(out.stdoutText).toContain("No config differences found."); + }).pipe(Effect.provide(layer)); + }); + + it.live("--target resolves a branch name via the parent project", () => { + const { layer, out, api } = setup({ + toml: 'project_id = "test"\n', + v2: { status: 200, body: v2Response({ ref: BRANCH_REF }) }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff({ ...noFlags, target: Option.some("staging") }); + expect(out.stderrText).toContain( + `Comparing against 'staging' (branch ${BRANCH_REF}) using base config`, + ); + const urls = api.requests.map((request) => request.url); + expect( + urls.some((url) => url.includes(`/v1/projects/${LEGACY_VALID_REF}/branches/staging`)), + ).toBe(true); + expect(urls.some((url) => url.includes(`/v2/projects/${BRANCH_REF}/config`))).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("--target resolves a branch UUID directly", () => { + const { layer, api } = setup({ + toml: 'project_id = "test"\n', + v2: { status: 200, body: v2Response({ ref: BRANCH_REF }) }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff({ ...noFlags, target: Option.some(BRANCH_UUID) }); + const urls = api.requests.map((request) => request.url); + expect(urls.some((url) => url.includes(`/v1/branches/${BRANCH_UUID}`))).toBe(true); + expect(urls.some((url) => url.includes(`/v2/projects/${BRANCH_REF}/config`))).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("--target accepts a raw project ref without touching the branches API", () => { + const { layer, api } = setup({ + toml: 'project_id = "test"\n', + v2: { status: 200, body: v2Response({ ref: BRANCH_REF }) }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff({ ...noFlags, target: Option.some(BRANCH_REF) }); + const urls = api.requests.map((request) => request.url); + expect(urls.some((url) => url.includes("/branches/"))).toBe(false); + expect(urls.some((url) => url.includes(`/v2/projects/${BRANCH_REF}/config`))).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("an unknown branch fails with a branches-list suggestion", () => { + const { layer } = setup({ + toml: 'project_id = "test"\n', + branchByName: { status: 404, body: { message: "not found" } }, + }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff({ ...noFlags, target: Option.some("ghost") }).pipe( + Effect.exit, + ); + expect(Exit.isFailure(exit)).toBe(true); + const rendered = JSON.stringify(exit); + expect(rendered).toContain("LegacyConfigDiffBranchNotFoundError"); + expect(rendered).toContain('Branch \\"ghost\\" not found'); + expect(rendered).toContain("supabase branches list"); + }).pipe(Effect.provide(layer)); + }); + + it.live("a non-404 branch lookup failure keeps its status error", () => { + const { layer } = setup({ + toml: 'project_id = "test"\n', + branchByName: { status: 500, body: { message: "boom" } }, + }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff({ ...noFlags, target: Option.some("staging") }).pipe( + Effect.exit, + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("LegacyConfigDiffBranchResolveStatusError"); + }).pipe(Effect.provide(layer)); + }); + + it.live("--target and --project-ref together are rejected", () => { + const { layer, api } = setup({ toml: 'project_id = "test"\n' }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff({ + exitCode: false, + target: Option.some("staging"), + projectRef: Option.some(LEGACY_VALID_REF), + }).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("LegacyConfigDiffFlagConflictError"); + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + + it.live("a missing config file points at supabase init", () => { + const { layer } = setup(); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + const rendered = JSON.stringify(exit); + expect(rendered).toContain("LegacyConfigDiffLoadConfigError"); + expect(rendered).toContain("supabase/config.toml: file not found"); + expect(rendered).toContain("supabase init"); + }).pipe(Effect.provide(layer)); + }); + + it.live("a malformed config file fails as a parse error", () => { + const { layer } = setup({ toml: "not [valid toml\n" }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("failed to parse supabase/config.toml"); + }).pipe(Effect.provide(layer)); + }); + + it.live("duplicate [remotes.*] project_ids abort the load", () => { + const { layer } = setup({ + toml: [ + 'project_id = "test"', + "[remotes.a]", + `project_id = "${LEGACY_VALID_REF}"`, + "[remotes.b]", + `project_id = "${LEGACY_VALID_REF}"`, + "", + ].join("\n"), + }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("LegacyConfigDiffLoadConfigError"); + }).pipe(Effect.provide(layer)); + }); + + it.live("a remote config transport failure maps to the read network error", () => { + const { layer, telemetry } = setup({ toml: 'project_id = "test"\n', v2: "fail" }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("LegacyConfigDiffReadNetworkError"); + // Telemetry still flushes on failure via Effect.ensuring. + expect(telemetry.flushed).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("a remote config error status maps to the read status error", () => { + const { layer } = setup({ + toml: 'project_id = "test"\n', + v2: { status: 403, body: { message: "forbidden" } }, + }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("LegacyConfigDiffReadStatusError"); + }).pipe(Effect.provide(layer)); + }); + + it.live("--output-format json emits the structured change set", () => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + format: "json", + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + const success = out.messages.find((message) => message.type === "success"); + expect(success).toBeDefined(); + expect(success?.message).toContain("1 config difference(s) found."); + const data = success?.data as Record; + expect(data["target"]).toMatchObject({ + project_ref: LEGACY_VALID_REF, + local_scope: "base", + }); + expect(data["scope"]).toEqual(["api", "auth", "database", "pooler", "realtime", "storage"]); + expect(data["changes"]).toEqual([ + { path: "api.max_rows", class: "update", local: 500, remote: 1000 }, + ]); + expect(data["counts"]).toEqual({ update: 1, remote_only: 0, local_only: 0, total: 1 }); + expect(data["masked"]).toEqual([]); + expect(typeof data["schema_version"]).toBe("string"); + }).pipe(Effect.provide(layer)); + }); + + it.live("--output-format stream-json reports zero differences as a success result", () => { + const { layer, out } = setup({ toml: 'project_id = "test"\n', format: "stream-json" }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + const success = out.messages.find((message) => message.type === "success"); + expect(success?.message).toContain("No config differences found."); + }).pipe(Effect.provide(layer)); + }); + + it.live("-o json wins over --output-format and emits the payload on stdout", () => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + format: "json", + goOutput: "json", + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.messages.find((message) => message.type === "success")).toBeUndefined(); + const parsed: unknown = JSON.parse(out.stdoutText); + expect(parsed).toMatchObject({ + counts: { total: 1 }, + changes: [{ path: "api.max_rows", class: "update", local: 500, remote: 1000 }], + }); + }).pipe(Effect.provide(layer)); + }); + + it.live("-o yaml, -o toml, and -o env each encode the payload", () => { + const run = (goOutput: "yaml" | "toml" | "env") => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + goOutput, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + return out.stdoutText; + }).pipe(Effect.provide(layer)); + }; + return Effect.gen(function* () { + const yaml = yield* run("yaml"); + expect(yaml).toContain("api.max_rows"); + expect(yaml).toContain("class: update"); + const toml = yield* run("toml"); + expect(toml).toContain("api.max_rows"); + // TOML cannot represent null — unset sides are dropped, not nulled. + expect(toml).not.toContain("null"); + const env = yield* run("env"); + expect(env).toContain("COUNTS_TOTAL=1"); + }); + }); + + it.live("-o pretty falls through to the text rendering", () => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + goOutput: "pretty", + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("api.max_rows [update]"); + }).pipe(Effect.provide(layer)); + }); + + it.live("a fetch failure in json mode still maps cleanly without a spinner", () => { + const { layer } = setup({ toml: 'project_id = "test"\n', v2: "fail", format: "json" }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("LegacyConfigDiffReadNetworkError"); + }).pipe(Effect.provide(layer)); + }); + + it.live("json payload carries the remotes scope and env variable annotations", () => { + const { layer, out } = setup({ + toml: [ + 'project_id = "test"', + "[remotes.staging]", + `project_id = "${LEGACY_VALID_REF}"`, + "[remotes.staging.api]", + 'max_rows = "env(PGRST_MAX_ROWS)"', + "", + ].join("\n"), + dotenv: "PGRST_MAX_ROWS=500\n", + format: "json", + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + const success = out.messages.find((message) => message.type === "success"); + const data = success?.data as Record; + expect(data["target"]).toMatchObject({ local_scope: "remotes.staging" }); + expect(data["changes"]).toEqual([ + { + path: "api.max_rows", + class: "update", + local: 500, + remote: 1000, + env_variable: "PGRST_MAX_ROWS", + }, + ]); + }).pipe(Effect.provide(layer)); + }); + + it.live("remote-only drift renders (unset) locals distinguishably from empty ones", () => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n', + v2: { + status: 200, + body: v2Response({ + attributes: (attributes) => ({ + ...attributes, + database: { + ...(attributes["database"] as Record), + postgres_settings: { work_mem: "64MB" }, + }, + }), + }), + }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("db.settings.work_mem [remote only]"); + expect(out.stdoutText).toContain("local: (unset)"); + expect(out.stdoutText).toContain('remote: "64MB"'); + }).pipe(Effect.provide(layer)); + }); +}); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.live.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.live.test.ts new file mode 100644 index 0000000000..efa6e463f2 --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.live.test.ts @@ -0,0 +1,49 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; + +import { + describeLiveProject, + requireLiveProjectRef, + runSupabaseLive, +} from "../../../../../tests/helpers/live.ts"; + +const LIVE_TIMEOUT_MS = 120_000; + +// Golden path only: the one thing mocks cannot prove is the real +// `GET /v2/projects/{ref}/config` response shape (the GoTrue-keyed auth +// record especially) decoding and classifying cleanly. Branch coverage lives +// in diff.integration.test.ts. +describeLiveProject("supabase config diff (live)", () => { + let projectDir: string | undefined; + + afterEach(async () => { + if (projectDir !== undefined) { + await rm(projectDir, { recursive: true, force: true }); + projectDir = undefined; + } + }); + + test( + "diffs a freshly-initialized config against the project", + { timeout: LIVE_TIMEOUT_MS }, + async () => { + const ref = requireLiveProjectRef(); + projectDir = await mkdtemp(join(tmpdir(), "supabase-config-diff-live-")); + + const init = await runSupabaseLive(["init"], { cwd: projectDir }); + expect(init.exitCode).toBe(0); + + const { exitCode, stdout, stderr } = await runSupabaseLive( + ["config", "diff", "--project-ref", ref], + { cwd: projectDir }, + ); + expect(`${stdout}${stderr}`).not.toContain("Unauthorized"); + expect(stderr).toContain(`Comparing against project ${ref} using base config`); + expect(stderr).toContain("Comparison scope:"); + // Read-only success regardless of drift (no --exit-code passed). + expect(exitCode).toBe(0); + }, + ); +}); diff --git a/apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts b/apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts new file mode 100644 index 0000000000..01ad03e877 --- /dev/null +++ b/apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts @@ -0,0 +1,70 @@ +import type { SupabaseApiError } from "@supabase/api/effect"; +import { Effect } from "effect"; + +import { LegacyPlatformApi } from "../auth/legacy-platform-api.service.ts"; + +/** + * Project ref pattern shared by every Management-API endpoint that accepts a + * 20-lowercase-letter project reference. + */ +export const LEGACY_BRANCH_PROJECT_REF_PATTERN = /^[a-z]{20}$/; + +/** + * Permissive UUID pattern (any 8-4-4-4-12 hex sequence) — accepts any RFC 4122 + * variant including v6/v7 and version 0, matching the established liberal + * acceptance rather than the v1–v5 + variant-1 subset. + */ +export const LEGACY_BRANCH_UUID_PATTERN = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +/** + * Per-family error mapping for {@link legacyResolveBranchProjectRef}: each + * caller keeps its own tagged error classes (built with `mapLegacyHttpError`) + * so error identities, messages, and actionability stay family-owned. + */ +export interface LegacyBranchRefResolveMappers { + /** Maps a `GET /v1/branches/{branch_id}` (UUID lookup) failure. */ + readonly mapGetError: (cause: SupabaseApiError) => Effect.Effect; + /** Maps a `GET /v1/projects/{ref}/branches/{name}` (name lookup) failure. */ + readonly mapFindError: (cause: SupabaseApiError) => Effect.Effect; +} + +/** + * Resolves an arbitrary branch identifier to its project ref: + * + * 1. If the input matches `^[a-z]{20}$`, it's already a project ref — return as-is. + * 2. Else if the input is a UUID, call `V1GetABranchConfig` (`GET /v1/branches/{id}`) + * and return `JSON200.ref`. + * 3. Otherwise treat as a branch name under the linked project ref: call + * `V1GetABranch` (`GET /v1/projects/{ref}/branches/{name}`) and return + * `JSON200.project_ref`. + * + * The persistent `--project-ref` is required for path 3 and is passed in by + * the caller (which has already run `LegacyProjectRefResolver` so the linked + * project cache write does not re-fire here). + */ +export function legacyResolveBranchProjectRef( + input: string, + projectRef: string, + mappers: LegacyBranchRefResolveMappers, +) { + return Effect.gen(function* () { + if (LEGACY_BRANCH_PROJECT_REF_PATTERN.test(input)) { + return input; + } + + const api = yield* LegacyPlatformApi; + + if (LEGACY_BRANCH_UUID_PATTERN.test(input)) { + const detail = yield* api.v1 + .getABranchConfig({ branch_id_or_ref: input }) + .pipe(Effect.catch(mappers.mapGetError)); + return detail.ref; + } + + const branch = yield* api.v1 + .getABranch({ ref: projectRef, name: input }) + .pipe(Effect.catch(mappers.mapFindError)); + return branch.project_ref; + }); +} diff --git a/packages/config/src/config-diff.ts b/packages/config/src/config-diff.ts index ac99d6ad8f..23905e59c3 100644 --- a/packages/config/src/config-diff.ts +++ b/packages/config/src/config-diff.ts @@ -116,9 +116,10 @@ export interface DiffProjectConfigOptions { /** * The raw (pre-decode, post-merge) document the config was loaded from. * Declares which paths the file actually sets — the decoded config cannot, - * because decoding materializes every default. + * because decoding materializes every default. `undefined` (a file that did + * not parse to an object) means nothing is declared. */ - readonly declared: Readonly>; + readonly declared: Readonly> | undefined; readonly remote: RemoteProjectConfig; /** * Baseline for `remote_only` suppression: a remote value equal to this @@ -223,11 +224,12 @@ export function isEqualConfigValue(a: unknown, b: unknown): boolean { */ export function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChangeSet { const defaults = options.defaults ?? getDefaultProjectConfig(); + const declaredRoot = options.declared ?? {}; const changes: Array = []; const masked: Array = []; for (const property of MANAGED_CONFIG_PROPERTIES) { - const declared = isDeclaredAtPath(options.declared, property.path); + const declared = isDeclaredAtPath(declaredRoot, property.path); if (property.secret === true) { if (declared) { diff --git a/packages/config/src/config-diff.unit.test.ts b/packages/config/src/config-diff.unit.test.ts index 30eb1c76a3..d74df7aff5 100644 --- a/packages/config/src/config-diff.unit.test.ts +++ b/packages/config/src/config-diff.unit.test.ts @@ -72,6 +72,15 @@ describe("managed surface", () => { }); describe("diffProjectConfig classification", () => { + test("an undefined declared document means nothing is declared", () => { + const result = diffProjectConfig({ + local: decodeProjectConfig({}), + declared: undefined, + remote: { api: { max_rows: 250 } }, + }); + expect(changeAt(result.changes, "api.max_rows")).toMatchObject({ class: "remote_only" }); + }); + test("declared value differing from remote is an update", () => { const result = diffWith( { api: { max_rows: 500 } }, From 24607dcb618e9f79a29e35b87d1f93089afa8812 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Fri, 21 Aug 2026 11:53:54 -0500 Subject: [PATCH 04/12] feat(cli): reject legacy -o on config diff (CLI-2156) Colum confirmed on the ticket that net-new commands carry no Go parity contract, so the Go-compat -o/--output flag is now rejected outright (every value, pretty included) with an error pointing at --output-format, failing fast before target resolution or any network call. Drops the four Go-encoder emit branches, simplifies the JSON payload to always carry explicit nulls for unset sides, and updates SIDE_EFFECTS.md, the divergences entry, and the tests. Ticket acceptance criteria amended accordingly. Co-Authored-By: Claude Fable 5 --- apps/cli/docs/go-cli-divergences.md | 16 +++--- .../commands/config/diff/SIDE_EFFECTS.md | 12 ++-- .../commands/config/diff/diff.errors.ts | 13 +++++ .../commands/config/diff/diff.format.ts | 17 ++---- .../commands/config/diff/diff.handler.ts | 35 +++++------- .../config/diff/diff.integration.test.ts | 55 +++++-------------- 6 files changed, 61 insertions(+), 87 deletions(-) diff --git a/apps/cli/docs/go-cli-divergences.md b/apps/cli/docs/go-cli-divergences.md index 3714a0b10c..edef0c6781 100644 --- a/apps/cli/docs/go-cli-divergences.md +++ b/apps/cli/docs/go-cli-divergences.md @@ -12,14 +12,14 @@ not a compatibility promise. These commands exist in the TS CLI today but have no direct top-level equivalent in the old Go CLI reference. -| TS command | TS path | Notes | -| ----------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `dev` | `planned` | Reserved for a TS-native long-running local development workflow command that watches files and orchestrates subcommands. Track this as TS-only unless a direct Go equivalent emerges. | -| `logs` | [`../src/next/commands/logs/logs.command.ts`](../src/next/commands/logs/logs.command.ts) | Streams local stack logs. No top-level `logs` command exists in the old Go CLI reference. | -| `api` | [`../src/next/commands/platform/api.command.ts`](../src/next/commands/platform/api.command.ts) | Low-level Management API client. It supersedes the old generated tree with explicit discovery via `supabase api routes` and execution via `supabase api request [--method ]`. | -| `stack` | [`../src/next/cli/root.ts`](../src/next/cli/root.ts) | TS-only local runtime namespace exposing `stack start`, `stack stop`, `stack status`, `stack list`, and `stack update`. Top-level `start`, `stop`, and `status` remain aliases. | -| `branches switch` | [`../src/next/commands/branches/switch/switch.command.ts`](../src/next/commands/branches/switch/switch.command.ts) | No direct Go equivalent. Updates local active-branch state so subsequent commands target the selected branch. | -| `config diff` | [`../src/legacy/commands/config/diff/diff.command.ts`](../src/legacy/commands/config/diff/diff.command.ts) | Read-only drift report between `supabase/config.toml` and `GET /v2/projects/{ref}/config` (CLI-2156). TS-only: the old Go CLI had no config diff. `--target` accepts a branch name/UUID/ref; `--exit-code` exits 1 on drift. Comparison core lives in `@supabase/config` (ADR 0019). | +| TS command | TS path | Notes | +| ----------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `dev` | `planned` | Reserved for a TS-native long-running local development workflow command that watches files and orchestrates subcommands. Track this as TS-only unless a direct Go equivalent emerges. | +| `logs` | [`../src/next/commands/logs/logs.command.ts`](../src/next/commands/logs/logs.command.ts) | Streams local stack logs. No top-level `logs` command exists in the old Go CLI reference. | +| `api` | [`../src/next/commands/platform/api.command.ts`](../src/next/commands/platform/api.command.ts) | Low-level Management API client. It supersedes the old generated tree with explicit discovery via `supabase api routes` and execution via `supabase api request [--method ]`. | +| `stack` | [`../src/next/cli/root.ts`](../src/next/cli/root.ts) | TS-only local runtime namespace exposing `stack start`, `stack stop`, `stack status`, `stack list`, and `stack update`. Top-level `start`, `stop`, and `status` remain aliases. | +| `branches switch` | [`../src/next/commands/branches/switch/switch.command.ts`](../src/next/commands/branches/switch/switch.command.ts) | No direct Go equivalent. Updates local active-branch state so subsequent commands target the selected branch. | +| `config diff` | [`../src/legacy/commands/config/diff/diff.command.ts`](../src/legacy/commands/config/diff/diff.command.ts) | Read-only drift report between `supabase/config.toml` and `GET /v2/projects/{ref}/config` (CLI-2156). TS-only: the old Go CLI had no config diff. `--target` accepts a branch name/UUID/ref; `--exit-code` exits 1 on drift. Rejects the Go-compat `-o/--output` flag outright — machine output is `--output-format json\|stream-json` only (no Go parity contract for net-new commands, per the CLI-2156 discussion). Comparison core lives in `@supabase/config` (ADR 0019). | ## Flag divergences from the Go reference diff --git a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md index 87f6e0ee9c..ad04d67554 100644 --- a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md @@ -54,6 +54,7 @@ All Bearer-authenticated, all read-only. | ---- | ------------------------------------------------------------------------------ | | `0` | success — including when differences are found, unless `--exit-code` is passed | | `1` | `--exit-code` passed and at least one difference found | +| `1` | the Go-compat `-o/--output` global flag passed (any value — unsupported here) | | `1` | missing or malformed `supabase/config.toml` | | `1` | `--target` and `--project-ref` passed together | | `1` | unknown branch (`--target` 404) | @@ -84,12 +85,13 @@ the file sets masked secrets. `env_variable`; unset sides are `null`), `masked[]`, and `counts` (per class + `total`). -### `-o json|yaml|toml|env` (Go-compat) +### `-o/--output` (Go-compat global flag) -The same payload through the shared Go-compatible map encoders. TOML and env -drop `null`-valued keys (TOML cannot represent null); `class` still -disambiguates which side is absent. `-o pretty` (or unset) falls through to -the `--output-format` behavior above. +**Not supported.** Any `-o` value — the machine formats and `pretty` alike — +fails fast (before target resolution or any network call) with +`the -o/--output flag is not supported by config diff; use --output-format +json|stream-json instead.` This is a net-new TS command with no Go parity +contract (CLI-2156 ticket discussion). ## Notes diff --git a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts index 2094c28a1e..902456af19 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts @@ -26,6 +26,19 @@ export class LegacyConfigDiffLoadConfigError extends Data.TaggedError( } } +/** + * The Go-compat global `-o/--output` flag was passed. `config diff` is a + * net-new TS command with no Go parity contract, so machine output goes + * through `--output-format` only (per Colum on CLI-2156). + */ +export class LegacyConfigDiffOutputFlagUnsupportedError extends Data.TaggedError( + "LegacyConfigDiffOutputFlagUnsupportedError", +)<{ readonly message: string }> { + get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { + return actionability.invalidInput; + } +} + /** `--target` and `--project-ref` passed together. */ export class LegacyConfigDiffFlagConflictError extends Data.TaggedError( "LegacyConfigDiffFlagConflictError", diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.ts index 07c97bf8a8..d0174d9c46 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.ts @@ -140,23 +140,16 @@ export function legacyRenderConfigDiffText(changeSet: ConfigChangeSet): string { } /** - * The structured result shared by `--output-format json|stream-json` and the - * Go-compat `-o` encodings. `includeNullValues: false` drops `null`-valued - * keys instead of emitting them — TOML cannot represent null, and the env - * flattening renders it uselessly; `class` still disambiguates which side is - * absent. + * The structured result for `--output-format json|stream-json`. Unset sides + * are explicit `null`s, distinguishable from empty values. */ export function legacyConfigDiffPayload( changeSet: ConfigChangeSet, context: LegacyConfigDiffContext, - options: { readonly includeNullValues: boolean }, ): Record { - const valueEntry = (key: string, value: unknown): Record => { - if (value !== undefined) { - return { [key]: value }; - } - return options.includeNullValues ? { [key]: null } : {}; - }; + const valueEntry = (key: string, value: unknown): Record => ({ + [key]: value === undefined ? null : value, + }); const { update, remote_only, local_only } = changeSet.counts; return { diff --git a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts index b155fd6299..7e80784077 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts @@ -17,12 +17,6 @@ import { legacySanitizeInlineName, mapLegacyHttpError, } from "../../../shared/legacy-http-errors.ts"; -import { - encodeEnv, - encodeGoJson, - encodeToml, - encodeYaml, -} from "../../../shared/legacy-go-output.encoders.ts"; import { legacyConfigDiffComparisonLine, legacyConfigDiffEnvReferences, @@ -38,6 +32,7 @@ import { LegacyConfigDiffBranchResolveStatusError, LegacyConfigDiffFlagConflictError, LegacyConfigDiffLoadConfigError, + LegacyConfigDiffOutputFlagUnsupportedError, LegacyConfigDiffReadNetworkError, LegacyConfigDiffReadStatusError, } from "./diff.errors.ts"; @@ -64,6 +59,17 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( const processControl = yield* ProcessControl; const goOutputFlag = yield* LegacyOutputFlag; + // Net-new TS command with no Go parity contract: the Go-compat `-o/--output` + // flag is rejected outright (every value, `pretty` included) rather than + // honored — machine output goes through `--output-format` only (CLI-2156, + // per Colum). Checked first so no target resolution or network call runs. + if (Option.isSome(goOutputFlag)) { + return yield* new LegacyConfigDiffOutputFlagUnsupportedError({ + message: + "the -o/--output flag is not supported by config diff; use --output-format json|stream-json instead.", + }); + } + if (Option.isSome(flags.target) && Option.isSome(flags.projectRef)) { return yield* new LegacyConfigDiffFlagConflictError({ message: "--target and --project-ref are mutually exclusive; pass at most one.", @@ -170,23 +176,12 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( yield* output.raw(legacyConfigDiffScopeLine(changeSet.scope), "stderr"); - // 4. Emit. Go-compat `-o` first, then `--output-format`, then text. - const goFormat = Option.getOrUndefined(goOutputFlag); - const machinePayload = (includeNullValues: boolean) => - legacyConfigDiffPayload(changeSet, context, { includeNullValues }); - if (goFormat === "json") { - yield* output.raw(encodeGoJson(machinePayload(true))); - } else if (goFormat === "yaml") { - yield* output.raw(encodeYaml(machinePayload(true))); - } else if (goFormat === "toml") { - yield* output.raw(encodeToml(machinePayload(false))); - } else if (goFormat === "env") { - yield* output.raw(`${encodeEnv(machinePayload(false))}\n`); - } else if (output.format !== "text") { + // 4. Emit: `--output-format json|stream-json` structured payload, or text. + if (output.format !== "text") { const total = changeSet.changes.length; const message = total === 0 ? "No config differences found." : `${total} config difference(s) found.`; - yield* output.success(message, machinePayload(true)); + yield* output.success(message, legacyConfigDiffPayload(changeSet, context)); } else { yield* output.raw(legacyRenderConfigDiffText(changeSet)); } diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts index dff2aef5b4..ebb2be012f 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -526,58 +526,29 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("-o json wins over --output-format and emits the payload on stdout", () => { - const { layer, out } = setup({ - toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', - format: "json", - goOutput: "json", - }); - return Effect.gen(function* () { - yield* legacyConfigDiff(noFlags); - expect(out.messages.find((message) => message.type === "success")).toBeUndefined(); - const parsed: unknown = JSON.parse(out.stdoutText); - expect(parsed).toMatchObject({ - counts: { total: 1 }, - changes: [{ path: "api.max_rows", class: "update", local: 500, remote: 1000 }], - }); - }).pipe(Effect.provide(layer)); - }); - - it.live("-o yaml, -o toml, and -o env each encode the payload", () => { - const run = (goOutput: "yaml" | "toml" | "env") => { - const { layer, out } = setup({ + it.live("the Go-compat -o flag is rejected outright before any work happens", () => { + // Net-new TS command, no Go parity: every `-o` value is rejected — the + // machine formats and `pretty` alike (CLI-2156, per Colum). + const run = (goOutput: "json" | "pretty") => { + const { layer, api } = setup({ toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', goOutput, }); return Effect.gen(function* () { - yield* legacyConfigDiff(noFlags); - return out.stdoutText; + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + const rendered = JSON.stringify(exit); + expect(rendered).toContain("LegacyConfigDiffOutputFlagUnsupportedError"); + expect(rendered).toContain("use --output-format json|stream-json instead"); + expect(api.requests).toHaveLength(0); }).pipe(Effect.provide(layer)); }; return Effect.gen(function* () { - const yaml = yield* run("yaml"); - expect(yaml).toContain("api.max_rows"); - expect(yaml).toContain("class: update"); - const toml = yield* run("toml"); - expect(toml).toContain("api.max_rows"); - // TOML cannot represent null — unset sides are dropped, not nulled. - expect(toml).not.toContain("null"); - const env = yield* run("env"); - expect(env).toContain("COUNTS_TOTAL=1"); + yield* run("json"); + yield* run("pretty"); }); }); - it.live("-o pretty falls through to the text rendering", () => { - const { layer, out } = setup({ - toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', - goOutput: "pretty", - }); - return Effect.gen(function* () { - yield* legacyConfigDiff(noFlags); - expect(out.stdoutText).toContain("api.max_rows [update]"); - }).pipe(Effect.provide(layer)); - }); - it.live("a fetch failure in json mode still maps cleanly without a spinner", () => { const { layer } = setup({ toml: 'project_id = "test"\n', v2: "fail", format: "json" }); return Effect.gen(function* () { From cae9c14a9723bf0df9eddf3f983d0dca848b6b60 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Thu, 27 Aug 2026 17:16:27 -0500 Subject: [PATCH 05/12] refactor(config): rebuild config diff on the CLI-2230 registry (CLI-2156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLI-2230 (#6339) landed the registry-driven ProjectConfig convergence normalizers with config diff as their intended consumer (ADR 0021), which made this branch's self-contained translation tables a parallel implementation of the same mapping. The classifier now takes two ProjectConfig projections — fromConfigDocument({config, document}) locally (raw-presence-masked) and fromApiProjectConfig(response) remotely — walks the union of their leaves filtered by isComparableProjectConfigPath, and keeps the declared-set-driven classes, masked transparency (registry isSecret rows), and env naming. remote_only suppression baselines on the default config's projection, falling back to the raw default value for push-gated containers (network restrictions' allow-all) and then the zero value. Deletes config-diff.{managed,auth,read}.ts (~900 lines); scope reporting moves to the command layer off the raw response attributes; ADR 0022 rewritten to record the consolidation; --target registered in the CLI-1896 value-consuming flag guard; purity-pin allowlists extended. Co-Authored-By: Claude Fable 5 --- .../commands/config/diff/SIDE_EFFECTS.md | 9 +- .../commands/config/diff/diff.format.ts | 50 +- .../config/diff/diff.format.unit.test.ts | 29 +- .../commands/config/diff/diff.handler.ts | 42 +- .../config/diff/diff.integration.test.ts | 27 + .../legacy/shared/legacy-db-target-flags.ts | 1 + ...diff-classification-and-managed-surface.md | 33 +- .../contracts.ts | 3150 ----------------- packages/config/src/config-diff.auth.ts | 461 --- packages/config/src/config-diff.managed.ts | 200 -- packages/config/src/config-diff.read.ts | 151 - packages/config/src/config-diff.ts | 244 +- packages/config/src/config-diff.unit.test.ts | 226 +- .../config/src/entrypoint-purity.unit.test.ts | 5 + packages/config/src/index.ts | 5 - 15 files changed, 314 insertions(+), 4319 deletions(-) delete mode 100644 packages/api/.generated-output-sync-umcANF/contracts.ts delete mode 100644 packages/config/src/config-diff.auth.ts delete mode 100644 packages/config/src/config-diff.managed.ts delete mode 100644 packages/config/src/config-diff.read.ts diff --git a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md index bb850a87e7..d42d1144cb 100644 --- a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md @@ -100,8 +100,13 @@ contract (CLI-2156 ticket discussion). `[remotes.]` block's `project_id`, the local side is that branch's merged effective config; otherwise the base config. The echoed scope line always says which. - **Masked credentials:** secret-valued managed properties (the platform returns an HMAC, - never plaintext) are treated as "present, unknown" — never reported as differences and - never counted for `--exit-code`; they are surfaced via the masked note / `masked[]`. + never plaintext; the registry's `isSecret` rows) are treated as "present, unknown" — never + reported as differences and never counted for `--exit-code`; they are surfaced via the + masked note / `masked[]`. +- **Values are convergence projections (ADR 0021):** both sides are normalized through + `@supabase/config`'s `fromConfigDocument`/`fromApiProjectConfig`, so a reported "local" + value is what pushing the file would produce hosted (canonicalized durations/byte sizes, + push-gated omissions), not necessarily the file's literal spelling. - **Partial responses:** a managed property the response does not carry is `local_only` when the file declares it and silent otherwise; a missing block is called out on the scope line rather than treated as an error. diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.ts index 5b045e5847..b90e89b1a6 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.ts @@ -1,47 +1,28 @@ -import type { - ConfigChange, - ConfigChangeSet, - CliConfigValueOrigin, - RemoteConfigBlock, - RemoteProjectConfig, -} from "@supabase/config"; -import { REMOTE_CONFIG_BLOCKS } from "@supabase/config"; +import type { ConfigChange, ConfigChangeSet, CliConfigValueOrigin } from "@supabase/config"; /** * Pure formatters, payload builders, and input adapters for `config diff` — * no Effect, no services, unit-testable in isolation. */ +/** The per-service blocks of the v2 project-config resource. */ +const REMOTE_CONFIG_BLOCKS = ["api", "auth", "database", "pooler", "realtime", "storage"] as const; + +export type LegacyConfigDiffScope = ReadonlyArray<(typeof REMOTE_CONFIG_BLOCKS)[number]>; + function isRemoteBlockRecord(value: unknown): value is Readonly> { return typeof value === "object" && value !== null && !Array.isArray(value); } -function asRemoteBlock(value: unknown): Readonly> | undefined { - return isRemoteBlockRecord(value) ? value : undefined; -} - /** - * Adapts the generated client's decoded `data.attributes` to the loose - * per-block records the comparison core reads. Non-record values (which the - * generated schema should never produce, but the core must not trust) read as - * "block not returned". + * Which per-service blocks the response's `data.attributes` actually carried + * — echoed to the user so a partially-populated response is never mistaken + * for a clean bill of health. */ -export function legacyConfigDiffRemoteBlocks(attributes: { - readonly api: unknown; - readonly auth: unknown; - readonly database: unknown; - readonly pooler: unknown; - readonly realtime: unknown; - readonly storage: unknown; -}): RemoteProjectConfig { - return { - api: asRemoteBlock(attributes.api), - auth: asRemoteBlock(attributes.auth), - database: asRemoteBlock(attributes.database), - pooler: asRemoteBlock(attributes.pooler), - realtime: asRemoteBlock(attributes.realtime), - storage: asRemoteBlock(attributes.storage), - }; +export function legacyConfigDiffScope( + attributes: Readonly>, +): LegacyConfigDiffScope { + return REMOTE_CONFIG_BLOCKS.filter((block) => isRemoteBlockRecord(attributes[block])); } /** @@ -101,7 +82,7 @@ export function legacyConfigDiffComparisonLine(context: LegacyConfigDiffContext) } /** The scope-echo line, printed to stderr once the response arrived. */ -export function legacyConfigDiffScopeLine(scope: ReadonlyArray): string { +export function legacyConfigDiffScopeLine(scope: LegacyConfigDiffScope): string { const present = scope.length === 0 ? "(none)" : scope.join(", "); const missing = REMOTE_CONFIG_BLOCKS.filter((block) => !scope.includes(block)); const suffix = missing.length === 0 ? "" : ` (not returned: ${missing.join(", ")})`; @@ -145,6 +126,7 @@ export function legacyRenderConfigDiffText(changeSet: ConfigChangeSet): string { */ export function legacyConfigDiffPayload( changeSet: ConfigChangeSet, + scope: LegacyConfigDiffScope, context: LegacyConfigDiffContext, ): Record { const valueEntry = (key: string, value: unknown): Record => ({ @@ -160,7 +142,7 @@ export function legacyConfigDiffPayload( local_scope: context.appliedRemote === undefined ? "base" : `remotes.${context.appliedRemote}`, }, - scope: changeSet.scope, + scope, changes: changeSet.changes.map((change) => ({ path: change.path, class: change.class, diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts index 0d93a1ee3f..1d0cb77b9c 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts @@ -2,26 +2,21 @@ import { describe, expect, test } from "vitest"; import { legacyConfigDiffEnvReferences, - legacyConfigDiffRemoteBlocks, + legacyConfigDiffScope, legacyConfigDiffScopeLine, } from "./diff.format.ts"; -describe("legacyConfigDiffRemoteBlocks", () => { - test("keeps record blocks and drops non-record ones", () => { - const blocks = legacyConfigDiffRemoteBlocks({ - api: { max_rows: 5 }, - auth: {}, - database: null, - pooler: undefined, - realtime: [1], - storage: "nope", - }); - expect(blocks.api).toEqual({ max_rows: 5 }); - expect(blocks.auth).toEqual({}); - expect(blocks.database).toBeUndefined(); - expect(blocks.pooler).toBeUndefined(); - expect(blocks.realtime).toBeUndefined(); - expect(blocks.storage).toBeUndefined(); +describe("legacyConfigDiffScope", () => { + test("lists record blocks the response carried, dropping non-records", () => { + expect( + legacyConfigDiffScope({ + api: { max_rows: 5 }, + auth: {}, + database: null, + realtime: [1], + storage: "nope", + }), + ).toEqual(["api", "auth"]); }); }); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts index f25f9990c7..a95d18b375 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts @@ -1,4 +1,9 @@ -import { diffProjectConfig, CLI_CONFIG_SCHEMA_URL } from "@supabase/config"; +import { + CLI_CONFIG_SCHEMA_URL, + diffProjectConfig, + fromApiProjectConfig, + fromConfigDocument, +} from "@supabase/config"; import { loadCliConfig } from "@supabase/config/effect"; import { Effect, Option } from "effect"; @@ -22,7 +27,7 @@ import { legacyConfigDiffComparisonLine, legacyConfigDiffEnvReferences, legacyConfigDiffPayload, - legacyConfigDiffRemoteBlocks, + legacyConfigDiffScope, legacyConfigDiffScopeLine, legacyRenderConfigDiffText, type LegacyConfigDiffContext, @@ -165,29 +170,44 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( ); yield* fetching?.clear() ?? Effect.void; - // 3. Classify. `declared` is the raw merged document (key presence); - // `local` is the decoded effective config; env-resolved leaves carry the - // resolving variable's name for the output. + // 3. Project both sides through CLI-2230's convergence normalizers (ADR + // 0021): `fromConfigDocument` gets the loaded config WITH its raw + // document so raw-presence masking applies, and `fromApiProjectConfig` + // canonicalizes the response into the same post-push shape. A response + // the registry cannot narrow (out-of-domain mapped values) is an API + // problem, not a transport one. + const remote = yield* Effect.try({ + try: () => fromApiProjectConfig(response), + catch: (cause) => + new LegacyConfigDiffReadNetworkError({ + message: `failed to read project config: ${String(cause)}`, + decode: true, + }), + }); + + // 4. Classify. `declared` is the raw merged document (key presence); + // env-resolved leaves carry the resolving variable's name for the output. const changeSet = diffProjectConfig({ - local: loaded.config, + local: fromConfigDocument(loaded), + remote, declared: loaded.document, - remote: legacyConfigDiffRemoteBlocks(response.data.attributes), envReferences: legacyConfigDiffEnvReferences(loaded.valueOrigins), }); - yield* output.raw(legacyConfigDiffScopeLine(changeSet.scope), "stderr"); + const scope = legacyConfigDiffScope(response.data.attributes); + yield* output.raw(legacyConfigDiffScopeLine(scope), "stderr"); - // 4. Emit: `--output-format json|stream-json` structured payload, or text. + // 5. Emit: `--output-format json|stream-json` structured payload, or text. if (output.format !== "text") { const total = changeSet.changes.length; const message = total === 0 ? "No config differences found." : `${total} config difference(s) found.`; - yield* output.success(message, legacyConfigDiffPayload(changeSet, context)); + yield* output.success(message, legacyConfigDiffPayload(changeSet, scope, context)); } else { yield* output.raw(legacyRenderConfigDiffText(changeSet)); } - // 5. `--exit-code`: differences flip the exit status after the payload is + // 6. `--exit-code`: differences flip the exit status after the payload is // out, without an error envelope corrupting machine output. if (flags.exitCode && changeSet.changes.length > 0) { yield* processControl.setExitCode(1); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts index d903abcc88..94d05c34be 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -481,6 +481,33 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); + it.live("an out-of-domain mapped value in the response maps to a decode error", () => { + // Wire-valid but semantically impossible: the registry's typed throw + // (ADR 0021 API-arm family) surfaces as a decode-flagged read error. + const { layer } = setup({ + toml: 'project_id = "test"\n', + v2: { + status: 200, + body: v2Response({ + attributes: (attributes) => ({ + ...attributes, + storage: { + ...(attributes["storage"] as Record), + file_size_limit: -1, + }, + }), + }), + }, + }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); + expect(Exit.isFailure(exit)).toBe(true); + const rendered = JSON.stringify(exit); + expect(rendered).toContain("LegacyConfigDiffReadNetworkError"); + expect(rendered).toContain("failed to read project config"); + }).pipe(Effect.provide(layer)); + }); + it.live("a remote config error status maps to the read status error", () => { const { layer } = setup({ toml: 'project_id = "test"\n', diff --git a/apps/cli/src/legacy/shared/legacy-db-target-flags.ts b/apps/cli/src/legacy/shared/legacy-db-target-flags.ts index 963e9b295e..dcdd852e20 100644 --- a/apps/cli/src/legacy/shared/legacy-db-target-flags.ts +++ b/apps/cli/src/legacy/shared/legacy-db-target-flags.ts @@ -146,6 +146,7 @@ export const VALUE_CONSUMING_LONG_FLAGS = new Set([ "status", "sub", "swift-access-control", + "target", "template", "timestamp", "to", diff --git a/docs/adr/0022-config-diff-classification-and-managed-surface.md b/docs/adr/0022-config-diff-classification-and-managed-surface.md index b807954dd8..92750857f7 100644 --- a/docs/adr/0022-config-diff-classification-and-managed-surface.md +++ b/docs/adr/0022-config-diff-classification-and-managed-surface.md @@ -1,7 +1,7 @@ # 0022. Config Diff Classification and Managed Surface **Status**: proposed -**Date**: 2026-08-20 +**Date**: 2026-08-20 (registry consolidation 2026-08-28) ## Problem Statement @@ -11,32 +11,41 @@ 2. **Managed vs. unmanaged.** Most of `config.toml` configures the _local_ stack — `[studio]`, ports, image pins, `[db.migrations]` — and has no platform counterpart. Reporting those as drift is noise; deciding which properties the platform manages needs a source of truth that cannot drift from the code that reads the response. 3. **Incomparable values.** The platform masks secrets (HMAC, never plaintext), reports byte counts where the file writes `"50MiB"`, comma-joins arrays, and types some scalars differently than the schema. Comparing representations instead of meanings misreports drift; silently skipping them misreports cleanliness. +This ADR was first accepted with a self-contained translation table inside `config-diff.ts` (a `read`-function-per-managed-path port of the Go CLI's `FromRemoteAuthConfig` at `7b469f5b3`). CLI-2230 (PR supabase/cli#6339) then landed the registry-driven `ProjectConfig` convergence normalizers — the same translation, shared with Studio, governed by ADR 0019 (passthrough), ADR 0020 (naming), and ADR 0021 (convergence semantics). Keeping two translations would have been exactly the parallel code path the repo's refactoring policy forbids, so the classifier now consumes the registry; this revision records the consolidated design. + ## Decision -`@supabase/config` owns the whole comparison core as pure, synchronous functions (`config-diff*.ts`), with no dependency on `@supabase/api`, output formatting, or command flags: +`@supabase/config` owns the comparison core as pure, synchronous functions (`config-diff.ts`), with no dependency on `@supabase/api`, output formatting, or command flags — layered on CLI-2230's registry rather than a translation of its own: -- **The managed surface is defined by the translation table.** `MANAGED_CONFIG_PROPERTIES` is a table of entries, one per local schema path the v2 resource can report, each carrying a `read` function that descends the structurally-typed response (`RemoteProjectConfig`, all six blocks as loose records) and coerces the wire value to the local schema's type. A schema path with no entry is _unmanaged by construction_ — the managed set and the response-reading code are the same artifact and cannot drift apart. The auth table is ported from the Go CLI's `FromRemoteAuthConfig` (commit `7b469f5b3`), including its inversions (`enable_signup` ← `!disable_signup`), duration/enum transforms, and provider fan-out. -- **Four-way classification per managed path**, driven by _declared_ presence (the raw pre-decode document) on the local side and `read` presence on the remote side: `update` (declared + returned, values differ), `remote_only` (returned, undeclared, and differing from the baseline default — equal-to-default values are suppressed, which is what CLI-2155's defaults reference exists for), `local_only` (declared, not returned — parsed-but-never-pushed attributes and permission-truncated responses), and unmanaged (never reported). The local operand follows ADR 0018: the branch's merged effective config when the target ref matches a `[remotes.*]` block's `project_id`, the base config otherwise. -- **Equality is meaning-based**: arrays compare as multisets, scalars tolerate string/number and string/boolean representation skew, and per-entry `normalize` hooks canonicalize (byte sizes via `RAMInBytes` semantics, Go-duration strings) before comparison while reported values stay un-normalized. -- **Secrets are "present, unknown".** Entries marked `secret` (the union of the schema's `x-secret` fields and Go's `Secret` machinery) are never compared and never counted; locally-declared ones are surfaced in `ConfigChangeSet.masked` so a clean change list is visibly a partial claim. Likewise `scope` records which blocks the response actually carried, so partially-populated responses degrade to `local_only` + an explicit scope note instead of an error or silent omission. +- **Both operands are `ProjectConfig` convergence projections (ADR 0021).** The caller builds the local operand with `fromConfigDocument({config, document})` — raw-presence-masked, canonicalized, secret-omitting — and the remote operand with `fromApiProjectConfig(response)`. All wire-shape knowledge (renames, inversions, unit conversions, the GoTrue key table) lives in `projectConfigMappingRows`, once, shared with Studio and the future push mapper. +- **The managed surface is the registry's.** The classifier walks the union of both operands' leaf paths filtered by `isComparableProjectConfigPath` — a path with no registry row is _unmanaged by construction_ and never reported (`[studio]`, ports, image pins, `[realtime]` locals, `workers`). +- **Three-way classification per comparable path**, driven by _declared_ presence (the raw pre-decode document), which a decoded config cannot recover: `update` (declared + reported, values differ), `remote_only` (reported while undeclared — or while push cannot communicate the declared state — and differing from the suppression baseline), `local_only` (a declared local projection value the response did not account for: parsed-but-never-pushed attributes and permission-truncated responses). The local operand follows ADR 0018: the branch's merged effective config when the target ref matches a `[remotes.*]` block's `project_id`, the base config otherwise. +- **`remote_only` suppression baseline**: the default config's own convergence projection, falling back — for push-gated containers the projection is silent on — to the raw default config's value (`db.network_restrictions`' allow-all default IS the platform's unconfigured state), then to the type's zero value. An unconfigured project therefore diffs clean instead of flooding with platform-default noise. +- **Equality is meaning-based**: the normalizers canonicalize representations (durations, byte sizes, comma-joins) per ADR 0021, and the classifier's residual equality compares arrays as multisets and tolerates string/number and string/boolean scalar skew. +- **Secrets are "present, unknown".** Both normalizers omit secret leaves (the platform only reports HMAC digests), so secrets can never classify; the registry's `isSecret` rows define the masked surface, and locally-declared ones are surfaced in `ConfigChangeSet.masked` so a clean change list is visibly a partial claim. The command layer separately echoes which response blocks were carried, so partially-populated responses degrade to `local_only` + an explicit scope note instead of an error or silent omission. - The interpolation pipeline records the resolving env var name on `"environment"` value origins, so a change on an `env()`-fed property can name the variable involved. -The command layer (`apps/cli/src/legacy/commands/config/diff/`) only resolves the target, fetches, and renders. +The command layer (`apps/cli/src/legacy/commands/config/diff/`) only resolves the target, fetches, projects, and renders. Per ADR 0021's rendering rule, reported "local" values are the convergence projection — what pushing the file would produce hosted — not the file's literal spelling. ## Considered Alternatives 1. **Derive the managed set from the response keys** (the POC's approach): whatever the remote returns is what's compared. Structurally blind to `local_only`, and a permission-truncated response silently shrinks the comparison. -2. **Schema annotations (`x-managed`) on each property**: keeps the knowledge in the schema, but the annotation and the response-reading code can disagree, and the annotation cannot express per-property wire transforms (comma-splits, inversions, unit conversions) that the table entry's `read` carries anyway. -3. **Reuse `config push`'s `config-sync` diffing** (`apps/cli/src/legacy/commands/config/push/config-sync/`): those helpers produce per-service unified-diff _text_ against the v1 per-service endpoints for push previews, not a typed change set, and they live in the CLI app. They remain the Go-parity push path; the classification core is the reusable engine `pull` needs. Consolidating push onto the core is possible later but out of scope here. +2. **Schema annotations (`x-managed`) on each property**: keeps the knowledge in the schema, but the annotation and the response-reading code can disagree, and the annotation cannot express per-property wire transforms that the registry rows carry. +3. **The original self-contained translation table** (this ADR's first accepted form): correct, but once CLI-2230 landed the registry it became a ~600-line parallel implementation of the same mapping with independently-drifting transforms. Superseded by the consolidation above. +4. **Reuse `config push`'s `config-sync` diffing** (`apps/cli/src/legacy/commands/config/push/config-sync/`): those helpers produce per-service unified-diff _text_ against the v1 per-service endpoints for push previews, not a typed change set, and they live in the CLI app. They remain the Go-parity push path; the registry rows were themselves mined from them (CLI-2230), and a shared push mapper is that ticket's tracked follow-up. ## Consequences -- `config pull` gets its comparison engine for free: the change set is typed data, and the same translation produces the local representation of any remote value it needs to write. -- Adding a newly platform-managed property is one table entry; forgetting it means the property is silently unmanaged (never misreported as drift), which fails safe. -- The structural `RemoteProjectConfig` type mirrors the v2 wire shape; if the API reshapes a block, the readers' runtime guards degrade to "not returned" (`local_only`/silent) rather than crashing, and the live test is the tripwire. +- `config pull` gets its comparison engine for free: the change set is typed data, and `fromApiProjectConfig` already produces the local representation of any remote value it needs to write. +- Adding a newly platform-managed property is one registry row (shared with Studio); forgetting it means the property is silently unmanaged (never misreported as drift), which fails safe. +- The wire shape is pinned by `apps/cli`'s `project-config-api-drift.unit.test.ts` type-drift guard plus the registry's lenient decode (ADR 0019); API evolution degrades to "not reported" rather than crashing, and the live test is the tripwire. - Platform defaults that diverge from schema defaults surface as `remote_only` drift by design — the file's meaning is defined by the schema defaults reference (ADR 0018), not by what the platform would have picked. +- The classifier inherits ADR 0021's limits verbatim: ADR 0021's "honest-but-push-unactionable" residual category (unconditionally-mapped fields with no local-silence signal, tracked on CLI-2266) surfaces here as `remote_only` entries a user cannot fix by editing their file. ## Related Decisions - [ADR 0018](0018-sparse-config-subtraction.md): Sparse Config Subtraction — the defaults baseline and merged-remote-block local operand this classification builds on. +- [ADR 0019](0019-config-api-response-passthrough.md): Raw API-Response Passthrough — the leniency boundary and `_apiResponse` escape hatch of the remote operand. +- [ADR 0020](0020-config-naming-vocabulary.md): Config Naming Vocabulary — `CliConfig` vs `ProjectConfig`. +- [ADR 0021](0021-projectconfig-convergence-semantics.md): ProjectConfig Convergence Semantics — what the two operand-producing normalizers compute, and why comparing them structurally is meaningful. - [ADR 0006](0006-environment-management.md): Environment Management — remote blocks and branch mapping semantics. diff --git a/packages/api/.generated-output-sync-umcANF/contracts.ts b/packages/api/.generated-output-sync-umcANF/contracts.ts deleted file mode 100644 index 709c4299f8..0000000000 --- a/packages/api/.generated-output-sync-umcANF/contracts.ts +++ /dev/null @@ -1,3150 +0,0 @@ -import * as Schema from "effect/Schema"; - -// non-recursive definitions -export const SupavisorConfigResponse = Schema.Struct({ "identifier": Schema.String, "database_type": Schema.Literals(["PRIMARY", "READ_REPLICA"]), "is_using_scram_auth": Schema.Boolean, "db_user": Schema.String, "db_host": Schema.String, "db_port": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "db_name": Schema.String, "connection_string": Schema.String, "connectionString": Schema.String.annotate({ "description": "Use connection_string instead" }), "default_pool_size": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "max_client_conn": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "pool_mode": Schema.Literals(["transaction", "session"]) }).annotate({ "identifier": "SupavisorConfigResponse" }) -export const ApiKeyResponse = Schema.Struct({ "api_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "type": Schema.optionalKey(Schema.Union([Schema.Literal("legacy"), Schema.Literal("publishable"), Schema.Literal("secret"), Schema.Null])), "prefix": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "name": Schema.String, "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hash": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "secret_jwt_template": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).check(Schema.isPropertyNames(Schema.String).annotate({ "expected": "an object with property names matching the schema" })), Schema.Null])), "inserted_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])), "updated_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])) }).annotate({ "identifier": "ApiKeyResponse" }) -export const V1ServiceHealthResponse = Schema.Struct({ "name": Schema.Literals(["auth", "db", "db_postgres_user", "pooler", "realtime", "rest", "storage", "pg_bouncer"]), "healthy": Schema.Boolean.annotate({ "description": "Deprecated. Use `status` instead." }), "status": Schema.Literals(["COMING_UP", "ACTIVE_HEALTHY", "UNHEALTHY"]), "info": Schema.optionalKey(Schema.Union([Schema.Struct({ "name": Schema.Literal("GoTrue"), "version": Schema.String, "description": Schema.String }), Schema.Struct({ "healthy": Schema.Boolean.annotate({ "description": "Deprecated. Use `status` instead." }), "db_connected": Schema.Boolean, "replication_connected": Schema.Boolean, "connected_cluster": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), Schema.Struct({ "db_schema": Schema.String })])), "error": Schema.optionalKey(Schema.String) }).annotate({ "identifier": "V1ServiceHealthResponse" }) -export const BranchResponse = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "name": Schema.String, "project_ref": Schema.String, "parent_project_ref": Schema.String, "is_default": Schema.Boolean, "git_branch": Schema.optionalKey(Schema.String), "pr_number": Schema.optionalKey(Schema.Number.annotate({ "format": "int32" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "latest_check_run_id": Schema.optionalKey(Schema.Number.annotate({ "description": "This field is deprecated and will not be populated." }).check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "persistent": Schema.Boolean, "status": Schema.Literals(["CREATING_PROJECT", "RUNNING_MIGRATIONS", "MIGRATIONS_PASSED", "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED"]).annotate({ "description": "This field is deprecated. List action runs to get branch status instead." }), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }), "review_requested_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "with_data": Schema.Boolean, "notify_url": Schema.optionalKey(Schema.String.annotate({ "format": "uri" })), "deletion_scheduled_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "preview_project_status": Schema.optionalKey(Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"])) }).annotate({ "identifier": "BranchResponse" }) -export const V1StorageBucketResponse = Schema.Struct({ "id": Schema.String, "name": Schema.String, "owner": Schema.String, "created_at": Schema.String, "updated_at": Schema.String, "public": Schema.Boolean }).annotate({ "identifier": "V1StorageBucketResponse" }) -export const FunctionResponse = Schema.Struct({ "id": Schema.String, "slug": Schema.String, "name": Schema.String, "status": Schema.Literals(["ACTIVE", "REMOVED", "THROTTLED"]), "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "created_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "updated_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "verify_jwt": Schema.optionalKey(Schema.Boolean), "import_map": Schema.optionalKey(Schema.Boolean), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "ezbr_sha256": Schema.optionalKey(Schema.String) }).annotate({ "identifier": "FunctionResponse" }) -export const OrganizationResponseV1 = Schema.Struct({ "id": Schema.String.annotate({ "description": "Deprecated: Use `slug` instead." }), "slug": Schema.String.annotate({ "description": "Organization slug" }).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "name": Schema.String }).annotate({ "identifier": "OrganizationResponseV1" }) -export const V1ProjectWithDatabaseResponse = Schema.Struct({ "id": Schema.String.annotate({ "description": "Deprecated: Use `ref` instead." }), "ref": Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "organization_id": Schema.String.annotate({ "description": "Deprecated: Use `organization_slug` instead." }), "organization_slug": Schema.String.annotate({ "description": "Organization slug" }).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "name": Schema.String.annotate({ "description": "Name of your project" }), "region": Schema.String.annotate({ "description": "Region of your project" }), "created_at": Schema.String.annotate({ "description": "Creation timestamp" }), "status": Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"]), "database": Schema.Struct({ "host": Schema.String.annotate({ "description": "Database host" }), "version": Schema.String.annotate({ "description": "Database version" }), "postgres_engine": Schema.String.annotate({ "description": "Database engine" }), "release_channel": Schema.String.annotate({ "description": "Release channel" }) }) }).annotate({ "identifier": "V1ProjectWithDatabaseResponse" }) -export const SecretResponse = Schema.Struct({ "name": Schema.String, "value": Schema.String, "updated_at": Schema.optionalKey(Schema.String) }).annotate({ "identifier": "SecretResponse" }) -export const V1OrganizationMemberResponse = Schema.Struct({ "user_id": Schema.String, "user_name": Schema.String, "email": Schema.optionalKey(Schema.String), "role_name": Schema.String, "mfa_enabled": Schema.Boolean, "avatar_url": Schema.Union([Schema.String, Schema.Null]) }).annotate({ "identifier": "V1OrganizationMemberResponse" }) -export const ThirdPartyAuth = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "type": Schema.String, "oidc_issuer_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "jwks_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "custom_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "resolved_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "inserted_at": Schema.String, "updated_at": Schema.String, "resolved_at": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) }).annotate({ "identifier": "ThirdPartyAuth" }) -// recursive definitions -export type UpdateCustomHostnameResponseJsonValue = string | number | boolean | null | ReadonlyArray | { readonly [x: string]: UpdateCustomHostnameResponseJsonValue } -export const UpdateCustomHostnameResponseJsonValue = Schema.Union([Schema.Union([Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Boolean]), Schema.Null]), Schema.Array(Schema.suspend((): Schema.Codec => UpdateCustomHostnameResponseJsonValue)), Schema.Record(Schema.String, Schema.suspend((): Schema.Codec => UpdateCustomHostnameResponseJsonValue))]).annotate({ "description": "Any JSON-serializable value", "identifier": "UpdateCustomHostnameResponseJsonValue" }) -export type ListProjectAddonsResponseJsonValue = string | number | boolean | null | ReadonlyArray | { readonly [x: string]: ListProjectAddonsResponseJsonValue } -export const ListProjectAddonsResponseJsonValue = Schema.Union([Schema.Union([Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Boolean]), Schema.Null]), Schema.Array(Schema.suspend((): Schema.Codec => ListProjectAddonsResponseJsonValue)), Schema.Record(Schema.String, Schema.suspend((): Schema.Codec => ListProjectAddonsResponseJsonValue))]).annotate({ "description": "Any JSON-serializable value", "identifier": "ListProjectAddonsResponseJsonValue" }) -// binary input helpers -export const BinaryInput = Schema.Union([Schema.Uint8Array, Schema.instanceOf(globalThis.ArrayBuffer, { expected: "ArrayBuffer" }), Schema.instanceOf(globalThis.Blob, { expected: "Blob" })]) -// operation schemas -export const V1AcceptInviteExternalJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "email": Schema.String.annotate({ "format": "email" }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })), "token": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) }) -export const V1AcceptInviteExternalJitAccessOutput = Schema.Struct({ "user_id": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "user_roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) }) -export const V1ActivateCustomHostnameInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ActivateCustomHostnameOutput = Schema.Struct({ "status": Schema.optionalKey(Schema.Literals(["1_not_started", "2_initiated", "3_challenge_verified", "4_origin_setup_completed", "5_services_reconfigured"])), "custom_hostname": Schema.optionalKey(Schema.String), "data": Schema.Struct({ "success": Schema.Boolean, "errors": Schema.Array(UpdateCustomHostnameResponseJsonValue), "messages": Schema.Array(UpdateCustomHostnameResponseJsonValue), "result": Schema.Struct({ "id": Schema.String, "hostname": Schema.String, "ssl": Schema.Struct({ "status": Schema.String, "validation_records": Schema.optionalKey(Schema.Array(Schema.Struct({ "txt_name": Schema.String, "txt_value": Schema.String }))), "validation_errors": Schema.optionalKey(Schema.Array(Schema.Struct({ "message": Schema.String }))) }), "ownership_verification": Schema.optionalKey(Schema.Struct({ "type": Schema.String, "name": Schema.String, "value": Schema.String })), "custom_origin_server": Schema.String, "verification_errors": Schema.optionalKey(Schema.Array(Schema.String)), "status": Schema.String }) }) }) -export const V1ActivateVanitySubdomainConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "vanity_subdomain": Schema.String.check(Schema.isMaxLength(63).annotate({ "expected": "a value with a length of at most 63" })) }) -export const V1ActivateVanitySubdomainConfigOutput = Schema.Struct({ "custom_domain": Schema.String }) -export const V1ApplyAMigrationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "Idempotency-Key": Schema.optionalKey(Schema.String), "query": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "name": Schema.optionalKey(Schema.String), "rollback": Schema.optionalKey(Schema.String) }) -export const V1ApplyProjectAddonInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "addon_variant": Schema.Union([Schema.Literals(["ci_micro", "ci_small", "ci_medium", "ci_large", "ci_xlarge", "ci_2xlarge", "ci_4xlarge", "ci_8xlarge", "ci_12xlarge", "ci_16xlarge", "ci_24xlarge", "ci_24xlarge_optimized_cpu", "ci_24xlarge_optimized_memory", "ci_24xlarge_high_memory", "ci_48xlarge", "ci_48xlarge_optimized_cpu", "ci_48xlarge_optimized_memory", "ci_48xlarge_high_memory"]), Schema.Literal("cd_default"), Schema.Literals(["pitr_7", "pitr_14", "pitr_28"]), Schema.Literal("ipv4_default")]), "addon_type": Schema.Literals(["custom_domain", "compute_instance", "pitr", "ipv4", "auth_mfa_phone", "auth_mfa_web_authn", "log_drain", "etl_pipeline"]) }) -export const V1AuthorizeJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "rhost": Schema.Union([Schema.String.annotate({ "format": "ipv4" }).check(Schema.isPattern(new RegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$" })), Schema.String.annotate({ "format": "ipv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$" }))]) }) -export const V1AuthorizeJitAccessOutput = Schema.Struct({ "user_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "user_role": Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) }) }) -export const V1AuthorizeUserInput = Schema.Struct({ "client_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "response_type": Schema.Literals(["code", "token", "id_token token"]), "redirect_uri": Schema.String, "scope": Schema.optionalKey(Schema.String), "state": Schema.optionalKey(Schema.String), "response_mode": Schema.optionalKey(Schema.String), "code_challenge": Schema.optionalKey(Schema.String), "code_challenge_method": Schema.optionalKey(Schema.Literals(["plain", "sha256", "S256"])), "organization_slug": Schema.optionalKey(Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" }))), "target_flow": Schema.optionalKey(Schema.String), "resource": Schema.optionalKey(Schema.String.annotate({ "format": "uri" })) }) -export const V1BulkCreateSecretsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "body": Schema.Array(Schema.Struct({ "name": Schema.String.annotate({ "description": "Secret name must not start with the SUPABASE_ prefix." }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })).check(Schema.isPattern(new RegExp("^(?!SUPABASE_).*")).annotate({ "expected": "a string matching the RegExp ^(?!SUPABASE_).*" })), "value": Schema.String.check(Schema.isMaxLength(24576).annotate({ "expected": "a value with a length of at most 24576" })) })).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) }) -export const V1BulkDeleteSecretsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "body": Schema.Array(Schema.String) }) -export const V1BulkUpdateFunctionsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "body": Schema.Array(Schema.Struct({ "id": Schema.String, "slug": Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z][A-Za-z0-9_-]*$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z][A-Za-z0-9_-]*$" })), "name": Schema.String, "status": Schema.Literals(["ACTIVE", "REMOVED", "THROTTLED"]), "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "created_at": Schema.optionalKey(Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "verify_jwt": Schema.optionalKey(Schema.Boolean), "import_map": Schema.optionalKey(Schema.Boolean), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.String), "ezbr_sha256": Schema.optionalKey(Schema.String) })) }) -export const V1BulkUpdateFunctionsOutput = Schema.Struct({ "functions": Schema.Array(Schema.Struct({ "id": Schema.String, "slug": Schema.String, "name": Schema.String, "status": Schema.Literals(["ACTIVE", "REMOVED", "THROTTLED"]), "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "created_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "updated_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "verify_jwt": Schema.optionalKey(Schema.Boolean), "import_map": Schema.optionalKey(Schema.Boolean), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "ezbr_sha256": Schema.optionalKey(Schema.String) })) }) -export const V1CancelAProjectRestorationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1CheckVanitySubdomainAvailabilityInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "vanity_subdomain": Schema.String.check(Schema.isMaxLength(63).annotate({ "expected": "a value with a length of at most 63" })) }) -export const V1CheckVanitySubdomainAvailabilityOutput = Schema.Struct({ "available": Schema.Boolean }) -export const V1ClaimProjectForOrganizationInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "token": Schema.String }) -export const V1CountActionRunsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1CreateABranchInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "branch_name": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "git_branch": Schema.optionalKey(Schema.String), "is_default": Schema.optionalKey(Schema.Boolean), "persistent": Schema.optionalKey(Schema.Boolean), "region": Schema.optionalKey(Schema.String), "desired_instance_size": Schema.optionalKey(Schema.Literals(["pico", "nano", "micro", "small", "medium", "large", "xlarge", "2xlarge", "4xlarge", "8xlarge", "12xlarge", "16xlarge", "24xlarge", "24xlarge_optimized_memory", "24xlarge_optimized_cpu", "24xlarge_high_memory", "48xlarge", "48xlarge_optimized_memory", "48xlarge_optimized_cpu", "48xlarge_high_memory"])), "release_channel": Schema.optionalKey(Schema.Literals(["internal", "alpha", "beta", "ga", "withdrawn", "preview"]).annotate({ "description": "Release channel. If not provided, GA will be used." })), "postgres_engine": Schema.optionalKey(Schema.Literals(["15", "17", "17-oriole"]).annotate({ "description": "Postgres engine version. If not provided, the latest version will be used." })), "secrets": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)), "with_data": Schema.optionalKey(Schema.Boolean), "notify_url": Schema.optionalKey(Schema.String.annotate({ "description": "HTTP endpoint to receive branch status updates.", "format": "uri" })) }) -export const V1CreateABranchOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "name": Schema.String, "project_ref": Schema.String, "parent_project_ref": Schema.String, "is_default": Schema.Boolean, "git_branch": Schema.optionalKey(Schema.String), "pr_number": Schema.optionalKey(Schema.Number.annotate({ "format": "int32" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "latest_check_run_id": Schema.optionalKey(Schema.Number.annotate({ "description": "This field is deprecated and will not be populated." }).check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "persistent": Schema.Boolean, "status": Schema.Literals(["CREATING_PROJECT", "RUNNING_MIGRATIONS", "MIGRATIONS_PASSED", "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED"]).annotate({ "description": "This field is deprecated. List action runs to get branch status instead." }), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }), "review_requested_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "with_data": Schema.Boolean, "notify_url": Schema.optionalKey(Schema.String.annotate({ "format": "uri" })), "deletion_scheduled_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "preview_project_status": Schema.optionalKey(Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"])) }) -export const V1CreateAFunctionInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "slug": Schema.optionalKey(Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z0-9_-]+$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z0-9_-]+$" }))), "name": Schema.optionalKey(Schema.String), "verify_jwt": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "import_map": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.String), "ezbr_sha256": Schema.optionalKey(Schema.String), "body": BinaryInput }) -export const V1CreateAFunctionOutput = Schema.Struct({ "id": Schema.String, "slug": Schema.String, "name": Schema.String, "status": Schema.Literals(["ACTIVE", "REMOVED", "THROTTLED"]), "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "created_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "updated_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "verify_jwt": Schema.optionalKey(Schema.Boolean), "import_map": Schema.optionalKey(Schema.Boolean), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "ezbr_sha256": Schema.optionalKey(Schema.String) }) -export const V1CreateAProjectInput = Schema.Struct({ "db_pass": Schema.String.annotate({ "description": "Database password" }), "name": Schema.String.annotate({ "description": "Name of your project" }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })), "organization_id": Schema.optionalKey(Schema.String.annotate({ "description": "Deprecated: Use `organization_slug` instead." })), "organization_slug": Schema.String.annotate({ "description": "Organization slug" }).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "plan": Schema.optionalKey(Schema.Literals(["free", "pro"]).annotate({ "description": "Subscription Plan is now set on organization level and is ignored in this request" })), "region": Schema.optionalKey(Schema.Literals(["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-east-1", "ap-southeast-1", "ap-northeast-1", "ap-northeast-2", "ap-southeast-2", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "eu-central-1", "eu-central-2", "ca-central-1", "ap-south-1", "sa-east-1"]).annotate({ "description": "Region you want your server to reside in. Use region_selection instead." })), "region_selection": Schema.optionalKey(Schema.Union([Schema.Struct({ "type": Schema.Literal("specific"), "code": Schema.Literals(["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-east-1", "ap-southeast-1", "ap-northeast-1", "ap-northeast-2", "ap-southeast-2", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "eu-central-1", "eu-central-2", "ca-central-1", "ap-south-1", "sa-east-1"]).annotate({ "description": "Specific region code. The codes supported are not a stable API, and should be retrieved from the /available-regions endpoint." }) }), Schema.Struct({ "type": Schema.Literal("smartGroup"), "code": Schema.Literals(["americas", "emea", "apac"]).annotate({ "description": "The Smart Region Group's code. The codes supported are not a stable API, and should be retrieved from the /available-regions endpoint." }) })], { mode: "oneOf" }).annotate({ "description": "Region selection. Only one of region or region_selection can be specified." })), "kps_enabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "This field is deprecated and is ignored in this request" })), "desired_instance_size": Schema.optionalKey(Schema.Literals(["nano", "micro", "small", "medium", "large", "xlarge", "2xlarge", "4xlarge", "8xlarge", "12xlarge", "16xlarge", "24xlarge", "24xlarge_optimized_memory", "24xlarge_optimized_cpu", "24xlarge_high_memory", "48xlarge", "48xlarge_optimized_memory", "48xlarge_optimized_cpu", "48xlarge_high_memory"]).annotate({ "description": "Desired instance size. Omit this field to always default to the smallest possible size." })), "template_url": Schema.optionalKey(Schema.String.annotate({ "description": "Template URL used to create the project from the CLI.", "format": "uri" })), "release_channel": Schema.optionalKey(Schema.Literals(["internal", "alpha", "beta", "ga", "withdrawn", "preview"]).annotate({ "description": "Release channel. If not provided, GA will be used." })), "postgres_engine": Schema.optionalKey(Schema.Literals(["15", "17", "17-oriole"]).annotate({ "description": "Postgres engine version. If not provided, the latest version will be used." })), "high_availability": Schema.optionalKey(Schema.Boolean.annotate({ "description": "[Experimental] Whether to enable high availability for the project." })) }) -export const V1CreateAProjectOutput = Schema.Struct({ "id": Schema.String.annotate({ "description": "Deprecated: Use `ref` instead." }), "ref": Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "organization_id": Schema.String.annotate({ "description": "Deprecated: Use `organization_slug` instead." }), "organization_slug": Schema.String.annotate({ "description": "Organization slug" }).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "name": Schema.String.annotate({ "description": "Name of your project" }), "region": Schema.String.annotate({ "description": "Region of your project" }), "created_at": Schema.String.annotate({ "description": "Creation timestamp" }), "status": Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"]) }) -export const V1CreateASsoProviderInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "type": Schema.Literal("saml").annotate({ "description": "What type of provider will be created" }), "metadata_xml": Schema.optionalKey(Schema.String), "metadata_url": Schema.optionalKey(Schema.String), "domains": Schema.optionalKey(Schema.Array(Schema.String)), "attribute_mapping": Schema.optionalKey(Schema.Struct({ "keys": Schema.Record(Schema.String, Schema.Struct({ "name": Schema.optionalKey(Schema.String), "names": Schema.optionalKey(Schema.Array(Schema.String)), "default": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), "array": Schema.optionalKey(Schema.Boolean) })) })), "name_id_format": Schema.optionalKey(Schema.Literals(["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"])) }) -export const V1CreateASsoProviderOutput = Schema.Struct({ "id": Schema.String, "saml": Schema.optionalKey(Schema.Struct({ "entity_id": Schema.String, "metadata_url": Schema.optionalKey(Schema.String), "metadata_xml": Schema.optionalKey(Schema.String), "attribute_mapping": Schema.optionalKey(Schema.Struct({ "keys": Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ "name": Schema.optionalKey(Schema.String), "names": Schema.optionalKey(Schema.Array(Schema.String)), "default": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), "array": Schema.optionalKey(Schema.Boolean) }))) })), "name_id_format": Schema.optionalKey(Schema.Literals(["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"])) })), "domains": Schema.optionalKey(Schema.Array(Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }))), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }) -export const V1CreateAnOrganizationInput = Schema.Struct({ "name": Schema.String.check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) }) -export const V1CreateAnOrganizationOutput = Schema.Struct({ "id": Schema.String.annotate({ "description": "Deprecated: Use `slug` instead." }), "slug": Schema.String.annotate({ "description": "Organization slug" }).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "name": Schema.String }) -export const V1CreateLegacySigningKeyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1CreateLegacySigningKeyOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]), "public_jwk": Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null]), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }) }) -export const V1CreateLoginRoleInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "read_only": Schema.Boolean }) -export const V1CreateLoginRoleOutput = Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "password": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "ttl_seconds": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }) -export const V1CreateProjectApiKeyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "reveal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "type": Schema.Literals(["publishable", "secret"]), "name": Schema.String.check(Schema.isMinLength(4).annotate({ "expected": "a value with a length of at least 4" })).check(Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" })).check(Schema.isPattern(new RegExp("^[a-z_][a-z0-9_]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z_][a-z0-9_]+$" })), "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "secret_jwt_template": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).check(Schema.isPropertyNames(Schema.String).annotate({ "expected": "an object with property names matching the schema" })), Schema.Null])) }) -export const V1CreateProjectApiKeyOutput = Schema.Struct({ "api_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "type": Schema.optionalKey(Schema.Union([Schema.Literal("legacy"), Schema.Literal("publishable"), Schema.Literal("secret"), Schema.Null])), "prefix": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "name": Schema.String, "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hash": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "secret_jwt_template": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).check(Schema.isPropertyNames(Schema.String).annotate({ "expected": "an object with property names matching the schema" })), Schema.Null])), "inserted_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])), "updated_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])) }) -export const V1CreateProjectClaimTokenInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1CreateProjectClaimTokenOutput = Schema.Struct({ "token": Schema.String, "token_alias": Schema.String, "expires_at": Schema.String, "created_at": Schema.String, "created_by": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1CreateProjectSigningKeyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.optionalKey(Schema.Literals(["in_use", "standby"])), "private_jwk": Schema.optionalKey(Schema.Union([Schema.Struct({ "kid": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "use": Schema.optionalKey(Schema.Literal("sig")), "key_ops": Schema.optionalKey(Schema.Array(Schema.Literals(["sign", "verify"])).check(Schema.isMinLength(2).annotate({ "expected": "a value with a length of at least 2" })).check(Schema.isMaxLength(2).annotate({ "expected": "a value with a length of at most 2" }))), "ext": Schema.optionalKey(Schema.Literal(true)), "kty": Schema.Literal("RSA"), "alg": Schema.optionalKey(Schema.Literal("RS256")), "n": Schema.String, "e": Schema.Literal("AQAB"), "d": Schema.String, "p": Schema.String, "q": Schema.String, "dp": Schema.String, "dq": Schema.String, "qi": Schema.String }), Schema.Struct({ "kid": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "use": Schema.optionalKey(Schema.Literal("sig")), "key_ops": Schema.optionalKey(Schema.Array(Schema.Literals(["sign", "verify"])).check(Schema.isMinLength(2).annotate({ "expected": "a value with a length of at least 2" })).check(Schema.isMaxLength(2).annotate({ "expected": "a value with a length of at most 2" }))), "ext": Schema.optionalKey(Schema.Literal(true)), "kty": Schema.Literal("EC"), "alg": Schema.optionalKey(Schema.Literal("ES256")), "crv": Schema.Literal("P-256"), "x": Schema.String, "y": Schema.String, "d": Schema.String }), Schema.Struct({ "kid": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "use": Schema.optionalKey(Schema.Literal("sig")), "key_ops": Schema.optionalKey(Schema.Array(Schema.Literals(["sign", "verify"])).check(Schema.isMinLength(2).annotate({ "expected": "a value with a length of at least 2" })).check(Schema.isMaxLength(2).annotate({ "expected": "a value with a length of at most 2" }))), "ext": Schema.optionalKey(Schema.Literal(true)), "kty": Schema.Literal("OKP"), "alg": Schema.optionalKey(Schema.Literal("EdDSA")), "crv": Schema.Literal("Ed25519"), "x": Schema.String, "d": Schema.String }), Schema.Struct({ "kid": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "use": Schema.optionalKey(Schema.Literal("sig")), "key_ops": Schema.optionalKey(Schema.Array(Schema.Literals(["sign", "verify"])).check(Schema.isMinLength(2).annotate({ "expected": "a value with a length of at least 2" })).check(Schema.isMaxLength(2).annotate({ "expected": "a value with a length of at most 2" }))), "ext": Schema.optionalKey(Schema.Literal(true)), "kty": Schema.Literal("oct"), "alg": Schema.optionalKey(Schema.Literal("HS256")), "k": Schema.String.check(Schema.isMinLength(16).annotate({ "expected": "a value with a length of at least 16" })) })], { mode: "oneOf" })) }) -export const V1CreateProjectSigningKeyOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]), "public_jwk": Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null]), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }) }) -export const V1CreateProjectTpaIntegrationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "oidc_issuer_url": Schema.optionalKey(Schema.String), "jwks_url": Schema.optionalKey(Schema.String), "custom_jwks": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })) }) -export const V1CreateProjectTpaIntegrationOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "type": Schema.String, "oidc_issuer_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "jwks_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "custom_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "resolved_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "inserted_at": Schema.String, "updated_at": Schema.String, "resolved_at": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) }) -export const V1CreateRestorePointInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String.check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })) }) -export const V1CreateRestorePointOutput = Schema.Struct({ "name": Schema.String, "status": Schema.Literals(["AVAILABLE", "PENDING", "REMOVED", "FAILED"]), "completed_on": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]) }) -export const V1DeactivateVanitySubdomainConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1DeleteHostnameConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "remove_addon": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])) }) -export const V1DeleteABranchInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]), "force": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])) }) -export const V1DeleteABranchOutput = Schema.Struct({ "message": Schema.Literal("ok") }) -export const V1DeleteAFunctionInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "function_slug": Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z0-9_-]+$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z0-9_-]+$" })) }) -export const V1DeleteAProjectInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1DeleteAProjectOutput = Schema.Struct({ "id": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "ref": Schema.String, "name": Schema.String }) -export const V1DeleteASsoProviderInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "provider_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1DeleteASsoProviderOutput = Schema.Struct({ "id": Schema.String, "saml": Schema.optionalKey(Schema.Struct({ "entity_id": Schema.String, "metadata_url": Schema.optionalKey(Schema.String), "metadata_xml": Schema.optionalKey(Schema.String), "attribute_mapping": Schema.optionalKey(Schema.Struct({ "keys": Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ "name": Schema.optionalKey(Schema.String), "names": Schema.optionalKey(Schema.Array(Schema.String)), "default": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), "array": Schema.optionalKey(Schema.Boolean) }))) })), "name_id_format": Schema.optionalKey(Schema.Literals(["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"])) })), "domains": Schema.optionalKey(Schema.Array(Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }))), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }) -export const V1DeleteInviteExternalJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "invite_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1DeleteJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "user_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1DeleteLoginRolesInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1DeleteLoginRolesOutput = Schema.Struct({ "message": Schema.Literal("ok") }) -export const V1DeleteNetworkBansInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "ipv4_addresses": Schema.Array(Schema.String).annotate({ "description": "List of IP addresses to unban." }), "requester_ip": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Include requester's public IP in the list of addresses to unban." })), "identifier": Schema.optionalKey(Schema.String) }) -export const V1DeleteProjectApiKeyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "reveal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "was_compromised": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "reason": Schema.optionalKey(Schema.String) }) -export const V1DeleteProjectApiKeyOutput = Schema.Struct({ "api_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "type": Schema.optionalKey(Schema.Union([Schema.Literal("legacy"), Schema.Literal("publishable"), Schema.Literal("secret"), Schema.Null])), "prefix": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "name": Schema.String, "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hash": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "secret_jwt_template": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).check(Schema.isPropertyNames(Schema.String).annotate({ "expected": "an object with property names matching the schema" })), Schema.Null])), "inserted_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])), "updated_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])) }) -export const V1DeleteProjectClaimTokenInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1DeleteProjectTpaIntegrationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "tpa_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1DeleteProjectTpaIntegrationOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "type": Schema.String, "oidc_issuer_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "jwks_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "custom_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "resolved_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "inserted_at": Schema.String, "updated_at": Schema.String, "resolved_at": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) }) -export const V1DeployAFunctionInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "slug": Schema.optionalKey(Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z][A-Za-z0-9_-]*$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z][A-Za-z0-9_-]*$" }))), "bundleOnly": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "body": Schema.Struct({ "file": Schema.Array(BinaryInput), "metadata": Schema.Struct({ "entrypoint_path": Schema.String, "import_map_path": Schema.optionalKey(Schema.String), "static_patterns": Schema.optionalKey(Schema.Array(Schema.String)), "verify_jwt": Schema.optionalKey(Schema.Boolean), "name": Schema.optionalKey(Schema.String) }) }) }) -export const V1DeployAFunctionOutput = Schema.Struct({ "id": Schema.String, "slug": Schema.String, "name": Schema.String, "status": Schema.Literals(["ACTIVE", "REMOVED", "THROTTLED"]), "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "created_at": Schema.optionalKey(Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "updated_at": Schema.optionalKey(Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "verify_jwt": Schema.optionalKey(Schema.Boolean), "import_map": Schema.optionalKey(Schema.Boolean), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "ezbr_sha256": Schema.optionalKey(Schema.String) }) -export const V1DiffABranchInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]), "included_schemas": Schema.optionalKey(Schema.String), "pgdelta": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])) }) -export const V1DiffABranchOutput = Schema.String -export const V1DisablePreviewBranchingInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1DisableReadonlyModeTemporarilyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1EnableDatabaseWebhookInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ExchangeOauthTokenInput = Schema.Struct({ "body": Schema.Struct({ "grant_type": Schema.optionalKey(Schema.Literals(["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"])), "client_id": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "client_secret": Schema.optionalKey(Schema.String), "code": Schema.optionalKey(Schema.String), "code_verifier": Schema.optionalKey(Schema.String), "redirect_uri": Schema.optionalKey(Schema.String), "refresh_token": Schema.optionalKey(Schema.String), "assertion": Schema.optionalKey(Schema.String.annotate({ "description": "IDJAG assertion JWT for grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. Beta - available on Team and Enterprise plans only." })), "resource": Schema.optionalKey(Schema.String.annotate({ "description": "Resource indicator for MCP (Model Context Protocol) clients", "format": "uri" })), "scope": Schema.optionalKey(Schema.String) }) }) -export const V1ExchangeOauthTokenOutput = Schema.Struct({ "access_token": Schema.String, "refresh_token": Schema.optionalKey(Schema.String.annotate({ "description": "The `urn:ietf:params:oauth:grant-type:jwt-bearer` grant type issues access tokens only, no refresh token is returned and the token cannot be revoked via `/v1/oauth/revoke`." })), "expires_in": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "token_type": Schema.Literal("Bearer") }) -export const V1GenerateTypescriptTypesInput = Schema.Struct({ "included_schemas": Schema.optionalKey(Schema.String), "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GenerateTypescriptTypesOutput = Schema.Struct({ "types": Schema.String }) -export const V1GetABranchInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String }) -export const V1GetABranchOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "name": Schema.String, "project_ref": Schema.String, "parent_project_ref": Schema.String, "is_default": Schema.Boolean, "git_branch": Schema.optionalKey(Schema.String), "pr_number": Schema.optionalKey(Schema.Number.annotate({ "format": "int32" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "latest_check_run_id": Schema.optionalKey(Schema.Number.annotate({ "description": "This field is deprecated and will not be populated." }).check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "persistent": Schema.Boolean, "status": Schema.Literals(["CREATING_PROJECT", "RUNNING_MIGRATIONS", "MIGRATIONS_PASSED", "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED"]).annotate({ "description": "This field is deprecated. List action runs to get branch status instead." }), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }), "review_requested_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "with_data": Schema.Boolean, "notify_url": Schema.optionalKey(Schema.String.annotate({ "format": "uri" })), "deletion_scheduled_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "preview_project_status": Schema.optionalKey(Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"])) }) -export const V1GetABranchConfigInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]) }) -export const V1GetABranchConfigOutput = Schema.Struct({ "ref": Schema.String, "postgres_version": Schema.String, "postgres_engine": Schema.String, "release_channel": Schema.String, "status": Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"]), "db_host": Schema.String, "db_port": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "db_user": Schema.optionalKey(Schema.String), "db_pass": Schema.optionalKey(Schema.String), "jwt_secret": Schema.optionalKey(Schema.String) }) -export const V1GetAFunctionInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "function_slug": Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z0-9_-]+$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z0-9_-]+$" })) }) -export const V1GetAFunctionOutput = Schema.Struct({ "id": Schema.String, "slug": Schema.String, "name": Schema.String, "status": Schema.Literals(["ACTIVE", "REMOVED", "THROTTLED"]), "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "created_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "updated_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "verify_jwt": Schema.optionalKey(Schema.Boolean), "import_map": Schema.optionalKey(Schema.Boolean), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "ezbr_sha256": Schema.optionalKey(Schema.String) }) -export const V1GetAFunctionBodyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "function_slug": Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z0-9_-]+$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z0-9_-]+$" })) }) -export const V1GetAFunctionBodyOutput = Schema.Record(Schema.String, Schema.Never) -export const V1GetAMigrationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "version": Schema.String.check(Schema.isPattern(new RegExp("^\\d+$")).annotate({ "expected": "a string matching the RegExp ^\\d+$" })) }) -export const V1GetAMigrationOutput = Schema.Struct({ "version": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "name": Schema.optionalKey(Schema.String), "statements": Schema.optionalKey(Schema.Array(Schema.String)), "rollback": Schema.optionalKey(Schema.Array(Schema.String)), "created_by": Schema.optionalKey(Schema.String), "idempotency_key": Schema.optionalKey(Schema.String) }) -export const V1GetASnippetInput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1GetASnippetOutput = Schema.Struct({ "id": Schema.String, "inserted_at": Schema.String, "updated_at": Schema.String, "type": Schema.Literal("sql"), "visibility": Schema.Literals(["user", "project", "org", "public"]), "name": Schema.String, "description": Schema.Union([Schema.String, Schema.Null]), "project": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "name": Schema.String }), "owner": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "username": Schema.String }), "updated_by": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "username": Schema.String }), "favorite": Schema.Boolean, "content": Schema.Struct({ "favorite": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Deprecated: Rely on root-level favorite property instead." })), "schema_version": Schema.String, "sql": Schema.String }) }) -export const V1GetASsoProviderInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "provider_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1GetASsoProviderOutput = Schema.Struct({ "id": Schema.String, "saml": Schema.optionalKey(Schema.Struct({ "entity_id": Schema.String, "metadata_url": Schema.optionalKey(Schema.String), "metadata_xml": Schema.optionalKey(Schema.String), "attribute_mapping": Schema.optionalKey(Schema.Struct({ "keys": Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ "name": Schema.optionalKey(Schema.String), "names": Schema.optionalKey(Schema.Array(Schema.String)), "default": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), "array": Schema.optionalKey(Schema.Boolean) }))) })), "name_id_format": Schema.optionalKey(Schema.Literals(["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"])) })), "domains": Schema.optionalKey(Schema.Array(Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }))), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }) -export const V1GetActionRunInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "run_id": Schema.String }) -export const V1GetActionRunOutput = Schema.Struct({ "id": Schema.String, "branch_id": Schema.String, "run_steps": Schema.Array(Schema.Struct({ "name": Schema.Literals(["clone", "pull", "health", "configure", "migrate", "seed", "deploy"]), "status": Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"]), "created_at": Schema.String, "updated_at": Schema.String })), "git_config": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "workdir": Schema.Union([Schema.String, Schema.Null]), "check_run_id": Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]), "created_at": Schema.String, "updated_at": Schema.String }) -export const V1GetActionRunLogsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "run_id": Schema.String }) -export const V1GetActionRunLogsOutput = Schema.String -export const V1GetAllProjectsForOrganizationInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "offset": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }))), "search": Schema.optionalKey(Schema.String), "sort": Schema.optionalKey(Schema.Literals(["name_asc", "name_desc", "created_asc", "created_desc"])), "statuses": Schema.optionalKey(Schema.String) }) -export const V1GetAllProjectsForOrganizationOutput = Schema.Struct({ "projects": Schema.Array(Schema.Struct({ "ref": Schema.String, "name": Schema.String, "cloud_provider": Schema.String, "region": Schema.String, "is_branch": Schema.Boolean, "status": Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"]), "inserted_at": Schema.String, "databases": Schema.Array(Schema.Struct({ "infra_compute_size": Schema.optionalKey(Schema.Literals(["pico", "nano", "micro", "small", "medium", "large", "xlarge", "2xlarge", "4xlarge", "8xlarge", "12xlarge", "16xlarge", "24xlarge", "24xlarge_optimized_memory", "24xlarge_optimized_cpu", "24xlarge_high_memory", "48xlarge", "48xlarge_optimized_memory", "48xlarge_optimized_cpu", "48xlarge_high_memory"])), "region": Schema.String, "status": Schema.Literals(["ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UNKNOWN", "INIT_READ_REPLICA", "INIT_READ_REPLICA_FAILED", "RESTARTING", "RESIZING"]), "cloud_provider": Schema.String, "identifier": Schema.String, "type": Schema.Literals(["PRIMARY", "READ_REPLICA"]), "disk_volume_size_gb": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "disk_type": Schema.optionalKey(Schema.Literals(["gp3", "io2"])), "disk_throughput_mbps": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "disk_last_modified_at": Schema.optionalKey(Schema.String) })) })), "pagination": Schema.Struct({ "count": Schema.Number.annotate({ "description": "Total number of projects. Use this to calculate the total number of pages." }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), "limit": Schema.Number.annotate({ "description": "Maximum number of projects per page" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), "offset": Schema.Number.annotate({ "description": "Number of projects skipped in this response" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) }) }) -export const V1GetAnOrganizationInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })) }) -export const V1GetAnOrganizationOutput = Schema.Struct({ "id": Schema.String, "name": Schema.String, "plan": Schema.optionalKey(Schema.Literals(["free", "pro", "team", "enterprise", "platform"])), "opt_in_tags": Schema.Array(Schema.Literals(["AI_SQL_GENERATOR_OPT_IN", "AI_DATA_GENERATOR_OPT_IN", "AI_LOG_GENERATOR_OPT_IN"])), "allowed_release_channels": Schema.Array(Schema.Literals(["internal", "alpha", "beta", "ga", "withdrawn", "preview"])) }) -export const V1GetAuthServiceConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetAuthServiceConfigOutput = Schema.Struct({ "api_max_request_duration": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "db_max_pool_size": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "db_max_pool_size_unit": Schema.Union([Schema.Literal("connections"), Schema.Literal("percent"), Schema.Null]), "disable_signup": Schema.Union([Schema.Boolean, Schema.Null]), "external_anonymous_users_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_apple_additional_client_ids": Schema.Union([Schema.String, Schema.Null]), "external_apple_client_id": Schema.Union([Schema.String, Schema.Null]), "external_apple_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_apple_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_apple_secret": Schema.Union([Schema.String, Schema.Null]), "external_azure_client_id": Schema.Union([Schema.String, Schema.Null]), "external_azure_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_azure_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_azure_secret": Schema.Union([Schema.String, Schema.Null]), "external_azure_url": Schema.Union([Schema.String, Schema.Null]), "external_bitbucket_client_id": Schema.Union([Schema.String, Schema.Null]), "external_bitbucket_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_bitbucket_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_bitbucket_secret": Schema.Union([Schema.String, Schema.Null]), "external_discord_client_id": Schema.Union([Schema.String, Schema.Null]), "external_discord_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_discord_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_discord_secret": Schema.Union([Schema.String, Schema.Null]), "external_email_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_facebook_client_id": Schema.Union([Schema.String, Schema.Null]), "external_facebook_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_facebook_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_facebook_secret": Schema.Union([Schema.String, Schema.Null]), "external_figma_client_id": Schema.Union([Schema.String, Schema.Null]), "external_figma_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_figma_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_figma_secret": Schema.Union([Schema.String, Schema.Null]), "external_github_client_id": Schema.Union([Schema.String, Schema.Null]), "external_github_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_github_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_github_secret": Schema.Union([Schema.String, Schema.Null]), "external_gitlab_client_id": Schema.Union([Schema.String, Schema.Null]), "external_gitlab_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_gitlab_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_gitlab_secret": Schema.Union([Schema.String, Schema.Null]), "external_gitlab_url": Schema.Union([Schema.String, Schema.Null]), "external_google_additional_client_ids": Schema.Union([Schema.String, Schema.Null]), "external_google_client_id": Schema.Union([Schema.String, Schema.Null]), "external_google_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_google_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_google_secret": Schema.Union([Schema.String, Schema.Null]), "external_google_skip_nonce_check": Schema.Union([Schema.Boolean, Schema.Null]), "external_kakao_client_id": Schema.Union([Schema.String, Schema.Null]), "external_kakao_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_kakao_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_kakao_secret": Schema.Union([Schema.String, Schema.Null]), "external_keycloak_client_id": Schema.Union([Schema.String, Schema.Null]), "external_keycloak_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_keycloak_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_keycloak_secret": Schema.Union([Schema.String, Schema.Null]), "external_keycloak_url": Schema.Union([Schema.String, Schema.Null]), "external_linkedin_oidc_client_id": Schema.Union([Schema.String, Schema.Null]), "external_linkedin_oidc_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_linkedin_oidc_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_linkedin_oidc_secret": Schema.Union([Schema.String, Schema.Null]), "external_slack_oidc_client_id": Schema.Union([Schema.String, Schema.Null]), "external_slack_oidc_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_oidc_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_oidc_secret": Schema.Union([Schema.String, Schema.Null]), "external_notion_client_id": Schema.Union([Schema.String, Schema.Null]), "external_notion_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_notion_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_notion_secret": Schema.Union([Schema.String, Schema.Null]), "external_phone_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_client_id": Schema.Union([Schema.String, Schema.Null]), "external_slack_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_secret": Schema.Union([Schema.String, Schema.Null]), "external_spotify_client_id": Schema.Union([Schema.String, Schema.Null]), "external_spotify_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_spotify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_spotify_secret": Schema.Union([Schema.String, Schema.Null]), "external_twitch_client_id": Schema.Union([Schema.String, Schema.Null]), "external_twitch_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitch_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitch_secret": Schema.Union([Schema.String, Schema.Null]), "external_twitter_client_id": Schema.Union([Schema.String, Schema.Null]), "external_twitter_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitter_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitter_secret": Schema.Union([Schema.String, Schema.Null]), "external_x_client_id": Schema.Union([Schema.String, Schema.Null]), "external_x_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_x_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_x_secret": Schema.Union([Schema.String, Schema.Null]), "external_workos_client_id": Schema.Union([Schema.String, Schema.Null]), "external_workos_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_workos_secret": Schema.Union([Schema.String, Schema.Null]), "external_workos_url": Schema.Union([Schema.String, Schema.Null]), "external_web3_solana_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_web3_ethereum_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_zoom_client_id": Schema.Union([Schema.String, Schema.Null]), "external_zoom_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_zoom_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_zoom_secret": Schema.Union([Schema.String, Schema.Null]), "hook_custom_access_token_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_custom_access_token_uri": Schema.Union([Schema.String, Schema.Null]), "hook_custom_access_token_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_mfa_verification_attempt_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_mfa_verification_attempt_uri": Schema.Union([Schema.String, Schema.Null]), "hook_mfa_verification_attempt_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_password_verification_attempt_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_password_verification_attempt_uri": Schema.Union([Schema.String, Schema.Null]), "hook_password_verification_attempt_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_send_sms_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_send_sms_uri": Schema.Union([Schema.String, Schema.Null]), "hook_send_sms_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_send_email_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_send_email_uri": Schema.Union([Schema.String, Schema.Null]), "hook_send_email_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_before_user_created_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_before_user_created_uri": Schema.Union([Schema.String, Schema.Null]), "hook_before_user_created_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_after_user_created_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_after_user_created_uri": Schema.Union([Schema.String, Schema.Null]), "hook_after_user_created_secrets": Schema.Union([Schema.String, Schema.Null]), "jwt_exp": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "mailer_allow_unverified_email_sign_ins": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_autoconfirm": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_otp_exp": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "mailer_otp_length": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "mailer_secure_email_change_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_subjects_confirmation": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_email_change": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_invite": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_magic_link": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_reauthentication": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_recovery": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_password_changed_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_email_changed_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_phone_changed_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_mfa_factor_enrolled_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_mfa_factor_unenrolled_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_identity_linked_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_identity_unlinked_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_confirmation_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_email_change_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_invite_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_magic_link_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_reauthentication_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_recovery_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_password_changed_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_email_changed_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_phone_changed_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_mfa_factor_enrolled_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_mfa_factor_unenrolled_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_identity_linked_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_identity_unlinked_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_notifications_password_changed_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_email_changed_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_phone_changed_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_mfa_factor_enrolled_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_mfa_factor_unenrolled_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_identity_linked_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_identity_unlinked_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_max_enrolled_factors": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "mfa_totp_enroll_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_totp_verify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_phone_enroll_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_phone_verify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_web_authn_enroll_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_web_authn_verify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "passkey_enabled": Schema.Boolean, "webauthn_rp_display_name": Schema.Union([Schema.String, Schema.Null]), "webauthn_rp_id": Schema.Union([Schema.String, Schema.Null]), "webauthn_rp_origins": Schema.Union([Schema.String, Schema.Null]), "mfa_phone_otp_length": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "mfa_phone_template": Schema.Union([Schema.String, Schema.Null]), "mfa_phone_max_frequency": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "nimbus_oauth_client_id": Schema.Union([Schema.String, Schema.Null]), "nimbus_oauth_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "nimbus_oauth_client_secret": Schema.Union([Schema.String, Schema.Null]), "password_hibp_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "password_min_length": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "password_required_characters": Schema.Union([Schema.Literal("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789"), Schema.Literal("abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789"), Schema.Literal("abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~"), Schema.Literal(""), Schema.Null]), "rate_limit_anonymous_users": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_email_sent": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_sms_sent": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_token_refresh": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_verify": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_otp": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_web3": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "refresh_token_rotation_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "saml_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "saml_external_url": Schema.Union([Schema.String, Schema.Null]), "saml_allow_encrypted_assertions": Schema.Union([Schema.Boolean, Schema.Null]), "security_sb_forwarded_for_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "security_captcha_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "security_captcha_provider": Schema.Union([Schema.Literal("turnstile"), Schema.Literal("hcaptcha"), Schema.Null]), "security_captcha_secret": Schema.Union([Schema.String, Schema.Null]), "security_manual_linking_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "security_refresh_token_reuse_interval": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "security_update_password_require_reauthentication": Schema.Union([Schema.Boolean, Schema.Null]), "sessions_inactivity_timeout": Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]), "sessions_single_per_user": Schema.Union([Schema.Boolean, Schema.Null]), "sessions_tags": Schema.Union([Schema.String, Schema.Null]), "sessions_timebox": Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]), "site_url": Schema.Union([Schema.String, Schema.Null]), "sms_autoconfirm": Schema.Union([Schema.Boolean, Schema.Null]), "sms_max_frequency": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "sms_messagebird_access_key": Schema.Union([Schema.String, Schema.Null]), "sms_messagebird_originator": Schema.Union([Schema.String, Schema.Null]), "sms_otp_exp": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "sms_otp_length": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "sms_provider": Schema.Union([Schema.Literal("messagebird"), Schema.Literal("textlocal"), Schema.Literal("twilio"), Schema.Literal("twilio_verify"), Schema.Literal("vonage"), Schema.Null]), "sms_template": Schema.Union([Schema.String, Schema.Null]), "sms_test_otp": Schema.Union([Schema.String, Schema.Null]), "sms_test_otp_valid_until": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]), "sms_textlocal_api_key": Schema.Union([Schema.String, Schema.Null]), "sms_textlocal_sender": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_account_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_auth_token": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_content_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_message_service_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_verify_account_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_verify_auth_token": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_verify_message_service_sid": Schema.Union([Schema.String, Schema.Null]), "sms_vonage_api_key": Schema.Union([Schema.String, Schema.Null]), "sms_vonage_api_secret": Schema.Union([Schema.String, Schema.Null]), "sms_vonage_from": Schema.Union([Schema.String, Schema.Null]), "smtp_admin_email": Schema.Union([Schema.String.annotate({ "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })), Schema.Null]), "smtp_host": Schema.Union([Schema.String, Schema.Null]), "smtp_max_frequency": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "smtp_pass": Schema.Union([Schema.String, Schema.Null]), "smtp_port": Schema.Union([Schema.String, Schema.Null]), "smtp_sender_name": Schema.Union([Schema.String, Schema.Null]), "smtp_user": Schema.Union([Schema.String, Schema.Null]), "uri_allow_list": Schema.Union([Schema.String, Schema.Null]), "oauth_server_enabled": Schema.Boolean, "oauth_server_allow_dynamic_registration": Schema.Boolean, "oauth_server_authorization_path": Schema.Union([Schema.String, Schema.Null]), "custom_oauth_enabled": Schema.Boolean, "custom_oauth_max_providers": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }) -export const V1GetAvailableRegionsInput = Schema.Struct({ "organization_slug": Schema.String, "continent": Schema.optionalKey(Schema.Literals(["NA", "SA", "EU", "AF", "AS", "OC", "AN"])), "desired_instance_size": Schema.optionalKey(Schema.Literals(["nano", "micro", "small", "medium", "large", "xlarge", "2xlarge", "4xlarge", "8xlarge", "12xlarge", "16xlarge", "24xlarge", "24xlarge_optimized_memory", "24xlarge_optimized_cpu", "24xlarge_high_memory", "48xlarge", "48xlarge_optimized_memory", "48xlarge_optimized_cpu", "48xlarge_high_memory"])) }) -export const V1GetAvailableRegionsOutput = Schema.Struct({ "recommendations": Schema.Struct({ "smartGroup": Schema.Struct({ "name": Schema.String, "code": Schema.Literals(["americas", "emea", "apac"]), "type": Schema.Literal("smartGroup") }), "specific": Schema.Array(Schema.Struct({ "name": Schema.String, "code": Schema.Literals(["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-southeast-1", "ap-northeast-1", "ap-northeast-2", "ap-east-1", "ap-southeast-2", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "eu-central-1", "eu-central-2", "ca-central-1", "ap-south-1", "sa-east-1"]), "type": Schema.Literal("specific"), "provider": Schema.Literals(["AWS", "AWS_K8S", "AWS_NIMBUS"]), "status": Schema.optionalKey(Schema.Literals(["capacity", "other"])) })) }), "all": Schema.Struct({ "smartGroup": Schema.Array(Schema.Struct({ "name": Schema.String, "code": Schema.Literals(["americas", "emea", "apac"]), "type": Schema.Literal("smartGroup") })), "specific": Schema.Array(Schema.Struct({ "name": Schema.String, "code": Schema.Literals(["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-southeast-1", "ap-northeast-1", "ap-northeast-2", "ap-east-1", "ap-southeast-2", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "eu-central-1", "eu-central-2", "ca-central-1", "ap-south-1", "sa-east-1"]), "type": Schema.Literal("specific"), "provider": Schema.Literals(["AWS", "AWS_K8S", "AWS_NIMBUS"]), "status": Schema.optionalKey(Schema.Literals(["capacity", "other"])) })) }) }) -export const V1GetBackupScheduleInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetBackupScheduleOutput = Schema.Struct({ "schedule_for": Schema.String.annotate({ "description": "Time of day to schedule daily backups, in UTC. Format: HH:MM:SS." }).check(Schema.isPattern(new RegExp("^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$")).annotate({ "expected": "a string matching the RegExp ^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$" })), "updated_at": Schema.String.annotate({ "description": "Timestamp of when the backup schedule was last updated.", "format": "date-time" }) }) -export const V1GetDatabaseDiskInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetDatabaseDiskOutput = Schema.Struct({ "attributes": Schema.Union([Schema.Struct({ "iops": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "size_gb": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "throughput_mibps": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" }))), "type": Schema.Literal("gp3") }), Schema.Struct({ "iops": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "size_gb": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "type": Schema.Literal("io2") })]), "last_modified_at": Schema.optionalKey(Schema.String) }) -export const V1GetDatabaseMetadataInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetDatabaseMetadataOutput = Schema.Struct({ "databases": Schema.Array(Schema.StructWithRest(Schema.Struct({ "name": Schema.String, "schemas": Schema.Array(Schema.StructWithRest(Schema.Struct({ "name": Schema.String }), [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))])) }), [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))])) }) -export const V1GetDatabaseOpenapiInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "schema": Schema.optionalKey(Schema.String) }) -export const V1GetDatabaseOpenapiOutput = Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) -export const V1GetDiskUtilizationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetDiskUtilizationOutput = Schema.Struct({ "timestamp": Schema.String, "metrics": Schema.Struct({ "fs_size_bytes": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "fs_avail_bytes": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "fs_used_bytes": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) }) }) -export const V1GetHostnameConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetHostnameConfigOutput = Schema.Struct({ "status": Schema.optionalKey(Schema.Literals(["1_not_started", "2_initiated", "3_challenge_verified", "4_origin_setup_completed", "5_services_reconfigured"])), "custom_hostname": Schema.optionalKey(Schema.String), "data": Schema.Struct({ "success": Schema.Boolean, "errors": Schema.Array(UpdateCustomHostnameResponseJsonValue), "messages": Schema.Array(UpdateCustomHostnameResponseJsonValue), "result": Schema.Struct({ "id": Schema.String, "hostname": Schema.String, "ssl": Schema.Struct({ "status": Schema.String, "validation_records": Schema.optionalKey(Schema.Array(Schema.Struct({ "txt_name": Schema.String, "txt_value": Schema.String }))), "validation_errors": Schema.optionalKey(Schema.Array(Schema.Struct({ "message": Schema.String }))) }), "ownership_verification": Schema.optionalKey(Schema.Struct({ "type": Schema.String, "name": Schema.String, "value": Schema.String })), "custom_origin_server": Schema.String, "verification_errors": Schema.optionalKey(Schema.Array(Schema.String)), "status": Schema.String }) }) }) -export const V1GetJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetJitAccessOutput = Schema.Struct({ "user_id": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "user_roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) }) -export const V1GetJitAccessConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetJitAccessConfigOutput = Schema.Union([Schema.Struct({ "state": Schema.Literals(["enabled", "disabled"]), "appliedSuccessfully": Schema.optionalKey(Schema.Boolean) }), Schema.Struct({ "state": Schema.Literal("unavailable"), "unavailableReason": Schema.Literals(["postgres_upgrade_required", "ssl_enforcement_required", "temporarily_unavailable"]) })], { mode: "oneOf" }) -export const V1GetLegacySigningKeyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetLegacySigningKeyOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]), "public_jwk": Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null]), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }) }) -export const V1GetNetworkRestrictionsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetNetworkRestrictionsOutput = Schema.Struct({ "entitlement": Schema.Literals(["disallowed", "allowed"]), "config": Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.String)), "dbAllowedCidrsV6": Schema.optionalKey(Schema.Array(Schema.String)) }).annotate({ "description": "At any given point in time, this is the config that the user has requested be applied to their project. The `status` field indicates if it has been applied to the project, or is pending. When an updated config is received, the applied config is moved to `old_config`." }), "old_config": Schema.optionalKey(Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.String)), "dbAllowedCidrsV6": Schema.optionalKey(Schema.Array(Schema.String)) }).annotate({ "description": "Populated when a new config has been received, but not registered as successfully applied to a project." })), "status": Schema.Literals(["stored", "applied"]), "updated_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "applied_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })) }) -export const V1GetOrganizationEntitlementsInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })) }) -export const V1GetOrganizationEntitlementsOutput = Schema.Struct({ "entitlements": Schema.Array(Schema.Struct({ "feature": Schema.Struct({ "key": Schema.Literals(["instances.compute_update_available_sizes", "instances.read_replicas", "instances.disk_modifications", "instances.high_availability", "instances.orioledb", "replication.etl", "storage.max_file_size", "storage.max_file_size.configurable", "storage.image_transformations", "storage.vector_buckets", "storage.iceberg_catalog", "storage.purge_cache", "security.audit_logs_days", "security.questionnaire", "security.soc2_report", "security.iso27001_certificate", "security.private_link", "security.enforce_mfa", "log.retention_days", "custom_domain", "vanity_subdomain", "ipv4", "pitr.available_variants", "log_drains", "audit_log_drains", "branching_limit", "branching_persistent", "auth.mfa_phone", "auth.mfa_web_authn", "auth.mfa_enhanced_security", "auth.hooks", "auth.platform.sso", "auth.custom_jwt_template", "auth.saml_2", "auth.user_sessions", "auth.leaked_password_protection", "auth.advanced_auth_settings", "auth.performance_settings", "auth.password_hibp", "auth.custom_oauth.max_providers", "backup.retention_days", "backup.restore_to_new_project", "backup.schedule", "function.max_count", "function.size_limit_mb", "realtime.max_concurrent_users", "realtime.max_events_per_second", "realtime.max_joins_per_second", "realtime.max_channels_per_client", "realtime.max_bytes_per_second", "realtime.max_presence_events_per_second", "realtime.max_payload_size_in_kb", "project_scoped_roles", "security.member_roles", "project_pausing", "project_cloning", "project_restore_after_expiry", "assistant.advance_model", "integrations.github_connections", "integrations.github_push_webhooks_limit", "dedicated_pooler", "observability.dashboard_advanced_metrics", "api.members.invitations", "api.members.roles"]), "type": Schema.Literals(["boolean", "numeric", "set"]) }), "hasAccess": Schema.Boolean, "type": Schema.Literals(["boolean", "numeric", "set"]), "config": Schema.Union([Schema.Struct({ "enabled": Schema.Boolean }), Schema.Struct({ "enabled": Schema.Boolean, "value": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "unlimited": Schema.Boolean, "unit": Schema.String }), Schema.Struct({ "enabled": Schema.Boolean, "set": Schema.Array(Schema.String) })]) })) }) -export const V1GetOrganizationProjectClaimInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "token": Schema.String }) -export const V1GetOrganizationProjectClaimOutput = Schema.Struct({ "project": Schema.Struct({ "ref": Schema.String, "name": Schema.String }), "preview": Schema.Struct({ "valid": Schema.Boolean, "warnings": Schema.Array(Schema.Struct({ "key": Schema.String, "message": Schema.String })), "errors": Schema.Array(Schema.Struct({ "key": Schema.String, "message": Schema.String })), "info": Schema.Array(Schema.Struct({ "key": Schema.String, "message": Schema.String })), "members_exceeding_free_project_limit": Schema.Array(Schema.Struct({ "name": Schema.String, "limit": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) })), "source_subscription_plan": Schema.Literals(["free", "pro", "team", "enterprise", "platform"]), "target_subscription_plan": Schema.Union([Schema.Literal("free"), Schema.Literal("pro"), Schema.Literal("team"), Schema.Literal("enterprise"), Schema.Literal("platform"), Schema.Null]) }), "expires_at": Schema.String, "created_at": Schema.String, "created_by": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1GetPerformanceAdvisorsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetPerformanceAdvisorsOutput = Schema.Struct({ "lints": Schema.Array(Schema.StructWithRest(Schema.Struct({ "name": Schema.Literals(["unindexed_foreign_keys", "auth_users_exposed", "auth_rls_initplan", "no_primary_key", "unused_index", "multiple_permissive_policies", "policy_exists_rls_disabled", "rls_enabled_no_policy", "duplicate_index", "security_definer_view", "function_search_path_mutable", "rls_disabled_in_public", "extension_in_public", "rls_references_user_metadata", "materialized_view_in_api", "foreign_table_in_api", "unsupported_reg_types", "auth_otp_long_expiry", "auth_otp_short_length", "ssl_not_enforced", "log_connections_not_enabled", "network_restrictions_not_set", "password_requirements_min_length", "pitr_not_enabled", "auth_leaked_password_protection", "auth_insufficient_mfa_options", "auth_password_policy_missing", "leaked_service_key", "no_backup_admin", "vulnerable_postgres_version", "db_not_reachable", "db_connection_failing", "db_connection_limit_reached", "instance_telemetry_lost", "instance_db_down", "instance_alert_firing", "log_service_error_rate_high", "project_not_active", "advisor_check_unavailable"]), "title": Schema.String, "level": Schema.Literals(["ERROR", "WARN", "INFO"]), "facing": Schema.Literal("EXTERNAL"), "categories": Schema.Array(Schema.Literals(["PERFORMANCE", "SECURITY", "HEALTH"])), "description": Schema.String, "detail": Schema.String, "remediation": Schema.String, "metadata": Schema.optionalKey(Schema.Struct({ "schema": Schema.optionalKey(Schema.String), "name": Schema.optionalKey(Schema.String), "entity": Schema.optionalKey(Schema.String), "type": Schema.optionalKey(Schema.Literals(["table", "view", "materialized view", "foreign table", "auth", "function", "extension", "compliance", "health"])), "fkey_name": Schema.optionalKey(Schema.String), "fkey_columns": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })))) })), "cache_key": Schema.String, "observed_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })) }), [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))])) }) -export const V1GetPgsodiumConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetPgsodiumConfigOutput = Schema.Struct({ "root_key": Schema.String.annotate({ "description": "The pgsodium root key: 32 bytes, hex-encoded (64 characters)." }) }) -export const V1GetPoolerConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetPoolerConfigOutput = Schema.Array(SupavisorConfigResponse) -export const V1GetPostgresConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetPostgresConfigOutput = Schema.Struct({ "effective_cache_size": Schema.optionalKey(Schema.String), "logical_decoding_work_mem": Schema.optionalKey(Schema.String), "cron.log_statement": Schema.optionalKey(Schema.Boolean), "log_autovacuum_min_duration": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_checkpoints": Schema.optionalKey(Schema.Boolean), "log_connections": Schema.optionalKey(Schema.Boolean), "log_disconnections": Schema.optionalKey(Schema.Boolean), "log_duration": Schema.optionalKey(Schema.Boolean), "log_lock_waits": Schema.optionalKey(Schema.Boolean), "log_recovery_conflict_waits": Schema.optionalKey(Schema.Boolean), "log_replication_commands": Schema.optionalKey(Schema.Boolean), "log_startup_progress_interval": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_temp_files": Schema.optionalKey(Schema.String), "maintenance_work_mem": Schema.optionalKey(Schema.String), "track_activity_query_size": Schema.optionalKey(Schema.String), "max_connections": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_locks_per_transaction": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(10).annotate({ "expected": "a value greater than or equal to 10" })).check(Schema.isLessThanOrEqualTo(2147483640).annotate({ "expected": "a value less than or equal to 2147483640" }))), "max_logical_replication_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_parallel_maintenance_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers_per_gather": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_replication_slots": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_slot_wal_keep_size": Schema.optionalKey(Schema.String), "max_standby_archive_delay": Schema.optionalKey(Schema.String), "max_standby_streaming_delay": Schema.optionalKey(Schema.String), "max_sync_workers_per_subscription": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_wal_size": Schema.optionalKey(Schema.String), "max_wal_senders": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_worker_processes": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "session_replication_role": Schema.optionalKey(Schema.Literals(["origin", "replica", "local"])), "shared_buffers": Schema.optionalKey(Schema.String), "statement_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "track_commit_timestamp": Schema.optionalKey(Schema.Boolean), "wal_keep_size": Schema.optionalKey(Schema.String), "wal_sender_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "work_mem": Schema.optionalKey(Schema.String), "checkpoint_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: s" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "hot_standby_feedback": Schema.optionalKey(Schema.Boolean) }) -export const V1GetPostgresUpgradeEligibilityInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetPostgresUpgradeEligibilityOutput = Schema.Struct({ "eligible": Schema.Boolean, "current_app_version": Schema.String, "current_app_version_release_channel": Schema.Literals(["internal", "alpha", "beta", "ga", "withdrawn", "preview"]), "latest_app_version": Schema.String, "target_upgrade_versions": Schema.Array(Schema.Struct({ "postgres_version": Schema.Literals(["13", "14", "15", "17", "17-oriole"]), "release_channel": Schema.Literals(["internal", "alpha", "beta", "ga", "withdrawn", "preview"]), "app_version": Schema.String })), "duration_estimate_hours": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "legacy_auth_custom_roles": Schema.Array(Schema.String), "objects_to_be_dropped": Schema.Array(Schema.String).annotate({ "description": "Use validation_errors instead." }), "unsupported_extensions": Schema.Array(Schema.String).annotate({ "description": "Use validation_errors instead." }), "user_defined_objects_in_internal_schemas": Schema.Array(Schema.String).annotate({ "description": "Use validation_errors instead." }), "validation_errors": Schema.Array(Schema.Union([Schema.Struct({ "type": Schema.Literal("objects_depending_on_pg_cron"), "dependents": Schema.Array(Schema.String) }), Schema.Struct({ "type": Schema.Literal("indexes_referencing_ll_to_earth"), "schema_name": Schema.String, "table_name": Schema.String, "index_name": Schema.String }), Schema.Struct({ "type": Schema.Literal("function_using_obsolete_lang"), "schema_name": Schema.String, "function_name": Schema.String, "lang_name": Schema.String }), Schema.Struct({ "type": Schema.Literal("unsupported_extension"), "extension_name": Schema.String }), Schema.Struct({ "type": Schema.Literal("unsupported_fdw_handler"), "fdw_name": Schema.String, "fdw_handler_name": Schema.String }), Schema.Struct({ "type": Schema.Literal("unlogged_table_with_persistent_sequence"), "schema_name": Schema.String, "table_name": Schema.String, "sequence_name": Schema.String }), Schema.Struct({ "type": Schema.Literal("user_defined_objects_in_internal_schemas"), "obj_type": Schema.Literals(["table", "function"]), "schema_name": Schema.String, "obj_name": Schema.String }), Schema.Struct({ "type": Schema.Literal("active_replication_slot"), "slot_name": Schema.String }), Schema.Struct({ "type": Schema.Literal("x86_architecture") }), Schema.Struct({ "type": Schema.Literal("project_hibernating") })])), "warnings": Schema.Array(Schema.Union([Schema.Struct({ "type": Schema.Literal("pg_graphql_introspection_change") }), Schema.Struct({ "type": Schema.Literal("ltree_reindex_required") }), Schema.Struct({ "type": Schema.Literal("operator_estimator_gate") })], { mode: "oneOf" })) }) -export const V1GetPostgresUpgradeStatusInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "tracking_id": Schema.optionalKey(Schema.String) }) -export const V1GetPostgresUpgradeStatusOutput = Schema.Struct({ "databaseUpgradeStatus": Schema.Union([Schema.Struct({ "initiated_at": Schema.String, "latest_status_at": Schema.String, "target_version": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "error": Schema.optionalKey(Schema.Literals(["1_upgraded_instance_launch_failed", "2_volume_detachchment_from_upgraded_instance_failed", "3_volume_attachment_to_original_instance_failed", "4_data_upgrade_initiation_failed", "5_data_upgrade_completion_failed", "6_volume_detachchment_from_original_instance_failed", "7_volume_attachment_to_upgraded_instance_failed", "8_upgrade_completion_failed", "9_post_physical_backup_failed"])), "progress": Schema.optionalKey(Schema.Literals(["0_requested", "1_started", "2_launched_upgraded_instance", "3_detached_volume_from_upgraded_instance", "4_attached_volume_to_original_instance", "5_initiated_data_upgrade", "6_completed_data_upgrade", "7_detached_volume_from_original_instance", "8_attached_volume_to_upgraded_instance", "9_completed_upgrade", "10_completed_post_physical_backup"])), "status": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) }), Schema.Null]) }) -export const V1GetPostgrestServiceConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetPostgrestServiceConfigOutput = Schema.Struct({ "db_schema": Schema.String, "max_rows": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "db_extra_search_path": Schema.String, "db_pool": Schema.Union([Schema.Number.annotate({ "description": "If `null`, the value is automatically configured based on compute size." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "db_pool_acquisition_timeout": Schema.Union([Schema.Number.annotate({ "description": "If `null`, the value is automatically configured to 10." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "jwt_secret": Schema.optionalKey(Schema.String) }) -export const V1GetProfileInput = Schema.Record(Schema.String, Schema.Never) -export const V1GetProfileOutput = Schema.Struct({ "gotrue_id": Schema.String, "primary_email": Schema.String, "username": Schema.String }) -export const V1GetProjectInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectOutput = Schema.Struct({ "id": Schema.String.annotate({ "description": "Deprecated: Use `ref` instead." }), "ref": Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "organization_id": Schema.String.annotate({ "description": "Deprecated: Use `organization_slug` instead." }), "organization_slug": Schema.String.annotate({ "description": "Organization slug" }).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "name": Schema.String.annotate({ "description": "Name of your project" }), "region": Schema.String.annotate({ "description": "Region of your project" }), "created_at": Schema.String.annotate({ "description": "Creation timestamp" }), "status": Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"]), "database": Schema.Struct({ "host": Schema.String.annotate({ "description": "Database host" }), "version": Schema.String.annotate({ "description": "Database version" }), "postgres_engine": Schema.String.annotate({ "description": "Database engine" }), "release_channel": Schema.String.annotate({ "description": "Release channel" }) }) }) -export const V1GetProjectApiKeyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "reveal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])) }) -export const V1GetProjectApiKeyOutput = Schema.Struct({ "api_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "type": Schema.optionalKey(Schema.Union([Schema.Literal("legacy"), Schema.Literal("publishable"), Schema.Literal("secret"), Schema.Null])), "prefix": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "name": Schema.String, "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hash": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "secret_jwt_template": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).check(Schema.isPropertyNames(Schema.String).annotate({ "expected": "an object with property names matching the schema" })), Schema.Null])), "inserted_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])), "updated_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])) }) -export const V1GetProjectApiKeysInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "reveal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])) }) -export const V1GetProjectApiKeysOutput = Schema.Array(ApiKeyResponse) -export const V1GetProjectClaimTokenInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectClaimTokenOutput = Schema.Struct({ "token_alias": Schema.String, "expires_at": Schema.String, "created_at": Schema.String, "created_by": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1GetProjectDiskAutoscaleConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectDiskAutoscaleConfigOutput = Schema.Struct({ "growth_percent": Schema.Union([Schema.Number.annotate({ "description": "Growth percentage for disk autoscaling" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), Schema.Null]), "min_increment_gb": Schema.Union([Schema.Number.annotate({ "description": "Minimum increment size for disk autoscaling in GB" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), Schema.Null]), "max_size_gb": Schema.Union([Schema.Number.annotate({ "description": "Maximum limit the disk size will grow to in GB" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), Schema.Null]) }) -export const V1GetProjectFunctionCombinedStatsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "interval": Schema.Literals(["15min", "1hr", "3hr", "1day"]), "function_id": Schema.String }) -export const V1GetProjectFunctionCombinedStatsOutput = Schema.Struct({ "result": Schema.optionalKey(Schema.Array(Schema.Json.annotate({ "expected": "JSON value" }))), "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ "code": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "errors": Schema.Array(Schema.Struct({ "domain": Schema.String, "location": Schema.String, "locationType": Schema.String, "message": Schema.String, "reason": Schema.String })), "message": Schema.String, "status": Schema.String })])) }) -export const V1GetProjectLegacyApiKeysInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectLegacyApiKeysOutput = Schema.Struct({ "enabled": Schema.Boolean }) -export const V1GetProjectLogsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "sql": Schema.optionalKey(Schema.String), "iso_timestamp_start": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "iso_timestamp_end": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })) }) -export const V1GetProjectLogsOutput = Schema.Struct({ "result": Schema.optionalKey(Schema.Array(Schema.Json.annotate({ "expected": "JSON value" }))), "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ "code": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "errors": Schema.Array(Schema.Struct({ "domain": Schema.String, "location": Schema.String, "locationType": Schema.String, "message": Schema.String, "reason": Schema.String })), "message": Schema.String, "status": Schema.String })])) }) -export const V1GetProjectLogsAllInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "sql": Schema.optionalKey(Schema.String), "iso_timestamp_start": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "iso_timestamp_end": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })) }) -export const V1GetProjectLogsAllOutput = Schema.Struct({ "result": Schema.optionalKey(Schema.Array(Schema.Json.annotate({ "expected": "JSON value" }))), "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ "code": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "errors": Schema.Array(Schema.Struct({ "domain": Schema.String, "location": Schema.String, "locationType": Schema.String, "message": Schema.String, "reason": Schema.String })), "message": Schema.String, "status": Schema.String })])) }) -export const V1GetProjectPgbouncerConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectPgbouncerConfigOutput = Schema.Struct({ "default_pool_size": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "ignore_startup_parameters": Schema.optionalKey(Schema.String), "max_client_conn": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "pool_mode": Schema.optionalKey(Schema.Literals(["transaction", "session", "statement"])), "connection_string": Schema.optionalKey(Schema.String), "server_idle_timeout": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "server_lifetime": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "query_wait_timeout": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "reserve_pool_size": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))) }) -export const V1GetProjectSigningKeyInput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectSigningKeyOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]), "public_jwk": Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null]), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }) }) -export const V1GetProjectSigningKeysInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectSigningKeysOutput = Schema.Struct({ "keys": Schema.Array(Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]), "public_jwk": Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null]), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }) })) }) -export const V1GetProjectTpaIntegrationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "tpa_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V1GetProjectTpaIntegrationOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "type": Schema.String, "oidc_issuer_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "jwks_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "custom_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "resolved_jwks": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "inserted_at": Schema.String, "updated_at": Schema.String, "resolved_at": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) }) -export const V1GetProjectUsageApiCountInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "interval": Schema.optionalKey(Schema.Literals(["15min", "30min", "1hr", "3hr", "1day", "3day", "7day"])) }) -export const V1GetProjectUsageApiCountOutput = Schema.Struct({ "result": Schema.optionalKey(Schema.Array(Schema.Struct({ "timestamp": Schema.String.annotate({ "format": "date-time" }), "total_auth_requests": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "total_realtime_requests": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "total_rest_requests": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "total_storage_requests": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) }))), "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ "code": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "errors": Schema.Array(Schema.Struct({ "domain": Schema.String, "location": Schema.String, "locationType": Schema.String, "message": Schema.String, "reason": Schema.String })), "message": Schema.String, "status": Schema.String })])) }) -export const V1GetProjectUsageRequestCountInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetProjectUsageRequestCountOutput = Schema.Struct({ "result": Schema.optionalKey(Schema.Array(Schema.Struct({ "count": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) }))), "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ "code": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "errors": Schema.Array(Schema.Struct({ "domain": Schema.String, "location": Schema.String, "locationType": Schema.String, "message": Schema.String, "reason": Schema.String })), "message": Schema.String, "status": Schema.String })])) }) -export const V1GetReadonlyModeStatusInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetReadonlyModeStatusOutput = Schema.Struct({ "enabled": Schema.Boolean, "override_enabled": Schema.Boolean, "override_active_until": Schema.String }) -export const V1GetRealtimeConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetRealtimeConfigOutput = Schema.Struct({ "private_only": Schema.Union([Schema.Boolean.annotate({ "description": "Whether to only allow private channels" }), Schema.Null]), "connection_pool": Schema.Union([Schema.Number.annotate({ "description": "Sets connection pool size for Realtime Authorization" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" })), Schema.Null]), "postgres_changes_pool": Schema.Union([Schema.Number.annotate({ "description": "Sets connection pool size used to create Postgres Changes subscriptions" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" })), Schema.Null]), "max_concurrent_users": Schema.Union([Schema.Number.annotate({ "description": "Sets maximum number of concurrent users rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(50000).annotate({ "expected": "a value less than or equal to 50000" })), Schema.Null]), "max_events_per_second": Schema.Union([Schema.Number.annotate({ "description": "Sets maximum number of events per second rate per channel limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(50000).annotate({ "expected": "a value less than or equal to 50000" })), Schema.Null]), "max_bytes_per_second": Schema.Union([Schema.Number.annotate({ "description": "Sets maximum number of bytes per second rate per channel limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(10000000).annotate({ "expected": "a value less than or equal to 10000000" })), Schema.Null]), "max_channels_per_client": Schema.Union([Schema.Number.annotate({ "description": "Sets maximum number of channels per client rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(10000).annotate({ "expected": "a value less than or equal to 10000" })), Schema.Null]), "max_joins_per_second": Schema.Union([Schema.Number.annotate({ "description": "Sets maximum number of joins per second rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(5000).annotate({ "expected": "a value less than or equal to 5000" })), Schema.Null]), "max_presence_events_per_second": Schema.Union([Schema.Number.annotate({ "description": "Sets maximum number of presence events per second rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(5000).annotate({ "expected": "a value less than or equal to 5000" })), Schema.Null]), "max_payload_size_in_kb": Schema.Union([Schema.Number.annotate({ "description": "Sets maximum number of payload size in KB rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(10000).annotate({ "expected": "a value less than or equal to 10000" })), Schema.Null]), "suspend": Schema.Union([Schema.Boolean.annotate({ "description": "Disables the Realtime service for this project when true. Set to false to re-enable it." }), Schema.Null]), "presence_enabled": Schema.Boolean.annotate({ "description": "Whether to enable presence" }) }) -export const V1GetRestorePointInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.optionalKey(Schema.String.check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" }))) }) -export const V1GetRestorePointOutput = Schema.Struct({ "name": Schema.String, "status": Schema.Literals(["AVAILABLE", "PENDING", "REMOVED", "FAILED"]), "completed_on": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]) }) -export const V1GetSecurityAdvisorsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "lint_type": Schema.optionalKey(Schema.Literal("sql")) }) -export const V1GetSecurityAdvisorsOutput = Schema.Struct({ "lints": Schema.Array(Schema.StructWithRest(Schema.Struct({ "name": Schema.Literals(["unindexed_foreign_keys", "auth_users_exposed", "auth_rls_initplan", "no_primary_key", "unused_index", "multiple_permissive_policies", "policy_exists_rls_disabled", "rls_enabled_no_policy", "duplicate_index", "security_definer_view", "function_search_path_mutable", "rls_disabled_in_public", "extension_in_public", "rls_references_user_metadata", "materialized_view_in_api", "foreign_table_in_api", "unsupported_reg_types", "auth_otp_long_expiry", "auth_otp_short_length", "ssl_not_enforced", "log_connections_not_enabled", "network_restrictions_not_set", "password_requirements_min_length", "pitr_not_enabled", "auth_leaked_password_protection", "auth_insufficient_mfa_options", "auth_password_policy_missing", "leaked_service_key", "no_backup_admin", "vulnerable_postgres_version", "db_not_reachable", "db_connection_failing", "db_connection_limit_reached", "instance_telemetry_lost", "instance_db_down", "instance_alert_firing", "log_service_error_rate_high", "project_not_active", "advisor_check_unavailable"]), "title": Schema.String, "level": Schema.Literals(["ERROR", "WARN", "INFO"]), "facing": Schema.Literal("EXTERNAL"), "categories": Schema.Array(Schema.Literals(["PERFORMANCE", "SECURITY", "HEALTH"])), "description": Schema.String, "detail": Schema.String, "remediation": Schema.String, "metadata": Schema.optionalKey(Schema.Struct({ "schema": Schema.optionalKey(Schema.String), "name": Schema.optionalKey(Schema.String), "entity": Schema.optionalKey(Schema.String), "type": Schema.optionalKey(Schema.Literals(["table", "view", "materialized view", "foreign table", "auth", "function", "extension", "compliance", "health"])), "fkey_name": Schema.optionalKey(Schema.String), "fkey_columns": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })))) })), "cache_key": Schema.String, "observed_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })) }), [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))])) }) -export const V1GetServicesHealthInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "services": Schema.Union([Schema.String.annotate({ "description": "Comma-separated list of enums:\n\n- `auth`\n- `db`\n- `db_postgres_user`\n- `pooler`\n- `realtime`\n- `rest`\n- `storage`\n- `pg_bouncer`" }), Schema.Array(Schema.Literals(["auth", "db", "db_postgres_user", "pooler", "realtime", "rest", "storage", "pg_bouncer"])).annotate({ "description": "Array of enums." })]), "timeout_ms": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(10000).annotate({ "expected": "a value less than or equal to 10000" }))) }) -export const V1GetServicesHealthOutput = Schema.Array(V1ServiceHealthResponse) -export const V1GetSslEnforcementConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetSslEnforcementConfigOutput = Schema.Struct({ "currentConfig": Schema.Struct({ "database": Schema.Boolean }), "appliedSuccessfully": Schema.Boolean }) -export const V1GetStorageConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetStorageConfigOutput = Schema.Struct({ "fileSizeLimit": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "features": Schema.Struct({ "imageTransformation": Schema.Struct({ "enabled": Schema.Boolean }), "s3Protocol": Schema.Struct({ "enabled": Schema.Boolean }), "purgeCache": Schema.Struct({ "enabled": Schema.Boolean }), "icebergCatalog": Schema.Struct({ "enabled": Schema.Boolean, "maxNamespaces": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "maxTables": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "maxCatalogs": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), "vectorBuckets": Schema.Struct({ "enabled": Schema.Boolean, "maxBuckets": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "maxIndexes": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }) }), "capabilities": Schema.Struct({ "list_v2": Schema.Boolean, "iceberg_catalog": Schema.Boolean }), "external": Schema.Struct({ "upstreamTarget": Schema.Literals(["main", "canary"]) }), "migrationVersion": Schema.String, "databasePoolMode": Schema.optionalKey(Schema.String) }) -export const V1GetVanitySubdomainConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1GetVanitySubdomainConfigOutput = Schema.Struct({ "status": Schema.Literals(["not-used", "custom-domain-used", "active"]), "custom_domain": Schema.optionalKey(Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }))) }) -export const V1InviteExternalJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "email": Schema.String.annotate({ "format": "email" }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })), "roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) }) -export const V1InviteExternalJitAccessOutput = Schema.Struct({ "email": Schema.String.annotate({ "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })), "invite_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "user_roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) }) -export const V1ListActionRunsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "offset": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }))), "limit": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check(Schema.isGreaterThanOrEqualTo(10).annotate({ "expected": "a value greater than or equal to 10" }))) }) -export const V1ListActionRunsOutput = Schema.Array(Schema.Struct({ "id": Schema.String, "branch_id": Schema.String, "run_steps": Schema.Array(Schema.Struct({ "name": Schema.Literals(["clone", "pull", "health", "configure", "migrate", "seed", "deploy"]), "status": Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"]), "created_at": Schema.String, "updated_at": Schema.String })), "git_config": Schema.optionalKey(Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null])), "workdir": Schema.Union([Schema.String, Schema.Null]), "check_run_id": Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]), "created_at": Schema.String, "updated_at": Schema.String })) -export const V1ListAllBackupsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllBackupsOutput = Schema.Struct({ "region": Schema.String, "walg_enabled": Schema.Boolean, "pitr_enabled": Schema.Boolean, "backups": Schema.Array(Schema.Struct({ "id": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "is_physical_backup": Schema.Boolean, "status": Schema.Literals(["COMPLETED", "FAILED", "PENDING", "REMOVED", "ARCHIVED", "CANCELLED"]), "inserted_at": Schema.String })), "physical_backup_data": Schema.Struct({ "earliest_physical_backup_date_unix": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "latest_physical_backup_date_unix": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))) }) }) -export const V1ListAllBranchesInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllBranchesOutput = Schema.Array(BranchResponse) -export const V1ListAllBucketsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllBucketsOutput = Schema.Array(V1StorageBucketResponse) -export const V1ListAllFunctionsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllFunctionsOutput = Schema.Array(FunctionResponse) -export const V1ListAllNetworkBansInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllNetworkBansOutput = Schema.Struct({ "banned_ipv4_addresses": Schema.Array(Schema.String) }) -export const V1ListAllNetworkBansEnrichedInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllNetworkBansEnrichedOutput = Schema.Struct({ "banned_ipv4_addresses": Schema.Array(Schema.Struct({ "banned_address": Schema.String, "identifier": Schema.String, "type": Schema.String })) }) -export const V1ListAllOrganizationsInput = Schema.Record(Schema.String, Schema.Never) -export const V1ListAllOrganizationsOutput = Schema.Array(OrganizationResponseV1) -export const V1ListAllProjectsInput = Schema.Record(Schema.String, Schema.Never) -export const V1ListAllProjectsOutput = Schema.Array(V1ProjectWithDatabaseResponse) -export const V1ListAllSecretsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllSecretsOutput = Schema.Array(SecretResponse) -export const V1ListAllSnippetsInput = Schema.Struct({ "project_ref": Schema.optionalKey(Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" }))), "cursor": Schema.optionalKey(Schema.String), "limit": Schema.optionalKey(Schema.String), "sort_by": Schema.optionalKey(Schema.Literals(["name", "inserted_at"])), "sort_order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) }) -export const V1ListAllSnippetsOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "id": Schema.String, "inserted_at": Schema.String, "updated_at": Schema.String, "type": Schema.Literal("sql"), "visibility": Schema.Literals(["user", "project", "org", "public"]), "name": Schema.String, "description": Schema.Union([Schema.String, Schema.Null]), "project": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "name": Schema.String }), "owner": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "username": Schema.String }), "updated_by": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "username": Schema.String }), "favorite": Schema.Boolean })), "cursor": Schema.optionalKey(Schema.String) }) -export const V1ListAllSsoProviderInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAllSsoProviderOutput = Schema.Struct({ "items": Schema.Array(Schema.Struct({ "id": Schema.String, "saml": Schema.optionalKey(Schema.Struct({ "entity_id": Schema.String, "metadata_url": Schema.optionalKey(Schema.String), "metadata_xml": Schema.optionalKey(Schema.String), "attribute_mapping": Schema.optionalKey(Schema.Struct({ "keys": Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ "name": Schema.optionalKey(Schema.String), "names": Schema.optionalKey(Schema.Array(Schema.String)), "default": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), "array": Schema.optionalKey(Schema.Boolean) }))) })), "name_id_format": Schema.optionalKey(Schema.Literals(["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"])) })), "domains": Schema.optionalKey(Schema.Array(Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }))), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) })) }) -export const V1ListAvailableRestoreVersionsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListAvailableRestoreVersionsOutput = Schema.Struct({ "available_versions": Schema.Array(Schema.Struct({ "version": Schema.String, "release_channel": Schema.Literals(["internal", "alpha", "beta", "ga", "withdrawn", "preview"]), "postgres_engine": Schema.Literals(["13", "14", "15", "17", "17-oriole"]) })) }) -export const V1ListJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListJitAccessOutput = Schema.Struct({ "items": Schema.Array(Schema.Union([Schema.Struct({ "user_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "primary_email": Schema.Union([Schema.String, Schema.Null]), "invite_id": Schema.Null, "expires_at": Schema.Null, "user_roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) }), Schema.Struct({ "user_id": Schema.Null, "primary_email": Schema.String, "invite_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "expires_at": Schema.String, "user_roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) })])) }) -export const V1ListMigrationHistoryInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListMigrationHistoryOutput = Schema.Array(Schema.Struct({ "version": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "name": Schema.optionalKey(Schema.String) })) -export const V1ListOrganizationMembersInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })) }) -export const V1ListOrganizationMembersOutput = Schema.Array(V1OrganizationMemberResponse) -export const V1ListProjectAddonsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListProjectAddonsOutput = Schema.Struct({ "selected_addons": Schema.Array(Schema.Struct({ "type": Schema.Literals(["custom_domain", "compute_instance", "pitr", "ipv4", "auth_mfa_phone", "auth_mfa_web_authn", "log_drain", "etl_pipeline"]), "variant": Schema.Struct({ "id": Schema.Union([Schema.Literals(["ci_micro", "ci_small", "ci_medium", "ci_large", "ci_xlarge", "ci_2xlarge", "ci_4xlarge", "ci_8xlarge", "ci_12xlarge", "ci_16xlarge", "ci_24xlarge", "ci_24xlarge_optimized_cpu", "ci_24xlarge_optimized_memory", "ci_24xlarge_high_memory", "ci_48xlarge", "ci_48xlarge_optimized_cpu", "ci_48xlarge_optimized_memory", "ci_48xlarge_high_memory"]), Schema.Literal("cd_default"), Schema.Literals(["pitr_7", "pitr_14", "pitr_28"]), Schema.Literal("ipv4_default"), Schema.Literal("auth_mfa_phone_default"), Schema.Literal("auth_mfa_web_authn_default"), Schema.Literal("log_drain_default"), Schema.Literal("etl_pipeline_default")]), "name": Schema.String, "price": Schema.Struct({ "description": Schema.String, "type": Schema.Literals(["fixed", "usage"]), "interval": Schema.Literals(["monthly", "hourly"]), "amount": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) }), "meta": Schema.optionalKey(ListProjectAddonsResponseJsonValue) }) })), "available_addons": Schema.Array(Schema.Struct({ "type": Schema.Literals(["custom_domain", "compute_instance", "pitr", "ipv4", "auth_mfa_phone", "auth_mfa_web_authn", "log_drain", "etl_pipeline"]), "name": Schema.String, "variants": Schema.Array(Schema.Struct({ "id": Schema.Union([Schema.Literals(["ci_micro", "ci_small", "ci_medium", "ci_large", "ci_xlarge", "ci_2xlarge", "ci_4xlarge", "ci_8xlarge", "ci_12xlarge", "ci_16xlarge", "ci_24xlarge", "ci_24xlarge_optimized_cpu", "ci_24xlarge_optimized_memory", "ci_24xlarge_high_memory", "ci_48xlarge", "ci_48xlarge_optimized_cpu", "ci_48xlarge_optimized_memory", "ci_48xlarge_high_memory"]), Schema.Literal("cd_default"), Schema.Literals(["pitr_7", "pitr_14", "pitr_28"]), Schema.Literal("ipv4_default"), Schema.Literal("auth_mfa_phone_default"), Schema.Literal("auth_mfa_web_authn_default"), Schema.Literal("log_drain_default"), Schema.Literal("etl_pipeline_default")]), "name": Schema.String, "price": Schema.Struct({ "description": Schema.String, "type": Schema.Literals(["fixed", "usage"]), "interval": Schema.Literals(["monthly", "hourly"]), "amount": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) }), "meta": Schema.optionalKey(ListProjectAddonsResponseJsonValue) })) })) }) -export const V1ListProjectTpaIntegrationsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ListProjectTpaIntegrationsOutput = Schema.Array(ThirdPartyAuth) -export const V1MergeABranchInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]), "migration_version": Schema.optionalKey(Schema.String) }) -export const V1MergeABranchOutput = Schema.Struct({ "workflow_run_id": Schema.String, "message": Schema.Literal("ok") }) -export const V1ModifyDatabaseDiskInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "attributes": Schema.Union([Schema.Struct({ "iops": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "size_gb": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "throughput_mibps": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" }))), "type": Schema.Literal("gp3") }), Schema.Struct({ "iops": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "size_gb": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })).check(Schema.isGreaterThan(0).annotate({ "expected": "a value greater than 0" })), "type": Schema.Literal("io2") })], { mode: "oneOf" }) }) -export const V1OauthAuthorizeProjectClaimInput = Schema.Struct({ "project_ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "client_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "response_type": Schema.Literals(["code", "token", "id_token token"]), "redirect_uri": Schema.String, "state": Schema.optionalKey(Schema.String), "response_mode": Schema.optionalKey(Schema.String), "code_challenge": Schema.optionalKey(Schema.String), "code_challenge_method": Schema.optionalKey(Schema.Literals(["plain", "sha256", "S256"])) }) -export const V1PatchAMigrationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "version": Schema.String.check(Schema.isPattern(new RegExp("^\\d+$")).annotate({ "expected": "a string matching the RegExp ^\\d+$" })), "name": Schema.optionalKey(Schema.String), "rollback": Schema.optionalKey(Schema.String) }) -export const V1PatchNetworkRestrictionsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "add": Schema.optionalKey(Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.String)), "dbAllowedCidrsV6": Schema.optionalKey(Schema.Array(Schema.String)) })), "remove": Schema.optionalKey(Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.String)), "dbAllowedCidrsV6": Schema.optionalKey(Schema.Array(Schema.String)) })) }) -export const V1PatchNetworkRestrictionsOutput = Schema.Struct({ "entitlement": Schema.Literals(["disallowed", "allowed"]), "config": Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "address": Schema.String, "type": Schema.Literals(["v4", "v6"]) }))) }).annotate({ "description": "At any given point in time, this is the config that the user has requested be applied to their project. The `status` field indicates if it has been applied to the project, or is pending. When an updated config is received, the applied config is moved to `old_config`." }), "old_config": Schema.optionalKey(Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "address": Schema.String, "type": Schema.Literals(["v4", "v6"]) }))) }).annotate({ "description": "Populated when a new config has been received, but not registered as successfully applied to a project." })), "updated_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "applied_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "status": Schema.Literals(["stored", "applied"]) }) -export const V1PauseAProjectInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1PushABranchInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]), "migration_version": Schema.optionalKey(Schema.String) }) -export const V1PushABranchOutput = Schema.Struct({ "workflow_run_id": Schema.String, "message": Schema.Literal("ok") }) -export const V1ReadOnlyQueryInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "query": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "parameters": Schema.optionalKey(Schema.Array(Schema.Json.annotate({ "expected": "JSON value" }))) }) -export const V1RemoveAReadReplicaInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "database_identifier": Schema.String }) -export const V1RemoveProjectAddonInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "addon_variant": Schema.Union([Schema.Literals(["ci_micro", "ci_small", "ci_medium", "ci_large", "ci_xlarge", "ci_2xlarge", "ci_4xlarge", "ci_8xlarge", "ci_12xlarge", "ci_16xlarge", "ci_24xlarge", "ci_24xlarge_optimized_cpu", "ci_24xlarge_optimized_memory", "ci_24xlarge_high_memory", "ci_48xlarge", "ci_48xlarge_optimized_cpu", "ci_48xlarge_optimized_memory", "ci_48xlarge_high_memory"]), Schema.Literal("cd_default"), Schema.Literals(["pitr_7", "pitr_14", "pitr_28"]), Schema.Literal("ipv4_default")]) }) -export const V1RemoveProjectSigningKeyInput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1RemoveProjectSigningKeyOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]), "public_jwk": Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null]), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }) }) -export const V1ResetABranchInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]), "migration_version": Schema.optionalKey(Schema.String) }) -export const V1ResetABranchOutput = Schema.Struct({ "workflow_run_id": Schema.String, "message": Schema.Literal("ok") }) -export const V1RestartAProjectInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1RestoreABranchInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]) }) -export const V1RestoreABranchOutput = Schema.Struct({ "message": Schema.Literal("Branch restoration initiated") }) -export const V1RestoreAProjectInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1RestorePhysicalBackupInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "id": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }) -export const V1RestorePitrBackupInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "recovery_time_target_unix": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }) -export const V1RevokeTokenInput = Schema.Struct({ "client_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "client_secret": Schema.String, "refresh_token": Schema.String }) -export const V1RollbackMigrationsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "gte": Schema.String.check(Schema.isPattern(new RegExp("^\\d+$")).annotate({ "expected": "a string matching the RegExp ^\\d+$" })) }) -export const V1RunAQueryInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "query": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "parameters": Schema.optionalKey(Schema.Array(Schema.Json.annotate({ "expected": "JSON value" }))), "read_only": Schema.optionalKey(Schema.Boolean) }) -export const V1ScrapeProjectMetricsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1ScrapeProjectMetricsOutput = Schema.String -export const V1SetupAReadReplicaInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "read_replica_region": Schema.Literals(["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-east-1", "ap-southeast-1", "ap-northeast-1", "ap-northeast-2", "ap-southeast-2", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "eu-central-1", "eu-central-2", "ca-central-1", "ap-south-1", "sa-east-1"]).annotate({ "description": "Region you want your read replica to reside in" }) }) -export const V1ShutdownRealtimeInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1UndoInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String.check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })) }) -export const V1UpdateABranchConfigInput = Schema.Struct({ "branch_id_or_ref": Schema.Union([Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))]), "branch_name": Schema.optionalKey(Schema.String), "git_branch": Schema.optionalKey(Schema.String), "reset_on_push": Schema.optionalKey(Schema.Boolean.annotate({ "description": "This field is deprecated and will be ignored. Use v1-reset-a-branch endpoint directly instead." })), "persistent": Schema.optionalKey(Schema.Boolean), "status": Schema.optionalKey(Schema.Literals(["CREATING_PROJECT", "RUNNING_MIGRATIONS", "MIGRATIONS_PASSED", "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED"])), "request_review": Schema.optionalKey(Schema.Boolean), "notify_url": Schema.optionalKey(Schema.String.annotate({ "description": "HTTP endpoint to receive branch status updates.", "format": "uri" })) }) -export const V1UpdateABranchConfigOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "name": Schema.String, "project_ref": Schema.String, "parent_project_ref": Schema.String, "is_default": Schema.Boolean, "git_branch": Schema.optionalKey(Schema.String), "pr_number": Schema.optionalKey(Schema.Number.annotate({ "format": "int32" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "latest_check_run_id": Schema.optionalKey(Schema.Number.annotate({ "description": "This field is deprecated and will not be populated." }).check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "persistent": Schema.Boolean, "status": Schema.Literals(["CREATING_PROJECT", "RUNNING_MIGRATIONS", "MIGRATIONS_PASSED", "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED"]).annotate({ "description": "This field is deprecated. List action runs to get branch status instead." }), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }), "review_requested_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "with_data": Schema.Boolean, "notify_url": Schema.optionalKey(Schema.String.annotate({ "format": "uri" })), "deletion_scheduled_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "preview_project_status": Schema.optionalKey(Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"])) }) -export const V1UpdateAFunctionInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "function_slug": Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z0-9_-]+$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z0-9_-]+$" })), "slug": Schema.optionalKey(Schema.String.check(Schema.isPattern(new RegExp("^[A-Za-z0-9_-]+$")).annotate({ "expected": "a string matching the RegExp ^[A-Za-z0-9_-]+$" }))), "name": Schema.optionalKey(Schema.String), "verify_jwt": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "import_map": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.String), "ezbr_sha256": Schema.optionalKey(Schema.String), "body": BinaryInput }) -export const V1UpdateAFunctionOutput = Schema.Struct({ "id": Schema.String, "slug": Schema.String, "name": Schema.String, "status": Schema.Literals(["ACTIVE", "REMOVED", "THROTTLED"]), "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "created_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "updated_at": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "verify_jwt": Schema.optionalKey(Schema.Boolean), "import_map": Schema.optionalKey(Schema.Boolean), "entrypoint_path": Schema.optionalKey(Schema.String), "import_map_path": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "ezbr_sha256": Schema.optionalKey(Schema.String) }) -export const V1UpdateAProjectInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) }) -export const V1UpdateAProjectOutput = Schema.Struct({ "id": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "ref": Schema.String, "name": Schema.String }) -export const V1UpdateASsoProviderInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "provider_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "metadata_xml": Schema.optionalKey(Schema.String), "metadata_url": Schema.optionalKey(Schema.String), "domains": Schema.optionalKey(Schema.Array(Schema.String)), "attribute_mapping": Schema.optionalKey(Schema.Struct({ "keys": Schema.Record(Schema.String, Schema.Struct({ "name": Schema.optionalKey(Schema.String), "names": Schema.optionalKey(Schema.Array(Schema.String)), "default": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), "array": Schema.optionalKey(Schema.Boolean) })) })), "name_id_format": Schema.optionalKey(Schema.Literals(["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"])) }) -export const V1UpdateASsoProviderOutput = Schema.Struct({ "id": Schema.String, "saml": Schema.optionalKey(Schema.Struct({ "entity_id": Schema.String, "metadata_url": Schema.optionalKey(Schema.String), "metadata_xml": Schema.optionalKey(Schema.String), "attribute_mapping": Schema.optionalKey(Schema.Struct({ "keys": Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ "name": Schema.optionalKey(Schema.String), "names": Schema.optionalKey(Schema.Array(Schema.String)), "default": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), "array": Schema.optionalKey(Schema.Boolean) }))) })), "name_id_format": Schema.optionalKey(Schema.Literals(["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"])) })), "domains": Schema.optionalKey(Schema.Array(Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }))), "created_at": Schema.optionalKey(Schema.String), "updated_at": Schema.optionalKey(Schema.String) }) -export const V1UpdateActionRunStatusInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "run_id": Schema.String, "clone": Schema.optionalKey(Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"])), "pull": Schema.optionalKey(Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"])), "health": Schema.optionalKey(Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"])), "configure": Schema.optionalKey(Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"])), "migrate": Schema.optionalKey(Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"])), "seed": Schema.optionalKey(Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"])), "deploy": Schema.optionalKey(Schema.Literals(["CREATED", "DEAD", "EXITED", "PAUSED", "REMOVING", "RESTARTING", "RUNNING"])) }) -export const V1UpdateActionRunStatusOutput = Schema.Struct({ "message": Schema.Literal("ok") }) -export const V1UpdateAuthServiceConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "site_url": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isPattern(new RegExp("^[^,]+$")).annotate({ "expected": "a string matching the RegExp ^[^,]+$" })), Schema.Null])), "disable_signup": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "jwt_exp": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(604800).annotate({ "expected": "a value less than or equal to 604800" })), Schema.Null])), "smtp_admin_email": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })), Schema.Null])), "smtp_host": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "smtp_port": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "smtp_user": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "smtp_pass": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "smtp_max_frequency": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(32767).annotate({ "expected": "a value less than or equal to 32767" })), Schema.Null])), "smtp_sender_name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_allow_unverified_email_sign_ins": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_autoconfirm": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_subjects_invite": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_confirmation": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_recovery": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_email_change": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_magic_link": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_reauthentication": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_password_changed_notification": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_email_changed_notification": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_phone_changed_notification": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_mfa_factor_enrolled_notification": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_mfa_factor_unenrolled_notification": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_identity_linked_notification": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_subjects_identity_unlinked_notification": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_invite_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_confirmation_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_recovery_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_email_change_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_magic_link_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_reauthentication_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_password_changed_notification_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_email_changed_notification_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_phone_changed_notification_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_mfa_factor_enrolled_notification_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_mfa_factor_unenrolled_notification_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_identity_linked_notification_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_templates_identity_unlinked_notification_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mailer_notifications_password_changed_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_notifications_email_changed_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_notifications_phone_changed_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_notifications_mfa_factor_enrolled_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_notifications_mfa_factor_unenrolled_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_notifications_identity_linked_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mailer_notifications_identity_unlinked_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mfa_max_enrolled_factors": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "uri_allow_list": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_anonymous_users_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_email_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_phone_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "saml_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "saml_external_url": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isPattern(new RegExp("^[^,]+$")).annotate({ "expected": "a string matching the RegExp ^[^,]+$" })), Schema.Null])), "security_sb_forwarded_for_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "security_captcha_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "security_captcha_provider": Schema.optionalKey(Schema.Union([Schema.Literal("turnstile"), Schema.Literal("hcaptcha"), Schema.Null])), "security_captcha_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sessions_timebox": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })), Schema.Null])), "sessions_inactivity_timeout": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })), Schema.Null])), "sessions_single_per_user": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "sessions_tags": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isPattern(new RegExp("^(?:\\s*|\\s*[a-zA-Z0-9_-]+(?:\\s*,+\\s*[a-zA-Z0-9_-]+)*(?:\\s*,+)?\\s*)$")).annotate({ "expected": "a string matching the RegExp ^(?:\\s*|\\s*[a-zA-Z0-9_-]+(?:\\s*,+\\s*[a-zA-Z0-9_-]+)*(?:\\s*,+)?\\s*)$" })), Schema.Null])), "rate_limit_anonymous_users": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "rate_limit_email_sent": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "rate_limit_sms_sent": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "rate_limit_verify": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "rate_limit_token_refresh": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "rate_limit_otp": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "rate_limit_web3": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "mailer_secure_email_change_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "refresh_token_rotation_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "password_hibp_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "password_min_length": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(6).annotate({ "expected": "a value greater than or equal to 6" })).check(Schema.isLessThanOrEqualTo(32767).annotate({ "expected": "a value less than or equal to 32767" })), Schema.Null])), "password_required_characters": Schema.optionalKey(Schema.Union([Schema.Literal("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789"), Schema.Literal("abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789"), Schema.Literal("abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~"), Schema.Literal(""), Schema.Null])), "security_manual_linking_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "security_update_password_require_reauthentication": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "security_refresh_token_reuse_interval": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "mailer_otp_exp": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" }))), "mailer_otp_length": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(6).annotate({ "expected": "a value greater than or equal to 6" })).check(Schema.isLessThanOrEqualTo(10).annotate({ "expected": "a value less than or equal to 10" })), Schema.Null])), "sms_autoconfirm": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "sms_max_frequency": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(32767).annotate({ "expected": "a value less than or equal to 32767" })), Schema.Null])), "sms_otp_exp": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(2147483647).annotate({ "expected": "a value less than or equal to 2147483647" })), Schema.Null])), "sms_otp_length": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(32767).annotate({ "expected": "a value less than or equal to 32767" }))), "sms_provider": Schema.optionalKey(Schema.Union([Schema.Literal("messagebird"), Schema.Literal("textlocal"), Schema.Literal("twilio"), Schema.Literal("twilio_verify"), Schema.Literal("vonage"), Schema.Null])), "sms_messagebird_access_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_messagebird_originator": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_test_otp": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isPattern(new RegExp("^(?:[0-9]{1,15}=(?:[0-9]+,[0-9]{1,15}=|[0-9]{2,}=)*[0-9]+,?)?$")).annotate({ "expected": "a string matching the RegExp ^(?:[0-9]{1,15}=(?:[0-9]+,[0-9]{1,15}=|[0-9]{2,}=)*[0-9]+,?)?$" })), Schema.Null])), "sms_test_otp_valid_until": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])), "sms_textlocal_api_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_textlocal_sender": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_twilio_account_sid": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_twilio_auth_token": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_twilio_content_sid": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_twilio_message_service_sid": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_twilio_verify_account_sid": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_twilio_verify_auth_token": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_twilio_verify_message_service_sid": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_vonage_api_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_vonage_api_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_vonage_from": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "sms_template": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_mfa_verification_attempt_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "hook_mfa_verification_attempt_uri": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_mfa_verification_attempt_secrets": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_password_verification_attempt_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "hook_password_verification_attempt_uri": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_password_verification_attempt_secrets": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_custom_access_token_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "hook_custom_access_token_uri": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_custom_access_token_secrets": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_send_sms_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "hook_send_sms_uri": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_send_sms_secrets": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_send_email_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "hook_send_email_uri": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_send_email_secrets": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_before_user_created_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "hook_before_user_created_uri": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_before_user_created_secrets": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_after_user_created_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "hook_after_user_created_uri": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hook_after_user_created_secrets": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_apple_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_apple_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_apple_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_apple_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_apple_additional_client_ids": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_azure_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_azure_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_azure_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_azure_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_azure_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_bitbucket_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_bitbucket_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_bitbucket_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_bitbucket_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_discord_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_discord_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_discord_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_discord_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_facebook_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_facebook_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_facebook_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_facebook_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_figma_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_figma_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_figma_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_figma_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_github_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_github_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_github_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_github_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_gitlab_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_gitlab_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_gitlab_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_gitlab_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_gitlab_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_google_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_google_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_google_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_google_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_google_additional_client_ids": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_google_skip_nonce_check": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_kakao_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_kakao_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_kakao_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_kakao_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_keycloak_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_keycloak_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_keycloak_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_keycloak_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_keycloak_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_linkedin_oidc_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_linkedin_oidc_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_linkedin_oidc_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_linkedin_oidc_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_slack_oidc_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_slack_oidc_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_slack_oidc_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_slack_oidc_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_notion_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_notion_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_notion_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_notion_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_slack_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_slack_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_slack_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_slack_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_spotify_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_spotify_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_spotify_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_spotify_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_twitch_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_twitch_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_twitch_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_twitch_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_twitter_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_twitter_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_twitter_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_twitter_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_x_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_x_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_x_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_x_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_workos_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_workos_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_workos_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_workos_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_web3_solana_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_web3_ethereum_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_zoom_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_zoom_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "external_zoom_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "external_zoom_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "db_max_pool_size": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null])), "db_max_pool_size_unit": Schema.optionalKey(Schema.Union([Schema.Literal("connections"), Schema.Literal("percent"), Schema.Null])), "api_max_request_duration": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null])), "mfa_totp_enroll_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mfa_totp_verify_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mfa_web_authn_enroll_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mfa_web_authn_verify_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "passkey_enabled": Schema.optionalKey(Schema.Boolean), "webauthn_rp_display_name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "webauthn_rp_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "webauthn_rp_origins": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "mfa_phone_enroll_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mfa_phone_verify_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "mfa_phone_max_frequency": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(32767).annotate({ "expected": "a value less than or equal to 32767" })), Schema.Null])), "mfa_phone_otp_length": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(32767).annotate({ "expected": "a value less than or equal to 32767" })), Schema.Null])), "mfa_phone_template": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "nimbus_oauth_client_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "nimbus_oauth_client_secret": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "oauth_server_enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "oauth_server_allow_dynamic_registration": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "oauth_server_authorization_path": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "custom_oauth_enabled": Schema.optionalKey(Schema.Boolean) }) -export const V1UpdateAuthServiceConfigOutput = Schema.Struct({ "api_max_request_duration": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "db_max_pool_size": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "db_max_pool_size_unit": Schema.Union([Schema.Literal("connections"), Schema.Literal("percent"), Schema.Null]), "disable_signup": Schema.Union([Schema.Boolean, Schema.Null]), "external_anonymous_users_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_apple_additional_client_ids": Schema.Union([Schema.String, Schema.Null]), "external_apple_client_id": Schema.Union([Schema.String, Schema.Null]), "external_apple_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_apple_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_apple_secret": Schema.Union([Schema.String, Schema.Null]), "external_azure_client_id": Schema.Union([Schema.String, Schema.Null]), "external_azure_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_azure_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_azure_secret": Schema.Union([Schema.String, Schema.Null]), "external_azure_url": Schema.Union([Schema.String, Schema.Null]), "external_bitbucket_client_id": Schema.Union([Schema.String, Schema.Null]), "external_bitbucket_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_bitbucket_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_bitbucket_secret": Schema.Union([Schema.String, Schema.Null]), "external_discord_client_id": Schema.Union([Schema.String, Schema.Null]), "external_discord_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_discord_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_discord_secret": Schema.Union([Schema.String, Schema.Null]), "external_email_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_facebook_client_id": Schema.Union([Schema.String, Schema.Null]), "external_facebook_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_facebook_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_facebook_secret": Schema.Union([Schema.String, Schema.Null]), "external_figma_client_id": Schema.Union([Schema.String, Schema.Null]), "external_figma_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_figma_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_figma_secret": Schema.Union([Schema.String, Schema.Null]), "external_github_client_id": Schema.Union([Schema.String, Schema.Null]), "external_github_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_github_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_github_secret": Schema.Union([Schema.String, Schema.Null]), "external_gitlab_client_id": Schema.Union([Schema.String, Schema.Null]), "external_gitlab_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_gitlab_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_gitlab_secret": Schema.Union([Schema.String, Schema.Null]), "external_gitlab_url": Schema.Union([Schema.String, Schema.Null]), "external_google_additional_client_ids": Schema.Union([Schema.String, Schema.Null]), "external_google_client_id": Schema.Union([Schema.String, Schema.Null]), "external_google_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_google_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_google_secret": Schema.Union([Schema.String, Schema.Null]), "external_google_skip_nonce_check": Schema.Union([Schema.Boolean, Schema.Null]), "external_kakao_client_id": Schema.Union([Schema.String, Schema.Null]), "external_kakao_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_kakao_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_kakao_secret": Schema.Union([Schema.String, Schema.Null]), "external_keycloak_client_id": Schema.Union([Schema.String, Schema.Null]), "external_keycloak_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_keycloak_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_keycloak_secret": Schema.Union([Schema.String, Schema.Null]), "external_keycloak_url": Schema.Union([Schema.String, Schema.Null]), "external_linkedin_oidc_client_id": Schema.Union([Schema.String, Schema.Null]), "external_linkedin_oidc_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_linkedin_oidc_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_linkedin_oidc_secret": Schema.Union([Schema.String, Schema.Null]), "external_slack_oidc_client_id": Schema.Union([Schema.String, Schema.Null]), "external_slack_oidc_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_oidc_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_oidc_secret": Schema.Union([Schema.String, Schema.Null]), "external_notion_client_id": Schema.Union([Schema.String, Schema.Null]), "external_notion_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_notion_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_notion_secret": Schema.Union([Schema.String, Schema.Null]), "external_phone_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_client_id": Schema.Union([Schema.String, Schema.Null]), "external_slack_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_slack_secret": Schema.Union([Schema.String, Schema.Null]), "external_spotify_client_id": Schema.Union([Schema.String, Schema.Null]), "external_spotify_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_spotify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_spotify_secret": Schema.Union([Schema.String, Schema.Null]), "external_twitch_client_id": Schema.Union([Schema.String, Schema.Null]), "external_twitch_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitch_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitch_secret": Schema.Union([Schema.String, Schema.Null]), "external_twitter_client_id": Schema.Union([Schema.String, Schema.Null]), "external_twitter_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitter_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_twitter_secret": Schema.Union([Schema.String, Schema.Null]), "external_x_client_id": Schema.Union([Schema.String, Schema.Null]), "external_x_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_x_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_x_secret": Schema.Union([Schema.String, Schema.Null]), "external_workos_client_id": Schema.Union([Schema.String, Schema.Null]), "external_workos_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_workos_secret": Schema.Union([Schema.String, Schema.Null]), "external_workos_url": Schema.Union([Schema.String, Schema.Null]), "external_web3_solana_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_web3_ethereum_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_zoom_client_id": Schema.Union([Schema.String, Schema.Null]), "external_zoom_email_optional": Schema.Union([Schema.Boolean, Schema.Null]), "external_zoom_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "external_zoom_secret": Schema.Union([Schema.String, Schema.Null]), "hook_custom_access_token_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_custom_access_token_uri": Schema.Union([Schema.String, Schema.Null]), "hook_custom_access_token_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_mfa_verification_attempt_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_mfa_verification_attempt_uri": Schema.Union([Schema.String, Schema.Null]), "hook_mfa_verification_attempt_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_password_verification_attempt_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_password_verification_attempt_uri": Schema.Union([Schema.String, Schema.Null]), "hook_password_verification_attempt_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_send_sms_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_send_sms_uri": Schema.Union([Schema.String, Schema.Null]), "hook_send_sms_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_send_email_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_send_email_uri": Schema.Union([Schema.String, Schema.Null]), "hook_send_email_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_before_user_created_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_before_user_created_uri": Schema.Union([Schema.String, Schema.Null]), "hook_before_user_created_secrets": Schema.Union([Schema.String, Schema.Null]), "hook_after_user_created_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "hook_after_user_created_uri": Schema.Union([Schema.String, Schema.Null]), "hook_after_user_created_secrets": Schema.Union([Schema.String, Schema.Null]), "jwt_exp": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "mailer_allow_unverified_email_sign_ins": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_autoconfirm": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_otp_exp": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "mailer_otp_length": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "mailer_secure_email_change_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_subjects_confirmation": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_email_change": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_invite": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_magic_link": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_reauthentication": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_recovery": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_password_changed_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_email_changed_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_phone_changed_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_mfa_factor_enrolled_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_mfa_factor_unenrolled_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_identity_linked_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_subjects_identity_unlinked_notification": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_confirmation_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_email_change_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_invite_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_magic_link_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_reauthentication_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_recovery_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_password_changed_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_email_changed_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_phone_changed_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_mfa_factor_enrolled_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_mfa_factor_unenrolled_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_identity_linked_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_templates_identity_unlinked_notification_content": Schema.Union([Schema.String, Schema.Null]), "mailer_notifications_password_changed_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_email_changed_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_phone_changed_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_mfa_factor_enrolled_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_mfa_factor_unenrolled_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_identity_linked_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mailer_notifications_identity_unlinked_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_max_enrolled_factors": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "mfa_totp_enroll_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_totp_verify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_phone_enroll_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_phone_verify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_web_authn_enroll_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "mfa_web_authn_verify_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "passkey_enabled": Schema.Boolean, "webauthn_rp_display_name": Schema.Union([Schema.String, Schema.Null]), "webauthn_rp_id": Schema.Union([Schema.String, Schema.Null]), "webauthn_rp_origins": Schema.Union([Schema.String, Schema.Null]), "mfa_phone_otp_length": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "mfa_phone_template": Schema.Union([Schema.String, Schema.Null]), "mfa_phone_max_frequency": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "nimbus_oauth_client_id": Schema.Union([Schema.String, Schema.Null]), "nimbus_oauth_email_optional": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), "nimbus_oauth_client_secret": Schema.Union([Schema.String, Schema.Null]), "password_hibp_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "password_min_length": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "password_required_characters": Schema.Union([Schema.Literal("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789"), Schema.Literal("abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789"), Schema.Literal("abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~"), Schema.Literal(""), Schema.Null]), "rate_limit_anonymous_users": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_email_sent": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_sms_sent": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_token_refresh": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_verify": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_otp": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "rate_limit_web3": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "refresh_token_rotation_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "saml_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "saml_external_url": Schema.Union([Schema.String, Schema.Null]), "saml_allow_encrypted_assertions": Schema.Union([Schema.Boolean, Schema.Null]), "security_sb_forwarded_for_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "security_captcha_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "security_captcha_provider": Schema.Union([Schema.Literal("turnstile"), Schema.Literal("hcaptcha"), Schema.Null]), "security_captcha_secret": Schema.Union([Schema.String, Schema.Null]), "security_manual_linking_enabled": Schema.Union([Schema.Boolean, Schema.Null]), "security_refresh_token_reuse_interval": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "security_update_password_require_reauthentication": Schema.Union([Schema.Boolean, Schema.Null]), "sessions_inactivity_timeout": Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]), "sessions_single_per_user": Schema.Union([Schema.Boolean, Schema.Null]), "sessions_tags": Schema.Union([Schema.String, Schema.Null]), "sessions_timebox": Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]), "site_url": Schema.Union([Schema.String, Schema.Null]), "sms_autoconfirm": Schema.Union([Schema.Boolean, Schema.Null]), "sms_max_frequency": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "sms_messagebird_access_key": Schema.Union([Schema.String, Schema.Null]), "sms_messagebird_originator": Schema.Union([Schema.String, Schema.Null]), "sms_otp_exp": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "sms_otp_length": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "sms_provider": Schema.Union([Schema.Literal("messagebird"), Schema.Literal("textlocal"), Schema.Literal("twilio"), Schema.Literal("twilio_verify"), Schema.Literal("vonage"), Schema.Null]), "sms_template": Schema.Union([Schema.String, Schema.Null]), "sms_test_otp": Schema.Union([Schema.String, Schema.Null]), "sms_test_otp_valid_until": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]), "sms_textlocal_api_key": Schema.Union([Schema.String, Schema.Null]), "sms_textlocal_sender": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_account_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_auth_token": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_content_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_message_service_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_verify_account_sid": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_verify_auth_token": Schema.Union([Schema.String, Schema.Null]), "sms_twilio_verify_message_service_sid": Schema.Union([Schema.String, Schema.Null]), "sms_vonage_api_key": Schema.Union([Schema.String, Schema.Null]), "sms_vonage_api_secret": Schema.Union([Schema.String, Schema.Null]), "sms_vonage_from": Schema.Union([Schema.String, Schema.Null]), "smtp_admin_email": Schema.Union([Schema.String.annotate({ "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })), Schema.Null]), "smtp_host": Schema.Union([Schema.String, Schema.Null]), "smtp_max_frequency": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "smtp_pass": Schema.Union([Schema.String, Schema.Null]), "smtp_port": Schema.Union([Schema.String, Schema.Null]), "smtp_sender_name": Schema.Union([Schema.String, Schema.Null]), "smtp_user": Schema.Union([Schema.String, Schema.Null]), "uri_allow_list": Schema.Union([Schema.String, Schema.Null]), "oauth_server_enabled": Schema.Boolean, "oauth_server_allow_dynamic_registration": Schema.Boolean, "oauth_server_authorization_path": Schema.Union([Schema.String, Schema.Null]), "custom_oauth_enabled": Schema.Boolean, "custom_oauth_max_providers": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }) -export const V1UpdateBackupScheduleInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "schedule_for": Schema.String.annotate({ "description": "Time of day to schedule daily backups, in UTC. Format: HH:MM:SS." }).check(Schema.isPattern(new RegExp("^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$")).annotate({ "expected": "a string matching the RegExp ^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$" })) }) -export const V1UpdateBackupScheduleOutput = Schema.Struct({ "schedule_for": Schema.String.annotate({ "description": "Time of day to schedule daily backups, in UTC. Format: HH:MM:SS." }).check(Schema.isPattern(new RegExp("^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$")).annotate({ "expected": "a string matching the RegExp ^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$" })), "updated_at": Schema.String.annotate({ "description": "Timestamp of when the backup schedule was last updated.", "format": "date-time" }) }) -export const V1UpdateDatabasePasswordInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "password": Schema.String.check(Schema.isMinLength(4).annotate({ "expected": "a value with a length of at least 4" })) }) -export const V1UpdateDatabasePasswordOutput = Schema.Struct({ "message": Schema.String }) -export const V1UpdateHostnameConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "custom_hostname": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isMaxLength(253).annotate({ "expected": "a value with a length of at most 253" })) }) -export const V1UpdateHostnameConfigOutput = Schema.Struct({ "status": Schema.optionalKey(Schema.Literals(["1_not_started", "2_initiated", "3_challenge_verified", "4_origin_setup_completed", "5_services_reconfigured"])), "custom_hostname": Schema.optionalKey(Schema.String), "data": Schema.Struct({ "success": Schema.Boolean, "errors": Schema.Array(UpdateCustomHostnameResponseJsonValue), "messages": Schema.Array(UpdateCustomHostnameResponseJsonValue), "result": Schema.Struct({ "id": Schema.String, "hostname": Schema.String, "ssl": Schema.Struct({ "status": Schema.String, "validation_records": Schema.optionalKey(Schema.Array(Schema.Struct({ "txt_name": Schema.String, "txt_value": Schema.String }))), "validation_errors": Schema.optionalKey(Schema.Array(Schema.Struct({ "message": Schema.String }))) }), "ownership_verification": Schema.optionalKey(Schema.Struct({ "type": Schema.String, "name": Schema.String, "value": Schema.String })), "custom_origin_server": Schema.String, "verification_errors": Schema.optionalKey(Schema.Array(Schema.String)), "status": Schema.String }) }) }) -export const V1UpdateJitAccessInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "user_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) }) -export const V1UpdateJitAccessOutput = Schema.Struct({ "user_id": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "user_roles": Schema.Array(Schema.Struct({ "role": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "expires_at": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "allowed_networks": Schema.optionalKey(Schema.Struct({ "allowed_cidrs": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv4" }).check(Schema.isPattern(new RegExp("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$")).annotate({ "expected": "a string matching the RegExp ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$" })) }))), "allowed_cidrs_v6": Schema.optionalKey(Schema.Array(Schema.Struct({ "cidr": Schema.String.annotate({ "format": "cidrv6" }).check(Schema.isPattern(new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$")).annotate({ "expected": "a string matching the RegExp ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" })) }))) })), "branches_only": Schema.optionalKey(Schema.Boolean) })) }) -export const V1UpdateJitAccessConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "state": Schema.Literals(["enabled", "disabled"]) }) -export const V1UpdateJitAccessConfigOutput = Schema.Union([Schema.Struct({ "state": Schema.Literals(["enabled", "disabled"]), "appliedSuccessfully": Schema.optionalKey(Schema.Boolean) }), Schema.Struct({ "state": Schema.Literal("unavailable"), "unavailableReason": Schema.Literals(["postgres_upgrade_required", "ssl_enforcement_required", "temporarily_unavailable"]) })], { mode: "oneOf" }) -export const V1UpdateNetworkRestrictionsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.String)), "dbAllowedCidrsV6": Schema.optionalKey(Schema.Array(Schema.String)) }) -export const V1UpdateNetworkRestrictionsOutput = Schema.Struct({ "entitlement": Schema.Literals(["disallowed", "allowed"]), "config": Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.String)), "dbAllowedCidrsV6": Schema.optionalKey(Schema.Array(Schema.String)) }).annotate({ "description": "At any given point in time, this is the config that the user has requested be applied to their project. The `status` field indicates if it has been applied to the project, or is pending. When an updated config is received, the applied config is moved to `old_config`." }), "old_config": Schema.optionalKey(Schema.Struct({ "dbAllowedCidrs": Schema.optionalKey(Schema.Array(Schema.String)), "dbAllowedCidrsV6": Schema.optionalKey(Schema.Array(Schema.String)) }).annotate({ "description": "Populated when a new config has been received, but not registered as successfully applied to a project." })), "status": Schema.Literals(["stored", "applied"]), "updated_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })), "applied_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })) }) -export const V1UpdatePgsodiumConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "root_key": Schema.String.annotate({ "description": "The pgsodium root key: 32 bytes, hex-encoded (64 characters)." }) }) -export const V1UpdatePgsodiumConfigOutput = Schema.Struct({ "root_key": Schema.String.annotate({ "description": "The pgsodium root key: 32 bytes, hex-encoded (64 characters)." }) }) -export const V1UpdatePoolerConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "default_pool_size": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(3000).annotate({ "expected": "a value less than or equal to 3000" })), Schema.Null])), "pool_mode": Schema.optionalKey(Schema.Literals(["transaction", "session"]).annotate({ "description": "Dedicated pooler mode for the project" })) }) -export const V1UpdatePoolerConfigOutput = Schema.Struct({ "default_pool_size": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "pool_mode": Schema.String }) -export const V1UpdatePostgresConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "effective_cache_size": Schema.optionalKey(Schema.String), "logical_decoding_work_mem": Schema.optionalKey(Schema.String), "cron.log_statement": Schema.optionalKey(Schema.Boolean), "log_autovacuum_min_duration": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_checkpoints": Schema.optionalKey(Schema.Boolean), "log_connections": Schema.optionalKey(Schema.Boolean), "log_disconnections": Schema.optionalKey(Schema.Boolean), "log_duration": Schema.optionalKey(Schema.Boolean), "log_lock_waits": Schema.optionalKey(Schema.Boolean), "log_recovery_conflict_waits": Schema.optionalKey(Schema.Boolean), "log_replication_commands": Schema.optionalKey(Schema.Boolean), "log_startup_progress_interval": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_temp_files": Schema.optionalKey(Schema.String), "maintenance_work_mem": Schema.optionalKey(Schema.String), "track_activity_query_size": Schema.optionalKey(Schema.String), "max_connections": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_locks_per_transaction": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(10).annotate({ "expected": "a value greater than or equal to 10" })).check(Schema.isLessThanOrEqualTo(2147483640).annotate({ "expected": "a value less than or equal to 2147483640" }))), "max_logical_replication_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_parallel_maintenance_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers_per_gather": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_replication_slots": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_slot_wal_keep_size": Schema.optionalKey(Schema.String), "max_standby_archive_delay": Schema.optionalKey(Schema.String), "max_standby_streaming_delay": Schema.optionalKey(Schema.String), "max_sync_workers_per_subscription": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_wal_size": Schema.optionalKey(Schema.String), "max_wal_senders": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_worker_processes": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "session_replication_role": Schema.optionalKey(Schema.Literals(["origin", "replica", "local"])), "shared_buffers": Schema.optionalKey(Schema.String), "statement_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "track_commit_timestamp": Schema.optionalKey(Schema.Boolean), "wal_keep_size": Schema.optionalKey(Schema.String), "wal_sender_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "work_mem": Schema.optionalKey(Schema.String), "checkpoint_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: s" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "hot_standby_feedback": Schema.optionalKey(Schema.Boolean), "restart_database": Schema.optionalKey(Schema.Boolean) }) -export const V1UpdatePostgresConfigOutput = Schema.Struct({ "effective_cache_size": Schema.optionalKey(Schema.String), "logical_decoding_work_mem": Schema.optionalKey(Schema.String), "cron.log_statement": Schema.optionalKey(Schema.Boolean), "log_autovacuum_min_duration": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_checkpoints": Schema.optionalKey(Schema.Boolean), "log_connections": Schema.optionalKey(Schema.Boolean), "log_disconnections": Schema.optionalKey(Schema.Boolean), "log_duration": Schema.optionalKey(Schema.Boolean), "log_lock_waits": Schema.optionalKey(Schema.Boolean), "log_recovery_conflict_waits": Schema.optionalKey(Schema.Boolean), "log_replication_commands": Schema.optionalKey(Schema.Boolean), "log_startup_progress_interval": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_temp_files": Schema.optionalKey(Schema.String), "maintenance_work_mem": Schema.optionalKey(Schema.String), "track_activity_query_size": Schema.optionalKey(Schema.String), "max_connections": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_locks_per_transaction": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(10).annotate({ "expected": "a value greater than or equal to 10" })).check(Schema.isLessThanOrEqualTo(2147483640).annotate({ "expected": "a value less than or equal to 2147483640" }))), "max_logical_replication_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_parallel_maintenance_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers_per_gather": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_replication_slots": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_slot_wal_keep_size": Schema.optionalKey(Schema.String), "max_standby_archive_delay": Schema.optionalKey(Schema.String), "max_standby_streaming_delay": Schema.optionalKey(Schema.String), "max_sync_workers_per_subscription": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_wal_size": Schema.optionalKey(Schema.String), "max_wal_senders": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_worker_processes": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "session_replication_role": Schema.optionalKey(Schema.Literals(["origin", "replica", "local"])), "shared_buffers": Schema.optionalKey(Schema.String), "statement_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "track_commit_timestamp": Schema.optionalKey(Schema.Boolean), "wal_keep_size": Schema.optionalKey(Schema.String), "wal_sender_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "work_mem": Schema.optionalKey(Schema.String), "checkpoint_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: s" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "hot_standby_feedback": Schema.optionalKey(Schema.Boolean) }) -export const V1UpdatePostgrestServiceConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "db_extra_search_path": Schema.optionalKey(Schema.String), "db_schema": Schema.optionalKey(Schema.String), "max_rows": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1000000).annotate({ "expected": "a value less than or equal to 1000000" }))), "db_pool": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" }))), "db_pool_acquisition_timeout": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(60).annotate({ "expected": "a value less than or equal to 60" }))) }) -export const V1UpdatePostgrestServiceConfigOutput = Schema.Struct({ "db_schema": Schema.String, "max_rows": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "db_extra_search_path": Schema.String, "db_pool": Schema.Union([Schema.Number.annotate({ "description": "If `null`, the value is automatically configured based on compute size." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]), "db_pool_acquisition_timeout": Schema.Union([Schema.Number.annotate({ "description": "If `null`, the value is automatically configured to 10." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]) }) -export const V1UpdateProjectApiKeyInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "reveal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Boolean])), "name": Schema.optionalKey(Schema.String.check(Schema.isMinLength(4).annotate({ "expected": "a value with a length of at least 4" })).check(Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" })).check(Schema.isPattern(new RegExp("^[a-z_][a-z0-9_]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z_][a-z0-9_]+$" }))), "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "secret_jwt_template": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).check(Schema.isPropertyNames(Schema.String).annotate({ "expected": "an object with property names matching the schema" })), Schema.Null])) }) -export const V1UpdateProjectApiKeyOutput = Schema.Struct({ "api_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "type": Schema.optionalKey(Schema.Union([Schema.Literal("legacy"), Schema.Literal("publishable"), Schema.Literal("secret"), Schema.Null])), "prefix": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "name": Schema.String, "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "hash": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "secret_jwt_template": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).check(Schema.isPropertyNames(Schema.String).annotate({ "expected": "an object with property names matching the schema" })), Schema.Null])), "inserted_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])), "updated_at": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null])) }) -export const V1UpdateProjectLegacyApiKeysInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "enabled": Schema.Union([Schema.String, Schema.Boolean]) }) -export const V1UpdateProjectLegacyApiKeysOutput = Schema.Struct({ "enabled": Schema.Boolean }) -export const V1UpdateProjectSigningKeyInput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]) }) -export const V1UpdateProjectSigningKeyOutput = Schema.Struct({ "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "algorithm": Schema.Literals(["EdDSA", "ES256", "RS256", "HS256"]), "status": Schema.Literals(["in_use", "previously_used", "revoked", "standby"]), "public_jwk": Schema.Union([Schema.Json.annotate({ "expected": "JSON value" }), Schema.Null]), "created_at": Schema.String.annotate({ "format": "date-time" }), "updated_at": Schema.String.annotate({ "format": "date-time" }) }) -export const V1UpdateRealtimeConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "private_only": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether to only allow private channels" })), "connection_pool": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets connection pool size for Realtime Authorization" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }))), "postgres_changes_pool": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets connection pool size used to create Postgres Changes subscriptions" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }))), "max_concurrent_users": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets maximum number of concurrent users rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(50000).annotate({ "expected": "a value less than or equal to 50000" }))), "max_events_per_second": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets maximum number of events per second rate per channel limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(50000).annotate({ "expected": "a value less than or equal to 50000" }))), "max_bytes_per_second": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets maximum number of bytes per second rate per channel limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(10000000).annotate({ "expected": "a value less than or equal to 10000000" }))), "max_channels_per_client": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets maximum number of channels per client rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(10000).annotate({ "expected": "a value less than or equal to 10000" }))), "max_joins_per_second": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets maximum number of joins per second rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(5000).annotate({ "expected": "a value less than or equal to 5000" }))), "max_presence_events_per_second": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets maximum number of presence events per second rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(5000).annotate({ "expected": "a value less than or equal to 5000" }))), "max_payload_size_in_kb": Schema.optionalKey(Schema.Number.annotate({ "description": "Sets maximum number of payload size in KB rate limit" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(10000).annotate({ "expected": "a value less than or equal to 10000" }))), "suspend": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Disables the Realtime service for this project when true. Set to false to re-enable it." })), "presence_enabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether to enable presence" })) }) -export const V1UpdateSslEnforcementConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "requestedConfig": Schema.Struct({ "database": Schema.Boolean }) }) -export const V1UpdateSslEnforcementConfigOutput = Schema.Struct({ "currentConfig": Schema.Struct({ "database": Schema.Boolean }), "appliedSuccessfully": Schema.Boolean }) -export const V1UpdateStorageConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "fileSizeLimit": Schema.optionalKey(Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(536870912000).annotate({ "expected": "a value less than or equal to 536870912000" }))), "features": Schema.optionalKey(Schema.Struct({ "imageTransformation": Schema.optionalKey(Schema.Struct({ "enabled": Schema.Boolean })), "s3Protocol": Schema.optionalKey(Schema.Struct({ "enabled": Schema.Boolean })), "purgeCache": Schema.optionalKey(Schema.Struct({ "enabled": Schema.Boolean })), "icebergCatalog": Schema.optionalKey(Schema.Struct({ "enabled": Schema.Boolean, "maxNamespaces": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "maxTables": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "maxCatalogs": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) })), "vectorBuckets": Schema.optionalKey(Schema.Struct({ "enabled": Schema.Boolean, "maxBuckets": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "maxIndexes": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) })) })), "external": Schema.optionalKey(Schema.Struct({ "upstreamTarget": Schema.Literals(["main", "canary"]) })) }) -export const V1UpgradePostgresVersionInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "target_version": Schema.String, "release_channel": Schema.optionalKey(Schema.Literals(["internal", "alpha", "beta", "ga", "withdrawn", "preview"])) }) -export const V1UpgradePostgresVersionOutput = Schema.Struct({ "tracking_id": Schema.String }) -export const V1UpsertAMigrationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "Idempotency-Key": Schema.optionalKey(Schema.String), "query": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "name": Schema.optionalKey(Schema.String), "rollback": Schema.optionalKey(Schema.String) }) -export const V1VerifyDnsConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V1VerifyDnsConfigOutput = Schema.Struct({ "status": Schema.optionalKey(Schema.Literals(["1_not_started", "2_initiated", "3_challenge_verified", "4_origin_setup_completed", "5_services_reconfigured"])), "custom_hostname": Schema.optionalKey(Schema.String), "data": Schema.Struct({ "success": Schema.Boolean, "errors": Schema.Array(UpdateCustomHostnameResponseJsonValue), "messages": Schema.Array(UpdateCustomHostnameResponseJsonValue), "result": Schema.Struct({ "id": Schema.String, "hostname": Schema.String, "ssl": Schema.Struct({ "status": Schema.String, "validation_records": Schema.optionalKey(Schema.Array(Schema.Struct({ "txt_name": Schema.String, "txt_value": Schema.String }))), "validation_errors": Schema.optionalKey(Schema.Array(Schema.Struct({ "message": Schema.String }))) }), "ownership_verification": Schema.optionalKey(Schema.Struct({ "type": Schema.String, "name": Schema.String, "value": Schema.String })), "custom_origin_server": Schema.String, "verification_errors": Schema.optionalKey(Schema.Array(Schema.String)), "status": Schema.String }) }) }) -export const V2AssignOrganizationMemberRoleInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "user_id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "data": Schema.Struct({ "type": Schema.Literal("organization_member_role").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "role": Schema.Literals(["owner", "administrator", "developer", "read-only"]).annotate({ "description": "Role name to assign. Must be one of: owner, administrator, developer, read-only. Must be on a Team or Enterprise plan to use the read-only role." }), "projects": Schema.optionalKey(Schema.Array(Schema.Struct({ "ref": Schema.String.annotate({ "description": "Project ref" }) })).annotate({ "description": "The projects to assign a project-scoped role for. If omitted, assigns an org-wide role." }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }))) }) }) }) -export const V2AssignOrganizationMemberRoleOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("organization_member_role").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "name": Schema.String.annotate({ "description": "Role name. For project-scoped assignments this is the base role name." }), "scope": Schema.Literals(["organization", "project"]).annotate({ "description": "Whether this role applies org-wide or is scoped to specific projects for the user." }), "projects": Schema.Array(Schema.Struct({ "ref": Schema.String, "name": Schema.String })).annotate({ "description": "Project refs this role is scoped to. Empty array for org-level roles." }) }) }) }) -export const V2CreateLogDrainInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "data": Schema.Struct({ "type": Schema.Literal("log_drain").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "name": Schema.String, "description": Schema.optionalKey(Schema.String), "config": Schema.Union([Schema.Struct({ "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "schema": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "port": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null])), "hostname": Schema.optionalKey(Schema.String) }).annotate({ "title": "postgres" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "http": Schema.optionalKey(Schema.Literals(["http1", "http2"])), "gzip": Schema.optionalKey(Schema.Boolean), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "webhook" }), Schema.Struct({ "project_id": Schema.optionalKey(Schema.String), "dataset_id": Schema.optionalKey(Schema.String) }).annotate({ "title": "bigquery" }), Schema.Struct({ "api_key": Schema.optionalKey(Schema.String), "region": Schema.optionalKey(Schema.String) }).annotate({ "title": "datadog" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "loki" }), Schema.Struct({ "dsn": Schema.optionalKey(Schema.String) }).annotate({ "title": "sentry" }), Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "api_token": Schema.optionalKey(Schema.String), "dataset_name": Schema.optionalKey(Schema.String) }).annotate({ "title": "axiom" }), Schema.Struct({ "host": Schema.optionalKey(Schema.String), "port": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(65535).annotate({ "expected": "a value less than or equal to 65535" }))), "tls": Schema.optionalKey(Schema.Boolean), "structured_data": Schema.optionalKey(Schema.String), "cipher_key": Schema.optionalKey(Schema.String), "ca_cert": Schema.optionalKey(Schema.String), "client_cert": Schema.optionalKey(Schema.String), "client_key": Schema.optionalKey(Schema.String) }).annotate({ "title": "syslog" })]), "backend_type": Schema.Literals(["postgres", "bigquery", "clickhouse", "webhook", "datadog", "loki", "sentry", "s3", "axiom", "last9", "otlp", "syslog"]) }) }) }) -export const V2CreateLogDrainOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("log_drain").annotate({ "description": "Resource type." }), "id": Schema.String, "attributes": Schema.Struct({ "name": Schema.String, "description": Schema.optionalKey(Schema.String), "config": Schema.Union([Schema.Struct({ "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "schema": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "port": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null])), "hostname": Schema.optionalKey(Schema.String) }).annotate({ "title": "postgres" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "http": Schema.optionalKey(Schema.Literals(["http1", "http2"])), "gzip": Schema.optionalKey(Schema.Boolean), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "webhook" }), Schema.Struct({ "project_id": Schema.optionalKey(Schema.String), "dataset_id": Schema.optionalKey(Schema.String) }).annotate({ "title": "bigquery" }), Schema.Struct({ "api_key": Schema.optionalKey(Schema.String), "region": Schema.optionalKey(Schema.String) }).annotate({ "title": "datadog" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "loki" }), Schema.Struct({ "dsn": Schema.optionalKey(Schema.String) }).annotate({ "title": "sentry" }), Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "api_token": Schema.optionalKey(Schema.String), "dataset_name": Schema.optionalKey(Schema.String) }).annotate({ "title": "axiom" }), Schema.Struct({ "host": Schema.optionalKey(Schema.String), "port": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(65535).annotate({ "expected": "a value less than or equal to 65535" }))), "tls": Schema.optionalKey(Schema.Boolean), "structured_data": Schema.optionalKey(Schema.String), "cipher_key": Schema.optionalKey(Schema.String), "ca_cert": Schema.optionalKey(Schema.String), "client_cert": Schema.optionalKey(Schema.String), "client_key": Schema.optionalKey(Schema.String) }).annotate({ "title": "syslog" })]), "backend_type": Schema.Literals(["postgres", "bigquery", "clickhouse", "webhook", "datadog", "loki", "sentry", "s3", "axiom", "last9", "otlp", "syslog"]) }) }) }) -export const V2CreateOrganizationInvitationsInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("organization_invitation").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "email": Schema.String.annotate({ "description": "Email address of the invitation receipient.", "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })), "role": Schema.Literals(["owner", "administrator", "developer", "read-only"]).annotate({ "description": "Role name to assign. Must be on a Team or Enterprise plan to use the read-only role." }), "projects": Schema.optionalKey(Schema.Array(Schema.Struct({ "ref": Schema.String.annotate({ "description": "Project ref" }) })).annotate({ "description": "The projects to limit a user to. If omitted, user will have org-wide access with the provided role." }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }))), "require_sso": Schema.optionalKey(Schema.Boolean) }) })).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isMaxLength(50).annotate({ "expected": "a value with a length of at most 50" })) }) -export const V2CreateOrganizationInvitationsOutput = Schema.Struct({ "error": Schema.optionalKey(Schema.Struct({ "id": Schema.optionalKey(Schema.String), "code": Schema.String, "message": Schema.String, "description": Schema.optionalKey(Schema.String), "links": Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ "href": Schema.String, "rel": Schema.optionalKey(Schema.String), "title": Schema.optionalKey(Schema.String), "type": Schema.optionalKey(Schema.String), "describedby": Schema.optionalKey(Schema.String), "meta": Schema.optionalKey(Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))) }))), "meta": Schema.optionalKey(Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))), "issues": Schema.optionalKey(Schema.Array(Schema.Struct({ "id": Schema.optionalKey(Schema.String), "code": Schema.String, "message": Schema.String, "description": Schema.optionalKey(Schema.String), "links": Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ "href": Schema.String, "rel": Schema.optionalKey(Schema.String), "title": Schema.optionalKey(Schema.String), "type": Schema.optionalKey(Schema.String), "describedby": Schema.optionalKey(Schema.String), "meta": Schema.optionalKey(Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))) }))), "meta": Schema.Struct({ "email": Schema.String.annotate({ "description": "Email address of the invitation receipient.", "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })) }) }))) })), "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("organization_invitation").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "email": Schema.String.annotate({ "description": "Email address of the invitation receipient.", "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })) }) })) }) -export const V2CreatePrivateLinkAssociationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "data": Schema.Struct({ "type": Schema.Literal("private_link_association").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "aws_account_id": Schema.String.annotate({ "description": "The AWS account ID to add to the project PrivateLink share." }).check(Schema.isMinLength(12).annotate({ "expected": "a value with a length of at least 12" })).check(Schema.isMaxLength(12).annotate({ "expected": "a value with a length of at most 12" })).check(Schema.isPattern(new RegExp("^\\d{12}$")).annotate({ "expected": "a string matching the RegExp ^\\d{12}$" })), "account_name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional human-readable name for the AWS account." }).check(Schema.isMaxLength(128).annotate({ "expected": "a value with a length of at most 128" }))), "database_identifier": Schema.optionalKey(Schema.String.annotate({ "description": "Identifier of the read replica this PrivateLink share should target. Omit to target the primary database." })) }) }) }) -export const V2CreatePrivateLinkAssociationOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("private_link_association").annotate({ "description": "Resource type." }), "id": Schema.String, "attributes": Schema.Struct({ "aws_account_id": Schema.String.annotate({ "description": "The AWS account ID this PrivateLink share is associated with." }).check(Schema.isMinLength(12).annotate({ "expected": "a value with a length of at least 12" })).check(Schema.isMaxLength(12).annotate({ "expected": "a value with a length of at most 12" })).check(Schema.isPattern(new RegExp("^\\d{12}$")).annotate({ "expected": "a string matching the RegExp ^\\d{12}$" })), "account_name": Schema.optionalKey(Schema.String.annotate({ "description": "Human-readable name for the AWS account." })), "status": Schema.Literals(["CREATING", "READY", "ASSOCIATION_REQUEST_EXPIRED", "ASSOCIATION_ACCEPTED", "CREATION_FAILED", "DELETING"]).annotate({ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n" }), "shared_at": Schema.Union([Schema.String.annotate({ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending.", "format": "date-time" }), Schema.Null]), "database_type": Schema.Literals(["PRIMARY", "READ_REPLICA"]).annotate({ "description": "Whether this PrivateLink share targets the primary database or a read replica." }), "database_identifier": Schema.String.annotate({ "description": "Identifier of the database this PrivateLink share targets - the project ref for the primary, or the read replica identifier." }), "resource_access_manager_resource_config_id": Schema.optionalKey(Schema.String.annotate({ "description": "ID of the AWS VPC Lattice resource configuration backing this PrivateLink share." })), "resource_access_manager_resource_config_arn": Schema.optionalKey(Schema.String.annotate({ "description": "ARN of the AWS VPC Lattice resource configuration backing this PrivateLink share." })), "resource_access_manager_share_arn": Schema.optionalKey(Schema.String.annotate({ "description": "ARN of the AWS Resource Access Manager resource share for this association." })) }) }) }) -export const V2CreateWorkerUploadInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String.check(Schema.isPattern(new RegExp("^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$")).annotate({ "expected": "a string matching the RegExp ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$" })) }) -export const V2CreateWorkerUploadOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("project_worker_upload").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "description": "Upload id to pass to the deploy endpoint as `context_upload_id`." }), "attributes": Schema.Struct({ "url": Schema.String.annotate({ "description": "Presigned destination for the `.tar.gz` build context." }), "method": Schema.String, "expires_at": Schema.String.annotate({ "description": "When the slot stops accepting the upload." }) }) }) }) -export const V2DeleteAWorkerInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String.check(Schema.isPattern(new RegExp("^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$")).annotate({ "expected": "a string matching the RegExp ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$" })) }) -export const V2DeleteLogDrainInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })) }) -export const V2DeleteOrganizationInvitationsInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("organization_invitation").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "email": Schema.String.annotate({ "description": "Email address of the invitation receipient.", "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })) }) })).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) }) -export const V2DeleteOrganizationInvitationsOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("organization_invitation").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "email": Schema.String.annotate({ "description": "Email address of the invitation receipient.", "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" })) }) })) }) -export const V2DeletePrivateLinkAssociationInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "aws_account_id": Schema.String }) -export const V2DeletePrivateLinkAssociationForDatabaseInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "aws_account_id": Schema.String, "database_identifier": Schema.String }) -export const V2DeployAWorkerInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String.check(Schema.isPattern(new RegExp("^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$")).annotate({ "expected": "a string matching the RegExp ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$" })), "data": Schema.Struct({ "type": Schema.Literal("project_worker").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "spec": Schema.Struct({ "runtime": Schema.optionalKey(Schema.String), "size": Schema.String, "exposure": Schema.String, "instances": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), "context_upload_id": Schema.optionalKey(Schema.String.annotate({ "description": "Id of a build context staged through the uploads endpoint. Required unless `runtime` is set." })) }) }) }) -export const V2DeployAWorkerOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("project_worker").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "description": "Worker name." }), "attributes": Schema.Struct({ "spec": Schema.Struct({ "runtime": Schema.optionalKey(Schema.String), "size": Schema.String, "exposure": Schema.String, "instances": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), "build_state": Schema.Literals(["building", "active", "failed"]), "secret_generation": Schema.String, "state_reason": Schema.optionalKey(Schema.String), "image_version": Schema.optionalKey(Schema.String), "deleting": Schema.optionalKey(Schema.Boolean), "instances": Schema.optionalKey(Schema.Struct({ "declared": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "live": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "ready": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "stale": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) })), "instances_error": Schema.optionalKey(Schema.String) }) }) }) -export const V2GetAWorkerInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String.check(Schema.isPattern(new RegExp("^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$")).annotate({ "expected": "a string matching the RegExp ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$" })) }) -export const V2GetAWorkerOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("project_worker").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "description": "Worker name." }), "attributes": Schema.Struct({ "spec": Schema.Struct({ "runtime": Schema.optionalKey(Schema.String), "size": Schema.String, "exposure": Schema.String, "instances": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), "build_state": Schema.Literals(["building", "active", "failed"]), "secret_generation": Schema.String, "state_reason": Schema.optionalKey(Schema.String), "image_version": Schema.optionalKey(Schema.String), "deleting": Schema.optionalKey(Schema.Boolean), "instances": Schema.optionalKey(Schema.Struct({ "declared": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "live": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "ready": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "stale": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) })), "instances_error": Schema.optionalKey(Schema.String) }) }) }) -export const V2GetProjectConfigInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V2GetProjectConfigOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("project_config").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "description": "Project ref." }), "attributes": Schema.Struct({ "database": Schema.Struct({ "major_version": Schema.Number.annotate({ "description": "The major Postgres version the database runs. `17` covers both Postgres 17 and Oriole on 17, since Oriole is a storage engine rather than a version." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "ssl_enforced": Schema.Boolean.annotate({ "description": "Whether the database rejects plaintext connections" }), "network_restrictions": Schema.Struct({ "entitlement": Schema.Literals(["disallowed", "allowed"]), "status": Schema.Literals(["stored", "applied"]).annotate({ "description": "Whether the allowlist below is applied to the project or only stored." }), "allowed_cidrs": Schema.Array(Schema.Struct({ "address": Schema.String, "type": Schema.Literals(["v4", "v6"]) })), "updated_at": Schema.optionalKey(Schema.String), "applied_at": Schema.optionalKey(Schema.String) }), "postgres_settings": Schema.Struct({ "effective_cache_size": Schema.optionalKey(Schema.String), "logical_decoding_work_mem": Schema.optionalKey(Schema.String), "log_autovacuum_min_duration": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_checkpoints": Schema.optionalKey(Schema.Boolean), "log_connections": Schema.optionalKey(Schema.Boolean), "log_disconnections": Schema.optionalKey(Schema.Boolean), "log_duration": Schema.optionalKey(Schema.Boolean), "log_lock_waits": Schema.optionalKey(Schema.Boolean), "log_recovery_conflict_waits": Schema.optionalKey(Schema.Boolean), "log_replication_commands": Schema.optionalKey(Schema.Boolean), "log_startup_progress_interval": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "log_temp_files": Schema.optionalKey(Schema.String), "maintenance_work_mem": Schema.optionalKey(Schema.String), "track_activity_query_size": Schema.optionalKey(Schema.String), "max_connections": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_locks_per_transaction": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(10).annotate({ "expected": "a value greater than or equal to 10" })).check(Schema.isLessThanOrEqualTo(2147483640).annotate({ "expected": "a value less than or equal to 2147483640" }))), "max_logical_replication_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_parallel_maintenance_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_parallel_workers_per_gather": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(1024).annotate({ "expected": "a value less than or equal to 1024" }))), "max_replication_slots": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_slot_wal_keep_size": Schema.optionalKey(Schema.String), "max_standby_archive_delay": Schema.optionalKey(Schema.String), "max_standby_streaming_delay": Schema.optionalKey(Schema.String), "max_sync_workers_per_subscription": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "max_wal_size": Schema.optionalKey(Schema.String), "max_wal_senders": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" }))), "max_worker_processes": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(262143).annotate({ "expected": "a value less than or equal to 262143" }))), "session_replication_role": Schema.optionalKey(Schema.Literals(["origin", "replica", "local"])), "shared_buffers": Schema.optionalKey(Schema.String), "statement_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "track_commit_timestamp": Schema.optionalKey(Schema.Boolean), "wal_keep_size": Schema.optionalKey(Schema.String), "wal_sender_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: ms" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "work_mem": Schema.optionalKey(Schema.String), "checkpoint_timeout": Schema.optionalKey(Schema.String.annotate({ "description": "Default unit: s" }).check(Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate({ "expected": "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$" }))), "hot_standby_feedback": Schema.optionalKey(Schema.Boolean), "cron_log_statement": Schema.optionalKey(Schema.Boolean) }).annotate({ "description": "Postgres parameter overrides. Empty when the project runs entirely on defaults." }) }), "pooler": Schema.Struct({ "pool_mode": Schema.Literals(["transaction", "session", "statement"]), "ignore_startup_parameters": Schema.String, "server_idle_timeout": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "server_lifetime": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "query_wait_timeout": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "reserve_pool_size": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "default_pool_size": Schema.Number.annotate({ "description": "Defaults to the pooler's size for the project's compute when not overridden." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_client_conn": Schema.Number.annotate({ "description": "Defaults to the pooler's size for the project's compute when not overridden." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), "auth": Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })).annotate({ "description": "Effective Auth config, keyed by lowercased GoTrue setting name and resolved through the `gotrue_config` view, so a setting the project has never overridden is reported at its platform default. Secrets are returned as an HMAC of their value, never in plaintext." }), "api": Schema.Struct({ "db_schema": Schema.String.annotate({ "description": "Schemas exposed through the Data API" }), "db_extra_search_path": Schema.String, "max_rows": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "db_pool_acquisition_timeout": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "db_pool": Schema.Union([Schema.Number.annotate({ "description": "If `null`, no pool size is written to the project's PostgREST config and PostgREST's own default applies. The platform does not pick a value here." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]) }), "realtime": Schema.Struct({ "private_only": Schema.Boolean, "max_concurrent_users": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_events_per_second": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_bytes_per_second": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_channels_per_client": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_joins_per_second": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_presence_events_per_second": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_payload_size_in_kb": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "presence_enabled": Schema.Boolean, "suspend": Schema.Boolean, "connection_pool": Schema.Number.annotate({ "description": "Defaults to Realtime's pool size for the project's compute when not overridden." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "postgres_changes_pool": Schema.Union([Schema.Number.annotate({ "description": "If `null`, no override is stored and Realtime applies its own default." }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), Schema.Null]) }), "storage": Schema.Struct({ "file_size_limit": Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "features": Schema.Struct({ "image_transformation": Schema.Struct({ "enabled": Schema.Boolean }), "s3_protocol": Schema.Struct({ "enabled": Schema.Boolean }), "purge_cache": Schema.Struct({ "enabled": Schema.Boolean }), "iceberg_catalog": Schema.Struct({ "enabled": Schema.Boolean, "max_namespaces": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_tables": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_catalogs": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), "vector_buckets": Schema.Struct({ "enabled": Schema.Boolean, "max_buckets": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "max_indexes": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }) }), "capabilities": Schema.Struct({ "list_v2": Schema.Boolean, "iceberg_catalog": Schema.Boolean }), "upstream_target": Schema.Literals(["main", "canary"]), "migration_version": Schema.String, "database_pool_mode": Schema.String }).annotate({ "description": "Read from the storage service's admin API rather than the middleware DB, so unlike the rest of this resource it reflects the tenant's live config." }) }) }) }) -export const V2ListAllWorkersInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V2ListAllWorkersOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("project_worker").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "description": "Worker name." }), "attributes": Schema.Struct({ "spec": Schema.Struct({ "runtime": Schema.optionalKey(Schema.String), "size": Schema.String, "exposure": Schema.String, "instances": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) }), "build_state": Schema.Literals(["building", "active", "failed"]), "secret_generation": Schema.String, "state_reason": Schema.optionalKey(Schema.String), "image_version": Schema.optionalKey(Schema.String), "deleting": Schema.optionalKey(Schema.Boolean), "instances": Schema.optionalKey(Schema.Struct({ "declared": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "live": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "ready": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })), "stale": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ "expected": "a value greater than or equal to -9007199254740991" })).check(Schema.isLessThanOrEqualTo(9007199254740991).annotate({ "expected": "a value less than or equal to 9007199254740991" })) })), "instances_error": Schema.optionalKey(Schema.String) }) })) }) -export const V2ListLogDrainsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V2ListLogDrainsOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("log_drain").annotate({ "description": "Resource type." }), "id": Schema.String, "attributes": Schema.Struct({ "name": Schema.String, "description": Schema.optionalKey(Schema.String), "config": Schema.Union([Schema.Struct({ "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "schema": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "port": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null])), "hostname": Schema.optionalKey(Schema.String) }).annotate({ "title": "postgres" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "http": Schema.optionalKey(Schema.Literals(["http1", "http2"])), "gzip": Schema.optionalKey(Schema.Boolean), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "webhook" }), Schema.Struct({ "project_id": Schema.optionalKey(Schema.String), "dataset_id": Schema.optionalKey(Schema.String) }).annotate({ "title": "bigquery" }), Schema.Struct({ "api_key": Schema.optionalKey(Schema.String), "region": Schema.optionalKey(Schema.String) }).annotate({ "title": "datadog" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "loki" }), Schema.Struct({ "dsn": Schema.optionalKey(Schema.String) }).annotate({ "title": "sentry" }), Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "api_token": Schema.optionalKey(Schema.String), "dataset_name": Schema.optionalKey(Schema.String) }).annotate({ "title": "axiom" }), Schema.Struct({ "host": Schema.optionalKey(Schema.String), "port": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(65535).annotate({ "expected": "a value less than or equal to 65535" }))), "tls": Schema.optionalKey(Schema.Boolean), "structured_data": Schema.optionalKey(Schema.String), "cipher_key": Schema.optionalKey(Schema.String), "ca_cert": Schema.optionalKey(Schema.String), "client_cert": Schema.optionalKey(Schema.String), "client_key": Schema.optionalKey(Schema.String) }).annotate({ "title": "syslog" })]), "backend_type": Schema.Literals(["postgres", "bigquery", "clickhouse", "webhook", "datadog", "loki", "sentry", "s3", "axiom", "last9", "otlp", "syslog"]) }) })) }) -export const V2ListOrganizationGithubConnectionsInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "page": Schema.optionalKey(Schema.Struct({ "size": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }))), "after": Schema.optionalKey(Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" }))), "before": Schema.optionalKey(Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" }))) })), "filter": Schema.optionalKey(Schema.Struct({ "project_ref": Schema.optionalKey(Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" }))) })) }) -export const V2ListOrganizationGithubConnectionsOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("github_connection").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "description": "Connection id." }), "attributes": Schema.Struct({ "inserted_at": Schema.String.annotate({ "description": "When the connection was created" }), "updated_at": Schema.String.annotate({ "description": "When the connection was last updated" }), "installation_id": Schema.Number.annotate({ "description": "GitHub App installation id" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), "workdir": Schema.String.annotate({ "description": "Directory within the repository the project lives in" }), "supabase_changes_only": Schema.Boolean.annotate({ "description": "Whether branches are only created for changes under `supabase/`" }), "branch_limit": Schema.Number.annotate({ "description": "Maximum number of preview branches" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), "new_branch_per_pr": Schema.Boolean.annotate({ "description": "Whether a preview branch is created for every pull request" }), "project": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "ref": Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "name": Schema.String }).annotate({ "description": "The connected Supabase project" }), "repository": Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "name": Schema.String }).annotate({ "description": "The connected GitHub repository" }), "user": Schema.Union([Schema.Struct({ "id": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), "username": Schema.String, "primary_email": Schema.Union([Schema.String, Schema.Null]) }).annotate({ "description": "The user who created the connection, if still known" }), Schema.Null]) }) })), "links": Schema.Struct({ "first": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "description": "URL path to the first page if available." }), Schema.Null])), "prev": Schema.Union([Schema.String.annotate({ "description": "URL path to the previous page." }), Schema.Null]), "next": Schema.Union([Schema.String.annotate({ "description": "URL path to the next page." }), Schema.Null]), "last": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "description": "URL path to the last page if available." }), Schema.Null])) }) }) -export const V2ListOrganizationMembersInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "page": Schema.optionalKey(Schema.Struct({ "size": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }))), "after": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))), "before": Schema.optionalKey(Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }))) })), "filter": Schema.optionalKey(Schema.Struct({ "username": Schema.optionalKey(Schema.String), "primary_email": Schema.optionalKey(Schema.String.annotate({ "format": "email" }).check(Schema.isPattern(new RegExp("^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$")).annotate({ "expected": "a string matching the RegExp ^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" }))) })) }) -export const V2ListOrganizationMembersOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("organization_member").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$" })), "attributes": Schema.Struct({ "username": Schema.Union([Schema.String.annotate({ "description": "Member's username" }), Schema.Null]), "primary_email": Schema.Union([Schema.String.annotate({ "description": "Member's primary email" }), Schema.Null]), "mfa_enabled": Schema.Boolean.annotate({ "description": "Whether Multi-Factor Authentication is enabled for this member" }), "is_sso_user": Schema.Boolean.annotate({ "description": "Whether this member is a Single Sign-On user" }), "avatar_url": Schema.Union([Schema.String.annotate({ "description": "Member's avatar URL" }), Schema.Null]), "roles": Schema.Array(Schema.Struct({ "name": Schema.String.annotate({ "description": "Role name. For project-scoped roles this is the base role name." }), "scope": Schema.Literals(["organization", "project"]).annotate({ "description": "Whether this role applies org-wide or is scoped to specific projects for the user." }), "projects": Schema.Array(Schema.Struct({ "ref": Schema.String, "name": Schema.String })).annotate({ "description": "Project refs this role is scoped to. Empty array for org-level roles." }) })).annotate({ "description": "Roles assigned to this member. Includes both org-level and project-scoped roles." }) }) })), "links": Schema.Struct({ "first": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "description": "URL path to the first page if available." }), Schema.Null])), "prev": Schema.Union([Schema.String.annotate({ "description": "URL path to the previous page." }), Schema.Null]), "next": Schema.Union([Schema.String.annotate({ "description": "URL path to the next page." }), Schema.Null]), "last": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "description": "URL path to the last page if available." }), Schema.Null])) }) }) -export const V2ListOrganizationProjectsInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), "page": Schema.optionalKey(Schema.Struct({ "size": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }))), "after": Schema.optionalKey(Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }))), "before": Schema.optionalKey(Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }))) })), "sort": Schema.optionalKey(Schema.Literals(["inserted_at", "-inserted_at"])), "search": Schema.optionalKey(Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }))) }) -export const V2ListOrganizationProjectsOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("project").annotate({ "description": "Resource type." }), "id": Schema.String.annotate({ "description": "Project ref" }).check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "attributes": Schema.Struct({ "name": Schema.String.annotate({ "description": "Project name" }), "status": Schema.Literals(["INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "UNKNOWN", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UPGRADING", "PAUSING", "RESTORE_FAILED", "RESTARTING", "PAUSE_FAILED", "RESIZING"]).annotate({ "description": "Project status" }), "cloud_provider": Schema.String.annotate({ "description": "Cloud provider hosting the project" }), "region": Schema.String.annotate({ "description": "Region the project is hosted in" }), "inserted_at": Schema.String.annotate({ "description": "When the project was created" }), "databases": Schema.Array(Schema.Struct({ "cloud_provider": Schema.String, "identifier": Schema.String, "region": Schema.Union([Schema.String, Schema.Null]), "status": Schema.Literals(["ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", "GOING_DOWN", "INIT_FAILED", "REMOVED", "RESTORING", "UNKNOWN", "INIT_READ_REPLICA", "INIT_READ_REPLICA_FAILED", "RESTARTING", "RESIZING"]), "type": Schema.Literals(["PRIMARY", "READ_REPLICA"]), "infra_compute_size": Schema.optionalKey(Schema.Literals(["pico", "nano", "micro", "small", "medium", "large", "xlarge", "2xlarge", "4xlarge", "8xlarge", "12xlarge", "16xlarge", "24xlarge", "24xlarge_optimized_memory", "24xlarge_optimized_cpu", "24xlarge_high_memory", "48xlarge", "48xlarge_optimized_memory", "48xlarge_optimized_cpu", "48xlarge_high_memory"])), "disk_volume_size_gb": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "disk_type": Schema.optionalKey(Schema.Literals(["gp3", "io2"])), "disk_throughput_mbps": Schema.optionalKey(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), "disk_last_modified_at": Schema.optionalKey(Schema.String) })).annotate({ "description": "The project's databases including compute and disk attributes." }) }) })), "links": Schema.Struct({ "first": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "description": "URL path to the first page if available." }), Schema.Null])), "prev": Schema.Union([Schema.String.annotate({ "description": "URL path to the previous page." }), Schema.Null]), "next": Schema.Union([Schema.String.annotate({ "description": "URL path to the next page." }), Schema.Null]), "last": Schema.optionalKey(Schema.Union([Schema.String.annotate({ "description": "URL path to the last page if available." }), Schema.Null])) }) }) -export const V2ListOrganizationRolesInput = Schema.Struct({ "slug": Schema.String.check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })) }) -export const V2ListOrganizationRolesOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("organization_role").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "name": Schema.String.annotate({ "description": "Role name." }) }) })) }) -export const V2ListPrivateLinkAssociationsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })) }) -export const V2ListPrivateLinkAssociationsOutput = Schema.Struct({ "data": Schema.Array(Schema.Struct({ "type": Schema.Literal("private_link_association").annotate({ "description": "Resource type." }), "id": Schema.String, "attributes": Schema.Struct({ "aws_account_id": Schema.String.annotate({ "description": "The AWS account ID this PrivateLink share is associated with." }).check(Schema.isMinLength(12).annotate({ "expected": "a value with a length of at least 12" })).check(Schema.isMaxLength(12).annotate({ "expected": "a value with a length of at most 12" })).check(Schema.isPattern(new RegExp("^\\d{12}$")).annotate({ "expected": "a string matching the RegExp ^\\d{12}$" })), "account_name": Schema.optionalKey(Schema.String.annotate({ "description": "Human-readable name for the AWS account." })), "status": Schema.Literals(["CREATING", "READY", "ASSOCIATION_REQUEST_EXPIRED", "ASSOCIATION_ACCEPTED", "CREATION_FAILED", "DELETING"]).annotate({ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n" }), "shared_at": Schema.Union([Schema.String.annotate({ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending.", "format": "date-time" }), Schema.Null]), "database_type": Schema.Literals(["PRIMARY", "READ_REPLICA"]).annotate({ "description": "Whether this PrivateLink share targets the primary database or a read replica." }), "database_identifier": Schema.String.annotate({ "description": "Identifier of the database this PrivateLink share targets - the project ref for the primary, or the read replica identifier." }), "resource_access_manager_resource_config_id": Schema.optionalKey(Schema.String.annotate({ "description": "ID of the AWS VPC Lattice resource configuration backing this PrivateLink share." })), "resource_access_manager_resource_config_arn": Schema.optionalKey(Schema.String.annotate({ "description": "ARN of the AWS VPC Lattice resource configuration backing this PrivateLink share." })), "resource_access_manager_share_arn": Schema.optionalKey(Schema.String.annotate({ "description": "ARN of the AWS Resource Access Manager resource share for this association." })) }) })) }) -export const V2PreviewAProjectTransferInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "data": Schema.Struct({ "type": Schema.Literal("project_transfer_input").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "target_organization_slug": Schema.String }) }) }) -export const V2PreviewAProjectTransferOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("project_transfer_result").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "valid": Schema.Boolean, "warnings": Schema.Array(Schema.Struct({ "key": Schema.String, "message": Schema.String })), "errors": Schema.Array(Schema.Struct({ "key": Schema.String, "message": Schema.String })), "info": Schema.Array(Schema.Struct({ "key": Schema.String, "message": Schema.String })) }) }) }) -export const V2RunProjectAdvisorsInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "data": Schema.Struct({ "type": Schema.Literal("project_advisors").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "lints": Schema.Array(Schema.Struct({ "name": Schema.Literals(["unindexed_foreign_keys", "auth_users_exposed", "auth_rls_initplan", "no_primary_key", "unused_index", "multiple_permissive_policies", "policy_exists_rls_disabled", "rls_enabled_no_policy", "duplicate_index", "security_definer_view", "function_search_path_mutable", "rls_disabled_in_public", "extension_in_public", "rls_references_user_metadata", "materialized_view_in_api", "foreign_table_in_api", "unsupported_reg_types", "auth_otp_long_expiry", "auth_otp_short_length", "ssl_not_enforced", "log_connections_not_enabled", "network_restrictions_not_set", "password_requirements_min_length", "pitr_not_enabled", "auth_leaked_password_protection", "auth_insufficient_mfa_options", "auth_password_policy_missing", "leaked_service_key", "no_backup_admin", "vulnerable_postgres_version", "db_not_reachable", "db_connection_failing", "db_connection_limit_reached", "instance_telemetry_lost", "instance_db_down", "instance_alert_firing", "log_service_error_rate_high"]) })).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check(Schema.isMaxLength(10).annotate({ "expected": "a value with a length of at most 10" })) }) }) }) -export const V2RunProjectAdvisorsOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("project_advisors").annotate({ "description": "Resource type." }), "attributes": Schema.StructWithRest(Schema.Struct({ "lints": Schema.Array(Schema.StructWithRest(Schema.Struct({ "name": Schema.Literals(["unindexed_foreign_keys", "auth_users_exposed", "auth_rls_initplan", "no_primary_key", "unused_index", "multiple_permissive_policies", "policy_exists_rls_disabled", "rls_enabled_no_policy", "duplicate_index", "security_definer_view", "function_search_path_mutable", "rls_disabled_in_public", "extension_in_public", "rls_references_user_metadata", "materialized_view_in_api", "foreign_table_in_api", "unsupported_reg_types", "auth_otp_long_expiry", "auth_otp_short_length", "ssl_not_enforced", "log_connections_not_enabled", "network_restrictions_not_set", "password_requirements_min_length", "pitr_not_enabled", "auth_leaked_password_protection", "auth_insufficient_mfa_options", "auth_password_policy_missing", "leaked_service_key", "no_backup_admin", "vulnerable_postgres_version", "db_not_reachable", "db_connection_failing", "db_connection_limit_reached", "instance_telemetry_lost", "instance_db_down", "instance_alert_firing", "log_service_error_rate_high", "project_not_active", "advisor_check_unavailable"]), "title": Schema.String, "level": Schema.Literals(["ERROR", "WARN", "INFO"]), "facing": Schema.Literal("EXTERNAL"), "categories": Schema.Array(Schema.Literals(["PERFORMANCE", "SECURITY", "HEALTH"])), "description": Schema.String, "detail": Schema.String, "remediation": Schema.String, "metadata": Schema.optionalKey(Schema.Struct({ "schema": Schema.optionalKey(Schema.String), "name": Schema.optionalKey(Schema.String), "entity": Schema.optionalKey(Schema.String), "type": Schema.optionalKey(Schema.Literals(["table", "view", "materialized view", "foreign table", "auth", "function", "extension", "compliance", "health"])), "fkey_name": Schema.optionalKey(Schema.String), "fkey_columns": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })))) })), "cache_key": Schema.String, "observed_at": Schema.optionalKey(Schema.String.annotate({ "format": "date-time" })) }), [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))])) }), [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))]) }) }) -export const V2TransferAProjectInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "data": Schema.Struct({ "type": Schema.Literal("project_transfer_input").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "target_organization_slug": Schema.String }) }) }) -export const V2UpdateLogDrainInput = Schema.Struct({ "ref": Schema.String.check(Schema.isMinLength(20).annotate({ "expected": "a value with a length of at least 20" })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })).check(Schema.isPattern(new RegExp("^[a-z]+$")).annotate({ "expected": "a string matching the RegExp ^[a-z]+$" })), "id": Schema.String.annotate({ "format": "uuid" }).check(Schema.isPattern(new RegExp("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$")).annotate({ "expected": "a string matching the RegExp ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" })), "data": Schema.Struct({ "type": Schema.Literal("log_drain").annotate({ "description": "Resource type." }), "attributes": Schema.Struct({ "name": Schema.optionalKey(Schema.String), "description": Schema.optionalKey(Schema.String), "config": Schema.optionalKey(Schema.Union([Schema.Struct({ "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "schema": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "port": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null])), "hostname": Schema.optionalKey(Schema.String) }).annotate({ "title": "postgres" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "http": Schema.optionalKey(Schema.Literals(["http1", "http2"])), "gzip": Schema.optionalKey(Schema.Boolean), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "webhook" }), Schema.Struct({ "project_id": Schema.optionalKey(Schema.String), "dataset_id": Schema.optionalKey(Schema.String) }).annotate({ "title": "bigquery" }), Schema.Struct({ "api_key": Schema.optionalKey(Schema.String), "region": Schema.optionalKey(Schema.String) }).annotate({ "title": "datadog" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "loki" }), Schema.Struct({ "dsn": Schema.optionalKey(Schema.String) }).annotate({ "title": "sentry" }), Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "api_token": Schema.optionalKey(Schema.String), "dataset_name": Schema.optionalKey(Schema.String) }).annotate({ "title": "axiom" }), Schema.Struct({ "host": Schema.optionalKey(Schema.String), "port": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(65535).annotate({ "expected": "a value less than or equal to 65535" }))), "tls": Schema.optionalKey(Schema.Boolean), "structured_data": Schema.optionalKey(Schema.String), "cipher_key": Schema.optionalKey(Schema.String), "ca_cert": Schema.optionalKey(Schema.String), "client_cert": Schema.optionalKey(Schema.String), "client_key": Schema.optionalKey(Schema.String) }).annotate({ "title": "syslog" })])), "backend_type": Schema.Literals(["postgres", "bigquery", "clickhouse", "webhook", "datadog", "loki", "sentry", "s3", "axiom", "last9", "otlp", "syslog"]) }) }) }) -export const V2UpdateLogDrainOutput = Schema.Struct({ "data": Schema.Struct({ "type": Schema.Literal("log_drain").annotate({ "description": "Resource type." }), "id": Schema.String, "attributes": Schema.Struct({ "name": Schema.String, "description": Schema.optionalKey(Schema.String), "config": Schema.Union([Schema.Struct({ "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "schema": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "port": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null])), "hostname": Schema.optionalKey(Schema.String) }).annotate({ "title": "postgres" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "http": Schema.optionalKey(Schema.Literals(["http1", "http2"])), "gzip": Schema.optionalKey(Schema.Boolean), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "webhook" }), Schema.Struct({ "project_id": Schema.optionalKey(Schema.String), "dataset_id": Schema.optionalKey(Schema.String) }).annotate({ "title": "bigquery" }), Schema.Struct({ "api_key": Schema.optionalKey(Schema.String), "region": Schema.optionalKey(Schema.String) }).annotate({ "title": "datadog" }), Schema.Struct({ "url": Schema.optionalKey(Schema.String), "username": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "password": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), "headers": Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) }).annotate({ "title": "loki" }), Schema.Struct({ "dsn": Schema.optionalKey(Schema.String) }).annotate({ "title": "sentry" }), Schema.Struct({ "domain": Schema.optionalKey(Schema.String), "api_token": Schema.optionalKey(Schema.String), "dataset_name": Schema.optionalKey(Schema.String) }).annotate({ "title": "axiom" }), Schema.Struct({ "host": Schema.optionalKey(Schema.String), "port": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check(Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" })).check(Schema.isLessThanOrEqualTo(65535).annotate({ "expected": "a value less than or equal to 65535" }))), "tls": Schema.optionalKey(Schema.Boolean), "structured_data": Schema.optionalKey(Schema.String), "cipher_key": Schema.optionalKey(Schema.String), "ca_cert": Schema.optionalKey(Schema.String), "client_cert": Schema.optionalKey(Schema.String), "client_key": Schema.optionalKey(Schema.String) }).annotate({ "title": "syslog" })]), "backend_type": Schema.Literals(["postgres", "bigquery", "clickhouse", "webhook", "datadog", "loki", "sentry", "s3", "axiom", "last9", "otlp", "syslog"]) }) }) }) -export const V1ApplyAMigrationOutput = Schema.Void -export const V1ApplyProjectAddonOutput = Schema.Void -export const V1AuthorizeUserOutput = Schema.Void -export const V1BulkCreateSecretsOutput = Schema.Void -export const V1BulkDeleteSecretsOutput = Schema.Void -export const V1CancelAProjectRestorationOutput = Schema.Void -export const V1ClaimProjectForOrganizationOutput = Schema.Void -export const V1CountActionRunsOutput = Schema.Void -export const V1DeactivateVanitySubdomainConfigOutput = Schema.Void -export const V1DeleteHostnameConfigOutput = Schema.Void -export const V1DeleteAFunctionOutput = Schema.Void -export const V1DeleteInviteExternalJitAccessOutput = Schema.Void -export const V1DeleteJitAccessOutput = Schema.Void -export const V1DeleteNetworkBansOutput = Schema.Void -export const V1DeleteProjectClaimTokenOutput = Schema.Void -export const V1DisablePreviewBranchingOutput = Schema.Void -export const V1DisableReadonlyModeTemporarilyOutput = Schema.Void -export const V1EnableDatabaseWebhookOutput = Schema.Void -export const V1ModifyDatabaseDiskOutput = Schema.Void -export const V1OauthAuthorizeProjectClaimOutput = Schema.Void -export const V1PatchAMigrationOutput = Schema.Void -export const V1PauseAProjectOutput = Schema.Void -export const V1ReadOnlyQueryOutput = Schema.Void -export const V1RemoveAReadReplicaOutput = Schema.Void -export const V1RemoveProjectAddonOutput = Schema.Void -export const V1RestartAProjectOutput = Schema.Void -export const V1RestoreAProjectOutput = Schema.Void -export const V1RestorePhysicalBackupOutput = Schema.Void -export const V1RestorePitrBackupOutput = Schema.Void -export const V1RevokeTokenOutput = Schema.Void -export const V1RollbackMigrationsOutput = Schema.Void -export const V1RunAQueryOutput = Schema.Void -export const V1SetupAReadReplicaOutput = Schema.Void -export const V1ShutdownRealtimeOutput = Schema.Void -export const V1UndoOutput = Schema.Void -export const V1UpdateRealtimeConfigOutput = Schema.Void -export const V1UpdateStorageConfigOutput = Schema.Void -export const V1UpsertAMigrationOutput = Schema.Void -export const V2DeleteAWorkerOutput = Schema.Void -export const V2DeleteLogDrainOutput = Schema.Void -export const V2DeletePrivateLinkAssociationOutput = Schema.Void -export const V2DeletePrivateLinkAssociationForDatabaseOutput = Schema.Void -export const V2TransferAProjectOutput = Schema.Void - -export const openApiOperationIdMap = { - "v1-accept-invite-external-jit-access": "v1AcceptInviteExternalJitAccess", - "v1-activate-custom-hostname": "v1ActivateCustomHostname", - "v1-activate-vanity-subdomain-config": "v1ActivateVanitySubdomainConfig", - "v1-apply-a-migration": "v1ApplyAMigration", - "v1-apply-project-addon": "v1ApplyProjectAddon", - "v1-authorize-jit-access": "v1AuthorizeJitAccess", - "v1-authorize-user": "v1AuthorizeUser", - "v1-bulk-create-secrets": "v1BulkCreateSecrets", - "v1-bulk-delete-secrets": "v1BulkDeleteSecrets", - "v1-bulk-update-functions": "v1BulkUpdateFunctions", - "v1-cancel-a-project-restoration": "v1CancelAProjectRestoration", - "v1-check-vanity-subdomain-availability": "v1CheckVanitySubdomainAvailability", - "v1-claim-project-for-organization": "v1ClaimProjectForOrganization", - "v1-count-action-runs": "v1CountActionRuns", - "v1-create-a-branch": "v1CreateABranch", - "v1-create-a-function": "v1CreateAFunction", - "v1-create-a-project": "v1CreateAProject", - "v1-create-a-sso-provider": "v1CreateASsoProvider", - "v1-create-an-organization": "v1CreateAnOrganization", - "v1-create-legacy-signing-key": "v1CreateLegacySigningKey", - "v1-create-login-role": "v1CreateLoginRole", - "v1-create-project-api-key": "v1CreateProjectApiKey", - "v1-create-project-claim-token": "v1CreateProjectClaimToken", - "v1-create-project-signing-key": "v1CreateProjectSigningKey", - "v1-create-project-tpa-integration": "v1CreateProjectTpaIntegration", - "v1-create-restore-point": "v1CreateRestorePoint", - "v1-deactivate-vanity-subdomain-config": "v1DeactivateVanitySubdomainConfig", - "v1-Delete hostname config": "v1DeleteHostnameConfig", - "v1-delete-a-branch": "v1DeleteABranch", - "v1-delete-a-function": "v1DeleteAFunction", - "v1-delete-a-project": "v1DeleteAProject", - "v1-delete-a-sso-provider": "v1DeleteASsoProvider", - "v1-delete-invite-external-jit-access": "v1DeleteInviteExternalJitAccess", - "v1-delete-jit-access": "v1DeleteJitAccess", - "v1-delete-login-roles": "v1DeleteLoginRoles", - "v1-delete-network-bans": "v1DeleteNetworkBans", - "v1-delete-project-api-key": "v1DeleteProjectApiKey", - "v1-delete-project-claim-token": "v1DeleteProjectClaimToken", - "v1-delete-project-tpa-integration": "v1DeleteProjectTpaIntegration", - "v1-deploy-a-function": "v1DeployAFunction", - "v1-diff-a-branch": "v1DiffABranch", - "v1-disable-preview-branching": "v1DisablePreviewBranching", - "v1-disable-readonly-mode-temporarily": "v1DisableReadonlyModeTemporarily", - "v1-enable-database-webhook": "v1EnableDatabaseWebhook", - "v1-exchange-oauth-token": "v1ExchangeOauthToken", - "v1-generate-typescript-types": "v1GenerateTypescriptTypes", - "v1-get-a-branch": "v1GetABranch", - "v1-get-a-branch-config": "v1GetABranchConfig", - "v1-get-a-function": "v1GetAFunction", - "v1-get-a-function-body": "v1GetAFunctionBody", - "v1-get-a-migration": "v1GetAMigration", - "v1-get-a-snippet": "v1GetASnippet", - "v1-get-a-sso-provider": "v1GetASsoProvider", - "v1-get-action-run": "v1GetActionRun", - "v1-get-action-run-logs": "v1GetActionRunLogs", - "v1-get-all-projects-for-organization": "v1GetAllProjectsForOrganization", - "v1-get-an-organization": "v1GetAnOrganization", - "v1-get-auth-service-config": "v1GetAuthServiceConfig", - "v1-get-available-regions": "v1GetAvailableRegions", - "v1-get-backup-schedule": "v1GetBackupSchedule", - "v1-get-database-disk": "v1GetDatabaseDisk", - "v1-get-database-metadata": "v1GetDatabaseMetadata", - "v1-get-database-openapi": "v1GetDatabaseOpenapi", - "v1-get-disk-utilization": "v1GetDiskUtilization", - "v1-get-hostname-config": "v1GetHostnameConfig", - "v1-get-jit-access": "v1GetJitAccess", - "v1-get-jit-access-config": "v1GetJitAccessConfig", - "v1-get-legacy-signing-key": "v1GetLegacySigningKey", - "v1-get-network-restrictions": "v1GetNetworkRestrictions", - "v1-get-organization-entitlements": "v1GetOrganizationEntitlements", - "v1-get-organization-project-claim": "v1GetOrganizationProjectClaim", - "v1-get-performance-advisors": "v1GetPerformanceAdvisors", - "v1-get-pgsodium-config": "v1GetPgsodiumConfig", - "v1-get-pooler-config": "v1GetPoolerConfig", - "v1-get-postgres-config": "v1GetPostgresConfig", - "v1-get-postgres-upgrade-eligibility": "v1GetPostgresUpgradeEligibility", - "v1-get-postgres-upgrade-status": "v1GetPostgresUpgradeStatus", - "v1-get-postgrest-service-config": "v1GetPostgrestServiceConfig", - "v1-get-profile": "v1GetProfile", - "v1-get-project": "v1GetProject", - "v1-get-project-api-key": "v1GetProjectApiKey", - "v1-get-project-api-keys": "v1GetProjectApiKeys", - "v1-get-project-claim-token": "v1GetProjectClaimToken", - "v1-get-project-disk-autoscale-config": "v1GetProjectDiskAutoscaleConfig", - "v1-get-project-function-combined-stats": "v1GetProjectFunctionCombinedStats", - "v1-get-project-legacy-api-keys": "v1GetProjectLegacyApiKeys", - "v1-get-project-logs": "v1GetProjectLogs", - "v1-get-project-logs-all": "v1GetProjectLogsAll", - "v1-get-project-pgbouncer-config": "v1GetProjectPgbouncerConfig", - "v1-get-project-signing-key": "v1GetProjectSigningKey", - "v1-get-project-signing-keys": "v1GetProjectSigningKeys", - "v1-get-project-tpa-integration": "v1GetProjectTpaIntegration", - "v1-get-project-usage-api-count": "v1GetProjectUsageApiCount", - "v1-get-project-usage-request-count": "v1GetProjectUsageRequestCount", - "v1-get-readonly-mode-status": "v1GetReadonlyModeStatus", - "v1-get-realtime-config": "v1GetRealtimeConfig", - "v1-get-restore-point": "v1GetRestorePoint", - "v1-get-security-advisors": "v1GetSecurityAdvisors", - "v1-get-services-health": "v1GetServicesHealth", - "v1-get-ssl-enforcement-config": "v1GetSslEnforcementConfig", - "v1-get-storage-config": "v1GetStorageConfig", - "v1-get-vanity-subdomain-config": "v1GetVanitySubdomainConfig", - "v1-invite-external-jit-access": "v1InviteExternalJitAccess", - "v1-list-action-runs": "v1ListActionRuns", - "v1-list-all-backups": "v1ListAllBackups", - "v1-list-all-branches": "v1ListAllBranches", - "v1-list-all-buckets": "v1ListAllBuckets", - "v1-list-all-functions": "v1ListAllFunctions", - "v1-list-all-network-bans": "v1ListAllNetworkBans", - "v1-list-all-network-bans-enriched": "v1ListAllNetworkBansEnriched", - "v1-list-all-organizations": "v1ListAllOrganizations", - "v1-list-all-projects": "v1ListAllProjects", - "v1-list-all-secrets": "v1ListAllSecrets", - "v1-list-all-snippets": "v1ListAllSnippets", - "v1-list-all-sso-provider": "v1ListAllSsoProvider", - "v1-list-available-restore-versions": "v1ListAvailableRestoreVersions", - "v1-list-jit-access": "v1ListJitAccess", - "v1-list-migration-history": "v1ListMigrationHistory", - "v1-list-organization-members": "v1ListOrganizationMembers", - "v1-list-project-addons": "v1ListProjectAddons", - "v1-list-project-tpa-integrations": "v1ListProjectTpaIntegrations", - "v1-merge-a-branch": "v1MergeABranch", - "v1-modify-database-disk": "v1ModifyDatabaseDisk", - "v1-oauth-authorize-project-claim": "v1OauthAuthorizeProjectClaim", - "v1-patch-a-migration": "v1PatchAMigration", - "v1-patch-network-restrictions": "v1PatchNetworkRestrictions", - "v1-pause-a-project": "v1PauseAProject", - "v1-push-a-branch": "v1PushABranch", - "v1-read-only-query": "v1ReadOnlyQuery", - "v1-remove-a-read-replica": "v1RemoveAReadReplica", - "v1-remove-project-addon": "v1RemoveProjectAddon", - "v1-remove-project-signing-key": "v1RemoveProjectSigningKey", - "v1-reset-a-branch": "v1ResetABranch", - "v1-restart-a-project": "v1RestartAProject", - "v1-restore-a-branch": "v1RestoreABranch", - "v1-restore-a-project": "v1RestoreAProject", - "v1-restore-physical-backup": "v1RestorePhysicalBackup", - "v1-restore-pitr-backup": "v1RestorePitrBackup", - "v1-revoke-token": "v1RevokeToken", - "v1-rollback-migrations": "v1RollbackMigrations", - "v1-run-a-query": "v1RunAQuery", - "v1-scrape-project-metrics": "v1ScrapeProjectMetrics", - "v1-setup-a-read-replica": "v1SetupAReadReplica", - "v1-shutdown-realtime": "v1ShutdownRealtime", - "v1-undo": "v1Undo", - "v1-update-a-branch-config": "v1UpdateABranchConfig", - "v1-update-a-function": "v1UpdateAFunction", - "v1-update-a-project": "v1UpdateAProject", - "v1-update-a-sso-provider": "v1UpdateASsoProvider", - "v1-update-action-run-status": "v1UpdateActionRunStatus", - "v1-update-auth-service-config": "v1UpdateAuthServiceConfig", - "v1-update-backup-schedule": "v1UpdateBackupSchedule", - "v1-update-database-password": "v1UpdateDatabasePassword", - "v1-update-hostname-config": "v1UpdateHostnameConfig", - "v1-update-jit-access": "v1UpdateJitAccess", - "v1-update-jit-access-config": "v1UpdateJitAccessConfig", - "v1-update-network-restrictions": "v1UpdateNetworkRestrictions", - "v1-update-pgsodium-config": "v1UpdatePgsodiumConfig", - "v1-update-pooler-config": "v1UpdatePoolerConfig", - "v1-update-postgres-config": "v1UpdatePostgresConfig", - "v1-update-postgrest-service-config": "v1UpdatePostgrestServiceConfig", - "v1-update-project-api-key": "v1UpdateProjectApiKey", - "v1-update-project-legacy-api-keys": "v1UpdateProjectLegacyApiKeys", - "v1-update-project-signing-key": "v1UpdateProjectSigningKey", - "v1-update-realtime-config": "v1UpdateRealtimeConfig", - "v1-update-ssl-enforcement-config": "v1UpdateSslEnforcementConfig", - "v1-update-storage-config": "v1UpdateStorageConfig", - "v1-upgrade-postgres-version": "v1UpgradePostgresVersion", - "v1-upsert-a-migration": "v1UpsertAMigration", - "v1-verify-dns-config": "v1VerifyDnsConfig", - "v2-assign-organization-member-role": "v2AssignOrganizationMemberRole", - "v2-create-log-drain": "v2CreateLogDrain", - "v2-create-organization-invitations": "v2CreateOrganizationInvitations", - "v2-create-private-link-association": "v2CreatePrivateLinkAssociation", - "v2-create-worker-upload": "v2CreateWorkerUpload", - "v2-delete-a-worker": "v2DeleteAWorker", - "v2-delete-log-drain": "v2DeleteLogDrain", - "v2-delete-organization-invitations": "v2DeleteOrganizationInvitations", - "v2-delete-private-link-association": "v2DeletePrivateLinkAssociation", - "v2-delete-private-link-association-for-database": "v2DeletePrivateLinkAssociationForDatabase", - "v2-deploy-a-worker": "v2DeployAWorker", - "v2-get-a-worker": "v2GetAWorker", - "v2-get-project-config": "v2GetProjectConfig", - "v2-list-all-workers": "v2ListAllWorkers", - "v2-list-log-drains": "v2ListLogDrains", - "v2-list-organization-github-connections": "v2ListOrganizationGithubConnections", - "v2-list-organization-members": "v2ListOrganizationMembers", - "v2-list-organization-projects": "v2ListOrganizationProjects", - "v2-list-organization-roles": "v2ListOrganizationRoles", - "v2-list-private-link-associations": "v2ListPrivateLinkAssociations", - "v2-preview-a-project-transfer": "v2PreviewAProjectTransfer", - "v2-run-project-advisors": "v2RunProjectAdvisors", - "v2-transfer-a-project": "v2TransferAProject", - "v2-update-log-drain": "v2UpdateLogDrain", -} as const; - -export const operationDefinitions = { - "v1AcceptInviteExternalJitAccess": { - id: "v1AcceptInviteExternalJitAccess", - description: "Accepts the invitation to JIT database access", - method: "POST", - path: "/v1/projects/{ref}/database/jit/invite/accept", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["email","token"] }, - response: { kind: "json" }, - inputSchema: V1AcceptInviteExternalJitAccessInput, - outputSchema: V1AcceptInviteExternalJitAccessOutput, - }, - "v1ActivateCustomHostname": { - id: "v1ActivateCustomHostname", - description: "[Beta] Activates a custom hostname for a project.", - method: "POST", - path: "/v1/projects/{ref}/custom-hostname/activate", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ActivateCustomHostnameInput, - outputSchema: V1ActivateCustomHostnameOutput, - }, - "v1ActivateVanitySubdomainConfig": { - id: "v1ActivateVanitySubdomainConfig", - description: "[Beta] Activates a vanity subdomain for a project.", - method: "POST", - path: "/v1/projects/{ref}/vanity-subdomain/activate", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["vanity_subdomain"] }, - response: { kind: "json" }, - inputSchema: V1ActivateVanitySubdomainConfigInput, - outputSchema: V1ActivateVanitySubdomainConfigOutput, - }, - "v1ApplyAMigration": { - id: "v1ApplyAMigration", - description: "Apply a database migration", - method: "POST", - path: "/v1/projects/{ref}/database/migrations", - pathParams: ["ref"], - queryParams: [], - headerParams: ["Idempotency-Key"], - requestBody: { kind: "json", contentType: "application/json", fields: ["query","name","rollback"] }, - response: { kind: "void" }, - inputSchema: V1ApplyAMigrationInput, - outputSchema: V1ApplyAMigrationOutput, - }, - "v1ApplyProjectAddon": { - id: "v1ApplyProjectAddon", - description: "Selects an addon variant, for example scaling the project’s compute instance up or down, and applies it to the project.", - method: "PATCH", - path: "/v1/projects/{ref}/billing/addons", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["addon_variant","addon_type"] }, - response: { kind: "void" }, - inputSchema: V1ApplyProjectAddonInput, - outputSchema: V1ApplyProjectAddonOutput, - }, - "v1AuthorizeJitAccess": { - id: "v1AuthorizeJitAccess", - description: "Authorizes the request to assume a role in the project database", - method: "POST", - path: "/v1/projects/{ref}/database/jit", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["role","rhost"] }, - response: { kind: "json" }, - inputSchema: V1AuthorizeJitAccessInput, - outputSchema: V1AuthorizeJitAccessOutput, - }, - "v1AuthorizeUser": { - id: "v1AuthorizeUser", - description: "[Beta] Authorize user through oauth", - method: "GET", - path: "/v1/oauth/authorize", - pathParams: [], - queryParams: ["client_id","response_type","redirect_uri","scope","state","response_mode","code_challenge","code_challenge_method","organization_slug","target_flow","resource"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1AuthorizeUserInput, - outputSchema: V1AuthorizeUserOutput, - }, - "v1BulkCreateSecrets": { - id: "v1BulkCreateSecrets", - description: "Creates multiple secrets and adds them to the specified project.", - method: "POST", - path: "/v1/projects/{ref}/secrets", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "body", contentType: "application/json", field: "body" }, - response: { kind: "void" }, - inputSchema: V1BulkCreateSecretsInput, - outputSchema: V1BulkCreateSecretsOutput, - }, - "v1BulkDeleteSecrets": { - id: "v1BulkDeleteSecrets", - description: "Deletes all secrets with the given names from the specified project", - method: "DELETE", - path: "/v1/projects/{ref}/secrets", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "body", contentType: "application/json", field: "body" }, - response: { kind: "void" }, - inputSchema: V1BulkDeleteSecretsInput, - outputSchema: V1BulkDeleteSecretsOutput, - }, - "v1BulkUpdateFunctions": { - id: "v1BulkUpdateFunctions", - description: "Bulk update functions. It will create a new function or replace existing. The operation is idempotent. NOTE: You will need to manually bump the version.", - method: "PUT", - path: "/v1/projects/{ref}/functions", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "body", contentType: "application/json", field: "body" }, - response: { kind: "json" }, - inputSchema: V1BulkUpdateFunctionsInput, - outputSchema: V1BulkUpdateFunctionsOutput, - }, - "v1CancelAProjectRestoration": { - id: "v1CancelAProjectRestoration", - description: "Cancels the given project restoration", - method: "POST", - path: "/v1/projects/{ref}/restore/cancel", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1CancelAProjectRestorationInput, - outputSchema: V1CancelAProjectRestorationOutput, - }, - "v1CheckVanitySubdomainAvailability": { - id: "v1CheckVanitySubdomainAvailability", - description: "[Beta] Checks vanity subdomain availability", - method: "POST", - path: "/v1/projects/{ref}/vanity-subdomain/check-availability", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["vanity_subdomain"] }, - response: { kind: "json" }, - inputSchema: V1CheckVanitySubdomainAvailabilityInput, - outputSchema: V1CheckVanitySubdomainAvailabilityOutput, - }, - "v1ClaimProjectForOrganization": { - id: "v1ClaimProjectForOrganization", - description: "Claims project for the specified organization", - method: "POST", - path: "/v1/organizations/{slug}/project-claim/{token}", - pathParams: ["slug","token"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1ClaimProjectForOrganizationInput, - outputSchema: V1ClaimProjectForOrganizationOutput, - }, - "v1CountActionRuns": { - id: "v1CountActionRuns", - description: "Returns the total number of action runs of the specified project.", - method: "HEAD", - path: "/v1/projects/{ref}/actions", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1CountActionRunsInput, - outputSchema: V1CountActionRunsOutput, - }, - "v1CreateABranch": { - id: "v1CreateABranch", - description: "Creates a database branch from the specified project.", - method: "POST", - path: "/v1/projects/{ref}/branches", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["branch_name","git_branch","is_default","persistent","region","desired_instance_size","release_channel","postgres_engine","secrets","with_data","notify_url"] }, - response: { kind: "json" }, - inputSchema: V1CreateABranchInput, - outputSchema: V1CreateABranchOutput, - }, - "v1CreateAFunction": { - id: "v1CreateAFunction", - description: "This endpoint is deprecated - use the deploy endpoint. Creates a function and adds it to the specified project.", - method: "POST", - path: "/v1/projects/{ref}/functions", - pathParams: ["ref"], - queryParams: ["slug","name","verify_jwt","import_map","entrypoint_path","import_map_path","ezbr_sha256"], - headerParams: [], - requestBody: { kind: "body", contentType: "application/vnd.denoland.eszip", field: "body" }, - response: { kind: "json" }, - inputSchema: V1CreateAFunctionInput, - outputSchema: V1CreateAFunctionOutput, - }, - "v1CreateAProject": { - id: "v1CreateAProject", - description: "Create a project", - method: "POST", - path: "/v1/projects", - pathParams: [], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["db_pass","name","organization_id","organization_slug","plan","region","region_selection","kps_enabled","desired_instance_size","template_url","release_channel","postgres_engine","high_availability"] }, - response: { kind: "json" }, - inputSchema: V1CreateAProjectInput, - outputSchema: V1CreateAProjectOutput, - }, - "v1CreateASsoProvider": { - id: "v1CreateASsoProvider", - description: "Creates a new SSO provider", - method: "POST", - path: "/v1/projects/{ref}/config/auth/sso/providers", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["type","metadata_xml","metadata_url","domains","attribute_mapping","name_id_format"] }, - response: { kind: "json" }, - inputSchema: V1CreateASsoProviderInput, - outputSchema: V1CreateASsoProviderOutput, - }, - "v1CreateAnOrganization": { - id: "v1CreateAnOrganization", - description: "Create an organization", - method: "POST", - path: "/v1/organizations", - pathParams: [], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["name"] }, - response: { kind: "json" }, - inputSchema: V1CreateAnOrganizationInput, - outputSchema: V1CreateAnOrganizationOutput, - }, - "v1CreateLegacySigningKey": { - id: "v1CreateLegacySigningKey", - description: "Set up the project's existing JWT secret as an in_use JWT signing key. This endpoint will be removed in the future always check for HTTP 404 Not Found.", - method: "POST", - path: "/v1/projects/{ref}/config/auth/signing-keys/legacy", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1CreateLegacySigningKeyInput, - outputSchema: V1CreateLegacySigningKeyOutput, - }, - "v1CreateLoginRole": { - id: "v1CreateLoginRole", - description: "[Beta] Create a login role for CLI with temporary password", - method: "POST", - path: "/v1/projects/{ref}/cli/login-role", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["read_only"] }, - response: { kind: "json" }, - inputSchema: V1CreateLoginRoleInput, - outputSchema: V1CreateLoginRoleOutput, - }, - "v1CreateProjectApiKey": { - id: "v1CreateProjectApiKey", - description: "Creates a new API key for the project", - method: "POST", - path: "/v1/projects/{ref}/api-keys", - pathParams: ["ref"], - queryParams: ["reveal"], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["type","name","description","secret_jwt_template"] }, - response: { kind: "json" }, - inputSchema: V1CreateProjectApiKeyInput, - outputSchema: V1CreateProjectApiKeyOutput, - }, - "v1CreateProjectClaimToken": { - id: "v1CreateProjectClaimToken", - description: "Creates project claim token", - method: "POST", - path: "/v1/projects/{ref}/claim-token", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1CreateProjectClaimTokenInput, - outputSchema: V1CreateProjectClaimTokenOutput, - }, - "v1CreateProjectSigningKey": { - id: "v1CreateProjectSigningKey", - description: "Create a new signing key for the project in standby status", - method: "POST", - path: "/v1/projects/{ref}/config/auth/signing-keys", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["algorithm","status","private_jwk"] }, - response: { kind: "json" }, - inputSchema: V1CreateProjectSigningKeyInput, - outputSchema: V1CreateProjectSigningKeyOutput, - }, - "v1CreateProjectTpaIntegration": { - id: "v1CreateProjectTpaIntegration", - description: "Creates a new third-party auth integration", - method: "POST", - path: "/v1/projects/{ref}/config/auth/third-party-auth", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["oidc_issuer_url","jwks_url","custom_jwks"] }, - response: { kind: "json" }, - inputSchema: V1CreateProjectTpaIntegrationInput, - outputSchema: V1CreateProjectTpaIntegrationOutput, - }, - "v1CreateRestorePoint": { - id: "v1CreateRestorePoint", - description: "Initiates a creation of a restore point for a database", - method: "POST", - path: "/v1/projects/{ref}/database/backups/restore-point", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["name"] }, - response: { kind: "json" }, - inputSchema: V1CreateRestorePointInput, - outputSchema: V1CreateRestorePointOutput, - }, - "v1DeactivateVanitySubdomainConfig": { - id: "v1DeactivateVanitySubdomainConfig", - description: "[Beta] Deletes a project's vanity subdomain configuration", - method: "DELETE", - path: "/v1/projects/{ref}/vanity-subdomain", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DeactivateVanitySubdomainConfigInput, - outputSchema: V1DeactivateVanitySubdomainConfigOutput, - }, - "v1DeleteHostnameConfig": { - id: "v1DeleteHostnameConfig", - description: "[Beta] Deletes a project's custom hostname configuration", - method: "DELETE", - path: "/v1/projects/{ref}/custom-hostname", - pathParams: ["ref"], - queryParams: ["remove_addon"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DeleteHostnameConfigInput, - outputSchema: V1DeleteHostnameConfigOutput, - }, - "v1DeleteABranch": { - id: "v1DeleteABranch", - description: "Deletes the specified database branch. By default, deletes immediately. Use force=false to schedule deletion with 1-hour grace period (only when soft deletion is enabled).", - method: "DELETE", - path: "/v1/branches/{branch_id_or_ref}", - pathParams: ["branch_id_or_ref"], - queryParams: ["force"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1DeleteABranchInput, - outputSchema: V1DeleteABranchOutput, - }, - "v1DeleteAFunction": { - id: "v1DeleteAFunction", - description: "Deletes a function with the specified slug from the specified project.", - method: "DELETE", - path: "/v1/projects/{ref}/functions/{function_slug}", - pathParams: ["ref","function_slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DeleteAFunctionInput, - outputSchema: V1DeleteAFunctionOutput, - }, - "v1DeleteAProject": { - id: "v1DeleteAProject", - description: "Deletes the given project", - method: "DELETE", - path: "/v1/projects/{ref}", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1DeleteAProjectInput, - outputSchema: V1DeleteAProjectOutput, - }, - "v1DeleteASsoProvider": { - id: "v1DeleteASsoProvider", - description: "Removes a SSO provider by its UUID", - method: "DELETE", - path: "/v1/projects/{ref}/config/auth/sso/providers/{provider_id}", - pathParams: ["ref","provider_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1DeleteASsoProviderInput, - outputSchema: V1DeleteASsoProviderOutput, - }, - "v1DeleteInviteExternalJitAccess": { - id: "v1DeleteInviteExternalJitAccess", - description: "Revokes and deletes the invitation", - method: "DELETE", - path: "/v1/projects/{ref}/database/jit/invite/{invite_id}", - pathParams: ["ref","invite_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DeleteInviteExternalJitAccessInput, - outputSchema: V1DeleteInviteExternalJitAccessOutput, - }, - "v1DeleteJitAccess": { - id: "v1DeleteJitAccess", - description: "Remove JIT mappings of a user, revoking all JIT database access", - method: "DELETE", - path: "/v1/projects/{ref}/database/jit/{user_id}", - pathParams: ["ref","user_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DeleteJitAccessInput, - outputSchema: V1DeleteJitAccessOutput, - }, - "v1DeleteLoginRoles": { - id: "v1DeleteLoginRoles", - description: "[Beta] Delete existing login roles used by CLI", - method: "DELETE", - path: "/v1/projects/{ref}/cli/login-role", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1DeleteLoginRolesInput, - outputSchema: V1DeleteLoginRolesOutput, - }, - "v1DeleteNetworkBans": { - id: "v1DeleteNetworkBans", - description: "[Beta] Remove network bans.", - method: "DELETE", - path: "/v1/projects/{ref}/network-bans", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["ipv4_addresses","requester_ip","identifier"] }, - response: { kind: "void" }, - inputSchema: V1DeleteNetworkBansInput, - outputSchema: V1DeleteNetworkBansOutput, - }, - "v1DeleteProjectApiKey": { - id: "v1DeleteProjectApiKey", - description: "Deletes an API key for the project", - method: "DELETE", - path: "/v1/projects/{ref}/api-keys/{id}", - pathParams: ["ref","id"], - queryParams: ["reveal","was_compromised","reason"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1DeleteProjectApiKeyInput, - outputSchema: V1DeleteProjectApiKeyOutput, - }, - "v1DeleteProjectClaimToken": { - id: "v1DeleteProjectClaimToken", - description: "Revokes project claim token", - method: "DELETE", - path: "/v1/projects/{ref}/claim-token", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DeleteProjectClaimTokenInput, - outputSchema: V1DeleteProjectClaimTokenOutput, - }, - "v1DeleteProjectTpaIntegration": { - id: "v1DeleteProjectTpaIntegration", - description: "Removes a third-party auth integration", - method: "DELETE", - path: "/v1/projects/{ref}/config/auth/third-party-auth/{tpa_id}", - pathParams: ["ref","tpa_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1DeleteProjectTpaIntegrationInput, - outputSchema: V1DeleteProjectTpaIntegrationOutput, - }, - "v1DeployAFunction": { - id: "v1DeployAFunction", - description: "A new endpoint to deploy functions. It will create if function does not exist.", - method: "POST", - path: "/v1/projects/{ref}/functions/deploy", - pathParams: ["ref"], - queryParams: ["slug","bundleOnly"], - headerParams: [], - requestBody: { kind: "body", contentType: "multipart/form-data", field: "body" }, - response: { kind: "json" }, - inputSchema: V1DeployAFunctionInput, - outputSchema: V1DeployAFunctionOutput, - }, - "v1DiffABranch": { - id: "v1DiffABranch", - description: "Diffs the specified database branch", - method: "GET", - path: "/v1/branches/{branch_id_or_ref}/diff", - pathParams: ["branch_id_or_ref"], - queryParams: ["included_schemas","pgdelta"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "text" }, - inputSchema: V1DiffABranchInput, - outputSchema: V1DiffABranchOutput, - }, - "v1DisablePreviewBranching": { - id: "v1DisablePreviewBranching", - description: "Disables preview branching for the specified project", - method: "DELETE", - path: "/v1/projects/{ref}/branches", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DisablePreviewBranchingInput, - outputSchema: V1DisablePreviewBranchingOutput, - }, - "v1DisableReadonlyModeTemporarily": { - id: "v1DisableReadonlyModeTemporarily", - description: "Disables project's readonly mode for the next 15 minutes", - method: "POST", - path: "/v1/projects/{ref}/readonly/temporary-disable", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1DisableReadonlyModeTemporarilyInput, - outputSchema: V1DisableReadonlyModeTemporarilyOutput, - }, - "v1EnableDatabaseWebhook": { - id: "v1EnableDatabaseWebhook", - description: "[Beta] Enables Database Webhooks on the project", - method: "POST", - path: "/v1/projects/{ref}/database/webhooks/enable", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1EnableDatabaseWebhookInput, - outputSchema: V1EnableDatabaseWebhookOutput, - }, - "v1ExchangeOauthToken": { - id: "v1ExchangeOauthToken", - description: "Supports `authorization_code`, `refresh_token`, and `urn:ietf:params:oauth:grant-type:jwt-bearer` grant types. The `jwt-bearer` grant type (IDJAG — identity-directed JWT assertion) is in beta and available on Team and Enterprise plans only.", - method: "POST", - path: "/v1/oauth/token", - pathParams: [], - queryParams: [], - headerParams: [], - requestBody: { kind: "body", contentType: "application/x-www-form-urlencoded", field: "body" }, - response: { kind: "json" }, - inputSchema: V1ExchangeOauthTokenInput, - outputSchema: V1ExchangeOauthTokenOutput, - }, - "v1GenerateTypescriptTypes": { - id: "v1GenerateTypescriptTypes", - description: "Returns the TypeScript types of your schema for use with supabase-js.", - method: "GET", - path: "/v1/projects/{ref}/types/typescript", - pathParams: ["ref"], - queryParams: ["included_schemas"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GenerateTypescriptTypesInput, - outputSchema: V1GenerateTypescriptTypesOutput, - }, - "v1GetABranch": { - id: "v1GetABranch", - description: "Fetches the specified database branch by its name.", - method: "GET", - path: "/v1/projects/{ref}/branches/{name}", - pathParams: ["ref","name"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetABranchInput, - outputSchema: V1GetABranchOutput, - }, - "v1GetABranchConfig": { - id: "v1GetABranchConfig", - description: "Fetches configurations of the specified database branch", - method: "GET", - path: "/v1/branches/{branch_id_or_ref}", - pathParams: ["branch_id_or_ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetABranchConfigInput, - outputSchema: V1GetABranchConfigOutput, - }, - "v1GetAFunction": { - id: "v1GetAFunction", - description: "Retrieves a function with the specified slug and project.", - method: "GET", - path: "/v1/projects/{ref}/functions/{function_slug}", - pathParams: ["ref","function_slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetAFunctionInput, - outputSchema: V1GetAFunctionOutput, - }, - "v1GetAFunctionBody": { - id: "v1GetAFunctionBody", - description: "Retrieves a function body for the specified slug and project.", - method: "GET", - path: "/v1/projects/{ref}/functions/{function_slug}/body", - pathParams: ["ref","function_slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetAFunctionBodyInput, - outputSchema: V1GetAFunctionBodyOutput, - }, - "v1GetAMigration": { - id: "v1GetAMigration", - description: "Fetch an existing entry from migration history", - method: "GET", - path: "/v1/projects/{ref}/database/migrations/{version}", - pathParams: ["ref","version"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetAMigrationInput, - outputSchema: V1GetAMigrationOutput, - }, - "v1GetASnippet": { - id: "v1GetASnippet", - description: "Gets a specific SQL snippet", - method: "GET", - path: "/v1/snippets/{id}", - pathParams: ["id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetASnippetInput, - outputSchema: V1GetASnippetOutput, - }, - "v1GetASsoProvider": { - id: "v1GetASsoProvider", - description: "Gets a SSO provider by its UUID", - method: "GET", - path: "/v1/projects/{ref}/config/auth/sso/providers/{provider_id}", - pathParams: ["ref","provider_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetASsoProviderInput, - outputSchema: V1GetASsoProviderOutput, - }, - "v1GetActionRun": { - id: "v1GetActionRun", - description: "Returns the current status of the specified action run.", - method: "GET", - path: "/v1/projects/{ref}/actions/{run_id}", - pathParams: ["ref","run_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetActionRunInput, - outputSchema: V1GetActionRunOutput, - }, - "v1GetActionRunLogs": { - id: "v1GetActionRunLogs", - description: "Returns the logs from the specified action run.", - method: "GET", - path: "/v1/projects/{ref}/actions/{run_id}/logs", - pathParams: ["ref","run_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "text" }, - inputSchema: V1GetActionRunLogsInput, - outputSchema: V1GetActionRunLogsOutput, - }, - "v1GetAllProjectsForOrganization": { - id: "v1GetAllProjectsForOrganization", - description: "Returns a paginated list of projects for the specified organization.\n\nThis endpoint uses offset-based pagination. Use the `offset` parameter to skip a number of projects and the `limit` parameter to control the number of projects returned per page.", - method: "GET", - path: "/v1/organizations/{slug}/projects", - pathParams: ["slug"], - queryParams: ["offset","limit","search","sort","statuses"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetAllProjectsForOrganizationInput, - outputSchema: V1GetAllProjectsForOrganizationOutput, - }, - "v1GetAnOrganization": { - id: "v1GetAnOrganization", - description: "Gets information about the organization", - method: "GET", - path: "/v1/organizations/{slug}", - pathParams: ["slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetAnOrganizationInput, - outputSchema: V1GetAnOrganizationOutput, - }, - "v1GetAuthServiceConfig": { - id: "v1GetAuthServiceConfig", - description: "Gets project's auth config", - method: "GET", - path: "/v1/projects/{ref}/config/auth", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetAuthServiceConfigInput, - outputSchema: V1GetAuthServiceConfigOutput, - }, - "v1GetAvailableRegions": { - id: "v1GetAvailableRegions", - description: "[Beta] Gets the list of available regions that can be used for a new project", - method: "GET", - path: "/v1/projects/available-regions", - pathParams: [], - queryParams: ["organization_slug","continent","desired_instance_size"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetAvailableRegionsInput, - outputSchema: V1GetAvailableRegionsOutput, - }, - "v1GetBackupSchedule": { - id: "v1GetBackupSchedule", - description: "Gets the backup schedule for a project", - method: "GET", - path: "/v1/projects/{ref}/database/backups/schedule", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetBackupScheduleInput, - outputSchema: V1GetBackupScheduleOutput, - }, - "v1GetDatabaseDisk": { - id: "v1GetDatabaseDisk", - description: "Get database disk attributes", - method: "GET", - path: "/v1/projects/{ref}/config/disk", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetDatabaseDiskInput, - outputSchema: V1GetDatabaseDiskOutput, - }, - "v1GetDatabaseMetadata": { - id: "v1GetDatabaseMetadata", - description: "This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.", - method: "GET", - path: "/v1/projects/{ref}/database/context", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetDatabaseMetadataInput, - outputSchema: V1GetDatabaseMetadataOutput, - }, - "v1GetDatabaseOpenapi": { - id: "v1GetDatabaseOpenapi", - description: "Returns the PostgREST OpenAPI specification for the project. This is the replacement for querying `/rest/v1/` directly with the anon key.", - method: "GET", - path: "/v1/projects/{ref}/database/openapi", - pathParams: ["ref"], - queryParams: ["schema"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetDatabaseOpenapiInput, - outputSchema: V1GetDatabaseOpenapiOutput, - }, - "v1GetDiskUtilization": { - id: "v1GetDiskUtilization", - description: "Get disk utilization", - method: "GET", - path: "/v1/projects/{ref}/config/disk/util", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetDiskUtilizationInput, - outputSchema: V1GetDiskUtilizationOutput, - }, - "v1GetHostnameConfig": { - id: "v1GetHostnameConfig", - description: "[Beta] Gets project's custom hostname config", - method: "GET", - path: "/v1/projects/{ref}/custom-hostname", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetHostnameConfigInput, - outputSchema: V1GetHostnameConfigOutput, - }, - "v1GetJitAccess": { - id: "v1GetJitAccess", - description: "Mappings of roles a user can assume in the project database", - method: "GET", - path: "/v1/projects/{ref}/database/jit", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetJitAccessInput, - outputSchema: V1GetJitAccessOutput, - }, - "v1GetJitAccessConfig": { - id: "v1GetJitAccessConfig", - description: "[Beta] Get project's temporary access configuration.", - method: "GET", - path: "/v1/projects/{ref}/jit-access", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetJitAccessConfigInput, - outputSchema: V1GetJitAccessConfigOutput, - }, - "v1GetLegacySigningKey": { - id: "v1GetLegacySigningKey", - description: "Get the signing key information for the JWT secret imported as signing key for this project. This endpoint will be removed in the future, check for HTTP 404 Not Found.", - method: "GET", - path: "/v1/projects/{ref}/config/auth/signing-keys/legacy", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetLegacySigningKeyInput, - outputSchema: V1GetLegacySigningKeyOutput, - }, - "v1GetNetworkRestrictions": { - id: "v1GetNetworkRestrictions", - description: "[Beta] Gets project's network restrictions", - method: "GET", - path: "/v1/projects/{ref}/network-restrictions", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetNetworkRestrictionsInput, - outputSchema: V1GetNetworkRestrictionsOutput, - }, - "v1GetOrganizationEntitlements": { - id: "v1GetOrganizationEntitlements", - description: "Returns the entitlements available to the organization based on their plan and any overrides.", - method: "GET", - path: "/v1/organizations/{slug}/entitlements", - pathParams: ["slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetOrganizationEntitlementsInput, - outputSchema: V1GetOrganizationEntitlementsOutput, - }, - "v1GetOrganizationProjectClaim": { - id: "v1GetOrganizationProjectClaim", - description: "Gets project details for the specified organization and claim token", - method: "GET", - path: "/v1/organizations/{slug}/project-claim/{token}", - pathParams: ["slug","token"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetOrganizationProjectClaimInput, - outputSchema: V1GetOrganizationProjectClaimOutput, - }, - "v1GetPerformanceAdvisors": { - id: "v1GetPerformanceAdvisors", - description: "This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.", - method: "GET", - path: "/v1/projects/{ref}/advisors/performance", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetPerformanceAdvisorsInput, - outputSchema: V1GetPerformanceAdvisorsOutput, - }, - "v1GetPgsodiumConfig": { - id: "v1GetPgsodiumConfig", - description: "[Beta] Gets project's pgsodium config", - method: "GET", - path: "/v1/projects/{ref}/pgsodium", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetPgsodiumConfigInput, - outputSchema: V1GetPgsodiumConfigOutput, - }, - "v1GetPoolerConfig": { - id: "v1GetPoolerConfig", - description: "Gets project's supavisor config", - method: "GET", - path: "/v1/projects/{ref}/config/database/pooler", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetPoolerConfigInput, - outputSchema: V1GetPoolerConfigOutput, - }, - "v1GetPostgresConfig": { - id: "v1GetPostgresConfig", - description: "Gets project's Postgres config", - method: "GET", - path: "/v1/projects/{ref}/config/database/postgres", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetPostgresConfigInput, - outputSchema: V1GetPostgresConfigOutput, - }, - "v1GetPostgresUpgradeEligibility": { - id: "v1GetPostgresUpgradeEligibility", - description: "[Beta] Returns the project's eligibility for upgrades", - method: "GET", - path: "/v1/projects/{ref}/upgrade/eligibility", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetPostgresUpgradeEligibilityInput, - outputSchema: V1GetPostgresUpgradeEligibilityOutput, - }, - "v1GetPostgresUpgradeStatus": { - id: "v1GetPostgresUpgradeStatus", - description: "[Beta] Gets the latest status of the project's upgrade", - method: "GET", - path: "/v1/projects/{ref}/upgrade/status", - pathParams: ["ref"], - queryParams: ["tracking_id"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetPostgresUpgradeStatusInput, - outputSchema: V1GetPostgresUpgradeStatusOutput, - }, - "v1GetPostgrestServiceConfig": { - id: "v1GetPostgrestServiceConfig", - description: "Gets project's postgrest config", - method: "GET", - path: "/v1/projects/{ref}/postgrest", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetPostgrestServiceConfigInput, - outputSchema: V1GetPostgrestServiceConfigOutput, - }, - "v1GetProfile": { - id: "v1GetProfile", - description: "Gets the user's profile", - method: "GET", - path: "/v1/profile", - pathParams: [], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProfileInput, - outputSchema: V1GetProfileOutput, - }, - "v1GetProject": { - id: "v1GetProject", - description: "Gets a specific project that belongs to the authenticated user", - method: "GET", - path: "/v1/projects/{ref}", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectInput, - outputSchema: V1GetProjectOutput, - }, - "v1GetProjectApiKey": { - id: "v1GetProjectApiKey", - description: "Get API key", - method: "GET", - path: "/v1/projects/{ref}/api-keys/{id}", - pathParams: ["ref","id"], - queryParams: ["reveal"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectApiKeyInput, - outputSchema: V1GetProjectApiKeyOutput, - }, - "v1GetProjectApiKeys": { - id: "v1GetProjectApiKeys", - description: "Get project api keys", - method: "GET", - path: "/v1/projects/{ref}/api-keys", - pathParams: ["ref"], - queryParams: ["reveal"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectApiKeysInput, - outputSchema: V1GetProjectApiKeysOutput, - }, - "v1GetProjectClaimToken": { - id: "v1GetProjectClaimToken", - description: "Gets project claim token", - method: "GET", - path: "/v1/projects/{ref}/claim-token", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectClaimTokenInput, - outputSchema: V1GetProjectClaimTokenOutput, - }, - "v1GetProjectDiskAutoscaleConfig": { - id: "v1GetProjectDiskAutoscaleConfig", - description: "Gets project disk autoscale config", - method: "GET", - path: "/v1/projects/{ref}/config/disk/autoscale", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectDiskAutoscaleConfigInput, - outputSchema: V1GetProjectDiskAutoscaleConfigOutput, - }, - "v1GetProjectFunctionCombinedStats": { - id: "v1GetProjectFunctionCombinedStats", - description: "Gets a project's function combined statistics", - method: "GET", - path: "/v1/projects/{ref}/analytics/endpoints/functions.combined-stats", - pathParams: ["ref"], - queryParams: ["interval","function_id"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectFunctionCombinedStatsInput, - outputSchema: V1GetProjectFunctionCombinedStatsOutput, - }, - "v1GetProjectLegacyApiKeys": { - id: "v1GetProjectLegacyApiKeys", - description: "Check whether JWT based legacy (anon, service_role) API keys are enabled. This API endpoint will be removed in the future, check for HTTP 404 Not Found.", - method: "GET", - path: "/v1/projects/{ref}/api-keys/legacy", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectLegacyApiKeysInput, - outputSchema: V1GetProjectLegacyApiKeysOutput, - }, - "v1GetProjectLogs": { - id: "v1GetProjectLogs", - description: "Executes an SQL or LQL query on the project's unified logs stream.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nFilter by the `source` column to specify specific log sources, such as edge_logs, postgres_logs, etc.\n\nNote: SQL must be written in **ClickHouse SQL dialect**.", - method: "GET", - path: "/v1/projects/{ref}/analytics/endpoints/logs", - pathParams: ["ref"], - queryParams: ["sql","iso_timestamp_start","iso_timestamp_end"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectLogsInput, - outputSchema: V1GetProjectLogsOutput, - }, - "v1GetProjectLogsAll": { - id: "v1GetProjectLogsAll", - description: "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](https://supabase.com/docs/guides/monitoring-and-debugging/logs#logs-explorer) for all available sources.", - method: "GET", - path: "/v1/projects/{ref}/analytics/endpoints/logs.all", - pathParams: ["ref"], - queryParams: ["sql","iso_timestamp_start","iso_timestamp_end"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectLogsAllInput, - outputSchema: V1GetProjectLogsAllOutput, - }, - "v1GetProjectPgbouncerConfig": { - id: "v1GetProjectPgbouncerConfig", - description: "Get project's pgbouncer config", - method: "GET", - path: "/v1/projects/{ref}/config/database/pgbouncer", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectPgbouncerConfigInput, - outputSchema: V1GetProjectPgbouncerConfigOutput, - }, - "v1GetProjectSigningKey": { - id: "v1GetProjectSigningKey", - description: "Get information about a signing key", - method: "GET", - path: "/v1/projects/{ref}/config/auth/signing-keys/{id}", - pathParams: ["id","ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectSigningKeyInput, - outputSchema: V1GetProjectSigningKeyOutput, - }, - "v1GetProjectSigningKeys": { - id: "v1GetProjectSigningKeys", - description: "List all signing keys for the project", - method: "GET", - path: "/v1/projects/{ref}/config/auth/signing-keys", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectSigningKeysInput, - outputSchema: V1GetProjectSigningKeysOutput, - }, - "v1GetProjectTpaIntegration": { - id: "v1GetProjectTpaIntegration", - description: "Get a third-party integration", - method: "GET", - path: "/v1/projects/{ref}/config/auth/third-party-auth/{tpa_id}", - pathParams: ["ref","tpa_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectTpaIntegrationInput, - outputSchema: V1GetProjectTpaIntegrationOutput, - }, - "v1GetProjectUsageApiCount": { - id: "v1GetProjectUsageApiCount", - description: "Gets project's usage api counts", - method: "GET", - path: "/v1/projects/{ref}/analytics/endpoints/usage.api-counts", - pathParams: ["ref"], - queryParams: ["interval"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectUsageApiCountInput, - outputSchema: V1GetProjectUsageApiCountOutput, - }, - "v1GetProjectUsageRequestCount": { - id: "v1GetProjectUsageRequestCount", - description: "Gets project's usage api requests count", - method: "GET", - path: "/v1/projects/{ref}/analytics/endpoints/usage.api-requests-count", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetProjectUsageRequestCountInput, - outputSchema: V1GetProjectUsageRequestCountOutput, - }, - "v1GetReadonlyModeStatus": { - id: "v1GetReadonlyModeStatus", - description: "Returns project's readonly mode status", - method: "GET", - path: "/v1/projects/{ref}/readonly", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetReadonlyModeStatusInput, - outputSchema: V1GetReadonlyModeStatusOutput, - }, - "v1GetRealtimeConfig": { - id: "v1GetRealtimeConfig", - description: "Gets realtime configuration", - method: "GET", - path: "/v1/projects/{ref}/config/realtime", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetRealtimeConfigInput, - outputSchema: V1GetRealtimeConfigOutput, - }, - "v1GetRestorePoint": { - id: "v1GetRestorePoint", - description: "Get restore points for project", - method: "GET", - path: "/v1/projects/{ref}/database/backups/restore-point", - pathParams: ["ref"], - queryParams: ["name"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetRestorePointInput, - outputSchema: V1GetRestorePointOutput, - }, - "v1GetSecurityAdvisors": { - id: "v1GetSecurityAdvisors", - description: "This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.", - method: "GET", - path: "/v1/projects/{ref}/advisors/security", - pathParams: ["ref"], - queryParams: ["lint_type"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetSecurityAdvisorsInput, - outputSchema: V1GetSecurityAdvisorsOutput, - }, - "v1GetServicesHealth": { - id: "v1GetServicesHealth", - description: "Gets project's service health status", - method: "GET", - path: "/v1/projects/{ref}/health", - pathParams: ["ref"], - queryParams: ["services","timeout_ms"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetServicesHealthInput, - outputSchema: V1GetServicesHealthOutput, - }, - "v1GetSslEnforcementConfig": { - id: "v1GetSslEnforcementConfig", - description: "[Beta] Get project's SSL enforcement configuration.", - method: "GET", - path: "/v1/projects/{ref}/ssl-enforcement", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetSslEnforcementConfigInput, - outputSchema: V1GetSslEnforcementConfigOutput, - }, - "v1GetStorageConfig": { - id: "v1GetStorageConfig", - description: "Gets project's storage config", - method: "GET", - path: "/v1/projects/{ref}/config/storage", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetStorageConfigInput, - outputSchema: V1GetStorageConfigOutput, - }, - "v1GetVanitySubdomainConfig": { - id: "v1GetVanitySubdomainConfig", - description: "[Beta] Gets current vanity subdomain config", - method: "GET", - path: "/v1/projects/{ref}/vanity-subdomain", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1GetVanitySubdomainConfigInput, - outputSchema: V1GetVanitySubdomainConfigOutput, - }, - "v1InviteExternalJitAccess": { - id: "v1InviteExternalJitAccess", - description: "Invites the external user and sets initial roles that can be assumed and for how long", - method: "POST", - path: "/v1/projects/{ref}/database/jit/invite", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["email","roles"] }, - response: { kind: "json" }, - inputSchema: V1InviteExternalJitAccessInput, - outputSchema: V1InviteExternalJitAccessOutput, - }, - "v1ListActionRuns": { - id: "v1ListActionRuns", - description: "Returns a paginated list of action runs of the specified project.", - method: "GET", - path: "/v1/projects/{ref}/actions", - pathParams: ["ref"], - queryParams: ["offset","limit"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListActionRunsInput, - outputSchema: V1ListActionRunsOutput, - }, - "v1ListAllBackups": { - id: "v1ListAllBackups", - description: "Lists all backups", - method: "GET", - path: "/v1/projects/{ref}/database/backups", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllBackupsInput, - outputSchema: V1ListAllBackupsOutput, - }, - "v1ListAllBranches": { - id: "v1ListAllBranches", - description: "Returns all database branches of the specified project.", - method: "GET", - path: "/v1/projects/{ref}/branches", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllBranchesInput, - outputSchema: V1ListAllBranchesOutput, - }, - "v1ListAllBuckets": { - id: "v1ListAllBuckets", - description: "Lists all buckets", - method: "GET", - path: "/v1/projects/{ref}/storage/buckets", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllBucketsInput, - outputSchema: V1ListAllBucketsOutput, - }, - "v1ListAllFunctions": { - id: "v1ListAllFunctions", - description: "Returns all functions you've previously added to the specified project.", - method: "GET", - path: "/v1/projects/{ref}/functions", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllFunctionsInput, - outputSchema: V1ListAllFunctionsOutput, - }, - "v1ListAllNetworkBans": { - id: "v1ListAllNetworkBans", - description: "[Beta] Gets project's network bans", - method: "POST", - path: "/v1/projects/{ref}/network-bans/retrieve", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllNetworkBansInput, - outputSchema: V1ListAllNetworkBansOutput, - }, - "v1ListAllNetworkBansEnriched": { - id: "v1ListAllNetworkBansEnriched", - description: "[Beta] Gets project's network bans with additional information about which databases they affect", - method: "POST", - path: "/v1/projects/{ref}/network-bans/retrieve/enriched", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllNetworkBansEnrichedInput, - outputSchema: V1ListAllNetworkBansEnrichedOutput, - }, - "v1ListAllOrganizations": { - id: "v1ListAllOrganizations", - description: "Returns a list of organizations that you currently belong to.", - method: "GET", - path: "/v1/organizations", - pathParams: [], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllOrganizationsInput, - outputSchema: V1ListAllOrganizationsOutput, - }, - "v1ListAllProjects": { - id: "v1ListAllProjects", - description: "Returns a list of all projects you've previously created.", - method: "GET", - path: "/v1/projects", - pathParams: [], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllProjectsInput, - outputSchema: V1ListAllProjectsOutput, - }, - "v1ListAllSecrets": { - id: "v1ListAllSecrets", - description: "Returns all secrets you've previously added to the specified project.", - method: "GET", - path: "/v1/projects/{ref}/secrets", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllSecretsInput, - outputSchema: V1ListAllSecretsOutput, - }, - "v1ListAllSnippets": { - id: "v1ListAllSnippets", - description: "Lists SQL snippets for the logged in user", - method: "GET", - path: "/v1/snippets", - pathParams: [], - queryParams: ["project_ref","cursor","limit","sort_by","sort_order"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllSnippetsInput, - outputSchema: V1ListAllSnippetsOutput, - }, - "v1ListAllSsoProvider": { - id: "v1ListAllSsoProvider", - description: "Lists all SSO providers", - method: "GET", - path: "/v1/projects/{ref}/config/auth/sso/providers", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAllSsoProviderInput, - outputSchema: V1ListAllSsoProviderOutput, - }, - "v1ListAvailableRestoreVersions": { - id: "v1ListAvailableRestoreVersions", - description: "Lists available restore versions for the given project", - method: "GET", - path: "/v1/projects/{ref}/restore", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListAvailableRestoreVersionsInput, - outputSchema: V1ListAvailableRestoreVersionsOutput, - }, - "v1ListJitAccess": { - id: "v1ListJitAccess", - description: "Mappings of roles a user can assume in the project database", - method: "GET", - path: "/v1/projects/{ref}/database/jit/list", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListJitAccessInput, - outputSchema: V1ListJitAccessOutput, - }, - "v1ListMigrationHistory": { - id: "v1ListMigrationHistory", - description: "List applied migration versions", - method: "GET", - path: "/v1/projects/{ref}/database/migrations", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListMigrationHistoryInput, - outputSchema: V1ListMigrationHistoryOutput, - }, - "v1ListOrganizationMembers": { - id: "v1ListOrganizationMembers", - description: "List members of an organization", - method: "GET", - path: "/v1/organizations/{slug}/members", - pathParams: ["slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListOrganizationMembersInput, - outputSchema: V1ListOrganizationMembersOutput, - }, - "v1ListProjectAddons": { - id: "v1ListProjectAddons", - description: "Returns the billing addons that are currently applied, including the active compute instance size, and lists every addon option that can be provisioned with pricing metadata.", - method: "GET", - path: "/v1/projects/{ref}/billing/addons", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListProjectAddonsInput, - outputSchema: V1ListProjectAddonsOutput, - }, - "v1ListProjectTpaIntegrations": { - id: "v1ListProjectTpaIntegrations", - description: "Lists all third-party auth integrations", - method: "GET", - path: "/v1/projects/{ref}/config/auth/third-party-auth", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1ListProjectTpaIntegrationsInput, - outputSchema: V1ListProjectTpaIntegrationsOutput, - }, - "v1MergeABranch": { - id: "v1MergeABranch", - description: "Merges the specified database branch", - method: "POST", - path: "/v1/branches/{branch_id_or_ref}/merge", - pathParams: ["branch_id_or_ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["migration_version"] }, - response: { kind: "json" }, - inputSchema: V1MergeABranchInput, - outputSchema: V1MergeABranchOutput, - }, - "v1ModifyDatabaseDisk": { - id: "v1ModifyDatabaseDisk", - description: "Modify database disk", - method: "POST", - path: "/v1/projects/{ref}/config/disk", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["attributes"] }, - response: { kind: "void" }, - inputSchema: V1ModifyDatabaseDiskInput, - outputSchema: V1ModifyDatabaseDiskOutput, - }, - "v1OauthAuthorizeProjectClaim": { - id: "v1OauthAuthorizeProjectClaim", - description: "Initiates the OAuth authorization flow for the specified provider. After successful authentication, the user can claim ownership of the specified project.", - method: "GET", - path: "/v1/oauth/authorize/project-claim", - pathParams: [], - queryParams: ["project_ref","client_id","response_type","redirect_uri","state","response_mode","code_challenge","code_challenge_method"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1OauthAuthorizeProjectClaimInput, - outputSchema: V1OauthAuthorizeProjectClaimOutput, - }, - "v1PatchAMigration": { - id: "v1PatchAMigration", - description: "Patch an existing entry in migration history", - method: "PATCH", - path: "/v1/projects/{ref}/database/migrations/{version}", - pathParams: ["ref","version"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["name","rollback"] }, - response: { kind: "void" }, - inputSchema: V1PatchAMigrationInput, - outputSchema: V1PatchAMigrationOutput, - }, - "v1PatchNetworkRestrictions": { - id: "v1PatchNetworkRestrictions", - description: "[Alpha] Updates project's network restrictions by adding or removing CIDRs", - method: "PATCH", - path: "/v1/projects/{ref}/network-restrictions", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["add","remove"] }, - response: { kind: "json" }, - inputSchema: V1PatchNetworkRestrictionsInput, - outputSchema: V1PatchNetworkRestrictionsOutput, - }, - "v1PauseAProject": { - id: "v1PauseAProject", - description: "Pauses the given project", - method: "POST", - path: "/v1/projects/{ref}/pause", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1PauseAProjectInput, - outputSchema: V1PauseAProjectOutput, - }, - "v1PushABranch": { - id: "v1PushABranch", - description: "Pushes the specified database branch", - method: "POST", - path: "/v1/branches/{branch_id_or_ref}/push", - pathParams: ["branch_id_or_ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["migration_version"] }, - response: { kind: "json" }, - inputSchema: V1PushABranchInput, - outputSchema: V1PushABranchOutput, - }, - "v1ReadOnlyQuery": { - id: "v1ReadOnlyQuery", - description: "All entity references must be schema qualified.", - method: "POST", - path: "/v1/projects/{ref}/database/query/read-only", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["query","parameters"] }, - response: { kind: "void" }, - inputSchema: V1ReadOnlyQueryInput, - outputSchema: V1ReadOnlyQueryOutput, - }, - "v1RemoveAReadReplica": { - id: "v1RemoveAReadReplica", - description: "[Beta] Remove a read replica", - method: "POST", - path: "/v1/projects/{ref}/read-replicas/remove", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["database_identifier"] }, - response: { kind: "void" }, - inputSchema: V1RemoveAReadReplicaInput, - outputSchema: V1RemoveAReadReplicaOutput, - }, - "v1RemoveProjectAddon": { - id: "v1RemoveProjectAddon", - description: "Disables the selected addon variant, including rolling the compute instance back to its previous size.", - method: "DELETE", - path: "/v1/projects/{ref}/billing/addons/{addon_variant}", - pathParams: ["ref","addon_variant"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1RemoveProjectAddonInput, - outputSchema: V1RemoveProjectAddonOutput, - }, - "v1RemoveProjectSigningKey": { - id: "v1RemoveProjectSigningKey", - description: "Remove a signing key from a project. Only possible if the key has been in revoked status for a while.", - method: "DELETE", - path: "/v1/projects/{ref}/config/auth/signing-keys/{id}", - pathParams: ["id","ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1RemoveProjectSigningKeyInput, - outputSchema: V1RemoveProjectSigningKeyOutput, - }, - "v1ResetABranch": { - id: "v1ResetABranch", - description: "Resets the specified database branch", - method: "POST", - path: "/v1/branches/{branch_id_or_ref}/reset", - pathParams: ["branch_id_or_ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["migration_version"] }, - response: { kind: "json" }, - inputSchema: V1ResetABranchInput, - outputSchema: V1ResetABranchOutput, - }, - "v1RestartAProject": { - id: "v1RestartAProject", - description: "Restarts the given project", - method: "POST", - path: "/v1/projects/{ref}/restart", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1RestartAProjectInput, - outputSchema: V1RestartAProjectOutput, - }, - "v1RestoreABranch": { - id: "v1RestoreABranch", - description: "Cancels scheduled deletion and restores the branch to active state", - method: "POST", - path: "/v1/branches/{branch_id_or_ref}/restore", - pathParams: ["branch_id_or_ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1RestoreABranchInput, - outputSchema: V1RestoreABranchOutput, - }, - "v1RestoreAProject": { - id: "v1RestoreAProject", - description: "Restores the given project", - method: "POST", - path: "/v1/projects/{ref}/restore", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1RestoreAProjectInput, - outputSchema: V1RestoreAProjectOutput, - }, - "v1RestorePhysicalBackup": { - id: "v1RestorePhysicalBackup", - description: "Restores a physical backup for a database", - method: "POST", - path: "/v1/projects/{ref}/database/backups/restore", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["id"] }, - response: { kind: "void" }, - inputSchema: V1RestorePhysicalBackupInput, - outputSchema: V1RestorePhysicalBackupOutput, - }, - "v1RestorePitrBackup": { - id: "v1RestorePitrBackup", - description: "Restores a PITR backup for a database", - method: "POST", - path: "/v1/projects/{ref}/database/backups/restore-pitr", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["recovery_time_target_unix"] }, - response: { kind: "void" }, - inputSchema: V1RestorePitrBackupInput, - outputSchema: V1RestorePitrBackupOutput, - }, - "v1RevokeToken": { - id: "v1RevokeToken", - description: "[Beta] Revoke oauth app authorization and it's corresponding tokens", - method: "POST", - path: "/v1/oauth/revoke", - pathParams: [], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["client_id","client_secret","refresh_token"] }, - response: { kind: "void" }, - inputSchema: V1RevokeTokenInput, - outputSchema: V1RevokeTokenOutput, - }, - "v1RollbackMigrations": { - id: "v1RollbackMigrations", - description: "Rollback database migrations and remove them from history table", - method: "DELETE", - path: "/v1/projects/{ref}/database/migrations", - pathParams: ["ref"], - queryParams: ["gte"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1RollbackMigrationsInput, - outputSchema: V1RollbackMigrationsOutput, - }, - "v1RunAQuery": { - id: "v1RunAQuery", - description: "[Beta] Run sql query", - method: "POST", - path: "/v1/projects/{ref}/database/query", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["query","parameters","read_only"] }, - response: { kind: "void" }, - inputSchema: V1RunAQueryInput, - outputSchema: V1RunAQueryOutput, - }, - "v1ScrapeProjectMetrics": { - id: "v1ScrapeProjectMetrics", - description: "Prometheus scrape endpoint. Returns metrics of a customer project in the Prometheus open exposition format.", - method: "GET", - path: "/v1/projects/{ref}/analytics/endpoints/metrics", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "text" }, - inputSchema: V1ScrapeProjectMetricsInput, - outputSchema: V1ScrapeProjectMetricsOutput, - }, - "v1SetupAReadReplica": { - id: "v1SetupAReadReplica", - description: "[Beta] Set up a read replica", - method: "POST", - path: "/v1/projects/{ref}/read-replicas/setup", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["read_replica_region"] }, - response: { kind: "void" }, - inputSchema: V1SetupAReadReplicaInput, - outputSchema: V1SetupAReadReplicaOutput, - }, - "v1ShutdownRealtime": { - id: "v1ShutdownRealtime", - description: "Shutdowns realtime connections for a project", - method: "POST", - path: "/v1/projects/{ref}/config/realtime/shutdown", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V1ShutdownRealtimeInput, - outputSchema: V1ShutdownRealtimeOutput, - }, - "v1Undo": { - id: "v1Undo", - description: "Initiates an undo to a given restore point", - method: "POST", - path: "/v1/projects/{ref}/database/backups/undo", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["name"] }, - response: { kind: "void" }, - inputSchema: V1UndoInput, - outputSchema: V1UndoOutput, - }, - "v1UpdateABranchConfig": { - id: "v1UpdateABranchConfig", - description: "Updates the configuration of the specified database branch", - method: "PATCH", - path: "/v1/branches/{branch_id_or_ref}", - pathParams: ["branch_id_or_ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["branch_name","git_branch","reset_on_push","persistent","status","request_review","notify_url"] }, - response: { kind: "json" }, - inputSchema: V1UpdateABranchConfigInput, - outputSchema: V1UpdateABranchConfigOutput, - }, - "v1UpdateAFunction": { - id: "v1UpdateAFunction", - description: "Updates a function with the specified slug and project.", - method: "PATCH", - path: "/v1/projects/{ref}/functions/{function_slug}", - pathParams: ["ref","function_slug"], - queryParams: ["slug","name","verify_jwt","import_map","entrypoint_path","import_map_path","ezbr_sha256"], - headerParams: [], - requestBody: { kind: "body", contentType: "application/vnd.denoland.eszip", field: "body" }, - response: { kind: "json" }, - inputSchema: V1UpdateAFunctionInput, - outputSchema: V1UpdateAFunctionOutput, - }, - "v1UpdateAProject": { - id: "v1UpdateAProject", - description: "Updates the given project", - method: "PATCH", - path: "/v1/projects/{ref}", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["name"] }, - response: { kind: "json" }, - inputSchema: V1UpdateAProjectInput, - outputSchema: V1UpdateAProjectOutput, - }, - "v1UpdateASsoProvider": { - id: "v1UpdateASsoProvider", - description: "Updates a SSO provider by its UUID", - method: "PUT", - path: "/v1/projects/{ref}/config/auth/sso/providers/{provider_id}", - pathParams: ["ref","provider_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["metadata_xml","metadata_url","domains","attribute_mapping","name_id_format"] }, - response: { kind: "json" }, - inputSchema: V1UpdateASsoProviderInput, - outputSchema: V1UpdateASsoProviderOutput, - }, - "v1UpdateActionRunStatus": { - id: "v1UpdateActionRunStatus", - description: "Updates the status of an ongoing action run.", - method: "PATCH", - path: "/v1/projects/{ref}/actions/{run_id}/status", - pathParams: ["ref","run_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["clone","pull","health","configure","migrate","seed","deploy"] }, - response: { kind: "json" }, - inputSchema: V1UpdateActionRunStatusInput, - outputSchema: V1UpdateActionRunStatusOutput, - }, - "v1UpdateAuthServiceConfig": { - id: "v1UpdateAuthServiceConfig", - description: "Updates a project's auth config", - method: "PATCH", - path: "/v1/projects/{ref}/config/auth", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["site_url","disable_signup","jwt_exp","smtp_admin_email","smtp_host","smtp_port","smtp_user","smtp_pass","smtp_max_frequency","smtp_sender_name","mailer_allow_unverified_email_sign_ins","mailer_autoconfirm","mailer_subjects_invite","mailer_subjects_confirmation","mailer_subjects_recovery","mailer_subjects_email_change","mailer_subjects_magic_link","mailer_subjects_reauthentication","mailer_subjects_password_changed_notification","mailer_subjects_email_changed_notification","mailer_subjects_phone_changed_notification","mailer_subjects_mfa_factor_enrolled_notification","mailer_subjects_mfa_factor_unenrolled_notification","mailer_subjects_identity_linked_notification","mailer_subjects_identity_unlinked_notification","mailer_templates_invite_content","mailer_templates_confirmation_content","mailer_templates_recovery_content","mailer_templates_email_change_content","mailer_templates_magic_link_content","mailer_templates_reauthentication_content","mailer_templates_password_changed_notification_content","mailer_templates_email_changed_notification_content","mailer_templates_phone_changed_notification_content","mailer_templates_mfa_factor_enrolled_notification_content","mailer_templates_mfa_factor_unenrolled_notification_content","mailer_templates_identity_linked_notification_content","mailer_templates_identity_unlinked_notification_content","mailer_notifications_password_changed_enabled","mailer_notifications_email_changed_enabled","mailer_notifications_phone_changed_enabled","mailer_notifications_mfa_factor_enrolled_enabled","mailer_notifications_mfa_factor_unenrolled_enabled","mailer_notifications_identity_linked_enabled","mailer_notifications_identity_unlinked_enabled","mfa_max_enrolled_factors","uri_allow_list","external_anonymous_users_enabled","external_email_enabled","external_phone_enabled","saml_enabled","saml_external_url","security_sb_forwarded_for_enabled","security_captcha_enabled","security_captcha_provider","security_captcha_secret","sessions_timebox","sessions_inactivity_timeout","sessions_single_per_user","sessions_tags","rate_limit_anonymous_users","rate_limit_email_sent","rate_limit_sms_sent","rate_limit_verify","rate_limit_token_refresh","rate_limit_otp","rate_limit_web3","mailer_secure_email_change_enabled","refresh_token_rotation_enabled","password_hibp_enabled","password_min_length","password_required_characters","security_manual_linking_enabled","security_update_password_require_reauthentication","security_refresh_token_reuse_interval","mailer_otp_exp","mailer_otp_length","sms_autoconfirm","sms_max_frequency","sms_otp_exp","sms_otp_length","sms_provider","sms_messagebird_access_key","sms_messagebird_originator","sms_test_otp","sms_test_otp_valid_until","sms_textlocal_api_key","sms_textlocal_sender","sms_twilio_account_sid","sms_twilio_auth_token","sms_twilio_content_sid","sms_twilio_message_service_sid","sms_twilio_verify_account_sid","sms_twilio_verify_auth_token","sms_twilio_verify_message_service_sid","sms_vonage_api_key","sms_vonage_api_secret","sms_vonage_from","sms_template","hook_mfa_verification_attempt_enabled","hook_mfa_verification_attempt_uri","hook_mfa_verification_attempt_secrets","hook_password_verification_attempt_enabled","hook_password_verification_attempt_uri","hook_password_verification_attempt_secrets","hook_custom_access_token_enabled","hook_custom_access_token_uri","hook_custom_access_token_secrets","hook_send_sms_enabled","hook_send_sms_uri","hook_send_sms_secrets","hook_send_email_enabled","hook_send_email_uri","hook_send_email_secrets","hook_before_user_created_enabled","hook_before_user_created_uri","hook_before_user_created_secrets","hook_after_user_created_enabled","hook_after_user_created_uri","hook_after_user_created_secrets","external_apple_enabled","external_apple_client_id","external_apple_email_optional","external_apple_secret","external_apple_additional_client_ids","external_azure_enabled","external_azure_client_id","external_azure_email_optional","external_azure_secret","external_azure_url","external_bitbucket_enabled","external_bitbucket_client_id","external_bitbucket_email_optional","external_bitbucket_secret","external_discord_enabled","external_discord_client_id","external_discord_email_optional","external_discord_secret","external_facebook_enabled","external_facebook_client_id","external_facebook_email_optional","external_facebook_secret","external_figma_enabled","external_figma_client_id","external_figma_email_optional","external_figma_secret","external_github_enabled","external_github_client_id","external_github_email_optional","external_github_secret","external_gitlab_enabled","external_gitlab_client_id","external_gitlab_email_optional","external_gitlab_secret","external_gitlab_url","external_google_enabled","external_google_client_id","external_google_email_optional","external_google_secret","external_google_additional_client_ids","external_google_skip_nonce_check","external_kakao_enabled","external_kakao_client_id","external_kakao_email_optional","external_kakao_secret","external_keycloak_enabled","external_keycloak_client_id","external_keycloak_email_optional","external_keycloak_secret","external_keycloak_url","external_linkedin_oidc_enabled","external_linkedin_oidc_client_id","external_linkedin_oidc_email_optional","external_linkedin_oidc_secret","external_slack_oidc_enabled","external_slack_oidc_client_id","external_slack_oidc_email_optional","external_slack_oidc_secret","external_notion_enabled","external_notion_client_id","external_notion_email_optional","external_notion_secret","external_slack_enabled","external_slack_client_id","external_slack_email_optional","external_slack_secret","external_spotify_enabled","external_spotify_client_id","external_spotify_email_optional","external_spotify_secret","external_twitch_enabled","external_twitch_client_id","external_twitch_email_optional","external_twitch_secret","external_twitter_enabled","external_twitter_client_id","external_twitter_email_optional","external_twitter_secret","external_x_enabled","external_x_client_id","external_x_email_optional","external_x_secret","external_workos_enabled","external_workos_client_id","external_workos_secret","external_workos_url","external_web3_solana_enabled","external_web3_ethereum_enabled","external_zoom_enabled","external_zoom_client_id","external_zoom_email_optional","external_zoom_secret","db_max_pool_size","db_max_pool_size_unit","api_max_request_duration","mfa_totp_enroll_enabled","mfa_totp_verify_enabled","mfa_web_authn_enroll_enabled","mfa_web_authn_verify_enabled","passkey_enabled","webauthn_rp_display_name","webauthn_rp_id","webauthn_rp_origins","mfa_phone_enroll_enabled","mfa_phone_verify_enabled","mfa_phone_max_frequency","mfa_phone_otp_length","mfa_phone_template","nimbus_oauth_client_id","nimbus_oauth_client_secret","oauth_server_enabled","oauth_server_allow_dynamic_registration","oauth_server_authorization_path","custom_oauth_enabled"] }, - response: { kind: "json" }, - inputSchema: V1UpdateAuthServiceConfigInput, - outputSchema: V1UpdateAuthServiceConfigOutput, - }, - "v1UpdateBackupSchedule": { - id: "v1UpdateBackupSchedule", - description: "Sets the time at which the daily backup runs. The change takes effect on the next backup window that includes the new time. If the new time has already passed for today, the first backup at the new time will occur the following day. It can only be updated 3 times per 24 hours.", - method: "PATCH", - path: "/v1/projects/{ref}/database/backups/schedule", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["schedule_for"] }, - response: { kind: "json" }, - inputSchema: V1UpdateBackupScheduleInput, - outputSchema: V1UpdateBackupScheduleOutput, - }, - "v1UpdateDatabasePassword": { - id: "v1UpdateDatabasePassword", - description: "Updates the database password", - method: "PATCH", - path: "/v1/projects/{ref}/database/password", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["password"] }, - response: { kind: "json" }, - inputSchema: V1UpdateDatabasePasswordInput, - outputSchema: V1UpdateDatabasePasswordOutput, - }, - "v1UpdateHostnameConfig": { - id: "v1UpdateHostnameConfig", - description: "[Beta] Updates project's custom hostname configuration", - method: "POST", - path: "/v1/projects/{ref}/custom-hostname/initialize", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["custom_hostname"] }, - response: { kind: "json" }, - inputSchema: V1UpdateHostnameConfigInput, - outputSchema: V1UpdateHostnameConfigOutput, - }, - "v1UpdateJitAccess": { - id: "v1UpdateJitAccess", - description: "Modifies the roles that can be assumed and for how long", - method: "PUT", - path: "/v1/projects/{ref}/database/jit", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["user_id","roles"] }, - response: { kind: "json" }, - inputSchema: V1UpdateJitAccessInput, - outputSchema: V1UpdateJitAccessOutput, - }, - "v1UpdateJitAccessConfig": { - id: "v1UpdateJitAccessConfig", - description: "[Beta] Update project's temporary access configuration.", - method: "PUT", - path: "/v1/projects/{ref}/jit-access", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["state"] }, - response: { kind: "json" }, - inputSchema: V1UpdateJitAccessConfigInput, - outputSchema: V1UpdateJitAccessConfigOutput, - }, - "v1UpdateNetworkRestrictions": { - id: "v1UpdateNetworkRestrictions", - description: "[Beta] Updates project's network restrictions", - method: "POST", - path: "/v1/projects/{ref}/network-restrictions/apply", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["dbAllowedCidrs","dbAllowedCidrsV6"] }, - response: { kind: "json" }, - inputSchema: V1UpdateNetworkRestrictionsInput, - outputSchema: V1UpdateNetworkRestrictionsOutput, - }, - "v1UpdatePgsodiumConfig": { - id: "v1UpdatePgsodiumConfig", - description: "[Beta] Updates project's pgsodium config. Updating the root_key can cause all data encrypted with the older key to become inaccessible.", - method: "PUT", - path: "/v1/projects/{ref}/pgsodium", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["root_key"] }, - response: { kind: "json" }, - inputSchema: V1UpdatePgsodiumConfigInput, - outputSchema: V1UpdatePgsodiumConfigOutput, - }, - "v1UpdatePoolerConfig": { - id: "v1UpdatePoolerConfig", - description: "Updates project's supavisor config", - method: "PATCH", - path: "/v1/projects/{ref}/config/database/pooler", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["default_pool_size","pool_mode"] }, - response: { kind: "json" }, - inputSchema: V1UpdatePoolerConfigInput, - outputSchema: V1UpdatePoolerConfigOutput, - }, - "v1UpdatePostgresConfig": { - id: "v1UpdatePostgresConfig", - description: "Updates project's Postgres config", - method: "PUT", - path: "/v1/projects/{ref}/config/database/postgres", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["effective_cache_size","logical_decoding_work_mem","cron.log_statement","log_autovacuum_min_duration","log_checkpoints","log_connections","log_disconnections","log_duration","log_lock_waits","log_recovery_conflict_waits","log_replication_commands","log_startup_progress_interval","log_temp_files","maintenance_work_mem","track_activity_query_size","max_connections","max_locks_per_transaction","max_logical_replication_workers","max_parallel_maintenance_workers","max_parallel_workers","max_parallel_workers_per_gather","max_replication_slots","max_slot_wal_keep_size","max_standby_archive_delay","max_standby_streaming_delay","max_sync_workers_per_subscription","max_wal_size","max_wal_senders","max_worker_processes","session_replication_role","shared_buffers","statement_timeout","track_commit_timestamp","wal_keep_size","wal_sender_timeout","work_mem","checkpoint_timeout","hot_standby_feedback","restart_database"] }, - response: { kind: "json" }, - inputSchema: V1UpdatePostgresConfigInput, - outputSchema: V1UpdatePostgresConfigOutput, - }, - "v1UpdatePostgrestServiceConfig": { - id: "v1UpdatePostgrestServiceConfig", - description: "Updates project's postgrest config", - method: "PATCH", - path: "/v1/projects/{ref}/postgrest", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["db_extra_search_path","db_schema","max_rows","db_pool","db_pool_acquisition_timeout"] }, - response: { kind: "json" }, - inputSchema: V1UpdatePostgrestServiceConfigInput, - outputSchema: V1UpdatePostgrestServiceConfigOutput, - }, - "v1UpdateProjectApiKey": { - id: "v1UpdateProjectApiKey", - description: "Updates an API key for the project", - method: "PATCH", - path: "/v1/projects/{ref}/api-keys/{id}", - pathParams: ["ref","id"], - queryParams: ["reveal"], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["name","description","secret_jwt_template"] }, - response: { kind: "json" }, - inputSchema: V1UpdateProjectApiKeyInput, - outputSchema: V1UpdateProjectApiKeyOutput, - }, - "v1UpdateProjectLegacyApiKeys": { - id: "v1UpdateProjectLegacyApiKeys", - description: "Disable or re-enable JWT based legacy (anon, service_role) API keys. This API endpoint will be removed in the future, check for HTTP 404 Not Found.", - method: "PUT", - path: "/v1/projects/{ref}/api-keys/legacy", - pathParams: ["ref"], - queryParams: ["enabled"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1UpdateProjectLegacyApiKeysInput, - outputSchema: V1UpdateProjectLegacyApiKeysOutput, - }, - "v1UpdateProjectSigningKey": { - id: "v1UpdateProjectSigningKey", - description: "Update a signing key, mainly its status", - method: "PATCH", - path: "/v1/projects/{ref}/config/auth/signing-keys/{id}", - pathParams: ["id","ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["status"] }, - response: { kind: "json" }, - inputSchema: V1UpdateProjectSigningKeyInput, - outputSchema: V1UpdateProjectSigningKeyOutput, - }, - "v1UpdateRealtimeConfig": { - id: "v1UpdateRealtimeConfig", - description: "Updates realtime configuration", - method: "PATCH", - path: "/v1/projects/{ref}/config/realtime", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["private_only","connection_pool","postgres_changes_pool","max_concurrent_users","max_events_per_second","max_bytes_per_second","max_channels_per_client","max_joins_per_second","max_presence_events_per_second","max_payload_size_in_kb","suspend","presence_enabled"] }, - response: { kind: "void" }, - inputSchema: V1UpdateRealtimeConfigInput, - outputSchema: V1UpdateRealtimeConfigOutput, - }, - "v1UpdateSslEnforcementConfig": { - id: "v1UpdateSslEnforcementConfig", - description: "[Beta] Update project's SSL enforcement configuration.", - method: "PUT", - path: "/v1/projects/{ref}/ssl-enforcement", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["requestedConfig"] }, - response: { kind: "json" }, - inputSchema: V1UpdateSslEnforcementConfigInput, - outputSchema: V1UpdateSslEnforcementConfigOutput, - }, - "v1UpdateStorageConfig": { - id: "v1UpdateStorageConfig", - description: "Updates project's storage config", - method: "PATCH", - path: "/v1/projects/{ref}/config/storage", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["fileSizeLimit","features","external"] }, - response: { kind: "void" }, - inputSchema: V1UpdateStorageConfigInput, - outputSchema: V1UpdateStorageConfigOutput, - }, - "v1UpgradePostgresVersion": { - id: "v1UpgradePostgresVersion", - description: "[Beta] Upgrades the project's Postgres version", - method: "POST", - path: "/v1/projects/{ref}/upgrade", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["target_version","release_channel"] }, - response: { kind: "json" }, - inputSchema: V1UpgradePostgresVersionInput, - outputSchema: V1UpgradePostgresVersionOutput, - }, - "v1UpsertAMigration": { - id: "v1UpsertAMigration", - description: "Upsert a database migration without applying", - method: "PUT", - path: "/v1/projects/{ref}/database/migrations", - pathParams: ["ref"], - queryParams: [], - headerParams: ["Idempotency-Key"], - requestBody: { kind: "json", contentType: "application/json", fields: ["query","name","rollback"] }, - response: { kind: "void" }, - inputSchema: V1UpsertAMigrationInput, - outputSchema: V1UpsertAMigrationOutput, - }, - "v1VerifyDnsConfig": { - id: "v1VerifyDnsConfig", - description: "[Beta] Attempts to verify the DNS configuration for project's custom hostname configuration", - method: "POST", - path: "/v1/projects/{ref}/custom-hostname/reverify", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V1VerifyDnsConfigInput, - outputSchema: V1VerifyDnsConfigOutput, - }, - "v2AssignOrganizationMemberRole": { - id: "v2AssignOrganizationMemberRole", - description: "Assigns an org-wide role when projects is omitted, or creates a project-scoped assignment when projects is provided. Uses an org-level role template id from GET /v2/organizations/{slug}/roles. Stale role assignments are automatically cleaned up: if a role no longer has any projects, it is deleted; overlapping project assignments in other roles are automatically removed to avoid duplication.", - method: "PATCH", - path: "/v2/organizations/{slug}/members/{user_id}/roles", - pathParams: ["slug","user_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2AssignOrganizationMemberRoleInput, - outputSchema: V2AssignOrganizationMemberRoleOutput, - }, - "v2CreateLogDrain": { - id: "v2CreateLogDrain", - description: "Create a log drain for a project", - method: "POST", - path: "/v2/projects/{ref}/analytics/log-drains", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2CreateLogDrainInput, - outputSchema: V2CreateLogDrainOutput, - }, - "v2CreateOrganizationInvitations": { - id: "v2CreateOrganizationInvitations", - description: "Creates member invitations for an organization. Each invitation can have different role and project scope settings.", - method: "POST", - path: "/v2/organizations/{slug}/members/invitations", - pathParams: ["slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2CreateOrganizationInvitationsInput, - outputSchema: V2CreateOrganizationInvitationsOutput, - }, - "v2CreatePrivateLinkAssociation": { - id: "v2CreatePrivateLinkAssociation", - description: "Adds an AWS account to the project's PrivateLink configuration and schedules the AWS resources to be created.", - method: "POST", - path: "/v2/projects/{ref}/private-link/associations", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2CreatePrivateLinkAssociationInput, - outputSchema: V2CreatePrivateLinkAssociationOutput, - }, - "v2CreateWorkerUpload": { - id: "v2CreateWorkerUpload", - description: "PUT the `.tar.gz` build context to the returned `url` before `expires_at`, then deploy with the upload id as `context_upload_id`. The bytes go straight to storage — no management API request carries them.", - method: "POST", - path: "/v2/projects/{ref}/workers/{name}/uploads", - pathParams: ["ref","name"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2CreateWorkerUploadInput, - outputSchema: V2CreateWorkerUploadOutput, - }, - "v2DeleteAWorker": { - id: "v2DeleteAWorker", - description: "Tombstones the worker. Its instances and image are torn down asynchronously.", - method: "DELETE", - path: "/v2/projects/{ref}/workers/{name}", - pathParams: ["ref","name"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V2DeleteAWorkerInput, - outputSchema: V2DeleteAWorkerOutput, - }, - "v2DeleteLogDrain": { - id: "v2DeleteLogDrain", - description: "Delete a project log drain", - method: "DELETE", - path: "/v2/projects/{ref}/analytics/log-drains/{id}", - pathParams: ["ref","id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V2DeleteLogDrainInput, - outputSchema: V2DeleteLogDrainOutput, - }, - "v2DeleteOrganizationInvitations": { - id: "v2DeleteOrganizationInvitations", - description: "Bulk delete member invitations for an organization by email address.", - method: "DELETE", - path: "/v2/organizations/{slug}/members/invitations", - pathParams: ["slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2DeleteOrganizationInvitationsInput, - outputSchema: V2DeleteOrganizationInvitationsOutput, - }, - "v2DeletePrivateLinkAssociation": { - id: "v2DeletePrivateLinkAssociation", - description: "Removes an AWS account from the project's PrivateLink configuration (targeting the primary database). Cleans up the associated AWS resources.", - method: "DELETE", - path: "/v2/projects/{ref}/private-link/associations/aws-account/{aws_account_id}", - pathParams: ["ref","aws_account_id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V2DeletePrivateLinkAssociationInput, - outputSchema: V2DeletePrivateLinkAssociationOutput, - }, - "v2DeletePrivateLinkAssociationForDatabase": { - id: "v2DeletePrivateLinkAssociationForDatabase", - description: "Removes an AWS account from the project's PrivateLink configuration for the given read replica. Cleans up the associated AWS resources.", - method: "DELETE", - path: "/v2/projects/{ref}/private-link/associations/aws-account/{aws_account_id}/database/{database_identifier}", - pathParams: ["ref","aws_account_id","database_identifier"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "void" }, - inputSchema: V2DeletePrivateLinkAssociationForDatabaseInput, - outputSchema: V2DeletePrivateLinkAssociationForDatabaseOutput, - }, - "v2DeployAWorker": { - id: "v2DeployAWorker", - description: "Creates the worker if it does not exist, building from a context staged through the uploads endpoint. The build runs asynchronously: this answers 202 and the worker reaches `build_state` `active` or `failed` later.", - method: "POST", - path: "/v2/projects/{ref}/workers/{name}/deploy", - pathParams: ["ref","name"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2DeployAWorkerInput, - outputSchema: V2DeployAWorkerOutput, - }, - "v2GetAWorker": { - id: "v2GetAWorker", - description: "Returns a worker along with its instance tally. Poll this after a deploy until `build_state` leaves `building`.", - method: "GET", - path: "/v2/projects/{ref}/workers/{name}", - pathParams: ["ref","name"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2GetAWorkerInput, - outputSchema: V2GetAWorkerOutput, - }, - "v2GetProjectConfig": { - id: "v2GetProjectConfig", - description: "Returns the project's database, pooler, Auth, Data API, Realtime and Storage configuration — the same configuration a branch inherits from its base project. Each is the effective config, so a setting the project has never overridden is reported at its platform default rather than as null. Auth secrets are returned as an HMAC of their value. `storage` is read live from the storage service; the rest come from this platform's own records.", - method: "GET", - path: "/v2/projects/{ref}/config", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2GetProjectConfigInput, - outputSchema: V2GetProjectConfigOutput, - }, - "v2ListAllWorkers": { - id: "v2ListAllWorkers", - description: "Returns all workers you've previously deployed to the specified project.", - method: "GET", - path: "/v2/projects/{ref}/workers", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2ListAllWorkersInput, - outputSchema: V2ListAllWorkersOutput, - }, - "v2ListLogDrains": { - id: "v2ListLogDrains", - description: "List project log drains", - method: "GET", - path: "/v2/projects/{ref}/analytics/log-drains", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2ListLogDrainsInput, - outputSchema: V2ListLogDrainsOutput, - }, - "v2ListOrganizationGithubConnections": { - id: "v2ListOrganizationGithubConnections", - description: "Returns a cursor-paginated list of the GitHub connections of the organization's projects.\n\nUse `page[after]` and `page[before]` to navigate pages and `page[size]` to control the page size.\nPaging walks the organization projects, so a page holds at most `page[size]` connections and can hold fewer (or none) when some of its projects are not connected.\nFollow `links.next` until it is `null` rather than stopping on a short page.\n\nUse `filter[project_ref]` to narrow the list down to a single project.", - method: "GET", - path: "/v2/organizations/{slug}/integrations/github/connections", - pathParams: ["slug"], - queryParams: ["page","filter"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2ListOrganizationGithubConnectionsInput, - outputSchema: V2ListOrganizationGithubConnectionsOutput, - }, - "v2ListOrganizationMembers": { - id: "v2ListOrganizationMembers", - description: "Returns a cursor-paginated list of organization members including their roles and project-scoped permissions.", - method: "GET", - path: "/v2/organizations/{slug}/members", - pathParams: ["slug"], - queryParams: ["page","filter"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2ListOrganizationMembersInput, - outputSchema: V2ListOrganizationMembersOutput, - }, - "v2ListOrganizationProjects": { - id: "v2ListOrganizationProjects", - description: "Returns a cursor-paginated list of projects for the specified organization, including their databases.\n\nUse `page[after]` and `page[before]` to navigate pages and `page[size]` to control the number of projects returned per page.", - method: "GET", - path: "/v2/organizations/{slug}/projects", - pathParams: ["slug"], - queryParams: ["page","sort","search"], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2ListOrganizationProjectsInput, - outputSchema: V2ListOrganizationProjectsOutput, - }, - "v2ListOrganizationRoles": { - id: "v2ListOrganizationRoles", - description: "Returns a list of org-level roles for the organization.", - method: "GET", - path: "/v2/organizations/{slug}/roles", - pathParams: ["slug"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2ListOrganizationRolesInput, - outputSchema: V2ListOrganizationRolesOutput, - }, - "v2ListPrivateLinkAssociations": { - id: "v2ListPrivateLinkAssociations", - description: "List AWS accounts attached to the project PrivateLink share", - method: "GET", - path: "/v2/projects/{ref}/private-link/associations", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "none" }, - response: { kind: "json" }, - inputSchema: V2ListPrivateLinkAssociationsInput, - outputSchema: V2ListPrivateLinkAssociationsOutput, - }, - "v2PreviewAProjectTransfer": { - id: "v2PreviewAProjectTransfer", - description: "Previews transferring a project to a different organizations, shows eligibility and impact", - method: "POST", - path: "/v2/projects/{ref}/transfers/previews", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2PreviewAProjectTransferInput, - outputSchema: V2PreviewAProjectTransferOutput, - }, - "v2RunProjectAdvisors": { - id: "v2RunProjectAdvisors", - description: "Runs the project advisors with the given names", - method: "POST", - path: "/v2/projects/{ref}/advisors/run", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2RunProjectAdvisorsInput, - outputSchema: V2RunProjectAdvisorsOutput, - }, - "v2TransferAProject": { - id: "v2TransferAProject", - description: "Transfers a project to a different organization", - method: "POST", - path: "/v2/projects/{ref}/transfers", - pathParams: ["ref"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "void" }, - inputSchema: V2TransferAProjectInput, - outputSchema: V2TransferAProjectOutput, - }, - "v2UpdateLogDrain": { - id: "v2UpdateLogDrain", - description: "Update a project log drain", - method: "PUT", - path: "/v2/projects/{ref}/analytics/log-drains/{id}", - pathParams: ["ref","id"], - queryParams: [], - headerParams: [], - requestBody: { kind: "json", contentType: "application/json", fields: ["data"] }, - response: { kind: "json" }, - inputSchema: V2UpdateLogDrainInput, - outputSchema: V2UpdateLogDrainOutput, - }, -} as const; - -export type OpenApiOperationId = keyof typeof openApiOperationIdMap; -export type OperationId = keyof typeof operationDefinitions; -export type OperationDefinition = (typeof operationDefinitions)[Id]; -export type OperationInput = typeof operationDefinitions[Id]["inputSchema"]["Type"]; -export type OperationOutput = typeof operationDefinitions[Id]["outputSchema"]["Type"]; -export type JsonOperationDefinition = Extract< - OperationDefinition, - { readonly response: { readonly kind: "json" } } ->; -export type TextOperationDefinition = Extract< - OperationDefinition, - { readonly response: { readonly kind: "text" } } ->; -export type VoidOperationDefinition = Extract< - OperationDefinition, - { readonly response: { readonly kind: "void" } } ->; diff --git a/packages/config/src/config-diff.auth.ts b/packages/config/src/config-diff.auth.ts deleted file mode 100644 index 1286ea2a7e..0000000000 --- a/packages/config/src/config-diff.auth.ts +++ /dev/null @@ -1,461 +0,0 @@ -import type { ManagedConfigProperty, RemoteProjectConfig } from "./config-diff.ts"; -import { - coerceRemoteScalar, - isRemoteRecord, - managedScalar, - managedStringList, - remoteValueAt, - type RemoteScalarKind, -} from "./config-diff.read.ts"; - -/** - * The auth portion of the managed surface (`config-diff.managed.ts`). The v2 - * `auth` block is a flat record keyed by lowercased GoTrue setting name — the - * same wire keys as the v1 `AuthConfigResponse`. Each entry maps one wire key - * to its `auth.*` config.toml path, mirroring the Go CLI's - * `FromRemoteAuthConfig` (`pkg/config/auth.go`): the same inversions - * (`disable_signup`, `mailer_autoconfirm`), duration conversions (wire - * seconds/hours to Go-style duration strings), and enum renames - * (`password_required_characters`) apply. - * - * Deliberately unmanaged: local-only fields the API never reports - * (`auth.enabled`, JWT key material, template `content_path`s, - * `auth.external.*.redirect_uri`), `auth.third_party.*` (not part of the - * gotrue config record), `auth.sms.test_otp` (a record-valued map, not a - * leaf), and wire keys with no local schema path (`passkey_enabled`, - * `webauthn_rp_*`, `external_figma_*`, SAML, OAuth server flags). - */ - -function readAuthValue(remote: RemoteProjectConfig, key: string): unknown { - return remoteValueAt(remote, "auth", [key]); -} - -function authScalar( - path: string, - remoteKey: string, - kind: RemoteScalarKind, -): ManagedConfigProperty { - return managedScalar({ path, block: "auth", remotePath: [remoteKey], kind }); -} - -function authSecret(path: string, remoteKey: string): ManagedConfigProperty { - return managedScalar({ - path, - block: "auth", - remotePath: [remoteKey], - kind: "string", - secret: true, - }); -} - -/** - * Inverted booleans: Go reads `EnableSignup = !DisableSignup` and - * `EnableConfirmations = !MailerAutoconfirm`. Only an actual boolean is - * negated; anything else (including "not returned") passes through so drift - * against an unexpected wire shape is reported rather than swallowed. - */ -function readNegatedBoolean(remoteKey: string) { - return (remote: RemoteProjectConfig): unknown => { - const value = coerceRemoteScalar(readAuthValue(remote, remoteKey), "boolean"); - return typeof value === "boolean" ? !value : value; - }; -} - -const GO_DURATION_UNIT_SECONDS = new Map([ - ["ns", 1e-9], - ["us", 1e-6], - ["µs", 1e-6], - ["ms", 1e-3], - ["s", 1], - ["m", 60], - ["h", 3600], -]); - -/** - * Canonicalizes Go-style duration strings (`"1h30m"`, `"5s"`, `"0"`) to - * seconds for comparison, matching `time.ParseDuration` for the non-negative - * durations the schema uses. Unparseable strings pass through so they still - * compare (and report) as-is. - */ -function normalizeGoDuration(value: unknown): unknown { - if (typeof value !== "string") { - return value; - } - const trimmed = value.trim(); - if (trimmed === "0") { - return 0; - } - const component = /(\d+(?:\.\d*)?|\.\d+)(ns|us|µs|ms|s|m|h)/y; - let total = 0; - let index = 0; - while (index < trimmed.length) { - component.lastIndex = index; - const match = component.exec(trimmed); - if (match === null) { - return value; - } - total += Number(match[1]) * (GO_DURATION_UNIT_SECONDS.get(match[2]!) ?? 0); - index = component.lastIndex; - } - return index > 0 ? total : value; -} - -/** - * A local Go-duration string fed by a wire number of seconds or hours (e.g. - * `smtp_max_frequency` seconds, `sessions_timebox` hours). The remote value is - * rendered as `""` and both sides normalize through - * {@link normalizeGoDuration}, so `"1h30m"` still equals a wire `1.5` hours. - */ -function authDuration(path: string, remoteKey: string, unit: "s" | "h"): ManagedConfigProperty { - return { - path, - block: "auth", - normalize: normalizeGoDuration, - read: (remote) => { - const value = coerceRemoteScalar(readAuthValue(remote, remoteKey), "number"); - return typeof value === "number" ? `${value}${unit}` : value; - }, - }; -} - -/** - * `password_required_characters` reports a character-class string; the local - * schema stores an enum name (Go's `NewPasswordRequirement`). Unknown wire - * values pass through unmapped so they surface as drift. - */ -const PASSWORD_REQUIREMENTS_BY_REQUIRED_CHARACTERS = new Map([ - ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", "letters_digits"], - [ - "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789", - "lower_upper_letters_digits", - ], - [ - "abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};'\\\\:\"|<>?,./`~", - "lower_upper_letters_digits_symbols", - ], -]); - -// -- Core / site -------------------------------------------------------------- - -const CORE_PROPERTIES: ReadonlyArray = [ - authScalar("auth.site_url", "site_url", "string"), - managedStringList({ - path: "auth.additional_redirect_urls", - block: "auth", - remotePath: ["uri_allow_list"], - }), - authScalar("auth.jwt_expiry", "jwt_exp", "number"), - authScalar("auth.enable_refresh_token_rotation", "refresh_token_rotation_enabled", "boolean"), - authScalar( - "auth.refresh_token_reuse_interval", - "security_refresh_token_reuse_interval", - "number", - ), - authScalar("auth.enable_manual_linking", "security_manual_linking_enabled", "boolean"), - // Go: `a.EnableSignup = !DisableSignup` (auth.go:454). - { path: "auth.enable_signup", block: "auth", read: readNegatedBoolean("disable_signup") }, - authScalar("auth.enable_anonymous_sign_ins", "external_anonymous_users_enabled", "boolean"), - authScalar("auth.minimum_password_length", "password_min_length", "number"), - { - path: "auth.password_requirements", - block: "auth", - read: (remote) => { - const value = coerceRemoteScalar( - readAuthValue(remote, "password_required_characters"), - "string", - ); - if (typeof value !== "string") { - return value; - } - return PASSWORD_REQUIREMENTS_BY_REQUIRED_CHARACTERS.get(value) ?? value; - }, - }, -]; - -// -- Email -------------------------------------------------------------------- - -const EMAIL_TEMPLATE_NAMES = [ - "invite", - "confirmation", - "recovery", - "magic_link", - "email_change", - "reauthentication", -]; - -const EMAIL_NOTIFICATION_NAMES = [ - "password_changed", - "email_changed", - "phone_changed", - "identity_linked", - "identity_unlinked", - "mfa_factor_enrolled", - "mfa_factor_unenrolled", -]; - -const EMAIL_PROPERTIES: ReadonlyArray = [ - authScalar("auth.email.enable_signup", "external_email_enabled", "boolean"), - authScalar("auth.email.double_confirm_changes", "mailer_secure_email_change_enabled", "boolean"), - // Go: `e.EnableConfirmations = !MailerAutoconfirm` (auth.go:825). - { - path: "auth.email.enable_confirmations", - block: "auth", - read: readNegatedBoolean("mailer_autoconfirm"), - }, - authScalar( - "auth.email.secure_password_change", - "security_update_password_require_reauthentication", - "boolean", - ), - authDuration("auth.email.max_frequency", "smtp_max_frequency", "s"), - authScalar("auth.email.otp_length", "mailer_otp_length", "number"), - authScalar("auth.email.otp_expiry", "mailer_otp_exp", "number"), - // Go derives enablement from `smtp_host` presence: the platform clears every - // SMTP field when custom SMTP is off (auth.go:1115: `Enabled = SmtpHost != nil`). - { - path: "auth.email.smtp.enabled", - block: "auth", - read: (remote) => { - if (!isRemoteRecord(remote.auth)) { - return undefined; - } - return readAuthValue(remote, "smtp_host") !== undefined; - }, - }, - authScalar("auth.email.smtp.host", "smtp_host", "string"), - // The wire reports the port as a string; the local schema types it a number. - authScalar("auth.email.smtp.port", "smtp_port", "number"), - authScalar("auth.email.smtp.user", "smtp_user", "string"), - authSecret("auth.email.smtp.pass", "smtp_pass"), - authScalar("auth.email.smtp.admin_email", "smtp_admin_email", "string"), - authScalar("auth.email.smtp.sender_name", "smtp_sender_name", "string"), - // Template subjects only: local templates store bodies as `content_path` - // files, which the wire never reports. - ...EMAIL_TEMPLATE_NAMES.map((name) => - authScalar(`auth.email.template.${name}.subject`, `mailer_subjects_${name}`, "string"), - ), - ...EMAIL_NOTIFICATION_NAMES.flatMap((name) => [ - authScalar( - `auth.email.notification.${name}.enabled`, - `mailer_notifications_${name}_enabled`, - "boolean", - ), - authScalar( - `auth.email.notification.${name}.subject`, - `mailer_subjects_${name}_notification`, - "string", - ), - ]), -]; - -// -- SMS ---------------------------------------------------------------------- - -/** - * The wire reports a single `sms_provider`; Go fans it out to per-provider - * `enabled` flags (auth.go:1207-1213). An empty provider reads as "not - * returned" because Go leaves the local flags untouched in that case. - */ -function readSmsProviderEnabled(provider: string) { - return (remote: RemoteProjectConfig): unknown => { - const value = coerceRemoteScalar(readAuthValue(remote, "sms_provider"), "string"); - if (typeof value !== "string") { - return value; - } - return value === "" ? undefined : value === provider; - }; -} - -const SMS_PROVIDER_IDS = ["twilio", "twilio_verify", "messagebird", "textlocal", "vonage"]; - -const SMS_PROPERTIES: ReadonlyArray = [ - authScalar("auth.sms.enable_signup", "external_phone_enabled", "boolean"), - authScalar("auth.sms.enable_confirmations", "sms_autoconfirm", "boolean"), - authScalar("auth.sms.template", "sms_template", "string"), - authDuration("auth.sms.max_frequency", "sms_max_frequency", "s"), - ...SMS_PROVIDER_IDS.map((provider): ManagedConfigProperty => ({ - path: `auth.sms.${provider}.enabled`, - block: "auth", - read: readSmsProviderEnabled(provider), - })), - authScalar("auth.sms.twilio.account_sid", "sms_twilio_account_sid", "string"), - authScalar("auth.sms.twilio.message_service_sid", "sms_twilio_message_service_sid", "string"), - authSecret("auth.sms.twilio.auth_token", "sms_twilio_auth_token"), - authScalar("auth.sms.twilio_verify.account_sid", "sms_twilio_verify_account_sid", "string"), - authScalar( - "auth.sms.twilio_verify.message_service_sid", - "sms_twilio_verify_message_service_sid", - "string", - ), - authSecret("auth.sms.twilio_verify.auth_token", "sms_twilio_verify_auth_token"), - authScalar("auth.sms.messagebird.originator", "sms_messagebird_originator", "string"), - authSecret("auth.sms.messagebird.access_key", "sms_messagebird_access_key"), - authScalar("auth.sms.textlocal.sender", "sms_textlocal_sender", "string"), - authSecret("auth.sms.textlocal.api_key", "sms_textlocal_api_key"), - authScalar("auth.sms.vonage.from", "sms_vonage_from", "string"), - authScalar("auth.sms.vonage.api_key", "sms_vonage_api_key", "string"), - authSecret("auth.sms.vonage.api_secret", "sms_vonage_api_secret"), -]; - -// -- MFA ---------------------------------------------------------------------- - -const MFA_PROPERTIES: ReadonlyArray = [ - authScalar("auth.mfa.max_enrolled_factors", "mfa_max_enrolled_factors", "number"), - authScalar("auth.mfa.totp.enroll_enabled", "mfa_totp_enroll_enabled", "boolean"), - authScalar("auth.mfa.totp.verify_enabled", "mfa_totp_verify_enabled", "boolean"), - authScalar("auth.mfa.phone.enroll_enabled", "mfa_phone_enroll_enabled", "boolean"), - authScalar("auth.mfa.phone.verify_enabled", "mfa_phone_verify_enabled", "boolean"), - authScalar("auth.mfa.phone.otp_length", "mfa_phone_otp_length", "number"), - authScalar("auth.mfa.phone.template", "mfa_phone_template", "string"), - authDuration("auth.mfa.phone.max_frequency", "mfa_phone_max_frequency", "s"), - authScalar("auth.mfa.web_authn.enroll_enabled", "mfa_web_authn_enroll_enabled", "boolean"), - authScalar("auth.mfa.web_authn.verify_enabled", "mfa_web_authn_verify_enabled", "boolean"), -]; - -// -- External OAuth providers --------------------------------------------------- - -interface OAuthProviderSpec { - readonly id: string; - /** Wire reports `external__url` (azure, gitlab, keycloak, workos). */ - readonly url?: boolean; - /** Wire reports `external__email_optional` (every provider but workos). */ - readonly emailOptional?: boolean; - /** Wire splits extra client ids into `external__additional_client_ids`. */ - readonly additionalClientIds?: boolean; - /** Wire reports `external__skip_nonce_check` (google only). */ - readonly skipNonceCheck?: boolean; -} - -/** - * The providers the local schema declares (`auth/providers.ts`), in schema - * order. Go also maps `figma`, which the local schema does not model. The - * local `redirect_uri` field (and `url`/`skip_nonce_check` on providers whose - * wire block omits them) has no remote counterpart and stays unmanaged. - */ -const OAUTH_PROVIDERS: ReadonlyArray = [ - { id: "apple", additionalClientIds: true, emailOptional: true }, - { id: "azure", url: true, emailOptional: true }, - { id: "bitbucket", emailOptional: true }, - { id: "discord", emailOptional: true }, - { id: "facebook", emailOptional: true }, - { id: "github", emailOptional: true }, - { id: "gitlab", url: true, emailOptional: true }, - { id: "google", additionalClientIds: true, skipNonceCheck: true, emailOptional: true }, - { id: "kakao", emailOptional: true }, - { id: "keycloak", url: true, emailOptional: true }, - { id: "linkedin_oidc", emailOptional: true }, - { id: "notion", emailOptional: true }, - { id: "twitch", emailOptional: true }, - { id: "twitter", emailOptional: true }, - { id: "x", emailOptional: true }, - { id: "slack_oidc", emailOptional: true }, - { id: "spotify", emailOptional: true }, - { id: "workos", url: true }, - { id: "zoom", emailOptional: true }, -]; - -function oauthProviderEntries(spec: OAuthProviderSpec): ReadonlyArray { - const prefix = `auth.external.${spec.id}`; - const wire = `external_${spec.id}`; - const entries: Array = [ - authScalar(`${prefix}.enabled`, `${wire}_enabled`, "boolean"), - ]; - if (spec.additionalClientIds === true) { - // Go folds `additional_client_ids` back into the comma-joined local - // `client_id` (auth.go:1415-1417, 1516-1518). - entries.push({ - path: `${prefix}.client_id`, - block: "auth", - read: (remote) => { - const clientId = coerceRemoteScalar(readAuthValue(remote, `${wire}_client_id`), "string"); - const additional = coerceRemoteScalar( - readAuthValue(remote, `${wire}_additional_client_ids`), - "string", - ); - if (typeof clientId !== "string" || typeof additional !== "string" || additional === "") { - return clientId; - } - return `${clientId},${additional}`; - }, - }); - } else { - entries.push(authScalar(`${prefix}.client_id`, `${wire}_client_id`, "string")); - } - entries.push(authSecret(`${prefix}.secret`, `${wire}_secret`)); - if (spec.url === true) { - entries.push(authScalar(`${prefix}.url`, `${wire}_url`, "string")); - } - if (spec.skipNonceCheck === true) { - entries.push(authScalar(`${prefix}.skip_nonce_check`, `${wire}_skip_nonce_check`, "boolean")); - } - if (spec.emailOptional === true) { - entries.push(authScalar(`${prefix}.email_optional`, `${wire}_email_optional`, "boolean")); - } - return entries; -} - -const EXTERNAL_PROPERTIES: ReadonlyArray = - OAUTH_PROVIDERS.flatMap(oauthProviderEntries); - -// -- Sessions ------------------------------------------------------------------- - -const SESSION_PROPERTIES: ReadonlyArray = [ - authDuration("auth.sessions.timebox", "sessions_timebox", "h"), - authDuration("auth.sessions.inactivity_timeout", "sessions_inactivity_timeout", "h"), -]; - -// -- Rate limits ---------------------------------------------------------------- - -const RATE_LIMIT_PROPERTIES: ReadonlyArray = [ - authScalar("auth.rate_limit.email_sent", "rate_limit_email_sent", "number"), - authScalar("auth.rate_limit.sms_sent", "rate_limit_sms_sent", "number"), - authScalar("auth.rate_limit.anonymous_users", "rate_limit_anonymous_users", "number"), - authScalar("auth.rate_limit.token_refresh", "rate_limit_token_refresh", "number"), - authScalar("auth.rate_limit.sign_in_sign_ups", "rate_limit_otp", "number"), - authScalar("auth.rate_limit.token_verifications", "rate_limit_verify", "number"), - authScalar("auth.rate_limit.web3", "rate_limit_web3", "number"), -]; - -// -- Captcha -------------------------------------------------------------------- - -const CAPTCHA_PROPERTIES: ReadonlyArray = [ - authScalar("auth.captcha.enabled", "security_captcha_enabled", "boolean"), - authScalar("auth.captcha.provider", "security_captcha_provider", "string"), - authSecret("auth.captcha.secret", "security_captcha_secret"), -]; - -// -- Web3 ----------------------------------------------------------------------- - -const WEB3_PROPERTIES: ReadonlyArray = [ - authScalar("auth.web3.solana.enabled", "external_web3_solana_enabled", "boolean"), - authScalar("auth.web3.ethereum.enabled", "external_web3_ethereum_enabled", "boolean"), -]; - -// -- Hooks ---------------------------------------------------------------------- - -const HOOK_NAMES = [ - "mfa_verification_attempt", - "password_verification_attempt", - "custom_access_token", - "send_sms", - "send_email", - "before_user_created", -]; - -const HOOK_PROPERTIES: ReadonlyArray = HOOK_NAMES.flatMap((name) => [ - authScalar(`auth.hook.${name}.enabled`, `hook_${name}_enabled`, "boolean"), - authScalar(`auth.hook.${name}.uri`, `hook_${name}_uri`, "string"), - authSecret(`auth.hook.${name}.secrets`, `hook_${name}_secrets`), -]); - -export const AUTH_MANAGED_CONFIG_PROPERTIES: ReadonlyArray = [ - ...CORE_PROPERTIES, - ...EMAIL_PROPERTIES, - ...SMS_PROPERTIES, - ...MFA_PROPERTIES, - ...EXTERNAL_PROPERTIES, - ...SESSION_PROPERTIES, - ...RATE_LIMIT_PROPERTIES, - ...CAPTCHA_PROPERTIES, - ...WEB3_PROPERTIES, - ...HOOK_PROPERTIES, -]; diff --git a/packages/config/src/config-diff.managed.ts b/packages/config/src/config-diff.managed.ts deleted file mode 100644 index a246f5647d..0000000000 --- a/packages/config/src/config-diff.managed.ts +++ /dev/null @@ -1,200 +0,0 @@ -import type { ManagedConfigProperty } from "./config-diff.ts"; -import { AUTH_MANAGED_CONFIG_PROPERTIES } from "./config-diff.auth.ts"; -import { - isRemoteRecord, - managedScalar, - managedStringList, - normalizeByteSize, - remoteValueAt, - type RemoteScalarKind, -} from "./config-diff.read.ts"; - -/** - * The managed surface: every local schema path the v2 project-config resource - * can report, with its reader. A local path with no entry here is unmanaged by - * construction — `[studio]`, `[local_smtp]`, ports, image pins, TLS material, - * `db.migrations`/`db.seed`, `storage.buckets` content, and the entire local - * `[realtime]` section (its local fields — `enabled`, `ip_version`, - * `max_header_length` — configure the local container only; none of the v2 - * `realtime` block's platform limits have a config.toml counterpart). - */ - -const API_PROPERTIES: ReadonlyArray = [ - managedStringList({ path: "api.schemas", block: "api", remotePath: ["db_schema"] }), - managedStringList({ - path: "api.extra_search_path", - block: "api", - remotePath: ["db_extra_search_path"], - }), - managedScalar({ path: "api.max_rows", block: "api", remotePath: ["max_rows"], kind: "number" }), -]; - -/** - * `db.settings.*` ↔ `database.postgres_settings.*`. The wire block carries - * more settings than the local schema declares; only locally-representable - * ones are managed. Kinds mirror `db.ts`'s `settings` struct. - */ -const POSTGRES_SETTINGS: ReadonlyArray = [ - ["effective_cache_size", "string"], - ["logical_decoding_work_mem", "string"], - ["maintenance_work_mem", "string"], - ["max_connections", "number"], - ["max_locks_per_transaction", "number"], - ["max_parallel_maintenance_workers", "number"], - ["max_parallel_workers", "number"], - ["max_parallel_workers_per_gather", "number"], - ["max_replication_slots", "number"], - ["max_slot_wal_keep_size", "string"], - ["max_standby_archive_delay", "string"], - ["max_standby_streaming_delay", "string"], - ["max_wal_size", "string"], - ["max_wal_senders", "number"], - ["max_worker_processes", "number"], - ["session_replication_role", "string"], - ["shared_buffers", "string"], - ["statement_timeout", "string"], - ["track_activity_query_size", "string"], - ["track_commit_timestamp", "boolean"], - ["wal_keep_size", "string"], - ["wal_sender_timeout", "string"], - ["work_mem", "string"], -]; - -function readAllowedCidrs(kind: "v4" | "v6") { - return (remote: Parameters[0]): unknown => { - const entries = remoteValueAt(remote, "database", ["network_restrictions", "allowed_cidrs"]); - if (!Array.isArray(entries)) { - return undefined; - } - return entries - .filter(isRemoteRecord) - .filter((entry) => entry["type"] === kind) - .map((entry) => entry["address"]) - .filter((address): address is string => typeof address === "string"); - }; -} - -const DATABASE_PROPERTIES: ReadonlyArray = [ - managedScalar({ - path: "db.ssl_enforcement.enabled", - block: "database", - remotePath: ["ssl_enforced"], - kind: "boolean", - }), - { - path: "db.network_restrictions.allowed_cidrs", - block: "database", - read: readAllowedCidrs("v4"), - }, - { - path: "db.network_restrictions.allowed_cidrs_v6", - block: "database", - read: readAllowedCidrs("v6"), - }, - ...POSTGRES_SETTINGS.map(([name, kind]) => - managedScalar({ - path: `db.settings.${name}`, - block: "database", - remotePath: ["postgres_settings", name], - kind, - }), - ), -]; - -const POOLER_PROPERTIES: ReadonlyArray = [ - managedScalar({ - path: "db.pooler.pool_mode", - block: "pooler", - remotePath: ["pool_mode"], - kind: "string", - }), - managedScalar({ - path: "db.pooler.default_pool_size", - block: "pooler", - remotePath: ["default_pool_size"], - kind: "number", - }), - managedScalar({ - path: "db.pooler.max_client_conn", - block: "pooler", - remotePath: ["max_client_conn"], - kind: "number", - }), -]; - -const STORAGE_PROPERTIES: ReadonlyArray = [ - managedScalar({ - path: "storage.file_size_limit", - block: "storage", - remotePath: ["file_size_limit"], - kind: "string", - normalize: normalizeByteSize, - }), - managedScalar({ - path: "storage.image_transformation.enabled", - block: "storage", - remotePath: ["features", "image_transformation", "enabled"], - kind: "boolean", - }), - managedScalar({ - path: "storage.s3_protocol.enabled", - block: "storage", - remotePath: ["features", "s3_protocol", "enabled"], - kind: "boolean", - }), - managedScalar({ - path: "storage.analytics.enabled", - block: "storage", - remotePath: ["features", "iceberg_catalog", "enabled"], - kind: "boolean", - }), - managedScalar({ - path: "storage.analytics.max_namespaces", - block: "storage", - remotePath: ["features", "iceberg_catalog", "max_namespaces"], - kind: "number", - }), - managedScalar({ - path: "storage.analytics.max_tables", - block: "storage", - remotePath: ["features", "iceberg_catalog", "max_tables"], - kind: "number", - }), - managedScalar({ - path: "storage.analytics.max_catalogs", - block: "storage", - remotePath: ["features", "iceberg_catalog", "max_catalogs"], - kind: "number", - }), - managedScalar({ - path: "storage.vector.enabled", - block: "storage", - remotePath: ["features", "vector_buckets", "enabled"], - kind: "boolean", - }), - managedScalar({ - path: "storage.vector.max_buckets", - block: "storage", - remotePath: ["features", "vector_buckets", "max_buckets"], - kind: "number", - }), - managedScalar({ - path: "storage.vector.max_indexes", - block: "storage", - remotePath: ["features", "vector_buckets", "max_indexes"], - kind: "number", - }), -]; - -export const MANAGED_CONFIG_PROPERTIES: ReadonlyArray = [ - ...API_PROPERTIES, - ...AUTH_MANAGED_CONFIG_PROPERTIES, - ...DATABASE_PROPERTIES, - ...POOLER_PROPERTIES, - ...STORAGE_PROPERTIES, -]; - -/** Dotted local schema paths of the managed surface. */ -export const MANAGED_CONFIG_PATHS: ReadonlySet = new Set( - MANAGED_CONFIG_PROPERTIES.map((property) => property.path), -); diff --git a/packages/config/src/config-diff.read.ts b/packages/config/src/config-diff.read.ts deleted file mode 100644 index 18d82d7b62..0000000000 --- a/packages/config/src/config-diff.read.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { - ManagedConfigProperty, - RemoteConfigBlock, - RemoteProjectConfig, -} from "./config-diff.ts"; - -/** - * Reader/constructor helpers for the managed-surface table - * (`config-diff.managed.ts`, `config-diff.auth.ts`). Every reader descends the - * loosely-typed v2 response with runtime guards and coerces the wire value to - * the local schema's type, so the classifier compares like with like. - */ - -export function isRemoteRecord(value: unknown): value is Readonly> { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - -/** - * Reads a nested value from a response block. `undefined` means "not - * returned"; an explicit `null` also reads as not returned (the API uses it - * for "no value set", e.g. `api.db_pool`). - */ -export function remoteValueAt( - remote: RemoteProjectConfig, - block: RemoteConfigBlock, - segments: ReadonlyArray, -): unknown { - let current: unknown = remote[block]; - for (const segment of segments) { - if (!isRemoteRecord(current) || !Object.hasOwn(current, segment)) { - return undefined; - } - current = current[segment]; - } - return current === null ? undefined : current; -} - -export type RemoteScalarKind = "string" | "number" | "boolean"; - -const REMOTE_BOOL_TRUE = new Set(["true", "1"]); -const REMOTE_BOOL_FALSE = new Set(["false", "0"]); - -/** - * Coerces a wire scalar to the local schema's primitive kind. Unconvertible - * values pass through unchanged so drift against an unexpected wire shape is - * reported rather than swallowed. - */ -export function coerceRemoteScalar(value: unknown, kind: RemoteScalarKind): unknown { - if (value === undefined) { - return undefined; - } - switch (kind) { - case "number": { - if (typeof value === "string" && value.trim() !== "") { - const parsed = Number(value.trim()); - return Number.isFinite(parsed) ? parsed : value; - } - return value; - } - case "boolean": { - if (typeof value === "string") { - const lowered = value.trim().toLowerCase(); - if (REMOTE_BOOL_TRUE.has(lowered)) return true; - if (REMOTE_BOOL_FALSE.has(lowered)) return false; - } - return value; - } - case "string": { - if (typeof value === "number" || typeof value === "boolean") { - return String(value); - } - return value; - } - } -} - -export interface ManagedScalarOptions { - /** Dotted local schema path. */ - readonly path: string; - readonly block: RemoteConfigBlock; - /** Segments below the block, e.g. `["postgres_settings", "work_mem"]`. */ - readonly remotePath: ReadonlyArray; - readonly kind: RemoteScalarKind; - readonly secret?: boolean; - readonly normalize?: (value: unknown) => unknown; -} - -export function managedScalar(options: ManagedScalarOptions): ManagedConfigProperty { - return { - path: options.path, - block: options.block, - ...(options.secret === true ? { secret: true } : {}), - ...(options.normalize === undefined ? {} : { normalize: options.normalize }), - read: (remote) => - coerceRemoteScalar(remoteValueAt(remote, options.block, options.remotePath), options.kind), - }; -} - -export interface ManagedListOptions { - readonly path: string; - readonly block: RemoteConfigBlock; - readonly remotePath: ReadonlyArray; - readonly secret?: boolean; -} - -/** - * A local string-array property the wire reports either as a comma-joined - * string (e.g. PostgREST's `db_schema`) or as an actual array. - */ -export function managedStringList(options: ManagedListOptions): ManagedConfigProperty { - return { - path: options.path, - block: options.block, - ...(options.secret === true ? { secret: true } : {}), - read: (remote) => { - const value = remoteValueAt(remote, options.block, options.remotePath); - if (typeof value === "string") { - return value === "" - ? [] - : value - .split(",") - .map((element) => element.trim()) - .filter((element) => element !== ""); - } - if (Array.isArray(value)) { - return value; - } - return undefined; - }, - }; -} - -/** - * Canonicalizes byte-size values for comparison: the wire reports byte - * counts (`52428800`) where the file writes human-readable sizes (`"50MiB"`). - * 1024-based and case-insensitive with an optional `b`/`ib` suffix, matching - * Go's `units.RAMInBytes` semantics used by the original config loader. - * Unparseable strings pass through so they still compare (and report) as-is. - */ -export function normalizeByteSize(value: unknown): unknown { - if (typeof value !== "string") { - return value; - } - const match = /^\s*(\d*\.?\d+)\s*([kmgtp]?)(?:i?b)?\s*$/i.exec(value); - if (match === null) { - return value; - } - const magnitude = Number(match[1]); - const exponent = { "": 0, k: 1, m: 2, g: 3, t: 4, p: 5 }[match[2]!.toLowerCase()] ?? 0; - return Math.floor(magnitude * 1024 ** exponent); -} diff --git a/packages/config/src/config-diff.ts b/packages/config/src/config-diff.ts index 79878d7097..db67fff163 100644 --- a/packages/config/src/config-diff.ts +++ b/packages/config/src/config-diff.ts @@ -1,86 +1,45 @@ -import type { EffectiveConfig } from "./sparse.ts"; +import { + fromConfigDocument, + isComparableProjectConfigPath, + type ProjectConfig, +} from "./project-config/project-config.ts"; +import { projectConfigMappingRows } from "./project-config/registry.ts"; import { getDefaultCliConfig } from "./sparse.ts"; -import { MANAGED_CONFIG_PROPERTIES } from "./config-diff.managed.ts"; /** - * Config drift classification between a local project config and the + * Config drift classification between the local project config and the * effective remote configuration reported by the Management API * (`GET /v2/projects/{ref}/config`). Pure and synchronous: fetching the * response, resolving the target, and rendering output are the caller's job * (`supabase config diff`, and `config pull` after it). See ADR 0022. + * + * Both operands are `ProjectConfig` values from CLI-2230's convergence + * normalizers (ADR 0021): the caller builds `local` with + * `fromConfigDocument({config, document})` (raw-presence-masked, + * canonicalized, secrets omitted) and `remote` with + * `fromApiProjectConfig(response)`. The comparable surface is the mapping + * registry's — a path with no registry row is unmanaged by construction — + * and the raw document's declared-key set drives `update` vs `remote_only`, + * since a decoded config cannot distinguish "the file wrote the default" + * from "the file is silent". */ -/** The per-service blocks of the v2 project-config resource. */ -export type RemoteConfigBlock = "api" | "auth" | "database" | "pooler" | "realtime" | "storage"; - -export const REMOTE_CONFIG_BLOCKS: ReadonlyArray = [ - "api", - "auth", - "database", - "pooler", - "realtime", - "storage", -]; - -/** - * Structural shape of the v2 response's `data.attributes`. Deliberately loose - * (`Record` per block): the wire format is owned by the - * Management API and may grow keys at any time, and every read below descends - * with runtime guards. This package must not import `@supabase/api` — the - * caller passes whatever the generated client decoded. - */ -export interface RemoteProjectConfig { - readonly api?: Readonly> | undefined; - readonly auth?: Readonly> | undefined; - readonly database?: Readonly> | undefined; - readonly pooler?: Readonly> | undefined; - readonly realtime?: Readonly> | undefined; - readonly storage?: Readonly> | undefined; -} - -/** - * One remotely-managed local schema property. The managed surface is *defined* - * by the table of these entries (`config-diff.managed.ts`): a schema path with - * no entry is unmanaged by construction and never appears in a change set. - */ -export interface ManagedConfigProperty { - /** Dotted local schema path, e.g. `"api.max_rows"`. Always a leaf. */ - readonly path: string; - /** Which v2 block reports this property. */ - readonly block: RemoteConfigBlock; - /** - * Secret-valued: the platform reports an HMAC (or omits the value), never - * plaintext. The property is "present, unknown" — excluded from comparison - * and surfaced via {@link ConfigChangeSet.masked} instead. - */ - readonly secret?: boolean; - /** - * Reads this property's value from the response, coerced to the local - * schema's type. `undefined` means the response did not carry it. - */ - readonly read: (remote: RemoteProjectConfig) => unknown; - /** - * Canonicalizes a value before equality on both sides (e.g. byte-size - * strings to byte counts). Reported values stay un-normalized. - */ - readonly normalize?: (value: unknown) => unknown; -} - export type ConfigChangeClass = "update" | "remote_only" | "local_only"; export interface ConfigChange { - /** Dotted local schema path. */ + /** Dotted config path within the hosted subset, e.g. `"api.max_rows"`. */ readonly path: string; /** - * `update`: declared locally and returned remotely, values differ. - * `remote_only`: returned remotely, not declared in the file, and differing - * from the schema default. `local_only`: declared in the file but the - * response did not account for it. + * `update`: declared locally and reported remotely, values differ. + * `remote_only`: reported remotely while the file does not declare it (or + * push cannot communicate the declared state), and differing from the + * default config's own convergence projection. `local_only`: the local + * projection carries a declared value the response did not account for. */ readonly class: ConfigChangeClass; - /** Effective local value; `undefined` when the file does not declare it. */ + /** Local convergence-projected value; `undefined` when absent. */ readonly local: unknown; - /** Remote value; `undefined` when the response did not return it. */ + /** Remote value; `undefined` when the response did not report it. */ readonly remote: unknown; /** Environment variable a local `env()` reference resolved from, if any. */ readonly envVariable?: string | undefined; @@ -96,37 +55,37 @@ export interface ConfigChangeSet { /** Reportable differences, ordered by path. */ readonly changes: ReadonlyArray; /** - * Managed secret paths the file sets a value for. These were never compared - * (the platform masks them), so a clean `changes` list is still only a - * partial claim — callers must surface this. + * Managed secret paths the file sets a value for (the registry's + * `isSecret` rows). These were never compared — the platform reports HMAC + * digests, and both normalizers omit secret leaves — so a clean `changes` + * list is still only a partial claim; callers must surface this. */ readonly masked: ReadonlyArray; - /** Blocks the response actually carried, ordered per {@link REMOTE_CONFIG_BLOCKS}. */ - readonly scope: ReadonlyArray; readonly counts: ConfigChangeCounts; } export interface DiffProjectConfigOptions { /** - * The *effective* local config: decoded with defaults filled, `env()` - * resolved, and — when the target is a branch with a matching `[remotes.*]` - * block — merged per ADR 0018. + * The local operand: `fromConfigDocument({config, document})`'s prediction + * of the post-push hosted state (pass the loaded config WITH its raw + * document so raw-presence masking applies — ADR 0021's remedy). */ - readonly local: EffectiveConfig; + readonly local: ProjectConfig; + /** The remote operand: `fromApiProjectConfig(response)`. */ + readonly remote: ProjectConfig; /** * The raw (pre-decode, post-merge) document the config was loaded from. * Declares which paths the file actually sets — the decoded config cannot, - * because decoding materializes every default. `undefined` (a file that did - * not parse to an object) means nothing is declared. + * because decoding materializes every default. `undefined` (a file that + * did not parse to an object) means nothing is declared. */ readonly declared: Readonly> | undefined; - readonly remote: RemoteProjectConfig; /** * Baseline for `remote_only` suppression: a remote value equal to this - * config's value at the same path is not drift. Defaults to the current - * schema's default config. + * projection's value at the same path is not drift. Defaults to the + * default config's own convergence projection. */ - readonly defaults?: EffectiveConfig; + readonly defaults?: ProjectConfig; /** Dotted local path → environment variable name, for `env()` reporting. */ readonly envReferences?: ReadonlyMap; } @@ -161,12 +120,31 @@ function isDeclaredAtPath(root: Readonly>, path: string) return true; } +/** Collects dotted leaf paths (arrays are leaves; records recurse). */ +function collectLeafPaths(root: ProjectConfig): Array { + const leaves: Array = []; + const walk = (value: unknown, prefix: ReadonlyArray): void => { + if (isPlainRecord(value)) { + for (const [key, child] of Object.entries(value)) { + walk(child, [...prefix, key]); + } + return; + } + if (prefix.length > 0) { + leaves.push(prefix.join(".")); + } + }; + walk(root, []); + return leaves; +} + function scalarEqual(a: unknown, b: unknown): boolean { if (a === b) { return true; } - // Type-aware comparison: the response may carry "8080" where the schema - // types the property as a number (or vice versa) — that is not drift. + // Type-aware comparison: both operands are already canonicalized by the + // convergence normalizers, but representation skew across schema versions + // ("8080" vs 8080, "true" vs true) is still not drift. if (typeof a === "string" && typeof b === "number") { const parsed = Number(a.trim()); return a.trim() !== "" && Number.isFinite(parsed) && parsed === b; @@ -218,69 +196,87 @@ export function isEqualConfigValue(a: unknown, b: unknown): boolean { return scalarEqual(a, b); } +/** Deduped dotted config paths of the registry's secret (`isSecret`) rows. */ +const secretConfigPaths: ReadonlyArray = [ + ...new Set( + projectConfigMappingRows + .filter((row) => row.isSecret === true) + .map((row) => row.configPath.join(".")), + ), +]; + +// The default config's own convergence projection — the `remote_only` +// suppression baseline. Lazy so importing this module never pays for a full +// schema decode + projection up front. +let defaultProjectionMemo: ProjectConfig | undefined; +function defaultProjection(): ProjectConfig { + defaultProjectionMemo ??= fromConfigDocument(getDefaultCliConfig()); + return defaultProjectionMemo; +} + /** - * Classifies every managed property into the change set. Pure: no I/O, no + * Classifies every comparable path into the change set. Pure: no I/O, no * dependency on command flags or output formatting. */ export function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChangeSet { - const defaults = options.defaults ?? getDefaultCliConfig(); + const defaults = options.defaults ?? defaultProjection(); const declaredRoot = options.declared ?? {}; const changes: Array = []; - const masked: Array = []; - for (const property of MANAGED_CONFIG_PROPERTIES) { - const declared = isDeclaredAtPath(declaredRoot, property.path); - - if (property.secret === true) { - if (declared) { - masked.push(property.path); - } + const paths = new Set([...collectLeafPaths(options.local), ...collectLeafPaths(options.remote)]); + for (const path of paths) { + if (!isComparableProjectConfigPath(path.split("."))) { continue; } + const localValue = valueAtPath(options.local, path); + const remoteValue = valueAtPath(options.remote, path); + const declared = isDeclaredAtPath(declaredRoot, path); + const envVariable = options.envReferences?.get(path); - const remoteValue = property.read(options.remote); - const localValue = valueAtPath(options.local, property.path); - const normalize = property.normalize ?? ((value: unknown) => value); - const envVariable = options.envReferences?.get(property.path); - - if (remoteValue !== undefined && declared) { - if (!isEqualConfigValue(normalize(localValue), normalize(remoteValue))) { - changes.push({ - path: property.path, - class: "update", - local: localValue, - remote: remoteValue, - ...(envVariable === undefined ? {} : { envVariable }), - }); + if (localValue !== undefined && remoteValue !== undefined) { + if (isEqualConfigValue(localValue, remoteValue)) { + continue; } + // A declared value differing from the remote is an update; an + // undeclared one is remote-side drift against the (materialized) + // default the local projection carries. + changes.push( + declared + ? { + path, + class: "update", + local: localValue, + remote: remoteValue, + ...(envVariable === undefined ? {} : { envVariable }), + } + : { path, class: "remote_only", local: undefined, remote: remoteValue }, + ); continue; } if (remoteValue !== undefined) { - const defaultValue = valueAtPath(defaults, property.path); - // Optional-key sections (db.ssl_enforcement, db.settings, auth - // providers…) never materialize in the default config, so their paths - // have no baseline value. The platform still reports the unconfigured - // state for them as the type's zero value (false / "" / 0 / []) — an - // undeclared feature reporting its zero value is not drift. + // The local projection is silent: the file doesn't declare it, or push + // cannot communicate the declared state (ADR 0021's unmanaged-by-push + // families). Suppress the remote value when it matches the default + // config's own projection; for paths that projection is also silent on + // (push-gated containers), fall back to the raw default config's value + // (e.g. `db.network_restrictions.allowed_cidrs`'s allow-all default is + // exactly the platform's unconfigured state), then to the type's zero + // value (the platform's report of an unconfigured feature). + const baseline = valueAtPath(defaults, path) ?? valueAtPath(getDefaultCliConfig(), path); const suppressed = - defaultValue === undefined + baseline === undefined ? isZeroValue(remoteValue) - : isEqualConfigValue(normalize(defaultValue), normalize(remoteValue)); + : isEqualConfigValue(baseline, remoteValue); if (!suppressed) { - changes.push({ - path: property.path, - class: "remote_only", - local: undefined, - remote: remoteValue, - }); + changes.push({ path, class: "remote_only", local: undefined, remote: remoteValue }); } continue; } if (declared) { changes.push({ - path: property.path, + path, class: "local_only", local: localValue, remote: undefined, @@ -290,12 +286,12 @@ export function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChan } changes.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); - masked.sort(); + + const masked = secretConfigPaths.filter((path) => isDeclaredAtPath(declaredRoot, path)).sort(); return { changes, masked, - scope: REMOTE_CONFIG_BLOCKS.filter((block) => isPlainRecord(options.remote[block])), counts: { update: changes.filter((change) => change.class === "update").length, remote_only: changes.filter((change) => change.class === "remote_only").length, diff --git a/packages/config/src/config-diff.unit.test.ts b/packages/config/src/config-diff.unit.test.ts index 507f38b19f..a14c15ca71 100644 --- a/packages/config/src/config-diff.unit.test.ts +++ b/packages/config/src/config-diff.unit.test.ts @@ -6,26 +6,26 @@ import { isEqualConfigValue, type ConfigChange, type DiffProjectConfigOptions, - type RemoteProjectConfig, } from "./config-diff.ts"; -import { MANAGED_CONFIG_PATHS, MANAGED_CONFIG_PROPERTIES } from "./config-diff.managed.ts"; -import { normalizeByteSize } from "./config-diff.read.ts"; +import { fromApiProjectConfig, fromConfigDocument } from "./project-config/project-config.ts"; const decodeCliConfig = Schema.decodeUnknownSync(CliConfigSchema); /** - * Builds the diff input the way the command layer does: `declared` is the raw - * document (key presence), `local` is its decoded effective config. + * Builds the diff input the way the command layer does: the local operand is + * `fromConfigDocument` over the decoded config WITH its raw document (so + * raw-presence masking applies), the remote operand is `fromApiProjectConfig` + * over bare v2 `data.attributes`, and `declared` is the raw document. */ function diffWith( declared: Record, - remote: RemoteProjectConfig, + attributes: Record, extra?: Partial, ) { return diffProjectConfig({ - local: decodeCliConfig(declared), + local: fromConfigDocument({ config: decodeCliConfig(declared), document: declared }), + remote: fromApiProjectConfig(attributes), declared, - remote, ...extra, }); } @@ -34,58 +34,18 @@ function changeAt(changes: ReadonlyArray, path: string): ConfigCha return changes.find((change) => change.path === path); } -describe("managed surface", () => { - test("declares no duplicate paths", () => { - expect(MANAGED_CONFIG_PATHS.size).toBe(MANAGED_CONFIG_PROPERTIES.length); - }); - - test("every managed path resolves to a real schema path in the default config", () => { - const defaults: unknown = decodeCliConfig({}); - for (const path of MANAGED_CONFIG_PATHS) { - let current: unknown = defaults; - for (const segment of path.split(".")) { - if (typeof current !== "object" || current === null) { - throw new Error(`managed path ${path} leaves the schema at ${segment}`); - } - // Optional-key subtrees (db.settings, storage.image_transformation, - // auth provider entries…) are absent from the default config; their - // presence in the schema is asserted by the entries' unit coverage - // below instead. - if (!Object.hasOwn(current, segment)) { - current = undefined; - break; - } - current = (current as Record)[segment]; - } - } - }); - - test("local-only sections are unmanaged by construction", () => { - for (const prefix of ["studio.", "local_smtp.", "edge_runtime.", "analytics.", "realtime."]) { - for (const path of MANAGED_CONFIG_PATHS) { - expect(path.startsWith(prefix)).toBe(false); - } - } - expect(MANAGED_CONFIG_PATHS.has("api.port")).toBe(false); - expect(MANAGED_CONFIG_PATHS.has("db.port")).toBe(false); - }); -}); - describe("diffProjectConfig classification", () => { test("an undefined declared document means nothing is declared", () => { const result = diffProjectConfig({ - local: decodeCliConfig({}), + local: fromConfigDocument(decodeCliConfig({})), + remote: fromApiProjectConfig({ api: { max_rows: 250 } }), declared: undefined, - remote: { api: { max_rows: 250 } }, }); expect(changeAt(result.changes, "api.max_rows")).toMatchObject({ class: "remote_only" }); }); test("declared value differing from remote is an update", () => { - const result = diffWith( - { api: { max_rows: 500 } }, - { api: { max_rows: 1000, db_schema: "public,graphql_public" } }, - ); + const result = diffWith({ api: { max_rows: 500 } }, { api: { max_rows: 1000 } }); const change = changeAt(result.changes, "api.max_rows"); expect(change).toMatchObject({ class: "update", local: 500, remote: 1000 }); expect(result.counts.update).toBe(1); @@ -108,34 +68,73 @@ describe("diffProjectConfig classification", () => { expect(change).toMatchObject({ class: "remote_only", local: undefined, remote: 250 }); }); - test("optional-key paths with no materialized default suppress zero-valued remotes", () => { - // db.ssl_enforcement and auth providers are optionalKey — absent from the - // default config — and the platform reports their unconfigured state as - // zero values. Those are not drift; a non-zero value is. + test("raw-presence-masked sections suppress zero-valued remotes", () => { + // db.ssl_enforcement is raw-presence-masked on the document arm (ADR + // 0021), so its local projection is silent when the file never declares + // it; the platform reporting the unconfigured state is not drift. + const clean = diffWith({}, { database: { ssl_enforced: false } }); + expect(changeAt(clean.changes, "db.ssl_enforcement.enabled")).toBeUndefined(); + + const drifted = diffWith({}, { database: { ssl_enforced: true } }); + expect(changeAt(drifted.changes, "db.ssl_enforcement.enabled")).toMatchObject({ + class: "remote_only", + remote: true, + }); + }); + + test("push-gated containers fall back to the raw schema default as baseline", () => { + // The registry maps network-restriction CIDRs unconditionally, but push + // gates them on the local `enabled` toggle, so the default projection is + // silent on them. The raw schema default (allow-all) IS the platform's + // unconfigured state — reporting it would flag every untouched project. const clean = diffWith( {}, { - database: { ssl_enforced: false }, - auth: { external_github_enabled: false, external_github_client_id: "" }, + database: { + network_restrictions: { + allowed_cidrs: [ + { address: "0.0.0.0/0", type: "v4" }, + { address: "::/0", type: "v6" }, + ], + }, + }, }, ); expect(clean.changes).toEqual([]); - const drifted = diffWith({}, { database: { ssl_enforced: true } }); - expect(changeAt(drifted.changes, "db.ssl_enforcement.enabled")).toMatchObject({ + const drifted = diffWith( + {}, + { + database: { + network_restrictions: { allowed_cidrs: [{ address: "10.0.0.0/8", type: "v4" }] }, + }, + }, + ); + expect(changeAt(drifted.changes, "db.network_restrictions.allowed_cidrs")).toMatchObject({ class: "remote_only", - remote: true, + remote: ["10.0.0.0/8"], }); }); + test("undeclared providers reporting their unconfigured state are not drift", () => { + const result = diffWith( + {}, + { auth: { external_github_enabled: false, external_github_client_id: "" } }, + ); + expect(result.changes.filter((change) => change.path.includes("github"))).toEqual([]); + }); + test("declared value the response does not carry is local_only", () => { const result = diffWith( - { api: { max_rows: 500 } }, - // api block present but without max_rows, and no other blocks at all. - { api: { db_schema: "public" } }, + { auth: { site_url: "https://local.example.com" } }, + // auth block present but without site_url. + { auth: {} }, ); - const change = changeAt(result.changes, "api.max_rows"); - expect(change).toMatchObject({ class: "local_only", local: 500, remote: undefined }); + expect(changeAt(result.changes, "auth.site_url")).toMatchObject({ + class: "local_only", + local: "https://local.example.com", + remote: undefined, + }); }); test("a wholly absent block turns its declared properties local_only", () => { @@ -144,7 +143,6 @@ describe("diffProjectConfig classification", () => { class: "local_only", local: 120, }); - expect(result.scope).toEqual([]); }); test("unmanaged declared properties are never reported", () => { @@ -165,86 +163,32 @@ describe("diffProjectConfig classification", () => { { api: { schemas: ["graphql_public", "public"] } }, { api: { db_schema: "public,graphql_public" } }, ); - expect(result.changes).toEqual([]); + expect(changeAt(result.changes, "api.schemas")).toBeUndefined(); }); - test("comma-joined remote strings trim around separators", () => { - const result = diffWith( - { api: { extra_search_path: ["public", "extensions"] } }, - { api: { db_extra_search_path: "public, extensions" } }, - ); - expect(result.changes).toEqual([]); - }); - - test("scalar comparison is type-aware across string/number and string/boolean", () => { - const result = diffWith( - { - db: { - settings: { max_connections: 120, track_commit_timestamp: true }, - }, - }, - { - database: { - postgres_settings: { max_connections: "120", track_commit_timestamp: "true" }, - }, - }, - ); - expect(result.changes).toEqual([]); - }); - - test("byte-size values compare canonically across representations", () => { + test("byte-size values converge across representations", () => { + // Local "50MiB" and the wire's byte count both canonicalize through the + // convergence normalizers (ADR 0021), so they compare equal. const equal = diffWith( { storage: { file_size_limit: "50MiB" } }, { storage: { file_size_limit: 52428800 } }, ); - expect(equal.changes).toEqual([]); + expect(changeAt(equal.changes, "storage.file_size_limit")).toBeUndefined(); const differing = diffWith( { storage: { file_size_limit: "50MiB" } }, { storage: { file_size_limit: 1048576 } }, ); - // The reader coerces the wire's byte count to the local schema's string - // kind before comparison, so the reported remote value is the coerced form. expect(changeAt(differing.changes, "storage.file_size_limit")).toMatchObject({ class: "update", - local: "50MiB", - remote: "1048576", - }); - }); - - test("network restriction CIDRs split by address family", () => { - const result = diffWith( - { - db: { - network_restrictions: { - enabled: true, - allowed_cidrs: ["10.0.0.0/8"], - allowed_cidrs_v6: [], - }, - }, - }, - { - database: { - network_restrictions: { - allowed_cidrs: [ - { address: "10.0.0.0/8", type: "v4" }, - { address: "fd00::/8", type: "v6" }, - ], - }, - }, - }, - ); - expect(changeAt(result.changes, "db.network_restrictions.allowed_cidrs")).toBeUndefined(); - expect(changeAt(result.changes, "db.network_restrictions.allowed_cidrs_v6")).toMatchObject({ - class: "update", - local: [], - remote: ["fd00::/8"], }); }); test("declared secret values are masked, never compared, never counted", () => { const declared = { - auth: { external: { github: { enabled: true, client_id: "id", secret: "shh" } } }, + auth: { + external: { github: { enabled: true, client_id: "id", secret: "env(GITHUB_SECRET)" } }, + }, }; const result = diffWith(declared, { auth: { external_github_enabled: true, external_github_client_id: "id" }, @@ -260,11 +204,6 @@ describe("diffProjectConfig classification", () => { expect(result.changes.filter((change) => change.path.includes("pass"))).toEqual([]); }); - test("scope lists exactly the blocks the response carried, in order", () => { - const result = diffWith({}, { storage: {}, api: {}, database: {} }); - expect(result.scope).toEqual(["api", "database", "storage"]); - }); - test("env references annotate the change for the involved variable", () => { const result = diffWith( { api: { max_rows: 500 } }, @@ -278,8 +217,8 @@ describe("diffProjectConfig classification", () => { test("changes are ordered by path and counts add up", () => { const result = diffWith( - { api: { max_rows: 5 }, storage: { file_size_limit: "1MiB" } }, - { api: { max_rows: 6 }, database: { postgres_settings: { work_mem: "64MB" } } }, + { api: { max_rows: 5 }, auth: { site_url: "https://local.example.com" } }, + { api: { max_rows: 6 }, auth: {}, database: { postgres_settings: { work_mem: "64MB" } } }, ); const paths = result.changes.map((change) => change.path); expect(paths).toEqual([...paths].sort()); @@ -307,20 +246,3 @@ describe("isEqualConfigValue", () => { expect(isEqualConfigValue(undefined, "")).toBe(false); }); }); - -describe("normalizeByteSize", () => { - test("parses 1024-based human sizes case-insensitively", () => { - expect(normalizeByteSize("50MiB")).toBe(52428800); - expect(normalizeByteSize("50MB")).toBe(52428800); - expect(normalizeByteSize("50mb")).toBe(52428800); - expect(normalizeByteSize("1GiB")).toBe(1073741824); - expect(normalizeByteSize("500")).toBe(500); - expect(normalizeByteSize("0.5k")).toBe(512); - }); - - test("passes through numbers and unparseable strings", () => { - expect(normalizeByteSize(52428800)).toBe(52428800); - expect(normalizeByteSize("not-a-size")).toBe("not-a-size"); - expect(normalizeByteSize(true)).toBe(true); - }); -}); diff --git a/packages/config/src/entrypoint-purity.unit.test.ts b/packages/config/src/entrypoint-purity.unit.test.ts index 696c7d0b4e..f0406776a0 100644 --- a/packages/config/src/entrypoint-purity.unit.test.ts +++ b/packages/config/src/entrypoint-purity.unit.test.ts @@ -275,6 +275,7 @@ const { visitedFiles, bareSpecifiers } = collectImportGraph(join(srcDir, "index. const expectedPureGraphFiles = [ "index.ts", "base.ts", + "config-diff.ts", "errors.ts", "config-document.ts", "functions-manifest-model.ts", @@ -352,6 +353,7 @@ describe("src/index.ts export surface", () => { "attachApiResponse", "cliConfigValueSourceAt", "comparableProjectConfigPaths", + "diffProjectConfig", "edgeFunctionDenoConfigFileName", "edgeFunctionEntrypointFileName", "edgeFunctionsDirectoryName", @@ -361,6 +363,7 @@ describe("src/index.ts export surface", () => { "fromConfigDocument", "getDefaultCliConfig", "isComparableProjectConfigPath", + "isEqualConfigValue", "omitDefaultValues", "projectConfigMappingRows", "subtractCliConfig", @@ -395,6 +398,7 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "comparableProjectConfigPaths", "configJsonPath", "configTomlPath", + "diffProjectConfig", "edgeFunctionDenoConfigFileName", "edgeFunctionEntrypointFileName", "edgeFunctionsDirectoryName", @@ -407,6 +411,7 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "getDefaultCliConfig", "inferFunctionsManifest", "isComparableProjectConfigPath", + "isEqualConfigValue", "loadCliConfig", "loadCliConfigFile", "loadCliProjectEnvironment", diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 6cffff1f61..fa0f54155b 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -59,14 +59,9 @@ export { type ConfigChangeCounts, type ConfigChangeSet, type DiffProjectConfigOptions, - type ManagedConfigProperty, - type RemoteConfigBlock, - type RemoteProjectConfig, - REMOTE_CONFIG_BLOCKS, diffProjectConfig, isEqualConfigValue, } from "./config-diff.ts"; -export { MANAGED_CONFIG_PATHS } from "./config-diff.managed.ts"; export { KONG_LOCAL_CA_CERT } from "./tls.ts"; export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; export { From a32ada78ee1ff10fcb6614767896672f437bfff8 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Mon, 31 Aug 2026 14:13:24 -0500 Subject: [PATCH 06/12] fix(api): make v2 project-config response blocks and keys optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated contract required every block and block key of V2ProjectConfigResponse, so a platform that reports a subset — staging predates storage.database_pool_mode; a permission-truncated response can omit whole blocks — failed the typed decode inside the API client before any consumer-side leniency could run. config diff hard-failed on every staging invocation with a SchemaError, and the documented "partially populated responses degrade, never error" behavior (ADR 0022) was unreachable. Relax all 13 object-level required arrays under data.attributes through the established openapi-overrides.json mechanism (test+replace pairs, same pattern as the SAML attribute_mapping and custom-hostname entries). The envelope (data/type/id/attributes) and array-item shapes stay strict: a partial response omits fields, not halves of array elements. @supabase/config's lenient mirror (ProjectConfigApiAttributes) already modeled every block as optional, so only the drift-guard test needed NonNullable on the generated side. A new client test pins the exact staging shape (missing storage.database_pool_mode and whole blocks) decoding successfully. Addresses PR #6295 review (Coly010): SIDE_EFFECTS.md contract thread. Co-Authored-By: Claude Fable 5 --- .../project-config-api-drift.unit.test.ts | 24 +- packages/api/scripts/openapi-overrides.json | 175 +++- packages/api/src/generated/contracts.ts | 952 ++++++++++-------- packages/api/src/generated/openapi.json | 72 +- packages/api/src/internal/client.unit.test.ts | 68 +- 5 files changed, 773 insertions(+), 518 deletions(-) diff --git a/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts b/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts index 4998105326..2338e575cf 100644 --- a/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts +++ b/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts @@ -54,13 +54,13 @@ type _RemovedTopLevelKeys = AssertNever< Exclude >; -type GeneratedDatabase = GeneratedAttrs["database"]; +type GeneratedDatabase = NonNullable; type MirrorDatabase = NonNullable; type _AddedDatabaseKeys = AssertNever>; type _RemovedDatabaseKeys = AssertNever>; -type GeneratedPostgresSettings = GeneratedDatabase["postgres_settings"]; +type GeneratedPostgresSettings = NonNullable; type MirrorPostgresSettings = NonNullable; type _AddedPostgresSettingsKeys = AssertNever< @@ -70,7 +70,7 @@ type _RemovedPostgresSettingsKeys = AssertNever< Exclude >; -type GeneratedNetworkRestrictions = GeneratedDatabase["network_restrictions"]; +type GeneratedNetworkRestrictions = NonNullable; type MirrorNetworkRestrictions = NonNullable; type _AddedNetworkRestrictionsKeys = AssertNever< @@ -97,31 +97,31 @@ type _RemovedAllowedCidrsElementKeys = AssertNever< Exclude >; -type GeneratedPooler = GeneratedAttrs["pooler"]; +type GeneratedPooler = NonNullable; type MirrorPooler = NonNullable; type _AddedPoolerKeys = AssertNever>; type _RemovedPoolerKeys = AssertNever>; -type GeneratedApi = GeneratedAttrs["api"]; +type GeneratedApi = NonNullable; type MirrorApi = NonNullable; type _AddedApiKeys = AssertNever>; type _RemovedApiKeys = AssertNever>; -type GeneratedRealtime = GeneratedAttrs["realtime"]; +type GeneratedRealtime = NonNullable; type MirrorRealtime = NonNullable; type _AddedRealtimeKeys = AssertNever>; type _RemovedRealtimeKeys = AssertNever>; -type GeneratedStorage = GeneratedAttrs["storage"]; +type GeneratedStorage = NonNullable; type MirrorStorage = NonNullable; type _AddedStorageKeys = AssertNever>; type _RemovedStorageKeys = AssertNever>; -type GeneratedStorageFeatures = GeneratedStorage["features"]; +type GeneratedStorageFeatures = NonNullable; type MirrorStorageFeatures = NonNullable; type _AddedStorageFeaturesKeys = AssertNever< @@ -135,7 +135,7 @@ type _RemovedStorageFeaturesKeys = AssertNever< // `registry.ts`), so — unlike sibling `purge_cache`, which the mirror widens // to `Schema.Unknown` since no row maps it — they stay concretely typed // `{enabled}` structs on the mirror side, each worth its own key-set pair. -type GeneratedImageTransformation = GeneratedStorageFeatures["image_transformation"]; +type GeneratedImageTransformation = NonNullable; type MirrorImageTransformation = NonNullable; type _AddedImageTransformationKeys = AssertNever< @@ -145,7 +145,7 @@ type _RemovedImageTransformationKeys = AssertNever< Exclude >; -type GeneratedS3Protocol = GeneratedStorageFeatures["s3_protocol"]; +type GeneratedS3Protocol = NonNullable; type MirrorS3Protocol = NonNullable; type _AddedS3ProtocolKeys = AssertNever>; @@ -153,7 +153,7 @@ type _RemovedS3ProtocolKeys = AssertNever< Exclude >; -type GeneratedIcebergCatalog = GeneratedStorageFeatures["iceberg_catalog"]; +type GeneratedIcebergCatalog = NonNullable; type MirrorIcebergCatalog = NonNullable; type _AddedIcebergCatalogKeys = AssertNever< @@ -163,7 +163,7 @@ type _RemovedIcebergCatalogKeys = AssertNever< Exclude >; -type GeneratedVectorBuckets = GeneratedStorageFeatures["vector_buckets"]; +type GeneratedVectorBuckets = NonNullable; type MirrorVectorBuckets = NonNullable; type _AddedVectorBucketsKeys = AssertNever< diff --git a/packages/api/scripts/openapi-overrides.json b/packages/api/scripts/openapi-overrides.json index b53de50a96..8393c85058 100644 --- a/packages/api/scripts/openapi-overrides.json +++ b/packages/api/scripts/openapi-overrides.json @@ -626,7 +626,7 @@ { "op": "remove", "path": "/paths/~1v2~1projects~1{ref}~1webhooks~1endpoints", - "$comment": "CLI-2157: the platform's v2 spec gives all 10 project-webhook operations the shared operationId \"allV2ProjectsByRefWebhooks\" (and all 10 org-webhook operations share \"allV2OrganizationsBySlugWebhooks\") — duplicated and not version-prefixed, which breaks codegen. Remove-if-present because staging's v2-json is currently served by two backend variants that disagree on whether these paths exist." + "$comment": "CLI-2157: the platform's v2 spec gives all 10 project-webhook operations the shared operationId \"allV2ProjectsByRefWebhooks\" (and all 10 org-webhook operations share \"allV2OrganizationsBySlugWebhooks\") \u2014 duplicated and not version-prefixed, which breaks codegen. Remove-if-present because staging's v2-json is currently served by two backend variants that disagree on whether these paths exist." }, { "op": "remove", @@ -656,7 +656,7 @@ { "op": "remove", "path": "/paths/~1v2~1organizations~1{slug}~1webhooks~1endpoints", - "$comment": "CLI-2157: the platform's v2 spec gives all 10 org-webhook operations the shared operationId \"allV2OrganizationsBySlugWebhooks\" (and all 10 project-webhook operations share \"allV2ProjectsByRefWebhooks\") — duplicated and not version-prefixed, which breaks codegen. Remove-if-present because staging's v2-json is currently served by two backend variants that disagree on whether these paths exist." + "$comment": "CLI-2157: the platform's v2 spec gives all 10 org-webhook operations the shared operationId \"allV2OrganizationsBySlugWebhooks\" (and all 10 project-webhook operations share \"allV2ProjectsByRefWebhooks\") \u2014 duplicated and not version-prefixed, which breaks codegen. Remove-if-present because staging's v2-json is currently served by two backend variants that disagree on whether these paths exist." }, { "op": "remove", @@ -723,5 +723,176 @@ "description": "Postgres engine version. If not provided, the latest version will be used." }, "$comment": "CLI-2180: the public spec deliberately hides this field (upstream marks it deprecated/null) even though POST /v1/projects accepts it; enum mirrors CreateBranchBody.postgres_engine in the same spec." + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/required", + "value": ["database", "pooler", "auth", "api", "realtime", "storage"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/database/required", + "value": ["major_version", "ssl_enforced", "network_restrictions", "postgres_settings"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/database/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/database/properties/network_restrictions/required", + "value": ["entitlement", "status", "allowed_cidrs"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/database/properties/network_restrictions/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/pooler/required", + "value": [ + "pool_mode", + "ignore_startup_parameters", + "server_idle_timeout", + "server_lifetime", + "query_wait_timeout", + "reserve_pool_size", + "default_pool_size", + "max_client_conn" + ] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/pooler/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/api/required", + "value": [ + "db_schema", + "db_extra_search_path", + "max_rows", + "db_pool_acquisition_timeout", + "db_pool" + ] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/api/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/realtime/required", + "value": [ + "private_only", + "max_concurrent_users", + "max_events_per_second", + "max_bytes_per_second", + "max_channels_per_client", + "max_joins_per_second", + "max_presence_events_per_second", + "max_payload_size_in_kb", + "presence_enabled", + "suspend", + "connection_pool", + "postgres_changes_pool" + ] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/realtime/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/required", + "value": [ + "file_size_limit", + "features", + "capabilities", + "upstream_target", + "migration_version", + "database_pool_mode" + ] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/required", + "value": [ + "image_transformation", + "s3_protocol", + "purge_cache", + "iceberg_catalog", + "vector_buckets" + ] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/image_transformation/required", + "value": ["enabled"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/image_transformation/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/s3_protocol/required", + "value": ["enabled"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/s3_protocol/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/purge_cache/required", + "value": ["enabled"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/purge_cache/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/iceberg_catalog/required", + "value": ["enabled", "max_namespaces", "max_tables", "max_catalogs"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/iceberg_catalog/required", + "value": [] + }, + { + "op": "test", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/vector_buckets/required", + "value": ["enabled", "max_buckets", "max_indexes"] + }, + { + "op": "replace", + "path": "/components/schemas/V2ProjectConfigResponse/properties/data/properties/attributes/properties/storage/properties/features/properties/vector_buckets/required", + "value": [] } ] diff --git a/packages/api/src/generated/contracts.ts b/packages/api/src/generated/contracts.ts index 659086a4aa..0dfb2af82b 100644 --- a/packages/api/src/generated/contracts.ts +++ b/packages/api/src/generated/contracts.ts @@ -10874,148 +10874,367 @@ export const V2GetProjectConfigOutput = Schema.Struct({ type: Schema.Literal("project_config").annotate({ description: "Resource type." }), id: Schema.String.annotate({ description: "Project ref." }), attributes: Schema.Struct({ - database: Schema.Struct({ - major_version: Schema.Number.annotate({ - description: - "The major Postgres version the database runs. `17` covers both Postgres 17 and Oriole on 17, since Oriole is a storage engine rather than a version.", - }) - .check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), + database: Schema.optionalKey( + Schema.Struct({ + major_version: Schema.optionalKey( + Schema.Number.annotate({ + description: + "The major Postgres version the database runs. `17` covers both Postgres 17 and Oriole on 17, since Oriole is a storage engine rather than a version.", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), ), - ssl_enforced: Schema.Boolean.annotate({ - description: "Whether the database rejects plaintext connections", - }), - network_restrictions: Schema.Struct({ - entitlement: Schema.Literals(["disallowed", "allowed"]), - status: Schema.Literals(["stored", "applied"]).annotate({ - description: "Whether the allowlist below is applied to the project or only stored.", - }), - allowed_cidrs: Schema.Array( - Schema.Struct({ address: Schema.String, type: Schema.Literals(["v4", "v6"]) }), + ssl_enforced: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "Whether the database rejects plaintext connections", + }), ), - updated_at: Schema.optionalKey(Schema.String), - applied_at: Schema.optionalKey(Schema.String), - }), - postgres_settings: Schema.Struct({ - effective_cache_size: Schema.optionalKey(Schema.String), - logical_decoding_work_mem: Schema.optionalKey(Schema.String), - log_autovacuum_min_duration: Schema.optionalKey( - Schema.String.annotate({ description: "Default unit: ms" }).check( - Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate( - { - expected: - "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", - }, + network_restrictions: Schema.optionalKey( + Schema.Struct({ + entitlement: Schema.optionalKey(Schema.Literals(["disallowed", "allowed"])), + status: Schema.optionalKey( + Schema.Literals(["stored", "applied"]).annotate({ + description: + "Whether the allowlist below is applied to the project or only stored.", + }), ), - ), + allowed_cidrs: Schema.optionalKey( + Schema.Array( + Schema.Struct({ address: Schema.String, type: Schema.Literals(["v4", "v6"]) }), + ), + ), + updated_at: Schema.optionalKey(Schema.String), + applied_at: Schema.optionalKey(Schema.String), + }), ), - log_checkpoints: Schema.optionalKey(Schema.Boolean), - log_connections: Schema.optionalKey(Schema.Boolean), - log_disconnections: Schema.optionalKey(Schema.Boolean), - log_duration: Schema.optionalKey(Schema.Boolean), - log_lock_waits: Schema.optionalKey(Schema.Boolean), - log_recovery_conflict_waits: Schema.optionalKey(Schema.Boolean), - log_replication_commands: Schema.optionalKey(Schema.Boolean), - log_startup_progress_interval: Schema.optionalKey( - Schema.String.annotate({ description: "Default unit: ms" }).check( - Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate( - { - expected: - "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", - }, + postgres_settings: Schema.optionalKey( + Schema.Struct({ + effective_cache_size: Schema.optionalKey(Schema.String), + logical_decoding_work_mem: Schema.optionalKey(Schema.String), + log_autovacuum_min_duration: Schema.optionalKey( + Schema.String.annotate({ description: "Default unit: ms" }).check( + Schema.isPattern( + new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$"), + ).annotate({ + expected: + "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", + }), + ), ), - ), + log_checkpoints: Schema.optionalKey(Schema.Boolean), + log_connections: Schema.optionalKey(Schema.Boolean), + log_disconnections: Schema.optionalKey(Schema.Boolean), + log_duration: Schema.optionalKey(Schema.Boolean), + log_lock_waits: Schema.optionalKey(Schema.Boolean), + log_recovery_conflict_waits: Schema.optionalKey(Schema.Boolean), + log_replication_commands: Schema.optionalKey(Schema.Boolean), + log_startup_progress_interval: Schema.optionalKey( + Schema.String.annotate({ description: "Default unit: ms" }).check( + Schema.isPattern( + new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$"), + ).annotate({ + expected: + "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", + }), + ), + ), + log_temp_files: Schema.optionalKey(Schema.String), + maintenance_work_mem: Schema.optionalKey(Schema.String), + track_activity_query_size: Schema.optionalKey(Schema.String), + max_connections: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ) + .check( + Schema.isLessThanOrEqualTo(262143).annotate({ + expected: "a value less than or equal to 262143", + }), + ), + ), + max_locks_per_transaction: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(10).annotate({ + expected: "a value greater than or equal to 10", + }), + ) + .check( + Schema.isLessThanOrEqualTo(2147483640).annotate({ + expected: "a value less than or equal to 2147483640", + }), + ), + ), + max_logical_replication_workers: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ) + .check( + Schema.isLessThanOrEqualTo(262143).annotate({ + expected: "a value less than or equal to 262143", + }), + ), + ), + max_parallel_maintenance_workers: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ) + .check( + Schema.isLessThanOrEqualTo(1024).annotate({ + expected: "a value less than or equal to 1024", + }), + ), + ), + max_parallel_workers: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ) + .check( + Schema.isLessThanOrEqualTo(1024).annotate({ + expected: "a value less than or equal to 1024", + }), + ), + ), + max_parallel_workers_per_gather: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ) + .check( + Schema.isLessThanOrEqualTo(1024).annotate({ + expected: "a value less than or equal to 1024", + }), + ), + ), + max_replication_slots: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + ), + max_slot_wal_keep_size: Schema.optionalKey(Schema.String), + max_standby_archive_delay: Schema.optionalKey(Schema.String), + max_standby_streaming_delay: Schema.optionalKey(Schema.String), + max_sync_workers_per_subscription: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ) + .check( + Schema.isLessThanOrEqualTo(262143).annotate({ + expected: "a value less than or equal to 262143", + }), + ), + ), + max_wal_size: Schema.optionalKey(Schema.String), + max_wal_senders: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + ), + max_worker_processes: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ) + .check( + Schema.isLessThanOrEqualTo(262143).annotate({ + expected: "a value less than or equal to 262143", + }), + ), + ), + session_replication_role: Schema.optionalKey( + Schema.Literals(["origin", "replica", "local"]), + ), + shared_buffers: Schema.optionalKey(Schema.String), + statement_timeout: Schema.optionalKey( + Schema.String.annotate({ description: "Default unit: ms" }).check( + Schema.isPattern( + new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$"), + ).annotate({ + expected: + "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", + }), + ), + ), + track_commit_timestamp: Schema.optionalKey(Schema.Boolean), + wal_keep_size: Schema.optionalKey(Schema.String), + wal_sender_timeout: Schema.optionalKey( + Schema.String.annotate({ description: "Default unit: ms" }).check( + Schema.isPattern( + new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$"), + ).annotate({ + expected: + "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", + }), + ), + ), + work_mem: Schema.optionalKey(Schema.String), + checkpoint_timeout: Schema.optionalKey( + Schema.String.annotate({ description: "Default unit: s" }).check( + Schema.isPattern( + new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$"), + ).annotate({ + expected: + "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", + }), + ), + ), + hot_standby_feedback: Schema.optionalKey(Schema.Boolean), + cron_log_statement: Schema.optionalKey(Schema.Boolean), + }).annotate({ + description: + "Postgres parameter overrides. Empty when the project runs entirely on defaults.", + }), ), - log_temp_files: Schema.optionalKey(Schema.String), - maintenance_work_mem: Schema.optionalKey(Schema.String), - track_activity_query_size: Schema.optionalKey(Schema.String), - max_connections: Schema.optionalKey( + }), + ), + pooler: Schema.optionalKey( + Schema.Struct({ + pool_mode: Schema.optionalKey(Schema.Literals(["transaction", "session", "statement"])), + ignore_startup_parameters: Schema.optionalKey(Schema.String), + server_idle_timeout: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(1).annotate({ - expected: "a value greater than or equal to 1", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(262143).annotate({ - expected: "a value less than or equal to 262143", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - max_locks_per_transaction: Schema.optionalKey( + server_lifetime: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(10).annotate({ - expected: "a value greater than or equal to 10", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(2147483640).annotate({ - expected: "a value less than or equal to 2147483640", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - max_logical_replication_workers: Schema.optionalKey( + query_wait_timeout: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(0).annotate({ - expected: "a value greater than or equal to 0", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(262143).annotate({ - expected: "a value less than or equal to 262143", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - max_parallel_maintenance_workers: Schema.optionalKey( + reserve_pool_size: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(0).annotate({ - expected: "a value greater than or equal to 0", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(1024).annotate({ - expected: "a value less than or equal to 1024", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - max_parallel_workers: Schema.optionalKey( - Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + default_pool_size: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Defaults to the pooler's size for the project's compute when not overridden.", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(0).annotate({ - expected: "a value greater than or equal to 0", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(1024).annotate({ - expected: "a value less than or equal to 1024", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - max_parallel_workers_per_gather: Schema.optionalKey( - Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + max_client_conn: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Defaults to the pooler's size for the project's compute when not overridden.", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(0).annotate({ - expected: "a value greater than or equal to 0", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(1024).annotate({ - expected: "a value less than or equal to 1024", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - max_replication_slots: Schema.optionalKey( + }), + ), + auth: Schema.optionalKey( + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })).annotate({ + description: + "Effective Auth config, keyed by lowercased GoTrue setting name and resolved through the `gotrue_config` view, so a setting the project has never overridden is reported at its platform default. Secrets are returned as an HMAC of their value, never in plaintext.", + }), + ), + api: Schema.optionalKey( + Schema.Struct({ + db_schema: Schema.optionalKey( + Schema.String.annotate({ description: "Schemas exposed through the Data API" }), + ), + db_extra_search_path: Schema.optionalKey(Schema.String), + max_rows: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ @@ -11028,24 +11247,45 @@ export const V2GetProjectConfigOutput = Schema.Struct({ }), ), ), - max_slot_wal_keep_size: Schema.optionalKey(Schema.String), - max_standby_archive_delay: Schema.optionalKey(Schema.String), - max_standby_streaming_delay: Schema.optionalKey(Schema.String), - max_sync_workers_per_subscription: Schema.optionalKey( + db_pool_acquisition_timeout: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(0).annotate({ - expected: "a value greater than or equal to 0", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(262143).annotate({ - expected: "a value less than or equal to 262143", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - max_wal_size: Schema.optionalKey(Schema.String), - max_wal_senders: Schema.optionalKey( + db_pool: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "If `null`, no pool size is written to the project's PostgREST config and PostgREST's own default applies. The platform does not pick a value here.", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + Schema.Null, + ]), + ), + }), + ), + realtime: Schema.optionalKey( + Schema.Struct({ + private_only: Schema.optionalKey(Schema.Boolean), + max_concurrent_users: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ @@ -11058,343 +11298,47 @@ export const V2GetProjectConfigOutput = Schema.Struct({ }), ), ), - max_worker_processes: Schema.optionalKey( + max_events_per_second: Schema.optionalKey( Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( - Schema.isGreaterThanOrEqualTo(0).annotate({ - expected: "a value greater than or equal to 0", + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", }), ) .check( - Schema.isLessThanOrEqualTo(262143).annotate({ - expected: "a value less than or equal to 262143", + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", }), ), ), - session_replication_role: Schema.optionalKey( - Schema.Literals(["origin", "replica", "local"]), - ), - shared_buffers: Schema.optionalKey(Schema.String), - statement_timeout: Schema.optionalKey( - Schema.String.annotate({ description: "Default unit: ms" }).check( - Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate( - { - expected: - "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", - }, - ), - ), - ), - track_commit_timestamp: Schema.optionalKey(Schema.Boolean), - wal_keep_size: Schema.optionalKey(Schema.String), - wal_sender_timeout: Schema.optionalKey( - Schema.String.annotate({ description: "Default unit: ms" }).check( - Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate( - { - expected: - "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", - }, + max_bytes_per_second: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), ), - ), ), - work_mem: Schema.optionalKey(Schema.String), - checkpoint_timeout: Schema.optionalKey( - Schema.String.annotate({ description: "Default unit: s" }).check( - Schema.isPattern(new RegExp("^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$")).annotate( - { - expected: - "a string matching the RegExp ^(-?[0-9]+(?:\\.[0-9]+)?)(us|ms|s|min|h|d)?$", - }, + max_channels_per_client: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), ), - ), - ), - hot_standby_feedback: Schema.optionalKey(Schema.Boolean), - cron_log_statement: Schema.optionalKey(Schema.Boolean), - }).annotate({ - description: - "Postgres parameter overrides. Empty when the project runs entirely on defaults.", - }), - }), - pooler: Schema.Struct({ - pool_mode: Schema.Literals(["transaction", "session", "statement"]), - ignore_startup_parameters: Schema.String, - server_idle_timeout: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - server_lifetime: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - query_wait_timeout: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - reserve_pool_size: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - default_pool_size: Schema.Number.annotate({ - description: - "Defaults to the pooler's size for the project's compute when not overridden.", - }) - .check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - max_client_conn: Schema.Number.annotate({ - description: - "Defaults to the pooler's size for the project's compute when not overridden.", - }) - .check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - }), - auth: Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })).annotate( - { - description: - "Effective Auth config, keyed by lowercased GoTrue setting name and resolved through the `gotrue_config` view, so a setting the project has never overridden is reported at its platform default. Secrets are returned as an HMAC of their value, never in plaintext.", - }, - ), - api: Schema.Struct({ - db_schema: Schema.String.annotate({ description: "Schemas exposed through the Data API" }), - db_extra_search_path: Schema.String, - max_rows: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - db_pool_acquisition_timeout: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - db_pool: Schema.Union([ - Schema.Number.annotate({ - description: - "If `null`, no pool size is written to the project's PostgREST config and PostgREST's own default applies. The platform does not pick a value here.", - }) - .check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - Schema.Null, - ]), - }), - realtime: Schema.Struct({ - private_only: Schema.Boolean, - max_concurrent_users: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - max_events_per_second: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), ), - max_bytes_per_second: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - max_channels_per_client: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - max_joins_per_second: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - max_presence_events_per_second: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - max_payload_size_in_kb: Schema.Number.check( - Schema.isInt().annotate({ expected: "an integer" }), - ) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - presence_enabled: Schema.Boolean, - suspend: Schema.Boolean, - connection_pool: Schema.Number.annotate({ - description: - "Defaults to Realtime's pool size for the project's compute when not overridden.", - }) - .check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - postgres_changes_pool: Schema.Union([ - Schema.Number.annotate({ - description: "If `null`, no override is stored and Realtime applies its own default.", - }) - .check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - Schema.Null, - ]), - }), - storage: Schema.Struct({ - file_size_limit: Schema.Number.annotate({ format: "int64" }) - .check(Schema.isInt().annotate({ expected: "an integer" })) - .check( - Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ - expected: "a value greater than or equal to -9007199254740991", - }), - ) - .check( - Schema.isLessThanOrEqualTo(9007199254740991).annotate({ - expected: "a value less than or equal to 9007199254740991", - }), - ), - features: Schema.Struct({ - image_transformation: Schema.Struct({ enabled: Schema.Boolean }), - s3_protocol: Schema.Struct({ enabled: Schema.Boolean }), - purge_cache: Schema.Struct({ enabled: Schema.Boolean }), - iceberg_catalog: Schema.Struct({ - enabled: Schema.Boolean, - max_namespaces: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + max_joins_per_second: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ expected: "a value greater than or equal to -9007199254740991", @@ -11405,7 +11349,9 @@ export const V2GetProjectConfigOutput = Schema.Struct({ expected: "a value less than or equal to 9007199254740991", }), ), - max_tables: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + ), + max_presence_events_per_second: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ expected: "a value greater than or equal to -9007199254740991", @@ -11416,7 +11362,9 @@ export const V2GetProjectConfigOutput = Schema.Struct({ expected: "a value less than or equal to 9007199254740991", }), ), - max_catalogs: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + ), + max_payload_size_in_kb: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) .check( Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ expected: "a value greater than or equal to -9007199254740991", @@ -11427,10 +11375,15 @@ export const V2GetProjectConfigOutput = Schema.Struct({ expected: "a value less than or equal to 9007199254740991", }), ), - }), - vector_buckets: Schema.Struct({ - enabled: Schema.Boolean, - max_buckets: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + ), + presence_enabled: Schema.optionalKey(Schema.Boolean), + suspend: Schema.optionalKey(Schema.Boolean), + connection_pool: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Defaults to Realtime's pool size for the project's compute when not overridden.", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) .check( Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ expected: "a value greater than or equal to -9007199254740991", @@ -11441,7 +11394,34 @@ export const V2GetProjectConfigOutput = Schema.Struct({ expected: "a value less than or equal to 9007199254740991", }), ), - max_indexes: Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + ), + postgres_changes_pool: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "If `null`, no override is stored and Realtime applies its own default.", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + Schema.Null, + ]), + ), + }), + ), + storage: Schema.optionalKey( + Schema.Struct({ + file_size_limit: Schema.optionalKey( + Schema.Number.annotate({ format: "int64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) .check( Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ expected: "a value greater than or equal to -9007199254740991", @@ -11452,16 +11432,106 @@ export const V2GetProjectConfigOutput = Schema.Struct({ expected: "a value less than or equal to 9007199254740991", }), ), - }), + ), + features: Schema.optionalKey( + Schema.Struct({ + image_transformation: Schema.optionalKey( + Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean) }), + ), + s3_protocol: Schema.optionalKey( + Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean) }), + ), + purge_cache: Schema.optionalKey( + Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean) }), + ), + iceberg_catalog: Schema.optionalKey( + Schema.Struct({ + enabled: Schema.optionalKey(Schema.Boolean), + max_namespaces: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + ), + max_tables: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + ), + max_catalogs: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + ), + }), + ), + vector_buckets: Schema.optionalKey( + Schema.Struct({ + enabled: Schema.optionalKey(Schema.Boolean), + max_buckets: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + ), + max_indexes: Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(-9007199254740991).annotate({ + expected: "a value greater than or equal to -9007199254740991", + }), + ) + .check( + Schema.isLessThanOrEqualTo(9007199254740991).annotate({ + expected: "a value less than or equal to 9007199254740991", + }), + ), + ), + }), + ), + }), + ), + capabilities: Schema.optionalKey( + Schema.Struct({ list_v2: Schema.Boolean, iceberg_catalog: Schema.Boolean }), + ), + upstream_target: Schema.optionalKey(Schema.Literals(["main", "canary"])), + migration_version: Schema.optionalKey(Schema.String), + database_pool_mode: Schema.optionalKey(Schema.String), + }).annotate({ + description: + "Read from the storage service's admin API rather than the middleware DB, so unlike the rest of this resource it reflects the tenant's live config.", }), - capabilities: Schema.Struct({ list_v2: Schema.Boolean, iceberg_catalog: Schema.Boolean }), - upstream_target: Schema.Literals(["main", "canary"]), - migration_version: Schema.String, - database_pool_mode: Schema.String, - }).annotate({ - description: - "Read from the storage service's admin API rather than the middleware DB, so unlike the rest of this resource it reflects the tenant's live config.", - }), + ), }), }), }); diff --git a/packages/api/src/generated/openapi.json b/packages/api/src/generated/openapi.json index 5c0978df1f..4627356765 100644 --- a/packages/api/src/generated/openapi.json +++ b/packages/api/src/generated/openapi.json @@ -24426,7 +24426,7 @@ "type": "string" } }, - "required": ["entitlement", "status", "allowed_cidrs"] + "required": [] }, "postgres_settings": { "type": "object", @@ -24580,12 +24580,7 @@ "description": "Postgres parameter overrides. Empty when the project runs entirely on defaults." } }, - "required": [ - "major_version", - "ssl_enforced", - "network_restrictions", - "postgres_settings" - ] + "required": [] }, "pooler": { "type": "object", @@ -24630,16 +24625,7 @@ "description": "Defaults to the pooler's size for the project's compute when not overridden." } }, - "required": [ - "pool_mode", - "ignore_startup_parameters", - "server_idle_timeout", - "server_lifetime", - "query_wait_timeout", - "reserve_pool_size", - "default_pool_size", - "max_client_conn" - ] + "required": [] }, "auth": { "type": "object", @@ -24674,13 +24660,7 @@ "nullable": true } }, - "required": [ - "db_schema", - "db_extra_search_path", - "max_rows", - "db_pool_acquisition_timeout", - "db_pool" - ] + "required": [] }, "realtime": { "type": "object", @@ -24743,20 +24723,7 @@ "nullable": true } }, - "required": [ - "private_only", - "max_concurrent_users", - "max_events_per_second", - "max_bytes_per_second", - "max_channels_per_client", - "max_joins_per_second", - "max_presence_events_per_second", - "max_payload_size_in_kb", - "presence_enabled", - "suspend", - "connection_pool", - "postgres_changes_pool" - ] + "required": [] }, "storage": { "type": "object", @@ -24777,7 +24744,7 @@ "type": "boolean" } }, - "required": ["enabled"] + "required": [] }, "s3_protocol": { "type": "object", @@ -24786,7 +24753,7 @@ "type": "boolean" } }, - "required": ["enabled"] + "required": [] }, "purge_cache": { "type": "object", @@ -24795,7 +24762,7 @@ "type": "boolean" } }, - "required": ["enabled"] + "required": [] }, "iceberg_catalog": { "type": "object", @@ -24819,7 +24786,7 @@ "maximum": 9007199254740991 } }, - "required": ["enabled", "max_namespaces", "max_tables", "max_catalogs"] + "required": [] }, "vector_buckets": { "type": "object", @@ -24838,16 +24805,10 @@ "maximum": 9007199254740991 } }, - "required": ["enabled", "max_buckets", "max_indexes"] + "required": [] } }, - "required": [ - "image_transformation", - "s3_protocol", - "purge_cache", - "iceberg_catalog", - "vector_buckets" - ] + "required": [] }, "capabilities": { "type": "object", @@ -24872,18 +24833,11 @@ "type": "string" } }, - "required": [ - "file_size_limit", - "features", - "capabilities", - "upstream_target", - "migration_version", - "database_pool_mode" - ], + "required": [], "description": "Read from the storage service's admin API rather than the middleware DB, so unlike the rest of this resource it reflects the tenant's live config." } }, - "required": ["database", "pooler", "auth", "api", "realtime", "storage"] + "required": [] } }, "required": ["type", "id", "attributes"] diff --git a/packages/api/src/internal/client.unit.test.ts b/packages/api/src/internal/client.unit.test.ts index 893dd619b4..d4fe0df8a1 100644 --- a/packages/api/src/internal/client.unit.test.ts +++ b/packages/api/src/internal/client.unit.test.ts @@ -1140,9 +1140,69 @@ describe("makeSupabaseApiClient", () => { ), ); - expect(result.data.attributes.database.network_restrictions.entitlement).toBe("disallowed"); - expect(result.data.attributes.database.major_version).toBe(17); - expect(result.data.attributes.storage.upstream_target).toBe("main"); - expect(result.data.attributes.api.db_pool).toBeNull(); + expect(result.data.attributes.database?.network_restrictions?.entitlement).toBe("disallowed"); + expect(result.data.attributes.database?.major_version).toBe(17); + expect(result.data.attributes.storage?.upstream_target).toBe("main"); + expect(result.data.attributes.api?.db_pool).toBeNull(); + }); + + test("decodes a partial v2GetProjectConfig payload missing blocks and block keys", async () => { + // The platform can report a subset of the config surface — staging + // predates `storage.database_pool_mode`, and a permission-truncated + // response can omit whole blocks. The contract keeps every block and + // block key optional (see the V2ProjectConfigResponse entries in + // scripts/openapi-overrides.json) so a partial response degrades at the + // consumer instead of failing the typed decode. + const result = await Effect.runPromise( + makeSupabaseApiClient(config).pipe( + Effect.flatMap((client) => + client.execute<"v2GetProjectConfig">(operationDefinitions.v2GetProjectConfig, { + ref: "abcdefghijklmnopqrst", + }), + ), + Effect.provide( + httpClientLayer((request) => + Effect.succeed( + jsonResponse(request, 200, { + data: { + type: "project_config", + id: "abcdefghijklmnopqrst", + attributes: { + auth: {}, + api: { db_schema: "public" }, + storage: { + file_size_limit: 0, + features: { + image_transformation: { enabled: true }, + s3_protocol: { enabled: true }, + purge_cache: { enabled: true }, + iceberg_catalog: { + enabled: false, + max_namespaces: 0, + max_tables: 0, + max_catalogs: 0, + }, + vector_buckets: { enabled: false, max_buckets: 0, max_indexes: 0 }, + }, + capabilities: { list_v2: true, iceberg_catalog: true }, + upstream_target: "main", + migration_version: "1", + // no database_pool_mode — the exact staging shape + }, + // no database, pooler, realtime blocks at all + }, + }, + }), + ), + ), + ), + ), + ); + + expect(result.data.attributes.api?.db_schema).toBe("public"); + expect(result.data.attributes.storage?.database_pool_mode).toBeUndefined(); + expect(result.data.attributes.database).toBeUndefined(); + expect(result.data.attributes.pooler).toBeUndefined(); + expect(result.data.attributes.realtime).toBeUndefined(); }); }); From 3f23ca8cef284f1de2469e4f87a21c1a375db8ed Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Mon, 31 Aug 2026 14:36:57 -0500 Subject: [PATCH 07/12] fix(config): rebuild the diff classifier on registry-declared semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four classifier fixes from the PR #6295 review, all expressed as registry row knowledge instead of type-level inference: - Array equality is per-field wire semantics: rows gain `arrayEquality`, defaulting to SEQUENCE (api.schemas' first entry is PostgREST's default schema; api.extra_search_path is a literal search_path), with auth.additional_redirect_urls opting into set semantics. Local ["public","extensions"] vs remote "extensions,public" now registers as drift instead of exiting 0. - remote_only suppression no longer infers "unconfigured" from JS zeros — canonicalization turns GoTrue's sessions_timebox: 0 into the string "0s", which escaped the zero check and flagged every untouched project. Rows now declare the platform's `unconfiguredValue` (sessions "0s", the 13 provisioning-default mailer subjects pinned by the recorded config_auth fixtures, notification toggles false per supabase/auth's defaults); with no baseline at any tier the value is reported rather than guessed. A registry-driven test walks every comparable path whose config-side baseline is undefined and pins the choice. - A declared path the local projection drops (auth.oauth_server, disabled storage.analytics/vector, sentinel-pruned siblings, …) surfaces in a new `unmanaged` bucket — rendered like the masked note — instead of printing a false "No config differences found" while the file disagrees with the remote. - ConfigChange paths are segment arrays end-to-end (a test_otp phone key containing "." previously round-tripped to undefined and the drift was silently dropped); joining is display-only in diff.format.ts. The JSON payload emits paths as arrays for the same reason. Structural cleanups riding along: remote_only entries keep the materialized local default plus a `declared` flag (text mode renders "1000 (schema default — not declared in config.toml)"); the dead `defaults` option is deleted; DiffProjectConfigOptions takes the loaded {config, document, valueOrigins} pair so the projection and declared set cannot come from different loads (env references derive from the same pair — CliConfigValueOrigin.envVariables is now a list, never a comma-joined string); counts are computed once in the package and carry `total`. The handler keeps ProjectConfigParseError in the typed channel for both normalizer calls, preserving its suggestion and its purpose-built actionability adapter instead of mislabeling response problems as network errors. Addresses PR #6295 review (Coly010): array-equality, zero-suppression, oauth_server false-clean, remote_only local-nulling, dotted-path, counts, paired-operands, and response-decode threads. Co-Authored-By: Claude Fable 5 --- .../commands/config/diff/diff.format.ts | 73 ++-- .../config/diff/diff.format.unit.test.ts | 23 +- .../commands/config/diff/diff.handler.ts | 53 +-- .../config/diff/diff.integration.test.ts | 75 +++- packages/config/src/config-diff.ts | 344 ++++++++++++------ packages/config/src/config-diff.unit.test.ts | 325 ++++++++++++++--- packages/config/src/config-document.ts | 7 +- packages/config/src/io.ts | 12 +- packages/config/src/lib/env.ts | 23 +- .../src/project-config/registry-auth.ts | 80 +++- .../config/src/project-config/registry-row.ts | 30 ++ 11 files changed, 774 insertions(+), 271 deletions(-) diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.ts index b90e89b1a6..41b207ce7d 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.ts @@ -1,4 +1,4 @@ -import type { ConfigChange, ConfigChangeSet, CliConfigValueOrigin } from "@supabase/config"; +import type { ConfigChange, ConfigChangeSet } from "@supabase/config"; /** * Pure formatters, payload builders, and input adapters for `config diff` — @@ -25,22 +25,6 @@ export function legacyConfigDiffScope( return REMOTE_CONFIG_BLOCKS.filter((block) => isRemoteBlockRecord(attributes[block])); } -/** - * Extracts `dotted path → env var name` for every `env()`-resolved leaf, so a - * change on such a property can name the variable involved. - */ -export function legacyConfigDiffEnvReferences( - valueOrigins: ReadonlyArray | undefined, -): ReadonlyMap { - const references = new Map(); - for (const origin of valueOrigins ?? []) { - if (origin.source === "environment" && origin.envVariable !== undefined) { - references.set(origin.path.join("."), origin.envVariable); - } - } - return references; -} - export interface LegacyConfigDiffContext { /** The resolved comparison target's project ref. */ readonly projectRef: string; @@ -68,6 +52,11 @@ function renderValue(value: unknown, absent: string): string { return JSON.stringify(value); } +/** Display-only join — `ConfigChange.path` is segment-array everywhere else. */ +function renderPath(path: ReadonlyArray): string { + return path.join("."); +} + function localScope(context: LegacyConfigDiffContext): string { return context.appliedRemote === undefined ? "base config" : `[remotes.${context.appliedRemote}]`; } @@ -89,24 +78,43 @@ export function legacyConfigDiffScopeLine(scope: LegacyConfigDiffScope): string return `Comparison scope: ${present}${suffix}\n`; } -function maskedNote(masked: ReadonlyArray): string { - return `Note: ${masked.length} credential value(s) not compared (masked by the API): ${masked.join(", ")}\n`; +function maskedNote(masked: ReadonlyArray>): string { + return `Note: ${masked.length} credential value(s) not compared (masked by the API): ${masked.map(renderPath).join(", ")}\n`; +} + +function unmanagedNote(unmanaged: ReadonlyArray>): string { + const phrase = + unmanaged.length === 1 + ? "1 declared property cannot be pushed and was not compared" + : `${unmanaged.length} declared properties cannot be pushed and were not compared`; + return `Note: ${phrase}: ${unmanaged.map(renderPath).join(", ")}\n`; +} + +function renderLocal(change: ConfigChange): string { + const value = renderValue(change.local, "(unset)"); + // A populated local value on an undeclared path is the schema default the + // projection materialized — the value a `config push` would write. Say so, + // or "[remote only]" reads as "this key exists only remotely", which is + // false for anything with a schema default (and the user will grep their + // file for a value that isn't there). + return change.local !== undefined && !change.declared + ? `${value} (schema default — not declared in config.toml)` + : value; } /** Human-readable diff body for text mode (stdout). */ export function legacyRenderConfigDiffText(changeSet: ConfigChangeSet): string { const lines: Array = []; for (const change of changeSet.changes) { - lines.push(`${change.path} [${CLASS_LABELS[change.class]}]`); - const local = renderValue(change.local, "(unset)"); - const env = change.envVariable === undefined ? "" : ` (from env ${change.envVariable})`; - lines.push(` local: ${local}${env}`); + lines.push(`${renderPath(change.path)} [${CLASS_LABELS[change.class]}]`); + const env = + change.envVariables === undefined ? "" : ` (from env ${change.envVariables.join(", ")})`; + lines.push(` local: ${renderLocal(change)}${env}`); lines.push(` remote: ${renderValue(change.remote, "(not returned)")}`); lines.push(""); } - const { update, remote_only, local_only } = changeSet.counts; - const total = update + remote_only + local_only; + const { update, remote_only, local_only, total } = changeSet.counts; if (total === 0) { lines.push("No config differences found."); } else { @@ -117,12 +125,18 @@ export function legacyRenderConfigDiffText(changeSet: ConfigChangeSet): string { if (changeSet.masked.length > 0) { lines.push(maskedNote(changeSet.masked).trimEnd()); } + if (changeSet.unmanaged.length > 0) { + lines.push(unmanagedNote(changeSet.unmanaged).trimEnd()); + } return `${lines.join("\n")}\n`; } /** * The structured result for `--output-format json|stream-json`. Unset sides - * are explicit `null`s, distinguishable from empty values. + * are explicit `null`s, distinguishable from empty values. Paths are segment + * arrays — a record key (an `sms.test_otp` phone number, a `[remotes.*]` + * name) may itself contain a `.`, so consumers must never split a joined + * string. */ export function legacyConfigDiffPayload( changeSet: ConfigChangeSet, @@ -133,7 +147,6 @@ export function legacyConfigDiffPayload( [key]: value === undefined ? null : value, }); - const { update, remote_only, local_only } = changeSet.counts; return { schema_version: context.schemaVersion, target: { @@ -146,11 +159,13 @@ export function legacyConfigDiffPayload( changes: changeSet.changes.map((change) => ({ path: change.path, class: change.class, + declared: change.declared, ...valueEntry("local", change.local), ...valueEntry("remote", change.remote), - ...(change.envVariable === undefined ? {} : { env_variable: change.envVariable }), + ...(change.envVariables === undefined ? {} : { env_variables: change.envVariables }), })), masked: changeSet.masked, - counts: { update, remote_only, local_only, total: update + remote_only + local_only }, + unmanaged: changeSet.unmanaged, + counts: changeSet.counts, }; } diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts index 1d0cb77b9c..be869bd580 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts @@ -1,10 +1,6 @@ import { describe, expect, test } from "vitest"; -import { - legacyConfigDiffEnvReferences, - legacyConfigDiffScope, - legacyConfigDiffScopeLine, -} from "./diff.format.ts"; +import { legacyConfigDiffScope, legacyConfigDiffScopeLine } from "./diff.format.ts"; describe("legacyConfigDiffScope", () => { test("lists record blocks the response carried, dropping non-records", () => { @@ -20,23 +16,6 @@ describe("legacyConfigDiffScope", () => { }); }); -describe("legacyConfigDiffEnvReferences", () => { - test("collects env-var names for environment origins only", () => { - const references = legacyConfigDiffEnvReferences([ - { path: ["api", "max_rows"], source: "environment", envVariable: "PGRST_MAX_ROWS" }, - { path: ["auth", "site_url"], source: "local" }, - // An environment origin with no recorded name (pre-existing data) is skipped. - { path: ["db", "port"], source: "environment" }, - ]); - expect(references.get("api.max_rows")).toBe("PGRST_MAX_ROWS"); - expect(references.size).toBe(1); - }); - - test("no value origins means no references", () => { - expect(legacyConfigDiffEnvReferences(undefined).size).toBe(0); - }); -}); - describe("legacyConfigDiffScopeLine", () => { test("calls out blocks the response did not return", () => { expect(legacyConfigDiffScopeLine(["api", "auth"])).toBe( diff --git a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts index a95d18b375..a8e8421e93 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts @@ -2,7 +2,7 @@ import { CLI_CONFIG_SCHEMA_URL, diffProjectConfig, fromApiProjectConfig, - fromConfigDocument, + ProjectConfigParseError, } from "@supabase/config"; import { loadCliConfig } from "@supabase/config/effect"; import { Effect, Option } from "effect"; @@ -25,7 +25,6 @@ import { } from "../../../shared/legacy-http-errors.ts"; import { legacyConfigDiffComparisonLine, - legacyConfigDiffEnvReferences, legacyConfigDiffPayload, legacyConfigDiffScope, legacyConfigDiffScopeLine, @@ -170,36 +169,42 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( ); yield* fetching?.clear() ?? Effect.void; - // 3. Project both sides through CLI-2230's convergence normalizers (ADR - // 0021): `fromConfigDocument` gets the loaded config WITH its raw - // document so raw-presence masking applies, and `fromApiProjectConfig` - // canonicalizes the response into the same post-push shape. A response - // the registry cannot narrow (out-of-domain mapped values) is an API - // problem, not a transport one. + // 3. Project the response through CLI-2230's convergence normalizer (ADR + // 0021). A response the registry cannot narrow (out-of-domain mapped + // values) is a response problem, not a transport one: + // `ProjectConfigParseError` stays in the typed channel with its own + // `suggestion` and its purpose-built actionability adapter + // (`externalActionabilityByTag` splits caller misuse from genuine + // response problems). Anything else escaping the normalizer would be a + // bug in this package pairing, so it stays a defect. const remote = yield* Effect.try({ try: () => fromApiProjectConfig(response), - catch: (cause) => - new LegacyConfigDiffReadNetworkError({ - message: `failed to read project config: ${String(cause)}`, - decode: true, - }), - }); + catch: (cause) => cause, + }).pipe( + Effect.catch((cause) => + cause instanceof ProjectConfigParseError ? Effect.fail(cause) : Effect.die(cause), + ), + ); - // 4. Classify. `declared` is the raw merged document (key presence); - // env-resolved leaves carry the resolving variable's name for the output. - const changeSet = diffProjectConfig({ - local: fromConfigDocument(loaded), - remote, - declared: loaded.document, - envReferences: legacyConfigDiffEnvReferences(loaded.valueOrigins), - }); + // 4. Classify. The loaded pair carries the raw merged document (declared + // keys) and the env-var origins; `diffProjectConfig` derives the local + // convergence projection from it, so the same `ProjectConfigParseError` + // boundary applies here. + const changeSet = yield* Effect.try({ + try: () => diffProjectConfig({ local: loaded, remote }), + catch: (cause) => cause, + }).pipe( + Effect.catch((cause) => + cause instanceof ProjectConfigParseError ? Effect.fail(cause) : Effect.die(cause), + ), + ); const scope = legacyConfigDiffScope(response.data.attributes); yield* output.raw(legacyConfigDiffScopeLine(scope), "stderr"); // 5. Emit: `--output-format json|stream-json` structured payload, or text. if (output.format !== "text") { - const total = changeSet.changes.length; + const total = changeSet.counts.total; const message = total === 0 ? "No config differences found." : `${total} config difference(s) found.`; yield* output.success(message, legacyConfigDiffPayload(changeSet, scope, context)); @@ -209,7 +214,7 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( // 6. `--exit-code`: differences flip the exit status after the payload is // out, without an error envelope corrupting machine output. - if (flags.exitCode && changeSet.changes.length > 0) { + if (flags.exitCode && changeSet.counts.total > 0) { yield* processControl.setExitCode(1); } }).pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush)); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts index 94d05c34be..62cb814be7 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -481,9 +481,12 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("an out-of-domain mapped value in the response maps to a decode error", () => { + it.live("an out-of-domain mapped value in the response keeps its typed parse error", () => { // Wire-valid but semantically impossible: the registry's typed throw - // (ADR 0021 API-arm family) surfaces as a decode-flagged read error. + // (ADR 0021 API-arm family) stays in the typed channel as + // ProjectConfigParseError, keeping its upstream suggestion and its + // purpose-built actionability adapter instead of masquerading as a + // network failure. const { layer } = setup({ toml: 'project_id = "test"\n', v2: { @@ -503,8 +506,11 @@ describe("legacy config diff integration", () => { const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); const rendered = JSON.stringify(exit); - expect(rendered).toContain("LegacyConfigDiffReadNetworkError"); - expect(rendered).toContain("failed to read project config"); + expect(rendered).toContain("ProjectConfigParseError"); + expect(rendered).toContain("Could not read the project config"); + // The upstream remedy survives to the renderer instead of being + // stringified away. + expect(rendered).toContain("suggestion"); }).pipe(Effect.provide(layer)); }); @@ -537,10 +543,11 @@ describe("legacy config diff integration", () => { }); expect(data["scope"]).toEqual(["api", "auth", "database", "pooler", "realtime", "storage"]); expect(data["changes"]).toEqual([ - { path: "api.max_rows", class: "update", local: 500, remote: 1000 }, + { path: ["api", "max_rows"], class: "update", declared: true, local: 500, remote: 1000 }, ]); expect(data["counts"]).toEqual({ update: 1, remote_only: 0, local_only: 0, total: 1 }); expect(data["masked"]).toEqual([]); + expect(data["unmanaged"]).toEqual([]); expect(typeof data["schema_version"]).toBe("string"); }).pipe(Effect.provide(layer)); }); @@ -606,11 +613,12 @@ describe("legacy config diff integration", () => { expect(data["target"]).toMatchObject({ local_scope: "remotes.staging" }); expect(data["changes"]).toEqual([ { - path: "api.max_rows", + path: ["api", "max_rows"], class: "update", + declared: true, local: 500, remote: 1000, - env_variable: "PGRST_MAX_ROWS", + env_variables: ["PGRST_MAX_ROWS"], }, ]); }).pipe(Effect.provide(layer)); @@ -639,4 +647,57 @@ describe("legacy config diff integration", () => { expect(out.stdoutText).toContain('remote: "64MB"'); }).pipe(Effect.provide(layer)); }); + + it.live("remote-only drift on a defaulted path shows the local schema default", () => { + // The someone-changed-it-in-the-dashboard case: the file never declares + // api.max_rows, the remote reports 250, and a `config push` would write + // the schema default 1000 over it — the output must say so instead of + // implying the key exists only remotely. + const { layer, out } = setup({ + toml: 'project_id = "test"\n', + v2: { + status: 200, + body: v2Response({ + attributes: (attributes) => ({ + ...attributes, + api: { ...(attributes["api"] as Record), max_rows: 250 }, + }), + }), + }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("api.max_rows [remote only]"); + expect(out.stdoutText).toContain( + "local: 1000 (schema default — not declared in config.toml)", + ); + expect(out.stdoutText).toContain("remote: 250"); + }).pipe(Effect.provide(layer)); + }); + + it.live("a declared path push cannot communicate surfaces in the unmanaged note", () => { + // auth.oauth_server is dropped from the local projection entirely (push + // has no oauth_server handling), so a declared `enabled = true` + // disagreeing with the remote's default `false` cannot be a change entry + // — but it must not vanish silently either. + const { layer, out } = setup({ + toml: 'project_id = "test"\n[auth.oauth_server]\nenabled = true\n', + v2: { + status: 200, + body: v2Response({ + attributes: (attributes) => ({ + ...attributes, + auth: { oauth_server_enabled: false }, + }), + }), + }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("No config differences found."); + expect(out.stdoutText).toContain( + "Note: 1 declared property cannot be pushed and was not compared: auth.oauth_server.enabled", + ); + }).pipe(Effect.provide(layer)); + }); }); diff --git a/packages/config/src/config-diff.ts b/packages/config/src/config-diff.ts index db67fff163..33c9f9b35c 100644 --- a/packages/config/src/config-diff.ts +++ b/packages/config/src/config-diff.ts @@ -1,4 +1,7 @@ +import type { CliConfigValueOrigin } from "./config-document.ts"; import { + type CliConfigWithRawPresence, + comparableProjectConfigPaths, fromConfigDocument, isComparableProjectConfigPath, type ProjectConfig, @@ -13,42 +16,61 @@ import { getDefaultCliConfig } from "./sparse.ts"; * response, resolving the target, and rendering output are the caller's job * (`supabase config diff`, and `config pull` after it). See ADR 0022. * - * Both operands are `ProjectConfig` values from CLI-2230's convergence - * normalizers (ADR 0021): the caller builds `local` with - * `fromConfigDocument({config, document})` (raw-presence-masked, - * canonicalized, secrets omitted) and `remote` with + * Both operands are convergence projections from CLI-2230's normalizers (ADR + * 0021): the local operand is derived here from the loaded `{config, + * document}` pair via `fromConfigDocument` (raw-presence-masked, + * canonicalized, secrets omitted), and the caller builds `remote` with * `fromApiProjectConfig(response)`. The comparable surface is the mapping * registry's — a path with no registry row is unmanaged by construction — * and the raw document's declared-key set drives `update` vs `remote_only`, * since a decoded config cannot distinguish "the file wrote the default" * from "the file is silent". + * + * Paths are segment arrays everywhere in this module's API (a record key — + * an `auth.sms.test_otp` phone number, a `[remotes.*]` name — may itself + * contain a `.`, so dotted strings are lossy); joining is display-only and + * belongs to the renderer. */ export type ConfigChangeClass = "update" | "remote_only" | "local_only"; export interface ConfigChange { - /** Dotted config path within the hosted subset, e.g. `"api.max_rows"`. */ - readonly path: string; + /** + * Config path segments within the hosted subset, e.g. `["api", + * "max_rows"]`. Join for display only — a segment may contain a `.`. + */ + readonly path: ReadonlyArray; /** * `update`: declared locally and reported remotely, values differ. * `remote_only`: reported remotely while the file does not declare it (or * push cannot communicate the declared state), and differing from the - * default config's own convergence projection. `local_only`: the local - * projection carries a declared value the response did not account for. + * unconfigured baseline. `local_only`: the local projection carries a + * declared value the response did not account for. */ readonly class: ConfigChangeClass; - /** Local convergence-projected value; `undefined` when absent. */ + /** + * Local convergence-projected value; `undefined` when the projection is + * silent. For an undeclared `remote_only` path this is the materialized + * schema default — the value a `config push` would write over the remote — + * so consumers can answer "what would push change?" without re-deriving it. + */ readonly local: unknown; /** Remote value; `undefined` when the response did not report it. */ readonly remote: unknown; - /** Environment variable a local `env()` reference resolved from, if any. */ - readonly envVariable?: string | undefined; + /** + * Whether the raw document declares this path — distinguishes "the file + * wrote this value" from "the local side is a schema-materialized default". + */ + readonly declared: boolean; + /** Environment variables local `env()` references resolved from, if any. */ + readonly envVariables?: ReadonlyArray | undefined; } export interface ConfigChangeCounts { readonly update: number; readonly remote_only: number; readonly local_only: number; + readonly total: number; } export interface ConfigChangeSet { @@ -60,44 +82,52 @@ export interface ConfigChangeSet { * digests, and both normalizers omit secret leaves — so a clean `changes` * list is still only a partial claim; callers must surface this. */ - readonly masked: ReadonlyArray; + readonly masked: ReadonlyArray>; + /** + * Comparable non-secret paths the file declares but the local projection + * dropped — declared state a `config push` structurally cannot communicate + * (ADR 0021's unmanaged-by-push families: `auth.oauth_server`, disabled + * `storage.analytics`/`storage.vector`, siblings of a disabled container's + * sentinel, an unselected SMS provider's credentials, …). These were never + * compared on the local side, so — like `masked` — a clean `changes` list + * is only a partial claim; callers must surface this rather than let a + * declared value silently vanish from the comparison. + */ + readonly unmanaged: ReadonlyArray>; readonly counts: ConfigChangeCounts; } export interface DiffProjectConfigOptions { /** - * The local operand: `fromConfigDocument({config, document})`'s prediction - * of the post-push hosted state (pass the loaded config WITH its raw - * document so raw-presence masking applies — ADR 0021's remedy). + * The loaded local config: the `{config, document}` pair + * `fromConfigDocument` accepts (pass the loaded config WITH its raw + * document so raw-presence masking applies — ADR 0021's remedy), plus the + * loader's `valueOrigins` when env-var attribution is wanted. The local + * projection and the declared-key set are both derived from this one value, + * so they can never come from different loads. `LoadedCliConfig` is + * structurally assignable. Note `fromConfigDocument` runs inside + * `diffProjectConfig`, so a document the registry cannot canonicalize + * throws `ProjectConfigParseError` from here. */ - readonly local: ProjectConfig; + readonly local: CliConfigWithRawPresence & { + readonly valueOrigins?: ReadonlyArray | undefined; + }; /** The remote operand: `fromApiProjectConfig(response)`. */ readonly remote: ProjectConfig; - /** - * The raw (pre-decode, post-merge) document the config was loaded from. - * Declares which paths the file actually sets — the decoded config cannot, - * because decoding materializes every default. `undefined` (a file that - * did not parse to an object) means nothing is declared. - */ - readonly declared: Readonly> | undefined; - /** - * Baseline for `remote_only` suppression: a remote value equal to this - * projection's value at the same path is not drift. Defaults to the - * default config's own convergence projection. - */ - readonly defaults?: ProjectConfig; - /** Dotted local path → environment variable name, for `env()` reporting. */ - readonly envReferences?: ReadonlyMap; } function isPlainRecord(value: unknown): value is Readonly> { return typeof value === "object" && value !== null && !Array.isArray(value); } -/** Walks a dotted path through records with own-key checks only. */ -function valueAtPath(root: unknown, path: string): unknown { +function pathKey(path: ReadonlyArray): string { + return JSON.stringify(path); +} + +/** Walks a segment path through records with own-key checks only. */ +function valueAtPath(root: unknown, path: ReadonlyArray): unknown { let current: unknown = root; - for (const segment of path.split(".")) { + for (const segment of path) { if (!isPlainRecord(current) || !Object.hasOwn(current, segment)) { return undefined; } @@ -106,23 +136,25 @@ function valueAtPath(root: unknown, path: string): unknown { return current; } -function isDeclaredAtPath(root: Readonly>, path: string): boolean { +function isDeclaredAtPath( + root: Readonly>, + path: ReadonlyArray, +): boolean { let current: unknown = root; - const segments = path.split("."); - for (const [index, segment] of segments.entries()) { + for (const [index, segment] of path.entries()) { if (!isPlainRecord(current) || !Object.hasOwn(current, segment)) { return false; } - if (index < segments.length - 1) { + if (index < path.length - 1) { current = current[segment]; } } return true; } -/** Collects dotted leaf paths (arrays are leaves; records recurse). */ -function collectLeafPaths(root: ProjectConfig): Array { - const leaves: Array = []; +/** Collects leaf paths as segment arrays (arrays are leaves; records recurse). */ +function collectLeafPaths(root: ProjectConfig): Array> { + const leaves: Array> = []; const walk = (value: unknown, prefix: ReadonlyArray): void => { if (isPlainRecord(value)) { for (const [key, child] of Object.entries(value)) { @@ -131,13 +163,26 @@ function collectLeafPaths(root: ProjectConfig): Array { return; } if (prefix.length > 0) { - leaves.push(prefix.join(".")); + leaves.push(prefix); } }; walk(root, []); return leaves; } +/** Segment-wise path order — the display order of the change list. */ +function comparePaths(a: ReadonlyArray, b: ReadonlyArray): number { + const length = Math.min(a.length, b.length); + for (let index = 0; index < length; index++) { + const left = a[index] as string; + const right = b[index] as string; + if (left !== right) { + return left < right ? -1 : 1; + } + } + return a.length - b.length; +} + function scalarEqual(a: unknown, b: unknown): boolean { if (a === b) { return true; @@ -173,41 +218,93 @@ function canonicalArrayElement(value: unknown): string { return `j:${JSON.stringify(value)}`; } -function isZeroValue(value: unknown): boolean { - return ( - value === false || value === "" || value === 0 || (Array.isArray(value) && value.length === 0) - ); -} +export type ConfigArrayEquality = "set" | "sequence"; /** - * Order-insensitive, type-aware value equality: arrays compare as multisets - * (`additional_redirect_urls` in a different order is not a difference), and - * scalars tolerate string/number and string/boolean representation skew. + * Type-aware value equality. Scalars tolerate string/number and + * string/boolean representation skew. Arrays default to SEQUENCE semantics — + * element order is meaningful unless the field's registry row opts into + * `"set"` (whether an array is a set or a sequence is per-field wire + * knowledge: `api.schemas`' first entry is PostgREST's default schema and + * `api.extra_search_path` is a literal `search_path`, while + * `auth.additional_redirect_urls` is membership-only). Defaulting to + * sequence over-reports rather than under-reports drift. */ -export function isEqualConfigValue(a: unknown, b: unknown): boolean { +export function isEqualConfigValue( + a: unknown, + b: unknown, + arrayEquality: ConfigArrayEquality = "sequence", +): boolean { if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { return false; } - const left = a.map(canonicalArrayElement).sort(); - const right = b.map(canonicalArrayElement).sort(); + const left = a.map(canonicalArrayElement); + const right = b.map(canonicalArrayElement); + if (arrayEquality === "set") { + left.sort(); + right.sort(); + } return left.every((element, index) => element === right[index]); } return scalarEqual(a, b); } -/** Deduped dotted config paths of the registry's secret (`isSecret`) rows. */ -const secretConfigPaths: ReadonlyArray = [ - ...new Set( - projectConfigMappingRows - .filter((row) => row.isSecret === true) - .map((row) => row.configPath.join(".")), - ), -]; - -// The default config's own convergence projection — the `remote_only` -// suppression baseline. Lazy so importing this module never pays for a full -// schema decode + projection up front. +/** Deduped secret (`isSecret`) row config paths, in registry order. */ +const secretConfigPaths: ReadonlyArray> = (() => { + const seen = new Set(); + const paths: Array> = []; + for (const row of projectConfigMappingRows) { + if (row.isSecret !== true || seen.has(pathKey(row.configPath))) { + continue; + } + seen.add(pathKey(row.configPath)); + paths.push(row.configPath); + } + return paths; +})(); + +// Per-path row knowledge the classifier consumes, first row wins — matching +// `comparableProjectConfigPaths`'s own dedupe order for paths several rows +// share. +const arrayEqualityByPathKey: ReadonlyMap = (() => { + const map = new Map(); + for (const row of projectConfigMappingRows) { + if (row.arrayEquality !== undefined && !map.has(pathKey(row.configPath))) { + map.set(pathKey(row.configPath), row.arrayEquality); + } + } + return map; +})(); + +const unconfiguredValueByPathKey: ReadonlyMap = (() => { + const map = new Map(); + for (const row of projectConfigMappingRows) { + if (Object.hasOwn(row, "unconfiguredValue") && !map.has(pathKey(row.configPath))) { + map.set(pathKey(row.configPath), row.unconfiguredValue); + } + } + return map; +})(); + +/** + * Equality at a specific path: array semantics come from the path's registry + * row (or the nearest mapped ancestor — a mapped container's descendant + * leaves inherit its row), defaulting to sequence. + */ +function equalsAtPath(path: ReadonlyArray, a: unknown, b: unknown): boolean { + for (let length = path.length; length >= 1; length--) { + const equality = arrayEqualityByPathKey.get(pathKey(path.slice(0, length))); + if (equality !== undefined) { + return isEqualConfigValue(a, b, equality); + } + } + return isEqualConfigValue(a, b); +} + +// The default config's own convergence projection — the first `remote_only` +// suppression baseline tier. Lazy so importing this module never pays for a +// full schema decode + projection up front. let defaultProjectionMemo: ProjectConfig | undefined; function defaultProjection(): ProjectConfig { defaultProjectionMemo ??= fromConfigDocument(getDefaultCliConfig()); @@ -216,60 +313,81 @@ function defaultProjection(): ProjectConfig { /** * Classifies every comparable path into the change set. Pure: no I/O, no - * dependency on command flags or output formatting. + * dependency on command flags or output formatting. Runs `fromConfigDocument` + * over `options.local`, so a document the registry cannot canonicalize throws + * `ProjectConfigParseError` — callers translate at their own boundary. */ export function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChangeSet { - const defaults = options.defaults ?? defaultProjection(); - const declaredRoot = options.declared ?? {}; + const local = fromConfigDocument(options.local); + const declaredRoot = options.local.document ?? {}; + const envReferences = new Map>(); + for (const origin of options.local.valueOrigins ?? []) { + if (origin.source === "environment" && origin.envVariables !== undefined) { + envReferences.set(pathKey(origin.path), origin.envVariables); + } + } + const changes: Array = []; + const paths = new Map>(); + for (const path of [...collectLeafPaths(local), ...collectLeafPaths(options.remote)]) { + paths.set(pathKey(path), path); + } - const paths = new Set([...collectLeafPaths(options.local), ...collectLeafPaths(options.remote)]); - for (const path of paths) { - if (!isComparableProjectConfigPath(path.split("."))) { + for (const path of paths.values()) { + if (!isComparableProjectConfigPath(path)) { continue; } - const localValue = valueAtPath(options.local, path); + const localValue = valueAtPath(local, path); const remoteValue = valueAtPath(options.remote, path); const declared = isDeclaredAtPath(declaredRoot, path); - const envVariable = options.envReferences?.get(path); + const envVariables = envReferences.get(pathKey(path)); if (localValue !== undefined && remoteValue !== undefined) { - if (isEqualConfigValue(localValue, remoteValue)) { + if (equalsAtPath(path, localValue, remoteValue)) { continue; } // A declared value differing from the remote is an update; an // undeclared one is remote-side drift against the (materialized) - // default the local projection carries. - changes.push( - declared - ? { - path, - class: "update", - local: localValue, - remote: remoteValue, - ...(envVariable === undefined ? {} : { envVariable }), - } - : { path, class: "remote_only", local: undefined, remote: remoteValue }, - ); + // default the local projection carries — which stays populated on the + // change so consumers can see what a push would write. + changes.push({ + path, + class: declared ? "update" : "remote_only", + local: localValue, + remote: remoteValue, + declared, + ...(envVariables === undefined ? {} : { envVariables }), + }); continue; } if (remoteValue !== undefined) { // The local projection is silent: the file doesn't declare it, or push // cannot communicate the declared state (ADR 0021's unmanaged-by-push - // families). Suppress the remote value when it matches the default - // config's own projection; for paths that projection is also silent on - // (push-gated containers), fall back to the raw default config's value - // (e.g. `db.network_restrictions.allowed_cidrs`'s allow-all default is - // exactly the platform's unconfigured state), then to the type's zero - // value (the platform's report of an unconfigured feature). - const baseline = valueAtPath(defaults, path) ?? valueAtPath(getDefaultCliConfig(), path); - const suppressed = - baseline === undefined - ? isZeroValue(remoteValue) - : isEqualConfigValue(baseline, remoteValue); + // families — those paths additionally surface in `unmanaged` below). + // Suppress the remote value when it matches the unconfigured baseline: + // the default config's own projection, then the raw default config + // (push-gated containers, e.g. network restrictions' allow-all), then + // the registry row's declared `unconfiguredValue` (the platform's + // report of an unconfigured feature, e.g. `sessions_timebox: 0` + // canonicalized to `"0s"`, or the provisioning-default mailer + // subjects). With no baseline at any tier the value is reported — + // "unconfigured" is never inferred from type-level zero values, since + // canonicalization can turn a platform zero into a non-zero shape. + const baseline = + valueAtPath(defaultProjection(), path) ?? + valueAtPath(getDefaultCliConfig(), path) ?? + unconfiguredValueByPathKey.get(pathKey(path)); + const suppressed = baseline !== undefined && equalsAtPath(path, baseline, remoteValue); if (!suppressed) { - changes.push({ path, class: "remote_only", local: undefined, remote: remoteValue }); + changes.push({ + path, + class: "remote_only", + local: undefined, + remote: remoteValue, + declared, + ...(envVariables === undefined ? {} : { envVariables }), + }); } continue; } @@ -280,22 +398,36 @@ export function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChan class: "local_only", local: localValue, remote: undefined, - ...(envVariable === undefined ? {} : { envVariable }), + declared, + ...(envVariables === undefined ? {} : { envVariables }), }); } } - changes.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); + changes.sort((a, b) => comparePaths(a.path, b.path)); + + const masked = secretConfigPaths + .filter((path) => isDeclaredAtPath(declaredRoot, path)) + .toSorted(comparePaths); + + // Declared comparable paths the local projection dropped: push cannot + // communicate them, so they were never compared on the local side. + // `comparableProjectConfigPaths` already excludes secret rows, so this + // never overlaps `masked`. + const unmanaged = comparableProjectConfigPaths + .filter( + (path) => isDeclaredAtPath(declaredRoot, path) && valueAtPath(local, path) === undefined, + ) + .toSorted(comparePaths); - const masked = secretConfigPaths.filter((path) => isDeclaredAtPath(declaredRoot, path)).sort(); + const update = changes.filter((change) => change.class === "update").length; + const remote_only = changes.filter((change) => change.class === "remote_only").length; + const local_only = changes.filter((change) => change.class === "local_only").length; return { changes, masked, - counts: { - update: changes.filter((change) => change.class === "update").length, - remote_only: changes.filter((change) => change.class === "remote_only").length, - local_only: changes.filter((change) => change.class === "local_only").length, - }, + unmanaged, + counts: { update, remote_only, local_only, total: update + remote_only + local_only }, }; } diff --git a/packages/config/src/config-diff.unit.test.ts b/packages/config/src/config-diff.unit.test.ts index a14c15ca71..6cccc17696 100644 --- a/packages/config/src/config-diff.unit.test.ts +++ b/packages/config/src/config-diff.unit.test.ts @@ -1,71 +1,85 @@ import { describe, expect, test } from "vitest"; import { Schema } from "effect"; import { CliConfigSchema } from "./base.ts"; +import { diffProjectConfig, isEqualConfigValue, type ConfigChange } from "./config-diff.ts"; +import type { CliConfigValueOrigin } from "./config-document.ts"; import { - diffProjectConfig, - isEqualConfigValue, - type ConfigChange, - type DiffProjectConfigOptions, -} from "./config-diff.ts"; -import { fromApiProjectConfig, fromConfigDocument } from "./project-config/project-config.ts"; + comparableProjectConfigPaths, + fromApiProjectConfig, + fromConfigDocument, +} from "./project-config/project-config.ts"; +import { projectConfigMappingRows } from "./project-config/registry.ts"; +import { getDefaultCliConfig } from "./sparse.ts"; const decodeCliConfig = Schema.decodeUnknownSync(CliConfigSchema); /** * Builds the diff input the way the command layer does: the local operand is - * `fromConfigDocument` over the decoded config WITH its raw document (so - * raw-presence masking applies), the remote operand is `fromApiProjectConfig` - * over bare v2 `data.attributes`, and `declared` is the raw document. + * the loaded `{config, document}` pair (so raw-presence masking applies and + * the declared-key set comes from the same load), the remote operand is + * `fromApiProjectConfig` over bare v2 `data.attributes`. */ function diffWith( declared: Record, attributes: Record, - extra?: Partial, + valueOrigins?: ReadonlyArray, ) { return diffProjectConfig({ - local: fromConfigDocument({ config: decodeCliConfig(declared), document: declared }), + local: { config: decodeCliConfig(declared), document: declared, valueOrigins }, remote: fromApiProjectConfig(attributes), - declared, - ...extra, }); } -function changeAt(changes: ReadonlyArray, path: string): ConfigChange | undefined { - return changes.find((change) => change.path === path); +function changeAt( + changes: ReadonlyArray, + path: ReadonlyArray, +): ConfigChange | undefined { + return changes.find( + (change) => + change.path.length === path.length && + change.path.every((segment, index) => segment === path[index]), + ); } describe("diffProjectConfig classification", () => { test("an undefined declared document means nothing is declared", () => { const result = diffProjectConfig({ - local: fromConfigDocument(decodeCliConfig({})), + local: { config: decodeCliConfig({}) }, remote: fromApiProjectConfig({ api: { max_rows: 250 } }), - declared: undefined, }); - expect(changeAt(result.changes, "api.max_rows")).toMatchObject({ class: "remote_only" }); + expect(changeAt(result.changes, ["api", "max_rows"])).toMatchObject({ class: "remote_only" }); }); test("declared value differing from remote is an update", () => { const result = diffWith({ api: { max_rows: 500 } }, { api: { max_rows: 1000 } }); - const change = changeAt(result.changes, "api.max_rows"); - expect(change).toMatchObject({ class: "update", local: 500, remote: 1000 }); + const change = changeAt(result.changes, ["api", "max_rows"]); + expect(change).toMatchObject({ class: "update", local: 500, remote: 1000, declared: true }); expect(result.counts.update).toBe(1); }); test("declared value equal to remote is not a difference", () => { const result = diffWith({ api: { max_rows: 500 } }, { api: { max_rows: 500 } }); expect(result.changes).toEqual([]); - expect(result.counts).toEqual({ update: 0, remote_only: 0, local_only: 0 }); + expect(result.counts).toEqual({ update: 0, remote_only: 0, local_only: 0, total: 0 }); }); test("remote value at the schema default is suppressed when undeclared", () => { const result = diffWith({}, { api: { max_rows: 1000 } }); - expect(changeAt(result.changes, "api.max_rows")).toBeUndefined(); + expect(changeAt(result.changes, ["api", "max_rows"])).toBeUndefined(); }); - test("remote value off the schema default is remote_only when undeclared", () => { + test("remote-only drift keeps the materialized local default and declared: false", () => { + // The primary someone-changed-it-in-the-dashboard case: the file is + // silent, the local projection carries the schema default (1000), and a + // push would overwrite the remote 250 with it — the change must say so. const result = diffWith({}, { api: { max_rows: 250 } }); - const change = changeAt(result.changes, "api.max_rows"); - expect(change).toMatchObject({ class: "remote_only", local: undefined, remote: 250 }); + const change = changeAt(result.changes, ["api", "max_rows"]); + expect(change).toMatchObject({ + class: "remote_only", + local: 1000, + remote: 250, + declared: false, + }); }); test("raw-presence-masked sections suppress zero-valued remotes", () => { @@ -73,10 +87,10 @@ describe("diffProjectConfig classification", () => { // 0021), so its local projection is silent when the file never declares // it; the platform reporting the unconfigured state is not drift. const clean = diffWith({}, { database: { ssl_enforced: false } }); - expect(changeAt(clean.changes, "db.ssl_enforcement.enabled")).toBeUndefined(); + expect(changeAt(clean.changes, ["db", "ssl_enforcement", "enabled"])).toBeUndefined(); const drifted = diffWith({}, { database: { ssl_enforced: true } }); - expect(changeAt(drifted.changes, "db.ssl_enforcement.enabled")).toMatchObject({ + expect(changeAt(drifted.changes, ["db", "ssl_enforcement", "enabled"])).toMatchObject({ class: "remote_only", remote: true, }); @@ -110,12 +124,142 @@ describe("diffProjectConfig classification", () => { }, }, ); - expect(changeAt(drifted.changes, "db.network_restrictions.allowed_cidrs")).toMatchObject({ + expect( + changeAt(drifted.changes, ["db", "network_restrictions", "allowed_cidrs"]), + ).toMatchObject({ class: "remote_only", remote: ["10.0.0.0/8"], }); }); + test("canonicalized zero durations suppress via the row's unconfiguredValue", () => { + // GoTrue reports 0 hours for unconfigured session bounds; the transform + // canonicalizes that to the STRING "0s", which no type-level zero check + // recognizes — the registry row's `unconfiguredValue` must. An untouched + // project reporting both bounds is clean; a real timebox is drift. + const clean = diffWith({}, { auth: { sessions_timebox: 0, sessions_inactivity_timeout: 0 } }); + expect(clean.changes).toEqual([]); + + const drifted = diffWith({}, { auth: { sessions_timebox: 24 } }); + expect(changeAt(drifted.changes, ["auth", "sessions", "timebox"])).toMatchObject({ + class: "remote_only", + remote: "24h0m0s", + }); + }); + + test("platform-default mailer subjects suppress via the row's unconfiguredValue", () => { + // A fresh project reports the provisioning-default subject lines (pinned + // by the recorded config_auth fixtures); the default config declares no + // subjects, so without the row-level baseline every untouched project + // would flag all 13 of them. + const clean = diffWith( + {}, + { + auth: { + mailer_subjects_confirmation: "Confirm Your Signup", + mailer_subjects_password_changed_notification: "Your password has been changed", + mailer_notifications_password_changed_enabled: false, + }, + }, + ); + expect(clean.changes).toEqual([]); + + const drifted = diffWith( + {}, + { + auth: { + mailer_subjects_confirmation: "Welcome to ACME", + mailer_notifications_password_changed_enabled: true, + }, + }, + ); + expect( + changeAt(drifted.changes, ["auth", "email", "template", "confirmation", "subject"]), + ).toMatchObject({ class: "remote_only", remote: "Welcome to ACME" }); + expect( + changeAt(drifted.changes, ["auth", "email", "notification", "password_changed", "enabled"]), + ).toMatchObject({ class: "remote_only", remote: true }); + }); + + test("every comparable path without a config-side baseline makes a deliberate choice", () => { + // Registry-driven guard for the remote_only suppression baseline: for + // each comparable path the default config's projection AND the raw + // default config are silent on, either its row declares the platform's + // `unconfiguredValue` (and a remote report equal to it classifies clean), + // or the platform's unconfigured report is structural ABSENCE (sentinel- + // pruned SMTP/captcha/SMS/hook siblings, sparse postgres_settings) and a + // zero-form remote — which absence-class paths never receive — must + // REPORT rather than be silently swallowed by type-level zero inference. + const defaults = fromConfigDocument(getDefaultCliConfig()); + const raw = getDefaultCliConfig(); + const valueAt = (root: unknown, path: ReadonlyArray): unknown => { + let current: unknown = root; + for (const segment of path) { + if ( + typeof current !== "object" || + current === null || + Array.isArray(current) || + !Object.hasOwn(current, segment) + ) { + return undefined; + } + current = (current as Record)[segment]; + } + return current; + }; + const rowFor = (path: ReadonlyArray) => + projectConfigMappingRows.find( + (row) => + row.configPath.length === path.length && + row.configPath.every((segment, index) => segment === path[index]), + ); + + const baselineless = comparableProjectConfigPaths.filter( + (path) => (valueAt(defaults, path) ?? valueAt(raw, path)) === undefined, + ); + expect(baselineless.length).toBeGreaterThan(0); + + for (const path of baselineless) { + const row = rowFor(path); + expect(row, path.join(".")).toBeDefined(); + if (row !== undefined && Object.hasOwn(row, "unconfiguredValue")) { + // The declared unconfigured value classifies clean... + const projected: Record = {}; + let cursor = projected; + for (const segment of path.slice(0, -1)) { + cursor[segment] = {}; + cursor = cursor[segment] as Record; + } + cursor[path[path.length - 1] as string] = row.unconfiguredValue; + const result = diffProjectConfig({ + local: { config: decodeCliConfig({}), document: {} }, + remote: projected, + }); + expect(changeAt(result.changes, path), path.join(".")).toBeUndefined(); + } else { + // ...and a path relying on structural absence must not silently + // swallow a zero-form value if the platform ever starts reporting + // one: inject a zero-form leaf directly into the remote projection + // (bypassing the normalizer, which today omits these paths) and + // assert it REPORTS. + const projected: Record = {}; + let cursor = projected; + for (const segment of path.slice(0, -1)) { + cursor[segment] = {}; + cursor = cursor[segment] as Record; + } + cursor[path[path.length - 1] as string] = ""; + const result = diffProjectConfig({ + local: { config: decodeCliConfig({}), document: {} }, + remote: projected, + }); + expect(changeAt(result.changes, path), path.join(".")).toMatchObject({ + class: "remote_only", + }); + } + } + }); + test("undeclared providers reporting their unconfigured state are not drift", () => { const result = diffWith( {}, @@ -130,16 +274,17 @@ describe("diffProjectConfig classification", () => { // auth block present but without site_url. { auth: {} }, ); - expect(changeAt(result.changes, "auth.site_url")).toMatchObject({ + expect(changeAt(result.changes, ["auth", "site_url"])).toMatchObject({ class: "local_only", local: "https://local.example.com", remote: undefined, + declared: true, }); }); test("a wholly absent block turns its declared properties local_only", () => { const result = diffWith({ db: { settings: { max_connections: 120 } } }, {}); - expect(changeAt(result.changes, "db.settings.max_connections")).toMatchObject({ + expect(changeAt(result.changes, ["db", "settings", "max_connections"])).toMatchObject({ class: "local_only", local: 120, }); @@ -158,12 +303,84 @@ describe("diffProjectConfig classification", () => { expect(result.changes).toEqual([]); }); - test("array comparison ignores element order", () => { + test("a declared path the projection cannot push surfaces in unmanaged, never as a false clean", () => { + // `auth.oauth_server` is dropped from the document projection entirely — + // push has no oauth_server handling — so a declared `enabled = true` + // disagreeing with the remote's `false` cannot be a change entry. It must + // surface in `unmanaged` so the clean changes list is visibly partial. const result = diffWith( - { api: { schemas: ["graphql_public", "public"] } }, - { api: { db_schema: "public,graphql_public" } }, + { auth: { oauth_server: { enabled: true } } }, + { auth: { oauth_server_enabled: false } }, ); - expect(changeAt(result.changes, "api.schemas")).toBeUndefined(); + expect(result.changes).toEqual([]); + expect(result.unmanaged).toContainEqual(["auth", "oauth_server", "enabled"]); + }); + + test("declared siblings of a disabled container surface in unmanaged", () => { + // Push writes only the disable sentinel for a disabled SMTP block, so a + // declared host is never communicated — the projection prunes it and the + // unmanaged list says so. + const result = diffWith( + { auth: { email: { smtp: { enabled: false, host: "mail.example.com" } } } }, + { auth: {} }, + ); + expect(result.unmanaged).toContainEqual(["auth", "email", "smtp", "host"]); + }); + + test("an undeclared config is fully managed", () => { + const result = diffWith({}, { auth: {} }); + expect(result.unmanaged).toEqual([]); + }); + + test("sequence arrays register reordering as drift", () => { + // api.schemas is order-significant (the first entry is PostgREST's + // default schema), so local ["public","extensions"] vs the wire's + // "extensions,public" is a real difference — in both declared and + // undeclared classifications. + const result = diffWith( + { api: { schemas: ["public", "extensions"] } }, + { api: { db_schema: "extensions,public" } }, + ); + expect(changeAt(result.changes, ["api", "schemas"])).toMatchObject({ class: "update" }); + + const searchPath = diffWith( + { api: { extra_search_path: ["public", "extensions"] } }, + { api: { db_extra_search_path: "extensions,public" } }, + ); + expect(changeAt(searchPath.changes, ["api", "extra_search_path"])).toMatchObject({ + class: "update", + }); + }); + + test("set-semantics arrays ignore element order", () => { + // additional_redirect_urls is membership-only — its registry row opts + // into set equality. + const result = diffWith( + { auth: { additional_redirect_urls: ["https://b.example.com", "https://a.example.com"] } }, + { auth: { uri_allow_list: "https://a.example.com,https://b.example.com" } }, + ); + expect(changeAt(result.changes, ["auth", "additional_redirect_urls"])).toBeUndefined(); + }); + + test("record keys containing dots survive the classification", () => { + // sms.test_otp is keyed by phone numbers — segment-array paths keep the + // key intact where a dotted-string round-trip would silently lose it. + const declared = { + auth: { + sms: { + enable_confirmations: true, + test_otp: { "415.2127777": "111111" }, + }, + }, + }; + const result = diffWith(declared, { + auth: { sms_test_otp: "415.2127777=999999" }, + }); + expect(changeAt(result.changes, ["auth", "sms", "test_otp", "415.2127777"])).toMatchObject({ + class: "update", + local: "111111", + remote: "999999", + }); }); test("byte-size values converge across representations", () => { @@ -173,13 +390,13 @@ describe("diffProjectConfig classification", () => { { storage: { file_size_limit: "50MiB" } }, { storage: { file_size_limit: 52428800 } }, ); - expect(changeAt(equal.changes, "storage.file_size_limit")).toBeUndefined(); + expect(changeAt(equal.changes, ["storage", "file_size_limit"])).toBeUndefined(); const differing = diffWith( { storage: { file_size_limit: "50MiB" } }, { storage: { file_size_limit: 1048576 } }, ); - expect(changeAt(differing.changes, "storage.file_size_limit")).toMatchObject({ + expect(changeAt(differing.changes, ["storage", "file_size_limit"])).toMatchObject({ class: "update", }); }); @@ -193,9 +410,9 @@ describe("diffProjectConfig classification", () => { const result = diffWith(declared, { auth: { external_github_enabled: true, external_github_client_id: "id" }, }); - expect(result.masked).toContain("auth.external.github.secret"); - expect(changeAt(result.changes, "auth.external.github.secret")).toBeUndefined(); - expect(result.counts).toEqual({ update: 0, remote_only: 0, local_only: 0 }); + expect(result.masked).toContainEqual(["auth", "external", "github", "secret"]); + expect(changeAt(result.changes, ["auth", "external", "github", "secret"])).toBeUndefined(); + expect(result.counts).toEqual({ update: 0, remote_only: 0, local_only: 0, total: 0 }); }); test("undeclared secrets are neither masked nor reported", () => { @@ -204,14 +421,12 @@ describe("diffProjectConfig classification", () => { expect(result.changes.filter((change) => change.path.includes("pass"))).toEqual([]); }); - test("env references annotate the change for the involved variable", () => { - const result = diffWith( - { api: { max_rows: 500 } }, - { api: { max_rows: 1000 } }, - { envReferences: new Map([["api.max_rows", "PGRST_MAX_ROWS"]]) }, - ); - expect(changeAt(result.changes, "api.max_rows")).toMatchObject({ - envVariable: "PGRST_MAX_ROWS", + test("env references annotate the change with every involved variable", () => { + const result = diffWith({ api: { max_rows: 500 } }, { api: { max_rows: 1000 } }, [ + { path: ["api", "max_rows"], source: "environment", envVariables: ["PGRST_MAX_ROWS"] }, + ]); + expect(changeAt(result.changes, ["api", "max_rows"])).toMatchObject({ + envVariables: ["PGRST_MAX_ROWS"], }); }); @@ -220,22 +435,28 @@ describe("diffProjectConfig classification", () => { { api: { max_rows: 5 }, auth: { site_url: "https://local.example.com" } }, { api: { max_rows: 6 }, auth: {}, database: { postgres_settings: { work_mem: "64MB" } } }, ); - const paths = result.changes.map((change) => change.path); - expect(paths).toEqual([...paths].sort()); + const joined = result.changes.map((change) => change.path.join("")); + expect(joined).toEqual([...joined].sort()); expect(result.counts.update).toBe(1); expect(result.counts.remote_only).toBe(1); expect(result.counts.local_only).toBe(1); + expect(result.counts.total).toBe(3); }); }); describe("isEqualConfigValue", () => { - test("multiset semantics for arrays", () => { - expect(isEqualConfigValue(["a", "b"], ["b", "a"])).toBe(true); - expect(isEqualConfigValue(["a", "a", "b"], ["a", "b", "b"])).toBe(false); + test("sequence semantics by default", () => { + expect(isEqualConfigValue(["a", "b"], ["a", "b"])).toBe(true); + expect(isEqualConfigValue(["a", "b"], ["b", "a"])).toBe(false); expect(isEqualConfigValue(["1"], [1])).toBe(true); expect(isEqualConfigValue(["a"], ["a", "a"])).toBe(false); }); + test("set semantics on request", () => { + expect(isEqualConfigValue(["a", "b"], ["b", "a"], "set")).toBe(true); + expect(isEqualConfigValue(["a", "a", "b"], ["a", "b", "b"], "set")).toBe(false); + }); + test("type-aware scalars", () => { expect(isEqualConfigValue("8080", 8080)).toBe(true); expect(isEqualConfigValue(8080, "8080")).toBe(true); diff --git a/packages/config/src/config-document.ts b/packages/config/src/config-document.ts index 5571ed0ace..0f79dd70e6 100644 --- a/packages/config/src/config-document.ts +++ b/packages/config/src/config-document.ts @@ -14,10 +14,11 @@ export interface CliConfigValueOrigin { readonly path: ReadonlyArray; readonly source: CliConfigValueSource; /** - * For `"environment"` origins: the env var name(s) the `env()` reference - * resolved from (comma-joined when one array literal drew on several). + * For `"environment"` origins: the env var names the `env()` reference + * resolved from (one array literal may draw on several, so this is always + * a list — consumers must never have to split a joined string). */ - readonly envVariable?: string; + readonly envVariables?: ReadonlyArray; } export interface LoadedCliConfig { diff --git a/packages/config/src/io.ts b/packages/config/src/io.ts index c32afd2609..89b76d0692 100644 --- a/packages/config/src/io.ts +++ b/packages/config/src/io.ts @@ -526,7 +526,7 @@ export const loadCliConfigFile = Effect.fnUntraced(function* ( const goViperCompat = options?.goViperCompat ?? false; const interpolateDocument = ( document: unknown, - onResolvedEnv?: (path: ReadonlyArray, envName: string) => void, + onResolvedEnv?: (path: ReadonlyArray, envNames: ReadonlyArray) => void, ): unknown => interpolateEnvReferencesAgainstSchema(document, cliProjectEnv?.values ?? {}, CliConfigSchema, { goViperCompat, @@ -574,11 +574,11 @@ export const loadCliConfigFile = Effect.fnUntraced(function* ( // that path, but correctness on the match+`env()` path matters more than // avoiding it. const resolvedEnvironmentPaths: Array = []; - const resolvedEnvironmentNames = new Map(); + const resolvedEnvironmentNames = new Map>(); documentForDecode = isObject(documentForDecode) - ? interpolateDocument(documentForDecode, (path, envName) => { + ? interpolateDocument(documentForDecode, (path, envNames) => { resolvedEnvironmentPaths.push(Array.from(path)); - resolvedEnvironmentNames.set(pathKey(Array.from(path)), envName); + resolvedEnvironmentNames.set(pathKey(Array.from(path)), envNames); }) : documentForDecode; @@ -626,9 +626,9 @@ export const loadCliConfigFile = Effect.fnUntraced(function* ( if (source === undefined) { return []; } - const envVariable = + const envVariables = source === "environment" ? resolvedEnvironmentNames.get(key) : undefined; - return [{ path, source, ...(envVariable === undefined ? {} : { envVariable }) }]; + return [{ path, source, ...(envVariables === undefined ? {} : { envVariables }) }]; }) : []; diff --git a/packages/config/src/lib/env.ts b/packages/config/src/lib/env.ts index 25c936ed9f..5232775110 100644 --- a/packages/config/src/lib/env.ts +++ b/packages/config/src/lib/env.ts @@ -258,7 +258,9 @@ function walk( ast: SchemaAST.AST | null, goViperCompat: boolean, path: ReadonlyArray, - onResolvedEnv: ((path: ReadonlyArray, envName: string) => void) | undefined, + onResolvedEnv: + | ((path: ReadonlyArray, envNames: ReadonlyArray) => void) + | undefined, ): unknown { if (Array.isArray(document)) { // Element-level resolutions are reported once, at the array's own path — @@ -267,9 +269,11 @@ function walk( const onResolvedArrayEnv = onResolvedEnv === undefined ? undefined - : (_: ReadonlyArray, envName: string) => { - if (!envNames.includes(envName)) { - envNames.push(envName); + : (_: ReadonlyArray, resolvedNames: ReadonlyArray) => { + for (const envName of resolvedNames) { + if (!envNames.includes(envName)) { + envNames.push(envName); + } } }; const result = document.map((item, index) => { @@ -277,7 +281,7 @@ function walk( return walk(item, env, child, goViperCompat, [...path, String(index)], onResolvedArrayEnv); }); if (envNames.length > 0) { - onResolvedEnv?.(path, envNames.join(", ")); + onResolvedEnv?.(path, envNames); } return result; } @@ -302,7 +306,7 @@ function walk( const interpolation = substituteEnvLeaf(document, env, goViperCompat); const substituted = interpolation.value; if (interpolation.resolved && interpolation.envName !== undefined) { - onResolvedEnv?.(path, interpolation.envName); + onResolvedEnv?.(path, [interpolation.envName]); } const expected = ast === null ? "unknown" : leafExpectedType(ast); @@ -361,9 +365,10 @@ export function interpolateEnvReferencesAgainstSchema( schema: { readonly ast: SchemaAST.AST }, options?: { readonly goViperCompat?: boolean; - /** Fires per resolved leaf with the substituting env var's name (array - * leaves report once at the array path, names comma-joined). */ - readonly onResolvedEnv?: (path: ReadonlyArray, envName: string) => void; + /** Fires per resolved leaf with the substituting env vars' names (array + * leaves report once at the array path, collecting every element's + * variable — one array literal may draw on several). */ + readonly onResolvedEnv?: (path: ReadonlyArray, envNames: ReadonlyArray) => void; }, ): unknown { return walk( diff --git a/packages/config/src/project-config/registry-auth.ts b/packages/config/src/project-config/registry-auth.ts index a6d1be5329..13aa9fcd98 100644 --- a/packages/config/src/project-config/registry-auth.ts +++ b/packages/config/src/project-config/registry-auth.ts @@ -735,6 +735,10 @@ const coreRows: ReadonlyArray = [ ? undefined : splitCommaSeparated(expectString(value, ["auth", "uri_allow_list"])), normalizeDocument: canonicalizeCommaJoinedArray, + // GoTrue treats the allow list as membership only — reordering the URLs + // changes nothing at runtime, unlike the sequence-semantics CSV arrays + // (`api.schemas`, `api.extra_search_path`). + arrayEquality: "set", unit: "csv → string[]", }, uintRow(["auth", "jwt_expiry"], "jwt_exp"), @@ -787,8 +791,18 @@ const rateLimitRows: ReadonlyArray = [ // SESSIONS (auth.sync.ts:1400-1408) const sessionsRows: ReadonlyArray = [ - hoursDurationRow(["auth", "sessions", "timebox"], "sessions_timebox"), - hoursDurationRow(["auth", "sessions", "inactivity_timeout"], "sessions_inactivity_timeout"), + // GoTrue reports 0 hours for a session bound that was never configured, and + // the transform canonicalizes that to the string "0s" — declare it here so + // the diff baseline recognizes the canonicalized form (a type-level zero + // check would miss it and flag every untouched project). + { + ...hoursDurationRow(["auth", "sessions", "timebox"], "sessions_timebox"), + unconfiguredValue: "0s", + }, + { + ...hoursDurationRow(["auth", "sessions", "inactivity_timeout"], "sessions_inactivity_timeout"), + unconfiguredValue: "0s", + }, ]; // EMAIL (auth.sync.ts:1548-1562) @@ -904,6 +918,24 @@ function smtpSiblingStringRow( // Email templates ×6 (auth.sync.ts:1439-1461; content_path has no API key) +/** + * The subject lines the platform provisions for a project that never touched + * its email templates — what `mailer_subjects_*` reports on a fresh project. + * The default config declares no subjects (there is no meaningful local + * default for a platform-rendered string), so without these the diff would + * flag every untouched project's subjects as `remote_only` drift. Pinned by + * the recorded real responses in `apps/cli-e2e/fixtures/recorded/ + * GET_v1_projects___PROJECT_REF___config_auth/`. + */ +const PLATFORM_DEFAULT_TEMPLATE_SUBJECTS = { + invite: "You have been invited", + confirmation: "Confirm Your Signup", + recovery: "Reset Your Password", + magic_link: "Your Magic Link", + email_change: "Confirm Email Change", + reauthentication: "Confirm Reauthentication", +} as const; + const EMAIL_TEMPLATE_NAMES = [ "invite", "confirmation", @@ -913,12 +945,24 @@ const EMAIL_TEMPLATE_NAMES = [ "reauthentication", ] as const; -const templateRows: ReadonlyArray = EMAIL_TEMPLATE_NAMES.map((name) => - stringRow(["auth", "email", "template", name, "subject"], `mailer_subjects_${name}`), -); +const templateRows: ReadonlyArray = EMAIL_TEMPLATE_NAMES.map((name) => ({ + ...stringRow(["auth", "email", "template", name, "subject"], `mailer_subjects_${name}`), + unconfiguredValue: PLATFORM_DEFAULT_TEMPLATE_SUBJECTS[name], +})); // Email notifications ×7 (auth.sync.ts:1491-1525) +/** Same provenance as {@link PLATFORM_DEFAULT_TEMPLATE_SUBJECTS}. */ +const PLATFORM_DEFAULT_NOTIFICATION_SUBJECTS = { + password_changed: "Your password has been changed", + email_changed: "Your email address has been changed", + phone_changed: "Your phone number has been changed", + identity_linked: "A new identity has been linked", + identity_unlinked: "An identity has been unlinked", + mfa_factor_enrolled: "A new MFA factor has been enrolled", + mfa_factor_unenrolled: "An MFA factor has been unenrolled", +} as const; + const EMAIL_NOTIFICATION_NAMES = [ "password_changed", "email_changed", @@ -931,14 +975,24 @@ const EMAIL_NOTIFICATION_NAMES = [ const notificationRows: ReadonlyArray = EMAIL_NOTIFICATION_NAMES.flatMap( (name) => [ - boolRow( - ["auth", "email", "notification", name, "enabled"], - `mailer_notifications_${name}_enabled`, - ), - stringRow( - ["auth", "email", "notification", name, "subject"], - `mailer_subjects_${name}_notification`, - ), + { + ...boolRow( + ["auth", "email", "notification", name, "enabled"], + `mailer_notifications_${name}_enabled`, + ), + // Every account-change notification defaults to disabled (supabase/auth + // `NotificationsConfiguration`, `default:"false"` on each field) — the + // config schema declares no default, so the diff baseline needs the + // platform's own unconfigured reading here. + unconfiguredValue: false, + }, + { + ...stringRow( + ["auth", "email", "notification", name, "subject"], + `mailer_subjects_${name}_notification`, + ), + unconfiguredValue: PLATFORM_DEFAULT_NOTIFICATION_SUBJECTS[name], + }, ], ); diff --git a/packages/config/src/project-config/registry-row.ts b/packages/config/src/project-config/registry-row.ts index a568dc5b96..1bb7427cf8 100644 --- a/packages/config/src/project-config/registry-row.ts +++ b/packages/config/src/project-config/registry-row.ts @@ -85,6 +85,36 @@ export interface ProjectConfigMappingRow { * counts as mapped for `unmappedApiFields`. */ readonly isSecret?: boolean; + /** + * Equality semantics for an array-valued row when a diff consumer compares + * the two projections (`../config-diff.ts`). Whether an array is a set or a + * sequence is per-field wire knowledge, so it lives here with the rest of + * the field's semantics. `"sequence"` — the default when absent — treats + * element order as meaningful: `api.schemas`' first entry is PostgREST's + * default schema and `api.extra_search_path` is a literal `search_path` + * whose order is resolution order, so a reordering changes runtime behavior + * and must register as drift. `"set"` opts a row out for arrays whose wire + * semantics are order-free (`auth.additional_redirect_urls`). Defaulting to + * sequence over-reports rather than under-reports when a new array row + * forgets to choose. + */ + readonly arrayEquality?: "set" | "sequence"; + /** + * The value the platform reports at `configPath` for a project that never + * configured this feature, expressed in CONFIG-space (post-`transform`) — + * e.g. the `"0s"` an unset `sessions.timebox` canonicalizes to, or the + * provisioning-default mailer subjects (pinned by the recorded + * `GET /v1/projects/{ref}/config/auth` fixtures under + * `apps/cli-e2e/fixtures/recorded/`). `../config-diff.ts` uses this as the + * last `remote_only`-suppression baseline tier for paths the default config + * (and its convergence projection) is silent on: a remote report equal to + * this value is the platform's spelling of "unconfigured", not drift. A row + * without it (and without any other baseline) over-reports rather than + * guesses — "unconfigured" is never inferred from type-level zero values, + * because canonicalization can turn a platform zero into a non-zero shape + * (`sessions_timebox: 0` arrives as the string `"0s"`). + */ + readonly unconfiguredValue?: unknown; /** * Unit/semantics note, e.g. `"csv → string[]"` or `"seconds → duration * string"`. Documentation-only — never read at runtime. From 04a174af160a5638cce50df564e57160ff07706d Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Mon, 31 Aug 2026 14:43:46 -0500 Subject: [PATCH 08/12] fix(cli): make config diff invocable and honor the -o/--output flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two flag-surface fixes from the PR #6295 review: - `Flag.boolean("exit-code")` without `Flag.withDefault(false)` is a REQUIRED flag, so plain `supabase config diff` — the help's own first example — failed with `required flag(s) "exit-code" not set`. The integration suite hands the handler a pre-built flags object and never parses, so a new diff.e2e.test.ts pins the parser at the subprocess boundary. - The global `-o/--output` flag was rejected outright, violating Legacy Shell Invariant #6 ("both --output and --output-format must be honored"). It is now honored with --output taking priority, following the backups/list pattern: `-o json|yaml|toml|env` encode the same structured payload the --output-format json envelope carries through the shared encoders, `pretty` falls through to the text renderer, and stdout stays payload-pure (root.ts already swaps in the quiet-progress layer for machine formats). This also retires the rejection error, its three papercuts (help advertising a flag the handler killed, the unactionable --debug suggestion, the missing suggestion field), and the entry the review flagged in the frozen go-cli-divergences.md record — that file is restored to develop's version, undoing the table reflow. Addresses PR #6295 review (Coly010 blockers/threads; Codex P1s): the required exit-code flag, -o handling, and the frozen-record row. Co-Authored-By: Claude Fable 5 --- apps/cli/docs/go-cli-divergences.md | 15 +++-- .../commands/config/diff/SIDE_EFFECTS.md | 17 +++--- .../commands/config/diff/diff.command.ts | 5 ++ .../commands/config/diff/diff.e2e.test.ts | 27 +++++++++ .../commands/config/diff/diff.errors.ts | 13 ----- .../commands/config/diff/diff.handler.ts | 38 +++++++----- .../config/diff/diff.integration.test.ts | 58 ++++++++++++++----- 7 files changed, 117 insertions(+), 56 deletions(-) create mode 100644 apps/cli/src/legacy/commands/config/diff/diff.e2e.test.ts diff --git a/apps/cli/docs/go-cli-divergences.md b/apps/cli/docs/go-cli-divergences.md index 86018b01b6..693e2e3328 100644 --- a/apps/cli/docs/go-cli-divergences.md +++ b/apps/cli/docs/go-cli-divergences.md @@ -12,14 +12,13 @@ not a compatibility promise. These commands exist in the TS CLI today but have no direct top-level equivalent in the old Go CLI reference. -| TS command | TS path | Notes | -| ----------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `dev` | `planned` | Reserved for a TS-native long-running local development workflow command that watches files and orchestrates subcommands. Track this as TS-only unless a direct Go equivalent emerges. | -| `logs` | [`../src/next/commands/logs/logs.command.ts`](../src/next/commands/logs/logs.command.ts) | Streams local stack logs. No top-level `logs` command exists in the old Go CLI reference. | -| `api` | [`../src/next/commands/platform/api.command.ts`](../src/next/commands/platform/api.command.ts) | Low-level Management API client. It supersedes the old generated tree with explicit discovery via `supabase api routes` and execution via `supabase api request [--method ]`. | -| `stack` | [`../src/next/cli/root.ts`](../src/next/cli/root.ts) | TS-only local runtime namespace exposing `stack start`, `stack stop`, `stack status`, `stack list`, and `stack update`. Top-level `start`, `stop`, and `status` remain aliases. | -| `branches switch` | [`../src/next/commands/branches/switch/switch.command.ts`](../src/next/commands/branches/switch/switch.command.ts) | No direct Go equivalent. Updates local active-branch state so subsequent commands target the selected branch. | -| `config diff` | [`../src/legacy/commands/config/diff/diff.command.ts`](../src/legacy/commands/config/diff/diff.command.ts) | Read-only drift report between `supabase/config.toml` and `GET /v2/projects/{ref}/config` (CLI-2156). TS-only: the old Go CLI had no config diff. `--target` accepts a branch name/UUID/ref; `--exit-code` exits 1 on drift. Rejects the Go-compat `-o/--output` flag outright — machine output is `--output-format json\|stream-json` only (no Go parity contract for net-new commands, per the CLI-2156 discussion). Comparison core lives in `@supabase/config` (ADR 0022). | +| TS command | TS path | Notes | +| ----------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `dev` | `planned` | Reserved for a TS-native long-running local development workflow command that watches files and orchestrates subcommands. Track this as TS-only unless a direct Go equivalent emerges. | +| `logs` | [`../src/next/commands/logs/logs.command.ts`](../src/next/commands/logs/logs.command.ts) | Streams local stack logs. No top-level `logs` command exists in the old Go CLI reference. | +| `api` | [`../src/next/commands/platform/api.command.ts`](../src/next/commands/platform/api.command.ts) | Low-level Management API client. It supersedes the old generated tree with explicit discovery via `supabase api routes` and execution via `supabase api request [--method ]`. | +| `stack` | [`../src/next/cli/root.ts`](../src/next/cli/root.ts) | TS-only local runtime namespace exposing `stack start`, `stack stop`, `stack status`, `stack list`, and `stack update`. Top-level `start`, `stop`, and `status` remain aliases. | +| `branches switch` | [`../src/next/commands/branches/switch/switch.command.ts`](../src/next/commands/branches/switch/switch.command.ts) | No direct Go equivalent. Updates local active-branch state so subsequent commands target the selected branch. | ## Flag divergences from the Go reference diff --git a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md index d42d1144cb..85fdec36f2 100644 --- a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md @@ -6,8 +6,6 @@ Classifies every remotely-managed property as `update` / `remote_only` / `local_only` (unmanaged local-only properties are never reported). **Never writes `config.toml` or any remote configuration.** -TS-only command — no Go CLI equivalent (see `docs/go-cli-divergences.md`). - ## Files Read | Path | Format | When | @@ -54,7 +52,6 @@ All Bearer-authenticated, all read-only. | ---- | ------------------------------------------------------------------------------ | | `0` | success — including when differences are found, unless `--exit-code` is passed | | `1` | `--exit-code` passed and at least one difference found | -| `1` | the Go-compat `-o/--output` global flag passed (any value — unsupported here) | | `1` | missing or malformed `supabase/config.toml` | | `1` | `--target` and `--project-ref` passed together | | `1` | unknown branch (`--target` 404) | @@ -85,13 +82,15 @@ the file sets masked secrets. `env_variable`; unset sides are `null`), `masked[]`, and `counts` (per class + `total`). -### `-o/--output` (Go-compat global flag) +### `-o/--output` (legacy machine formats) -**Not supported.** Any `-o` value — the machine formats and `pretty` alike — -fails fast (before target resolution or any network call) with -`the -o/--output flag is not supported by config diff; use --output-format -json|stream-json instead.` This is a net-new TS command with no Go parity -contract (CLI-2156 ticket discussion). +Honored, and takes priority over `--output-format` (Legacy Shell Invariant +#6): `-o json|yaml|toml|env` encodes the same structured payload the +`--output-format json` envelope carries (TOML omits `null`-valued entries — +TOML has no null; env flattens to SCREAMING_SNAKE keys with arrays collapsing +to empty strings, the established `godotenv` shape). stdout is payload-pure in +every machine mode; diagnostics stay on stderr. `-o pretty` (and no `-o`) +falls through to `--output-format` handling. ## Notes diff --git a/apps/cli/src/legacy/commands/config/diff/diff.command.ts b/apps/cli/src/legacy/commands/config/diff/diff.command.ts index c8fba014dc..3d194203b2 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.command.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.command.ts @@ -19,6 +19,11 @@ const config = { ), exitCode: Flag.boolean("exit-code").pipe( Flag.withDescription("Exit with status 1 when any difference is found."), + // Without an explicit default a boolean flag is REQUIRED by the parser, + // making plain `supabase config diff` fail with `required flag(s) + // "exit-code" not set` — pinned by diff.e2e.test.ts, since integration + // tests hand the handler a pre-built flags object and never parse. + Flag.withDefault(false), ), } as const; diff --git a/apps/cli/src/legacy/commands/config/diff/diff.e2e.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.e2e.test.ts new file mode 100644 index 0000000000..5d93a0cef3 --- /dev/null +++ b/apps/cli/src/legacy/commands/config/diff/diff.e2e.test.ts @@ -0,0 +1,27 @@ +import { mkdtemp } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, test } from "vitest"; + +import { runSupabase } from "../../../../../tests/helpers/cli.ts"; + +describe("config diff CLI surface", () => { + test("plain `config diff` parses — no boolean flag is accidentally required", async () => { + // Parser-level regression pin (PR #6295 review): a `Flag.boolean` without + // `Flag.withDefault(false)` is a REQUIRED flag, so the help's own first + // example (`supabase config diff`) failed with `required flag(s) + // "exit-code" not set`. Integration tests hand the handler a pre-built + // flags object and never exercise the parser, so this must be pinned at + // the subprocess boundary. The invocation is expected to fail LATER (no + // linked project in this hermetic cwd/HOME) — the assertion is only that + // it gets past the parser. + const cwd = await mkdtemp(join(tmpdir(), "supabase-config-diff-e2e-")); + const { stdout, stderr } = await runSupabase(["config", "diff"], { + entrypoint: "legacy", + cwd, + }); + const combined = `${stdout}\n${stderr}`; + expect(combined).not.toContain("required flag"); + expect(combined).not.toContain("exit-code"); + }); +}); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts index 902456af19..2094c28a1e 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts @@ -26,19 +26,6 @@ export class LegacyConfigDiffLoadConfigError extends Data.TaggedError( } } -/** - * The Go-compat global `-o/--output` flag was passed. `config diff` is a - * net-new TS command with no Go parity contract, so machine output goes - * through `--output-format` only (per Colum on CLI-2156). - */ -export class LegacyConfigDiffOutputFlagUnsupportedError extends Data.TaggedError( - "LegacyConfigDiffOutputFlagUnsupportedError", -)<{ readonly message: string }> { - get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { - return actionability.invalidInput; - } -} - /** `--target` and `--project-ref` passed together. */ export class LegacyConfigDiffFlagConflictError extends Data.TaggedError( "LegacyConfigDiffFlagConflictError", diff --git a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts index a8e8421e93..f8267cc7a9 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts @@ -31,13 +31,18 @@ import { legacyRenderConfigDiffText, type LegacyConfigDiffContext, } from "./diff.format.ts"; +import { + encodeEnv, + encodeGoJson, + encodeToml, + encodeYaml, +} from "../../../shared/legacy-go-output.encoders.ts"; import { LegacyConfigDiffBranchNotFoundError, LegacyConfigDiffBranchResolveNetworkError, LegacyConfigDiffBranchResolveStatusError, LegacyConfigDiffFlagConflictError, LegacyConfigDiffLoadConfigError, - LegacyConfigDiffOutputFlagUnsupportedError, LegacyConfigDiffReadNetworkError, LegacyConfigDiffReadStatusError, } from "./diff.errors.ts"; @@ -64,17 +69,6 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( const processControl = yield* ProcessControl; const goOutputFlag = yield* LegacyOutputFlag; - // Net-new TS command with no Go parity contract: the Go-compat `-o/--output` - // flag is rejected outright (every value, `pretty` included) rather than - // honored — machine output goes through `--output-format` only (CLI-2156, - // per Colum). Checked first so no target resolution or network call runs. - if (Option.isSome(goOutputFlag)) { - return yield* new LegacyConfigDiffOutputFlagUnsupportedError({ - message: - "the -o/--output flag is not supported by config diff; use --output-format json|stream-json instead.", - }); - } - if (Option.isSome(flags.target) && Option.isSome(flags.projectRef)) { return yield* new LegacyConfigDiffFlagConflictError({ message: "--target and --project-ref are mutually exclusive; pass at most one.", @@ -203,7 +197,25 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( yield* output.raw(legacyConfigDiffScopeLine(scope), "stderr"); // 5. Emit: `--output-format json|stream-json` structured payload, or text. - if (output.format !== "text") { + // Both output mechanisms are honored, `--output` first (Legacy Shell + // Invariant #6): the machine formats encode the same structured payload + // the `--output-format json` envelope carries; `pretty` (and unset) falls + // through to `--output-format` handling. stdout stays payload-pure in + // every machine mode — diagnostics above went to stderr, and root.ts + // swaps in the quiet-progress layer for `-o` machine formats (CLI-1546). + const goFmt = Option.getOrUndefined(goOutputFlag); + if (goFmt !== undefined && goFmt !== "pretty") { + const payload = legacyConfigDiffPayload(changeSet, scope, context); + if (goFmt === "json") { + yield* output.raw(encodeGoJson(payload)); + } else if (goFmt === "yaml") { + yield* output.raw(encodeYaml(payload)); + } else if (goFmt === "toml") { + yield* output.raw(encodeToml(payload)); + } else { + yield* output.raw(encodeEnv(payload) + "\n"); + } + } else if (output.format !== "text") { const total = changeSet.counts.total; const message = total === 0 ? "No config differences found." : `${total} config difference(s) found.`; diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts index 62cb814be7..d24ab43cbd 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -561,26 +561,58 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("the Go-compat -o flag is rejected outright before any work happens", () => { - // Net-new TS command, no Go parity: every `-o` value is rejected — the - // machine formats and `pretty` alike (CLI-2156, per Colum). - const run = (goOutput: "json" | "pretty") => { - const { layer, api } = setup({ + it.live("-o json emits the raw payload on a payload-pure stdout", () => { + // Legacy Shell Invariant #6: `--output` is honored and takes priority. + // No envelope — the payload object itself, parseable from stdout. + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + goOutput: "json", + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + const payload = JSON.parse(out.stdoutText) as Record; + expect(payload["changes"]).toEqual([ + { path: ["api", "max_rows"], class: "update", declared: true, local: 500, remote: 1000 }, + ]); + expect(payload["counts"]).toMatchObject({ total: 1 }); + // The envelope fields of --output-format json must not leak in. + expect(payload["message"]).toBeUndefined(); + }).pipe(Effect.provide(layer)); + }); + + it.live("-o pretty falls through to the text renderer", () => { + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + goOutput: "pretty", + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("api.max_rows [update]"); + expect(out.stdoutText).toContain("1 difference(s) found"); + }).pipe(Effect.provide(layer)); + }); + + it.live("-o yaml/toml/env encode the payload through the shared encoders", () => { + const run = (goOutput: "yaml" | "toml" | "env", assert: (stdout: string) => void) => { + const { layer, out } = setup({ toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', goOutput, }); return Effect.gen(function* () { - const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); - expect(Exit.isFailure(exit)).toBe(true); - const rendered = JSON.stringify(exit); - expect(rendered).toContain("LegacyConfigDiffOutputFlagUnsupportedError"); - expect(rendered).toContain("use --output-format json|stream-json instead"); - expect(api.requests).toHaveLength(0); + yield* legacyConfigDiff(noFlags); + assert(out.stdoutText); }).pipe(Effect.provide(layer)); }; return Effect.gen(function* () { - yield* run("json"); - yield* run("pretty"); + yield* run("yaml", (stdout) => { + expect(stdout).toContain("class: update"); + }); + yield* run("toml", (stdout) => { + expect(stdout).toContain('class = "update"'); + }); + yield* run("env", (stdout) => { + expect(stdout).toContain("COUNTS_TOTAL=1"); + }); }); }); From fba78b70840b58305d6f56b107658363411fd2d2 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Mon, 31 Aug 2026 14:52:38 -0500 Subject: [PATCH 09/12] refactor(cli): fold config diff's --target into a branch-accepting --project-ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `--target` re-invented vocabulary `link` already settled (CLI-2167): it was a strict superset of `--project-ref`, the two were mutually exclusive, and its description omitted the 20-lowercase-letters rule, so a branch named like a ref silently resolved as a project. The command now has one flag — `--project-ref` accepting a project ref or the name (or UUID) of a branch of the linked project, with link's exact description sentence — keeping diff flag-compatible with config push and retiring the conflict error. The resolution pipeline is restructured around it: - The local config is loaded and validated BEFORE any network call: a fresh directory gets `supabase init` instead of the resolver's not-linked error, and a malformed TOML no longer burns a branch-resolution round trip. Configs declaring [remotes.*] reload once the target ref is known so the overlay stays keyed by the RESOLVED ref; remotes-free configs load exactly once. - The parent project ref is passed to the branch resolver lazily and evaluated only for branch-NAME lookups, so a UUID --project-ref works in an unlinked directory (`GET /v1/branches/{id}` needs no parent). - Branch resolution runs under an output.task, matching the config fetch's own progress treatment. - A UUID target echoes as `branch (project ref )` instead of being quoted as if it were a display name. - Telemetry now flushes on EVERY invocation (Legacy Shell Invariant #1) — load failures and branch-resolution failures included — while the linked-project cache write fires exactly when a ref resolved. Telemetry alignment rides along: diff logs `--project-ref` verbatim only when ref-shaped (link's guard — a branch name must never reach PostHog), config push gains the same-family safe logging its ref-only flag always qualified for, and the documented safe list in apps/cli/CLAUDE.md now names the config family and the branch-accepting guard rule. Addresses PR #6295 review (Coly010: --target vocabulary, safeFlags drift, TOML-before-network, telemetry-flush threads; Codex: UUID-without- link, branch-resolution progress, pre-resolution telemetry). Co-Authored-By: Claude Fable 5 --- apps/cli/AGENTS.md | 2 +- .../commands/config/diff/SIDE_EFFECTS.md | 27 ++- .../commands/config/diff/diff.command.ts | 24 ++- .../commands/config/diff/diff.errors.ts | 11 +- .../commands/config/diff/diff.format.ts | 8 +- .../commands/config/diff/diff.handler.ts | 175 +++++++++++------- .../config/diff/diff.integration.test.ts | 52 +++--- .../commands/config/push/push.command.ts | 6 +- .../shared/legacy-branch-ref.resolver.ts | 15 +- 9 files changed, 185 insertions(+), 135 deletions(-) diff --git a/apps/cli/AGENTS.md b/apps/cli/AGENTS.md index 797be14ebd..8013faaeee 100644 --- a/apps/cli/AGENTS.md +++ b/apps/cli/AGENTS.md @@ -310,7 +310,7 @@ The legacy shell sends PostHog events to the product analytics pipeline. Drift i - **The canonical catalog is `shared/telemetry/event-catalog.ts`.** Reference its exported constants (`EventCommandExecuted`, `PropFlags`, `EnvSignalPresenceKeys`, …) instead of writing bare strings. The TS catalog is the source of truth for event names and property keys. - **Native legacy commands wrap with `withLegacyCommandInstrumentation`** (from `legacy/telemetry/legacy-command-instrumentation.ts`) — _not_ the shared `withCommandInstrumentation`. The legacy variant emits the established property shape: a single `flags` map (vs `flags_used`/`flag_values`), `is_agent: boolean` (vs `ai_tool: string`), and `env_signals`. - **Pass `flags` to the wrapper** so boolean flag values can be detected and logged verbatim: `handler(flags).pipe(withLegacyCommandInstrumentation({ flags }), ...)`. Sensitive values become the literal string `""`. -- **Use `safeFlags: ["flag-name"]`** to whitelist flags whose values are safe to log verbatim. The established list: `--project-ref` (sso, branches, link, functions, projects/api-keys), `--project-id` (gen/types), `--org-id` (projects/create), and `--version` (migration/squash). Extend it only for flags whose values carry no user data. +- **Use `safeFlags: ["flag-name"]`** to whitelist flags whose values are safe to log verbatim. The established list: `--project-ref` (sso, branches, link, functions, projects/api-keys, config push/diff), `--project-id` (gen/types), `--org-id` (projects/create), and `--version` (migration/squash). Extend it only for flags whose values carry no user data. When a `--project-ref` also accepts branch names (link, config diff — CLI-2167 vocabulary), gate the whitelist on `PROJECT_REF_PATTERN.test(...)` so a user-created branch name is never logged verbatim. - **Pass `config` (the command's own flag config record) to the wrapper** if it has any `Flag.choice`/`Flag.choiceWithValue` flags: `withLegacyCommandInstrumentation({ flags, config })`. Every choice flag declared in that command's own `config` is auto-detected and treated as safe — closed enums carry no user data — and it stays correct as choices are added or removed. A command's own `config` only ever contains its own locally-declared flags, so this cannot cover the 3 global choice flags (`--output`, `--dns-resolver`, `--agent` in `shared/legacy/global-flags.ts`) — those are handled separately, see below. - **Global/persistent flags (`shared/legacy/global-flags.ts`) resolve automatically** — the wrapper reads `legacyGlobalFlagValues` (via `Effect.serviceOption`, so it's a no-op outside the real CLI tree) and falls back to it whenever a changed flag name isn't in the handler's own `flags` record. No per-command wiring needed. This gives two flag families their real value automatically, via the boolean-is-safe rule and the choice-is-safe rule (`GLOBAL_CHOICE_FLAG_NAMES` — CLI-1904) respectively: - Boolean globals: `--debug`, `--yes`, `--experimental`, `--create-ticket`. diff --git a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md index 85fdec36f2..5fa35537c5 100644 --- a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md @@ -8,13 +8,13 @@ writes `config.toml` or any remote configuration.** ## Files Read -| Path | Format | When | -| ---------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------ | -| `/supabase/config.toml` | TOML | always, before any network call (missing file or parse error aborts, exit 1) | -| `/supabase/.env`, `.env.local` | dotenv | always, to resolve `env(VAR)` references inside `config.toml` | -| `/supabase/.temp/project-ref` | plain text | project-ref fallback (flag → `SUPABASE_PROJECT_ID` → this file); parent-ref for `--target` | -| `/supabase/.temp/linked-project.json` | JSON | existence check only, for the telemetry cache write below | -| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| Path | Format | When | +| ---------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `/supabase/config.toml` | TOML | always, before any network call (missing file or parse error aborts, exit 1); re-read after target resolution when the file declares `[remotes.*]`, to apply the matching overlay | +| `/supabase/.env`, `.env.local` | dotenv | always, to resolve `env(VAR)` references inside `config.toml` | +| `/supabase/.temp/project-ref` | plain text | project-ref fallback (flag → `SUPABASE_PROJECT_ID` → this file); parent-ref for a branch-name `--project-ref` | +| `/supabase/.temp/linked-project.json` | JSON | existence check only, for the telemetry cache write below | +| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | ## Files Written @@ -31,11 +31,11 @@ that finds differences. All Bearer-authenticated, all read-only. -| # | Purpose | Method | Path | Success | Notes | -| --- | ---------------------------------- | ------ | ------------------------------------ | ------- | ---------------------------------------------------------------------- | -| 0a | branch by UUID (`--target `) | GET | `/v1/branches/{branch_id}` | 200 | only when `--target` is a UUID | -| 0b | branch by name (`--target `) | GET | `/v1/projects/{ref}/branches/{name}` | 200 | only when `--target` is not a ref/UUID; 404 → "branch not found" error | -| 1 | effective remote config | GET | `/v2/projects/{ref}/config` | 200 | always (after target resolution) | +| # | Purpose | Method | Path | Success | Notes | +| --- | ----------------------- | ------ | ------------------------------------ | ------- | --------------------------------------------------------------------- | +| 0a | branch by UUID | GET | `/v1/branches/{branch_id}` | 200 | only when `--project-ref` is a UUID; needs no linked project | +| 0b | branch by name | GET | `/v1/projects/{ref}/branches/{name}` | 200 | only when `--project-ref` is not a ref/UUID; 404 → "branch not found" | +| 1 | effective remote config | GET | `/v2/projects/{ref}/config` | 200 | always (after target resolution) | ## Environment Variables @@ -53,8 +53,7 @@ All Bearer-authenticated, all read-only. | `0` | success — including when differences are found, unless `--exit-code` is passed | | `1` | `--exit-code` passed and at least one difference found | | `1` | missing or malformed `supabase/config.toml` | -| `1` | `--target` and `--project-ref` passed together | -| `1` | unknown branch (`--target` 404) | +| `1` | unknown branch (branch-name `--project-ref` 404) | | `1` | two `[remotes.*]` blocks declare the same `project_id` as the target ref | | `1` | remote config read failure (network or unexpected status) | diff --git a/apps/cli/src/legacy/commands/config/diff/diff.command.ts b/apps/cli/src/legacy/commands/config/diff/diff.command.ts index 3d194203b2..f1d6fae6bb 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.command.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.command.ts @@ -1,19 +1,19 @@ +import { Option } from "effect"; import type * as CliCommand from "effect/unstable/cli/Command"; import { Command, Flag } from "effect/unstable/cli"; +import { PROJECT_REF_PATTERN } from "../../../config/legacy-project-ref.service.ts"; import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyConfigDiff } from "./diff.handler.ts"; const config = { + // `link`'s settled vocabulary (CLI-2167): one flag that accepts either a + // project ref or a branch of the linked project — no separate `--target`. projectRef: Flag.string("project-ref").pipe( - Flag.withDescription("Project ref of the Supabase project."), - Flag.optional, - ), - target: Flag.string("target").pipe( Flag.withDescription( - "Branch name, branch ID, or project ref to compare against. Mutually exclusive with --project-ref.", + "Project ref of the Supabase project, or the name (or UUID) of one of its branches. Values that are exactly 20 lowercase letters are always treated as project refs.", ), Flag.optional, ), @@ -40,13 +40,23 @@ export const legacyConfigDiffCommand = Command.make("diff", config).pipe( description: "Diff against the linked project", }, { - command: "supabase config diff --target staging --exit-code", + command: "supabase config diff --project-ref staging --exit-code", description: "Diff against the 'staging' branch, exiting 1 on drift", }, ]), Command.withHandler((flags) => legacyConfigDiff(flags).pipe( - withLegacyCommandInstrumentation({ flags, safeFlags: ["project-ref"] }), + // `--project-ref` accepts branch names here (CLI-2167 vocabulary), so + // its value is only safe to log verbatim when it is actually ref-shaped + // — a user-created branch name must never reach PostHog. Same guard as + // `link`. + withLegacyCommandInstrumentation({ + flags, + safeFlags: + Option.isSome(flags.projectRef) && PROJECT_REF_PATTERN.test(flags.projectRef.value) + ? ["project-ref"] + : [], + }), withJsonErrorHandling, ), ), diff --git a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts index 2094c28a1e..29e014edf8 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts @@ -26,16 +26,7 @@ export class LegacyConfigDiffLoadConfigError extends Data.TaggedError( } } -/** `--target` and `--project-ref` passed together. */ -export class LegacyConfigDiffFlagConflictError extends Data.TaggedError( - "LegacyConfigDiffFlagConflictError", -)<{ readonly message: string }> { - get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { - return actionability.invalidInput; - } -} - -/** `--target` named a branch the parent project does not have. */ +/** `--project-ref` named a branch the parent project does not have. */ export class LegacyConfigDiffBranchNotFoundError extends Data.TaggedError( "LegacyConfigDiffBranchNotFoundError", )<{ readonly message: string }> { diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.ts index 41b207ce7d..2d29371f4d 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.ts @@ -1,5 +1,7 @@ import type { ConfigChange, ConfigChangeSet } from "@supabase/config"; +import { LEGACY_BRANCH_UUID_PATTERN } from "../../../shared/legacy-branch-ref.resolver.ts"; + /** * Pure formatters, payload builders, and input adapters for `config diff` — * no Effect, no services, unit-testable in isolation. @@ -63,10 +65,14 @@ function localScope(context: LegacyConfigDiffContext): string { /** The target-echo line, printed to stderr before any comparison output. */ export function legacyConfigDiffComparisonLine(context: LegacyConfigDiffContext): string { + // A UUID target is an identifier, not a display name — quoting it as + // `'1111…'` would imply the branch is literally named that. const target = context.branch === undefined ? `project ${context.projectRef}` - : `'${context.branch}' (branch ${context.projectRef})`; + : LEGACY_BRANCH_UUID_PATTERN.test(context.branch) + ? `branch ${context.branch} (project ref ${context.projectRef})` + : `'${context.branch}' (branch ${context.projectRef})`; return `Comparing against ${target} using ${localScope(context)}\n`; } diff --git a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts index f8267cc7a9..83a208e85b 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts @@ -41,7 +41,6 @@ import { LegacyConfigDiffBranchNotFoundError, LegacyConfigDiffBranchResolveNetworkError, LegacyConfigDiffBranchResolveStatusError, - LegacyConfigDiffFlagConflictError, LegacyConfigDiffLoadConfigError, LegacyConfigDiffReadNetworkError, LegacyConfigDiffReadStatusError, @@ -57,6 +56,10 @@ const mapBranchResolveError = mapLegacyHttpError({ statusMessage: readStatusMessage, }); +function isRecord(value: unknown): value is Readonly> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( flags: LegacyConfigDiffFlags, ) { @@ -69,57 +72,11 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( const processControl = yield* ProcessControl; const goOutputFlag = yield* LegacyOutputFlag; - if (Option.isSome(flags.target) && Option.isSome(flags.projectRef)) { - return yield* new LegacyConfigDiffFlagConflictError({ - message: "--target and --project-ref are mutually exclusive; pass at most one.", - }); - } - - // Resolve the comparison target to a project ref. `--target` accepts a - // branch name, a branch UUID, or a raw project ref (same acceptance as - // `link`); a ref-shaped value skips the parent-project resolution entirely - // so it works in an unlinked directory. - let ref: string; - let branch: string | undefined; - if (Option.isSome(flags.target) && !LEGACY_BRANCH_PROJECT_REF_PATTERN.test(flags.target.value)) { - const target = flags.target.value; - branch = target; - const parentRef = yield* resolver.resolve(Option.none()); - ref = yield* legacyResolveBranchProjectRef(target, parentRef, { - mapGetError: mapBranchResolveError, - mapFindError: mapBranchResolveError, - }).pipe( - Effect.catchTag( - "LegacyConfigDiffBranchResolveStatusError", - ( - cause, - ): Effect.Effect< - never, - LegacyConfigDiffBranchNotFoundError | LegacyConfigDiffBranchResolveStatusError - > => - cause.status === 404 - ? Effect.fail( - new LegacyConfigDiffBranchNotFoundError({ - message: `Branch "${legacySanitizeInlineName(target)}" not found. Run \`supabase branches list\` to see available branches.`, - }), - ) - : Effect.fail(cause), - ), - ); - } else if (Option.isSome(flags.target)) { - ref = flags.target.value; - } else { - ref = yield* resolver.resolve(flags.projectRef); - } + // An empty `--project-ref` value is absent, mirroring the resolver's own rule. + const requested = Option.filter(flags.projectRef, (value) => value.length > 0); - yield* Effect.gen(function* () { - // 1. Load the local config, merging a matching `[remotes.*]` block over - // the base document when the target ref names a declared branch (ADR - // 0018). Never writes — this command is read-only by contract. - const loaded = yield* loadCliConfig(runtimeInfo.cwd, { - projectRef: ref, - goViperCompat: true, - }).pipe( + const loadLocalConfig = (projectRef: string | undefined) => + loadCliConfig(runtimeInfo.cwd, { projectRef, goViperCompat: true }).pipe( Effect.catchTag( "CliConfigParseError", (cause) => @@ -131,12 +88,83 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( "DuplicateRemoteProjectIdError", (cause) => new LegacyConfigDiffLoadConfigError({ message: cause.message }), ), + Effect.flatMap((loaded) => + loaded === null + ? Effect.fail( + new LegacyConfigDiffLoadConfigError({ + message: + "failed to read supabase/config.toml: file not found. Run `supabase init` to create one.", + }), + ) + : Effect.succeed(loaded), + ), ); - if (loaded === null) { - return yield* new LegacyConfigDiffLoadConfigError({ - message: - "failed to read supabase/config.toml: file not found. Run `supabase init` to create one.", - }); + + // Written once the comparison target is known, so the linked-project cache + // finalizer below only fires for invocations that got that far — matching + // the family pattern of caching exactly the resolved ref. + let resolvedRef: string | undefined; + + yield* Effect.gen(function* () { + // 1. Load and validate the local config BEFORE any network call or + // target resolution (never writes — this command is read-only by + // contract): a missing file must point at `supabase init` rather than + // the resolver's not-linked error, and a malformed document must not + // burn a branch-resolution round trip. This first load applies no + // `[remotes.*]` overlay — the overlay is keyed by the RESOLVED target + // ref, so a config that declares remotes is reloaded in step 3. + let loaded = yield* loadLocalConfig(undefined); + + // 2. Resolve the comparison target. `--project-ref` accepts a project + // ref, or the name (or UUID) of a branch of the linked project — + // `link`'s settled vocabulary (CLI-2167). A ref-shaped value (exactly 20 + // lowercase letters) is always treated as a project ref; a UUID resolves + // through `GET /v1/branches/{id}` directly, so it works in an unlinked + // directory (the parent ref is passed lazily and only evaluated for a + // branch-NAME lookup). + let ref: string; + let branch: string | undefined; + if (Option.isSome(requested) && !LEGACY_BRANCH_PROJECT_REF_PATTERN.test(requested.value)) { + const target = requested.value; + branch = target; + const resolving = + output.format === "text" ? yield* output.task("Resolving branch...") : undefined; + ref = yield* legacyResolveBranchProjectRef(target, resolver.resolve(Option.none()), { + mapGetError: mapBranchResolveError, + mapFindError: mapBranchResolveError, + }).pipe( + Effect.tapError(() => resolving?.fail() ?? Effect.void), + Effect.catchTag( + "LegacyConfigDiffBranchResolveStatusError", + ( + cause, + ): Effect.Effect< + never, + LegacyConfigDiffBranchNotFoundError | LegacyConfigDiffBranchResolveStatusError + > => + cause.status === 404 + ? Effect.fail( + new LegacyConfigDiffBranchNotFoundError({ + message: `Branch "${legacySanitizeInlineName(target)}" not found. Run \`supabase branches list\` to see available branches.`, + }), + ) + : Effect.fail(cause), + ), + ); + yield* resolving?.clear() ?? Effect.void; + } else { + ref = yield* resolver.resolve(requested); + } + resolvedRef = ref; + + // 3. Apply the matching `[remotes.*]` overlay (ADR 0018) now that the + // target ref is known. Only configs that declare remotes reload — the + // common remotes-free config keeps the step-1 load. A config that both + // declares remotes and triggers a deprecation warning prints that + // warning twice (once per load); the alternative is validating after the + // network call, which is worse. + if (isRecord(loaded.document?.["remotes"])) { + loaded = yield* loadLocalConfig(ref); } const context: LegacyConfigDiffContext = { @@ -147,7 +175,7 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( }; yield* output.raw(legacyConfigDiffComparisonLine(context), "stderr"); - // 2. Fetch the effective remote config (single read-only call). + // 4. Fetch the effective remote config (single read-only call). const fetching = output.format === "text" ? yield* output.task("Fetching remote config...") : undefined; const response = yield* api.v2.getProjectConfig({ ref }).pipe( @@ -163,7 +191,7 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( ); yield* fetching?.clear() ?? Effect.void; - // 3. Project the response through CLI-2230's convergence normalizer (ADR + // 5. Project the response through CLI-2230's convergence normalizer (ADR // 0021). A response the registry cannot narrow (out-of-domain mapped // values) is a response problem, not a transport one: // `ProjectConfigParseError` stays in the typed channel with its own @@ -180,7 +208,7 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( ), ); - // 4. Classify. The loaded pair carries the raw merged document (declared + // 6. Classify. The loaded pair carries the raw merged document (declared // keys) and the env-var origins; `diffProjectConfig` derives the local // convergence projection from it, so the same `ProjectConfigParseError` // boundary applies here. @@ -196,13 +224,13 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( const scope = legacyConfigDiffScope(response.data.attributes); yield* output.raw(legacyConfigDiffScopeLine(scope), "stderr"); - // 5. Emit: `--output-format json|stream-json` structured payload, or text. - // Both output mechanisms are honored, `--output` first (Legacy Shell - // Invariant #6): the machine formats encode the same structured payload - // the `--output-format json` envelope carries; `pretty` (and unset) falls - // through to `--output-format` handling. stdout stays payload-pure in - // every machine mode — diagnostics above went to stderr, and root.ts - // swaps in the quiet-progress layer for `-o` machine formats (CLI-1546). + // 7. Emit. Both output mechanisms are honored, `--output` first (Legacy + // Shell Invariant #6): the machine formats encode the same structured + // payload the `--output-format json` envelope carries; `pretty` (and + // unset) falls through to `--output-format` handling. stdout stays + // payload-pure in every machine mode — diagnostics above went to stderr, + // and root.ts swaps in the quiet-progress layer for `-o` machine formats + // (CLI-1546). const goFmt = Option.getOrUndefined(goOutputFlag); if (goFmt !== undefined && goFmt !== "pretty") { const payload = legacyConfigDiffPayload(changeSet, scope, context); @@ -224,10 +252,21 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( yield* output.raw(legacyRenderConfigDiffText(changeSet)); } - // 6. `--exit-code`: differences flip the exit status after the payload is + // 8. `--exit-code`: differences flip the exit status after the payload is // out, without an error envelope corrupting machine output. if (flags.exitCode && changeSet.counts.total > 0) { yield* processControl.setExitCode(1); } - }).pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush)); + }).pipe( + // Legacy Shell Invariant #1: telemetry flushes on EVERY invocation — + // including load/parse failures and branch-resolution failures — while + // the linked-project cache write needs a resolved ref, so it fires + // exactly when one exists. + Effect.ensuring( + Effect.suspend(() => + resolvedRef === undefined ? Effect.void : linkedProjectCache.cache(resolvedRef), + ), + ), + Effect.ensuring(telemetryState.flush), + ); }); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts index d24ab43cbd..39f75b64b6 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -120,7 +120,7 @@ function v2Response( }; } -/** V1GetABranch body for the `--target ` lookup. */ +/** V1GetABranch body for the branch-name `--project-ref` lookup. */ const BRANCH_BY_NAME = { id: BRANCH_UUID, name: "staging", @@ -134,7 +134,7 @@ const BRANCH_BY_NAME = { with_data: false, }; -/** V1GetABranchConfig body for the `--target ` lookup. */ +/** V1GetABranchConfig body for the UUID `--project-ref` lookup. */ const BRANCH_CONFIG = { ref: BRANCH_REF, postgres_version: "15", @@ -153,6 +153,8 @@ interface SetupOpts { readonly v2?: { status: number; body: unknown } | "fail"; readonly branchByName?: { status: number; body: unknown }; readonly branchByUuid?: { status: number; body: unknown }; + /** `false` simulates a directory with no linked project. */ + readonly linked?: boolean; } function setup(opts: SetupOpts = {}) { @@ -191,7 +193,10 @@ function setup(opts: SetupOpts = {}) { buildLegacyTestRuntime({ out, api, - cliSettings: mockLegacyCliSettings({ workdir: tempRoot.current }), + cliSettings: mockLegacyCliSettings({ + workdir: tempRoot.current, + ...(opts.linked === false ? { projectId: Option.none() } : {}), + }), runtimeInfo: mockRuntimeInfo({ cwd: tempRoot.current }), telemetry: telemetry.layer, linkedProjectCache: linkedProjectCache.layer, @@ -204,7 +209,6 @@ function setup(opts: SetupOpts = {}) { const noFlags = { projectRef: Option.none(), - target: Option.none(), exitCode: false, }; @@ -342,13 +346,13 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("--target resolves a branch name via the parent project", () => { + it.live("a branch-named --project-ref resolves via the parent project", () => { const { layer, out, api } = setup({ toml: 'project_id = "test"\n', v2: { status: 200, body: v2Response({ ref: BRANCH_REF }) }, }); return Effect.gen(function* () { - yield* legacyConfigDiff({ ...noFlags, target: Option.some("staging") }); + yield* legacyConfigDiff({ ...noFlags, projectRef: Option.some("staging") }); expect(out.stderrText).toContain( `Comparing against 'staging' (branch ${BRANCH_REF}) using base config`, ); @@ -360,26 +364,34 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("--target resolves a branch UUID directly", () => { - const { layer, api } = setup({ + it.live("a UUID --project-ref resolves directly, even in an unlinked directory", () => { + // The UUID endpoint (`GET /v1/branches/{id}`) does not use a parent + // project ref, so the lookup must not demand a linked directory — the + // parent is only resolved (lazily) for branch-NAME lookups. + const { layer, api, out } = setup({ toml: 'project_id = "test"\n', v2: { status: 200, body: v2Response({ ref: BRANCH_REF }) }, + linked: false, }); return Effect.gen(function* () { - yield* legacyConfigDiff({ ...noFlags, target: Option.some(BRANCH_UUID) }); + yield* legacyConfigDiff({ ...noFlags, projectRef: Option.some(BRANCH_UUID) }); const urls = api.requests.map((request) => request.url); expect(urls.some((url) => url.includes(`/v1/branches/${BRANCH_UUID}`))).toBe(true); expect(urls.some((url) => url.includes(`/v2/projects/${BRANCH_REF}/config`))).toBe(true); + // A UUID is an identifier, not a display name — never quoted as one. + expect(out.stderrText).toContain( + `Comparing against branch ${BRANCH_UUID} (project ref ${BRANCH_REF})`, + ); }).pipe(Effect.provide(layer)); }); - it.live("--target accepts a raw project ref without touching the branches API", () => { + it.live("a ref-shaped --project-ref never touches the branches API", () => { const { layer, api } = setup({ toml: 'project_id = "test"\n', v2: { status: 200, body: v2Response({ ref: BRANCH_REF }) }, }); return Effect.gen(function* () { - yield* legacyConfigDiff({ ...noFlags, target: Option.some(BRANCH_REF) }); + yield* legacyConfigDiff({ ...noFlags, projectRef: Option.some(BRANCH_REF) }); const urls = api.requests.map((request) => request.url); expect(urls.some((url) => url.includes("/branches/"))).toBe(false); expect(urls.some((url) => url.includes(`/v2/projects/${BRANCH_REF}/config`))).toBe(true); @@ -392,7 +404,7 @@ describe("legacy config diff integration", () => { branchByName: { status: 404, body: { message: "not found" } }, }); return Effect.gen(function* () { - const exit = yield* legacyConfigDiff({ ...noFlags, target: Option.some("ghost") }).pipe( + const exit = yield* legacyConfigDiff({ ...noFlags, projectRef: Option.some("ghost") }).pipe( Effect.exit, ); expect(Exit.isFailure(exit)).toBe(true); @@ -409,7 +421,7 @@ describe("legacy config diff integration", () => { branchByName: { status: 500, body: { message: "boom" } }, }); return Effect.gen(function* () { - const exit = yield* legacyConfigDiff({ ...noFlags, target: Option.some("staging") }).pipe( + const exit = yield* legacyConfigDiff({ ...noFlags, projectRef: Option.some("staging") }).pipe( Effect.exit, ); expect(Exit.isFailure(exit)).toBe(true); @@ -417,20 +429,6 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("--target and --project-ref together are rejected", () => { - const { layer, api } = setup({ toml: 'project_id = "test"\n' }); - return Effect.gen(function* () { - const exit = yield* legacyConfigDiff({ - exitCode: false, - target: Option.some("staging"), - projectRef: Option.some(LEGACY_VALID_REF), - }).pipe(Effect.exit); - expect(Exit.isFailure(exit)).toBe(true); - expect(JSON.stringify(exit)).toContain("LegacyConfigDiffFlagConflictError"); - expect(api.requests).toHaveLength(0); - }).pipe(Effect.provide(layer)); - }); - it.live("a missing config file points at supabase init", () => { const { layer } = setup(); return Effect.gen(function* () { diff --git a/apps/cli/src/legacy/commands/config/push/push.command.ts b/apps/cli/src/legacy/commands/config/push/push.command.ts index 05854892e9..22fdc89779 100644 --- a/apps/cli/src/legacy/commands/config/push/push.command.ts +++ b/apps/cli/src/legacy/commands/config/push/push.command.ts @@ -32,7 +32,11 @@ export const legacyConfigPushCommand = Command.make("push", config).pipe( ]), Command.withHandler((flags) => legacyConfigPush(flags).pipe( - withLegacyCommandInstrumentation({ flags }), + // Unlike `config diff`'s branch-accepting flag, push's `--project-ref` + // is ref-only, so its value is always safe to log verbatim — keeping + // the config family's telemetry consistent (documented safe list in + // apps/cli/CLAUDE.md). + withLegacyCommandInstrumentation({ flags, safeFlags: ["project-ref"] }), withJsonErrorHandling, ), ), diff --git a/apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts b/apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts index 01ad03e877..5e6a9d2274 100644 --- a/apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts +++ b/apps/cli/src/legacy/shared/legacy-branch-ref.resolver.ts @@ -39,13 +39,15 @@ export interface LegacyBranchRefResolveMappers { * `V1GetABranch` (`GET /v1/projects/{ref}/branches/{name}`) and return * `JSON200.project_ref`. * - * The persistent `--project-ref` is required for path 3 and is passed in by - * the caller (which has already run `LegacyProjectRefResolver` so the linked - * project cache write does not re-fire here). + * The parent project ref is required only for path 3, so it may be passed + * lazily as an Effect — it is evaluated exactly then, never for a ref-shaped + * or UUID input. That keeps `--project-ref ` working in an unlinked + * directory: the UUID endpoint does not use a parent ref, so requiring one + * up front would fail invocations the API itself can serve. */ -export function legacyResolveBranchProjectRef( +export function legacyResolveBranchProjectRef( input: string, - projectRef: string, + projectRef: string | Effect.Effect, mappers: LegacyBranchRefResolveMappers, ) { return Effect.gen(function* () { @@ -62,8 +64,9 @@ export function legacyResolveBranchProjectRef( return detail.ref; } + const parentRef = typeof projectRef === "string" ? projectRef : yield* projectRef; const branch = yield* api.v1 - .getABranch({ ref: projectRef, name: input }) + .getABranch({ ref: parentRef, name: input }) .pipe(Effect.catch(mappers.mapFindError)); return branch.project_ref; }); From 27b62ef0bb5f0c3c7dcddc49f2bb7aa85c627f7e Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Mon, 31 Aug 2026 15:01:08 -0500 Subject: [PATCH 10/12] fix(cli): harden config diff output and machine payload contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The remaining handler/formatter fixes from the PR #6295 review: - --workdir is honored: config loading resolves against cliSettings.workdir (the same root the project-ref resolver and linked-project cache use) instead of process.cwd(), so `config diff --workdir ../other` compares ../other's config.toml against ../other's linked project. config push shared the bug and gets the same fix. - --exit-code drift exits 2, with 1 reserved for errors (terraform plan -detailed-exitcode's convention) — `config diff --exit-code || alert` no longer fires on an expired token. - Text output is injection-safe: every non-constant string (path segments — [remotes.*] names and sms.test_otp keys are unconstrained TOML keys —, env-var names, branch/remote names, the project ref) goes through legacySanitizeInlineName, so a hostile name can no longer emit raw ANSI or forge a "No config differences found." line. Pinned by an integration test with an ESC-carrying remotes name. - The machine payload is contract-clean: `schema_version` is now an integer version of the payload shape itself (1) with the user's `$schema` URL moved to `config_schema`; `scope` is `{present, missing}` with the block set owned by @supabase/config (exported projectConfigApiBlockKeys, derived from its response mirror) instead of hand-copied; an EMPTY block record counts as not-returned, so a permission-truncated `auth: {}` can't be claimed compared while 38 auth keys print local-only; and the json/stream-json message carries the masked/unmanaged caveats so echoing it never reports "in sync" on a project whose SMTP password may have drifted. - A 404 from /v2/projects/{ref}/config classifies as invalid input (the ref names a user-selected resource), matching the branch-resolve error and the ref-addressed push.errors.ts convention. - One spelling per concept: labels and summary both say remote-only / local-only (JSON keeps snake_case remote_only), and counts pluralize properly now that they're known at render time. Addresses PR #6295 review (Coly010: workdir, exit-code conflation, ANSI injection, schema_version, scope machinery, JSON message caveat, naming threads). Co-Authored-By: Claude Fable 5 --- .../commands/config/diff/SIDE_EFFECTS.md | 38 +++-- .../commands/config/diff/diff.command.ts | 6 +- .../commands/config/diff/diff.errors.ts | 6 +- .../commands/config/diff/diff.format.ts | 148 +++++++++++++----- .../config/diff/diff.format.unit.test.ts | 27 +++- .../commands/config/diff/diff.handler.ts | 31 ++-- .../config/diff/diff.integration.test.ts | 81 ++++++++-- .../commands/config/push/push.handler.ts | 12 +- .../config/src/entrypoint-purity.unit.test.ts | 2 + packages/config/src/index.ts | 5 +- .../src/project-config/api-attributes.ts | 12 ++ 11 files changed, 282 insertions(+), 86 deletions(-) diff --git a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md index 5fa35537c5..e83bd0f40e 100644 --- a/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/config/diff/SIDE_EFFECTS.md @@ -48,10 +48,15 @@ All Bearer-authenticated, all read-only. ## Exit Codes +Drift has its own exit code (`2`), distinct from every failure (`1`), so +`config diff --exit-code` scripts can tell "config drifted" from "token +expired" without parsing output (`terraform plan -detailed-exitcode`'s +convention; `1` stays the CLI-wide failure code). + | Code | Condition | | ---- | ------------------------------------------------------------------------------ | | `0` | success — including when differences are found, unless `--exit-code` is passed | -| `1` | `--exit-code` passed and at least one difference found | +| `2` | `--exit-code` passed and at least one difference found | | `1` | missing or malformed `supabase/config.toml` | | `1` | unknown branch (branch-name `--project-ref` 404) | | `1` | two `[remotes.*]` blocks declare the same `project_id` as the target ref | @@ -66,20 +71,29 @@ blocks are called out). The payload is on **stdout**. ### `--output-format text` -One block per difference (` [update|remote only|local only]` with -`local:`/`remote:` lines; unset renders `(unset)` / `(not returned)`, -env-resolved values append `(from env VAR)`), then a summary count line — -`No config differences found.` when clean — and a -`Note: N credential value(s) not compared (masked by the API): …` line when -the file sets masked secrets. +One block per difference (` [update|remote-only|local-only]` with +`local:`/`remote:` lines; unset renders `(unset)` / `(not returned)`, an +undeclared path with a schema default renders ` (schema default — not +declared in config.toml)`, env-resolved values append `(from env VAR, …)`), +then a summary count line — `No config differences found.` when clean — +followed by a `Note: … (masked by the API): …` line when the file sets masked +secrets and a `Note: … cannot be pushed and … not compared: …` line for +declared properties push cannot communicate. Every non-constant string +(path segments, env-var names, remotes/branch names) is sanitized against +control characters before rendering. ### `--output-format json` / `stream-json` -`output.success(message, payload)` with the payload containing -`schema_version`, `target` (`project_ref`, optional `branch`, `local_scope`), -`scope`, `changes[]` (`path`, `class`, `local`, `remote`, optional -`env_variable`; unset sides are `null`), `masked[]`, and `counts` -(per class + `total`). +`output.success(message, payload)` — the message carries the masked/unmanaged +caveats too, so echoing it never claims "in sync" while masked values may have +drifted. The payload contains `schema_version` (integer version of THIS +payload contract, currently `1`), `config_schema` (the file's `$schema` URL), +`target` (`project_ref`, optional `branch`, `local_scope`), `scope` +(`{present, missing}` block lists — the block set is owned by +`@supabase/config`), `changes[]` (`path` as a SEGMENT ARRAY — a record key may +contain a `.` — plus `class`, `declared`, `local`, `remote`, optional +`env_variables[]`; unset sides are `null`), `masked[]` and `unmanaged[]` +(segment-array paths), and `counts` (per class + `total`). ### `-o/--output` (legacy machine formats) diff --git a/apps/cli/src/legacy/commands/config/diff/diff.command.ts b/apps/cli/src/legacy/commands/config/diff/diff.command.ts index f1d6fae6bb..a190abd21e 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.command.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.command.ts @@ -18,7 +18,9 @@ const config = { Flag.optional, ), exitCode: Flag.boolean("exit-code").pipe( - Flag.withDescription("Exit with status 1 when any difference is found."), + Flag.withDescription( + "Exit with status 2 when any difference is found (errors keep exiting 1).", + ), // Without an explicit default a boolean flag is REQUIRED by the parser, // making plain `supabase config diff` fail with `required flag(s) // "exit-code" not set` — pinned by diff.e2e.test.ts, since integration @@ -41,7 +43,7 @@ export const legacyConfigDiffCommand = Command.make("diff", config).pipe( }, { command: "supabase config diff --project-ref staging --exit-code", - description: "Diff against the 'staging' branch, exiting 1 on drift", + description: "Diff against the 'staging' branch, exiting 2 on drift", }, ]), Command.withHandler((flags) => diff --git a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts index 29e014edf8..07b22b873a 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.errors.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.errors.ts @@ -67,6 +67,10 @@ export class LegacyConfigDiffReadStatusError extends Data.TaggedError( "LegacyConfigDiffReadStatusError", ) { get [ErrorActionabilityId](): CliErrorActionabilityDeclaration { - return statusCodeActionability(this.status); + // `/v2/projects/{ref}/config` names a user-selected resource, so a 404 + // means "wrong project ref" — user-actionable, not an external-service + // problem (same rule as the branch-resolve error above and the + // ref-addressed push.errors.ts status errors). + return statusCodeActionability(this.status, { notFoundIsInvalidInput: true }); } } diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.ts index 2d29371f4d..d335aff3cd 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.ts @@ -1,19 +1,49 @@ -import type { ConfigChange, ConfigChangeSet } from "@supabase/config"; +import { + type ConfigChange, + type ConfigChangeSet, + projectConfigApiBlockKeys, +} from "@supabase/config"; import { LEGACY_BRANCH_UUID_PATTERN } from "../../../shared/legacy-branch-ref.resolver.ts"; +import { legacySanitizeInlineName } from "../../../shared/legacy-http-errors.ts"; /** * Pure formatters, payload builders, and input adapters for `config diff` — * no Effect, no services, unit-testable in isolation. + * + * Every non-constant string interpolated into TEXT output goes through + * `legacySanitizeInlineName`: path segments (`[remotes.*]` names, + * `sms.test_otp` record keys) and env-var names are unconstrained + * user/API-controlled strings, so a hostile value could otherwise emit raw + * ANSI or forge output lines (e.g. a name ending `\nNo config differences + * found.`). JSON output needs no sanitizing — `JSON.stringify` escapes + * control characters. */ -/** The per-service blocks of the v2 project-config resource. */ -const REMOTE_CONFIG_BLOCKS = ["api", "auth", "database", "pooler", "realtime", "storage"] as const; - -export type LegacyConfigDiffScope = ReadonlyArray<(typeof REMOTE_CONFIG_BLOCKS)[number]>; +/** + * The per-service blocks of the v2 project-config resource — owned by + * `@supabase/config` (derived from its response mirror), never hand-copied + * here, so a block the package learns is never reported "not returned" + * forever. + */ +const REMOTE_CONFIG_BLOCKS: ReadonlyArray = projectConfigApiBlockKeys; + +export interface LegacyConfigDiffScope { + /** Blocks the response's `data.attributes` carried with at least one key. */ + readonly present: ReadonlyArray; + /** Blocks absent from the response — or present but EMPTY, which is how a + * permission-truncated response most plausibly reports a block it could + * not read; claiming an empty block was "compared" would be false. */ + readonly missing: ReadonlyArray; +} -function isRemoteBlockRecord(value: unknown): value is Readonly> { - return typeof value === "object" && value !== null && !Array.isArray(value); +function isPopulatedBlockRecord(value: unknown): value is Readonly> { + return ( + typeof value === "object" && + value !== null && + !Array.isArray(value) && + Object.keys(value).length > 0 + ); } /** @@ -24,24 +54,36 @@ function isRemoteBlockRecord(value: unknown): value is Readonly>, ): LegacyConfigDiffScope { - return REMOTE_CONFIG_BLOCKS.filter((block) => isRemoteBlockRecord(attributes[block])); + const present = REMOTE_CONFIG_BLOCKS.filter((block) => isPopulatedBlockRecord(attributes[block])); + return { + present, + missing: REMOTE_CONFIG_BLOCKS.filter((block) => !present.includes(block)), + }; } export interface LegacyConfigDiffContext { /** The resolved comparison target's project ref. */ readonly projectRef: string; - /** The `--target` value, when a branch was named. */ + /** The branch name or UUID `--project-ref` carried, when it named one. */ readonly branch: string | undefined; /** Matched `[remotes.]` block, when the local operand was merged. */ readonly appliedRemote: string | undefined; /** The local file's `$schema` ref (or the current schema URL). */ - readonly schemaVersion: string; + readonly configSchema: string; } +/** + * Version of the machine payload's own shape — bump when the payload + * contract changes incompatibly. Distinct from the config document's + * `$schema` URL (`config_schema` in the payload), which is user-controlled + * and per-repo. + */ +export const LEGACY_CONFIG_DIFF_PAYLOAD_VERSION = 1; + const CLASS_LABELS: Record = { update: "update", - remote_only: "remote only", - local_only: "local only", + remote_only: "remote-only", + local_only: "local-only", }; function renderValue(value: unknown, absent: string): string { @@ -56,51 +98,79 @@ function renderValue(value: unknown, absent: string): string { /** Display-only join — `ConfigChange.path` is segment-array everywhere else. */ function renderPath(path: ReadonlyArray): string { - return path.join("."); + return legacySanitizeInlineName(path.join(".")); +} + +function plural(count: number, singular: string, pluralForm: string): string { + return `${count} ${count === 1 ? singular : pluralForm}`; } function localScope(context: LegacyConfigDiffContext): string { - return context.appliedRemote === undefined ? "base config" : `[remotes.${context.appliedRemote}]`; + return context.appliedRemote === undefined + ? "base config" + : `[remotes.${legacySanitizeInlineName(context.appliedRemote)}]`; } /** The target-echo line, printed to stderr before any comparison output. */ export function legacyConfigDiffComparisonLine(context: LegacyConfigDiffContext): string { // A UUID target is an identifier, not a display name — quoting it as // `'1111…'` would imply the branch is literally named that. + const projectRef = legacySanitizeInlineName(context.projectRef); const target = context.branch === undefined - ? `project ${context.projectRef}` + ? `project ${projectRef}` : LEGACY_BRANCH_UUID_PATTERN.test(context.branch) - ? `branch ${context.branch} (project ref ${context.projectRef})` - : `'${context.branch}' (branch ${context.projectRef})`; + ? `branch ${legacySanitizeInlineName(context.branch)} (project ref ${projectRef})` + : `'${legacySanitizeInlineName(context.branch)}' (branch ${projectRef})`; return `Comparing against ${target} using ${localScope(context)}\n`; } /** The scope-echo line, printed to stderr once the response arrived. */ export function legacyConfigDiffScopeLine(scope: LegacyConfigDiffScope): string { - const present = scope.length === 0 ? "(none)" : scope.join(", "); - const missing = REMOTE_CONFIG_BLOCKS.filter((block) => !scope.includes(block)); - const suffix = missing.length === 0 ? "" : ` (not returned: ${missing.join(", ")})`; + const present = scope.present.length === 0 ? "(none)" : scope.present.join(", "); + const suffix = scope.missing.length === 0 ? "" : ` (not returned: ${scope.missing.join(", ")})`; return `Comparison scope: ${present}${suffix}\n`; } -function maskedNote(masked: ReadonlyArray>): string { - return `Note: ${masked.length} credential value(s) not compared (masked by the API): ${masked.map(renderPath).join(", ")}\n`; +function maskedCaveat(masked: ReadonlyArray>): string { + return `${plural(masked.length, "credential value", "credential values")} not compared (masked by the API): ${masked.map(renderPath).join(", ")}`; } -function unmanagedNote(unmanaged: ReadonlyArray>): string { +function unmanagedCaveat(unmanaged: ReadonlyArray>): string { const phrase = unmanaged.length === 1 ? "1 declared property cannot be pushed and was not compared" : `${unmanaged.length} declared properties cannot be pushed and were not compared`; - return `Note: ${phrase}: ${unmanaged.map(renderPath).join(", ")}\n`; + return `${phrase}: ${unmanaged.map(renderPath).join(", ")}`; +} + +/** + * One-line summary including the masked/unmanaged caveats — the text-mode + * count line's caveats also travel with the machine-mode `message`, so an + * agent echoing `.message` never reports "in sync" on a project whose + * masked SMTP password (or unpushable declared value) may have drifted. + */ +export function legacyConfigDiffSummaryMessage(changeSet: ConfigChangeSet): string { + const total = changeSet.counts.total; + const base = + total === 0 + ? "No config differences found." + : `${plural(total, "config difference", "config differences")} found.`; + const parts = [base]; + if (changeSet.masked.length > 0) { + parts.push(`${maskedCaveat(changeSet.masked)}.`); + } + if (changeSet.unmanaged.length > 0) { + parts.push(`${unmanagedCaveat(changeSet.unmanaged)}.`); + } + return parts.join(" "); } function renderLocal(change: ConfigChange): string { const value = renderValue(change.local, "(unset)"); // A populated local value on an undeclared path is the schema default the // projection materialized — the value a `config push` would write. Say so, - // or "[remote only]" reads as "this key exists only remotely", which is + // or "[remote-only]" reads as "this key exists only remotely", which is // false for anything with a schema default (and the user will grep their // file for a value that isn't there). return change.local !== undefined && !change.declared @@ -114,7 +184,9 @@ export function legacyRenderConfigDiffText(changeSet: ConfigChangeSet): string { for (const change of changeSet.changes) { lines.push(`${renderPath(change.path)} [${CLASS_LABELS[change.class]}]`); const env = - change.envVariables === undefined ? "" : ` (from env ${change.envVariables.join(", ")})`; + change.envVariables === undefined + ? "" + : ` (from env ${legacySanitizeInlineName(change.envVariables.join(", "))})`; lines.push(` local: ${renderLocal(change)}${env}`); lines.push(` remote: ${renderValue(change.remote, "(not returned)")}`); lines.push(""); @@ -125,24 +197,24 @@ export function legacyRenderConfigDiffText(changeSet: ConfigChangeSet): string { lines.push("No config differences found."); } else { lines.push( - `${total} difference(s) found (${update} update, ${remote_only} remote-only, ${local_only} local-only).`, + `${plural(total, "difference", "differences")} found (${update} update, ${remote_only} remote-only, ${local_only} local-only).`, ); } if (changeSet.masked.length > 0) { - lines.push(maskedNote(changeSet.masked).trimEnd()); + lines.push(`Note: ${maskedCaveat(changeSet.masked)}`); } if (changeSet.unmanaged.length > 0) { - lines.push(unmanagedNote(changeSet.unmanaged).trimEnd()); + lines.push(`Note: ${unmanagedCaveat(changeSet.unmanaged)}`); } return `${lines.join("\n")}\n`; } /** - * The structured result for `--output-format json|stream-json`. Unset sides - * are explicit `null`s, distinguishable from empty values. Paths are segment - * arrays — a record key (an `sms.test_otp` phone number, a `[remotes.*]` - * name) may itself contain a `.`, so consumers must never split a joined - * string. + * The structured result for `--output-format json|stream-json` and the `-o` + * machine formats. Unset sides are explicit `null`s, distinguishable from + * empty values. Paths are segment arrays — a record key (an `sms.test_otp` + * phone number, a `[remotes.*]` name) may itself contain a `.`, so consumers + * must never split a joined string. */ export function legacyConfigDiffPayload( changeSet: ConfigChangeSet, @@ -154,14 +226,18 @@ export function legacyConfigDiffPayload( }); return { - schema_version: context.schemaVersion, + // The payload contract's own version — what a forward-compat consumer + // gates on. The user's `$schema` document reference is `config_schema`: + // user-controlled and per-repo, never a contract signal. + schema_version: LEGACY_CONFIG_DIFF_PAYLOAD_VERSION, + config_schema: context.configSchema, target: { project_ref: context.projectRef, ...valueEntry("branch", context.branch), local_scope: context.appliedRemote === undefined ? "base" : `remotes.${context.appliedRemote}`, }, - scope, + scope: { present: scope.present, missing: scope.missing }, changes: changeSet.changes.map((change) => ({ path: change.path, class: change.class, diff --git a/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts index be869bd580..49fd5c66ec 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.format.unit.test.ts @@ -3,7 +3,11 @@ import { describe, expect, test } from "vitest"; import { legacyConfigDiffScope, legacyConfigDiffScopeLine } from "./diff.format.ts"; describe("legacyConfigDiffScope", () => { - test("lists record blocks the response carried, dropping non-records", () => { + test("lists record blocks the response carried, dropping non-records and empty records", () => { + // An EMPTY block record is how a permission-truncated response most + // plausibly reports a block it could not read — claiming it was + // "compared" while all its keys render (not returned) would be false, + // and with --exit-code that is a permanently red CI no file edit fixes. expect( legacyConfigDiffScope({ api: { max_rows: 5 }, @@ -12,19 +16,30 @@ describe("legacyConfigDiffScope", () => { realtime: [1], storage: "nope", }), - ).toEqual(["api", "auth"]); + ).toEqual({ + present: ["api"], + missing: ["auth", "database", "pooler", "realtime", "storage"], + }); }); }); describe("legacyConfigDiffScopeLine", () => { test("calls out blocks the response did not return", () => { - expect(legacyConfigDiffScopeLine(["api", "auth"])).toBe( - "Comparison scope: api, auth (not returned: database, pooler, realtime, storage)\n", - ); + expect( + legacyConfigDiffScopeLine({ + present: ["api", "auth"], + missing: ["database", "pooler", "realtime", "storage"], + }), + ).toBe("Comparison scope: api, auth (not returned: database, pooler, realtime, storage)\n"); }); test("an empty response scope renders (none)", () => { - expect(legacyConfigDiffScopeLine([])).toBe( + expect( + legacyConfigDiffScopeLine({ + present: [], + missing: ["api", "auth", "database", "pooler", "realtime", "storage"], + }), + ).toBe( "Comparison scope: (none) (not returned: api, auth, database, pooler, realtime, storage)\n", ); }); diff --git a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts index 83a208e85b..62c313443e 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.handler.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.handler.ts @@ -8,13 +8,13 @@ import { loadCliConfig } from "@supabase/config/effect"; import { Effect, Option } from "effect"; import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts"; +import { LegacyCliSettings } from "../../../config/legacy-cli-settings.service.ts"; import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; import { LegacyOutputFlag } from "../../../../shared/legacy/global-flags.ts"; import { Output } from "../../../../shared/output/output.service.ts"; import { ProcessControl } from "../../../../shared/runtime/process-control.service.ts"; -import { RuntimeInfo } from "../../../../shared/runtime/runtime-info.service.ts"; import { LEGACY_BRANCH_PROJECT_REF_PATTERN, legacyResolveBranchProjectRef, @@ -28,6 +28,7 @@ import { legacyConfigDiffPayload, legacyConfigDiffScope, legacyConfigDiffScopeLine, + legacyConfigDiffSummaryMessage, legacyRenderConfigDiffText, type LegacyConfigDiffContext, } from "./diff.format.ts"; @@ -68,15 +69,19 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( const resolver = yield* LegacyProjectRefResolver; const linkedProjectCache = yield* LegacyLinkedProjectCache; const telemetryState = yield* LegacyTelemetryState; - const runtimeInfo = yield* RuntimeInfo; + const cliSettings = yield* LegacyCliSettings; const processControl = yield* ProcessControl; const goOutputFlag = yield* LegacyOutputFlag; // An empty `--project-ref` value is absent, mirroring the resolver's own rule. const requested = Option.filter(flags.projectRef, (value) => value.length > 0); + // Resolved against `cliSettings.workdir` — the same root the project-ref + // resolver and the linked-project cache use — so `--workdir ../other` + // compares `../other`'s config.toml against `../other`'s linked project, + // never the invoking directory's file against another root's project. const loadLocalConfig = (projectRef: string | undefined) => - loadCliConfig(runtimeInfo.cwd, { projectRef, goViperCompat: true }).pipe( + loadCliConfig(cliSettings.workdir, { projectRef, goViperCompat: true }).pipe( Effect.catchTag( "CliConfigParseError", (cause) => @@ -171,7 +176,7 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( projectRef: ref, branch, appliedRemote: loaded.appliedRemote, - schemaVersion: loaded.schemaRef ?? CLI_CONFIG_SCHEMA_URL, + configSchema: loaded.schemaRef ?? CLI_CONFIG_SCHEMA_URL, }; yield* output.raw(legacyConfigDiffComparisonLine(context), "stderr"); @@ -244,18 +249,22 @@ export const legacyConfigDiff = Effect.fn("legacy.config.diff")(function* ( yield* output.raw(encodeEnv(payload) + "\n"); } } else if (output.format !== "text") { - const total = changeSet.counts.total; - const message = - total === 0 ? "No config differences found." : `${total} config difference(s) found.`; - yield* output.success(message, legacyConfigDiffPayload(changeSet, scope, context)); + yield* output.success( + legacyConfigDiffSummaryMessage(changeSet), + legacyConfigDiffPayload(changeSet, scope, context), + ); } else { yield* output.raw(legacyRenderConfigDiffText(changeSet)); } - // 8. `--exit-code`: differences flip the exit status after the payload is - // out, without an error envelope corrupting machine output. + // 8. `--exit-code`: differences flip the exit status to 2 after the + // payload is out, without an error envelope corrupting machine output. + // Drift gets its OWN code — every failure exits 1, and a script's + // `config diff --exit-code || alert` must not fire on an expired token + // (`terraform plan -detailed-exitcode`'s 0/1/2 convention, with 1 kept + // for errors to match the rest of the CLI). if (flags.exitCode && changeSet.counts.total > 0) { - yield* processControl.setExitCode(1); + yield* processControl.setExitCode(2); } }).pipe( // Legacy Shell Invariant #1: telemetry flushes on EVERY invocation — diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts index 39f75b64b6..cdabeeb836 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -155,6 +155,8 @@ interface SetupOpts { readonly branchByUuid?: { status: number; body: unknown }; /** `false` simulates a directory with no linked project. */ readonly linked?: boolean; + /** Overrides the process cwd (defaults to the temp workdir). */ + readonly cwd?: string; } function setup(opts: SetupOpts = {}) { @@ -197,7 +199,7 @@ function setup(opts: SetupOpts = {}) { workdir: tempRoot.current, ...(opts.linked === false ? { projectId: Option.none() } : {}), }), - runtimeInfo: mockRuntimeInfo({ cwd: tempRoot.current }), + runtimeInfo: mockRuntimeInfo({ cwd: opts.cwd ?? tempRoot.current }), telemetry: telemetry.layer, linkedProjectCache: linkedProjectCache.layer, processControl, @@ -232,14 +234,16 @@ describe("legacy config diff integration", () => { expect(out.stderrText).toContain( `Comparing against project ${LEGACY_VALID_REF} using base config`, ); + // The fixture's `auth: {}` is an EMPTY block — reported not-returned + // rather than falsely claimed compared. expect(out.stderrText).toContain( - "Comparison scope: api, auth, database, pooler, realtime, storage", + "Comparison scope: api, database, pooler, realtime, storage (not returned: auth)", ); expect(out.stdoutText).toContain("api.max_rows [update]"); expect(out.stdoutText).toContain("local: 500"); expect(out.stdoutText).toContain("remote: 1000"); expect(out.stdoutText).toContain( - "1 difference(s) found (1 update, 0 remote-only, 0 local-only).", + "1 difference found (1 update, 0 remote-only, 0 local-only).", ); // Differences without --exit-code leave the exit status alone. expect(processControl.exitCode).toBeUndefined(); @@ -257,13 +261,16 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("--exit-code sets exit 1 when differences are found", () => { + it.live("--exit-code sets exit 2 when differences are found", () => { + // Drift gets its own exit code (2) so scripts can tell it from failure + // (1) — `config diff --exit-code || alert` must not fire on an expired + // token. const { layer, processControl } = setup({ toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', }); return Effect.gen(function* () { yield* legacyConfigDiff({ ...noFlags, exitCode: true }); - expect(processControl.exitCode).toBe(1); + expect(processControl.exitCode).toBe(2); }).pipe(Effect.provide(layer)); }); @@ -273,7 +280,7 @@ describe("legacy config diff integration", () => { }); return Effect.gen(function* () { yield* legacyConfigDiff(noFlags); - expect(out.stdoutText).toContain("auth.site_url [local only]"); + expect(out.stdoutText).toContain("auth.site_url [local-only]"); expect(out.stdoutText).toContain('local: "https://local.example.com"'); expect(out.stdoutText).toContain("remote: (not returned)"); }).pipe(Effect.provide(layer)); @@ -316,7 +323,7 @@ describe("legacy config diff integration", () => { yield* legacyConfigDiff({ ...noFlags, exitCode: true }); expect(out.stdoutText).toContain("No config differences found."); expect(out.stdoutText).toContain( - "Note: 1 credential value(s) not compared (masked by the API): auth.external.github.secret", + "Note: 1 credential value not compared (masked by the API): auth.external.github.secret", ); expect(processControl.exitCode).toBeUndefined(); }).pipe(Effect.provide(layer)); @@ -533,20 +540,26 @@ describe("legacy config diff integration", () => { yield* legacyConfigDiff(noFlags); const success = out.messages.find((message) => message.type === "success"); expect(success).toBeDefined(); - expect(success?.message).toContain("1 config difference(s) found."); + expect(success?.message).toContain("1 config difference found."); const data = success?.data as Record; expect(data["target"]).toMatchObject({ project_ref: LEGACY_VALID_REF, local_scope: "base", }); - expect(data["scope"]).toEqual(["api", "auth", "database", "pooler", "realtime", "storage"]); + // `schema_version` is the PAYLOAD contract's version; the user's + // `$schema` document reference travels separately as `config_schema`. + expect(data["schema_version"]).toBe(1); + expect(typeof data["config_schema"]).toBe("string"); + expect(data["scope"]).toEqual({ + present: ["api", "database", "pooler", "realtime", "storage"], + missing: ["auth"], + }); expect(data["changes"]).toEqual([ { path: ["api", "max_rows"], class: "update", declared: true, local: 500, remote: 1000 }, ]); expect(data["counts"]).toEqual({ update: 1, remote_only: 0, local_only: 0, total: 1 }); expect(data["masked"]).toEqual([]); expect(data["unmanaged"]).toEqual([]); - expect(typeof data["schema_version"]).toBe("string"); }).pipe(Effect.provide(layer)); }); @@ -586,7 +599,7 @@ describe("legacy config diff integration", () => { return Effect.gen(function* () { yield* legacyConfigDiff(noFlags); expect(out.stdoutText).toContain("api.max_rows [update]"); - expect(out.stdoutText).toContain("1 difference(s) found"); + expect(out.stdoutText).toContain("1 difference found"); }).pipe(Effect.provide(layer)); }); @@ -672,7 +685,7 @@ describe("legacy config diff integration", () => { }); return Effect.gen(function* () { yield* legacyConfigDiff(noFlags); - expect(out.stdoutText).toContain("db.settings.work_mem [remote only]"); + expect(out.stdoutText).toContain("db.settings.work_mem [remote-only]"); expect(out.stdoutText).toContain("local: (unset)"); expect(out.stdoutText).toContain('remote: "64MB"'); }).pipe(Effect.provide(layer)); @@ -697,7 +710,7 @@ describe("legacy config diff integration", () => { }); return Effect.gen(function* () { yield* legacyConfigDiff(noFlags); - expect(out.stdoutText).toContain("api.max_rows [remote only]"); + expect(out.stdoutText).toContain("api.max_rows [remote-only]"); expect(out.stdoutText).toContain( "local: 1000 (schema default — not declared in config.toml)", ); @@ -705,6 +718,48 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); + it.live("the config file is read relative to --workdir, not the invoking directory", () => { + // `--workdir ../other` must compare `../other`'s config.toml against + // `../other`'s linked project — reading the invoking directory's file + // would silently diff the WRONG config (the resolver and linked-project + // cache already use the workdir). The ambient cwd here points somewhere + // with no supabase/ directory at all; only cliSettings.workdir knows + // where the project lives. + const elsewhere = join(tempRoot.current, "unrelated-cwd"); + mkdirSync(elsewhere, { recursive: true }); + const { layer, out } = setup({ + toml: 'project_id = "test"\n[api]\nmax_rows = 500\n', + cwd: elsewhere, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stdoutText).toContain("api.max_rows [update]"); + }).pipe(Effect.provide(layer)); + }); + + it.live("hostile names cannot inject ANSI or forge output lines in text mode", () => { + // Path segments are attacker-influenced ([remotes.*] names and + // sms.test_otp keys are unconstrained TOML keys) — a name carrying an + // escape byte or newline must not reach the terminal raw, where it could + // recolor output or append a fake "No config differences found." line. + const { layer, out } = setup({ + toml: [ + 'project_id = "test"', + '[remotes."evil\\u001B[31mred"]', + `project_id = "${LEGACY_VALID_REF}"`, + '[remotes."evil\\u001B[31mred".api]', + "max_rows = 500", + "", + ].join("\n"), + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stderrText).toContain("[remotes.evil[31mred]"); + expect(out.stderrText).not.toContain("\u001b"); + expect(out.stdoutText).not.toContain("\u001b"); + }).pipe(Effect.provide(layer)); + }); + it.live("a declared path push cannot communicate surfaces in the unmanaged note", () => { // auth.oauth_server is dropped from the local projection entirely (push // has no oauth_server handling), so a declared `enabled = true` diff --git a/apps/cli/src/legacy/commands/config/push/push.handler.ts b/apps/cli/src/legacy/commands/config/push/push.handler.ts index ba0004663c..d038ca645a 100644 --- a/apps/cli/src/legacy/commands/config/push/push.handler.ts +++ b/apps/cli/src/legacy/commands/config/push/push.handler.ts @@ -3,12 +3,12 @@ import { findCliProjectRoot, loadCliConfig } from "@supabase/config/effect"; import { Effect, FileSystem, Path } from "effect"; import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts"; +import { LegacyCliSettings } from "../../../config/legacy-cli-settings.service.ts"; import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; import { legacyResolveYesWithProjectEnv } from "../../../../shared/legacy/global-flags.ts"; import { Output } from "../../../../shared/output/output.service.ts"; -import { RuntimeInfo } from "../../../../shared/runtime/runtime-info.service.ts"; import { legacyAssertDecryptableSecrets, legacyLoadProjectEnv, @@ -88,9 +88,9 @@ export const legacyConfigPush = Effect.fn("legacy.config.push")(function* ( const output = yield* Output; const api = yield* LegacyPlatformApi; const resolver = yield* LegacyProjectRefResolver; + const cliSettings = yield* LegacyCliSettings; const linkedProjectCache = yield* LegacyLinkedProjectCache; const telemetryState = yield* LegacyTelemetryState; - const runtimeInfo = yield* RuntimeInfo; const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; // `--yes` OR `SUPABASE_YES`. `config push` imports `supabase/.env` before @@ -100,7 +100,11 @@ export const legacyConfigPush = Effect.fn("legacy.config.push")(function* ( // (walking up, same as `loadCliConfig` below and the workdir change // before config load), so a push from a subdirectory still reads the // project root's `supabase/.env`. - const projectRoot = (yield* findCliProjectRoot(runtimeInfo.cwd)) ?? runtimeInfo.cwd; + // Resolved against `cliSettings.workdir` — the same root the project-ref + // resolver and the linked-project cache use — so `--workdir ../other` + // pushes `../other`'s config.toml, never the invoking directory's file to + // another root's linked project. + const projectRoot = (yield* findCliProjectRoot(cliSettings.workdir)) ?? cliSettings.workdir; const projectEnv = yield* legacyLoadProjectEnv(fs, path, projectRoot); const yes = yield* legacyResolveYesWithProjectEnv(projectEnv); // dotenvx private keys for decrypting `encrypted:` secrets, from the shell @@ -131,7 +135,7 @@ export const legacyConfigPush = Effect.fn("legacy.config.push")(function* ( // Pass `ref` so a matching `[remotes.*]` block is merged over the base // config before decode. A duplicate `project_id` across remotes surfaces // an established error message. - const loaded = yield* loadCliConfig(runtimeInfo.cwd, { + const loaded = yield* loadCliConfig(cliSettings.workdir, { projectRef: ref, goViperCompat: true, }).pipe( diff --git a/packages/config/src/entrypoint-purity.unit.test.ts b/packages/config/src/entrypoint-purity.unit.test.ts index f0406776a0..502e6648cd 100644 --- a/packages/config/src/entrypoint-purity.unit.test.ts +++ b/packages/config/src/entrypoint-purity.unit.test.ts @@ -365,6 +365,7 @@ describe("src/index.ts export surface", () => { "isComparableProjectConfigPath", "isEqualConfigValue", "omitDefaultValues", + "projectConfigApiBlockKeys", "projectConfigMappingRows", "subtractCliConfig", "toCliConfigJsonSchema", @@ -417,6 +418,7 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "loadCliProjectEnvironment", "loadDotEnvFile", "omitDefaultValues", + "projectConfigApiBlockKeys", "projectConfigMappingRows", "resolveCliConfigSubtree", "resolveCliConfigValue", diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index fa0f54155b..e028d72e56 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -77,7 +77,10 @@ export { toProjectConfig, unmappedApiFields, } from "./project-config/project-config.ts"; -export { type ProjectConfigApiAttributes } from "./project-config/api-attributes.ts"; +export { + type ProjectConfigApiAttributes, + projectConfigApiBlockKeys, +} from "./project-config/api-attributes.ts"; export { type ProjectConfigMappingRow } from "./project-config/registry-row.ts"; export { projectConfigMappingRows } from "./project-config/registry.ts"; export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; diff --git a/packages/config/src/project-config/api-attributes.ts b/packages/config/src/project-config/api-attributes.ts index 2352cccfd5..2f448ce122 100644 --- a/packages/config/src/project-config/api-attributes.ts +++ b/packages/config/src/project-config/api-attributes.ts @@ -261,3 +261,15 @@ export const ProjectConfigApiAttributesSchema = Schema.Struct({ }); export type ProjectConfigApiAttributes = typeof ProjectConfigApiAttributesSchema.Type; + +/** + * The per-service block keys of the v2 project-config resource's + * `data.attributes`, in alphabetical order — derived from the mirror schema's + * own key set so consumers never hand-copy the block list (a hand-copied list + * reports a newly-learned block "not returned" forever, test-green). The + * package owns the response shape; a consumer rendering comparison scope + * (CLI-2156's scope line) reads it from here. + */ +export const projectConfigApiBlockKeys: ReadonlyArray = Object.keys( + ProjectConfigApiAttributesSchema.fields, +).sort(); From 977e3001d8b691986893c905c3c462ea6bd8df69 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Mon, 31 Aug 2026 15:06:38 -0500 Subject: [PATCH 11/12] test(cli): close the config diff suite's structural blind spots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The review traced every blocker it found to a hole in the test suite's structure; this closes them: - The shared v2 fixture's `auth: {}` meant the largest, most transform-heavy mapping surface (~200 GoTrue keys: durations, inversions, unconfigured sentinels, provisioning-default subjects) never ran end to end. It now carries a realistic fresh-project auth record at platform defaults, and the existing clean-config test proves the whole surface classifies cleanly against an empty config.toml. The empty-block case keeps its own test pinning the "(not returned: auth)" scope report. - diff.live.test.ts asserted only exit 0 while its own comment named auth-record cleanliness as the one thing mocks can't prove — it now asserts no `auth.` change lines on a fresh project. - The masking scenario seeded a secret and an HMAC-shaped remote value without asserting ABSENCE; both streams (and the JSON payload + message) now pin `not.toContain()` against formatter changes. - Failure paths assert telemetry flushed (branch 404, missing config, malformed TOML with a branch target — the last also pinning zero API requests) and that the linked-project cache stays untouched when no ref resolved. Addresses PR #6295 review (Coly010): the test-suite-structure section and the four inline test-addition asks. Co-Authored-By: Claude Fable 5 --- .../config/diff/diff.integration.test.ts | 192 +++++++++++++++++- .../commands/config/diff/diff.live.test.ts | 8 + 2 files changed, 190 insertions(+), 10 deletions(-) diff --git a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts index cdabeeb836..d0dc368cd3 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.integration.test.ts @@ -74,7 +74,79 @@ function v2Response( default_pool_size: 20, max_client_conn: 100, }, - auth: {}, + // A realistic fresh-project GoTrue record at platform defaults — the + // largest, most transform-heavy mapping surface (durations, inversions, + // unconfigured sentinels, provisioning-default subjects) must run end to + // end and classify CLEANLY against an empty config.toml. An `auth: {}` + // here previously let two classifier blockers through untested. + auth: { + site_url: "http://127.0.0.1:3000", + uri_allow_list: "https://127.0.0.1:3000", + jwt_exp: 3600, + refresh_token_rotation_enabled: true, + security_refresh_token_reuse_interval: 10, + security_manual_linking_enabled: false, + disable_signup: false, + external_anonymous_users_enabled: false, + password_min_length: 6, + password_required_characters: "", + rate_limit_anonymous_users: 30, + rate_limit_token_refresh: 150, + rate_limit_otp: 30, + rate_limit_verify: 30, + rate_limit_sms_sent: 30, + rate_limit_web3: 30, + // GoTrue reports 0 hours for unconfigured session bounds; the mapping + // canonicalizes them to the STRING "0s" (registry unconfiguredValue). + sessions_timebox: 0, + sessions_inactivity_timeout: 0, + external_email_enabled: true, + mailer_secure_email_change_enabled: true, + mailer_autoconfirm: true, + security_update_password_require_reauthentication: false, + mailer_otp_length: 6, + mailer_otp_exp: 3600, + smtp_max_frequency: 1, + smtp_host: null, + // Provisioning-default subject lines (recorded config_auth fixtures). + mailer_subjects_invite: "You have been invited", + mailer_subjects_confirmation: "Confirm Your Signup", + mailer_subjects_recovery: "Reset Your Password", + mailer_subjects_magic_link: "Your Magic Link", + mailer_subjects_email_change: "Confirm Email Change", + mailer_subjects_reauthentication: "Confirm Reauthentication", + mailer_subjects_password_changed_notification: "Your password has been changed", + mailer_subjects_email_changed_notification: "Your email address has been changed", + mailer_subjects_phone_changed_notification: "Your phone number has been changed", + mailer_subjects_identity_linked_notification: "A new identity has been linked", + mailer_subjects_identity_unlinked_notification: "An identity has been unlinked", + mailer_subjects_mfa_factor_enrolled_notification: "A new MFA factor has been enrolled", + mailer_subjects_mfa_factor_unenrolled_notification: "An MFA factor has been unenrolled", + mailer_notifications_password_changed_enabled: false, + mailer_notifications_email_changed_enabled: false, + mailer_notifications_phone_changed_enabled: false, + mailer_notifications_identity_linked_enabled: false, + mailer_notifications_identity_unlinked_enabled: false, + mailer_notifications_mfa_factor_enrolled_enabled: false, + mailer_notifications_mfa_factor_unenrolled_enabled: false, + external_phone_enabled: false, + sms_autoconfirm: false, + sms_max_frequency: 5, + sms_otp_exp: 600, + sms_otp_length: 6, + external_github_enabled: false, + external_github_client_id: "", + mfa_totp_enroll_enabled: false, + mfa_totp_verify_enabled: false, + mfa_phone_enroll_enabled: false, + mfa_phone_verify_enabled: false, + mfa_phone_otp_length: 6, + mfa_phone_template: "Your code is {{ .Code }}", + mfa_phone_max_frequency: 5, + mfa_web_authn_enroll_enabled: false, + mfa_web_authn_verify_enabled: false, + mfa_max_enrolled_factors: 10, + }, api: { db_schema: "public,graphql_public", db_extra_search_path: "public,extensions", @@ -234,10 +306,8 @@ describe("legacy config diff integration", () => { expect(out.stderrText).toContain( `Comparing against project ${LEGACY_VALID_REF} using base config`, ); - // The fixture's `auth: {}` is an EMPTY block — reported not-returned - // rather than falsely claimed compared. expect(out.stderrText).toContain( - "Comparison scope: api, database, pooler, realtime, storage (not returned: auth)", + "Comparison scope: api, auth, database, pooler, realtime, storage", ); expect(out.stdoutText).toContain("api.max_rows [update]"); expect(out.stdoutText).toContain("local: 500"); @@ -277,6 +347,17 @@ describe("legacy config diff integration", () => { it.live("declared properties the response does not carry are local_only", () => { const { layer, out } = setup({ toml: 'project_id = "test"\n[auth]\nsite_url = "https://local.example.com"\n', + v2: { + status: 200, + body: v2Response({ + attributes: (attributes) => { + // Drop site_url from the otherwise-complete auth record so the + // response genuinely does not carry the declared property. + const { site_url: _siteUrl, ...auth } = attributes["auth"] as Record; + return { ...attributes, auth }; + }, + }), + }, }); return Effect.gen(function* () { yield* legacyConfigDiff(noFlags); @@ -314,7 +395,13 @@ describe("legacy config diff integration", () => { body: v2Response({ attributes: (attributes) => ({ ...attributes, - auth: { external_github_enabled: true, external_github_client_id: "id" }, + auth: { + external_github_enabled: true, + external_github_client_id: "id", + // The platform reports secret fields as HMAC digests, never + // plaintext — the digest must not surface either. + external_github_secret: "v1,whmac-sha256-digest-of-the-secret", + }, }), }), }, @@ -325,10 +412,54 @@ describe("legacy config diff integration", () => { expect(out.stdoutText).toContain( "Note: 1 credential value not compared (masked by the API): auth.external.github.secret", ); + // The secret STRING never leaks — neither the local plaintext resolved + // from the env var nor the API-reported HMAC digest, on either stream. + // Pins the "secrets never leak" claim against formatter changes. + const everything = out.stdoutText + out.stderrText; + expect(everything).not.toContain("shh"); + expect(everything).not.toContain("whmac-sha256"); expect(processControl.exitCode).toBeUndefined(); }).pipe(Effect.provide(layer)); }); + it.live("secret strings never reach the machine payload either", () => { + const { layer, out } = setup({ + toml: [ + 'project_id = "test"', + "[auth.external.github]", + "enabled = true", + 'client_id = "id"', + 'secret = "env(GITHUB_SECRET)"', + "", + ].join("\n"), + dotenv: "GITHUB_SECRET=shh\n", + format: "json", + v2: { + status: 200, + body: v2Response({ + attributes: (attributes) => ({ + ...attributes, + auth: { + external_github_enabled: true, + external_github_client_id: "id", + external_github_secret: "v1,whmac-sha256-digest-of-the-secret", + }, + }), + }), + }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + const success = out.messages.find((message) => message.type === "success"); + const serialized = JSON.stringify(success); + expect(serialized).not.toContain("shh"); + expect(serialized).not.toContain("whmac-sha256"); + // The message itself carries the masked caveat, so `.message` echoers + // never claim full sync. + expect(success?.message).toContain("masked by the API"); + }).pipe(Effect.provide(layer)); + }); + it.live("a matching [remotes.*] block becomes the local operand", () => { const { layer, out } = setup({ toml: [ @@ -406,7 +537,7 @@ describe("legacy config diff integration", () => { }); it.live("an unknown branch fails with a branches-list suggestion", () => { - const { layer } = setup({ + const { layer, telemetry, linkedProjectCache } = setup({ toml: 'project_id = "test"\n', branchByName: { status: 404, body: { message: "not found" } }, }); @@ -419,6 +550,10 @@ describe("legacy config diff integration", () => { expect(rendered).toContain("LegacyConfigDiffBranchNotFoundError"); expect(rendered).toContain('Branch \\"ghost\\" not found'); expect(rendered).toContain("supabase branches list"); + // Legacy Shell Invariant #1: telemetry flushes on failure too; the + // linked-project cache stays untouched because no target ref resolved. + expect(telemetry.flushed).toBe(true); + expect(linkedProjectCache.cachedRef).toBeUndefined(); }).pipe(Effect.provide(layer)); }); @@ -436,8 +571,8 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); - it.live("a missing config file points at supabase init", () => { - const { layer } = setup(); + it.live("a missing config file points at supabase init before any resolution", () => { + const { layer, telemetry, api } = setup(); return Effect.gen(function* () { const exit = yield* legacyConfigDiff(noFlags).pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); @@ -445,6 +580,24 @@ describe("legacy config diff integration", () => { expect(rendered).toContain("LegacyConfigDiffLoadConfigError"); expect(rendered).toContain("supabase/config.toml: file not found"); expect(rendered).toContain("supabase init"); + // The load runs before any network call, and telemetry still flushes. + expect(api.requests).toHaveLength(0); + expect(telemetry.flushed).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("a malformed config aborts before any network call, even with a branch target", () => { + // A broken TOML must not burn a branch-resolution round trip — the local + // document is parsed and validated first. + const { layer, api, telemetry } = setup({ toml: "not [valid toml\n" }); + return Effect.gen(function* () { + const exit = yield* legacyConfigDiff({ ...noFlags, projectRef: Option.some("staging") }).pipe( + Effect.exit, + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(JSON.stringify(exit)).toContain("failed to parse supabase/config.toml"); + expect(api.requests).toHaveLength(0); + expect(telemetry.flushed).toBe(true); }).pipe(Effect.provide(layer)); }); @@ -551,8 +704,8 @@ describe("legacy config diff integration", () => { expect(data["schema_version"]).toBe(1); expect(typeof data["config_schema"]).toBe("string"); expect(data["scope"]).toEqual({ - present: ["api", "database", "pooler", "realtime", "storage"], - missing: ["auth"], + present: ["api", "auth", "database", "pooler", "realtime", "storage"], + missing: [], }); expect(data["changes"]).toEqual([ { path: ["api", "max_rows"], class: "update", declared: true, local: 500, remote: 1000 }, @@ -760,6 +913,25 @@ describe("legacy config diff integration", () => { }).pipe(Effect.provide(layer)); }); + it.live("an empty block record is reported not-returned, not silently compared", () => { + // A permission-truncated `auth: {}` is schema-valid; claiming it was + // compared while every auth key silently vanishes would make a red CI + // unfixable by any file edit. + const { layer, out } = setup({ + toml: 'project_id = "test"\n', + v2: { + status: 200, + body: v2Response({ attributes: (attributes) => ({ ...attributes, auth: {} }) }), + }, + }); + return Effect.gen(function* () { + yield* legacyConfigDiff(noFlags); + expect(out.stderrText).toContain( + "Comparison scope: api, database, pooler, realtime, storage (not returned: auth)", + ); + }).pipe(Effect.provide(layer)); + }); + it.live("a declared path push cannot communicate surfaces in the unmanaged note", () => { // auth.oauth_server is dropped from the local projection entirely (push // has no oauth_server handling), so a declared `enabled = true` diff --git a/apps/cli/src/legacy/commands/config/diff/diff.live.test.ts b/apps/cli/src/legacy/commands/config/diff/diff.live.test.ts index 34918aafc4..bcec195898 100644 --- a/apps/cli/src/legacy/commands/config/diff/diff.live.test.ts +++ b/apps/cli/src/legacy/commands/config/diff/diff.live.test.ts @@ -12,6 +12,14 @@ test("diffs a freshly-initialized config against the project", async ({ cli, pro expect(`${result.stdout}${result.stderr}`).not.toContain("Unauthorized"); expect(result.stderr).toContain(`Comparing against project ${project.ref} using base config`); expect(result.stderr).toContain("Comparison scope:"); + // The GoTrue-keyed auth record — the one surface mocks cannot prove — must + // classify CLEANLY against a fresh config: the platform's reports of + // unconfigured state (session zeros canonicalized to "0s", the + // provisioning-default mailer subjects, disabled notification toggles) are + // suppressed by the registry's unconfiguredValue baselines, not flagged as + // drift. Asserting only exit 0 here would let that noise through silently. + const authChangeLines = result.stdout.split("\n").filter((line) => line.startsWith("auth.")); + expect(authChangeLines, result.stdout).toEqual([]); // Read-only success regardless of drift (no --exit-code passed). requireLiveSuccess(result, "config diff"); }); From a4dc035bacaed7e1bcf85e4331095e669f74ed99 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Mon, 31 Aug 2026 15:09:10 -0500 Subject: [PATCH 12/12] docs(cli): publish config diff reference and finalize ADR 0022 - Add the docs-site overlay (docs/supabase/config/diff.md, the published reference page config push already had) covering branch targeting, the update/remote-only/local-only classes, (unset) vs (not returned), masking and unpushable-declared notes, the convergence-projection rendering ("1m" renders as "1m0s"), the 0/1/2 exit-code contract, and the machine output modes. - ADR 0022 moves to accepted and records the review-driven revisions its body previously contradicted or omitted: the unconfiguredValue baseline tier replacing type-level zero inference, registry-declared array equality (sequence default), the unmanaged bucket, segment-array paths with declared and materialized-local on change entries, the branch-accepting --project-ref surface, honoring -o per Legacy Shell Invariant #6, the 0/1/2 exit codes, the versioned payload contract, and the relaxed response contract behind the scope note. Addresses PR #6295 review (Coly010): docs-overlay and ADR-status threads. Co-Authored-By: Claude Fable 5 --- apps/cli/docs/supabase/config/diff.md | 11 +++++++++++ ...config-diff-classification-and-managed-surface.md | 12 +++++++----- docs/adr/README.md | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 apps/cli/docs/supabase/config/diff.md diff --git a/apps/cli/docs/supabase/config/diff.md b/apps/cli/docs/supabase/config/diff.md new file mode 100644 index 0000000000..c4a45d74ec --- /dev/null +++ b/apps/cli/docs/supabase/config/diff.md @@ -0,0 +1,11 @@ +# supabase-config-diff + +Shows the configuration differences between the local `supabase/config.toml` and the effective configuration of a remote project or branch. Read-only: it never modifies the local file or any remote configuration. + +Pass `--project-ref` to compare against a specific project, or the name (or UUID) of a branch of the currently linked project — values that are exactly 20 lowercase letters are always treated as project refs. Without it, the linked project is the target. When the target ref matches a `[remotes.*]` block's `project_id`, that block's merged config is the local side of the comparison. + +Each difference is classified as `update` (the file declares a value that differs remotely), `remote-only` (the remote differs while the file is silent — the shown local value is the schema default a `config push` would write), or `local-only` (the file declares a value the remote did not report). `(unset)` means the local side has no value at all; `(not returned)` means the response did not carry the property. Secret values are never compared — the platform only reports digests — and are listed in a masked-credentials note instead, as are declared properties that `config push` cannot communicate. + +Local values are shown as the configuration your file would produce once pushed, not its literal spelling: a duration written as `"1m"` renders as `"1m0s"`, and byte sizes are shown in the units you wrote. + +With `--exit-code`, the command exits `2` when any difference is found, keeping exit `1` for errors — so scripts can distinguish drift from failure. Machine-readable output is available through `--output-format json|stream-json` (a versioned payload with per-change paths as segment arrays) or the global `-o json|yaml|toml|env` flag. diff --git a/docs/adr/0022-config-diff-classification-and-managed-surface.md b/docs/adr/0022-config-diff-classification-and-managed-surface.md index 92750857f7..ca6f24cd5f 100644 --- a/docs/adr/0022-config-diff-classification-and-managed-surface.md +++ b/docs/adr/0022-config-diff-classification-and-managed-surface.md @@ -1,7 +1,7 @@ # 0022. Config Diff Classification and Managed Surface -**Status**: proposed -**Date**: 2026-08-20 (registry consolidation 2026-08-28) +**Status**: accepted +**Date**: 2026-08-20 (registry consolidation 2026-08-28; review revision 2026-08-31) ## Problem Statement @@ -20,9 +20,11 @@ This ADR was first accepted with a self-contained translation table inside `conf - **Both operands are `ProjectConfig` convergence projections (ADR 0021).** The caller builds the local operand with `fromConfigDocument({config, document})` — raw-presence-masked, canonicalized, secret-omitting — and the remote operand with `fromApiProjectConfig(response)`. All wire-shape knowledge (renames, inversions, unit conversions, the GoTrue key table) lives in `projectConfigMappingRows`, once, shared with Studio and the future push mapper. - **The managed surface is the registry's.** The classifier walks the union of both operands' leaf paths filtered by `isComparableProjectConfigPath` — a path with no registry row is _unmanaged by construction_ and never reported (`[studio]`, ports, image pins, `[realtime]` locals, `workers`). - **Three-way classification per comparable path**, driven by _declared_ presence (the raw pre-decode document), which a decoded config cannot recover: `update` (declared + reported, values differ), `remote_only` (reported while undeclared — or while push cannot communicate the declared state — and differing from the suppression baseline), `local_only` (a declared local projection value the response did not account for: parsed-but-never-pushed attributes and permission-truncated responses). The local operand follows ADR 0018: the branch's merged effective config when the target ref matches a `[remotes.*]` block's `project_id`, the base config otherwise. -- **`remote_only` suppression baseline**: the default config's own convergence projection, falling back — for push-gated containers the projection is silent on — to the raw default config's value (`db.network_restrictions`' allow-all default IS the platform's unconfigured state), then to the type's zero value. An unconfigured project therefore diffs clean instead of flooding with platform-default noise. -- **Equality is meaning-based**: the normalizers canonicalize representations (durations, byte sizes, comma-joins) per ADR 0021, and the classifier's residual equality compares arrays as multisets and tolerates string/number and string/boolean scalar skew. -- **Secrets are "present, unknown".** Both normalizers omit secret leaves (the platform only reports HMAC digests), so secrets can never classify; the registry's `isSecret` rows define the masked surface, and locally-declared ones are surfaced in `ConfigChangeSet.masked` so a clean change list is visibly a partial claim. The command layer separately echoes which response blocks were carried, so partially-populated responses degrade to `local_only` + an explicit scope note instead of an error or silent omission. +- **`remote_only` suppression baseline**: the default config's own convergence projection, falling back — for push-gated containers the projection is silent on — to the raw default config's value (`db.network_restrictions`' allow-all default IS the platform's unconfigured state), then to the registry row's declared `unconfiguredValue` (the platform's own report of an unconfigured feature — GoTrue's `sessions_timebox: 0` canonicalizes to the STRING `"0s"`, and the provisioning-default mailer subjects are real strings; both are pinned by the recorded `config_auth` fixtures). "Unconfigured" is never inferred from type-level zero values — canonicalization can turn a platform zero into a non-zero shape, and PR #6295's review reproduced 15 noise lines on an untouched staging project from exactly that inference. A path with no baseline at any tier reports rather than guesses (over-report over under-report). An unconfigured project therefore diffs clean instead of flooding with platform-default noise. +- **Equality is meaning-based**: the normalizers canonicalize representations (durations, byte sizes, comma-joins) per ADR 0021, and the classifier's residual equality tolerates string/number and string/boolean scalar skew. Whether an array is a SET or a SEQUENCE is per-field wire knowledge and lives on the registry row (`arrayEquality`), defaulting to sequence — `api.schemas`' first entry is PostgREST's default schema and `api.extra_search_path` is a literal `search_path`, so reordering is drift; `auth.additional_redirect_urls` opts into set semantics. +- **Secrets are "present, unknown".** Both normalizers omit secret leaves (the platform only reports HMAC digests), so secrets can never classify; the registry's `isSecret` rows define the masked surface, and locally-declared ones are surfaced in `ConfigChangeSet.masked` so a clean change list is visibly a partial claim. The same visibility rule covers `ConfigChangeSet.unmanaged`: a declared comparable path the local projection drops (ADR 0021's unmanaged-by-push families — `auth.oauth_server`, disabled `storage.analytics`/`vector`, sentinel-pruned siblings) can never classify either, and silently vanishing would print a false "no differences" over a real disagreement. The command layer separately echoes which response blocks were carried — a block absent from the response, or present but EMPTY (the plausible permission-truncated shape), reports as not-returned — so partially-populated responses degrade to `local_only` + an explicit scope note instead of an error or silent omission (the generated API contract keeps every block and block key optional via `openapi-overrides.json` for exactly this reason). +- **Change entries carry what a consumer needs verbatim**: `path` is a SEGMENT ARRAY end to end (an `sms.test_otp` phone-number key may itself contain a `.`; joining is display-only), `declared` distinguishes file-written values from schema-materialized defaults, and a `remote_only` entry keeps the materialized local default so "what would `config push` change?" is answerable from the change alone — the primary dashboard-drift use case, and what `config pull` will need to write back. +- **Command surface (review revision, PR #6295)**: the target flag is `link`'s settled vocabulary — a single `--project-ref` accepting a project ref or a branch name/UUID (ref-shaped values always read as refs; a UUID resolves without a linked parent) — not a bespoke `--target`. The global `-o/--output` flag is honored per Legacy Shell Invariant #6, encoding the same structured payload as `--output-format json`. `--exit-code` gives drift its own exit code `2`, keeping `1` for failures, so scripts can tell "drifted" from "token expired". The machine payload versions its own contract (`schema_version: 1`, an integer) separately from the user-controlled `$schema` document reference (`config_schema`). - The interpolation pipeline records the resolving env var name on `"environment"` value origins, so a change on an `env()`-fed property can name the variable involved. The command layer (`apps/cli/src/legacy/commands/config/diff/`) only resolves the target, fetches, projects, and renders. Per ADR 0021's rendering rule, reported "local" values are the convergence projection — what pushing the file would produce hosted — not the file's literal spelling. diff --git a/docs/adr/README.md b/docs/adr/README.md index e6c8b4015a..acb10ea67e 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -62,7 +62,7 @@ When an ADR becomes outdated, mark it as `deprecated` or reference the supersedi | 0019 | [Raw API-Response Passthrough on API-Sourced Config](0019-config-api-response-passthrough.md) | accepted | | 0020 | [Config Naming Vocabulary](0020-config-naming-vocabulary.md) | accepted | | 0021 | [ProjectConfig Convergence Semantics](0021-projectconfig-convergence-semantics.md) | accepted | -| 0022 | [Config Diff Classification and Managed Surface](0022-config-diff-classification-and-managed-surface.md) | proposed | +| 0022 | [Config Diff Classification and Managed Surface](0022-config-diff-classification-and-managed-surface.md) | accepted | ## Template