Github|...

SolidJS Guide

This guide walks you through building a reactive app with SolidJS and Spooky.

1. Installation

Install the required dependencies:

Bash
npm install @spooky-sync/client-solid surrealdb

2. Generate Schema Types

First, create your schema file and generate TypeScript types:

Bash
# Generate TypeScript types from your schema
spooky --input src/schema.surql --output src/schema.gen.ts --format typescript

This generates a schema.gen.ts file with your schema definition and types.

3. Initialize Database

Create a db.ts file to configure and export your database instance:

src/db.ts
TypeScript
import { SyncedDb, type SyncedDbConfig } from '@spooky-sync/client-solid';
import { schema, SURQL_SCHEMA } from './schema.gen';

export const dbConfig: SyncedDbConfig<typeof schema> = {
logLevel: 'info',
schema: schema,
schemaSurql: SURQL_SCHEMA,
database: {
  namespace: 'main',
  database: 'main',
  endpoint: 'ws://localhost:8000/rpc',
  store: 'indexeddb',
},
};

export const db = new SyncedDb<typeof schema>(dbConfig);

// Initialize the database
let initPromise: Promise<void> | null = null;

export function initDatabase(): Promise<void> {
if (initPromise) return initPromise;

initPromise = (async () => {
  try {
    await db.init();
    console.log('Database initialized');
  } catch (error) {
    initPromise = null;
    throw error;
  }
})();

return initPromise;
}

4. App Entry Point

Initialize the database in your root component before rendering the app:

src/App.tsx
tsx
import { createSignal, onMount, Show } from 'solid-js';
import { initDatabase } from './db';

export default function App() {
const [isReady, setIsReady] = createSignal(false);

onMount(async () => {
  try {
    await initDatabase();
    setIsReady(true);
  } catch (error) {
    console.error('Failed to initialize database:', error);
  }
});

return (
  <Show
    when={isReady()}
    fallback={<div>Initializing database...</div>}
  >
    <MainContent />
  </Show>
);
}

5. Querying Data with useQuery

Use the useQuery hook to create reactive queries that automatically update:

src/components/ThreadList.tsx
tsx
import { For } from 'solid-js';
import { useQuery } from '@spooky-sync/client-solid';
import { db } from '../db';

export function ThreadList() {
// Build the query
const threadsQuery = useQuery(db, () => {
  return db.query('thread')
    .related('author')
    .orderBy('created_at', 'desc')
    .limit(20)
    .build();
});

return (
  <div>
    <Show when={threadsQuery.isLoading()}>
      <div>Loading...</div>
    </Show>
    
    <Show when={threadsQuery.error()}>
      <div>Error: {threadsQuery.error()?.message}</div>
    </Show>
    
    <ul>
      <For each={threadsQuery.data() || []}>
        {(thread) => (
          <li>
            <h3>{thread.title}</h3>
            <p>{thread.content}</p>
            <small>By {thread.author?.username}</small>
          </li>
        )}
      </For>
    </ul>
  </div>
);
}

6. Authentication with Auth Context

Create an auth context to manage user authentication state:

src/lib/auth.tsx
tsx
import { createContext, useContext, createSignal, onCleanup, type JSX } from 'solid-js';
import { useQuery } from '@spooky-sync/client-solid';
import { db } from '../db';

interface AuthContextType {
userId: () => string | null;
isLoading: () => boolean;
signIn: (username: string, password: string) => Promise<void>;
signUp: (username: string, password: string) => Promise<void>;
signOut: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType>();

export function AuthProvider(props: { children: JSX.Element }) {
const [userId, setUserId] = createSignal<string | null>(null);
const [isInitializing, setIsInitializing] = createSignal(true);

// Subscribe to auth state changes
const unsubscribe = db.auth.subscribe((uid) => {
  setUserId(uid);
  setIsInitializing(false);
});

onCleanup(() => unsubscribe());

const signIn = async (username: string, password: string) => {
  await db.auth.signIn('account', { username, password });
};

const signUp = async (username: string, password: string) => {
  await db.auth.signUp('account', { username, password });
};

const signOut = async () => {
  await db.auth.signOut();
};

const value: AuthContextType = {
  userId,
  isLoading: () => isInitializing(),
  signIn,
  signUp,
  signOut,
};

return (
  <AuthContext.Provider value={value}>
    {props.children}
  </AuthContext.Provider>
);
}

export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
  throw new Error('useAuth must be used within AuthProvider');
}
return context;
}

7. Creating and Updating Data

Use the database methods to create and update records:

TypeScript
import { Uuid } from 'surrealdb';
import { db } from './db';

// Create a new thread
async function createThread(title: string, content: string, authorId: string) {
const id = `thread:${Uuid.v4().toString().replace(/-/g, '')}`;

await db.create(id, {
  title,
  content,
  author: authorId,
  active: true,
});
}

// Update a thread
async function updateThread(threadId: string, updates: { title?: string; content?: string }) {
await db.update('thread', threadId, updates);
}

// Delete a thread
async function deleteThread(threadId: string) {
await db.delete('thread', threadId);
}