> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/trailbaseio/trailbase/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript/JavaScript SDK

> Official TypeScript/JavaScript client for TrailBase

The official TypeScript/JavaScript SDK for TrailBase provides a type-safe client for accessing your TrailBase backend from web browsers, Node.js, Deno, and other JavaScript runtimes.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install trailbase
  ```

  ```bash yarn theme={null}
  yarn add trailbase
  ```

  ```bash pnpm theme={null}
  pnpm add trailbase
  ```

  ```bash bun theme={null}
  bun add trailbase
  ```
</CodeGroup>

## Initialization

### Basic Client

Initialize a client with your TrailBase server URL:

```typescript theme={null}
import { initClient } from 'trailbase';

const client = initClient('https://your-server.trailbase.io');
```

### Client with Existing Tokens

If you have persisted tokens from a previous session:

```typescript theme={null}
import { initClient, type Tokens } from 'trailbase';

const tokens: Tokens = {
  auth_token: 'your-auth-token',
  refresh_token: 'your-refresh-token',
  csrf_token: 'your-csrf-token',
};

const client = initClient('https://your-server.trailbase.io', { tokens });
```

### Client from Cookies

For server-side rendering or when working with cookies:

```typescript theme={null}
import { initClientFromCookies } from 'trailbase';

const client = await initClientFromCookies('https://your-server.trailbase.io');
```

### Auth State Changes

Listen to authentication state changes:

```typescript theme={null}
import { initClient, type Client, type User } from 'trailbase';

const client = initClient('https://your-server.trailbase.io', {
  onAuthChange: (client: Client, user?: User) => {
    if (user) {
      console.log('User logged in:', user.email);
    } else {
      console.log('User logged out');
    }
  },
});
```

## Authentication

### Login

```typescript theme={null}
try {
  await client.login('user@example.com', 'password');
  const user = client.user();
  console.log('Logged in as:', user?.email);
} catch (error) {
  console.error('Login failed:', error);
}
```

### Logout

```typescript theme={null}
await client.logout();
```

### Check Current User

```typescript theme={null}
const user = client.user();
if (user) {
  console.log('User ID:', user.id);
  console.log('Email:', user.email);
  console.log('Is Admin:', user.admin);
}
```

### Access Tokens

```typescript theme={null}
const tokens = client.tokens();
if (tokens) {
  // Persist tokens for future sessions
  localStorage.setItem('trailbase_tokens', JSON.stringify(tokens));
}
```

### Refresh Token

```typescript theme={null}
await client.refreshAuthToken();
```

## Record API

### List Records

```typescript theme={null}
interface Post {
  id: string;
  title: string;
  content: string;
  author_id: string;
  created_at: number;
}

const posts = client.records<Post>('posts');

const response = await posts.list({
  pagination: { limit: 10, offset: 0 },
  order: ['-created_at'],
  count: true,
});

console.log('Posts:', response.records);
console.log('Total count:', response.total_count);
console.log('Next cursor:', response.cursor);
```

### Read a Single Record

```typescript theme={null}
const post = await posts.read('post-id');
console.log('Post:', post);

// With expanded relationships
const postWithAuthor = await posts.read('post-id', {
  expand: ['author'],
});
```

### Create a Record

```typescript theme={null}
const newPostId = await posts.create({
  title: 'Hello World',
  content: 'My first post',
});

console.log('Created post with ID:', newPostId);
```

### Create Multiple Records

```typescript theme={null}
const ids = await posts.createBulk([
  { title: 'Post 1', content: 'Content 1' },
  { title: 'Post 2', content: 'Content 2' },
]);
```

### Update a Record

```typescript theme={null}
await posts.update('post-id', {
  title: 'Updated Title',
});
```

### Delete a Record

```typescript theme={null}
await posts.delete('post-id');
```

## Filtering

TrailBase supports powerful filtering with comparison operators:

```typescript theme={null}
import type { Filter, CompareOp } from 'trailbase';

// Simple equality filter
const response = await posts.list({
  filters: [
    {
      column: 'author_id',
      value: 'user-123',
    },
  ],
});

// With comparison operators
const recentPosts = await posts.list({
  filters: [
    {
      column: 'created_at',
      op: 'greaterThan' as CompareOp,
      value: String(Date.now() - 7 * 24 * 60 * 60 * 1000),
    },
  ],
});

// LIKE operator for text search
const searchResults = await posts.list({
  filters: [
    {
      column: 'title',
      op: 'like' as CompareOp,
      value: '%search%',
    },
  ],
});

