Package Exports
- @objectstack/client
- @objectstack/client/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@objectstack/client) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@objectstack/client
The official TypeScript client for ObjectStack.
Features
- Auto-Discovery: Connects to your ObjectStack server and automatically configures API endpoints.
- 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/delete with transaction support.
- View Storage: Save, load, and share custom UI view configurations.
- Standardized Errors: Machine-readable error codes with retry guidance.
🤖 AI Development Context
Role: Browser/Node Client SDK Usage:
- Use this in Frontend Apps (React, etc.) or external Node scripts.
- Interacts with
packages/runtimeover HTTP.
Key namespaces:
client.meta: Metadata operations.client.data: Data operations.
Installation
pnpm add @objectstack/clientUsage
import { ObjectStackClient } from '@objectstack/client';
// 1. Initialize
const client = new ObjectStackClient({
baseUrl: 'http://localhost:3004', // Your ObjectStack Server URL
token: 'optional-auth-token'
});
async function main() {
// 2. Connect (Fetches system capabilities)
await client.connect();
// 3. Metadata Access
const todoSchema = await client.meta.getObject('todo_task');
console.log('Fields:', todoSchema.fields);
// Save Metadata (New Feature)
await client.meta.saveItem('object', 'my_custom_object', {
label: 'My Object',
fields: { name: { type: 'text' } }
});
// 4. Advanced Query
const tasks = await client.data.find<{ subject: string; priority: number }>('todo_task', {
select: ['subject', 'priority'], // Field Selection
filters: ['priority', '>=', 2], // ObjectStack Filter AST
sort: ['-priority'], // Sorting
top: 10
});
// 5. Create Data
const newTask = await client.data.create('todo_task', {
subject: 'New Task',
priority: 1
});
// 6. Batch Operations (New!)
const batchResult = await client.data.batch('todo_task', {
operation: 'update',
records: [
{ id: '1', data: { status: 'active' } },
{ id: '2', data: { status: 'active' } }
],
options: {
atomic: true, // Rollback on any failure
returnRecords: true // Include full records in response
}
});
console.log(`Updated ${batchResult.succeeded} records`);
// 7. Metadata Caching (New!)
const cachedObject = await client.meta.getCached('todo_task', {
ifNoneMatch: '"686897696a7c876b7e"' // ETag from previous request
});
if (cachedObject.notModified) {
console.log('Using cached metadata');
}
// 8. View Storage (New!)
const view = await client.views.create({
name: 'active_tasks',
label: 'Active Tasks',
object: 'todo_task',
type: 'list',
visibility: 'public',
query: {
object: 'todo_task',
where: { status: 'active' },
orderBy: [{ field: 'priority', order: 'desc' }],
limit: 50
},
layout: {
columns: [
{ field: 'subject', label: 'Task', width: 200 },
{ field: 'priority', label: 'Priority', width: 100 }
]
}
});
}API Reference
client.connect()
Initializes the client by fetching the system discovery manifest from /api/v1.
client.meta
getObject(name: string): Get object schema.getCached(name: string, options?): Get object schema with ETag-based caching.getView(name: string): Get view configuration.
client.data
find<T>(object, options): Advanced query with filtering, sorting, selection.get<T>(object, id): Get single record by ID.query<T>(object, ast): Execute complex query using full AST.create<T>(object, data): Create record.batch(object, request): Recommended - Execute batch operations (create/update/upsert/delete) with full control.createMany<T>(object, data[]): Batch create records (convenience method).update<T>(object, id, data): Update record.updateMany<T>(object, records[], options?): Batch update records (convenience method).delete(object, id): Delete record.deleteMany(object, ids[], options?): Batch delete records (convenience method).
client.views (New!)
create(request): Create a new saved view.get(id): Get a saved view by ID.list(request?): List saved views with optional filters.update(request): Update an existing view.delete(id): Delete a saved view.share(id, userIds[]): Share a view with users/teams.setDefault(id, object): Set a view as default for an object.
Query Options
The find method accepts an options object:
select: Array of field names to retrieve.filters: Simple key-value map OR Filter AST['field', 'op', 'value'].sort: Sort string ('name') or array['-created_at', 'name'].top: Limit number of records.skip: Offset for pagination.
Batch Options
Batch operations support the following options:
atomic: If true, rollback entire batch on any failure (default: true).returnRecords: If true, return full record data in response (default: false).continueOnError: If true (and atomic=false), continue processing remaining records after errors.validateOnly: If true, validate records without persisting changes (dry-run mode).
Error Handling
The client provides standardized error handling with machine-readable error codes:
try {
await client.data.create('todo_task', { subject: '' });
} catch (error) {
console.error('Error code:', error.code); // e.g., 'validation_error'
console.error('Category:', error.category); // e.g., 'validation'
console.error('HTTP status:', error.httpStatus); // e.g., 400
console.error('Retryable:', error.retryable); // e.g., false
console.error('Details:', error.details); // Additional error info
}Error Code Reference
Client Errors (4xx)
| Error Code | HTTP Status | Retryable | Description |
|---|---|---|---|
validation_error |
400 | No | Input validation failed. Check error.details for field-specific errors |
unauthenticated |
401 | No | Authentication required. Provide valid token |
permission_denied |
403 | No | Insufficient permissions for this operation |
resource_not_found |
404 | No | Resource does not exist. Verify object name or record ID |
conflict |
409 | No | Resource conflict (e.g., duplicate unique field) |
rate_limit_exceeded |
429 | Yes | Too many requests. Wait before retrying |
Server Errors (5xx)
| Error Code | HTTP Status | Retryable | Description |
|---|---|---|---|
internal_error |
500 | Yes | Server encountered an error. Retry with backoff |
service_unavailable |
503 | Yes | Service temporarily unavailable. Retry later |
gateway_timeout |
504 | Yes | Request timeout. Consider increasing timeout or retrying |
Retry Strategy Example:
async function retryableRequest<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error: any) {
if (!error.retryable || i === maxRetries - 1) {
throw error;
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
throw new Error('Max retries exceeded');
}
// Usage
const data = await retryableRequest(() =>
client.data.create('todo_task', { subject: 'Task' })
);