@supabase/lite 0.9.0 → 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 (64) hide show
  1. package/FEATURES.md +176 -0
  2. package/LIMITATIONS.md +9 -6
  3. package/PATTERNS.md +66 -6
  4. package/README.md +16 -8
  5. package/STATUS.md +33 -20
  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 +17 -17
  13. package/dist/index.d.ts +116 -40
  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 +627 -31
  25. package/dist/vite/index.js +2 -2
  26. package/docs/auth/email.mdx +214 -0
  27. package/docs/auth/not-supported.mdx +57 -0
  28. package/docs/auth/overview.mdx +52 -0
  29. package/docs/auth/supported-flows.mdx +120 -0
  30. package/docs/cli/overview.mdx +112 -0
  31. package/docs/cli/telemetry.mdx +34 -0
  32. package/docs/compatibility.mdx +115 -0
  33. package/docs/database/backends.mdx +118 -0
  34. package/docs/database/data-api.mdx +90 -0
  35. package/docs/database/functions-triggers.mdx +93 -0
  36. package/docs/database/migrations.mdx +95 -0
  37. package/docs/database/overview.mdx +66 -0
  38. package/docs/database/postgres-sqlite-translation.mdx +130 -0
  39. package/docs/database/rls.mdx +161 -0
  40. package/docs/database/schemas.mdx +58 -0
  41. package/docs/index.mdx +49 -0
  42. package/docs/integrations/embedded.mdx +100 -0
  43. package/docs/integrations/frameworks.mdx +83 -0
  44. package/docs/integrations/vite.mdx +87 -0
  45. package/docs/llms.txt +52 -0
  46. package/docs/other/edge-functions.mdx +34 -0
  47. package/docs/other/realtime.mdx +22 -0
  48. package/docs/quickstart.mdx +150 -0
  49. package/docs/running.mdx +127 -0
  50. package/docs/storage/adapters.mdx +75 -0
  51. package/docs/storage/limitations.mdx +21 -0
  52. package/docs/storage/overview.mdx +88 -0
  53. package/docs/upgrade.mdx +108 -0
  54. package/package.json +5 -1
  55. package/skills/supalite/SKILL.md +6 -4
  56. package/dist/static/assets/main-1bwWb_1q.js +0 -40996
  57. package/dist/static/assets/main-BDsRycsc.css +0 -4045
  58. package/dist/static/fonts/CustomFont-Black.woff2 +0 -0
  59. package/dist/static/fonts/CustomFont-BlackItalic.woff2 +0 -0
  60. package/dist/static/fonts/CustomFont-Bold.woff2 +0 -0
  61. package/dist/static/fonts/CustomFont-BoldItalic.woff2 +0 -0
  62. package/dist/static/fonts/CustomFont-Book.woff2 +0 -0
  63. package/dist/static/fonts/CustomFont-BookItalic.woff2 +0 -0
  64. package/dist/static/fonts/CustomFont-Medium.woff2 +0 -0
