@supabase/lite 0.9.1-next.1 → 0.10.0
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.
- package/FEATURES.md +6 -6
- package/LIMITATIONS.md +8 -6
- package/PATTERNS.md +66 -6
- package/README.md +15 -8
- package/STATUS.md +26 -19
- package/dist/{Connection-ZWTDByQ5.d.ts → Connection-f_d5HhQ0.d.ts} +301 -293
- package/dist/cli/index.js +127 -124
- package/dist/cli/lib.d.ts +1 -12
- package/dist/cli/lib.js +44 -39
- package/dist/db/fallback.d.ts +1 -1
- package/dist/db/postgres/PostgresConnection.js +18 -18
- package/dist/db/postgres/pglite/PgliteConnection.js +4 -4
- package/dist/index.d.ts +90 -38
- package/dist/index.js +181 -73
- package/dist/static/.vite/manifest.json +34 -2
- package/dist/static/assets/InterVariable-Dx4kXJAl.woff2 +0 -0
- package/dist/static/assets/InterVariable-Italic-DpCbqKDY.woff2 +0 -0
- package/dist/static/assets/SourceCodePro-Variable-BP8Zz55n.woff2 +0 -0
- package/dist/static/assets/SourceCodePro-Variable-Italic-eALmlzX7.woff2 +0 -0
- package/dist/static/assets/main-BY4iigay.css +1 -0
- package/dist/static/assets/main-BnQ-v4V9.js +199 -0
- package/dist/static/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2 +0 -0
- package/dist/static/assets/manrope-latin-wght-normal-DHIcAJRg.woff2 +0 -0
- package/dist/vite/index.d.ts +601 -29
- package/dist/vite/index.js +2 -2
- package/docs/auth/overview.mdx +1 -1
- package/docs/auth/supported-flows.mdx +4 -4
- package/docs/compatibility.mdx +4 -4
- package/docs/database/migrations.mdx +2 -0
- package/docs/database/rls.mdx +7 -5
- package/docs/database/schemas.mdx +1 -1
- package/docs/integrations/embedded.mdx +20 -3
- package/docs/integrations/frameworks.mdx +1 -1
- package/docs/integrations/vite.mdx +4 -2
- package/docs/llms.txt +2 -2
- package/docs/quickstart.mdx +2 -2
- package/docs/running.mdx +20 -10
- package/docs/storage/limitations.mdx +2 -11
- package/docs/storage/overview.mdx +8 -2
- package/package.json +2 -1
- package/skills/supalite/SKILL.md +1 -1
- package/dist/static/assets/main-1bwWb_1q.js +0 -40996
- package/dist/static/assets/main-BDsRycsc.css +0 -4045
- package/dist/static/fonts/CustomFont-Black.woff2 +0 -0
- package/dist/static/fonts/CustomFont-BlackItalic.woff2 +0 -0
- package/dist/static/fonts/CustomFont-Bold.woff2 +0 -0
- package/dist/static/fonts/CustomFont-BoldItalic.woff2 +0 -0
- package/dist/static/fonts/CustomFont-Book.woff2 +0 -0
- package/dist/static/fonts/CustomFont-BookItalic.woff2 +0 -0
- package/dist/static/fonts/CustomFont-Medium.woff2 +0 -0
package/dist/vite/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {loadEnv}from'vite';import
|
|
1
|
+
import {loadEnv}from'vite';import B from'node:path';import G from'chokidar';import {watchSchema,migrationsDir,applyPendingMigrations,ensureSchema,ensureInitialStructure,resolveConfiguredPublishableKey,createApi,listMigrationFiles}from'@supabase/lite/cli';import {getRequestListener}from'@hono/node-server';try {
|
|
2
2
|
/**
|
|
3
3
|
* Adding this to avoid warnings from node:sqlite being experimental
|
|
4
4
|
*/
|
|
@@ -9,4 +9,4 @@ import {loadEnv}from'vite';import A from'node:path';import G from'chokidar';impo
|
|
|
9
9
|
};
|
|
10
10
|
} catch {}
|
|
11
11
|
|
|
12
|
-
var
|
|
12
|
+
var k=Object.create;var _=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var C=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var M=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var W=(t,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of T(e))!N.call(t,r)&&r!==o&&_(t,r,{get:()=>e[r],enumerable:!(i=I(e,r))||i.enumerable});return t};var V=(t,e,o)=>(o=t!=null?k(C(t)):{},W(_(o,"default",{value:t,enumerable:true}),t));var U=M((oe,A)=>{var h=process||{},O=h.argv||[],u=h.env||{},Y=!(u.NO_COLOR||O.includes("--no-color"))&&(!!u.FORCE_COLOR||O.includes("--color")||h.platform==="win32"||(h.stdout||{}).isTTY&&u.TERM!=="dumb"||!!u.CI),K=(t,e,o=t)=>i=>{let r=""+i,n=r.indexOf(e,t.length);return ~n?t+H(r,e,o,n)+e:t+r+e},H=(t,e,o,i)=>{let r="",n=0;do r+=t.substring(n,i)+o,n=i+e.length,i=t.indexOf(e,n);while(~i);return r+t.substring(n)},P=(t=Y)=>{let e=t?K:()=>String;return {isColorSupported:t,reset:e("\x1B[0m","\x1B[0m"),bold:e("\x1B[1m","\x1B[22m","\x1B[22m\x1B[1m"),dim:e("\x1B[2m","\x1B[22m","\x1B[22m\x1B[2m"),italic:e("\x1B[3m","\x1B[23m"),underline:e("\x1B[4m","\x1B[24m"),inverse:e("\x1B[7m","\x1B[27m"),hidden:e("\x1B[8m","\x1B[28m"),strikethrough:e("\x1B[9m","\x1B[29m"),black:e("\x1B[30m","\x1B[39m"),red:e("\x1B[31m","\x1B[39m"),green:e("\x1B[32m","\x1B[39m"),yellow:e("\x1B[33m","\x1B[39m"),blue:e("\x1B[34m","\x1B[39m"),magenta:e("\x1B[35m","\x1B[39m"),cyan:e("\x1B[36m","\x1B[39m"),white:e("\x1B[37m","\x1B[39m"),gray:e("\x1B[90m","\x1B[39m"),bgBlack:e("\x1B[40m","\x1B[49m"),bgRed:e("\x1B[41m","\x1B[49m"),bgGreen:e("\x1B[42m","\x1B[49m"),bgYellow:e("\x1B[43m","\x1B[49m"),bgBlue:e("\x1B[44m","\x1B[49m"),bgMagenta:e("\x1B[45m","\x1B[49m"),bgCyan:e("\x1B[46m","\x1B[49m"),bgWhite:e("\x1B[47m","\x1B[49m"),blackBright:e("\x1B[90m","\x1B[39m"),redBright:e("\x1B[91m","\x1B[39m"),greenBright:e("\x1B[92m","\x1B[39m"),yellowBright:e("\x1B[93m","\x1B[39m"),blueBright:e("\x1B[94m","\x1B[39m"),magentaBright:e("\x1B[95m","\x1B[39m"),cyanBright:e("\x1B[96m","\x1B[39m"),whiteBright:e("\x1B[97m","\x1B[39m"),bgBlackBright:e("\x1B[100m","\x1B[49m"),bgRedBright:e("\x1B[101m","\x1B[49m"),bgGreenBright:e("\x1B[102m","\x1B[49m"),bgYellowBright:e("\x1B[103m","\x1B[49m"),bgBlueBright:e("\x1B[104m","\x1B[49m"),bgMagentaBright:e("\x1B[105m","\x1B[49m"),bgCyanBright:e("\x1B[106m","\x1B[49m"),bgWhiteBright:e("\x1B[107m","\x1B[49m")}};A.exports=P();A.exports.createColors=P;});var x=V(U());var w=["/auth/v1","/rest/v1","/_system"];function S(t,e=w){let o=getRequestListener(async(i,...r)=>{let n=r[0]?.incoming;return t.fetch(i,{peerAddress:n?.socket?.remoteAddress??null})});return (i,r,n)=>{let p=i.url??"";if(!e.some(g=>p===g||p.startsWith(`${g}/`)||p.startsWith(`${g}?`)))return n();o(i,r).catch(n);}}function $(t){let e=t.trim().toLowerCase();if(e.startsWith("[")&&e.endsWith("]")&&(e=e.slice(1,-1)),e=e.split("%")[0],e==="::1")return true;e.startsWith("::ffff:")&&(e=e.slice(7));let o=e.split(".");return o.length===4&&o.every(i=>/^\d{1,3}$/.test(i)&&Number(i)<=255)&&Number(o[0])===127}function D(t){let e=t.trim().toLowerCase();return e==="localhost"||$(e)}function ee(t,e){let o=t.root?B.resolve(t.root):process.cwd(),r=t.envFile===false||t.envDir===false?false:t.envDir?B.resolve(o,t.envDir):o,n=t.envPrefix??"VITE_";return loadEnv(e,r,n)}function te(t={}){let{config:e,migrateOnBoot:o=true,watchSchema:i=true,forceSchema:r=false,initOnBoot:n=true,prefixes:p=w,admin:g}=t,m;async function E(a){n&&await ensureInitialStructure({recreate:false,template:false});let s=await(await createApi({withSupabaseClient:false})).project.local.createApp(e,a);if(r&&console.log(x.default.yellow("\u26A0 supalite: forceSchema=true \u2014 data-loss migrations allowed")),o){if((await listMigrationFiles(s)).length>0){let c=await applyPendingMigrations(s);for(let b of c.applied)console.log(x.default.green(" \u2713"),"Applied migration",x.default.cyan(b.filename));}else s.hasEnabledSystemBaseSchema()&&await s.ensureSystemSchema();await ensureSchema(s,{force:r});}return s}async function y(){try{await m?.connection.close();}catch{}}return {name:"@supabase/lite",async config(a,{mode:f}){let s=ee(a,f),d=a.define??{},c={},b=(l,v)=>l in s||v in d;if(b("VITE_SUPABASE_URL","import.meta.env.VITE_SUPABASE_URL")||(c["import.meta.env.VITE_SUPABASE_URL"]="window.location.origin"),!b("VITE_SUPABASE_ANON_KEY","import.meta.env.VITE_SUPABASE_ANON_KEY")){n&&await ensureInitialStructure({recreate:false,template:false});let l=await resolveConfiguredPublishableKey(e);c["import.meta.env.VITE_SUPABASE_ANON_KEY"]=JSON.stringify(l??"sb-lite-anon-key");}return {define:c}},async configureServer(a){let f=a.config.server.host,s=f===true||typeof f=="string"&&!D(f);if(m=await E({admin:s&&g===void 0?false:g,adminDefault:!s}),a.middlewares.use(S(m,p)),i){let d=watchSchema(m,{translate:true,force:r}),c=B.join(process.cwd(),"supabase",migrationsDir(m)),b=G.watch(c,{ignoreInitial:true});b.on("add",async()=>{try{let l=await applyPendingMigrations(m);for(let v of l.applied)console.log(x.default.green(" \u2713"),"Applied",x.default.cyan(v.filename));await ensureSchema(m,{force:r});}catch(l){console.error(x.default.red("Migration failed: "+String(l.message??l)));}}),a.httpServer?.once("close",()=>{b.close(),d();});}a.httpServer?.once("close",y);},async configurePreviewServer(a){m=await E({admin:false,adminDefault:false}),a.middlewares.use(S(m,p)),a.httpServer?.once("close",y);},async closeBundle(){await y();}}}var pe=te;export{w as DEFAULT_PREFIXES,pe as default,S as honoMiddleware,te as supalite};
|
package/docs/auth/overview.mdx
CHANGED
|
@@ -37,7 +37,7 @@ Supabase Lite issues opaque `sb_publishable_*` / `sb_secret_*` keys, matching Su
|
|
|
37
37
|
|
|
38
38
|
The OpenAPI root (`GET /rest/v1/`) requires the secret key, mirroring upstream's admin-only ACL on that route: a publishable key gets 403, the secret key gets 200.
|
|
39
39
|
|
|
40
|
-
There is one exception to enforcement, and it only exists locally: [admin mode](/running#admin-mode). `lite dev` and
|
|
40
|
+
There is one exception to enforcement, and it only exists locally: [admin mode](/running#admin-mode). Loopback `lite dev`/`lite start` and Vite dev listeners serve keyless `/rest/v1` requests (`/storage/v1` on the CLI too) as `service_role`, so browser Studio can work without receiving a secret key. Only same-origin requests on a loopback socket **and** a loopback hostname are elevated, and credentialed requests keep their normal role. `--host [host]` and exposed Vite listeners default admin off unless explicitly enabled; preview and embedders default off.
|
|
41
41
|
|
|
42
42
|
Embedders using the `App` class directly can disable enforcement (`options.server.apiKeys: false`) or supply a custom resolver (`options.server.apiKeys.resolver`); `app.getClient()` defaults to the configured publishable key. See `STATUS.md` (`#api-keys`) in the installed package for the full behavior matrix.
|
|
43
43
|
|
|
@@ -28,7 +28,7 @@ Each method below hits a real backend endpoint at `/auth/v1/*` and behaves like
|
|
|
28
28
|
```ts
|
|
29
29
|
import { createClient } from "@supabase/supabase-js";
|
|
30
30
|
|
|
31
|
-
const supabase = createClient("http://
|
|
31
|
+
const supabase = createClient("http://127.0.0.1:54321", "<anon-key>");
|
|
32
32
|
|
|
33
33
|
await supabase.auth.signUp({
|
|
34
34
|
email: "user@example.com",
|
|
@@ -68,7 +68,7 @@ Both the authorization-code (PKCE) and implicit flows are supported. Which one y
|
|
|
68
68
|
### Implicit (the `supabase-js` default)
|
|
69
69
|
|
|
70
70
|
```ts
|
|
71
|
-
const supabase = createClient("http://
|
|
71
|
+
const supabase = createClient("http://127.0.0.1:54321", "<anon-key>");
|
|
72
72
|
// flowType defaults to "implicit"
|
|
73
73
|
|
|
74
74
|
await supabase.auth.signInWithOAuth({ provider: "github" });
|
|
@@ -85,7 +85,7 @@ await supabase.auth.signInWithOAuth({ provider: "github" });
|
|
|
85
85
|
With `flowType: "pkce"` and the default `detectSessionInUrl: true`, the browser client redeems the `?code=...` param itself as soon as the callback page loads. This is the path to use in a browser app — no exchange call of your own:
|
|
86
86
|
|
|
87
87
|
```ts
|
|
88
|
-
const supabase = createClient("http://
|
|
88
|
+
const supabase = createClient("http://127.0.0.1:54321", "<anon-key>", {
|
|
89
89
|
auth: { flowType: "pkce" },
|
|
90
90
|
});
|
|
91
91
|
|
|
@@ -101,7 +101,7 @@ const { data } = await supabase.auth.getSession();
|
|
|
101
101
|
Only call `exchangeCodeForSession()` yourself when you own the callback handling — a server-side route, or a custom client flow. Turn detection off on that client, otherwise it races your call and one of the two attempts fails against the single-use code:
|
|
102
102
|
|
|
103
103
|
```ts
|
|
104
|
-
const supabase = createClient("http://
|
|
104
|
+
const supabase = createClient("http://127.0.0.1:54321", "<anon-key>", {
|
|
105
105
|
auth: { flowType: "pkce", detectSessionInUrl: false },
|
|
106
106
|
});
|
|
107
107
|
|
package/docs/compatibility.mdx
CHANGED
|
@@ -61,7 +61,7 @@ See [Auth overview](/auth/overview) and [Not supported](/auth/not-supported) for
|
|
|
61
61
|
|
|
62
62
|
## Storage
|
|
63
63
|
|
|
64
|
-
Gated behind `EXPERIMENTAL_STORAGE`. CRUD, public buckets, signed URLs,
|
|
64
|
+
Gated behind `EXPERIMENTAL_STORAGE`. CRUD, public buckets, signed URLs, image transforms, and Storage-table RLS work across all database backends.
|
|
65
65
|
|
|
66
66
|
| Capability | SQLite | PGlite | Postgres | Coverage |
|
|
67
67
|
|---|:---:|:---:|:---:|---|
|
|
@@ -70,9 +70,9 @@ Gated behind `EXPERIMENTAL_STORAGE`. CRUD, public buckets, signed URLs, and imag
|
|
|
70
70
|
| Signed URLs (download, batch, upload) | ✅ | ✅ | ✅ | |
|
|
71
71
|
| Image transforms (Sharp, Cloudflare) | ✅ | ✅ | ✅ | |
|
|
72
72
|
| Storage backends (filesystem, S3-compatible) | ✅ | ✅ | ✅ | |
|
|
73
|
-
| Role-based access (service_role/anon/authenticated gating) |
|
|
74
|
-
| RLS policies on storage objects |
|
|
75
|
-
| **Total** | **20/20 methods** | **20/20 methods** | **20/20 methods** |
|
|
73
|
+
| Role-based access (service_role/anon/authenticated gating) | ✅ | ✅ | ✅ | Shared verified JWT/API-key claims; `service_role` bypasses RLS |
|
|
74
|
+
| RLS policies on storage buckets and objects | ✅ | ✅ | ✅ | App-layer AST enforcement on SQLite; native RLS on Postgres/PGlite; canonical path helpers and full operation identifiers on all three |
|
|
75
|
+
| **Total** | **20/20 methods** | **20/20 methods** | **20/20 methods** | `/status` health endpoint also implemented |
|
|
76
76
|
|
|
77
77
|
See [Storage overview](/storage/overview) and [Storage limitations](/storage/limitations).
|
|
78
78
|
|
|
@@ -61,6 +61,8 @@ Unrelated changes in the same diff do not cause a split: they are ordered ahead
|
|
|
61
61
|
|
|
62
62
|
Migrations and declarative schemas coexist: `lite dev` and the Vite plugin apply pending migrations on boot, then run the declarative diff against `schemas/*.sql`. RLS policies authored directly in migration files are picked up at request time, same as policies from the declarative schema.
|
|
63
63
|
|
|
64
|
+
On `sqlite-postgres`, each migration's SQL and its history row commit in one transaction, matching the Supabase CLI. Afterward, `lite migration up` rebuilds complete runtime metadata from the ordered SQL stored in that history table, including on a no-op run. The database copy is authoritative: editing or deleting an applied file does not change `lite start`, and a pending migration has no effect until `lite migration up` records it. Invalid or null recorded `statements`, or a live structure that cannot be reproduced from recorded history, requires `lite db reset`.
|
|
65
|
+
|
|
64
66
|
Destructive deltas (dropped columns/tables, incompatible type changes) aren't auto-emitted: `lite db diff` errors on data-loss rather than generating a lossy migration, and you author those by hand, matching how Supabase's own declarative diff behaves.
|
|
65
67
|
|
|
66
68
|
## Which DDL dialect to write
|
package/docs/database/rls.mdx
CHANGED
|
@@ -16,17 +16,19 @@ What differs is *how* it's enforced, and a handful of SQLite-specific gaps.
|
|
|
16
16
|
|
|
17
17
|
`auth.role()` resolves from the JWT `role` claim (defaulting to `"anon"` if missing, matching PostgREST). `auth.uid()` resolves to the JWT `sub` claim. `auth.jwt()` exposes the full payload. These placeholders are supported in policy expressions on both dialects.
|
|
18
18
|
|
|
19
|
-
On
|
|
19
|
+
On `sqlite-postgres`, runtime metadata is persisted to the disposable cache file `supabase/.temp/.runtime-metadata-cache.json`. `lite start` accepts only migration-derived entries and trusts only the ordered SQL recorded in `supabase_migrations.schema_migrations`; migration files and `schemas/*.sql` are never startup inputs. Missing, corrupt, old, stale, or tampered caches rebuild automatically from applied history, including the complete RLS table/policy lifecycle. A rename carries RLS and policies to the new table name; dropping a table removes them, even if a table with the same name is created later.
|
|
20
|
+
|
|
21
|
+
Rebuilds also replay the physical migrations in memory and compare their raw SQLite structure with the live database. Invalid/null recorded statements or out-of-band structural changes stop startup with a `lite db reset` hint. Introspection only proves equality and never generates policies. `lite dev` and the Vite plugin instead recompute metadata from the declarative schema they apply; use exactly one backend process per project, and capture declarative changes in migrations before switching to `lite start`.
|
|
20
22
|
|
|
21
23
|
<Aside type="caution">
|
|
22
|
-
While [admin mode](/running#admin-mode) is on (the default for `lite dev`, `lite start`, and
|
|
24
|
+
While [admin mode](/running#admin-mode) is on (the default for loopback `lite dev`, `lite start`, and Vite dev listeners), requests that carry no credential at all run as `service_role` and skip RLS entirely. To exercise your policies, send an `apikey`:
|
|
23
25
|
|
|
24
26
|
```bash
|
|
25
27
|
# anon
|
|
26
|
-
curl -H "apikey: $PUBLISHABLE_KEY"
|
|
28
|
+
curl -H "apikey: $PUBLISHABLE_KEY" 127.0.0.1:54321/rest/v1/notes
|
|
27
29
|
# authenticated — the apikey is required IN ADDITION to the user JWT
|
|
28
30
|
curl -H "apikey: $PUBLISHABLE_KEY" -H "Authorization: Bearer $USER_JWT" \
|
|
29
|
-
|
|
31
|
+
127.0.0.1:54321/rest/v1/notes
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
Neither request is ever elevated, so both behave exactly as they will in production. A bearer token alone (no `apikey`) is rejected with 401 before RLS is reached — opaque keys are only sourced from `apikey`, matching upstream. Or start with `--no-admin`.
|
|
@@ -96,7 +98,7 @@ Postgres applies `UPDATE` policies on conflict resolution during an upsert. Supa
|
|
|
96
98
|
|
|
97
99
|
### `FORCE ROW LEVEL SECURITY` is accepted and ignored
|
|
98
100
|
|
|
99
|
-
`ALTER TABLE ... FORCE ROW LEVEL SECURITY` (and `NO FORCE`) is parsed and then ignored. On Postgres it only controls whether the table owner is subject to the table's policies; Supabase Lite has no table-owner exemption to begin with, so there is nothing to toggle. As on Postgres, `FORCE` alone does not enable RLS: only `ENABLE ROW LEVEL SECURITY` does, and only `DISABLE ROW LEVEL SECURITY` turns it back off.
|
|
101
|
+
`ALTER TABLE ... FORCE ROW LEVEL SECURITY` (and `NO FORCE`) is parsed and then ignored. On Postgres it only controls whether the table owner is subject to the table's policies; Supabase Lite has no table-owner exemption to begin with, so there is nothing to toggle. As on Postgres, `FORCE` alone does not enable RLS: only `ENABLE ROW LEVEL SECURITY` does, and only `DISABLE ROW LEVEL SECURITY` turns it back off. Policies may exist while RLS is disabled or has never been enabled; they remain stored but inert until RLS is enabled.
|
|
100
102
|
|
|
101
103
|
### `RETURNING` isn't checked against the `SELECT` policy
|
|
102
104
|
|
|
@@ -41,7 +41,7 @@ Postgres supports `CREATE SCHEMA` and cross-schema references (`billing.invoices
|
|
|
41
41
|
- On `sqlite-postgres` (the default for declarative Postgres DDL), `CREATE SCHEMA` and schema-qualified tables are accepted but flattened into a single physical namespace. PostgREST records the schema each table came from, so `schema()` / the `Accept-Profile` header routes to the right tables. There's no true isolation, though: two schemas with a same-named table collide once the prefix is stripped.
|
|
42
42
|
- On the bare `sqlite` driver (raw SQLite DDL, no translation), there's no schema handling at all. Everything is `public`.
|
|
43
43
|
|
|
44
|
-
Either way, this is not real Postgres multi-schema. `auth.users`, `storage.objects`, and other system-schema tables
|
|
44
|
+
Either way, this is not real Postgres multi-schema. `auth.users`, `storage.objects`, and other system-schema tables are handled internally by Supabase Lite; you don't need to (and can't) create competing user-defined schemas with the same names. The protected `auth` and `storage` schemas are not exposed through the Data API by default. `storage` can be added explicitly to `api.schemas` when direct metadata endpoints are intentional; `auth` should remain private.
|
|
45
45
|
|
|
46
46
|
See [Postgres → SQLite translation](/database/postgres-sqlite-translation) for the full list of DDL constructs that don't survive the SQLite path.
|
|
47
47
|
|
|
@@ -43,19 +43,36 @@ Serve `app.fetch` with any Web-API-compatible server and point `@supabase/supaba
|
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
45
|
// Bun
|
|
46
|
-
Bun.serve({
|
|
46
|
+
const server = Bun.serve({
|
|
47
|
+
hostname: "127.0.0.1",
|
|
48
|
+
fetch(request) {
|
|
49
|
+
return app.fetch(request, {
|
|
50
|
+
peerAddress: server.requestIP(request)?.address ?? null,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
47
54
|
```
|
|
48
55
|
|
|
49
56
|
```ts
|
|
50
57
|
// Node, via @hono/node-server
|
|
51
58
|
import { serve } from "@hono/node-server";
|
|
52
|
-
serve({
|
|
59
|
+
serve({
|
|
60
|
+
port: 3000,
|
|
61
|
+
hostname: "127.0.0.1",
|
|
62
|
+
fetch(request, env) {
|
|
63
|
+
return app.fetch(request, {
|
|
64
|
+
peerAddress: env.incoming.socket.remoteAddress ?? null,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
53
68
|
```
|
|
54
69
|
|
|
55
70
|
```ts
|
|
56
71
|
import { createClient } from "@supabase/supabase-js";
|
|
57
|
-
const client = createClient("http://
|
|
72
|
+
const client = createClient("http://127.0.0.1:3000", "<sb_publishable_...>");
|
|
58
73
|
```
|
|
74
|
+
|
|
75
|
+
`AppRequestContext.peerAddress` is trusted transport metadata, not a forwarded header. Supply it only from the socket object owned by your server adapter. If the adapter cannot determine the peer, pass `null` so admin mode fails closed. Omitting the context preserves the in-process/edge fallback, where only the loopback hostname guard is available.
|
|
59
76
|
</TabItem>
|
|
60
77
|
</Tabs>
|
|
61
78
|
|
|
@@ -73,7 +73,7 @@ What's different from a standard Supabase setup: there's no separate database se
|
|
|
73
73
|
|
|
74
74
|
## Docker
|
|
75
75
|
|
|
76
|
-
Package the published `@supabase/lite` npm package into a container
|
|
76
|
+
Package the published `@supabase/lite` npm package into a container with `supabase/` bind-mounted for config, schema, seed, and the SQLite file. Run `lite migration up` (or `lite db diff -f <name>` then `lite migration up`) as a one-off bootstrap step, then serve with `lite start --host --no-admin`: `--host` listens beyond the container loopback and `--no-admin` makes the public-boundary choice explicit.
|
|
77
77
|
|
|
78
78
|
## Related
|
|
79
79
|
|
|
@@ -18,7 +18,7 @@ export default defineConfig({
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
<Aside type="caution">
|
|
21
|
-
Don't run `lite dev` or `lite start` at the same time as this plugin
|
|
21
|
+
Run exactly one backend per project. Don't run `lite dev` or `lite start` at the same time as this plugin, and don't run both CLI backends together. It isn't necessarily a port conflict: two backends driving the same `supabase/` project and SQLite file race migrations and declarative metadata reconciliation. Pick one process model per project; see [Running Supabase Lite](/running).
|
|
22
22
|
</Aside>
|
|
23
23
|
|
|
24
24
|
## What it does
|
|
@@ -76,7 +76,9 @@ supalite({
|
|
|
76
76
|
| `forceSchema` | boolean | `false` | Allow the declarative schema diff to apply changes that would otherwise be blocked as data-loss. Logs a warning on boot when enabled. |
|
|
77
77
|
| `initOnBoot` | boolean | `true` | Scaffold the initial `supabase/` structure if it doesn't exist yet, equivalent to `lite init`, before starting. |
|
|
78
78
|
| `prefixes` | string[] | `["/auth/v1", "/rest/v1", "/_system"]` | Which path prefixes the plugin mounts on the Vite server. Only override this if you need to move the API off its default paths. |
|
|
79
|
-
| `admin` | boolean | `true` | [Admin mode](/running#admin-mode): serve keyless, same-origin, loopback `/rest/v1` requests as `service_role`.
|
|
79
|
+
| `admin` | boolean | `true` on loopback | [Admin mode](/running#admin-mode): serve keyless, same-origin, loopback `/rest/v1` requests as `service_role`. A non-loopback Vite host (including bare `vite --host`) defaults it off and requires explicit `admin: true`; config cannot silently re-enable an exposed listener. Preview always disables it. Admin also covers `/storage/v1`, but that prefix is not mounted by default. |
|
|
80
|
+
|
|
81
|
+
Vite and the Lite CLI use the same host posture: local binding keeps the local admin default, while `vite --host` / `lite dev --host` exposes the listener and disables admin unless explicitly enabled. If a sandbox or tunnel publishes the port, leave admin off even when its local forwarding socket appears loopback.
|
|
80
82
|
|
|
81
83
|
## Related
|
|
82
84
|
|
package/docs/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Supabase Lite docs
|
|
2
2
|
|
|
3
|
-
Bundled with @supabase/lite 0.
|
|
3
|
+
Bundled with @supabase/lite 0.10.0. Same content as https://docs.lite.dev, which tracks the latest release.
|
|
4
4
|
Files are MDX source (Astro Starlight). Site path -> file: `/` -> `index.mdx`, `/database/rls` -> `database/rls.mdx`.
|
|
5
5
|
|
|
6
6
|
## Start
|
|
@@ -33,7 +33,7 @@ Files are MDX source (Astro Starlight). Site path -> file: `/` -> `index.mdx`, `
|
|
|
33
33
|
|
|
34
34
|
- [Storage overview](storage/overview.mdx): Storage API for @supabase/lite: experimental flag, endpoint coverage, and supabase-js parity.
|
|
35
35
|
- [Storage adapters](storage/adapters.mdx): Pluggable storage backends (filesystem, S3-compatible) and transformation adapters (Noop, Sharp, Cloudflare) for Supabase Lite Storage.
|
|
36
|
-
- [Storage limitations](storage/limitations.mdx): What's not yet implemented in Supabase Lite Storage:
|
|
36
|
+
- [Storage limitations](storage/limitations.mdx): What's not yet implemented in Supabase Lite Storage: TUS uploads, webhooks, bucket-list query params, and the S3-compatible server protocol.
|
|
37
37
|
|
|
38
38
|
## Integrations
|
|
39
39
|
|
package/docs/quickstart.mdx
CHANGED
|
@@ -104,7 +104,7 @@ This walks through the minimal path: install, scaffold, write a schema, run a se
|
|
|
104
104
|
</TabItem>
|
|
105
105
|
</Tabs>
|
|
106
106
|
|
|
107
|
-
The API is now running at `http://
|
|
107
|
+
The API is now running at `http://127.0.0.1:54321`. `lite init` also generated a publishable/secret API key pair into root `.env` and printed them.
|
|
108
108
|
|
|
109
109
|
1. **Create a client**
|
|
110
110
|
|
|
@@ -112,7 +112,7 @@ This walks through the minimal path: install, scaffold, write a schema, run a se
|
|
|
112
112
|
import { createClient } from "@supabase/supabase-js";
|
|
113
113
|
|
|
114
114
|
const supabase = createClient(
|
|
115
|
-
"http://
|
|
115
|
+
"http://127.0.0.1:54321",
|
|
116
116
|
"<sb_publishable_...>"
|
|
117
117
|
);
|
|
118
118
|
```
|
package/docs/running.mdx
CHANGED
|
@@ -40,6 +40,12 @@ lite dev
|
|
|
40
40
|
|
|
41
41
|
Starts the API server as its own process and watches `supabase/schemas/*.sql`, re-applying the schema automatically on every change. Use this for non-Vite apps (plain Node/Bun backends, mobile clients hitting a local API, etc.) during active development.
|
|
42
42
|
|
|
43
|
+
Both CLI servers bind `127.0.0.1` by default. The host flag matches Vite: `--host` listens on `0.0.0.0`, while `--host <address>` selects a specific interface.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
lite dev --host --no-admin # reachable from containers/LAN; normal auth only
|
|
47
|
+
```
|
|
48
|
+
|
|
43
49
|
By default, auth emails (signup confirmation, magic link, password recovery, and so on) are printed to this server console rather than actually sent. See [Email delivery & templates](/auth/email).
|
|
44
50
|
|
|
45
51
|
```bash
|
|
@@ -54,37 +60,41 @@ lite start
|
|
|
54
60
|
|
|
55
61
|
Starts the API server with no file watching and no auto-migration. This is the closest Supabase Lite gets to a production-like posture: you control exactly when migrations run (`lite migration up`, `lite db reset`), and the server won't silently reapply schema changes. Use this for CI pipelines and any environment meant to behave like production.
|
|
56
62
|
|
|
57
|
-
On the `sqlite-postgres` driver,
|
|
63
|
+
On the `sqlite-postgres` driver, the ordered SQL in `supabase_migrations.schema_migrations` is the sole startup authority. `lite start` hashes the recorded `{version, statements}` values, restores a matching migration-derived `supabase/.temp/.runtime-metadata-cache.json`, or fully rebuilds fields, enums, constraints, comments, variables, relationships, RLS tables, and policies from that history. It never reads migration files or `supabase/schemas/*.sql`, so pending files and edits or deletions of already-applied files have no effect.
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
The runtime metadata cache is disposable. Missing, corrupt, old, stale, or tampered files rebuild automatically. During a rebuild, Supabase Lite also replays the recorded SQL into an in-memory SQLite database and compares that raw structure with the live database. Invalid history or out-of-band structural DDL makes `lite start` exit with a `lite db reset` hint; live introspection is never used as a metadata source.
|
|
60
66
|
|
|
61
|
-
`lite
|
|
67
|
+
`lite dev` and the Vite plugin remain declarative workflows: they apply pending migrations, reconcile the current `schemas/*.sql`, and replace runtime metadata from the schema they actually applied. `lite start` ignores that declarative state. If declarative changes made the live structure differ from applied migration history, generate a migration with `lite db diff -f <name>` and use `lite db reset` to establish a clean migration-derived database before running `lite start`.
|
|
62
68
|
|
|
63
69
|
## Admin mode
|
|
64
70
|
|
|
65
|
-
`lite dev`, `lite start`, and
|
|
71
|
+
Loopback `lite dev`, `lite start`, and Vite dev listeners run with **admin mode on**. A request that arrives with no credential at all (no `apikey` header or query parameter, no `Authorization` header) is served as `service_role`, so a local admin UI like the built-in studio can read and edit any table without a secret key ever reaching the browser. This mirrors self-hosted Supabase Studio, where the Next.js server holds the service key and does the privileged work on the client's behalf.
|
|
66
72
|
|
|
67
73
|
Elevation is deliberately narrow. A request is only elevated when all of these hold:
|
|
68
74
|
|
|
69
75
|
- the path is under `/rest/v1` or `/storage/v1` (`/auth/v1` is never elevated). The Vite plugin does not mount `/storage/v1` by default, so there it's `/rest/v1` only
|
|
70
76
|
- it carries no `apikey` and no `Authorization`
|
|
71
77
|
- it is same-origin, or has no `Origin` header at all (so `curl` works, but another website open in your browser does not)
|
|
72
|
-
- it arrived from a loopback socket **and** names a loopback host. Both are required: the peer address stops a machine on your network spoofing `Host: localhost
|
|
78
|
+
- it arrived from a loopback socket **and** names a loopback host. Both are required: the peer address stops a machine on your network 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 really is loopback while `Host` and `Origin` stay theirs
|
|
73
79
|
|
|
74
80
|
Anything else falls through to normal [API key enforcement](/auth/overview#api-keys).
|
|
75
81
|
|
|
76
82
|
```bash
|
|
77
|
-
lite start --no-admin
|
|
83
|
+
lite start --no-admin # loopback, admin explicitly off
|
|
84
|
+
lite start --host # all interfaces, admin defaults off and warns
|
|
85
|
+
lite start --host --no-admin # all interfaces, explicit/silent opt-out
|
|
86
|
+
lite start --host --admin # all interfaces, local peers retain admin
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
Two things to know:
|
|
81
90
|
|
|
82
91
|
1. **Keyless traffic does not go through RLS while admin mode is on.** To exercise your policies, send an `apikey` — the publishable key for `anon`, or the publishable key **plus** `Authorization: Bearer <user JWT>` for `authenticated`. Those requests are never elevated, so both roles behave exactly as they do in production. That is the whole reason elevation is credential-aware rather than blanket. A bearer token on its own is not enough: with keys configured, a request without an `apikey` is rejected before RLS is reached (401 `UNAUTHORIZED_MISSING_API_KEY`), matching upstream.
|
|
83
|
-
2. **
|
|
92
|
+
2. **Exposing a server changes the default.** A non-loopback `--host` disables admin mode unless `--admin` is explicit. With neither admin flag, Lite warns that exposure disabled admin and shows both choices. Vite applies the same rule when its resolved host is non-loopback.
|
|
93
|
+
3. **A public tunnel erases locality.** A sandbox or tunnel can publish a loopback listener and forward traffic over a local socket. Run with `--no-admin` whenever another system exposes the port; socket locality cannot authenticate the original remote user through a proxy.
|
|
84
94
|
|
|
85
|
-
|
|
95
|
+
On a loopback CLI listener, precedence is explicit `--admin`/`--no-admin`, then `options.server.admin`, then the on-by-default launcher value. A non-loopback `--host` forces admin off unless `--admin` is present, so config cannot silently re-enable it. For Vite, an exposed host similarly requires the explicit `supalite({ admin: true })` option.
|
|
86
96
|
|
|
87
|
-
Embedding
|
|
97
|
+
Embedding `App` without request context applies only the hostname check. An adapter that owns a listener should call `app.fetch(request, { peerAddress })`; a null peer fails closed. Only enable admin on a loopback-bound server.
|
|
88
98
|
|
|
89
99
|
With admin mode off, the bundled studio connects with the publishable key instead and shows you what `anon` can see under RLS, with a banner saying so — it stays usable, just scoped.
|
|
90
100
|
|
|
@@ -114,4 +124,4 @@ This is the path for custom server frameworks, Cloudflare Workers/Durable Object
|
|
|
114
124
|
- Deploying, running in CI, or want explicit control over migrations? Use `lite start`.
|
|
115
125
|
- Building your own server, or targeting a runtime the CLI doesn't drive directly (Workers, Durable Objects)? Use the [embedded API](/integrations/embedded).
|
|
116
126
|
|
|
117
|
-
Whichever mode you pick, run one per project.
|
|
127
|
+
Whichever mode you pick, run exactly one backend per project. Never run `lite dev` and `lite start` together, or either alongside the Vite plugin. Two backends over the same SQLite file race migrations and schema watchers even when they do not share a port.
|
|
@@ -1,30 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Storage limitations"
|
|
3
|
-
description: "What's not yet implemented in Supabase Lite Storage:
|
|
3
|
+
description: "What's not yet implemented in Supabase Lite Storage: TUS uploads, webhooks, bucket-list query params, and the S3-compatible server protocol."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { Aside } from '@astrojs/starlight/components';
|
|
7
|
-
|
|
8
6
|
Storage is [experimental](/storage/overview) and behind the `EXPERIMENTAL_STORAGE` flag. Everything below is a gap on top of that: even with the flag on and an adapter wired up, these features don't exist yet.
|
|
9
7
|
|
|
10
8
|
## Not yet implemented
|
|
11
9
|
|
|
12
10
|
| Feature | Notes |
|
|
13
11
|
|---|---|
|
|
14
|
-
| Role-based access control | API keys resolve `service_role`/`anon`/`authenticated` for storage's route-level checks when configured (see [API keys](/auth/overview#api-keys)); still no per-object RLS. |
|
|
15
|
-
| RLS policies on storage tables | No per-user object access via row-level security on `storage.objects`. |
|
|
16
|
-
| `/status` health endpoint | Not registered. Upstream returns `200` with no auth required. |
|
|
17
12
|
| Bucket list query params | `GET /bucket` doesn't yet accept `?search=`, `?limit=`, `?offset=`. |
|
|
18
13
|
| S3-compatible protocol (server side) | No `PUT`/`GET`/`DELETE` under `/storage/v1/s3/*`. Supabase Lite can act as an S3 *client* via the [S3 storage adapter](/storage/adapters), but doesn't expose an S3-compatible server. |
|
|
19
14
|
| TUS resumable uploads | No `POST`/`PATCH`/`HEAD` on `/upload/resumable`. |
|
|
20
15
|
| Webhooks | No `ObjectCreated`/`ObjectRemoved` event delivery. |
|
|
21
16
|
|
|
22
|
-
<Aside type="caution">
|
|
23
|
-
Because there's no per-object RLS, anyone who can authenticate to `/storage/v1/*` (via a valid `anon`/`authenticated`/`service_role` key or session) has broad access to buckets they're routed to — there's no per-user object ownership check. Don't expose Supabase Lite Storage to untrusted clients.
|
|
24
|
-
</Aside>
|
|
25
|
-
|
|
26
17
|
## Workaround
|
|
27
18
|
|
|
28
19
|
For any of the above, run the storage-dependent parts of your app against hosted Supabase and keep the rest (Data API, Auth) on Supabase Lite. Mixed backends are a normal transitional setup, not something you need to migrate wholesale for.
|
|
29
20
|
|
|
30
|
-
For
|
|
21
|
+
For Storage access-control policies, see [Supabase Storage access control](https://supabase.com/docs/guides/storage/security/access-control); Supabase Lite supports that model across SQLite, PGlite, and PostgreSQL. For what is implemented today, see [Storage overview](/storage/overview) and [Storage adapters](/storage/adapters).
|
|
@@ -5,7 +5,7 @@ description: "Storage API for @supabase/lite: experimental flag, endpoint covera
|
|
|
5
5
|
|
|
6
6
|
import { Aside, Card, CardGrid, Steps } from '@astrojs/starlight/components';
|
|
7
7
|
|
|
8
|
-
Supabase Lite ships a Storage API at `/storage/v1/*` covering all 20 endpoints upstream Supabase Storage exposes to `supabase-js`: bucket CRUD, object upload/download/list/remove/move/copy, signed URLs, and image transformations. For the shipped surface, `@supabase/supabase-js`'s `storage` client works unchanged.
|
|
8
|
+
Supabase Lite ships a Storage API at `/storage/v1/*` covering all 20 endpoints upstream Supabase Storage exposes to `supabase-js`: bucket CRUD, object upload/download/list/remove/move/copy, signed URLs, and image transformations. The same policies on `storage.buckets` and `storage.objects` work on SQLite, PGlite, and PostgreSQL. For the shipped surface, `@supabase/supabase-js`'s `storage` client works unchanged.
|
|
9
9
|
|
|
10
10
|
<Aside type="caution">
|
|
11
11
|
Storage is **experimental** and disabled by default. It is gated behind the `EXPERIMENTAL_STORAGE` environment variable and requires you to wire a storage adapter explicitly. Don't rely on it in production.
|
|
@@ -60,6 +60,12 @@ All 20 supabase-js storage methods map to real endpoints: `upload`, `download`,
|
|
|
60
60
|
|
|
61
61
|
`/storage/v1` is transform-only for API keys, like upstream self-hosted Kong: a missing or invalid `apikey` never 401s at the gateway, so public objects, signed URLs, and S3 presigned flows stay keyless. When a key is present, it resolves to a role — a secret key satisfies storage's own authed routes as `service_role`. See [API keys](/auth/overview#api-keys).
|
|
62
62
|
|
|
63
|
+
Storage verifies bearer JWTs with the Data API verifier. Missing credentials become `anon`; protected operations return a Storage-shaped 401 for a malformed, invalid, or expired bearer JWT. Object `GET`, `HEAD`, and info routes tolerate an invalid JWT so stale sessions do not block public objects. As upstream does, an unauthenticated request to those routes receives `NoSuchBucket` for a private bucket; a verified `anon` JWT can still use an `anon` `SELECT` policy. `service_role` bypasses Storage RLS, while custom string roles remain available to `TO role` policies. Uploads made with a verified JWT carrying a `sub` claim populate `owner_id` and `owner` from that claim; `service_role` uploads remain ownerless. Public buckets bypass object `SELECT` for `GET`, `HEAD`, and info; listing, signing, copy, and move remain caller-scoped. Signed URL redemption uses the authorization decision made when the URL was created. These behaviors match [Supabase Storage access control](https://supabase.com/docs/guides/storage/security/access-control).
|
|
64
|
+
|
|
65
|
+
The protected `storage` schema is not exposed through `/rest/v1` by default; use `supabase.storage` and `/storage/v1` for normal operations. You may explicitly add `storage` to `api.schemas` for direct metadata reads. Treat those endpoints as read-only: PostgREST writes may succeed when policies permit, but they are unsupported because they do not update the backing filesystem or object store. On SQLite, Storage's system declaration keeps `storage.buckets` and `storage.objects` fail-closed even if external runtime metadata omits their RLS entries: no policies means no caller access, while `service_role` keeps its upstream bypass.
|
|
66
|
+
|
|
67
|
+
The upstream policy helpers `storage.foldername()`, `storage.filename()`, `storage.extension()`, `storage.operation()`, `storage.allow_only_operation()`, and `storage.allow_any_operation()` work on SQLite, PGlite, and PostgreSQL. `storage.operation()` returns the complete upstream route identifier, such as `storage.object.upload`, and can be used with comparison, `LIKE`/`ILIKE`, `IN`, and null predicates; the `allow_*` helpers accept identifiers with or without the `storage.` prefix. Policies can therefore distinguish actions such as object listing from authenticated download. See [Supabase Storage helper functions](https://supabase.com/docs/guides/storage/schema/helper-functions) for their canonical policy forms.
|
|
68
|
+
|
|
63
69
|
```ts
|
|
64
70
|
const { data, error } = await supabase.storage
|
|
65
71
|
.from("avatars")
|
|
@@ -75,7 +81,7 @@ const { data: url } = supabase.storage
|
|
|
75
81
|
Filesystem, S3-compatible, and image transformation backends, and how to configure each. [Read more →](/storage/adapters/)
|
|
76
82
|
</Card>
|
|
77
83
|
<Card title="Storage limitations">
|
|
78
|
-
What's not implemented yet:
|
|
84
|
+
What's not implemented yet: TUS uploads, webhooks, and the server-side S3 protocol. [Read more →](/storage/limitations/)
|
|
79
85
|
</Card>
|
|
80
86
|
</CardGrid>
|
|
81
87
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/lite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Lightweight TypeScript-native Supabase implementation on SQLite (alpha). PostgREST + GoTrue compatible — use @supabase/supabase-js as-is.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
"build": "bun run build.ts",
|
|
122
122
|
"build:all": "bun run build:static && bun run build.ts --types --minify",
|
|
123
123
|
"build:static": "vite build",
|
|
124
|
+
"check:bundle": "bun run internal/bundle-budget.ts",
|
|
124
125
|
"watch:static": "vite build --watch",
|
|
125
126
|
"dev:bun": "bun run --hot dev/bun/bun.dev.ts",
|
|
126
127
|
"debug:bun": "bun run --hot dev/debug.ts",
|
package/skills/supalite/SKILL.md
CHANGED
|
@@ -27,7 +27,7 @@ The package is pre-1.0 and changes fast. **Do not rely on this skill's specifics
|
|
|
27
27
|
| Non-Vite app, want auto schema-reload | `lite dev` (separate process) | Watches `schemas/*.sql`. |
|
|
28
28
|
| Non-Vite app, manual control / CI / prod-like | `lite start` (separate process) | No watch, no auto-migrate. |
|
|
29
29
|
|
|
30
|
-
**Never** run `lite dev
|
|
30
|
+
**Never** run more than one backend for a project: do not combine `lite dev`, `lite start`, or the Vite plugin against the same SQLite file.
|
|
31
31
|
|
|
32
32
|
## Patterns, limitations, anti-patterns
|
|
33
33
|
|