@supabase/lite 0.9.1-next.1 → 0.9.1-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/FEATURES.md +6 -6
  2. package/LIMITATIONS.md +8 -6
  3. package/PATTERNS.md +66 -6
  4. package/README.md +15 -8
  5. package/STATUS.md +26 -19
  6. package/dist/{Connection-ZWTDByQ5.d.ts → Connection-f_d5HhQ0.d.ts} +301 -293
  7. package/dist/cli/index.js +127 -124
  8. package/dist/cli/lib.d.ts +1 -12
  9. package/dist/cli/lib.js +44 -39
  10. package/dist/db/fallback.d.ts +1 -1
  11. package/dist/db/postgres/PostgresConnection.js +18 -18
  12. package/dist/db/postgres/pglite/PgliteConnection.js +4 -4
  13. package/dist/index.d.ts +90 -38
  14. package/dist/index.js +181 -73
  15. package/dist/static/.vite/manifest.json +34 -2
  16. package/dist/static/assets/InterVariable-Dx4kXJAl.woff2 +0 -0
  17. package/dist/static/assets/InterVariable-Italic-DpCbqKDY.woff2 +0 -0
  18. package/dist/static/assets/SourceCodePro-Variable-BP8Zz55n.woff2 +0 -0
  19. package/dist/static/assets/SourceCodePro-Variable-Italic-eALmlzX7.woff2 +0 -0
  20. package/dist/static/assets/main-BY4iigay.css +1 -0
  21. package/dist/static/assets/main-BnQ-v4V9.js +199 -0
  22. package/dist/static/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2 +0 -0
  23. package/dist/static/assets/manrope-latin-wght-normal-DHIcAJRg.woff2 +0 -0
  24. package/dist/vite/index.d.ts +601 -29
  25. package/dist/vite/index.js +2 -2
  26. package/docs/auth/overview.mdx +1 -1
  27. package/docs/auth/supported-flows.mdx +4 -4
  28. package/docs/compatibility.mdx +4 -4
  29. package/docs/database/migrations.mdx +2 -0
  30. package/docs/database/rls.mdx +7 -5
  31. package/docs/database/schemas.mdx +1 -1
  32. package/docs/integrations/embedded.mdx +20 -3
  33. package/docs/integrations/frameworks.mdx +1 -1
  34. package/docs/integrations/vite.mdx +4 -2
  35. package/docs/llms.txt +2 -2
  36. package/docs/quickstart.mdx +2 -2
  37. package/docs/running.mdx +20 -10
  38. package/docs/storage/limitations.mdx +2 -11
  39. package/docs/storage/overview.mdx +8 -2
  40. package/package.json +2 -1
  41. package/skills/supalite/SKILL.md +1 -1
  42. package/dist/static/assets/main-1bwWb_1q.js +0 -40996
  43. package/dist/static/assets/main-BDsRycsc.css +0 -4045
  44. package/dist/static/fonts/CustomFont-Black.woff2 +0 -0
  45. package/dist/static/fonts/CustomFont-BlackItalic.woff2 +0 -0
  46. package/dist/static/fonts/CustomFont-Bold.woff2 +0 -0
  47. package/dist/static/fonts/CustomFont-BoldItalic.woff2 +0 -0
  48. package/dist/static/fonts/CustomFont-Book.woff2 +0 -0
  49. package/dist/static/fonts/CustomFont-BookItalic.woff2 +0 -0
  50. package/dist/static/fonts/CustomFont-Medium.woff2 +0 -0
package/FEATURES.md CHANGED
@@ -56,7 +56,7 @@ CRUD, filtering, embedding, and RLS over your tables. This is the most complete
56
56
  | Resource embedding (FK joins, `!inner`, spreads, nested, aggregates) | ✅ | ✅ | - | - | |
57
57
  | Embedding through views (view→base-table FK tracing) | ⚠️ | ⚠️ | `M` | `platform-limited` | Simple updatable views resolve embeds via their base-table FKs on both paths. Postgres path additionally supports: **materialized views** (introspected via `pg_matviews`/`relkind 'm'` and treated as routable relations with derived FKs) and **multi-level recursive view-of-view** (column maps composed transitively to the ultimate physical base via a fixpoint loop with a depth-16 cap and cycle guard). Not yet traced on either path: complex views (CTE, GROUP BY, subselect-in-FROM, JOINs), and same-name relation collisions across two exposed schemas. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#database-api-postgrest-compatible) / [LIMITATIONS.md](https://github.com/supabase-community/lite/blob/HEAD/LIMITATIONS.md). |
58
58
  | Bulk ops & `Prefer` headers (representation, merge/ignore-duplicates, max-affected) | ✅ | ✅ | - | - | |
59
- | RLS enforcement (`auth.uid()`/`role()`/`jwt()`, permissive/restrictive, per-command, roles) | ✅ | ✅ | - | - | App-layer rewrite on SQLite; native on Postgres. On `sqlite-postgres` the policy metadata persists to `supabase/.temp/.deparse-cache.json`, so `lite start` (which never migrates) restores it, recalculates it from the applied migration history when the cache is gone, and fails closed instead of booting with RLS off. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls). |
59
+ | RLS enforcement (`auth.uid()`/`role()`/`jwt()`, permissive/restrictive, per-command, roles) | ✅ | ✅ | - | - | App-layer rewrite on SQLite; native on Postgres. On `sqlite-postgres`, `lite start` restores or fully rebuilds runtime metadata from authoritative recorded migration SQL, validates it against live structure, and fails with a reset hint on invalid history or drift. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls). |
60
60
  | CSV input/output (`csv()`) | ✅ | ✅ | - | - | Via `text/csv` Accept/Content-Type |
61
61
  | Response utilities (`abortSignal`, `setHeader`, `throwOnError`, `maxAffected`) | ✅ | ✅ | - | - | |
62
62
  | CORS / `OPTIONS` preflight | ✅ | ✅ | - | - | Server-wide on `/auth`, `/rest`, `/storage`; preflight answered before auth, origin `*`, exposes `Content-Range`. |
@@ -95,7 +95,7 @@ GoTrue-compatible endpoints at `/auth/v1/*`, backed by `app/src/auth/`. Email/pa
95
95
  | `getUser` / `updateUser` (metadata, password, email change) | ✅ | - | - | Secure email change (`double_confirm_changes=true`) is ⚠️ partial: spec-compatible (confirms from the current mailbox before finalizing) but not GoTrue's full two-mailbox flow. See [LIMITATIONS.md](https://github.com/supabase-community/lite/blob/HEAD/LIMITATIONS.md#auth-shipped-with-caveats). |
96
96
  | RLS auth context (`auth.uid()`, `auth.role()`, `auth.jwt()`) | ✅ | - | - | Shared with the Data API |
