Vanilla JS / TS
You can use Sp00ky with plain JavaScript or TypeScript without any framework. This guide shows how to use the core Sp00kyClient directly.
Installation
Bash
pnpm add @spooky-sync/core surrealdb Generate Schema Types
First, generate your schema types from your SurrealDB schema:
Bash
# Recommended: drive from sp00ky.yml's clientTypes entries
spky generate
# One-shot variant
spky --input ./schema/src/schema.surql --output ./src/schema.gen.ts --format typescript Initialize the Client
Create and initialize a Sp00kyClient instance:
src/db.ts
TypeScript
import { Sp00kyClient, type Sp00kyConfig } from '@spooky-sync/core';
import { schema, SURQL_SCHEMA } from './schema.gen';
const config: Sp00kyConfig<typeof schema> = {
logLevel: 'info',
schema,
schemaSurql: SURQL_SCHEMA,
database: {
namespace: 'main',
database: 'main',
// `spky dev` exposes SurrealDB on 8666 by default.
endpoint: 'ws://localhost:8666/rpc',
store: 'indexeddb', // or 'memory'
},
};
export const client = new Sp00kyClient<typeof schema>(config);
// Initialize the client (idempotent — safe to await multiple times).
export async function initDatabase() {
await client.init();
console.log('Sp00ky client initialized');
} Querying Data
Use the query builder to fetch data and subscribe to updates:
TypeScript
import { client } from './db';
// Build a query. The second argument is `QueryOptions` — pass `{}`
// for defaults. SolidJS users get the same builder from `db.query()`
// without the options argument.
const builder = client.query('thread', {})
.related('author')
.orderBy('created_at', 'desc')
.limit(10)
.build();
// Register the query with the client and get back its `{ hash }`.
const { hash } = await builder.run();
// Subscribe to query updates. `immediate: true` invokes the callback
// synchronously with whatever the local store has right now.
const unsubscribe = await client.subscribe(
hash,
(records) => {
console.log('Threads updated:', records);
renderThreads(records);
},
{ immediate: true }
);
// 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 { RecordId, Uuid } from 'surrealdb';
// Create a new thread. `client.create` takes a fully-qualified record
// id ("thread:abc…") plus a typed payload.
async function createThread(title: string, content: string, authorId: string) {
const id = \`thread:\${Uuid.v4().toString().replace(/-/g, '')}\`;
await client.create(id, {
title,
content,
active: true,
author: new RecordId('user', authorId),
});
console.log('Thread created:', id);
}
// Update a thread. Only listed fields are merged.
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:
src/main.ts
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 builder = client.query('thread', {})
.related('author')
.orderBy('created_at', 'desc')
.limit(20)
.build();
const { hash } = await builder.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);