# Migrer depuis Supabase

Cette page déplace un projet Supabase vers un projet Bastide : le schéma et les données, les utilisateurs de l'authentification, les seaux et les objets du stockage. Votre application garde `supabase-js` : elle change l'adresse et la clé du projet.

## Ce qu'il faut avant de commencer

- Un projet Bastide créé avec son backend (`project_create_with_backend`), **vierge** : l'importeur refuse un projet cible déjà occupé, avant d'écrire.
- L'adresse de connexion directe de la base source (rôle `postgres`, connexion directe ou mode session), sa clé `service_role`, et sa clé `anon` si vous voulez garder les clés historiques.
- Un exploitant : pendant la bêta sur invitation, l'import est un geste d'exploitant, pas une opération du registre. Il ouvre la base du locataire en administrateur pour installer les extensions.
- Lisez d'abord la section « Incompatibilités connues » plus bas : un client qui s'appuie sur SAML, sur le point S3 ou sur `postgres_changes` ne passe pas tel quel.

## 1. Lancer l'import

Les secrets de la source ne passent pas par la ligne de commande, que `ps` montre à tout utilisateur de l'hôte : ils se lisent dans l'environnement.

```
SUPABASE_DB_URL='postgres://postgres:<mot-de-passe>@db.<ref>.supabase.co:5432/postgres' SUPABASE_SERVICE_ROLE_KEY='<service_role>' SUPABASE_ANON_KEY='<anon>' pnpm importer:supabase <bak_…> --stockage https://<ref>.supabase.co --rapport rapport.json
```

`--stockage` est exigé dès que la source a un seau ; sans lui l'import s'interrompt et le dit. L'ordre des étapes n'est pas libre : inventaire et cible jugée vierge, utilisateurs, schéma et données (politiques et droits recomposés, en une transaction), stockage (une somme par objet), comparaison du catalogue et recomptage des lignes, rapport.

Le code de sortie dit l'issue : `0` importé, `3` importé avec des écarts à lire dans le rapport, `1` interrompu ou échoué, `2` pas fait (argument ou variable manquants). En cas d'échec, l'importeur ne défait rien : il écrit dans un projet neuf, que vous retirez si besoin ; chaque étape qui écrit le fait dans une transaction, et le rapport dit laquelle s'est arrêtée.

L'importeur n'écrit rien à la source : ni jeton révoqué, ni passage en lecture seule.

## 2. Double exécution : faire tourner les deux côtés avant de basculer

Tant que vous ne basculez pas, la source reste votre application vivante. La procédure :

1. **Importer** comme ci-dessus. L'import est une copie à un instant donné.
2. **Garder les clés d'origine.** Fournies à l'import, `SUPABASE_ANON_KEY` et `SUPABASE_SERVICE_ROLE_KEY` sont rangées par leur empreinte et restent acceptées douze mois, chaque réponse portant `Deprecation: true` et `Sunset` (voir `LIM-09`). Une copie de votre application peut donc viser Bastide sans changer ses clés : seule l'adresse du projet change.
3. **Faire tourner une copie de l'application sur Bastide**, à côté de la production qui reste sur Supabase : la même adresse de projet remplacée, les mêmes clés, le même parcours. Rejouez vos parcours ordinaires : inscription, confirmation, connexion, lecture, écriture, téléversement.
4. **Comparer.** Lisez le rapport (`--rapport`) : catalogue comparé, lignes recomptées, somme de chaque objet. Comparez ce que vos parcours rendent des deux côtés.
5. **Passer aux clés `sb_publishable_…` et `sb_secret_…`** (`backend.key.create`) avant l'échéance des douze mois, puis révoquez les clés historiques.
6. **Basculer.** Aucune réplication continue n'existe : les écritures faites à la source après l'import ne suivent pas. La fenêtre de lecture seule de la source (TR-194, `EX-MIG-140`) est prévue au lot L2 et n'existe pas encore. Pour basculer sans perte, suspendez vous-même les écritures de l'application, lancez l'import dans un NOUVEAU projet Bastide vierge, vérifiez le rapport, puis changez l'adresse de production.

