---
title: Client SDK
description: The official TypeScript client for ObjectStack — auto-discovery, typed metadata, CRUD, batch operations, and service-aware feature detection.
---

# Client SDK

The `@objectstack/client` is the official TypeScript client for ObjectStack. It provides a typed, protocol-aware interface that automatically adapts to your server's available services.

## Features

- **Auto-Discovery**: Connects to your ObjectStack server and discovers available services, routes, and capabilities
- **Service-Aware**: Checks `discovery.services` to determine which features are available before calling them
- **Typed Metadata**: Retrieve Object and View definitions with full type support
- **Metadata Caching**: ETag-based conditional requests for efficient metadata caching
- **Unified Data Access**: Simple CRUD operations for any object in your schema
- **Batch Operations**: Efficient bulk create/update/upsert/delete with transaction support
- **Query Builder**: Programmatic query construction with `createQuery()` and `createFilter()`
- **Standardized Errors**: Machine-readable error codes with retry guidance
- **100% Protocol Compliant**: Implements all 15 API namespaces defined in `@objectstack/spec`

## Installation

```bash
pnpm add @objectstack/client
```

## Quick Start

```typescript
import { ObjectStackClient } from '@objectstack/client';

const client = new ObjectStackClient({
  baseUrl: 'http://localhost:3004',
});

async function main() {
  // 1. Connect — fetches discovery manifest
  await client.connect();

  // 2. Check available services
  console.log('Services:', client.discovery?.services);
  // → { metadata: { enabled: true, status: 'degraded' }, data: { enabled: true, status: 'available' }, auth: { enabled: false, ... } }

  // 3. Query data
  const tasks = await client.data.find('todo_task', {
    fields: ['subject', 'priority'],
    where: { priority: { $gte: 2 } },
    orderBy: ['-priority'],
    limit: 10
  });

  // 4. Create a record
  const newTask = await client.data.create('todo_task', {
    subject: 'New Task',
    priority: 1
  });

  // 5. Batch operations
  const result = await client.data.batch('todo_task', {
    operation: 'update',
    records: [
      { id: '1', data: { status: 'active' } },
      { id: '2', data: { status: 'active' } }
    ],
    options: { atomic: true, returnRecords: true }
  });
  console.log(`Updated ${result.succeeded} records`);
}
```

---

## Auto-Discovery

When you call `client.connect()`, the client:

1. Fetches `/.well-known/objectstack` (or falls back to `/api/v1`)
2. Parses the discovery response including `routes`, `features`, and `services`
3. Configures all API route paths dynamically

```typescript
await client.connect();

// Discovery result is available
const discovery = client.discovery;
console.log(discovery.version);      // "1.0.0"
console.log(discovery.environment);  // "development"

// Check if a service is available before using it
if (discovery.services?.auth?.enabled) {
  // Auth plugin is installed — login is available
  await client.auth.login({ username: 'admin', password: 'secret' });
} else {
  console.log(discovery.services?.auth?.message);
  // → "Install an auth plugin to enable"
}
```

<Callout type="info">
**Plugin-driven services**: Auth, workflow, automation, AI, and other services are only available when the corresponding plugin is installed. Always check `discovery.services` before calling plugin-provided API methods.
</Callout>

---

## Protocol Coverage

The `@objectstack/client` SDK aims to implement the ObjectStack API protocol specification. It covers all 15 API namespaces defined in `@objectstack/spec`:

| Namespace | Status | Methods | Purpose |
|:----------|:------:|:--------|:--------|
| **discovery** | ✅ | 1 | API version & capabilities detection |
| **meta** | ✅ | 7 | Metadata operations (objects, views, plugins) |
| **data** | ✅ | 10 | CRUD & query operations |
| **auth** | ✅ | 5 | Authentication & user management |
| **permissions** | ✅ | 3 | Access control checks |
| **packages** | ✅ | 6 | Plugin/package lifecycle management |
| **views** | ✅ | 5 | UI view definitions |
| **workflow** | ✅ | 5 | Workflow state transitions |
| **analytics** | ✅ | 3 | Analytics queries |
| **automation** | ✅ | 1 | Automation triggers |
| **storage** | ✅ | 2 | File upload & download |
| **i18n** | ✅ | 3 | Internationalization |
| **notifications** | 🟡 | 7 | Legacy notification helpers; receipt/inbox cut-over pending |
| **realtime** | ✅ | 6 | WebSocket subscriptions |
| **ai** | ✅ | 4 | AI services (NLQ, chat, insights) |

