@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.
Files changed (50) hide show
  1. package/FEATURES.md +6 -6
  2. package/LIMITATIONS.md +8 -6
  3. package/PATTERNS.md +66 -6
  4. package/README.md +15 -8
  5. package/STATUS.md +26 -19
  6. package/dist/{Connection-ZWTDByQ5.d.ts → Connection-f_d5HhQ0.d.ts} +301 -293
  7. package/dist/cli/index.js +127 -124
  8. package/dist/cli/lib.d.ts +1 -12
  9. package/dist/cli/lib.js +44 -39
  10. package/dist/db/fallback.d.ts +1 -1
  11. package/dist/db/postgres/PostgresConnection.js +18 -18
  12. package/dist/db/postgres/pglite/PgliteConnection.js +4 -4
  13. package/dist/index.d.ts +90 -38
  14. package/dist/index.js +181 -73
  15. package/dist/static/.vite/manifest.json +34 -2
  16. package/dist/static/assets/InterVariable-Dx4kXJAl.woff2 +0 -0
  17. package/dist/static/assets/InterVariable-Italic-DpCbqKDY.woff2 +0 -0
  18. package/dist/static/assets/SourceCodePro-Variable-BP8Zz55n.woff2 +0 -0
  19. package/dist/static/assets/SourceCodePro-Variable-Italic-eALmlzX7.woff2 +0 -0
  20. package/dist/static/assets/main-BY4iigay.css +1 -0
  21. package/dist/static/assets/main-BnQ-v4V9.js +199 -0
  22. package/dist/static/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2 +0 -0
  23. package/dist/static/assets/manrope-latin-wght-normal-DHIcAJRg.woff2 +0 -0
  24. package/dist/vite/index.d.ts +601 -29
  25. package/dist/vite/index.js +2 -2
  26. package/docs/auth/overview.mdx +1 -1
  27. package/docs/auth/supported-flows.mdx +4 -4
  28. package/docs/compatibility.mdx +4 -4
  29. package/docs/database/migrations.mdx +2 -0
  30. package/docs/database/rls.mdx +7 -5
  31. package/docs/database/schemas.mdx +1 -1
  32. package/docs/integrations/embedded.mdx +20 -3
  33. package/docs/integrations/frameworks.mdx +1 -1
  34. package/docs/integrations/vite.mdx +4 -2
  35. package/docs/llms.txt +2 -2
  36. package/docs/quickstart.mdx +2 -2
  37. package/docs/running.mdx +20 -10
  38. package/docs/storage/limitations.mdx +2 -11
  39. package/docs/storage/overview.mdx +8 -2
  40. package/package.json +2 -1
  41. package/skills/supalite/SKILL.md +1 -1
  42. package/dist/static/assets/main-1bwWb_1q.js +0 -40996
  43. package/dist/static/assets/main-BDsRycsc.css +0 -4045
  44. package/dist/static/fonts/CustomFont-Black.woff2 +0 -0
  45. package/dist/static/fonts/CustomFont-BlackItalic.woff2 +0 -0
  46. package/dist/static/fonts/CustomFont-Bold.woff2 +0 -0
  47. package/dist/static/fonts/CustomFont-BoldItalic.woff2 +0 -0
  48. package/dist/static/fonts/CustomFont-Book.woff2 +0 -0
  49. package/dist/static/fonts/CustomFont-BookItalic.woff2 +0 -0
  50. package/dist/static/fonts/CustomFont-Medium.woff2 +0 -0
