diff --git a/mintlify/docs/docs.json b/mintlify/docs/docs.json index e4b85211..624bdc2f 100644 --- a/mintlify/docs/docs.json +++ b/mintlify/docs/docs.json @@ -54,7 +54,9 @@ "sdk/overview", "sdk/auth", "sdk/database", - "sdk/storage" + "sdk/storage", + "sdk/schema", + "sdk/mail" ] }, { diff --git a/mintlify/docs/sdk/auth.mdx b/mintlify/docs/sdk/auth.mdx index 67ccad5f..e23f8a02 100644 --- a/mintlify/docs/sdk/auth.mdx +++ b/mintlify/docs/sdk/auth.mdx @@ -1,12 +1,12 @@ --- title: "Auth" -description: "Create accounts, log in users, and retrieve the current session with the SDK auth module." -keywords: ["auth", "authentication", "login", "signup", "sdk"] +description: "Manage user accounts, sessions, and social authentication with the SDK auth module." +keywords: ["auth", "authentication", "login", "signup", "sdk", "social auth", "oauth"] --- -Access auth methods via `client.auth`. The module stores the session token from `login()` automatically, so you do not need to thread it through subsequent calls manually. +You can access auth methods via `client.auth`. When you call `login()` or `refreshToken()`, the module stores the `accessToken` and uses it for subsequent authenticated requests such as `me()` or `updateProfile()`. -## signUp +## `signUp` Create a new user account. @@ -14,150 +14,172 @@ Create a new user account. signUp(payload: SignUpPayload): Promise ``` -**Parameters** - -| Name | Type | Required | Description | -|---|---|---|---| -| `email` | `string` | Yes | User's email address. | -| `password` | `string` | Yes | User's password. | -| `name` | `string` | No | Display name. | - -**Returns** `AuthUser` - -```typescript -interface AuthUser { - _id: string; - email: string; - name?: string; - [key: string]: unknown; -} -``` - **Example** ```typescript const user = await client.auth.signUp({ email: 'alice@example.com', password: 'secret123', + username: 'alice_dev', name: 'Alice', }); - -console.log(user._id); // '507f1f77bcf86cd799439011' ``` --- -## login +## `login` -Authenticate an existing user. The returned token is stored internally in the auth module and used automatically by `me()`. +Authenticate an existing user. The returned `accessToken` is stored internally. ```typescript login(payload: LoginPayload): Promise ``` -**Parameters** - -| Name | Type | Required | Description | -|---|---|---|---| -| `email` | `string` | Yes | User's email address. | -| `password` | `string` | Yes | User's password. | - **Returns** `AuthResponse` ```typescript interface AuthResponse { - token: string; // accessToken — store this securely - user: AuthUser; + accessToken?: string; + token?: string; // Alias for accessToken (deprecated) + expiresIn?: string; + userId?: string; + user?: AuthUser; } ``` **Example** ```typescript -const { token, user } = await client.auth.login({ +const { accessToken, user } = await client.auth.login({ email: 'alice@example.com', password: 'secret123', }); - -console.log(token); // 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' -console.log(user.name); // 'Alice' ``` - -Store `token` securely — for example in `localStorage` or an HTTP-only cookie — and pass it to authenticated API calls. Never expose it in URLs or logs. - +--- + +## `refreshToken` + +Rotate the current access token. + +- **Browser**: Call without arguments. It will automatically use the `refreshToken` stored in your HTTP-only cookies. +- **Mobile/Node**: Pass the `refreshToken` string manually. + +```typescript +refreshToken(refreshToken?: string): Promise +``` --- -## me +## `me` -Fetch the profile of the currently authenticated user. If you call `login()` first, you do not need to pass a token explicitly — the module uses the stored session token automatically. +Fetch the profile of the currently authenticated user. ```typescript me(token?: string): Promise ``` -**Parameters** - -| Name | Type | Required | Description | -|---|---|---|---| -| `token` | `string` | No | Override the stored session token. | +--- -**Returns** `AuthUser` +## `updateProfile` -**Example — using the stored token after login()** +Update the authenticated user's profile fields. ```typescript -await client.auth.login({ email: 'alice@example.com', password: 'secret123' }); - -const user = await client.auth.me(); -console.log(user.email); // 'alice@example.com' +updateProfile(payload: UpdateProfilePayload, token?: string): Promise<{ message: string }> ``` -**Example — passing a token directly** +**Example** ```typescript -const token = localStorage.getItem('urbackend_token'); -const user = await client.auth.me(token); +await client.auth.updateProfile({ name: 'Alice Smith' }); ``` - -Calling `me()` without a token and without a prior `login()` in the same client instance throws an `AuthError`. Always pass the token explicitly if the client is newly initialized (for example, on page reload). - +--- + +## `changePassword` + +Change the authenticated user's password. + +```typescript +changePassword(payload: ChangePasswordPayload, token?: string): Promise<{ message: string }> +``` --- -## logout +## Social auth -Clear the locally stored session token. This does not invalidate the token on the server. +urBackend supports OAuth via GitHub and Google. + +### `socialStart` + +You receive a URL to initiate the OAuth flow. Redirect your user's browser to this URL. + +```typescript +socialStart(provider: 'github' | 'google'): string +``` + +### `socialExchange` + +Exchange the `rtCode` received at your callback URL for a refresh token. ```typescript -logout(): void +socialExchange(payload: SocialExchangePayload): Promise ``` **Example** ```typescript -client.auth.logout(); -// The stored session token is cleared. -// Subsequent calls to me() will require a token to be passed explicitly. +// At your /auth/callback page +const urlParams = new URLSearchParams(window.location.search); +const rtCode = urlParams.get('rtCode'); +const token = new URLSearchParams(window.location.hash.slice(1)).get('token'); + +if (!token || !rtCode) { + throw new Error('Missing required OAuth callback parameters'); +} + +const { refreshToken } = await client.auth.socialExchange({ token, rtCode }); ``` --- -## Error handling +## Account verification + +Use these methods to handle email OTP flows. + +| Method | Description | +|---|---| +| `verifyEmail(payload)` | Verify an account using the OTP sent to email. | +| `resendVerificationOtp(payload)` | Request a new verification OTP. | +| `requestPasswordReset(payload)` | Start the "forgot password" flow. | +| `resetPassword(payload)` | Complete password reset using an OTP. | + +--- + +## `publicProfile` + +Fetch a public-safe profile for any user by their username. This does not return sensitive fields like email or provider IDs. ```typescript -import urBackend, { AuthError } from '@urbackend/sdk'; +publicProfile(username: string): Promise +``` -const client = urBackend({ apiKey: process.env.URBACKEND_API_KEY }); +--- -try { - await client.auth.login({ email: 'alice@example.com', password: 'wrong' }); -} catch (e) { - if (e instanceof AuthError) { - console.error('Login failed:', e.message); - // e.statusCode is 401 or 403 - } -} +## `logout` + +Call this to revoke your current session on the server and clear the local token. + +```typescript +logout(token?: string): Promise<{ success: boolean; message: string }> ``` + +--- + +## Manual token management + +If you need to manage tokens manually (for example, after social auth), you can use these helper methods: + +- `getToken()`: Returns the current in-memory access token. +- `setToken(token)`: Manually set the access token for the client. diff --git a/mintlify/docs/sdk/database.mdx b/mintlify/docs/sdk/database.mdx index 75a09d78..5762b70a 100644 --- a/mintlify/docs/sdk/database.mdx +++ b/mintlify/docs/sdk/database.mdx @@ -1,47 +1,51 @@ --- title: "Database" description: "Read, insert, update, and delete documents in any collection using the SDK database module." -keywords: ["database", "collections", "crud", "sdk", "typescript"] +keywords: ["database", "collections", "crud", "sdk", "typescript", "rls", "query builder"] --- -Access database methods via `client.db`. Collections must be created in the [urBackend Dashboard](https://urbackend.bitbros.in) before you can write to them. Once registered, you can optionally define a schema or write arbitrary JSON documents. +Access database methods via `client.db`. Collections must be created in the [urBackend Dashboard](https://urbackend.bitbros.in) before you can interact with them. -All methods accept a generic type parameter `T` (extending `DocumentData`) so the returned documents are typed: +Methods that return documents accept a generic type parameter `T` (extending `DocumentData`) so returned documents are fully typed. Methods like `delete()` return status objects and do not accept `T`. +## Row-Level Security (RLS) + +If a collection has RLS enabled, you must provide the user's `accessToken` to perform write operations (`insert`, `update`, `patch`, `delete`). + +Example pattern: ```typescript -interface DocumentData { - _id: string; - [key: string]: unknown; -} +const { accessToken } = await client.auth.login({ ... }); +await client.db.insert('posts', { content: 'Hello' }, accessToken); ``` - -Use a `pk_live_...` (publishable) key for read operations in frontend code. Use a `sk_live_...` (secret) key for writes in server-side code only. - - --- ## getAll -Fetch all documents from a collection. +Fetch documents from a collection with optional filtering, sorting, and pagination. ```typescript -getAll(collection: string): Promise +getAll(collection: string, params?: QueryParams): Promise ``` -Returns an empty array if the collection does not exist yet. +**Query Parameters (`params`)** + +| Parameter | Type | Description | +|---|---|---| +| `filter` | `object` | Filter by field suffixes (e.g. `{ age_gt: 18 }`). | +| `sort` | `string` | Sort order (e.g. `"createdAt:desc"`). | +| `limit` | `number` | Max documents to return (max 100). | +| `page` | `number` | Page number for pagination. | +| `populate` | `string \| string[]` | Expand Reference fields into full objects. | **Example** ```typescript -interface Product { - _id: string; - name: string; - price: number; -} - -const products = await client.db.getAll('products'); -// products is Product[] +const products = await client.db.getAll('products', { + filter: { category: 'electronics', price_lt: 500 }, + sort: 'price:asc', + limit: 20 +}); ``` --- @@ -51,61 +55,50 @@ const products = await client.db.getAll('products'); Fetch a single document by its ID. ```typescript -getOne(collection: string, id: string): Promise +getOne(collection: string, id: string, options?: { populate?: string | string[] }): Promise ``` -Throws `NotFoundError` if no document with that ID exists. - **Example** ```typescript -const product = await client.db.getOne('products', '507f1f77bcf86cd799439011'); -console.log(product.name); // 'Chair' +const product = await client.db.getOne('products', 'id_123', { populate: 'vendor' }); ``` --- ## insert -Insert a new document into a collection. The collection must already exist in the dashboard. +Insert a new document. If RLS is enabled, the `token` parameter is required. ```typescript -insert(collection: string, data: Record): Promise +insert(collection: string, data: Record, token?: string): Promise ``` -Returns the inserted document, including its server-assigned `_id`. +--- -**Example** +## update -```typescript -const product = await client.db.insert('products', { - name: 'Chair', - price: 99, -}); +Update an existing document by its ID. This performs a **full replacement** of the document fields. -console.log(product._id); // '507f1f77bcf86cd799439011' +```typescript +update(collection: string, id: string, data: Record, token?: string): Promise ``` --- -## update +## patch -Update an existing document by its ID. The `data` object is merged into the existing document. +Partially update a document. Only the fields provided in `data` will be modified. ```typescript -update(collection: string, id: string, data: Record): Promise +patch(collection: string, id: string, data: Record, token?: string): Promise ``` -Returns the updated document. - **Example** ```typescript -const updated = await client.db.update('products', '507f1f77bcf86cd799439011', { - price: 79, -}); - -console.log(updated.price); // 79 +// Only updates the price, leaves other fields unchanged +await client.db.patch('products', 'id_123', { price: 45 }); ``` --- @@ -115,61 +108,22 @@ console.log(updated.price); // 79 Delete a document by its ID. ```typescript -delete(collection: string, id: string): Promise<{ deleted: boolean }> -``` - -**Example** - -```typescript -const result = await client.db.delete('products', '507f1f77bcf86cd799439011'); -console.log(result.deleted); // true +delete(collection: string, id: string, token?: string): Promise<{ deleted: boolean }> ``` --- -## TypeScript generics +## TypeScript Support -Passing a generic type to database methods gives you full type inference on the returned document. Define an interface that extends the `_id: string` field: +Define your interfaces to get full IDE autocomplete: ```typescript interface BlogPost { _id: string; title: string; - body: string; - publishedAt: string; + author: string; } -// Typed array const posts = await client.db.getAll('posts'); - -// Typed document -const post = await client.db.getOne('posts', id); -post.title; // string — no type assertion needed -``` - ---- - -## Error handling - -```typescript -import urBackend, { NotFoundError, AuthError, RateLimitError } from '@urbackend/sdk'; - -const client = urBackend({ apiKey: process.env.URBACKEND_API_KEY }); - -try { - const product = await client.db.getOne('products', id); -} catch (e) { - if (e instanceof NotFoundError) { - // Document does not exist - console.error('Product not found'); - } else if (e instanceof AuthError) { - // Invalid or missing API key - console.error('Unauthorized'); - } else if (e instanceof RateLimitError) { - // Exceeded 100 requests per 15 minutes per IP - console.error(`Rate limited. Retry after ${e.retryAfter}s`); - } else { - throw e; - } -} +posts[0].title; // typed as string ``` diff --git a/mintlify/docs/sdk/mail.mdx b/mintlify/docs/sdk/mail.mdx new file mode 100644 index 00000000..ac3aeb35 --- /dev/null +++ b/mintlify/docs/sdk/mail.mdx @@ -0,0 +1,49 @@ +--- +title: "Mail" +description: "Send transactional emails from your server using the SDK mail module." +keywords: ["mail", "email", "resend", "transactional email", "sdk"] +--- + +Access mail methods via `client.mail`. This module allows you to send emails using your project's configured mail provider (e.g. Resend). + + +Mail operations require a **Secret Key** (`sk_live_...`). This module should only be used in server-side environments (Node.js, Edge Functions, etc.). Never call these methods from a browser. + + +## send + +Send an email. + +```typescript +send(payload: SendMailPayload): Promise +``` + +**Parameters (`SendMailPayload`)** + +| Field | Type | Required | Description | +|---|---|---|---| +| `to` | `string \| string[]` | Yes | Recipient email address(es). | +| `subject` | `string` | Yes | Email subject line. | +| `text` | `string` | No | Plain text body. | +| `html` | `string` | No | HTML body. | + +**Returns (`SendMailResponse`)** + +| Field | Type | Description | +|---|---|---| +| `id` | `string \| null` | The message ID from the provider (`null` if unavailable). | +| `provider` | `"byok" \| "default"` | The provider used for sending. | +| `monthlyUsage` | `number` | Total emails sent this month. | +| `monthlyLimit` | `number` | Your project's monthly quota. | + +**Example** + +```typescript +const response = await client.mail.send({ + to: 'customer@example.com', + subject: 'Welcome to urBackend', + html: '

Hi there!

Thanks for joining.

' +}); + +console.log(response.id); +``` diff --git a/mintlify/docs/sdk/overview.mdx b/mintlify/docs/sdk/overview.mdx index 21734ccc..77246379 100644 --- a/mintlify/docs/sdk/overview.mdx +++ b/mintlify/docs/sdk/overview.mdx @@ -37,22 +37,30 @@ import urBackend from '@urbackend/sdk'; const client = urBackend({ apiKey: process.env.URBACKEND_API_KEY }); -// Sign up a new user -const user = await client.auth.signUp({ +// Log in an existing user +const { accessToken } = await client.auth.login({ email: 'alice@example.com', - password: 'secret123', - name: 'Alice', + password: 'secret123' }); -// Insert a document (collection must be created in the dashboard first) -const product = await client.db.insert('products', { name: 'Chair', price: 99 }); +// Insert a document with RLS support (passing the user token) +const product = await client.db.insert('products', { name: 'Chair', price: 99 }, accessToken); -// Fetch all documents -const products = await client.db.getAll('products'); +// Fetch documents with filtering and sorting +const products = await client.db.getAll('products', { + filter: { price_gt: 50 }, + sort: 'price:asc' +}); + +// ⚠️ Server-side only — requires a secret key (sk_live_...). +// Do not use this initialization in browser/client code. +const serverClient = urBackend({ apiKey: process.env.URBACKEND_SECRET_KEY }); -// Upload a file (browser) -const input = document.querySelector('input[type="file"]'); -const { url, path } = await client.storage.upload(input.files[0]); +await serverClient.mail.send({ + to: 'user@example.com', + subject: 'Order Confirmed', + text: 'Your chair is on the way!' +}); ``` ## TypeScript generics @@ -109,23 +117,29 @@ try { ## API key security -Use `pk_live_...` (publishable key) in frontend/browser code. It allows read operations only. +Use `pk_live_...` (publishable key) in frontend/browser code. It allows read operations by default, and write operations only if RLS is enabled. -Use `sk_live_...` (secret key) in server-side code only. It allows reads and writes. Never expose it in client-side bundles or public repositories. +Use `sk_live_...` (secret key) in server-side code only. It allows full CRUD access. Never expose it in client-side bundles or public repositories. -The SDK logs a console warning if it detects you are using it in a browser context, as a reminder to check which key you are passing. +The SDK logs a console warning if it detects you are using a **Secret Key** (`sk_live_...`) in a browser context. ## Explore the SDK - + - Sign up, log in, and fetch the current user. + Login, logout, profile updates, and social auth. - Read and write documents in any collection. + CRUD operations, query building, and RLS support. Upload files and get back a public CDN URL. + + Inspect collection definitions and field types. + + + Send transactional emails from your server. + diff --git a/mintlify/docs/sdk/schema.mdx b/mintlify/docs/sdk/schema.mdx new file mode 100644 index 00000000..d0824f4f --- /dev/null +++ b/mintlify/docs/sdk/schema.mdx @@ -0,0 +1,44 @@ +--- +title: "Schema" +description: "Inspect collection definitions and field types using the SDK schema module." +keywords: ["schema", "introspection", "metadata", "sdk"] +--- + +Access schema methods via `client.schema`. This module allows you to programmatically retrieve the definitions of your collections, which is useful for building dynamic forms or data table components. + +## getSchema + +Fetch the schema definition for a specific collection. + +```typescript +getSchema(collection: string): Promise +``` + +**Returns (`CollectionSchema`)** + +| Field | Type | Description | +|---|---|---| +| `name` | `string` | The name of the collection. | +| `model` | `SchemaField[]` | Array of field definitions. | + +**`SchemaField` Object** + +| Field | Type | Description | +|---|---|---| +| `key` | `string` | The field name. | +| `type` | `string` | The Mongoose type (e.g. `"String"`, `"Number"`, `"Ref"`, `"Array"`). | +| `required` | `boolean` | Whether the field is mandatory. | +| `unique` | `boolean` | Whether the field has a uniqueness constraint. | +| `ref` | `string` | If type is `"Ref"`, the target collection name. | +| `items` | `object` | Element type definition when type is `"Array"`. Contains `type` and optional nested `fields`. | +| `fields` | `SchemaField[]` | Nested field definitions for embedded objects or sub-documents. | + +**Example** + +```typescript +const schema = await client.schema.getSchema('products'); + +console.log(schema.name); // 'products' +console.log(schema.model[0].key); // 'name' +console.log(schema.model[0].type); // 'String' +``` diff --git a/mintlify/docs/sdk/storage.mdx b/mintlify/docs/sdk/storage.mdx index d6e5cf91..f8132221 100644 --- a/mintlify/docs/sdk/storage.mdx +++ b/mintlify/docs/sdk/storage.mdx @@ -17,7 +17,7 @@ Storage operations accept both `pk_live_...` and `sk_live_...` keys. Use `pk_liv Upload a file to storage. ```typescript -upload(file: File | Blob | Buffer, filename?: string): Promise<{ url: string; path: string }> +upload(file: File | Blob | Buffer, filename?: string): Promise ``` **Parameters** @@ -27,12 +27,13 @@ upload(file: File | Blob | Buffer, filename?: string): Promise<{ url: string; pa | `file` | `File \| Blob \| Buffer` | Yes | The file to upload. | | `filename` | `string` | No | Override the stored filename. | -**Returns** +**Returns (`UploadResponse`)** | Field | Type | Description | |---|---|---| | `url` | `string` | Public CDN URL for the uploaded file. | | `path` | `string` | Storage path — required to delete the file later. | +| `provider` | `'internal' \| 'external'` | Whether the file is stored on urBackend or your own Supabase. | Store the `path` alongside the `url` in your database. You need it to delete the file. @@ -42,29 +43,16 @@ Store the `path` alongside the `url` in your database. You need it to delete the ```typescript const input = document.querySelector('input[type="file"]'); -const file = input.files[0]; - -const { url, path } = await client.storage.upload(file); -console.log(url); // 'https://cdn.example.com/uploads/abc123.jpg' -console.log(path); // 'uploads/abc123.jpg' - -// Store both so you can delete the file later -await client.db.insert('product_images', { url, path }); -``` - -**Example — custom filename** - -```typescript -const { url, path } = await client.storage.upload(file, 'profile-photo.jpg'); -``` -**Example — Node.js Buffer** +if (!input?.files?.length) { + throw new Error('No file selected'); +} -```typescript -import { readFileSync } from 'fs'; +const file = input.files[0]; -const buffer = readFileSync('./report.pdf'); -const { url, path } = await client.storage.upload(buffer, 'report.pdf'); +const { url, path, provider } = await client.storage.upload(file); +console.log(url); // 'https://cdn.example.com/uploads/abc123.jpg' +console.log(provider); // 'internal' ``` --- @@ -83,46 +71,15 @@ deleteFile(path: string): Promise<{ deleted: boolean }> |---|---|---|---| | `path` | `string` | Yes | The `path` returned by `upload()`. | -**Example** - -```typescript -// Retrieve the stored path -const image = await client.db.getOne('product_images', id); - -const result = await client.storage.deleteFile(image.path); -console.log(result.deleted); // true -``` - --- ## Limits | Limit | Value | |---|---| -| Max file size | 5 MB per file | -| Total storage per project | 100 MB | +| Max file size | 10 MB per file | +| Total storage per project | 100 MB (Free Tier) | -Uploads that exceed 5 MB are rejected with a `StorageError`. Check `file.size` before calling `upload()` if you need to show a friendly error to your users. +Uploads that exceed 10 MB are rejected with a `StorageError`. - -**Example — checking file size before upload** - -```typescript -import { StorageError } from '@urbackend/sdk'; - -const MAX_BYTES = 5 * 1024 * 1024; // 5 MB - -if (file.size > MAX_BYTES) { - console.error('File must be 5 MB or smaller'); - return; -} - -try { - const { url, path } = await client.storage.upload(file); -} catch (e) { - if (e instanceof StorageError) { - console.error('Upload failed:', e.message); - } -} -```