# scribase import

Two shapes share one verb:

- `scribase import <url>` — reads an existing Postgres database and produces a Scribase schema file.
- `scribase import supabase ...` — migrates a live Supabase project into a Scribase environment via the management API.

---

## Import a schema from a URL

Connects to an existing Postgres database, introspects its columns, constraints,
and RLS policies, and emits a `.scribase` schema file.

```sh
scribase import <connection-url> [--out FILE] [--partial]
```

### Options

| Flag | Description |
|---|---|
| `--out <file>` | Write output to file instead of stdout |
| `--partial` | Allow incomplete import (some columns may not be expressible) |

### Example

```sh
scribase import "postgresql://user:pass@host/db" --out schema.scribase
```

### Notes

- Any column type, constraint, or default that cannot be expressed in the Scribase schema language is printed as a **fidelity note**. Without `--partial`, the command fails if there are any such notes.
- Tables without row-level security are printed as **security notes** — they are imported but the output warns you.
- Use `--partial` when working with complex legacy schemas and iterating toward full coverage.

---

## Migrate from Supabase

Migrates a live Supabase project into a Scribase environment. The source project's schema and data are read and applied to the destination environment.

```sh
scribase import supabase \
  --project-ref <supabase-project-ref> \
  --org <scribase-org-id> \
  --project <scribase-project-id> \
  --env <scribase-env-id> \
  [--connection-string <url>] \
  [--service-key <key>] \
  [--dry-run]
```

### Options

| Flag | Env override | Description |
|---|---|---|
| `--project-ref` | — | Supabase project reference ID |
| `--org` | — | Destination Scribase organization ID |
| `--project` | — | Destination Scribase project ID |
| `--env` | — | Destination Scribase environment ID |
| `--connection-string` | `SCRIBASE_SOURCE_CONNECTION_STRING` | Supabase database URL |
| `--service-key` | `SCRIBASE_SOURCE_SERVICE_KEY` | Supabase service role key |
| `--schemas a,b` | — | Copy only these schemas (default: every application schema) |
| `--no-storage` | — | Skip buckets and objects |
| `--dry-run` | — | Plan what would move without touching anything |
| `--no-wait` | — | Start the import and print its `import_id` without following progress |
| `--functions-dir DIR` | — | After the data import succeeds, deploy every edge function in this local `supabase/functions` directory (ignored with `--dry-run`) |
| `--functions-only` | — | Skip the data import and only deploy the functions (`--functions-dir` defaults to `supabase/functions`) |

The connection string and service key are secrets. Pass them via environment variables rather than on the command line in production contexts.

### Example

```sh
export SCRIBASE_SOURCE_CONNECTION_STRING="postgresql://..."
export SCRIBASE_SOURCE_SERVICE_KEY="eyJhbGci..."

scribase import supabase \
  --project-ref abcdefghijkl \
  --org acme \
  --project store \
  --env production \
  --dry-run
```

The server returns a report describing what would be imported. Remove `--dry-run` to execute: the server starts the import in the background, and the command follows `GET /v1/imports/{import_id}`, printing each phase as it starts, then prints the verified report. Pressing Ctrl-C stops watching, not the import.

### Edge functions

```sh
# Data first, then every function in the local checkout:
scribase import supabase --project-ref <ref> --org acme --project store --env production \
  --functions-dir supabase/functions

# Or only the functions, for example after a data import that already finished:
scribase import supabase --functions-only --org acme --project store --env production \
  --functions-dir supabase/functions
```