@@ -1,4 +1,4 @@
1
- import {loadEnv}from'vite';import A from'node:path';import G from'chokidar';import {watchSchema,migrationsDir,applyPendingMigrations,ensureInitialStructure,resolveConfiguredPublishableKey,createApi,listMigrationFiles,ensureSchema}from'@supabase/lite/cli';import {getRequestListener}from'@hono/node-server';try {
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 I=Object.create;var E=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var C=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var M=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var N=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of k(e))!L.call(t,r)&&r!==n&&E(t,r,{get:()=>e[r],enumerable:!(i=T(e,r))||i.enumerable});return t};var V=(t,e,n)=>(n=t!=null?I(C(t)):{},N(E(n,"default",{value:t,enumerable:true}),t));var O=M((ie,v)=>{var u=process||{},_=u.argv||[],d=u.env||{},W=!(d.NO_COLOR||_.includes("--no-color"))&&(!!d.FORCE_COLOR||_.includes("--color")||u.platform==="win32"||(u.stdout||{}).isTTY&&d.TERM!=="dumb"||!!d.CI),Y=(t,e,n=t)=>i=>{let r=""+i,o=r.indexOf(e,t.length);return ~o?t+K(r,e,n,o)+e:t+r+e},K=(t,e,n,i)=>{let r="",o=0;do r+=t.substring(o,i)+n,o=i+e.length,i=t.indexOf(e,o);while(~i);return r+t.substring(o)},P=(t=W)=>{let e=t?Y:()=>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")}};v.exports=P();v.exports.createColors=P;});var b=V(O());var j=Symbol("lite-peer-address");function R(t,e){e&&Object.defineProperty(t,j,{value:e,enumerable:false,configurable:false,writable:false});}var h=["/auth/v1","/rest/v1","/_system"];function w(t,e=h){let n=getRequestListener(async(i,...r)=>{let o=r[0]?.incoming;return R(i,o?.socket?.remoteAddress),t.fetch(i)});return (i,r,o)=>{let c=i.url??"";if(!e.some(p=>c===p||c.startsWith(`${p}/`)||c.startsWith(`${p}?`)))return o();n(i,r).catch(o);}}function ee(t,e){let n=t.root?A.resolve(t.root):process.cwd(),r=t.envFile===false||t.envDir===false?false:t.envDir?A.resolve(n,t.envDir):n,o=t.envPrefix??"VITE_";return loadEnv(e,r,o)}function te(t={}){let{config:e,migrateOnBoot:n=true,watchSchema:i=true,forceSchema:r=false,initOnBoot:o=true,prefixes:c=h,admin:p}=t,s;async function B(a){o&&await ensureInitialStructure({recreate:false,template:false});let l=await(await createApi({withSupabaseClient:false})).project.local.createApp(e,a);if(r&&console.log(b.default.yellow("\u26A0 supalite: forceSchema=true \u2014 data-loss migrations allowed")),n){if((await listMigrationFiles(l)).length>0){let m=await applyPendingMigrations(l);for(let x of m.applied)console.log(b.default.green(" \u2713"),"Applied migration",b.default.cyan(x.filename));}await ensureSchema(l,{force:r});}return l}async function S(){try{await s?.connection.close();}catch{}}return {name:"@supabase/lite",async config(a,{mode:f}){let l=ee(a,f),g=a.define??{},m={},x=(y,F)=>y in l||F in g;if(x("VITE_SUPABASE_URL","import.meta.env.VITE_SUPABASE_URL")||(m["import.meta.env.VITE_SUPABASE_URL"]="window.location.origin"),!x("VITE_SUPABASE_ANON_KEY","import.meta.env.VITE_SUPABASE_ANON_KEY")){o&&await ensureInitialStructure({recreate:false,template:false});let y=await resolveConfiguredPublishableKey(e);m["import.meta.env.VITE_SUPABASE_ANON_KEY"]=JSON.stringify(y??"sb-lite-anon-key");}return {define:m}},async configureServer(a){if(s=await B({admin:p,adminDefault:true}),a.middlewares.use(w(s,c)),i){let f=watchSchema(s,{translate:true,force:r}),l=A.join(process.cwd(),"supabase",migrationsDir(s)),g=G.watch(l,{ignoreInitial:true});g.on("add",async()=>{try{let m=await applyPendingMigrations(s);for(let x of m.applied)console.log(b.default.green(" \u2713"),"Applied",b.default.cyan(x.filename));}catch(m){console.error(b.default.red("Migration failed: "+String(m.message??m)));}}),a.httpServer?.once("close",()=>{g.close(),f();});}a.httpServer?.once("close",S);},async configurePreviewServer(a){s=await B({admin:false,adminDefault:false}),a.middlewares.use(w(s,c)),a.httpServer?.once("close",S);},async closeBundle(){await S();}}}var pe=te;export{h as DEFAULT_PREFIXES,pe as default,w as honoMiddleware,te as supalite};
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};
@@ -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 `lite start` serve keyless `/rest/v1` and `/storage/v1` requests as `service_role`, so the browser studio can do admin work without a secret key shipping to the browser (which the browser guard would reject anyway). The Vite dev server does the same for `/rest/v1`; it does not mount `/storage/v1` unless you add it to the plugin's `prefixes`. Only same-origin requests on a loopback socket **and** a loopback hostname are elevated that pairing is what blocks both LAN peers spoofing `Host` and DNS rebinding and any request carrying a key or an `Authorization` header keeps its normal role. Turn it off with `--no-admin`; it is off by default for embedders and in `vite preview`.
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://localhost:54321", "<anon-key>");
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://localhost:54321", "<anon-key>");
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://localhost:54321", "<anon-key>", {
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://localhost:54321", "<anon-key>", {
104
+ const supabase = createClient("http://127.0.0.1:54321", "<anon-key>", {
105
105
  auth: { flowType: "pkce", detectSessionInUrl: false },
106
106
  });
107
107
 
@@ -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, and image transforms are done; role-based access control is the main gap.
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) | ⚠️ | ⚠️ | ⚠️ | API keys now resolve roles for route-level checks; no per-object RLS yet |
74
- | RLS policies on storage objects | 🔄 | 🔄 | 🔄 | Planned |
75
- | **Total** | **20/20 methods** | **20/20 methods** | **20/20 methods** | Access control and `/status` health endpoint pending |
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
@@ -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 SQLite, this RLS and schema metadata is derived from your applied migrations and persisted to `supabase/.temp/.deparse-cache.json` so `lite start` doesn't need to reparse SQL at boot. A declarative apply (`lite dev` / the Vite plugin) and `lite db reset` always rewrite it; `lite migration up` only rewrites it on a migrations-only project, since on a mixed project migration-derived metadata would not describe the declarative schema the live database still carries. On a migrations-only project the file is safe to delete — `lite start` rebuilds it from the applied migration history. On a declarative project (`supabase/schemas/*.sql`) `lite start` will not rebuild it: it runs the migrations workflow and never applies schema files, so a missing or invalidated cache is always refused, and it points you at `lite db diff -f <name>` + `lite db reset`, or at `lite dev`. That pair is the transition that establishes migration authority — the generated migration includes your `ENABLE ROW LEVEL SECURITY` and `CREATE POLICY` statements, and `lite db reset` is destructive, so the replayed migration state becomes the authoritative one and `lite start` boots again. Anything `schemas/*.sql` declares that you never captured with `lite db diff -f`, policies included, is not in the reset database. RLS metadata also follows the table lifecycle exactly as Postgres does: a migration that renames a table carries enforcement and its policies to the new name, and one that drops a table (even if it recreates it under the same name) drops them. Whenever the metadata is missing and can't be regenerated, requests to tables with RLS enabled are denied by default (empty results for `SELECT`/`DELETE`, a policy violation for `INSERT`/`UPDATE`) rather than served with RLS off; `service_role` still bypasses RLS as usual.
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 the Vite dev server), requests that carry no credential at all run as `service_role` and skip RLS entirely. To exercise your policies, send an `apikey`:
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" localhost:54321/rest/v1/notes
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
- localhost:54321/rest/v1/notes
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 that Supabase exposes under separate schemas are handled internally by Supabase Lite; you don't need to (and can't) create competing user-defined schemas with the same names.
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({ fetch: app.fetch });
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({ port: 3000, fetch: app.fetch });
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://localhost:3000", "<sb_publishable_...>");
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 running `lite start`, with `supabase/` bind-mounted from the host 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 before the first start, then serve with `lite start`.
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. It isn't a port conflict (the CLI binds `[api].port` from `config.toml`; the plugin rides on the Vite dev server's port). The issue is two backends driving the same `supabase/` project and SQLite file, racing migrations and schema watchers. Pick one process model per project, see [Running Supabase Lite](/running).
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`. (Admin mode also covers `/storage/v1`, but the plugin does not mount that prefix by default — add it to `prefixes` first.) Only takes effect during `vite`/`vite dev`; `vite preview` never enables it, regardless of this option. Set `false` to keep key enforcement on every request while developing. An `admin` value in `config.toml` is used only when this option is unset. |
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.9.1-next.1. Same content as https://docs.lite.dev, which tracks the latest release.
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: access control, RLS on objects, TUS uploads, webhooks, and the S3-compatible server protocol.
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
 
@@ -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://localhost:54321`. `lite init` also generated a publishable/secret API key pair into root `.env` and printed them.
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://localhost:54321",
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, RLS and schema metadata is written to `supabase/.temp/.deparse-cache.json` when the declarative schema translates (`lite dev`, the Vite plugin), on every `lite db reset`, and on `lite migration up` in a migrations-only project (in a project that also has `supabase/schemas/*.sql`, `migration up` deliberately leaves the declarative snapshot alone). `lite start` loads this cache at boot to enforce RLS without reparsing SQL. If the file is missing or stale, a migrations-only project recalculates from the applied migration history never from migration files that haven't been run. If nothing recovers and the database has RLS-enabled tables, `lite start` exits with an error instead of serving with RLS silently off.
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
- `lite start` is the migrations workflow, so it never applies `supabase/schemas/*.sql`. A declarative project starts fine while the cache from its last `lite dev` (or Vite plugin) run is still valid; without a valid cache it is always refused, with no exceptions. `lite start` will not re-derive RLS from schema files it cannot prove were applied, and it will not substitute migration-only metadata for them either. Keep developing with `lite dev`, which applies the schema and rewrites the cache, or ship the schema as a migration.
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 db diff -f <name>` then `lite db reset` is the transition that establishes migration authority. The generated migration carries your RLS (`ALTER TABLE ... ENABLE ROW LEVEL SECURITY` and `CREATE POLICY`) along with the tables, and `lite db reset` is destructive: it replays the migrations onto a fresh database, so afterwards the migration-derived metadata describes that database exactly and is always persisted, and `lite start` boots again. The flip side is that `lite db reset` on a project with un-diffed declarative changes adopts **only** the migration state anything `schemas/*.sql` declares that you never captured, policies included, is not in the reset database. Run `lite db diff -f <name>` first; reset warns when it sees pending declarative changes. `lite migration up` is not the transition: it is non-destructive, so the generated migration re-creates objects the declarative apply already created and fails against the live development database, and it deliberately never overwrites the declarative snapshot on disk.
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 the Vite dev server 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.
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` (the CLI listens on all interfaces), 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
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 # off: keyless requests are no longer elevated
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. **"It runs on my machine" is not per-request trust.** The CLI binds `0.0.0.0`, and any page open in your browser can reach `localhost`. The same-origin and loopback-socket checks are what keep admin mode from being a way to read your local database from a web page or from another machine on your network. Admin mode is for local development only; it is off by default in the embedded/programmatic API and in `vite preview`.
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
- Precedence, when several places have an opinion: an explicit `--no-admin` (or the plugin's `admin` option) wins, then `options.server.admin` in `config.toml`, then the launcher's default. So a config file can opt a project out of admin mode entirely, but it can never re-enable it after you passed `--no-admin`.
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 the `App` yourself is the one case where only the hostname check applies, because there is no socket the library owns. Only set `options.server.admin` on a server you know is bound to loopback.
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. Two backends over the same SQLite file (two CLI processes, or a process plus the Vite plugin) race migrations and schema watchers against one database, even though they don't share a port.
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: access control, RLS on objects, TUS uploads, webhooks, and the S3-compatible server protocol."
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 the target access-control model these gaps are working toward, see [Supabase Storage access control](https://supabase.com/docs/guides/storage). For what is implemented today, see [Storage overview](/storage/overview) and [Storage adapters](/storage/adapters).
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: RLS on objects, role-based access, TUS uploads, webhooks. [Read more →](/storage/limitations/)
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.9.1-next.1",
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",
@@ -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` or `lite start` alongside the Vite plugin port collision.
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