JavaScript/TypeScript Client
Build applications with the official RaisinDB JavaScript client library.
Installation
npm install @raisindb/client
Or with yarn:
yarn add @raisindb/client
Quick Start
Basic Connection
import { RaisinClient } from '@raisindb/client';
const client = new RaisinClient('ws://localhost:8080/ws/myapp');
await client.connect();
await client.authenticate({
username: 'admin',
password: 'your-password'
});
// Get database interface
const db = client.database('myapp');
Connection URLs use the tenant-less ws://host/ws/{repository} form — no
tenant id needed. Equivalently, pass a bare host URL with the repository
option: new RaisinClient('ws://localhost:8080', { repository: 'myapp' }).
Multi-tenant operators can address a specific tenant with
ws://host/sys/{tenant}/{repository}.
HTTP-Only Client (Server-Side Rendering)
For server-side rendering where WebSocket is not available:
import { RaisinClient } from '@raisindb/client';
const client = RaisinClient.forSSR('http://localhost:8080');
await client.authenticate({
username: 'admin',
password: 'your-password'
});
const db = client.database('myapp');
Authentication
Admin Authentication
await client.authenticate({
username: 'admin',
password: 'your-password'
});
Email / Password Authentication
Register and log in end users with the built-in identity system:
// Register a new user
const user = await client.registerWithEmail(
'alice@example.com',
'securePassword',
'myapp',
'Alice' // optional display name
);
// Log in an existing user
const user = await client.loginWithEmail(
'alice@example.com',
'securePassword',
'myapp'
);
Session Restoration
Restore a session from a stored token (e.g. after a page reload):
const user = await client.initSession('myapp');
if (user) {
console.log('Session restored for', user.email);
} else {
console.log('No stored session, redirect to login');
}
Auth State Listener
React to sign-in, sign-out, and token refresh events:
const unsubscribe = client.onAuthStateChange(({ event, session }) => {
switch (event) {
case 'SIGNED_IN':
console.log('User signed in:', session.user?.email);
break;
case 'SIGNED_OUT':
console.log('User signed out');
break;
case 'TOKEN_REFRESHED':
console.log('Token refreshed');
break;
case 'SESSION_EXPIRED':
console.log('Session expired, redirect to login');
break;
}
});
// Stop listening
unsubscribe();
Ready State
The client is "ready" when it is both connected and authenticated:
const unsubscribe = client.onReadyStateChange((ready) => {
if (ready) {
console.log('Client is connected and authenticated');
}
});
console.log(client.isReady()); // true | false
Token Storage
By default tokens are stored in memory. For browser persistence:
import { RaisinClient, LocalStorageTokenStorage } from '@raisindb/client';
const client = new RaisinClient('ws://localhost:8080/ws/myapp', {
tokenStorage: new LocalStorageTokenStorage()
});
Working with Nodes
Create Nodes
const ws = db.workspace('content');
const article = await ws.nodes().create({
type: 'Article',
path: '/articles/hello-world',
properties: {
title: 'Hello World',
author: 'John Doe',
status: 'draft'
}
});
console.log(article.id); // "01HQRS4T8K..."
Get Nodes
// Get by path
const article = await ws.nodes().getByPath('/articles/hello-world');
// Get by ID
const node = await ws.nodes().get('01HQRS4T8K...');
// Query by type
const articles = await ws.nodes().queryByType('Article', 10);
// Query by property
const published = await ws.nodes().queryByProperty(
'status',
'published',
20
);
Update Nodes
await ws.nodes().update(article.id, {
properties: {
status: 'published',
published_date: new Date().toISOString()
}
});
Delete Nodes
await ws.nodes().delete(article.id);
Tree Operations
List Children
const children = await ws.nodes().listChildren('/articles');
Get Tree
// Get full tree
const tree = await ws.nodes().getTree('/articles');
// Limit depth
const tree = await ws.nodes().getTree('/articles', 2);
// Get flattened tree
const flatTree = await ws.nodes().getTreeFlat('/articles');
Move and Rename
// Move node
await ws.nodes().move('/articles/old-path', '/articles/new-folder');
// Rename node
await ws.nodes().rename('/articles/hello-world', 'hello-raisindb');
// Copy node (shallow)
await ws.nodes().copy('/articles/template', '/articles/new-article');
// Copy tree (deep)
await ws.nodes().copyTree('/articles/series', '/articles/archived-series');
Reorder Nodes
// Set specific order key
await ws.nodes().reorder('/articles/item-1', 'a0');
// Move before sibling
await ws.nodes().moveChildBefore(
'/articles',
'/articles/item-2',
'/articles/item-1'
);
// Move after sibling
await ws.nodes().moveChildAfter(
'/articles',
'/articles/item-3',
'/articles/item-2'
);
Child order is per-branch and is carried automatically when a branch is merged.
When promoting content by copying nodes between branches instead of merging, use
applyChildOrder(parentPath, sourceBranch) to replay the order onto the target
branch. See Node Operations → Ordering.
Relationships
Add Relationships
// Add relationship
await ws.nodes().addRelation(
'/articles/hello-world',
'authored_by',
'/users/john-doe'
);
// With weight
await ws.nodes().addRelation(
'/articles/hello-world',
'related_to',
'/articles/getting-started',
{ weight: 0.9 }
);
// Cross-workspace relationship
await ws.nodes().addRelation(
'/articles/product-review',
'reviews',
'/products/item-123',
{ targetWorkspace: 'products' }
);
Remove Relationships
await ws.nodes().removeRelation(
'/articles/hello-world',
'/users/john-doe'
);
Get Relationships
const rels = await ws.nodes().getRelationships('/articles/hello-world');
console.log(rels.outgoing); // Relationships from this node
console.log(rels.incoming); // Relationships to this node
SQL Queries
Execute SQL
const result = await db.executeSql(
'SELECT * FROM nodes WHERE node_type = $1 LIMIT $2',
['Article', 10]
);
console.log(result.rows);
Tagged Template Literals
const status = 'published';
const limit = 10;
const result = await db.sql`
SELECT * FROM nodes
WHERE node_type = 'Article'
AND properties->>'status' = ${status}
LIMIT ${limit}
`;
for (const row of result.rows) {
console.log(row.properties.title);
}
Branches
Switch Branch
// Work on feature branch
const featureWs = db.workspace('content').onBranch('feature-xyz');
const node = await featureWs.nodes().create({
type: 'Article',
path: '/articles/new-feature',
properties: { title: 'New Feature' }
});
Time Travel
// Query node at specific revision
const historicWs = db.workspace('content').atRevision('01HQRS4T8K...');
const oldVersion = await historicWs.nodes().getByPath('/articles/hello-world');
Transactions
const ws = db.workspace('content');
const tx = ws.transaction();
try {
await tx.begin({ message: 'Create article series' });
await tx.nodes().create({
type: 'Article',
path: '/articles/part-1',
properties: { title: 'Part 1' }
});
await tx.nodes().create({
type: 'Article',
path: '/articles/part-2',
properties: { title: 'Part 2' }
});
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}
Real-Time Events
Subscribe to Node Changes
const ws = db.workspace('content');
// Subscribe to all changes in workspace
const subscription = await ws.events().subscribe({}, (event) => {
console.log('Event:', event.event_type, event.payload);
});
// Unsubscribe
await subscription.unsubscribe();
Filter Events
// Subscribe to specific node type
const sub = await ws.events().subscribeToNodeType('Article', (event) => {
console.log('Article changed:', event.payload);
});
// Subscribe to path pattern
const sub = await ws.events().subscribeToPath('/articles', (event) => {
console.log('Article in /articles changed');
});
// Subscribe to specific event types
const sub = await ws.events().subscribeToTypes(
['node:created', 'node:updated'],
(event) => {
console.log('Node created or updated:', event.payload);
}
);
AI Chat
Build conversational AI features with the ConversationManager (db.conversations). It handles conversation lifecycle, real-time streaming, plan approvals, and message history. Conversations are stored as raisin:Conversation nodes with raisin:Message children. See Chat & Conversations for the full reference.
Access the ConversationManager
const db = client.database('myapp');
const conversations = db.conversations;
The getter returns a lazily-created, cached ConversationManager pre-configured with the correct base URL, repository, and auth manager.
One-Shot Chat
Send a single message and collect the full response:
const { response, conversationPath } = await db.conversations.chat(
'/agents/my-assistant',
'What is RaisinDB?'
);
console.log(response);
Multi-Turn Conversations
Create a conversation and stream responses in real time:
// Start a conversation (agent paths create an ai_chat)
const conversation = await db.conversations.create({
participant: '/agents/my-assistant'
});
// Send a message and stream the response
for await (const event of db.conversations.sendMessage(
conversation.conversationPath,
'Tell me about your capabilities'
)) {
switch (event.type) {
case 'text_chunk':
process.stdout.write(event.text);
break;
case 'assistant_message':
console.log('\nFull response:', event.message.content);
break;
case 'tool_call_started':
console.log('Calling tool:', event.functionName);
break;
case 'waiting':
console.log('Ready for next message');
break;
}
}
Resume a Conversation
Restore a conversation after a page reload:
const conversation = await db.conversations.open(conversationPath);
if (conversation) {
// Load previous messages
const messages = await db.conversations.getMessages(conversation.conversationPath);
}
Manage Conversations
// List AI chat conversations
const chats = await db.conversations.list({
type: 'ai_chat',
limit: 20,
});
// Mark as read
await db.conversations.markAsRead(chats[0].conversationPath);
ConversationStore (UI state)
For building chat UIs, ConversationStore wraps the manager with a snapshot/subscribe state container — streaming text, tool-call tracking, plan projection, and hang recovery included:
import { ConversationStore } from '@raisindb/client';
const store = new ConversationStore({
database: db,
createOptions: { participant: '/agents/my-assistant' },
});
store.subscribe((s) => {
render(s.messages, s.isStreaming, s.streamingText, s.activeToolCalls);
});
await store.sendMessage('Hello!');
// later
store.destroy();
Flow Execution
Run server-side workflows and stream their progress in real time.
Create a Flow Client
const db = client.database('myapp');
const flowClient = db.flow;
Or create one manually from an authenticated client:
import { FlowClient } from '@raisindb/client';
const flowClient = FlowClient.fromHttpClient(
client, // authenticated RaisinClient or RaisinHttpClient
'http://localhost:8080',
'myapp'
);
Run a Flow and Wait for Completion
const result = await flowClient.runAndWait(
'flows/process-order',
{ orderId: '12345', priority: 'high' }
);
if (result.status === 'completed') {
console.log('Output:', result.output);
} else {
console.error('Failed:', result.error);
}
Stream Flow Events
const { instance_id } = await flowClient.run(
'flows/generate-report',
{ month: '2025-01' }
);
for await (const event of flowClient.streamEvents(instance_id)) {
switch (event.type) {
case 'step_started':
console.log('Step started:', event.node_id);
break;
case 'step_completed':
console.log('Step completed:', event.node_id);
break;
case 'text_chunk':
process.stdout.write(event.text);
break;
case 'flow_completed':
console.log('Flow done:', event.output);
break;
case 'flow_failed':
console.error('Flow failed:', event.error);
break;
}
}
Resume a Waiting Flow
Flows can pause and wait for external input (human tasks, chat sessions):
// Resume with data
await flowClient.resume(instanceId, {
approved: true,
comment: 'Looks good'
});
// Respond to a human task
await flowClient.respondToHumanTask(instanceId, taskId, {
selectedOption: 'approve'
});
Check Flow Status
const status = await flowClient.getInstanceStatus(instanceId);
console.log(status.status); // 'running' | 'completed' | 'failed' | 'waiting' | ...
File Uploads
Upload a Single File
const upload = await client.upload(file, {
repository: 'myapp',
workspace: 'content',
path: '/images/photo.jpg'
});
Upload from a Workspace
const ws = db.workspace('content');
const upload = await ws.upload(file, '/images/photo.jpg');
Batch Upload
const batch = await ws.uploadFiles(fileList, '/images/', {
concurrency: 3,
onProgress: (progress) => {
console.log(`${progress.filesCompleted}/${progress.filesTotal} files`);
}
});
Signed Asset URLs
Generate time-limited URLs for accessing binary assets:
const { url } = await ws.signAssetUrl('/images/photo.jpg');
React Integration
The @raisindb/client/react subpath export provides a Provider plus hooks (useAuth, useSql, useSubscription, useConversation, useFlow, ...). See Framework Integrations for the complete reference.
useConversation Hook
import React from 'react';
import { createRaisinReact } from '@raisindb/client/react';
import { client } from './lib/raisin'; // your RaisinClient instance
const { RaisinProvider, useConversation } = createRaisinReact(React);
function ChatWidget() {
const chat = useConversation({
database: client.database('myapp'),
createOptions: { participant: '/agents/my-assistant' },
});
const [input, setInput] = React.useState('');
return (
<div>
{chat.messages.map((msg, i) => (
<div key={i} className={msg.role}>
{msg.content}
</div>
))}
{chat.isStreaming && <div className="assistant">{chat.streamingText}</div>}
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={() => { chat.sendMessage(input); setInput(''); }}>
Send
</button>
{chat.isStreaming && <button onClick={chat.stop}>Stop</button>}
</div>
);
}
React Router Loader (SSR)
import { RaisinClient } from '@raisindb/client';
export async function articleLoader({ params }) {
const client = RaisinClient.forSSR('http://localhost:8080');
await client.authenticate({
username: 'admin',
password: process.env.RAISIN_PASSWORD
});
const db = client.database('myapp');
const ws = db.workspace('content');
const article = await ws.nodes().getByPath(`/articles/${params.slug}`);
return { article };
}
Svelte Integration
Conversation adapter
The @raisindb/client/svelte subpath export provides adapter factories designed for Svelte 5 runes (createAuthAdapter, createSqlAdapter, createConversationAdapter, ...):
import { createConversationAdapter } from '@raisindb/client/svelte';
const adapter = createConversationAdapter({
database: client.database('myapp'),
createOptions: { participant: '/agents/my-assistant' },
});
// Subscribe to state changes (bind to $state in a .svelte.ts file)
const unsubscribe = adapter.subscribe((state) => {
console.log(state.messages, state.isStreaming, state.streamingText);
});
// Send a message
await adapter.sendMessage('Hello!');
// Clean up
adapter.destroy();
See Framework Integrations for the full adapter list and a runes-based example.
Error Handling
import {
RaisinError,
RaisinConnectionError,
RaisinAuthError,
RaisinFlowError,
RaisinTimeoutError,
} from '@raisindb/client';
try {
await ws.nodes().getByPath('/articles/missing');
} catch (error) {
if (error instanceof RaisinAuthError) {
console.error('Auth error:', error.code, error.status);
} else if (error instanceof RaisinConnectionError) {
console.error('Connection lost:', error.code);
} else if (error instanceof RaisinFlowError) {
console.error('Flow error:', error.code, error.instanceId);
} else if (error instanceof RaisinTimeoutError) {
console.error('Timed out after', error.timeoutMs, 'ms');
} else if (error instanceof RaisinError) {
console.error('RaisinDB error:', error.code, error.message);
}
}
Configuration Options
import { RaisinClient, LocalStorageTokenStorage, LogLevel } from '@raisindb/client';
const client = new RaisinClient('ws://localhost:8080', {
repository: 'myapp', // builds the /ws/myapp route
defaultBranch: 'main',
requestTimeout: 30000,
logLevel: LogLevel.Info, // Silent | Error | Warn | Info | Debug
tokenStorage: new LocalStorageTokenStorage(),
});
See the ClientOptions reference for the full option table.
Connection State
Monitor and react to connection lifecycle:
client.onConnectionStateChange((state) => {
// state: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'closed'
console.log('Connection:', state);
});
client.onReconnected(() => {
console.log('Reconnected — subscriptions auto-restored');
});
TypeScript Types
The client is fully typed:
import type {
Node,
NodeCreateOptions,
NodeUpdateOptions,
NodeQueryOptions,
PropertyValue,
ChatMessage,
ChatEvent,
Conversation,
ConversationType,
ConversationListItem,
FlowExecutionEvent,
FlowRunResponse,
} from '@raisindb/client';