From 5217972239359efe5348bcb7d371a165ceba5c41 Mon Sep 17 00:00:00 2001 From: Sutisna Date: Sat, 25 Jul 2026 19:46:17 +0200 Subject: [PATCH] feat(sawala): akuna storage status + isolate (org-level data residency) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sawala akuna storage status — show whether the org uses a dedicated DB sawala akuna storage isolate — enable a dedicated DB for the WHOLE org: move all BYO connections onto it, new BYO connections inherit it; managed stays shared Org-level counterpart to the per-connection 'akuna connection isolate' primitive, backed by /cli/akuna/storage{,/isolate}. Live-verified against the deployed backend. Changeset included. --- .changeset/akuna-storage-org.md | 5 +++ packages/sawala/src/commands/akuna.ts | 64 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 .changeset/akuna-storage-org.md diff --git a/.changeset/akuna-storage-org.md b/.changeset/akuna-storage-org.md new file mode 100644 index 0000000..4ef30ba --- /dev/null +++ b/.changeset/akuna-storage-org.md @@ -0,0 +1,5 @@ +--- +"@sawala/cli": minor +--- + +Add `sawala akuna storage status` and `sawala akuna storage isolate` — org-level data residency. `isolate` enables a dedicated database for the whole org (moves all BYO connections onto it, new BYO connections inherit it; managed stays shared); `status` shows current residency. Org-level counterpart to the per-connection `akuna connection isolate` primitive. diff --git a/packages/sawala/src/commands/akuna.ts b/packages/sawala/src/commands/akuna.ts index 54df04d..080e864 100644 --- a/packages/sawala/src/commands/akuna.ts +++ b/packages/sawala/src/commands/akuna.ts @@ -23,6 +23,7 @@ import { confirmOrThrow } from '../lib/io' */ const CONNECTIONS = '/cli/akuna/connections' +const STORAGE = '/cli/akuna/storage' interface Connection { id: string @@ -38,6 +39,16 @@ interface ProvisionResult { isolatedDbId: string } +// Org-level data-residency status. Isolation is an ORG setting (per-product): +// enabling moves ALL the org's BYO connections onto one dedicated database. +// Managed (Quick Login) connections always stay shared and are not counted. +interface StorageStatus { + isolated: boolean + databaseName: string | null + byoTotal: number + byoIsolated: number +} + function printJson(value: unknown): void { process.stdout.write(JSON.stringify(value, null, 2) + '\n') } @@ -102,5 +113,58 @@ export function createAkunaCommand(): Command { }) akuna.addCommand(connection) + + // ── Org-level data residency (the primary model) ──────────────────────────── + // Isolation is an ORG setting: `storage isolate` moves ALL the org's BYO + // connections onto one dedicated per-org database, and new BYO connections + // inherit it. This is the org-level counterpart to the per-connection + // `connection isolate` primitive above. Managed connections always stay shared. + const storage = new Command('storage').description( + "Manage the org's data residency (dedicated database for all BYO connections).", + ) + + storage + .command('status') + .description('Show whether the org uses a dedicated database, and how many BYO connections are on it.') + .action(async () => { + const ctx = await orgCtx() + const s = await apiFetch(ctx, STORAGE) + if (!s.isolated) { + process.stdout.write(`Shared storage. ${s.byoTotal} BYO connection(s) in the org.\n`) + return + } + process.stdout.write( + `Dedicated database active (${s.databaseName}). ${s.byoIsolated} of ${s.byoTotal} BYO connection(s) on it; new BYO connections inherit it.\n`, + ) + }) + + storage + .command('isolate') + .description( + 'Enable a dedicated database for the whole org: move ALL BYO connections onto ' + + 'it (managed connections stay shared) and auto-isolate future BYO connections. ' + + 'Idempotent. NOTE: effectively one-way — no isolated→shared migration. Requires --yes or a TTY.', + ) + .option('-y, --yes', 'Skip the confirmation prompt.') + .option('--dry-run', 'Print what would be sent without writing.') + .action(async (opts: { yes?: boolean; dryRun?: boolean }) => { + const ctx = await orgCtx() + const path = `${STORAGE}/isolate` + if (opts.dryRun) { + printJson({ wouldSend: { method: 'POST', path } }) + return + } + if (!opts.yes) { + await confirmOrThrow( + 'Enable a dedicated database for this org? All BYO connections move onto it. This is effectively one-way.', + ) + } + const s = await apiFetch(ctx, path, { method: 'POST', body: {} }) + process.stdout.write( + `Dedicated database enabled (${s.databaseName}). ${s.byoIsolated} of ${s.byoTotal} BYO connection(s) on it.\n`, + ) + }) + + akuna.addCommand(storage) return akuna }