Vanilla JS / TS
You can use Spooky with plain JavaScript or TypeScript without any framework. This guide shows how to use the core SpookyClient directly.
Installation
Bash npm install @spooky-sync/core surrealdb
Generate Schema Types
First, generate your schema types from your SurrealDB schema:
Bash spooky --input src/schema.surql --output src/schema.gen.ts --format typescript
Initialize the Client
Create and initialize a SpookyClient instance:
TypeScript import { SpookyClient, type SpookyConfig } from '@spooky-sync/core';
import { schema, SURQL_SCHEMA } from './schema.gen';
const config: SpookyConfig<typeof schema> = {
logLevel: 'info',
schema: schema,
schemaSurql: SURQL_SCHEMA,
database: {
namespace: 'main',
database: 'main',
endpoint: 'ws://localhost:8000/rpc',
store: 'indexeddb', // or 'memory'
},
};
export const client = new SpookyClient<typeof schema>(config);
// Initialize the client
export async function initDatabase() {
await client.init();
console.log('Spooky client initialized');
}
Querying Data
Use the query builder to fetch data and subscribe to updates:
TypeScript import { client } from './db';
// Build a query
const query = client.query('thread')
.related('author')
.orderBy('created_at', 'desc')
.limit(10);
// Execute and get the query hash
const { hash } = await query.build().run();
// Subscribe to query updates
const unsubscribe = await client.subscribe(
hash,
(records) => {
console.log('Threads updated:', records);
renderThreads(records);
},
{ immediate: true } // Get initial results immediately
);
// Later: unsubscribe when done
// unsubscribe();
function renderThreads(threads: any[]) {
const container = document.getElementById('threads');
if (!container) return;
container.innerHTML = threads.map(thread => `
<div class="thread">
<h3>${thread.title}</h3>
<p>${thread.content}</p>
<small>By ${thread.author?.username || 'Unknown'}</small>
</div>
`).join('');
}
Authentication
Use the auth service to handle user authentication:
TypeScript import { client } from './db';
// Sign up a new user
async function signUp(username: string, password: string) {
try {
await client.auth.signUp('account', { username, password });
console.log('Signed up successfully');
} catch (error) {
console.error('Sign up failed:', error);
}
}
// Sign in
async function signIn(username: string, password: string) {
try {
await client.auth.signIn('account', { username, password });
console.log('Signed in successfully');
} catch (error) {
console.error('Sign in failed:', error);
}
}
// Subscribe to auth state changes
client.auth.subscribe((userId) => {
if (userId) {
console.log('User logged in:', userId);
showAuthenticatedUI();
} else {
console.log('User logged out');
showLoginUI();
}
});
// Sign out
async function signOut() {
await client.auth.signOut();
}
Creating and Updating Records
Use the client methods to mutate data:
TypeScript import { client } from './db';
import { Uuid } from 'surrealdb';
// Create a new thread
async function createThread(title: string, content: string, authorId: string) {
const id = `thread:${Uuid.v4().toString().replace(/-/g, '')}`;
await client.create(id, {
title,
content,
author: authorId,
active: true,
});
console.log('Thread created:', id);
}
// Update a thread
async function updateThread(threadId: string, title: string) {
await client.update('thread', threadId, {
title,
});
console.log('Thread updated');
}
// Delete a thread
async function deleteThread(threadId: string) {
await client.delete('thread', threadId);
console.log('Thread deleted');
}
Complete Example
Here’s a complete working example:
TypeScript import { client, initDatabase } from './db';
async function main() {
// Initialize database
await initDatabase();
// Subscribe to auth state
client.auth.subscribe((userId) => {
if (userId) {
loadThreads();
}
});
// Load and display threads
async function loadThreads() {
const query = client.query('thread')
.related('author')
.orderBy('created_at', 'desc')
.limit(20);
const { hash } = await query.build().run();
await client.subscribe(hash, (threads) => {
renderThreads(threads);
}, { immediate: true });
}
function renderThreads(threads: any[]) {
const container = document.getElementById('threads');
if (!container) return;
container.innerHTML = threads.map(thread => `
<article>
<h2>${thread.title}</h2>
<p>${thread.content}</p>
<footer>By ${thread.author?.username || 'Unknown'}</footer>
</article>
`).join('');
}
}
main().catch(console.error);