Un retour arrière avant l'étape 6 est sans effet sur la source : ne rien faire côté Supabase, et retirer le projet Bastide.

## 3. Brancher l'application

Remplacez l'adresse et la clé du projet. Les chemins de Supabase sont tenus sans réécriture : `https://<projet>.<domaine>/rest/v1/…`, `/auth/v1/…`, `/realtime/v1/…`. Les en-têtes de `supabase-js` sont ceux de Supabase : la clé dans `apikey`, un jeton de session dans `Authorization`.

## Incompatibilités connues

Chaque limite ci-dessous est reproduite par un test (`pnpm limites`) ; la page publique est <https://bastide.app/limites-supabase>.

- `LIM-01` : une réponse REST porte au plus 1000 lignes, tronquée sans erreur ; paginez (`Range`, ou `limit` et `offset`).
- `LIM-02` : une ressource ne s'embarque que le long d'une clé étrangère déclarée, sinon 400 `PGRST200` ; déclarez la clé, ou exposez une vue.
- `LIM-03` : en mode transaction du mutualiseur (celui des connexions directes, par défaut), `NOTIFY` n'atteint pas un client qui fait `LISTEN` ; écoutez depuis une connexion en mode session.
- `LIM-04` : en mode transaction, `pg_advisory_lock` n'exclut pas une autre connexion du même projet ; employez `pg_advisory_xact_lock`, ou le mode session.
- `LIM-05` : en mode transaction, l'état de session n'est pas isolé (`SET`, table temporaire, curseur `WITH HOLD`) ; employez `SET LOCAL`, `ON COMMIT DROP`, ou le mode session.
- `LIM-06` : ne sont pas servis la connexion d'entreprise SAML (`signInWithSSO`), la liaison d'une identité (`linkIdentity`) et `/auth/v1/settings` (404, `chemin_auth_inconnu`). Une connexion d'entreprise passe par Keycloak en OpenID Connect. Les douze fournisseurs de `signInWithOAuth` et la double authentification sont servis.
- `LIM-07` : le point compatible S3 (`/storage/v1/s3`) n'est pas servi (404, `chemin_inconnu`) ; il viendra avec `EX-DATA-370` (lot L2). Le stockage de `storage-js` est servi.
- `LIM-08` : le temps réel sert la diffusion et la présence, pas l'écoute des changements : un abonnement `postgres_changes` est refusé. Employez `select realtime.diffuser_les_changements('public.ma_table')`.
- `LIM-09` : les clés historiques `anon` et `service_role` ne valent que pour un projet importé, deux clés d'origine seulement, douze mois. Toute autre reçoit 401 `cle_refusee`.
- `LIM-10` : `updateUser` ne change que le mot de passe ; une adresse ou des métadonnées sont refusées (400, `changement_d_adresse_ailleurs`, `metadonnees_non_rangees`). Employez `POST /auth/v1/user/email`, et une table du projet pour les métadonnées.
- `LIM-11` : une table n'est accordée aux rôles de l'API que si elle porte la sécurité au niveau des lignes ET au moins une politique ; les rôles portent le nom de la base en préfixe, donc un `grant … to authenticated` écrit tel quel est refusé (code `42704`).

Autres écarts mesurés : l'utilisateur rendu ne porte pas `last_sign_in_at` (le champ est absent, pas nul) ; ne sont ni mesurés ni servis les codes de secours, le web3 et le changement d'adresse par le client. Le taux de la suite d'intégration officielle de `supabase-js` (95 % exigés par `EX-DATA-090`) n'est pas encore joué : elle suppose des clés asymétriques avec un point JWKS, prévues au lot L2.

Les briques sous le plan de données sont six, épinglées par empreinte (matrice des briques) : PostgreSQL 17, pgvector, Supavisor, PostgREST, imgproxy (édition libre) et SeaweedFS. Le client éprouvé est `@supabase/supabase-js` 2.117.2. L'import ne reprend que les extensions admises par le palier du projet cible.

Voir aussi : [Migrer depuis Vercel](from-vercel.md), [Sortir de Bastide](leave.md).
