The official JavaScript/TypeScript SDK for DYPAI — backend-as-a-service with visual workflows, AI agents, and MCP.
npm install @dypai-ai/client-sdk
import { createClient } from '@dypai-ai/client-sdk';
const dypai = createClient('https://your-project.dypai.ai');
// Sign in
const { data, error } = await dypai.auth.signInWithPassword({
email: 'user@example.com',
password: 'securepassword',
});
// Call an endpoint
const { data: products } = await dypai.api.get('list_products');
npm install @dypai-ai/client-sdk
React hooks (optional):
# React hooks are included — no extra package needed
# Import from '@dypai-ai/client-sdk/react'
import { createClient } from '@dypai-ai/client-sdk';
// Basic — JWT-authenticated apps
const dypai = createClient('https://your-project.dypai.ai');
// With API key — for public endpoints that don't require auth
const dypai = createClient(
'https://your-project.dypai.ai',
'your-public-api-key'
);
// With options
const dypai = createClient('https://your-project.dypai.ai', {
auth: {
autoRefreshToken: true, // default: true
persistSession: true, // default: true
},
redirects: {
passwordRecovery: '/reset-password',
signIn: '/dashboard',
},
});
const { data, error } = await dypai.auth.signUp({
email: 'user@example.com',
password: 'securepassword',
name: 'John Doe',
});
if (error) {
console.error(error.message);
} else if (data.confirmationRequired) {
console.log('Check your email to confirm your account');
}
const { data, error } = await dypai.auth.signInWithPassword({
email: 'user@example.com',
password: 'securepassword',
});
if (data) {
console.log('Welcome!', data.user.email);
}
await dypai.auth.signInWithOAuth('google');
// Redirects to Google login page
// Send magic link
const { error } = await dypai.auth.signInWithOtp({ email: 'user@example.com' });
// Verify OTP code
const { data, error } = await dypai.auth.verifyOtp({
email: 'user@example.com',
token: '123456',
type: 'email',
});
const { data: user, error } = await dypai.auth.getUser();
console.log(user.email, user.role);
const { data: session, error } = await dypai.auth.getSession();
if (session) {
console.log('Token:', session.token);
console.log('User:', session.user.email);
}
// Request reset email
const { error } = await dypai.auth.resetPasswordForEmail('user@example.com');
// Set new password on the passwordRecovery route.
// The SDK stores the reset token and cleans the URL automatically.
const { error } = await dypai.auth.setPassword('new-secure-password');
const { error } = await dypai.auth.resendConfirmationEmail('user@example.com');
const { data, error } = await dypai.auth.updateUser({
data: { name: 'Jane Doe', avatar_url: 'https://...' },
});
await dypai.auth.signOut();
const { data: { subscription } } = dypai.auth.onAuthStateChange((event, session) => {
console.log(event); // 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | ...
if (event === 'SIGNED_IN') {
console.log('User:', session.user.email);
}
if (event === 'SIGNED_OUT') {
window.location.href = '/login';
}
});
// Stop listening
subscription.unsubscribe();
Interact with your project's database tables using a fluent API:
// Get all rows
const { data, error } = await dypai.db.from('products').select();
// With filters
const { data, error } = await dypai.db.from('products').select({
category: 'electronics',
active: true,
});
const { data, error } = await dypai.db.from('products').insert({
name: 'Wireless Headphones',
price: 59.99,
category: 'electronics',
});
const { data, error } = await dypai.db.from('products').update('prod_123', {
price: 49.99,
on_sale: true,
});
const { data, error } = await dypai.db.from('products').delete('prod_123');
Call your custom DYPAI endpoints (workflows exposed as APIs):
const { data, error } = await dypai.api.get('list_products');
// With query parameters
const { data, error } = await dypai.api.get('search', {
params: { q: 'headphones', limit: 20 },
});
const { data, error } = await dypai.api.post('create_order', {
product_id: 'prod_123',
quantity: 2,
});
const { data, error } = await dypai.api.put('update_profile', {
name: 'Jane Doe',
bio: 'Developer',
});
const { data, error } = await dypai.api.patch('update_settings', {
theme: 'dark',
});
const { data, error } = await dypai.api.delete('cancel_subscription', {
params: { subscription_id: 'sub_123' },
});
Upload files using Smart Upload (signed URLs, direct upload to cloud storage). Since SDK 1.12.0, params.operation defaults to "upload" on dedicated upload endpoints:
const file = document.querySelector('input[type="file"]').files[0];
const { data, error } = await dypai.api.upload('upload-avatar', file, {
params: {
file_path: `avatars/${userId}.jpg`,
// Domain params are fine here, e.g. task_id/content_type.
// Do not pass confirm or client_upload; the SDK controls Smart Upload.
},
onProgress: (percent) => {
console.log(`Upload: ${percent}%`);
},
});
Backend (Flow): prefer storage.upload({ bucket, path }) from @dypai-ai/flow — see platform docs file storage / flow ts.
confirm and client_upload are internal Smart Upload flags. They may exist in the endpoint input schema, but browser code should not include them in params.
await dypai.api.download('download-report', {
file_path: 'reports/monthly.pdf',
});
Server-side only. This is for scripts, migrations, seeds, and backend code. Never expose the
serviceRoleKeyin browser/client-side code. For end-user data access, use Endpoints or Database CRUD.
Direct database access bypasses endpoints and workflows. It requires a serviceRoleKey:
import { createClient } from '@dypai-ai/client-sdk';
// Server-side only (Node.js, Bun, Deno)
const dypai = createClient('https://your-project.dypai.ai', {
serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY,
});
const { data } = await dypai.db.direct
.from('products')
.eq('category', 'electronics')
.gt('price', 50)
.orderBy('price', 'DESC')
.limit(20)
.select();
// Auto-chunked at 1,000 rows per request
const { data, error } = await dypai.db.direct
.from('products')
.insert(tenThousandRows);
await dypai.db.direct
.from('products')
.eq('category', 'deprecated')
.update({ active: false });
await dypai.db.direct
.from('products')
.eq('id', 'abc123')
.delete();
await dypai.db.direct
.from('products')
.upsert({ id: 'prod_1', name: 'Widget', price: 12.99 }, 'id');
// Migrations
await dypai.db.direct.sql('ALTER TABLE products ADD COLUMN sku TEXT');
// Queries with bind parameters ($1, $2, ...)
const { data } = await dypai.db.direct.sql(
'SELECT category, COUNT(*) as count FROM products WHERE price > $1 GROUP BY category',
[25]
);
| Method | SQL |
|---|---|
.eq(col, val) |
col = val |
.neq(col, val) |
col != val |
.gt(col, val) |
col > val |
.gte(col, val) |
col >= val |
.lt(col, val) |
col < val |
.lte(col, val) |
col <= val |
.like(col, val) |
col ILIKE %val% |
.in(col, [a,b]) |
col IN (a, b) |
.isNull(col) |
col IS NULL |
.notNull(col) |
col IS NOT NULL |
Manage application users (requires manage_users permission):
const { data, error } = await dypai.users.list({ page: 1, per_page: 50 });
console.log(data.users); // [{ id, email, role, ... }, ...]
const { data, error } = await dypai.users.create({
email: 'new@example.com',
password: 'securepassword',
role: 'editor',
user_metadata: { name: 'New User' },
});
const { data, error } = await dypai.users.update('user_id', {
role: 'admin',
});
const { data, error } = await dypai.users.delete('user_id');
// src/lib/dypai.ts
import { createClient } from '@dypai-ai/client-sdk';
export const dypai = createClient(import.meta.env.VITE_DYPAI_URL);
// src/App.tsx
import { DypaiProvider } from '@dypai-ai/client-sdk/react';
import { dypai } from './lib/dypai';
function App() {
return (
<DypaiProvider client={dypai}>
<Router />
</DypaiProvider>
);
}
import { useAuth } from '@dypai-ai/client-sdk/react';
function LoginPage() {
const { signIn, isLoading, isAuthenticated } = useAuth();
if (isAuthenticated) return <Navigate to="/dashboard" />;
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
const { error } = await signIn(email, password);
if (error) alert(error.message);
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={...} />
<input type="password" value={password} onChange={...} />
<button disabled={isLoading}>Sign In</button>
</form>
);
}
function Navbar() {
const { user, isAuthenticated, signOut } = useAuth();
if (!isAuthenticated) return <Link to="/login">Sign In</Link>;
return (
<div>
<span>{user.email}</span>
<button onClick={signOut}>Sign Out</button>
</div>
);
}
import { useEndpoint } from '@dypai-ai/client-sdk/react';
function ProductList() {
const { data: products, isLoading, error, refetch } = useEndpoint('list_products');
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<div>
{products.map(p => <ProductCard key={p.id} product={p} />)}
<button onClick={refetch}>Refresh</button>
</div>
);
}
// With params and auto-refetch
function Notifications() {
const { data } = useEndpoint('get_notifications', {
params: { unread: true },
refetchInterval: 30000, // every 30s
});
return <Badge count={data?.length ?? 0} />;
}
// Conditional fetching
function UserProfile({ userId }: { userId?: string }) {
const { data: profile } = useEndpoint('get_user_profile', {
params: { id: userId },
enabled: !!userId, // only fetch when userId exists
});
return profile ? <Profile data={profile} /> : null;
}
import { useAction } from '@dypai-ai/client-sdk/react';
function CreateProduct() {
const { mutate, isLoading } = useAction('create_product', {
onSuccess: (data) => toast.success(`Created: ${data.name}`),
onError: (err) => toast.error(err.message),
});
const handleSubmit = async (formData: ProductForm) => {
const { data, error } = await mutate(formData);
};
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
<button disabled={isLoading}>Create</button>
</form>
);
}
// PUT, PATCH, DELETE
const { mutate: updateTask } = useAction('update_task', { method: 'PUT' });
const { mutate: patchSettings } = useAction('settings', { method: 'PATCH' });
const { mutate: deleteItem } = useAction('delete_item', { method: 'DELETE' });
await updateTask({ id: '123', done: true });
await patchSettings({ theme: 'dark' });
await deleteItem({ id: '123' });
import { useChat } from '@dypai-ai/client-sdk/react';
function SupportChat() {
const { messages, input, setInput, sendMessage, isLoading, stop } = useChat('support_agent');
return (
<section>
{messages.map(message => (
<article key={message.id}>
<strong>{message.role}</strong>
<p>{message.content}</p>
{message.parts?.map((part, index) => {
if (part.type === 'tool-call') {
return <small key={index}>Calling {part.toolName}...</small>;
}
if (part.type === 'tool-result') {
return <small key={index}>{part.toolName} finished</small>;
}
return null;
})}
</article>
))}
<input value={input} onChange={event => setInput(event.target.value)} />
<button onClick={() => sendMessage()} disabled={isLoading}>Send</button>
{isLoading && <button onClick={stop}>Stop</button>}
</section>
);
}
useChat supports DYPAI agent streams that use the Vercel AI SDK UI Message Stream protocol, plus legacy DYPAI text streams for older engines. Assistant text is accumulated in message.content; streamed tool calls and tool results are exposed in message.parts.
import { useUpload } from '@dypai-ai/client-sdk/react';
function AvatarUpload() {
const { upload, progress, isUploading } = useUpload('upload-avatar', {
onSuccess: () => toast.success('Uploaded!'),
});
const handleFile = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
await upload(file, {
file_path: `avatars/${userId}.jpg`,
});
};
return (
<div>
<input type="file" accept="image/*" onChange={handleFile} disabled={isUploading} />
{isUploading && <ProgressBar value={progress} />}
</div>
);
}
import { ProtectedRoute } from '@dypai-ai/client-sdk/react';
// Basic protection
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
// With redirect
<ProtectedRoute redirectTo="/login">
<Dashboard />
</ProtectedRoute>
// Role-based access
<ProtectedRoute
roles={['admin', 'editor']}
loadingComponent={<Spinner />}
unauthorizedComponent={<AccessDenied />}
unauthenticatedComponent={<LoginPrompt />}
>
<AdminPanel />
</ProtectedRoute>
The SDK is fully typed. You can pass your database schema and endpoint map for complete type safety:
// Define your database schema
interface Database {
products: {
id: string;
name: string;
price: number;
active: boolean;
};
orders: {
id: string;
product_id: string;
quantity: number;
status: 'pending' | 'shipped' | 'delivered';
};
}
// Define your endpoint map.
// This map is normally auto-generated into dypai/types/endpoints.gen.ts — prefer
// importing it. The canonical shape is { method, input, output }
// (the older { response, params, body } form is deprecated and loses method-aware typing).
interface Api {
list_products: {
method: 'GET';
input: Record<string, never>;
output: Database['products'][];
};
create_order: {
method: 'POST';
input: { product_id: string; quantity: number };
output: Database['orders'];
};
}
// Create a fully typed client
const dypai = createClient<Database, Api>(
'https://your-project.dypai.ai'
);
// Now everything has autocomplete and type checking
const { data } = await dypai.db.from('products').select(); // data: Product[]
const { data } = await dypai.api.post('create_order', { // input is typed
product_id: 'prod_123',
quantity: 2,
});
Full auto-generated API docs: dypai-solutions.github.io/client-sdk
All SDK methods return a consistent { data, error } object:
const { data, error } = await dypai.auth.signInWithPassword({ ... });
if (error) {
console.error(error.message); // Human-readable message
console.error(error.status); // HTTP status code
console.error(error.code); // Error code (if available)
return;
}
// data is guaranteed to be non-null here
console.log(data);
MIT