SDK / Remote Driver
Universal HTTP client for ObjectQL servers - works in browser, Node.js, Deno, and edge runtimes
SDK / Remote Driver
The SDK package provides a type-safe HTTP client for ObjectQL servers. It works seamlessly in browsers, Node.js, Deno, and edge runtimes like Cloudflare Workers, enabling you to connect client applications to remote ObjectQL servers.
Features
- 🌍 Universal Runtime: Works in browsers, Node.js, Deno, and edge environments
- 📦 Zero Dependencies: Only depends on
@objectql/typesand@objectstack/specfor type definitions - 🔒 Type-Safe: Full TypeScript support with generics
- 🚀 Modern APIs: Uses native
fetchAPI available in all modern JavaScript runtimes - 🎯 RESTful Interface: Clean, predictable API design
- ✅ DriverInterface v4.0: Fully compliant with ObjectStack protocol specification
- 🔐 Authentication: Built-in support for Bearer tokens and API keys
- 🔄 Retry Logic: Automatic retry with exponential backoff for network resilience
- 📊 Request Logging: Optional request/response logging for debugging
Installation
npm install @objectql/sdk @objectql/typesFor frontend projects:
# Using npm
npm install @objectql/sdk @objectql/types
# Using yarn
yarn add @objectql/sdk @objectql/types
# Using pnpm
pnpm add @objectql/sdk @objectql/typesQuick Start
Browser Usage (ES Modules)
<!DOCTYPE html>
<html>
<head>
<title>ObjectQL SDK Browser Example</title>
</head>
<body>
<h1>ObjectQL Browser Client</h1>
<div id="users"></div>
<script type="module">
// Option 1: Using unpkg CDN
import { DataApiClient } from 'https://unpkg.com/@objectql/sdk/dist/index.js';
// Option 2: Using a bundler (Vite, Webpack, etc.)
// import { DataApiClient } from '@objectql/sdk';
const client = new DataApiClient({
baseUrl: 'http://localhost:3000',
token: 'your-auth-token' // Optional
});
// Fetch and display users
async function loadUsers() {
const response = await client.list('users', {
filter: [['status', '=', 'active']],
limit: 10
});
const usersDiv = document.getElementById('users');
usersDiv.innerHTML = response.items
.map(user => `<p>${user.name} - ${user.email}</p>`)
.join('');
}
loadUsers().catch(console.error);
</script>
</body>
</html>React / Vue / Angular
import { DataApiClient, MetadataApiClient } from '@objectql/sdk';
// Initialize clients
const dataClient = new DataApiClient({
baseUrl: process.env.REACT_APP_API_URL || 'http://localhost:3000',
token: localStorage.getItem('auth_token')
});
const metadataClient = new MetadataApiClient({
baseUrl: process.env.REACT_APP_API_URL || 'http://localhost:3000'
});
// Use in your components
async function fetchUsers() {
const response = await dataClient.list('users', {
filter: [['status', '=', 'active']],
sort: [['created_at', 'desc']]
});
return response.items;
}Node.js
const { DataApiClient } = require('@objectql/sdk');
const client = new DataApiClient({
baseUrl: 'http://localhost:3000'
});
async function main() {
const users = await client.list('users');
console.log(users.items);
}
main();API Clients
The SDK provides three main client classes:
1. DataApiClient
For CRUD operations on data records.
2. MetadataApiClient
For reading object schemas and metadata.
3. RemoteDriver
Implements the full DriverInterface from @objectstack/spec for use with ObjectQL core.
DataApiClient
Constructor
new DataApiClient(config: DataApiClientConfig)Config Options:
baseUrl(string, required) - Base URL of the ObjectQL servertoken(string, optional) - Authentication tokenheaders(Record<string, string>, optional) - Additional HTTP headerstimeout(number, optional) - Request timeout in milliseconds (default: 30000)
Methods
list<T>(objectName: string, params?: DataApiListParams): Promise<DataApiListResponse<T>>
List records with optional filtering, sorting, and pagination.
const users = await client.list('users', {
filter: [['status', '=', 'active']],
sort: [['name', 'asc']],
limit: 20,
skip: 0,
fields: ['name', 'email', 'status']
});get<T>(objectName: string, id: string | number): Promise<DataApiItemResponse<T>>
Get a single record by ID.
const user = await client.get('users', 'user_123');create<T>(objectName: string, data: DataApiCreateRequest): Promise<DataApiItemResponse<T>>
Create a new record.
const newUser = await client.create('users', {
name: 'Alice',
email: 'alice@example.com',
status: 'active'
});createMany<T>(objectName: string, data: DataApiCreateManyRequest): Promise<DataApiListResponse<T>>
Create multiple records at once.
const newUsers = await client.createMany('users', [
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' }
]);update<T>(objectName: string, id: string | number, data: DataApiUpdateRequest): Promise<DataApiItemResponse<T>>
Update an existing record.
const updated = await client.update('users', 'user_123', {
status: 'inactive'
});updateMany(objectName: string, request: DataApiBulkUpdateRequest): Promise<DataApiResponse>
Update multiple records matching filters.
await client.updateMany('users', {
filters: [['status', '=', 'pending']],
data: { status: 'active' }
});delete(objectName: string, id: string | number): Promise<DataApiDeleteResponse>
Delete a record by ID.
await client.delete('users', 'user_123');deleteMany(objectName: string, request: DataApiBulkDeleteRequest): Promise<DataApiDeleteResponse>
Delete multiple records matching filters.
await client.deleteMany('users', {
filters: [['created_at', '<', '2023-01-01']]
});count(objectName: string, filters?: FilterExpression): Promise<DataApiCountResponse>
Count records matching filters.
const result = await client.count('users', [['status', '=', 'active']]);
console.log(result.count);MetadataApiClient
Constructor
new MetadataApiClient(config: MetadataApiClientConfig)Methods
listObjects(): Promise<MetadataApiObjectListResponse>
List all available objects.
const objects = await metadataClient.listObjects();getObject(objectName: string): Promise<MetadataApiObjectDetailResponse>
Get detailed schema for an object.
const userSchema = await metadataClient.getObject('users');
console.log(userSchema.fields);getField(objectName: string, fieldName: string): Promise<FieldMetadataResponse>
Get metadata for a specific field.
const emailField = await metadataClient.getField('users', 'email');listActions(objectName: string): Promise<MetadataApiActionsResponse>
List actions available for an object.
const actions = await metadataClient.listActions('users');RemoteDriver (DriverInterface v4.0)
The RemoteDriver implements the standard DriverInterface from @objectstack/spec, allowing you to use it as a data source for ObjectQL core.
Constructor
// New config-based constructor (recommended)
new RemoteDriver(config: SdkConfig)
// Legacy constructor
new RemoteDriver(baseUrl: string, rpcPath?: string)Config Options:
baseUrl(string, required) - Base URL of the ObjectQL serverrpcPath(string, optional) - JSON-RPC endpoint path (default: /api/objectql)queryPath(string, optional) - Query endpoint path (default: /api/query)commandPath(string, optional) - Command endpoint path (default: /api/command)executePath(string, optional) - Custom execute endpoint path (default: /api/execute)token(string, optional) - Authentication token (Bearer)apiKey(string, optional) - API key for authenticationheaders(Record<string, string>, optional) - Custom HTTP headerstimeout(number, optional) - Request timeout in milliseconds (default: 30000)enableRetry(boolean, optional) - Enable automatic retry on failure (default: false)maxRetries(number, optional) - Maximum retry attempts (default: 3)enableLogging(boolean, optional) - Enable request/response logging (default: false)
Methods
executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }>
Execute a query using QueryAST format (DriverInterface v4.0).
import { RemoteDriver } from '@objectql/sdk';
const driver = new RemoteDriver({
baseUrl: 'http://localhost:3000',
token: 'your-auth-token',
enableRetry: true,
maxRetries: 3
});
// Execute a QueryAST
const result = await driver.executeQuery({
object: 'users',
fields: ['name', 'email', 'status'],
filters: {
type: 'comparison',
field: 'status',
operator: '=',
value: 'active'
},
sort: [{ field: 'created_at', order: 'desc' }],
top: 10,
skip: 0
});
console.log(result.value); // Array of users
console.log(result.count); // Total countexecuteCommand(command: Command, options?: any): Promise<CommandResult>
Execute a command for mutation operations (create, update, delete, bulk operations).
// Create a record
const createResult = await driver.executeCommand({
type: 'create',
object: 'users',
data: {
name: 'Alice',
email: 'alice@example.com',
status: 'active'
}
});
// Bulk create
const bulkCreateResult = await driver.executeCommand({
type: 'bulkCreate',
object: 'users',
records: [
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' }
]
});
// Update
const updateResult = await driver.executeCommand({
type: 'update',
object: 'users',
id: 'user_123',
data: { status: 'inactive' }
});
// Delete
const deleteResult = await driver.executeCommand({
type: 'delete',
object: 'users',
id: 'user_123'
});execute(endpoint?: string, payload?: any, options?: any): Promise<any>
Execute a custom operation on the remote server.
// Execute a custom workflow
const workflowResult = await driver.execute('/api/workflows/approve', {
workflowId: 'wf_123',
comment: 'Approved by manager'
});
// Call a custom action
const actionResult = await driver.execute('/api/actions/send-email', {
to: 'user@example.com',
subject: 'Welcome',
body: 'Welcome to our platform!'
});Authentication
Bearer Token
const driver = new RemoteDriver({
baseUrl: 'http://localhost:3000',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
});API Key
const driver = new RemoteDriver({
baseUrl: 'http://localhost:3000',
apiKey: 'sk-1234567890abcdef'
});Custom Headers
const driver = new RemoteDriver({
baseUrl: 'http://localhost:3000',
headers: {
'X-Tenant-ID': 'tenant_123',
'X-Request-ID': crypto.randomUUID()
}
});Error Handling and Retry
import { ObjectQLError, ApiErrorCode } from '@objectql/types';
// Enable retry with exponential backoff
const driver = new RemoteDriver({
baseUrl: 'http://localhost:3000',
enableRetry: true,
maxRetries: 3,
timeout: 10000
});
try {
const result = await driver.executeQuery({ object: 'users' });
} catch (error) {
if (error instanceof ObjectQLError) {
switch (error.code) {
case ApiErrorCode.UNAUTHORIZED:
console.error('Authentication required');
break;
case ApiErrorCode.VALIDATION_ERROR:
console.error('Validation failed:', error.details);
break;
case ApiErrorCode.NOT_FOUND:
console.error('Resource not found');
break;
default:
console.error('API error:', error.message);
}
}
}Request Logging
// Enable logging for debugging
const driver = new RemoteDriver({
baseUrl: 'http://localhost:3000',
enableLogging: true
});
// Logs will be printed to console:
// [RemoteDriver] executeQuery { endpoint: '...', ast: {...} }
// [RemoteDriver] executeQuery response { value: [...], count: 10 }Browser Compatibility
The SDK uses modern JavaScript APIs available in all current browsers:
- fetch API - Available in all modern browsers
- Promises/async-await - ES2017+
- AbortSignal.timeout() - Chrome 103+, Firefox 100+, Safari 16.4+
Automatic Polyfill
The SDK automatically includes a polyfill for AbortSignal.timeout() that activates when running in older browsers. You don't need to add any polyfills manually - the SDK works universally out of the box!
The polyfill ensures compatibility with:
- Chrome 90+
- Firefox 90+
- Safari 15+
- Edge 90+
For even older browsers, you may need to add polyfills for:
fetchAPI (viawhatwg-fetch)AbortController(viaabort-controllerpackage)
Framework Examples
React Hook
import { useState, useEffect } from 'react';
import { DataApiClient } from '@objectql/sdk';
const client = new DataApiClient({
baseUrl: process.env.REACT_APP_API_URL
});
export function useObjectData<T>(objectName: string, params?: any) {
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
const response = await client.list<T>(objectName, params);
setData(response.items || []);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}
fetchData();
}, [objectName, JSON.stringify(params)]);
return { data, loading, error };
}Vue Composable
import { ref, watchEffect } from 'vue';
import { DataApiClient } from '@objectql/sdk';
const client = new DataApiClient({
baseUrl: import.meta.env.VITE_API_URL
});
export function useObjectData<T>(objectName: string, params?: any) {
const data = ref<T[]>([]);
const loading = ref(true);
const error = ref<Error | null>(null);
watchEffect(async () => {
try {
loading.value = true;
const response = await client.list<T>(objectName, params);
data.value = response.items || [];
} catch (err) {
error.value = err as Error;
} finally {
loading.value = false;
}
});
return { data, loading, error };
}