@biltme/backend
v0.7.0
Published
Bilt backend SDK — thin shim over @supabase/supabase-js for bilt-cloud projects
Readme
@biltme/backend
Thin shim over @supabase/supabase-js
for Bilt projects. Public surface = supabase-js's surface; this package adds
storage adapter helpers, a stable import for the agent prompt template, and
re-exports the functions error classes for clean catch blocks.
Install
bun add @biltme/backend
# Expo only — also install AsyncStorage as the session store
bun add @react-native-async-storage/async-storageQuick start (Expo)
import { createClient, asyncStorage } from '@biltme/backend'
import AsyncStorage from '@react-native-async-storage/async-storage'
const url = process.env.EXPO_PUBLIC_BILT_URL! // https://<pid>.cloud.bilt.me
const key = process.env.EXPO_PUBLIC_BILT_ANON_KEY!
export const bilt = createClient(url, key, {
auth: {
storage: asyncStorage(AsyncStorage),
persistSession: true,
autoRefreshToken: true,
detectSessionInUrl: false,
},
})Every supabase-js method works:
const { data, error } = await bilt.auth.signInWithPassword({ email, password })
const { data: rows } = await bilt.from('notes').select('*')Typed queries
createClient reads the schema off an empty Database interface this package
exports. Augment it and the columns of every table in the file are typed:
declare module '@biltme/backend' {
interface Database {
public: {
Tables: {
notes: {
Row: { id: string; title: string; tags: string[] | null }
Insert: { id?: string; title: string; tags?: string[] | null }
Update: { id?: string; title?: string; tags?: string[] | null }
Relationships: []
}
}
Views: Record<never, never>
Functions: Record<never, never>
Enums: Record<never, never>
CompositeTypes: Record<never, never>
}
}
}You do not write that by hand. bilt-cloud regenerates it from the live schema on
every bilt_apply_sql, and the agent drops it into the project. Because it is a
module augmentation rather than createClient<Database>, there is no generic to
thread through and nothing to keep in sync at the call site.
Every key bilt-cloud emits is closed. A table, view or column that is not in
the file fails to compile, and so does an rpc() call to a function the file
does not list or with an argument it does not have. A stale file therefore
blocks a build until the next schema change regenerates it.
Leave Database unaugmented and rows come back any, which is what 0.5.x did.
Naming a row
Four helpers resolve over that same augmented Database, so a row can cross a
component boundary without being restated:
import type { Tables, TablesInsert, TablesUpdate, Enums } from '@biltme/backend'
function MessageCard({ message }: { message: Tables<'messages'> }) { … }
function draft(): TablesInsert<'messages'> { … }
function patch(): TablesUpdate<'messages'> { … }
function badge(status: Enums<'order_status'>) { … }Tables spans tables and views and gives the same type select() returns per
element. TablesInsert and TablesUpdate take tables only, as in supabase-js,
so a writable view has a row name but no payload name. Enums resolves to the
union bilt-cloud renders under Database['public']['Enums'], so a switch over
it is checked for exhaustiveness.
Before the first schema change there are no names, and passing one is a compile
error rather than any.
Calling functions
const { data, error } = await bilt.functions.invoke('send-email', {
body: { to: '[email protected]', subject: 'Welcome' },
})Auth header is set automatically: the active session's access token if signed
in, otherwise the anon key. Functions deployed with verify_jwt: true require
either signed-in session or the anon key; verify_jwt: false accepts unsigned
requests.
Catch the typed error classes:
import { FunctionsHttpError, FunctionsRelayError, FunctionsFetchError } from '@biltme/backend'
const { data, error } = await bilt.functions.invoke('send-email', { body: {...} })
if (error instanceof FunctionsHttpError) {
// user function returned 4xx/5xx — inspect error.context (Response)
} else if (error instanceof FunctionsRelayError) {
// bilt-cloud / pool relay failed (function not found, cold start error)
} else if (error instanceof FunctionsFetchError) {
// transport-level (DNS, TCP, TLS)
}region option on invoke() is accepted but silently ignored — bilt-cloud is
single-region.
Storage
// Admin (server-side, service key) — bucket lifecycle
await bilt.storage.createBucket('avatars', { public: false, fileSizeLimit: 5 * 1024 * 1024 })
await bilt.storage.createBucket('public-assets', { public: true })
// Upload (RLS-gated for anon-key / user-token; bypassed for service key)
const { data, error } = await bilt.storage
.from('avatars')
.upload(`${user.id}/photo.jpg`, file, { contentType: 'image/jpeg', upsert: true })
// Public bucket → URL is unauthenticated
const { data: { publicUrl } } = bilt.storage
.from('public-assets')
.getPublicUrl('logo.png')
// Private bucket → time-limited signed URL (up to 7 days)
const { data, error } = await bilt.storage
.from('avatars')
.createSignedUrl(`${user.id}/photo.jpg`, 3600)
// Catch storage errors:
import { StorageApiError } from '@biltme/backend'
if (error instanceof StorageApiError) { /* … */ }Bucket admin (createBucket, updateBucket, deleteBucket, emptyBucket,
listBuckets) is ServiceKey-only. Object ops are gated by Postgres RLS
policies on storage.objects — author them with the bilt_storage_apply
agent tool or the SQL editor.
Web
import { createClient, webStorage } from '@biltme/backend'
const bilt = createClient(url, key, {
auth: { storage: webStorage(), persistSession: true, autoRefreshToken: true },
})Server (no session persistence)
import { createClient, memoryStorage } from '@biltme/backend'
const bilt = createClient(url, key, {
auth: { storage: memoryStorage(), persistSession: false, autoRefreshToken: false },
})What this package adds vs @supabase/supabase-js
createClientis supabase-js'screateClient, re-typed so that a call with no type arguments defaults its schema to the augmentableDatabaseinterface above instead ofany.Database, the empty interface a project augments to type its schema.asyncStorage(impl)factory wrapping@react-native-async-storage/async-storageto the supabase-js storage shape.webStorage()andmemoryStorage()for completeness.FunctionsHttpError,FunctionsRelayError,FunctionsFetchError,FunctionRegionre-exports for cleancatchand option typing.StorageApiErrorre-export for storagecatchblocks.- Stable import path for the bilt-agent prompt template.
Docs
For everything else (auth flows, RLS, OAuth, realtime, functions reference), use Supabase's docs. The bilt backend speaks the same wire protocol.