Function secrets are never read from the source. Re-enter them with
[`scribase secrets set`](https://docs.scribase.com/docs/cli/secrets.md).

### What is migrated

In order, each step replay-safe (`--resume <import_id>` continues from the last committed step):

1. **Extensions** the app schemas use (best effort; any the target cannot install is listed as an action item).
2. **Schema** of every application schema (`public` plus any schema that Supabase or an extension does not own): tables, types, functions and RPCs, views, indexes, `ENABLE ROW LEVEL SECURITY`. Foreign keys and triggers are held back until after the data, as `pg_restore` does.
3. **Grants**: the exact privileges `anon`, `authenticated` and `service_role` hold on every schema, table, sequence and function (revokes included, so an RPC hidden from `anon` stays hidden).
4. **Auth**: `auth.users` with ids and bcrypt `encrypted_password` hashes byte for byte, plus `instance_id`, `aud` and `role` (the lookup columns GoTrue uses on sign-in); `auth.identities` (`provider`, `provider_id`, `identity_data`), so Apple and Google `signInWithIdToken` users resolve to the same user id; `auth.mfa_factors` (TOTP secrets verbatim).
5. **Data**, page by page, keyset-paged and idempotent.
6. **Post-data**: foreign keys, table triggers, sequence positions (`setval`), and triggers on `auth.users` / `storage.objects` that call app functions (for example `on_auth_user_created`).
7. **RLS policies** on the app schemas and on `storage.objects` / `storage.buckets`, with their `auth.uid()` / `auth.jwt()` expressions unchanged.
8. **Storage**: buckets, then each object's bytes through the destination environment's storage API (`x-upsert`, so a resume overwrites), then the object's original `owner_id`, so owner-based storage policies keep matching.
9. **Verification**: per-table row counts and checksums, policy parity, auth user and identity counts, object counts, and an optional login proof.

### What is not migrated

- **Sessions and refresh tokens.** Scribase signs with its own JWT secret, so every existing session ends. Users sign in once more after the switch, with the same password or the same Apple/Google account, and keep their user id and data.
- **API keys and the JWT secret.** They are never copied. The app gets the environment's own keys (see below).
- **Edge function source from the hosted project.** The Supabase API cannot reliably hand back function source, so functions move from your local checkout instead: pass `--functions-dir supabase/functions` (or run `--functions-only` later) and the CLI deploys each function, honouring `_shared`, `deno.json` / import maps and `verify_jwt` in `config.toml`. The console import wizard does not move functions yet.
- **Vault secrets and function env vars** (listed by name; re-enter with `scribase secrets set`), **custom auth hooks, SMTP, and auth provider settings** (set them on the environment's Auth page), **`realtime` publications**, **`pg_cron` jobs**, and **column-level grants**.
- Keyless tables (no single-column primary key) are protected against duplicates on a resume, not on a second fresh run. Import into an empty environment.

## Switch an Expo app to Scribase

The end-to-end procedure for a mobile app (Expo + `supabase-js`, email/password plus native Apple/Google sign-in):

1. **Create the environment** (console, or `scribase env create ...`). Open its Storage page once so the storage service creates its schema.
2. **Dry run**, then **import**:

   ```sh
   export SCRIBASE_SOURCE_CONNECTION_STRING="postgresql://postgres:...@db.<ref>.supabase.co:5432/postgres"
   export SCRIBASE_SOURCE_SERVICE_KEY="<source service_role key>"
   scribase import supabase --project-ref <ref> --org <org> --project <app> --env production --dry-run
   scribase import supabase --project-ref <ref> --org <org> --project <app> --env production
   ```

   Proceed only when the verdict is `VERIFIED`.
3. **Configure auth on the environment** (Auth page), mirroring the Supabase project's Auth settings: `site_url`, the redirect allow list (`myapp://**`, plus `exp://**` for Expo Go), and for native sign-in the Apple provider (client id = the iOS bundle id, plus the Services ID if you also use the web flow) and the Google provider (client ids = the web, iOS and Android OAuth client ids, comma-separated). The console also asks for each provider's client secret.
4. **Print the app config**:

   ```sh
   scribase apps switch <org> <app> --env production            # .env lines for Expo
   scribase apps switch <org> <app> --format json               # for scripts
   scribase apps switch <org> <app> --key publishable           # sb_publishable_ key instead of the anon JWT
   ```

   It prints exactly `EXPO_PUBLIC_SUPABASE_URL=https://<label>.<domain>` and `EXPO_PUBLIC_SUPABASE_ANON_KEY=<key>`, plus the auth setting names above. It never prints the `service_role` / `sb_secret_` key. That key must never ship in an app, because it bypasses row-level security.
5. **Update the app**: put the two values in `.env` (or the EAS environment), rebuild, and ship. `createClient(url, key)` needs no code change.
6. **Cut over**: rehearse steps 2 to 5 against a `staging` environment first. At cutover, stop writes on the Supabase project (or force-update the old app version), import into the still-empty `production` environment, and release the build that points at it. The URL and key printed in step 4 are fixed once the environment exists, so the build can be prepared before the final import. Existing users see the sign-in screen once.