97
97
  | API keys (`sb_publishable_*` → `anon`, `sb_secret_*` → `service_role`) | ✅ | - | - | Enforced on `/rest/v1` and `/auth/v1` when `auth.publishable_key`/`auth.secret_key` are configured; legacy JWT-as-apikey not supported. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#api-keys). |
98
- | Local admin mode (keyless local requests run as `service_role`) | ✅ | - | - | On by default for `lite dev`/`lite start` (`/rest/v1` + `/storage/v1`) and the Vite dev server (`/rest/v1` only the plugin doesn't mount `/storage/v1` by default). `--no-admin` or `supalite({ admin: false })` to disable. Elevation requires keyless + same-origin + loopback socket + loopback hostname. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#api-keys). |
98
+ | Local admin mode (keyless local requests run as `service_role`) | ✅ | - | - | On by default for loopback `lite dev`/`lite start` and Vite dev listeners. `--host [host]` or an exposed Vite host defaults it off; opt in with `--admin` / `supalite({ admin: true })`. Elevation requires keyless + same-origin + loopback socket + loopback hostname. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#api-keys). |
99
99
  | Email delivery (default templates + customization) | ✅ | - | - | Supabase-styled default HTML, `site_url`-based verify links, GET `/auth/v1/verify` redirect. Override subject + HTML per type via `auth.email.template.<type>.content_path` (GoTrue `{{ .ConfirmationURL }}` etc.). |
100
100
  | Email driver (SMTP / provider) | ✅ | - | - | `SmtpEmailDriver` (via Nodemailer) auto-selects when `[auth.email.smtp] enabled = true` in `config.toml` (e.g. pointing at Mailpit or a real SMTP server); `Resend`/`SES`/`Sendmail` remain available via `options.drivers.email`. Default without config or explicit driver is `ConsoleEmailDriver`. `SmtpEmailDriver` requires Node or Bun; not supported on Cloudflare Workers or in the browser. |
101
101
  | OAuth / social providers + PKCE (`signInWithOAuth`, `exchangeCodeForSession`) | ⚠️ | `S` per provider | `external-service` | `github` and `google` implemented: authorization-code (PKCE) and implicit flows, automatic account linking. Other configured providers (incl. `apple`) return "not yet implemented"; each is roughly `S` effort, gated on registering a provider app. |
@@ -115,7 +115,7 @@ GoTrue-compatible endpoints at `/auth/v1/*`, backed by `app/src/auth/`. Email/pa
115
115
 
116
116
  ## Storage
117
117
 
118
- storage-api-compatible endpoints at `/storage/v1/*` (`app/src/storage/`), with pluggable filesystem and S3 backends and Sharp/Cloudflare image transforms. CRUD and signed URLs are done; access control is the main gap. Currently gated behind `EXPERIMENTAL_STORAGE`. Detail in [STATUS.md → Storage API](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#storage-api).
118
+ storage-api-compatible endpoints at `/storage/v1/*` (`app/src/storage/`), with pluggable filesystem and S3 backends, Sharp/Cloudflare image transforms, and Storage-table RLS across every database backend. Currently gated behind `EXPERIMENTAL_STORAGE`. Detail in [STATUS.md → Storage API](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#storage-api).
119
119
 
120
120
  | Capability | Status | Effort | Blocker | Notes |
121
121
  |------------|:------:|:------:|:-------:|-------|
@@ -125,9 +125,9 @@ storage-api-compatible endpoints at `/storage/v1/*` (`app/src/storage/`), with p
125
125
  | Signed URLs (download, batch, upload) | ✅ | - | - | JWT-signed, configurable TTL |
126
126
  | Image transformations (resize, format, quality) | ✅ | - | - | Sharp (buffer) and Cloudflare (URL) adapters |
127
127
  | Storage backends | ✅ | - | - | Filesystem + S3-compatible (AWS S3, MinIO, R2) |
128
- | Role-based access (service_role / anon / authenticated gating) | ⚠️ | `M` | `-` | API keys now resolve `service_role`/`anon`/`authenticated` for storage's route-level checks (secret key satisfies authed routes as `service_role`); auth-error format still diverges (401 vs 400), and there's no per-object RLS yet. |
129
- | RLS policies on storage tables | 🔄 | `M` | `-` | Per-user object access; can reuse the existing `RlsEnforcer`. ~48 `access_control` spec cases depend on it. |
130
- | `/status` health endpoint | 🔄 | `S` | `-` | Returns 200 without auth. |
128
+ | Role-based access (service_role / anon / authenticated gating) | | - | - | Verified JWT/API-key claims are shared with the Data API; invalid bearer JWTs return 401 on protected operations, retrieval routes still serve public objects, and `service_role` bypasses RLS. |
129
+ | RLS policies on storage tables | | - | - | Native on Postgres/PGlite and translated on SQLite, including custom JWT roles, ownership, full route-operation context and helpers, serving-only public bypass, signed exceptions, and canonical Storage path helpers. SQLite fails closed for system Storage tables omitted from runtime metadata; `storage` is not exposed through PostgREST by default. |
130
+ | `/status` health endpoint | | - | - | Returns 200 without auth. |
131
131
  | Bucket-list query params (`search`/`limit`/`offset`) | 🔄 | `S` | `-` | |
132
132
  | TUS resumable uploads | 🔄 | `L` | `-` | `POST/PATCH/HEAD /upload/resumable`; spec cases exist, endpoints don't. |
133
133
  | S3-compatible protocol paths (`/s3/`) | 🔄 | `L` | `-` | |
package/LIMITATIONS.md CHANGED
@@ -13,6 +13,7 @@ Anchors below point to the corresponding STATUS.md section. If a limitation here
13
13
  - `nextval` / `currval`, `clock_timestamp`, `txid_current`, user-defined functions → not supported. See [Column Defaults](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#column-defaults).
14
14
  - `FORCE ROW LEVEL SECURITY` / `NO FORCE` → accepted and ignored (no table-owner exemption to toggle). See [RLS known limitations](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls).
15
15
  - PL/pgSQL `DECLARE`, `IF`, `LOOP`, `RAISE`, variables → not supported in trigger bodies. See [PL/pgSQL Trigger Functions](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#plpgsql-trigger-functions).
16
+ - `ALTER TYPE ... ADD VALUE` / `RENAME VALUE` (enum value changes) → not supported; the error names the type and operation. Recreate the type with all values, or use a manual migration. See [Translated Field Types](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#translated-field-types).
16
17
 
17
18
  ## supabase-js (SQLite path)
18
19
 
@@ -40,12 +41,13 @@ Anchors below point to the corresponding STATUS.md section. If a limitation here
40
41
  ## Runtime / dev
41
42
 
42
43
  - `vite preview` mounts the API and runs boot migrations, but does **not** watch schemas and never enables admin mode (it simulates production). `vite build` and standalone production servers do not mount the API at all. See [Vite plugin scope](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#vite-plugin-scope).
43
- - Do not run `lite dev` (or `lite start`) alongside the Vite plugin both bind the API and collide. See [When to use what](https://github.com/supabase-community/lite/blob/HEAD/README.md#when-to-use-what).
44
- - `lite start` runs the imperative (migrations) workflow. Develop a declarative project (`supabase/schemas/*.sql`) with `lite dev` or the Vite plugin, and ship it by generating a migration: `lite db diff -f <name>` then `lite db reset`. `lite start` works on a declarative project while the cache written by the last `lite dev` run is valid; without a valid cache it is always refused, with no exceptions — it never re-derives RLS from schema files it did not apply, and never substitutes migration-only metadata for them. `lite db diff -f <name>` followed by `lite db reset` is the transition that makes the migration history authoritative, and after it `lite start` boots again. `lite migration up` is not that transition: it is non-destructive, so the generated migration re-creates objects the declarative apply already created and fails against the live development database. `lite db reset` is the related gotcha: it is destructive and replays migrations only, so anything `schemas/*.sql` describes that you never captured with `lite db diff -f` — tables and RLS policies alike — is simply not in the reset database. See [RLS](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls).
45
- - `supabase/.temp/.deparse-cache.json` is safe to delete: it is regenerated on the next apply/translate, and a boot that cannot rebuild it fails closed (refuses to serve), never open. Only `sqlite-postgres` uses it. See [RLS](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls).
46
- - **Admin mode is on by default locally.** `lite dev`, `lite start`, and the Vite dev server serve *keyless* `/rest/v1` (+ `/storage/v1` on the CLI) requests as `service_role`, so those requests bypass RLS. Credentialed requests are unaffected. Disable with `--no-admin` / `supalite({ admin: false })`. See [API Keys](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#api-keys).
47
- - Admin mode never elevates `/auth/v1`, cross-origin requests, requests from a non-loopback socket, or requests for a non-loopback hostname (DNS rebinding) so it is not a way to reach a dev server from another machine or from a hostile page. Embedders get the hostname check only, so set `options.server.admin` on a loopback-bound server or not at all.
44
+ - Run only one backend per project: never combine `lite dev`, `lite start`, or the Vite plugin against the same SQLite file. See [When to use what](https://github.com/supabase-community/lite/blob/HEAD/README.md#when-to-use-what).
45
+ - `lite start` is migration-only: it ignores migration files and `schemas/*.sql`, rebuilding metadata from recorded applied SQL. Invalid history or live structural drift requires `lite db reset`; declarative-only live changes therefore cannot be recovered by `start`. See [RLS](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls).
46
+ - `supabase/.temp/.runtime-metadata-cache.json` is disposable: missing, corrupt, stale, or tampered caches rebuild automatically when applied history matches the live structure. Only `sqlite-postgres` uses it. See [RLS](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls).
47
+ - **Admin mode is on by default only on loopback listeners.** `lite dev`/`lite start` bind `127.0.0.1`; `--host [host]` exposes/selects an address and defaults admin off unless `--admin` is explicit. Vite does the same for non-loopback hosts. Credentialed requests are unaffected. See [API Keys](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#api-keys).
48
+ - Admin mode never elevates `/auth/v1`, cross-origin requests, requests from a non-loopback socket, or requests for a non-loopback hostname. Public tunnels can make remote traffic appear loopback, so always use `--no-admin` when forwarding the port. Embedders get the hostname check unless their adapter supplies `AppRequestContext.peerAddress`.
48
49
  - The Vite plugin mounts `/rest/v1` but not `/storage/v1`, so admin mode covers storage on the CLI only unless you add the prefix. See [Vite plugin scope](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#vite-plugin-scope).
50
+ - The protected `storage` schema is not exposed through `/rest/v1` by default. Use `supabase.storage`; adding `storage` to `api.schemas` intentionally enables direct metadata endpoints. See [Storage API](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#storage-api).
49
51
 
50
52
  ## Postgres backends (pglite, postgres)
51
53
 
@@ -62,7 +64,7 @@ Common ways code goes wrong against supalite. The fix for each is the correspond
62
64
  - Don't put `DEFAULT auth.uid()` on a column. Drop the default; pass `user_id` from the client; let RLS `WITH CHECK` enforce ownership.
63
65
  - Don't call `rpc()` on the SQLite path. Run a regular HTTP endpoint, or switch the driver to `pglite` / `postgres` in `config.toml`.
64
66
  - Don't use embedded dotted-path filters (`.eq('rel.col', v)`) on SQLite. Filter the FK column on the parent, or fetch matching ids first.
65
- - Don't run `lite dev` or `lite start` next to the Vite plugin — port collision.
67
+ - Don't run more than one of `lite dev`, `lite start`, or the Vite plugin for a project they race one SQLite database even when ports differ.
66
68
  - Don't `lite db reset` then `lite start` on a declarative project and expect your schema to be there. Reset is destructive and replays migrations only, adopting that state (RLS included) as the authoritative one — run `lite db diff -f <name>` first so the declarative schema exists as a migration.
67
69
  - Don't test RLS with a keyless request while admin mode is on — it runs as `service_role` and sees everything. Send `apikey: $PUBLISHABLE_KEY` (for `anon`), plus `Authorization: Bearer $USER_JWT` for `authenticated`, or start with `--no-admin`.
68
70
  - Don't send only `Authorization: Bearer $USER_JWT` and expect `authenticated` RLS. With keys configured that's a 401 — the `apikey` is required as well.
package/PATTERNS.md CHANGED
@@ -36,26 +36,86 @@ await supabase.from("<thing>").insert({
36
36
  });
37
37
  ```
38
38
 
39
+ ## Per-user Storage ownership and folders
40
+
41
+ Uploads made with a verified JWT carrying a `sub` claim set both `storage.objects.owner_id` and the legacy `owner` field from that claim; `service_role` uploads remain ownerless. Custom string roles are preserved, so policies such as `TO manager` work when the database role and grants are configured. Use `owner_id` for per-user policies:
42
+
43
+ ```sql
44
+ create policy "read own avatars"
45
+ on storage.objects for select to authenticated
46
+ using (bucket_id = 'avatars' and owner_id = auth.uid()::text);
47
+
48
+ create policy "upload own avatars"
49
+ on storage.objects for insert to authenticated
50
+ with check (bucket_id = 'avatars' and owner_id = auth.uid()::text);
51
+
52
+ create policy "update own avatars"
53
+ on storage.objects for update to authenticated
54
+ using (bucket_id = 'avatars' and owner_id = auth.uid()::text)
55
+ with check (bucket_id = 'avatars' and owner_id = auth.uid()::text);
56
+
57
+ create policy "delete own avatars"
58
+ on storage.objects for delete to authenticated
59
+ using (bucket_id = 'avatars' and owner_id = auth.uid()::text);
60
+ ```
61
+
62
+ For a bucket laid out as `<user-id>/<filename>`, scope uploads to the caller's first folder:
63
+
64
+ ```sql
65
+ create policy "upload to own avatar folder"
66
+ on storage.objects for insert to authenticated
67
+ with check (
68
+ bucket_id = 'avatars'
69
+ and (storage.foldername(name))[1] = auth.uid()::text
70
+ );
71
+ ```
72
+
73
+ The indexed `storage.foldername()` form uses PostgreSQL's one-based indexing and works on SQLite, PGlite, and PostgreSQL. The upstream `storage.filename()` and `storage.extension()` helpers work across the same backends.
74
+
75
+ Use the operation helpers when one SQL command backs multiple Storage actions. This policy permits downloads without exposing the same objects through list:
76
+
77
+ ```sql
78
+ create policy "download avatars without listing"
79
+ on storage.objects for select to authenticated
80
+ using (
81
+ bucket_id = 'avatars'
82
+ and storage.allow_any_operation(array[
83
+ 'object.get_authenticated',
84
+ 'object.get_authenticated_info'
85
+ ])
86
+ );
87
+ ```
88
+
89
+ `storage.operation()` returns the complete route identifier, such as `storage.object.upload`. `storage.allow_only_operation()` and `storage.allow_any_operation()` accept operation names with or without the `storage.` prefix. Public buckets bypass RLS only when serving objects through `GET`, `HEAD`, and info. Keep `SELECT` policies for listing, signing, copy sources, and move sources, plus mutation policies for uploads, deletes, moves, and copies.
90
+
39
91
  ## Verifying RLS policies locally
40
92
 
41
- Local admin mode is on by default (`lite dev`, `lite start`, Vite dev server), and a request with **no** credential runs as `service_role` so a bare `curl` sees every row and proves nothing about your policies. Always test with a credential; those requests are never elevated and behave exactly as they will in production.
93
+ Local admin mode is on by default for loopback `lite dev`, `lite start`, and Vite dev listeners. A request with **no** credential then runs as `service_role`, so a bare `curl` sees every row and proves nothing about your policies. Always test with a credential; those requests are never elevated and behave exactly as they will in production.
42
94
 
43
95
  ```bash
44
96
  # anon: what a logged-out visitor sees
45
97
  curl -H "apikey: $SUPABASE_PUBLISHABLE_KEY" \
46
- "http://localhost:54321/rest/v1/<thing>?select=*"
98
+ "http://127.0.0.1:54321/rest/v1/<thing>?select=*"
47
99
 
48
100
  # authenticated: the apikey is required IN ADDITION to the user JWT
49
101
  JWT=$(curl -s -H "apikey: $SUPABASE_PUBLISHABLE_KEY" \
50
102
  -H 'Content-Type: application/json' \
51
103
  -d '{"email":"a@b.co","password":"secret123"}' \
52
- "http://localhost:54321/auth/v1/token?grant_type=password" | jq -r .access_token)
104
+ "http://127.0.0.1:54321/auth/v1/token?grant_type=password" | jq -r .access_token)
53
105
 
54
106
  curl -H "apikey: $SUPABASE_PUBLISHABLE_KEY" -H "Authorization: Bearer $JWT" \
55
- "http://localhost:54321/rest/v1/<thing>?select=*"
107
+ "http://127.0.0.1:54321/rest/v1/<thing>?select=*"
108
+ ```
109
+
110
+ `Authorization` alone is a 401 — opaque keys are only read from `apikey`, matching upstream. supabase-js sends both automatically, so app code needs no special handling. To take admin mode out of the picture entirely, start with `--no-admin` (or `supalite({ admin: false })`); keyless requests are not elevated and normal auth rules apply.
111
+
112
+ For a container, mobile device, or LAN client, expose the listener without exposing admin mode:
113
+
114
+ ```bash
115
+ lite dev --host --no-admin
56
116
  ```
57
117
 
58
- `Authorization` alone is a 401 opaque keys are only read from `apikey`, matching upstream. supabase-js sends both automatically, so app code needs no special handling. To take admin mode out of the picture entirely, start with `--no-admin` (or `supalite({ admin: false })`) and every request will require a key.
118
+ Bare `--host` has the same auth behavior but warns so the implicit admin shutdown is visible. `--host --admin` is only for direct trusted-network access: remote peers still require credentials, and a public tunnel can erase the peer boundary.
59
119
 
60
120
  ## Filtering an embedded resource (SQLite path)
61
121
 
@@ -103,7 +163,7 @@ The canonical Vite recipe:
103
163
  ```
104
164
  6. `bun run dev`.
105
165
 
106
- Same-process, same origin, hot-reload on schema changes. Do **not** run `lite dev` alongside.
166
+ Same-process, same origin, hot-reload on schema changes. This plugin is the project's only backend process: do **not** run `lite dev` or `lite start` alongside it.
107
167
 
108
168
  ## `updated_at` timestamps via trigger
109
169
 
package/README.md CHANGED
@@ -74,14 +74,14 @@ lite init # scaffold supabase/ directory
74
74
  lite dev # start server with schema hot-reload
75
75
  ```
76
76
 
77
- The API is now running at `http://localhost:54321`. Point `@supabase/supabase-js` at it:
77
+ The API is now running at `http://127.0.0.1:54321`. Point `@supabase/supabase-js` at it:
78
78
 
79
79
  `lite init` also generates any missing publishable/secret API key(s) into root `.env` (per-variable, never overwrites an existing one) and prints them:
80
80
 
81
81
  ```typescript
82
82
  import { createClient } from "@supabase/supabase-js";
83
83
 
84
- const supabase = createClient("http://localhost:54321", "<sb_publishable_...>");
84
+ const supabase = createClient("http://127.0.0.1:54321", "<sb_publishable_...>");
85
85
  const { data } = await supabase.from("todos").select("*");
86
86
  ```
87
87
 
@@ -99,7 +99,7 @@ Edit `supabase/schemas/schema.sql` and the dev server re-applies the schema auto
99
99
  | Non-Vite app, want auto schema-reload | `lite dev` | Separate process. Watches `schemas/*.sql`, re-applies on change. |
100
100
  | Non-Vite app, manual control / CI / prod-like | `lite start` | Separate process. No watch, no auto-migrate. |
101
101
 
102
- > Do not run `lite dev` or `lite start` alongside the Vite plugin both bind the API and will collide.
102
+ > Run only one backend per project. Never run `lite dev` and `lite start` together, or either command alongside the Vite plugin: they would race migrations and schema reconciliation against the same SQLite file.
103
103
 
104
104
  Known limitations across all paths: see [LIMITATIONS.md](https://github.com/supabase-community/lite/blob/HEAD/LIMITATIONS.md). Canonical recipes: see [PATTERNS.md](https://github.com/supabase-community/lite/blob/HEAD/PATTERNS.md).
105
105
 
@@ -123,6 +123,8 @@ to show details like the config file and database location on stderr.
123
123
  | `generate-keys` | (Re)generate the publishable/secret API key pair, upsert `.env` |
124
124
  | `dev` | Start server + watch `schemas/*.sql`, auto-apply on change |
125
125
  | `start` | Start server (no watch, no auto-migrate) |
126
+
127
+ `lite dev` and `lite start` bind `127.0.0.1` by default. Pass `--host` to listen on all interfaces, matching Vite's flag, or `--host <address>` to choose one explicitly. An exposed listener defaults admin mode off; add `--admin` only when peer-scoped local Studio access is intentional.
126
128
  | `db schema` | Print current DB schema; `--diff` compares vs `schemas/*.sql` |
127
129
  | `db diff` | Emit a new pg-DDL migration from the declarative schema diff |
128
130
  | `db translate` | Translate Postgres SQL to this project's backend dialect (arg or stdin) |
@@ -275,18 +277,23 @@ Lost or rotating keys: `lite generate-keys` mints a fresh pair and upserts `.env
275
277
 
276
278
  ### Admin mode (local only)
277
279
 
278
- `lite dev`, `lite start`, and the Vite dev server run with admin mode **on**. A request carrying no credential at all — no `apikey`, no `Authorization` — on `/rest/v1` (and `/storage/v1` on the CLI) is served as `service_role`. That's what lets the built-in studio read and edit any table without a secret key shipping to the browser, mirroring self-hosted Supabase Studio where the server holds the key.
280
+ `lite dev`, `lite start`, and a loopback-bound Vite dev server run with admin mode **on**. A request carrying no credential at all — no `apikey`, no `Authorization` — on `/rest/v1` (and `/storage/v1` on the CLI) is served as `service_role`. That's what lets the built-in studio read and edit any table without a secret key shipping to the browser, mirroring self-hosted Supabase Studio where the server holds the key.
279
281
 
280
282
  Elevation additionally requires the request to be same-origin (or carry no `Origin`), to arrive on a loopback socket, and to name a loopback host. Both locality checks are needed: the socket peer stops a machine on your network from spoofing `Host: localhost`, and the hostname stops DNS rebinding, where a hostile page re-resolves its own domain to `127.0.0.1` so the socket is genuinely loopback. `/auth/v1` is never elevated, and the Vite plugin only mounts `/rest/v1`.
281
283
 
282
284
  ```bash
283
- lite start --no-admin # off: keyless requests are no longer elevated
285
+ lite start --no-admin # loopback server, admin explicitly off
286
+ lite start --host # all interfaces, admin defaults off and warns
287
+ lite start --host --no-admin # all interfaces, explicit/silent admin opt-out
288
+ lite start --host --admin # all interfaces, local peers may use admin
284
289
  ```
285
290
  ```ts
286
291
  supalite({ admin: false }) # off for the Vite dev server
287
292
  ```
288
293
 
289
- Precedence is explicit flag > `options.server.admin` in `config.toml` > launcher default, so a config file can opt a project out but can never re-enable admin after `--no-admin`. Off by default when embedding `App` yourself and in `vite preview`.
294
+ On a loopback CLI listener, precedence is explicit flag > `options.server.admin` in `config.toml` > the on-by-default launcher value. A non-loopback `--host` forces admin off unless `--admin` is present, so config cannot silently re-enable it. Vite follows the same rule: an exposed Vite host requires `supalite({ admin: true })`; preview and embedded `App` instances remain off by default.
295
+
296
+ A sandbox or tunnel may publish even a loopback listener and make remote traffic appear local. Use `--no-admin` whenever another system forwards the port publicly; locality is not an authentication mechanism across a proxy.
290
297
 
291
298
  Because keyless requests skip RLS, test policies with a credential: `apikey: $PUBLISHABLE_KEY` for `anon`, and that **plus** `Authorization: Bearer $USER_JWT` for `authenticated`. A bearer token alone is a 401.
292
299
 
@@ -328,7 +335,7 @@ lite db diff -f tweak # diff schemas/ against applied migrations, emit
328
335
  lite db reset # drop everything, replay migrations, run seed
329
336
  ```
330
337
 
331
- Migrations and declarative schemas coexist: `lite dev` and the Vite plugin apply pending migrations on boot, then run the declarative diff. RLS policies authored in migration files are picked up at request time.
338
+ Migrations and declarative schemas coexist: `lite dev` and the Vite plugin apply pending migrations on boot, then run the declarative diff. `lite start` is migration-only: the ordered SQL recorded in `supabase_migrations.schema_migrations` is authoritative, so pending, edited, deleted, or declarative files do not change runtime metadata until their SQL is applied and recorded. On `sqlite-postgres`, deleting `supabase/.temp/.runtime-metadata-cache.json` is safe; startup rebuilds it from applied history and asks for `lite db reset` only when that history is invalid or does not match the live structure.
332
339
 
333
340
  ---
334
341
 
@@ -341,7 +348,7 @@ Two ways to get a client:
341
348
  ```typescript
342
349
  import { createClient } from "@supabase/supabase-js";
343
350
 
344
- const client = createClient("http://localhost:54321", "<anon-key>");
351
+ const client = createClient("http://127.0.0.1:54321", "<anon-key>");
345
352
 
346
353
  // database
347
354
  const { data } = await client.from("todos").select("*");
package/STATUS.md CHANGED
@@ -160,6 +160,8 @@ To keep declarative `db diff` state convergent, `ALTER EXTENSION <accepted-name>
160
160
  | `CREATE TYPE ... AS ENUM` | `TEXT` | `EnumField` | Emits allowed-value `CHECK (... IN (...))`. |
161
161
  | `<type>[]`, `_type` arrays | `TEXT` | `ArrayField` | Stores JSON arrays and delegates element serialization where possible. |
162
162
 
163
+ Mutating an existing enum on the SQLite path (`ALTER TYPE ... ADD VALUE` / `RENAME VALUE`) is not supported; the translator fails with a clear error that names the type and operation. Recreate the type with the full value set, or track the change in a manual migration.
164
+
163
165
  Unsupported PostgreSQL data types currently include `oid`, `xid`, `xid8`, `cid`, `money`, `citext`, `cidr`, `macaddr`, `macaddr8`, `bit`, `bit varying`, `varbit`, geometric types (`point`, `line`, `lseg`, `box`, `path`, `polygon`, `circle`), `xml`, text-search types (`tsvector`, `tsquery`), range and multirange types, `reg*` catalog reference types, internal types (`tid`, `pg_lsn`, `internal`), pseudo-types, and handler types.
164
166
 
165
167
  **Custom domains & data representations.** On the Postgres path (PGlite/PostgreSQL), a column typed as a domain that defines `CAST(<domain> AS json)` (the PostgREST "data representations" feature) renders through that cast on reads and `RETURNING` — e.g. a `unixtz` domain over `timestamptz` returns epoch seconds, a `monetary` domain over `numeric` returns a fixed-precision string. On the SQLite path the known representation types (`color`, `unixtz`, `isodate`, `monetary`, `bytea_b64`) are handled by built-in field shims. Mutating JSON values **into** a domain column (epoch→`timestamptz`, base64→`bytea`, decimal-string→`numeric`) is converted on the Postgres path via `buildDomainInputValue`. Not yet handled: domain formatting through cross-relation embeds. Postgres connections run with `TimeZone=UTC` by default so `timestamptz` rendering is deterministic regardless of the server's host timezone; override via the `postgresOptions.connection.TimeZone` connection option.
@@ -228,6 +230,7 @@ RLS is supported on all database backends. The enforcement strategy differs by d
228
230
  | Feature | SQLite | PGlite/Postgres | Notes |
229
231
  |------------------------------------------------|--------|-----------------|-------------------------------------------------------------|
230
232
  | `ENABLE ROW LEVEL SECURITY` | ✅ | ✅ | Default-deny when no policies match |
233
+ | `DISABLE ROW LEVEL SECURITY` | ✅ | ✅ | Policies remain stored but inert until RLS is enabled again |
231
234
  | `CREATE POLICY ... USING (expr)` | ✅ | ✅ | SQLite: merged into `WHERE`; Postgres: native |
232
235
  | `CREATE POLICY ... WITH CHECK (expr)` | ✅ | ✅ | SQLite: validated in-memory; Postgres: native |
233
236
  | `AS PERMISSIVE` (default) | ✅ | ✅ | Multiple permissive policies `OR`'d |
@@ -250,7 +253,7 @@ Each supported behavior is regression-covered against both backends in [`app/tes
250
253
 
251
254
  📖 See [`internal/docs/postgres/rls.md`](https://github.com/supabase-community/lite/blob/HEAD/internal/docs/postgres/rls.md) for the full RLS reference and behavior matrix.
252
255
 
253
- **SQLite metadata persistence (`sqlite-postgres`):** Policies live in translation metadata, not in the database file, so a command that never migrates would otherwise boot with RLS off. Every declarative schema translation, every `lite db reset`, and every migration apply in a migrations-only project writes the full deparse info to `supabase/.temp/.deparse-cache.json`. `lite start` restores that cache, verified against the migration-history fingerprint, the schema hash, member-level payload validation, and a cross-check that its RLS table list still covers everything the applied migration history enabled RLS on. When the cache is missing or stale, a migrations-only project recalculates from the applied migration history (never from unapplied migration files). A declarative project (`supabase/schemas/*.sql`) is instead refused outright, with no exceptions: `lite start` serves the migrations workflow and never applies schema files, so without a valid cache it exits with non-interactive recovery instructions rather than guessing. `lite db diff -f <name>` then `lite db reset` is the transition that establishes migration authority — the generated migration carries the schema's `ENABLE ROW LEVEL SECURITY` / `CREATE POLICY` statements, and `db reset` is destructive, so afterwards the migration-derived metadata describes the fresh database exactly and is always persisted. That is what makes the recovery loop terminate. A non-destructive `lite migration up` never persists over a declarative project's snapshot, because it cannot know what the live database still carries from a declarative apply. RLS metadata also follows the table lifecycle: an `ALTER TABLE ... RENAME TO` carries enforcement and its policies to the new name, and a `DROP TABLE` (even followed by a `CREATE TABLE` of the same name) drops them, matching Postgres. Embedded/programmatic use falls back to a per-table deny backstop. The cache file is safe to delete — it is regenerated by the next apply, and a boot that cannot rebuild it fails closed, never open. `pglite` / `postgres` (native RLS) and the bare `sqlite` driver are unaffected. See [LIMITATIONS.md](https://github.com/supabase-community/lite/blob/HEAD/LIMITATIONS.md) for the declarative-project workflow.
256
+ **SQLite runtime metadata (`sqlite-postgres`):** Policies, Postgres field types, enums, constraints, comments, variables, and relationship metadata are collected during translation and stored in the disposable runtime metadata cache at `supabase/.temp/.runtime-metadata-cache.json`. For `lite start`, the only authority is the ordered `{version, statements}` history in `supabase_migrations.schema_migrations`: applied files may be edited or deleted, pending files and `schemas/*.sql` are ignored, and a missing, corrupt, old, stale, or tampered cache is rebuilt automatically from the recorded SQL. Startup also replays the same history into an in-memory SQLite database and compares its raw structure with the live database; introspection is used only for equality checking, never to invent metadata. Invalid history or out-of-band structural DDL exits with a `lite db reset` hint. `lite migration up` and `lite db reset` refresh migration-derived metadata even when no new migration was applied. `lite dev` and the Vite plugin remain declarative: after applying migrations they replace the complete metadata payload from the applied declarative schema, including after watched migrations. Cache writes use a sibling temporary file plus atomic rename and are best-effort; a write failure leaves the previous file intact while the rebuilt in-memory payload remains usable. Embedded/programmatic use retains the history-based deny backstop as a last resort. `pglite` / `postgres` (native RLS) and bare `sqlite` are unaffected.
254
257
 
255
258
  **PGlite / PostgreSQL auto-setup:** When any table has `ENABLE ROW LEVEL SECURITY`, supalite creates `anon`, `authenticated`, and `service_role` roles (if missing; `service_role` uses `BYPASSRLS`) and grants default privileges on all tables and sequences in the relevant schemas. No manual `CREATE ROLE` or `GRANT` statements needed for these built-in roles.
256
259
 
@@ -540,7 +543,7 @@ Opaque `sb_publishable_*` / `sb_secret_*` keys, matching Supabase's current key
540
543
  | `/storage/v1` | ⚠️ | Transform-only, like upstream self-hosted Kong: keys map to roles when present, but a missing/invalid key never 401s at the gateway (public objects, signed URLs, S3 presigned flows stay keyless). Storage's own route auth still applies; a secret key satisfies storage's authed routes as `service_role` (including bypassing RLS-equivalent checks on SQLite) |
541
544
  | Secret key + browser `User-Agent` (`Mozilla/5.0`) | ✅ | Rejected (401), mirrors the hosted gateway's browser guard |
542
545
  | OpenAPI root (`GET /rest/v1/`) | ✅ | Requires the secret key: publishable → 403, secret → 200, mirroring upstream's admin-only ACL on that route (LITE-35) |
543
- | Local admin mode (`options.server.admin`) | ✅ | Local-dev only. A request with **no** credential (no `apikey` header/query, no `Authorization`) on `/rest/v1` or `/storage/v1` is served as `service_role`, so a browser studio can do admin work without a secret key reaching the browser. Also requires same-origin (or no `Origin`), a loopback **socket peer**, and a loopback **hostname** both halves: the peer check stops a LAN client spoofing `Host: localhost`, the hostname check stops DNS rebinding (where the socket really is loopback but `Host`/`Origin` are the attacker's domain). `/auth/v1` is never elevated. On by default for `lite dev`, `lite start` (`--no-admin` to disable) and the Vite dev server (`/rest/v1` only there the plugin doesn't mount `/storage/v1`); off for `vite preview` and for embedders. Precedence: explicit flag > `options.server.admin` in config > launcher default. Credentialed requests are unaffected, so `anon`/`authenticated` RLS stays testable (LITE-309) |
546
+ | Local admin mode (`options.server.admin`) | ✅ | Local-dev only. A request with **no** credential (no `apikey` header/query, no `Authorization`) on `/rest/v1` or `/storage/v1` is served as `service_role`, so browser Studio can do admin work without receiving a secret key. Elevation requires same-origin (or no `Origin`), a loopback **socket peer**, and a loopback **hostname**; transport adapters pass the peer through `App.fetch(request, { peerAddress })`, which fails closed when unavailable. `/auth/v1` is never elevated. `lite dev`/`lite start` bind `127.0.0.1` and default admin on; `--host [host]` exposes/selects an address and defaults it off unless `--admin` is explicit. Exposed Vite hosts likewise require `supalite({ admin: true })`; preview and embedders default off. Credentialed requests are never elevated (LITE-309, LITE-332) |
544
547
  | Secrets redacted from `/_system/config` / `/_system/info` | ✅ | `auth.secret_key` and `auth.jwt_secret` are masked in both responses |
545
548
  | Legacy JWT-as-apikey (`ANON_KEY`/`SERVICE_ROLE_KEY` HS256) | ❌ | Not supported |
546
549
  | `options.server.apiKeys: false` | ✅ | Disables enforcement entirely (embedders) |
@@ -665,6 +668,11 @@ Backend implementation in `app/src/storage/`. HTTP endpoints at `/storage/v1/*`.
665
668
  | `POST /object/upload/sign/:bucketId/*` | Sign | Create signed upload URL |
666
669
  | `GET /object/sign/:bucketId/*` | Download | Via signed URL token |
667
670
  | `PUT /object/upload/sign/:bucketId/*` | Upload | Via signed upload URL |
671
+ | `GET /status` | Health | No authentication required |
672
+
673
+ Storage metadata operations use the same verified JWT claims and RLS execution path as the Data API on SQLite, PGlite, and PostgreSQL. The `storage` schema is protected and omitted from the default PostgREST `api.schemas` list; `/storage/v1` remains the supported client surface, while explicitly adding `storage` enables direct metadata endpoints. On SQLite, the system schema declaration keeps `storage.buckets` and `storage.objects` RLS-enforced even when supplied runtime metadata omits them: no policies deny access and `service_role` still bypasses. Missing credentials run as `anon`; protected operations return a Storage-shaped 401 for invalid or expired bearer JWTs, while object `GET`, `HEAD`, and info tolerate invalid JWTs so public objects remain retrievable. Unauthenticated private retrieval returns `NoSuchBucket`, matching upstream; a verified `anon` JWT can still reach an `anon` `SELECT` policy. `service_role` bypasses RLS, and custom string roles are preserved for `TO role` policies. Uploads made with a verified JWT carrying a `sub` claim set both `owner_id` and the legacy `owner` field from that claim; `service_role` uploads remain ownerless. Public object retrieval through `GET`, `HEAD`, and info bypasses object `SELECT`; list, signing, copy, and move remain caller-scoped.
674
+
675
+ Bucket CRUD maps to matching policies on `storage.buckets`; PostgreSQL/PGlite bucket deletion runs trusted, row-locking existence and emptiness preflights plus the caller-scoped `DELETE` in one transaction. SQLite/D1 uses trusted preflights followed by one caller-scoped `DELETE`, with the bucket-object foreign key keeping that mutation race-safe. Object upload maps to `INSERT`; private download and info, list, and signing map to `SELECT`; remove maps to `DELETE`; move maps to source `SELECT` plus `UPDATE`; copy maps to source `SELECT` plus destination `INSERT`. Upsert additionally requires `SELECT` and `UPDATE`. Permission probes roll back before adapter writes, so denied mutations cannot change filesystem or S3 data. Public `GET`, `HEAD`, and info bypass `SELECT`, while signed URL redemption uses the authorization decision captured when the URL was created. The canonical `storage.foldername()`, `storage.filename()`, `storage.extension()`, `storage.operation()`, `storage.allow_only_operation()`, and `storage.allow_any_operation()` policy helpers work on every backend; `storage.operation()` preserves the complete upstream route identifier and supports comparison, pattern, membership, and null predicates with SQL null semantics.
668
676
 
669
677
  ### Storage Adapters
670
678
 
@@ -686,9 +694,6 @@ Backend implementation in `app/src/storage/`. HTTP endpoints at `/storage/v1/*`.
686
694
 
687
695
  | Feature | Notes |
688
696
  |--------------------------------|------------------------------------------------------------|
689
- | `/status` health endpoint | Oracle returns 200 with no auth |
690
- | Role-based access control | API keys now resolve `service_role`/`anon`/`authenticated` for storage's route-level auth (see [API Keys](#api-keys)); no per-object RLS policies yet |
691
- | RLS policies on storage tables | Per-user object access via row-level security |
692
697
  | Bucket list query params | `?search=`, `?limit=`, `?offset=` on `GET /bucket` |
693
698
  | S3-compatible protocol | `PUT/GET/DELETE` via S3 API paths (`/s3/`) |
694
699
  | TUS resumable uploads | `POST/PATCH/HEAD` on `/upload/resumable` |
@@ -698,9 +703,9 @@ Backend implementation in `app/src/storage/`. HTTP endpoints at `/storage/v1/*`.
698
703
 
699
704
  | Status | Count |
700
705
  |---------------------|------------------------------|
701
- | ✅ Endpoints | 20 |
706
+ | ✅ Endpoints | 20 supabase-js + health |
702
707
  | ✅ Adapters | 3 storage + 3 transformation |
703
- | 🔄 Missing features | 7 |
708
+ | 🔄 Missing features | 4 |
704
709
 
705
710
  ### Spec Test Results
706
711
 
@@ -708,21 +713,23 @@ Tests run via supabase-spec JSON test cases against Postgres (pgserve). Run: `cd
708
713
 
709
714
  | Category | Pass | Fail | Notes |
710
715
  |------------------|------|------|---------------------------------------------|
711
- | bucket_crud | 36 | 2 | Fails: anon/authenticated access |
712
- | object_upload | 19 | 3 | Fails: anon access, size limit, auth format |
713
- | access_control | -- | 48 | Needs RLS + role-based access |
716
+ | bucket_crud | 38 | 0 | Full current category |
717
+ | object_upload | 20 | 2 | Residual size-limit fixture and auth-envelope shape |
718
+ | access_control | 70 | 7 | Core authorization passes; residual fixture body bytes and a legacy null-owner assertion |
714
719
  | object_read | -- | 28 | Cascade from setup/access issues |
715
720
  | object_list | -- | 26 | Cascade from setup/access issues |
716
721
  | signed_urls | -- | 24 | Cascade from setup/access issues |
717
- | errors | -- | 27 | Auth error format (401 vs 400) |
722
+ | errors | 44 | 10 | Includes unrelated TUS, signed-URL shape, validation, and one legacy missing-auth validation shape; 4 skipped TUS steps |
718
723
  | object_move_copy | -- | 21 | Cascade from setup/access issues |
719
724
  | object_naming | -- | 15 | Cascade from setup/access issues |
720
725
  | user_metadata | -- | 10 | Cascade from setup/access issues |
721
726
  | file_types | -- | 8 | Cascade from setup/access issues |
722
- | health | -- | 3 | Missing `/status` endpoint |
727
+ | health | 6 | 0 | Full current category |
723
728
  | object_delete | -- | 3 | Cascade from setup/access issues |
724
729
 
725
- > Many failures cascade from a few root issues (missing role-based access, auth error format). Fixing role checking, `/status`, and error format would flip a large number of tests.
730
+ > Counts reflect the current vendored phenotype files, whose `access_control` category contains 77 setup/action assertions rather than the older 48-case snapshot. Remaining failures are tracked by their actual response-shape, fixture, or unrelated feature root cause; `storage_auth_policy` is no longer a skip category.
731
+ >
732
+ > `bun run test:spec:storage` completes with zero failures on PostgreSQL, PGlite, SQLite, and SQLite-Postgres. Its default report lists seven vendored expectation conflicts separately: five fixture-body byte mismatches, one legacy missing-auth validation shape, and one legacy null-owner assertion. `SKIP=0` retains the raw phenotype counts above.
726
733
 
727
734
  ---
728
735
 
@@ -767,7 +774,7 @@ Default command output is pipe-friendly: no global banner, and config/database-l
767
774
  | `db query` | ✅ | Renamed from top-level `exec`; supports `--remote`, `--config`, and stdin |
768
775
  | `db schema` | ✅ | `[lite]`: moved from top-level; `--diff` and `--sql` modes |
769
776
  | `db push` | 🔄 | Not registered; use `lite cloud deploy` |
770
- | `db reset` | ✅ | Replays migrations + seed; does not apply declarative schema_paths; clears `supabase/.temp` caches and always rewrites the deparse cache from the replayed migrations — the reset is destructive, so migration state (RLS included) becomes the authoritative state, and declarative schemas are only in the database if you generated a migration from them first (`lite db diff -f <name>`) |
777
+ | `db reset` | ✅ | Replays migrations + seed; does not apply declarative schema_paths; clears `supabase/.temp` caches and always rewrites the runtime metadata cache from the replayed migrations — the reset is destructive, so migration state (RLS included) becomes the authoritative state, and declarative schemas are only in the database if you generated a migration from them first (`lite db diff -f <name>`) |
771
778
  | `db pull`, `db dump`, `db lint`, `db advisors` | 🔄 | Not registered |
772
779
  | `db start` | 🚫 | Not applicable; in-process, no separate DB start |
773
780
 
@@ -776,7 +783,7 @@ Default command output is pipe-friendly: no global banner, and config/database-l
776
783
  | Command | Status | Notes |
777
784
  |---------------------|--------|--------------------------------------------------------|
778
785
  | `migration new` | ✅ | Create an empty migration file in `supabase/migrations/` |
779
- | `migration up` | ✅ | Apply pending migrations; `--dry-run` lists without applying |
786
+ | `migration up` | ✅ | Apply pending migrations; `--dry-run` lists without applying. On `sqlite-postgres`, refreshes complete runtime metadata from recorded history even on a no-op run. |
780
787
  | `migration list` | ✅ | Show applied vs pending migrations |
781
788
  | `migration down`, `migration repair`, `migration squash`, `migration fetch` | 🔄 | Not registered |
782
789
 
@@ -848,8 +855,8 @@ Mirrors upstream behavior documented in [`internal/docs/cli/environment.md`](htt
848
855
  ### Vite plugin scope
849
856
 
850
857
  - **Active during `vite` / `vite dev` and `vite preview`.** `vite dev` watches `schemas/*.sql` for hot-reload; `vite preview` mounts the API and runs boot migrations but does **not** watch schemas (it simulates production). `vite build` and any standalone production server do **not** mount the API — use a real backend (`lite start`, hosted Supabase, or equivalent) there.
851
- - **Same-process by design.** The plugin mounts `/auth/v1`, `/rest/v1`, and `/_system` on the Vite dev server. `/storage/v1` is not mounted by default — add it to `prefixes` if you need it. Do not run `lite dev` or `lite start` alongside both bind the API and will collide on port.
852
- - **Admin mode on in dev, never in preview.** `vite`/`vite dev` default to `admin: true` (keyless same-origin loopback `/rest/v1` requests run as `service_role`); `configurePreviewServer` forces it off. Override with `supalite({ admin: false })`. See [API Keys](#api-keys).
858
+ - **Same-process by design.** The plugin mounts `/auth/v1`, `/rest/v1`, and `/_system` on the Vite dev server. `/storage/v1` is not mounted by default — add it to `prefixes` if you need it. Run only one backend per project: never combine the plugin with `lite dev` or `lite start`, and never run both CLI backends together.
859
+ - **Admin mode on for loopback dev, never in preview.** `vite`/`vite dev` default to `admin: true` only when Vite binds a loopback host. `vite --host` and other non-loopback hosts default it off and require `supalite({ admin: true })` to opt in; `configurePreviewServer` always forces it off. See [API Keys](#api-keys).
853
860
  - **Env-var injection.** The plugin's `config()` hook injects `VITE_SUPABASE_URL` (the current origin) and a dev `VITE_SUPABASE_ANON_KEY`, so `createClient(import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_ANON_KEY)` works with no `.env`. A user-provided `.env` overrides either value.
854
861
 
855
862
  ---
@@ -872,9 +879,9 @@ See [UPGRADE.md](https://github.com/supabase-community/lite/blob/HEAD/UPGRADE.md
872
879
 
873
880
  | Test Suite | Passing | Skipped | Failed | Assertions | Files |
874
881
  |------------|-------------------|-------------|-----------------|-------------------|----------------|
875
- | App | **2,939 passing** | 486 skipped | 0 failed | 19,345 assertions | 204 test files |
882
+ | App | **3,385 passing** | 486 skipped | 0 failed | 20,741 assertions | 228 test files |
876
883
  | App (vitest: node + browser + D1 + DO + KV) | **65 passing** | 0 skipped | 0 failed | — | 5 test files |
877
- | Repo | **3,967 passing** | 540 skipped | 0 failed | 33,127 assertions | 209 test files |
884
+ | Repo | **4,413 passing** | 540 skipped | 0 failed | 34,523 assertions | 233 test files |
878
885
 
879
886
  Latest `cd app && bun test`, `cd app && bun run vitest`, and root `bun test --recursive` completed with zero failures.
880
887