<Callout type="info">
**Protocol compliance & verification**: See [`CLIENT_SPEC_COMPLIANCE.md`](https://github.com/objectstack-ai/spec/blob/main/packages/client/CLIENT_SPEC_COMPLIANCE.md) for detailed method-by-method verification and [`CLIENT_SERVER_INTEGRATION_TESTS.md`](https://github.com/objectstack-ai/spec/blob/main/packages/client/CLIENT_SERVER_INTEGRATION_TESTS.md) for comprehensive integration test specifications.
</Callout>

---

## API Namespaces

### `client.meta` — Metadata

```typescript
// Get object schema
const accountSchema = await client.meta.getObject('account');
console.log(accountSchema.fields);

// List all metadata types
const types = await client.meta.getTypes();
// → { types: ['object', 'view', 'plugin', ...] }

// Get with ETag caching
const cached = await client.meta.getCached('account', {
  ifNoneMatch: '"686897696a7c876b7e"'
});
if (cached.notModified) {
  console.log('Using cached metadata');
}

// Get auto-generated view
const listView = await client.meta.getView('account', 'list');
```

### `client.data` — CRUD & Batch

```typescript
// Find with query options
const accounts = await client.data.find('account', {
  fields: ['name', 'industry', 'revenue'],
  where: { industry: 'Technology' },
  orderBy: ['-revenue'],
  limit: 20,
  offset: 0,
});

// Get by ID
const account = await client.data.get('account', '123');

// Create
const created = await client.data.create('account', {
  name: 'Acme Corp',
  industry: 'Technology',
});

// Update (partial)
const updated = await client.data.update('account', '123', {
  industry: 'Healthcare',
});

// Delete
await client.data.delete('account', '123');

// Batch operation (recommended for bulk)
const batchResult = await client.data.batch('account', {
  operation: 'upsert',
  records: [
    { id: '1', data: { name: 'Acme', status: 'active' } },
    { id: '2', data: { name: 'Beta', status: 'active' } },
  ],
  options: {
    atomic: true,
    returnRecords: true,
    continueOnError: false,
    validateOnly: false,
  },
});

// Convenience bulk methods
await client.data.createMany('account', [{ name: 'A' }, { name: 'B' }]);
await client.data.updateMany('account', [
  { id: '1', data: { status: 'closed' } },
  { id: '2', data: { status: 'closed' } },
]);
await client.data.deleteMany('account', ['1', '2', '3']);
```

### `client.analytics` — BI Queries

```typescript
// Semantic analytics query (cube-style)
const result = await client.analytics.query({
  cube: 'account',
  measures: ['revenue.sum', 'count'],
  dimensions: ['industry'],
  filters: [{ member: 'status', operator: 'equals', values: ['active'] }],
  limit: 100,
});

// Get cube metadata for a specific cube
const meta = await client.analytics.meta('account');

// Explain a query plan
const explained = await client.analytics.explain({
  cube: 'account',
  measures: ['revenue.sum'],
});
```

### `client.packages` — Package Management

```typescript
const packages = await client.packages.list();
await client.packages.install({ name: 'plugin-auth', version: '1.0.0' });
await client.packages.enable('plugin-auth');
await client.packages.disable('plugin-auth');
await client.packages.uninstall('plugin-auth');
```

### Additional Namespaces

The client also provides full implementations for:

```typescript
// Auth — User authentication and session management
await client.auth.login({ email: 'user@example.com', password: 'pass' });
await client.auth.register({ email: 'new@example.com', password: 'pass' });
await client.auth.me();
await client.auth.logout();
await client.auth.refreshToken('refresh-token-string');

// Permissions — Access control checks
await client.permissions.check({ object: 'account', action: 'create' });
await client.permissions.getObjectPermissions('account');
await client.permissions.getEffectivePermissions();

// Workflow — State machine management
await client.workflow.getConfig('approval');
await client.workflow.getState('approval', recordId);
await client.workflow.transition({ object: 'approval', recordId, transition: 'submit' });
await client.workflow.approve({ object: 'approval', recordId });
await client.workflow.reject({ object: 'approval', recordId, reason: 'Incomplete' });

// Realtime — WebSocket subscriptions
await client.realtime.connect({ protocol: 'websocket' });
await client.realtime.subscribe({ channel: 'account', event: 'update' });
await client.realtime.unsubscribe('subscription-id');
await client.realtime.setPresence('account', { status: 'online' });
await client.realtime.getPresence('account');
await client.realtime.disconnect();

// Notifications — legacy helper surface
await client.notifications.registerDevice({ token: 'device-token', platform: 'ios' });
await client.notifications.unregisterDevice('device-id');
await client.notifications.getPreferences();
await client.notifications.updatePreferences({ email: true, push: false });
await client.notifications.list({ read: false });
await client.notifications.markRead(['notif-1', 'notif-2']);
await client.notifications.markAllRead();

// ADR-0030 note: the framework pipeline now materializes in-app messages
// through sys_inbox_message and tracks read-state in sys_notification_receipt.
// These helpers will be repointed during the objectui bell cut-over.

// AI — AI-powered features
await client.ai.nlq({ query: 'Show me all active accounts' });
await client.ai.chat({ message: 'Summarize this environment', context: environmentId });
await client.ai.suggest({ object: 'account', field: 'industry' });
await client.ai.insights({ object: 'sales', recordId: dealId });

// i18n — Internationalization
await client.i18n.getLocales();
await client.i18n.getTranslations('zh-CN');
await client.i18n.getFieldLabels('account', 'zh-CN');

// Automation — Trigger workflows and automations
await client.automation.trigger('send_welcome_email', { userId });

// Storage — File upload and management
await client.storage.upload(fileData, 'user');
await client.storage.getDownloadUrl('file-123');

// Views — UI view management
await client.views.list('account');
await client.views.get('account', viewId);
await client.views.create('account', { name: 'my_view', ... });
await client.views.update('account', viewId, { ... });
await client.views.delete('account', viewId);
```

<Callout type="info">
**Service availability**: Optional services (workflow, ai, etc.) are only available when the corresponding plugin is installed on the server. Always check `client.discovery?.services` to verify service availability before calling these methods.
</Callout>

---

## Query Builder

Build complex queries programmatically:

```typescript
import { createQuery, createFilter } from '@objectstack/client';

const query = createQuery('account')
  .select('name', 'industry', 'revenue')
  .where((f) => {
    f.equals('industry', 'Technology');
    f.greaterThan('revenue', 10000);
  })
  .orderBy('revenue', 'desc')
  .limit(50)
  .build();

const results = await client.data.query('account', query);
```

### Filter Builder Methods

The `FilterBuilder` provides a rich set of filter methods:

```typescript
import { createFilter } from '@objectstack/client';

const filter = createFilter()
  .equals('status', 'active')        // field = value
  .notEquals('type', 'archived')      // field != value
  .greaterThan('revenue', 10000)      // field > value
  .lessThanOrEqual('age', 100)        // field <= value
  .in('category', ['A', 'B', 'C'])   // field IN (...)
  .like('name', '%Corp%')             // field LIKE pattern
  .contains('name', 'Corp')           // LIKE %Corp%
  .startsWith('name', 'Acme')         // LIKE Acme%
  .isNull('deleted_at')               // field IS NULL
  .between('created_at', '2024-01', '2024-12')
  .build();
```

---

## Query Options

The `find` method accepts an options object with **canonical** (recommended) field names:

| Property | Type | Description | Example |
|:---------|:-----|:------------|:--------|
| `where` | `Object` or `FilterCondition` | Filter conditions (WHERE clause) | `{ status: 'active' }` |
| `fields` | `string[]` | Fields to retrieve (SELECT) | `['name', 'email']` |
| `orderBy` | `string` or `string[]` or `SortNode[]` | Sort order (ORDER BY) | `['-created_at']` |
| `limit` | `number` | Max records to return (LIMIT) | `20` |
| `offset` | `number` | Records to skip (OFFSET) | `0` |
| `expand` | `Record<string, any>` or `string[]` | Relation loading (JOIN) | `{ owner: {} }` |

<Callout type="warn">
**Deprecated aliases:** The following legacy field names are still accepted for backward compatibility but will be removed in a future major version: `select` → `fields`, `filter`/`filters` → `where`, `sort` → `orderBy`, `top` → `limit`, `skip` → `offset`.
</Callout>

### Batch Options

| Property | Type | Default | Description |
|:---------|:-----|:--------|:------------|
| `atomic` | `boolean` | `true` | Rollback entire batch on any failure |
| `returnRecords` | `boolean` | `false` | Include full records in response |
| `continueOnError` | `boolean` | `false` | Continue after errors (when atomic=false) |
| `validateOnly` | `boolean` | `false` | Dry-run mode — validate without persisting |

---

## Error Handling

All errors follow a standardized format:

```typescript
try {
  await client.data.create('todo_task', { subject: '' });
} catch (error) {
  console.error(error.code);       // 'validation_error'
  console.error(error.category);   // 'validation'
  console.error(error.httpStatus); // 400
  console.error(error.retryable);  // false
  console.error(error.details);    // { ... }
}
```

### Error Codes

| Code | HTTP | Category | Retryable | Description |
|:-----|:-----|:---------|:----------|:------------|
| `validation_error` | 400 | validation | No | Input validation failed |
| `invalid_query` | 400 | validation | No | Malformed query expression |
| `unauthenticated` | 401 | auth | No | Authentication required |
| `permission_denied` | 403 | auth | No | Insufficient permissions |
| `resource_not_found` | 404 | request | No | Resource does not exist |
| `rate_limit_exceeded` | 429 | rate_limit | Yes | Too many requests |
| `internal_error` | 500 | server | Yes | Unexpected server error |
| `service_unavailable` | 503 | server | Yes | Service temporarily unavailable |
| `not_implemented` | 501 | server | No | Service not installed (plugin missing) |

---

## Configuration

```typescript
interface ClientConfig {
  /** ObjectStack server URL */
  baseUrl: string;
  /** Bearer token for authentication (if auth plugin installed) */
  token?: string;
  /** Custom fetch implementation (e.g. for SSR or testing) */
  fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
  /** Logger instance for debugging */
  logger?: Logger;
  /** Enable debug logging */
  debug?: boolean;
}
```

---

## React Hooks

For React applications, use `@objectstack/client-react`:

```bash
pnpm add @objectstack/client-react
```

```typescript
import { ObjectStackProvider, useClient, useQuery } from '@objectstack/client-react';
import { ObjectStackClient } from '@objectstack/client';

// 1. Wrap your app with the provider
const client = new ObjectStackClient({ baseUrl: 'http://localhost:3004' });

function App() {
  return (
    <ObjectStackProvider client={client}>
      <AccountList />
    </ObjectStackProvider>
  );
}

// 2. Use hooks in child components
function AccountList() {
  const { data, isLoading, error, refetch } = useQuery('account', {
    where: { status: 'active' },
    orderBy: ['-created_at'],
    limit: 20,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return data?.records.map(a => <div key={a.id}>{a.name}</div>);
}
```

### Available Hooks

| Hook | Purpose |
|:-----|:--------|
| `useClient()` | Access the `ObjectStackClient` instance from context |
| `useQuery(object, options)` | Query data with auto caching and refetching |
| `useMutation(object)` | Create/update/delete mutations |
| `usePagination(object, options)` | Paginated data queries |
| `useInfiniteQuery(object, options)` | Infinite scroll queries |
| `useObject(name)` | Get object metadata |
| `useView(object, type)` | Get view definition |
| `useFields(object)` | Get field definitions |
| `useMetadata(type, name)` | Get arbitrary metadata |

---

## Testing

The client SDK includes comprehensive unit and integration tests to ensure reliability and protocol compliance.

### Unit Tests

```bash
cd packages/client
pnpm test
```

Unit tests use mocks to verify client behavior without requiring a server.

### Integration Tests

**Note:** Integration tests require a running ObjectStack server. The server is provided by a separate repository and must be set up independently.

```bash
# Prerequisite: Start an ObjectStack server with test data
# For example, using the reference server repository
# Follow the server repository's documentation for local setup

# From this repository, run the integration test script
cd packages/client
pnpm test:integration
```

Integration tests verify end-to-end communication with a live ObjectStack server across all 15 API namespaces.

<Callout type="info">
**Test coverage**: Integration test specifications cover discovery/connection, authentication, metadata operations, CRUD operations (basic, batch, advanced queries), permissions, workflow, realtime, notifications, AI services, i18n, analytics, packages, views, storage, and automation.
</Callout>

---

## Protocol Compliance Documentation

For detailed information about the client's protocol implementation:

- **[Protocol Compliance Matrix](https://github.com/objectstack-ai/spec/blob/main/packages/client/CLIENT_SPEC_COMPLIANCE.md)** — Method-by-method verification of all API methods across 15 namespaces
- **[Integration Test Specifications](https://github.com/objectstack-ai/spec/blob/main/packages/client/CLIENT_SERVER_INTEGRATION_TESTS.md)** — Comprehensive test cases for client-server communication
- **[Quick Reference Guide](https://github.com/objectstack-ai/spec/blob/main/packages/client/QUICK_REFERENCE.md)** — Developer navigation and API reference

---

## Next Steps

<Cards>
  <Card 
    href="./api-reference" 
    title="API Reference"
    description="Complete REST endpoint documentation"
  />
  <Card 
    href="./kernel-services" 
    title="Kernel Services"
    description="Service checklist and plugin development guide"
  />
  <Card 
    href="../references/api/protocol" 
    title="Protocol Types"
    description="All 57 Zod protocol schemas"
  />
</Cards>