// AND/OR composite filters
const filtered = await posts.list({
  filters: [
    {
      and: [
        { column: 'status', value: 'published' },
        { column: 'author_id', value: 'user-123' },
      ],
    },
  ],
});
```

### Available Comparison Operators

* `equal` - Equality (default)
* `notEqual` - Not equal
* `lessThan` - Less than
* `lessThanEqual` - Less than or equal
* `greaterThan` - Greater than
* `greaterThanEqual` - Greater than or equal
* `like` - SQL LIKE pattern matching
* `regexp` - Regular expression matching
* `@within` - Spatial within (for geospatial data)
* `@intersects` - Spatial intersects
* `@contains` - Spatial contains

## Real-time Subscriptions

### Subscribe to a Single Record

```typescript theme={null}
const stream = await posts.subscribe('post-id');

for await (const event of stream) {
  if ('Insert' in event) {
    console.log('Record inserted:', event.Insert);
  } else if ('Update' in event) {
    console.log('Record updated:', event.Update);
  } else if ('Delete' in event) {
    console.log('Record deleted:', event.Delete);
  } else if ('Error' in event) {
    console.error('Subscription error:', event.Error);
  }
}
```

### Subscribe to All Records

```typescript theme={null}
const stream = await posts.subscribeAll({
  filters: [
    { column: 'author_id', value: 'user-123' },
  ],
});

for await (const event of stream) {
  console.log('Change event:', event);
}
```

### WebSocket Subscriptions

For improved performance, you can use WebSocket-based subscriptions:

```typescript theme={null}
const recordApi = client.records('posts') as any;
const stream = await recordApi.subscribeWs('post-id');

for await (const event of stream) {
  console.log('WebSocket event:', event);
}
```

## Batch Operations

Execute multiple operations in a single transaction:

```typescript theme={null}
const ops = [
  posts.createOp({ title: 'Post 1', content: 'Content 1' }),
  posts.updateOp('post-id', { title: 'Updated' }),
  posts.deleteOp('old-post-id'),
];

const ids = await client.execute(ops, true);
console.log('Operation results:', ids);
```

## File Handling

### Access File URLs

```typescript theme={null}
import { filePath, filesPath } from 'trailbase';

// Single file column
const avatarUrl = filePath('users', userId, 'avatar');

// Files array column
const attachmentUrl = filesPath('messages', messageId, 'attachments', 'document.pdf');
```

### Avatar URL

```typescript theme={null}
const avatarUrl = client.avatarUrl(userId);
// Or current user's avatar
const myAvatarUrl = client.avatarUrl();
```

## Error Handling

```typescript theme={null}
import { FetchError } from 'trailbase';

try {
  await posts.create({ title: 'Test' });
} catch (error) {
  if (error instanceof FetchError) {
    console.error('Status:', error.status);
    console.error('Message:', error.message);
    console.error('URL:', error.url);
    
    if (error.isClient()) {
      console.log('Client error (4xx)');
    } else if (error.isServer()) {
      console.log('Server error (5xx)');
    }
  }
}
```

## Advanced Usage

### Custom Fetch Options

```typescript theme={null}
const response = await client.fetch('/api/custom/endpoint', {
  method: 'POST',
  body: JSON.stringify({ data: 'value' }),
  throwOnError: false, // Don't throw on error status
});
```

### Deferred Operations

Build operations without executing them immediately:

```typescript theme={null}
const listOp = posts.listOp({ pagination: { limit: 5 } });
const readOp = posts.readOp('post-id');

// Execute later
const listResult = await listOp.query();
const readResult = await readOp.query();
```

## Type Definitions

### User

```typescript theme={null}
type User = {
  id: string;
  email: string;
  admin?: boolean;
};
```

### Tokens

```typescript theme={null}
type Tokens = {
  auth_token: string;
  refresh_token: string | null;
  csrf_token: string | null;
};
```

### ListResponse

```typescript theme={null}
type ListResponse<T> = {
  cursor?: string;
  records: T[];
  total_count?: number;
};
```

### Pagination

```typescript theme={null}
type Pagination = {
  cursor?: string;
  limit?: number;
  offset?: number;
};
```

## Best Practices

<Note>
  Always handle authentication errors gracefully and redirect users to login when tokens expire.
</Note>

<Warning>
  Store tokens securely. For web applications, consider using secure, HTTP-only cookies instead of localStorage.
</Warning>

<Info>
  The client automatically refreshes auth tokens before they expire. Manual refresh is rarely needed.
</Info>
