@resultdev/sdk
v0.12.0
Published
Result Backend SDK - authentication, database, storage, functions, realtime, AI, email, web analytics and customer support for apps built on Result.
Maintainers
Readme
@resultdev/sdk
The SDK for apps built on Result Backend - authentication, database, file storage, serverless functions, realtime, AI models, transactional email, web analytics and customer support, one client.
npm install @resultdev/sdkimport { createClient } from "@resultdev/sdk";
export const backend = createClient({
baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
anonKey: process.env.NEXT_PUBLIC_BACKEND_ANON_KEY!,
});Database
const { data, error } = await backend.database
.from("todos")
.select()
.order("created_at", { ascending: false });
await backend.database.from("todos").insert([{ title: "Hello" }]);
await backend.database.from("todos").update({ done: true }).eq("id", id);
await backend.database.from("todos").delete().eq("id", id);Authentication
await backend.auth.signInWithOAuth("google", {
redirectTo: window.location.origin,
});
await backend.auth.signUp({ email, password, name });
await backend.auth.signInWithPassword({ email, password });
const { data } = await backend.auth.getCurrentUser();
await backend.auth.signOut();Once signed in, every database/storage/function call carries the user's session automatically - row-level-secured tables just work.
Google and GitHub are configured on every backend, and there is no callback
route to build: the SDK exchanges the code when the client initializes on the
page it came back to. Call it from a click handler, not an effect. An app
running inside an iframe gets a popup instead of a redirect, because providers
refuse to be framed; override with flow: "redirect" | "popup" | "auto".
Sessions persist across reloads, new tabs and framed previews on their own:
the access token is kept in memory and the refresh token in a first-party
cookie. Call getCurrentUser() once when your app mounts, since that is what
restores the session, and keep a loading state while it resolves.
onAuthStateChange() reports changes from that point on and does not fire for
a session that already exists, so use both. Nothing is written to
localStorage, and no refresh timer or callback route is needed.
Storage
const { data } = await backend.storage.from("uploads").uploadAuto(file);Functions
await backend.functions.invoke("my-function", { method: "POST", body });Realtime
await backend.realtime.connect();
await backend.realtime.subscribe("chat:room-1");
backend.realtime.on("new_message", (msg) => console.log(msg));
await backend.realtime.publish("chat:room-1", "new_message", { text: "Hi" });Channels are declared once with the admin key (see the backend guide);
subscribing to an undeclared channel fails with REALTIME_UNAUTHORIZED.
AI models
const completion = await backend.ai.chat.completions.create({
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "Hello!" }],
});
const embeddings = await backend.ai.embeddings.create({
model: "openai/text-embedding-3-small",
input: "hello world",
});
const image = await backend.ai.images.generate({
model: "google/gemini-2.5-flash-image",
prompt: "A sunset over mountains",
});await backend.emails.send({ to, subject, html });Web analytics
No script tag: pass your site id and pageviews, sessions and SPA route changes are tracked automatically wherever the client runs in a browser. No cookies.
export const backend = createClient({
baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
anonKey: process.env.NEXT_PUBLIC_BACKEND_ANON_KEY!,
analytics: { siteId: "SITE_ID" },
});
backend.analytics.track("Signup", { plan: "pro" });
backend.analytics.identify("user-123");Payments
The same code uses Result Payments or the workspace's connected live Stripe account. The provider is resolved by Result, so apps do not branch on Stripe or Paddle and do not hold payment-provider keys.
const { data: plans } = await backend.payments.plans();
await backend.payments.checkout({
plan: "pro-monthly",
successUrl: `${window.location.origin}/account?checkout=success`,
});
const { data: subscribed } = await backend.payments.hasAccess("pro-monthly");
const { data: purchased } = await backend.payments.hasPurchased("starter-pack");
const { data: portalUrl } = await backend.payments.portalUrl();checkout() requires a signed-in buyer. Verified provider events write
customers, subscriptions and one-time purchases into the app's own billing
tables; redirects never grant access. changePlan() and cancel() also use
the active provider automatically.
Customer support
A support launcher on your pages. It opens your Result support page in a panel, and conversations land in your Result support inbox. Visitors need no account.
export const backend = createClient({
baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
anonKey: process.env.NEXT_PUBLIC_BACKEND_ANON_KEY!,
support: { handle: "acme" }, // your @handle, without the @
});
useEffect(() => {
backend.support.mount();
return () => backend.support.unmount();
}, []);No support UI ships in this package. The panel frames the hosted page, so the conversation surface updates without a release.
Server-side (Next.js SSR)
import { createServerClient } from "@resultdev/sdk/ssr";Admin operations (schema changes, service-level reads) use your app's admin key server-side - see your app's page in the Result dashboard for the credentials and the full agent prompt.
