Vanilla JS / TS
Use the core client directly, with no framework bindings.
You can use Sp00ky with plain JavaScript or TypeScript without any framework. This guide shows how to use the core Sp00kyClient directly.
Installation
pnpm add @spooky-sync/core surrealdb Generate Schema Types
First, generate your schema types from your SurrealDB schema:
# 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
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:
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:
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:
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
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);