package/FEATURES.md ADDED
@@ -0,0 +1,176 @@
1
+ # Feature Parity
2
+
3
+ Capability-level view of how @supabase/lite maps to Supabase. [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md) is the per-method API matrix; this document groups features into capabilities and, for anything not yet supported, adds two columns: an **Effort** estimate and the **Blocker** reason.
4
+
5
+ Compatibility is measured from the `@supabase/supabase-js` surface; direct Postgres wire-protocol access is not a target.
6
+
7
+ ## Legend
8
+
9
+ Status (same icons as [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md)):
10
+
11
+ | Icon | Meaning |
12
+ |------|---------|
13
+ | ✅ | Supported end-to-end |
14
+ | ⚠️ | Partial, works with caveats |
15
+ | 🔄 | Planned / not built yet |
16
+ | ❌ | Not feasible on this path |
17
+ | ⚫ | N/A (client-side only, or no backend needed) |
18
+
19
+ For rows that are not fully ✅, two columns estimate the cost of closing the gap.
20
+
21
+ **Effort** (T-shirt size, relative engineering cost):
22
+
23
+ | Size | Rough meaning |
24
+ |------|---------------|
25
+ | `S` | Small, self-contained change |
26
+ | `M` | A feature's worth of work |
27
+ | `L` | Large, multi-part or new subsystem |
28
+ | `XL` | Major subsystem or new runtime |
29
+
30
+ **Blocker** (why it isn't trivial):
31
+
32
+ | Blocker | Meaning |
33
+ |---------|---------|
34
+ | `platform-limited` | SQLite lacks the primitive (range types, stored procedures, logical replication); any emulation is partial/lossy |
35
+ | `extension-required` | Needs an extension not loadable across all targets (e.g. pgvector / sqlite-vec, full REGEXP) |
36
+ | `external-service` | Needs an outside dependency (SMTP, SMS provider, OAuth provider apps) |
37
+
38
+ `-` marks not-applicable: both columns on ✅ rows, and the Blocker column on planned features with no hard blocker (just ordinary backlog work).
39
+
40
+ > **The SQLite vs Postgres split matters.** @supabase/lite runs on SQLite backends (`node:sqlite`, `bun:sqlite`, WASM, Cloudflare D1/DO) and on Postgres backends (PGlite, PostgreSQL, Supabase Cloud). Many "not feasible on SQLite" features work natively on the Postgres path. The Data tables below carry both columns; Auth and Storage are backend services that behave the same across drivers. One exception: on the Cloudflare D1 backend, multi-statement Auth transaction spans (OAuth callback/token writes, email-change and other OTP verification) run best-effort without a wrapping transaction (D1 has no callback transaction API); single-statement guards still prevent auth-code/state reuse. All other backends, including Durable Objects, are fully transactional.
41
+
42
+ ---
43
+
44
+ ## Data API (PostgREST)
45
+
46
+ CRUD, filtering, embedding, and RLS over your tables. This is the most complete surface. Method-level detail and per-operator notes live in [STATUS.md → Database API](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#database-api-postgrest-compatible).
47
+
48
+ | Capability | SQLite | Postgres | Effort | Blocker | Notes |
49
+ |------------|:------:|:--------:|:------:|:-------:|-------|
50
+ | select / insert / update / delete / upsert | ✅ | ✅ | - | - | Batch ops, `ON CONFLICT`, `RETURNING` |
51
+ | Comparison filters (eq, neq, gt/gte/lt/lte, in, is, …) | ✅ | ✅ | - | - | 10/10 |
52
+ | Pattern matching (like, ilike, + quantified) | ✅ | ✅ | - | - | 6/6 |
53
+ | Ordering, limit, range/offset, single/maybeSingle | ✅ | ✅ | - | - | NULLS FIRST/LAST |
54
+ | Logical (or, and, not, match, filter) | ✅ | ✅ | - | - | |
55
+ | JSON path (`->`, `->>`) in select/order/where | ✅ | ✅ | - | - | |
56
+ | Resource embedding (FK joins, `!inner`, spreads, nested, aggregates) | ✅ | ✅ | - | - | |
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
+ | 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`, `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
+ | CSV input/output (`csv()`) | ✅ | ✅ | - | - | Via `text/csv` Accept/Content-Type |
61
+ | Response utilities (`abortSignal`, `setHeader`, `throwOnError`, `maxAffected`) | ✅ | ✅ | - | - | |
62
+ | CORS / `OPTIONS` preflight | ✅ | ✅ | - | - | Server-wide on `/auth`, `/rest`, `/storage`; preflight answered before auth, origin `*`, exposes `Content-Range`. |
63
+ | PostgreSQL extension declarations | ⚠️ | ⚠️ | `XL` | `extension-required` | SQLite treats `plpgsql`, `pgcrypto`, and `uuid-ossp` declarations plus their declarative schema/drop reconciliation as compatibility no-ops; unsupported creates and mutations fail during translation. PGlite runs only its configured extensions; PostgreSQL delegates to the server. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#extension-statements). |
64
+ | Full-text search (fts/plfts/phfts/wfts) | ⚠️ | ✅ | `L` | `platform-limited` | SQLite uses a LIKE-based lexeme approximation, not FTS5 ranking or tsvector semantics. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#full-text-search). |
65
+ | Regex (`regexMatch`/`regexIMatch`) | ⚠️ | ✅ | `M` | `extension-required` | Only simple anchored/literal patterns today. Full regex needs a registered `REGEXP` function, available on `node:`/`bun:sqlite` but not WASM/D1. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#regex). |
66
+ | Array/JSON containment (`contains`, `containedBy`, `overlaps`) | ⚠️ | ✅ | `M` | `platform-limited` | Scalar arrays + shallow objects work via `json_each`/`json_extract`; arrays-of-objects and nested objects do not. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#array--json-filters). |
67
+ | Custom domain types / data representations | ⚠️ | ⚠️ | `M` | `platform-limited` | Postgres: domain columns with `CAST(… AS json)` render via that cast on reads/`RETURNING`, and JSON values mutated **into** a domain (epoch/base64/decimal) are converted; sessions run in UTC by default (configurable). SQLite: built-in shims for the known data-rep types. Remaining gap: domain formatting through cross-relation embeds. See [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#translated-field-types). |
68
+ | `explain()` | ⚠️ | ⚠️ | `M` | `-` | Returns compiled SQL, not a real query plan. |
69
+ | OpenAPI / schema spec at `/rest/v1/` | ⚠️ | ⚠️ | `M` | `-` | A mock spec is served; not generated from live schema. |
70
+ | `count=planned` / `count=estimated` | ⚠️ | ⚠️ | `S` | `-` | Parsed but treated as exact on SQLite. |
71
+ | Quantified comparisons (`eq(any)`, `gt(all)`, …) | 🔄 | ✅ | `M` | `-` | No SQLite array type, but emulatable by expanding to `OR`/`AND` chains over the literal list. |
72
+ | Embedded dotted-path filters (`.eq('rel.col', v)`) | 🔄 | ✅ | `M` | `-` | Not rewritten into the embedded subquery on SQLite. Workaround in [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#embedded-filters). |
73
+ | `rpc()` (stored procedures) | ❌ | ✅ | `XL` | `platform-limited` | Postgres RPC depends on `CREATE FUNCTION … LANGUAGE sql/plpgsql`. SQLite has no stored-procedure model, and the project deliberately avoids a parallel JS-function registry. Use an HTTP endpoint, or a Postgres driver. Supports PostgREST function response control: `response.status`/`response.headers` GUCs override the HTTP status/headers (invalid → PGRST112/PGRST111), GET/HEAD calls run in a read-only transaction (a mutating function raises 25006 → 405), and `request.method`/`path`/`headers` GUCs are injected for functions that read the HTTP context. A function returning a media-type **domain** (e.g. `returns "text/plain"`/`"image/png"`/`"*/*"`) and matched by the request `Accept` emits the raw scalar (text or bytes) with that `Content-Type` instead of JSON; the `response.headers` GUC can override the Content-Type. Gap: table custom-aggregate media handlers. |
74
+ | Computed columns & relationships (PostgREST) | ❌ | ✅ | `L` | `platform-limited` | Postgres: function-based computed columns `fn(row)→scalar` (select + filter, incl. FTS) and computed relationships `fn(row)→[SETOF] table` (embed with no FK, incl. FK/o2o override, self-joins, nested, and on `SETOF` RPC results). Resolved from introspected `pg_proc` metadata, schema-scoped. SQLite has no stored functions. Gap: computed columns on an RPC result (`*_on_rpc`). |
75
+ | Composite-type columns (`CREATE TYPE … AS (…)`) | ❌ | ✅ | `M` | `platform-limited` | Postgres: composite-field access in select/order/filter (`->`/`->>` → `(col).field`) and composite columns rendered as JSON objects (`to_jsonb`) on reads and `RETURNING`. SQLite has no composite types (spec fixtures store the column as JSON there). |
76
+ | Partitioned tables (declarative partitioning) | ❌ | ✅ | `M` | `platform-limited` | Postgres: the partitioned parent is queryable; individual partitions are hidden from the relation cache like PostgREST — direct access → 404, embedding a partition → no-relationship error with a "did you mean '<parent>'" hint. SQLite has no partitioning. |
77
+ | Range operators (`rangeGt`, `rangeAdjacent`, …) | ❌ | ✅ | `XL` | `platform-limited` | No range types in SQLite. |
78
+ | `schema()` (multi-schema) | ❌ | ✅ | `L` | `platform-limited` | SQLite is single-schema. |
79
+ | `geojson()` | ❌ | ❌ | `XL` | `extension-required` | Needs SpatiaLite (SQLite) or PostGIS (Postgres). |
80
+ | `rollback()` (Prefer tx-rollback) | ❌ | ❌ | `S` | `-` | PostgREST-specific; not implemented on either path. |
81
+ | Vector / semantic search (`pgvector`) | ❌ | ❌ | `XL` | `extension-required` | Hosted/CLI Supabase ships `pgvector`; lite does not. `sqlite-vec` is a loadable extension unavailable on WASM/D1, and the PGlite/`postgres` drivers here don't enable `pgvector`. Point users at hosted Supabase for embeddings. |
82
+
83
+ ---
84
+
85
+ ## Auth (GoTrue)
86
+
87
+ GoTrue-compatible endpoints at `/auth/v1/*`, backed by `app/src/auth/`. Email/password, OTP, sessions, RLS auth context, and OAuth sign-in (`github`/`google`) are solid; other OAuth providers, admin, and MFA are the main gaps. Method list in [STATUS.md → Auth API](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#auth-api-gotrue-compatible).
88
+
89
+ | Capability | Status | Effort | Blocker | Notes |
90
+ |------------|:------:|:------:|:-------:|-------|
91
+ | Email + password sign-up / sign-in | ✅ | - | - | bcrypt, optional metadata, confirmation flow |
92
+ | Magic link / email OTP (`signInWithOtp`, `verifyOtp`) | ✅ | - | - | signup, magiclink, recovery, email_change, reauthentication; numeric code + `token_hash` both verify against the DB (durable on Workers), `otp_expiry`/`otp_length` honored |
93
+ | JWT issuance, refresh-token rotation, sessions | ✅ | - | - | Revocation, reuse handling, timebox/inactivity expiry |
94
+ | Password recovery (`recover`, `resend`, `reauthenticate`) | ✅ | - | - | |
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
+ | RLS auth context (`auth.uid()`, `auth.role()`, `auth.jwt()`) | ✅ | - | - | Shared with the Data API |
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 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
+ | 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
+ | 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
+ | 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. |
102
+ | Anonymous sign-in (`signInAnonymously`) | 🔄 | `S` | `-` | Create a credential-less user + session. |
103
+ | Admin user API (create/list/get/update/delete, generateLink, signOut) | 🔄 | `M` | `-` | Mostly CRUD over `auth.users`; ~69 skipped spec cases. |
104
+ | Identity linking (`linkIdentity` / `unlinkIdentity`) | 🔄 | `M` | `-` | Automatic linking on OAuth sign-in (verified-email match) already works; the explicit link/unlink API is still planned. Unlink is `S`. |
105
+ | Phone / SMS OTP | 🔄 | `M` | `external-service` | Needs an SMS provider (e.g. Twilio). |
106
+ | MFA / TOTP (enroll, challenge, verify) | 🔄 | `M` | `-` | TOTP via a library (e.g. otplib) + QR; AAL tracking. |
107
+ | `getClaims()` / JWKS verification | 🔄 | `M` | `-` | Fetch + cache JWKS, asymmetric verify. |
108
+ | OIDC ID-token sign-in (`signInWithIdToken`) | 🔄 | `M` | `-` | Validate provider ID token + nonce; lighter than full OAuth handshake. |
109
+ | WebAuthn / passkeys (`mfa.webauthn.*`) | 🔄 | `XL` | `-` | FIDO2/CTAP2 registration + assertion ceremonies; credential storage, signature verification. |
110
+ | OAuth 2.1 authorization server + client admin | 🔄 | `L` | `-` | Lite acting as an OAuth provider: authorization endpoint + client management. |
111
+ | Web3 sign-in (`signInWithWeb3`, SIWE/SIWS) | 🔄 | `L` | `-` | Wallet message + signature verification. |
112
+ | SAML / SSO (`signInWithSSO`) | 🔄 | `XL` | `-` | Enterprise SSO: XML signing, IdP metadata exchange, assertion validation. |
113
+
114
+ ---
115
+
116
+ ## Storage
117
+
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
+
120
+ | Capability | Status | Effort | Blocker | Notes |
121
+ |------------|:------:|:------:|:-------:|-------|
122
+ | Bucket CRUD (create, list, get, update, delete, empty) | ✅ | - | - | Public/private, file-size limits, allowed MIME types |
123
+ | Object CRUD (upload, replace, download, list, move, copy, remove, info, exists) | ✅ | - | - | Multipart, upsert via `x-upsert`, range requests |
124
+ | Public buckets & public URLs | ✅ | - | - | |
125
+ | Signed URLs (download, batch, upload) | ✅ | - | - | JWT-signed, configurable TTL |
126
+ | Image transformations (resize, format, quality) | ✅ | - | - | Sharp (buffer) and Cloudflare (URL) adapters |
127
+ | Storage backends | ✅ | - | - | Filesystem + S3-compatible (AWS S3, MinIO, R2) |
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
+ | Bucket-list query params (`search`/`limit`/`offset`) | 🔄 | `S` | `-` | |
132
+ | TUS resumable uploads | 🔄 | `L` | `-` | `POST/PATCH/HEAD /upload/resumable`; spec cases exist, endpoints don't. |
133
+ | S3-compatible protocol paths (`/s3/`) | 🔄 | `L` | `-` | |
134
+ | Webhooks (ObjectCreated/ObjectRemoved) | 🔄 | `M` | `-` | |
135
+
136
+ ---
137
+
138
+ ## Edge Functions
139
+
140
+ Planned, not started. Only config schemas exist (`app/src/config/functions.ts`, `app/src/config/edge_runtime.ts`): per-function `enabled`, `verify_jwt`, `import_map`, `entrypoint`, and runtime `policy`/`inspector_port`. No `/functions/v1/*` routes, no runtime.
141
+
142
+ The client surface is small: `@supabase/functions-js` is essentially one method, `invoke(name, opts)` (plus `constructor`/`setAuth`). The cost is almost entirely server-side, and splits into two layers. **Request/response plumbing** (routing, auth injection, body/content-type handling, streaming, region headers, error classification) is ordinary HTTP work. **The runtime** (executing user TypeScript) is the hard part. Supabase uses a custom Rust/Deno `edge-runtime` with V8 isolates and a main/user worker model. Lite's path is a Web-API-compatible runtime adapter so you can plug in whichever executor fits the host (`vm`/`vm2`/`sval`, dynamic workers, Bun, Deno). This likely depends on Supabase Workers landing, since that runtime targets Node.js.
143
+
144
+ | Capability | Status | Effort | Blocker | Notes |
145
+ |------------|:------:|:------:|:-------:|-------|
146
+ | Invoke routing (`/functions/v1/{name}`), auth + `apikey` injection | 🔄 | `M` | `-` | Proxy mode: forward to an external runtime; reuses existing Hono server + auth. |
147
+ | Per-function JWT verification (`verify_jwt`) | 🔄 | `S` | `-` | Config schema + auth system already exist. |
148
+ | Body / Content-Type handling + response dispatch | 🔄 | `M` | `-` | json / blob / text / formData; auto Content-Type detection. |
149
+ | Streaming responses (SSE passthrough) | 🔄 | `M` | `-` | Return the raw `Response` for `text/event-stream`. |
150
+ | Region routing (`x-region` / `forceFunctionRegion`) | 🔄 | `S` | `-` | Header + query passthrough; no real multi-region locally. |
151
+ | Pluggable runtime (execute user code) | 🔄 | `XL` | `platform-limited` | Web-API-compatible runtime adapter with pluggable executors (`vm`/`vm2`/`sval`, dynamic workers, Bun, Deno) + worker lifecycle (`oneshot`/`per_worker`). Likely depends on Supabase Workers (Node.js-targeted) landing. |
152
+ | Local `functions serve` (dev) + file-watch reload | 🔄 | `L` | `platform-limited` | Depends on the runtime above. |
153
+ | Deployment / management API (deploy, list, get, update, delete, body) | 🔄 | `L` | `-` | ESZip bundling + function registry; lite needs its own tracking (SQLite/config). |
154
+ | Secrets / env injection (`Deno.env`) | 🔄 | `S` | `-` | Inject `SUPABASE_URL`/keys + user secrets. |
155
+ | Database Webhooks (`supabase_functions.hooks`, `http_request` trigger) | 🔄 | `M` | `platform-limited` | Distinct feature: in Postgres, row triggers fire HTTP via `pg_net`. Lite can hook the same events at the app layer (mutations flow through the Data API) and fetch out, avoiding `pg_net`. Caveat: misses out-of-band direct DB writes. |
156
+
157
+ ---
158
+
159
+ ## Realtime
160
+
161
+ Planned, not started. Only a config schema exists (`app/src/config/realtime.ts`: `enabled`, `ip_version`, `max_header_length`). No WebSocket server, no channels.
162
+
163
+ The `@supabase/realtime-js` surface is one channel abstraction (`supabase.channel(...).on(...).subscribe()`) carrying three feature families. **Broadcast** and **Presence** are pure pub/sub over a WebSocket and don't touch the database, so they're tractable once the socket transport exists. **Postgres Changes** is the harder one: Supabase streams it from Postgres logical replication (WAL), which SQLite does not have. Rather than reach for an extension, lite can capture changes at the app layer: every mutation flows through the Data API, so change events can be emitted from there without DB triggers or replication. The tradeoff is that this only sees writes made through lite, not out-of-band writes straight to the database.
164
+
165
+ | Capability | Status | Effort | Blocker | Notes |
166
+ |------------|:------:|:------:|:-------:|-------|
167
+ | WebSocket transport + channel protocol (`/realtime/v1`) | 🔄 | `M` | `-` | Foundation: socket server, Phoenix-style channel join/leave, heartbeat. |
168
+ | Broadcast (ephemeral pub/sub messages) | 🔄 | `M` | `-` | Independent of the database; fan-out to channel subscribers. |
169
+ | Presence (track / sync / join / leave state) | 🔄 | `M` | `-` | Built on the same channel + a per-channel state CRDT. |
170
+ | Channel authorization (private channels / RLS) | 🔄 | `M` | `-` | Reuse the existing RLS engine + JWT to gate join and message access. |
171
+ | Postgres Changes / CDC (`postgres_changes` on INSERT/UPDATE/DELETE) | 🔄 | `L` | `platform-limited` | No SQLite logical replication. Capture changes at the app layer (all mutations flow through the Data API), avoiding extensions and DB triggers. Caveat: only catches writes made through lite, not out-of-band direct DB writes. |
172
+ | Broadcast-from-database (`realtime.broadcast_changes`) | 🔄 | `L` | `platform-limited` | Same app-layer change capture as CDC; emits broadcast messages from row changes. |
173
+
174
+ ---
175
+
176
+ > Maintenance: update this file alongside [STATUS.md](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md) whenever feature support changes. Statuses here must agree with STATUS.md; this doc only adds the effort/blocker lens, it never overrides a support verdict. See [AGENTS.md](https://github.com/supabase-community/lite/blob/HEAD/AGENTS.md).
package/LIMITATIONS.md CHANGED
@@ -6,12 +6,14 @@ Anchors below point to the corresponding STATUS.md section. If a limitation here
6
6
 
7
7
  ## SQL / DDL (SQLite path)
8
8
 
9
+ - Extension declarations are no-ops only for `plpgsql`, `pgcrypto`, and `uuid-ossp`; schema moves and drops for those names are ignored for declarative convergence, while unsupported names and other mutations fail. Only the documented PL/pgSQL trigger subset and UUID v4 defaults are emulated. See [Extension Statements](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#extension-statements).
9
10
  - `DEFAULT auth.uid()` (and `auth.role()`, `auth.email()`, `auth.jwt()`) on columns → not supported. Drop the default, pass `user_id` from the client, rely on RLS `WITH CHECK`. See [Column Defaults](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#column-defaults).
10
11
  - Subquery `WITH CHECK` on `INSERT` (`user_id IN (SELECT …)`, `EXISTS (…)`) → throws. Denormalise the owning column. See [RLS known limitations](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#row-level-security-rls).
11
12
  - Scalar functions outside the allow-list in `DEFAULT` or `CHECK` (`trim`, `btrim`, `length`, `lower`, `upper`, …) → `Function call "<name>" not supported`. Use literals or move the check to the app layer. See [Column Defaults](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#column-defaults) and [CHECK constraint functions](https://github.com/supabase-community/lite/blob/HEAD/STATUS.md#check-constraint-functions).
12
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).
13
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).
14
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).
15
17
 
16
18
  ## supabase-js (SQLite path)
17
19
 
@@ -39,12 +41,13 @@ Anchors below point to the corresponding STATUS.md section. If a limitation here
39
41
  ## Runtime / dev
40
42
 
41
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).
42
- - 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).
43
- - `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).
44
- - `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).
45
- - **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).
46
- - 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`.
47
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).
48
51
 
49
52
  ## Postgres backends (pglite, postgres)
50
53
 
@@ -61,7 +64,7 @@ Common ways code goes wrong against supalite. The fix for each is the correspond
61
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.
62
65
  - Don't call `rpc()` on the SQLite path. Run a regular HTTP endpoint, or switch the driver to `pglite` / `postgres` in `config.toml`.
63
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.
64
- - 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.
65
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.
66
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`.
67
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
@@ -24,6 +24,7 @@ If you're an agent working on a supalite project, read these first:
24
24
 
25
25
  - [`LIMITATIONS.md`](https://github.com/supabase-community/lite/blob/HEAD/LIMITATIONS.md) — what's unsupported / partial, plus anti-patterns. Token-efficient cheat sheet.
26
26
  - [`PATTERNS.md`](https://github.com/supabase-community/lite/blob/HEAD/PATTERNS.md) — canonical recipes (per-user RLS, embedded filter workarounds, custom server logic, Vite cold start, triggers).
27
+ - The full product documentation, shipped in the package at `node_modules/@supabase/lite/docs/` (index: `llms.txt`) and published at [docs.lite.dev](https://docs.lite.dev).
27
28
 
28
29
  The npm package ships a [`supalite` skill](https://github.com/supabase-community/lite/blob/HEAD/skills/supalite/SKILL.md). After `npm install`, link it into your agent's skills dir so the cold-start checklist, routing rule, and limitation pointers trigger automatically:
29
30
 
@@ -73,14 +74,14 @@ lite init # scaffold supabase/ directory
73
74
  lite dev # start server with schema hot-reload
74
75
  ```
75
76
 
76
- 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:
77
78
 
78
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:
79
80
 
80
81
  ```typescript
81
82
  import { createClient } from "@supabase/supabase-js";
82
83
 
83
- const supabase = createClient("http://localhost:54321", "<sb_publishable_...>");
84
+ const supabase = createClient("http://127.0.0.1:54321", "<sb_publishable_...>");
84
85
  const { data } = await supabase.from("todos").select("*");
85
86
  ```
86
87
 
@@ -98,7 +99,7 @@ Edit `supabase/schemas/schema.sql` and the dev server re-applies the schema auto
98
99
  | Non-Vite app, want auto schema-reload | `lite dev` | Separate process. Watches `schemas/*.sql`, re-applies on change. |
99
100
  | Non-Vite app, manual control / CI / prod-like | `lite start` | Separate process. No watch, no auto-migrate. |
100
101
 
101
- > 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.
102
103
 
103
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).
104
105
 
@@ -122,6 +123,8 @@ to show details like the config file and database location on stderr.
122
123
  | `generate-keys` | (Re)generate the publishable/secret API key pair, upsert `.env` |
123
124
  | `dev` | Start server + watch `schemas/*.sql`, auto-apply on change |
124
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.
125
128
  | `db schema` | Print current DB schema; `--diff` compares vs `schemas/*.sql` |
126
129
  | `db diff` | Emit a new pg-DDL migration from the declarative schema diff |
127
130
  | `db translate` | Translate Postgres SQL to this project's backend dialect (arg or stdin) |
@@ -274,18 +277,23 @@ Lost or rotating keys: `lite generate-keys` mints a fresh pair and upserts `.env
274
277
 
275
278
  ### Admin mode (local only)
276
279
 
277
- `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.
278
281
 
279
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`.
280
283
 
281
284
  ```bash
282
- 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
283
289
  ```
284
290
  ```ts
285
291
  supalite({ admin: false }) # off for the Vite dev server
286
292
  ```
287
293
 
288
- 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.
289
297
 
290
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.
291
299
 
@@ -327,7 +335,7 @@ lite db diff -f tweak # diff schemas/ against applied migrations, emit
327
335
  lite db reset # drop everything, replay migrations, run seed
328
336
  ```
329
337
 
330
- 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.
331
339
 
332
340
  ---
333
341
 
@@ -340,7 +348,7 @@ Two ways to get a client:
340
348
  ```typescript
341
349
  import { createClient } from "@supabase/supabase-js";
342
350
 
343
- const client = createClient("http://localhost:54321", "<anon-key>");
351
+ const client = createClient("http://127.0.0.1:54321", "<anon-key>");
344
352
 
345
353
  // database
346
354
  const { data } = await client.from("todos").select("*");