JSPM

  • Created
  • Published
  • Downloads 840
  • Score
    100M100P100Q94722F
  • License MIT

SurrealDB client with local and remote database support for browser applications

Package Exports

  • @spooky-sync/client-solid

Readme

db-solid

A SurrealDB client for Solid.js with automatic cache synchronization and live query support.

Features

  • Live Queries: Real-time data synchronization from remote SurrealDB server
  • Local Cache: Fast reads from local WASM SurrealDB instance
  • Automatic Sync: Changes from remote automatically update local cache
  • Query Deduplication: Multiple components share the same remote subscriptions
  • Type-Safe: Full TypeScript support with generated schema types
  • Reactive: Seamless integration with Solid.js reactivity
  • Offline Support: Local cache works even when remote is temporarily unavailable

Quick Start

Installation

pnpm add db-solid

Basic Setup

// db.ts
import { SyncedDb, type SyncedDbConfig } from 'db-solid';
import { type Schema, SURQL_SCHEMA } from './schema.gen';

export const dbConfig: SyncedDbConfig<Schema> = {
  schema: SURQL_SCHEMA,
  localDbName: 'my-app-local',
  internalDbName: 'syncdb-int',
  storageStrategy: 'indexeddb',
  namespace: 'main',
  database: 'my_db',
  remoteUrl: 'http://localhost:8000',
  tables: ['user', 'thread', 'comment'],
};

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

export async function initDatabase() {
  await db.init();
}

Usage in Components

import { db } from "./db";
import { createSignal, createEffect, onMount, onCleanup, For } from "solid-js";

function ThreadList() {
  const [threads, setThreads] = createSignal([]);

  onMount(async () => {
    // Create live query
    const liveQuery = await db.query.thread
      .find({})
      .orderBy("created_at", "desc")
      .query();

    // React to changes
    createEffect(() => {
      setThreads([...liveQuery.data]);
    });

    // Cleanup on unmount
    onCleanup(() => {
      liveQuery.kill();
    });
  });

  return <For each={threads()}>{(thread) => <div>{thread.title}</div>}</For>;
}

Creating Records

// Creates on remote server, automatically syncs to local cache and updates UI
await db.query.thread.createRemote({
  title: 'New Thread',
  content: 'Thread content',
  author: userId,
  created_at: new Date(),
});

How It Works

┌─────────────────────────────────────────────────────────────┐
│                    Your Application                          │
│  Multiple components can query the same data                 │
└────────────────────────┬─────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                    Query Deduplication                       │
│  Identical queries share a single remote subscription       │
└────────────────────────┬─────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                    Remote SurrealDB Server                   │
│  Live queries watch for data changes                         │
└────────────────────────┬─────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                    Syncer (Cache Manager)                    │
│  Receives changes and updates local cache                    │
└────────────────────────┬─────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                    Local Cache (WASM)                        │
│  Fast reads, automatic synchronization                       │
└────────────────────────┬─────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                    UI Updates (Solid.js)                     │
│  Components re-render with new data                          │
└─────────────────────────────────────────────────────────────┘

API Overview

Query Builder

db.query.tableName
  .find({ status: 'active' }) // Filter records
  .select('field1', 'field2') // Select fields (optional)
  .orderBy('created_at', 'desc') // Sort results
  .limit(50) // Limit results
  .offset(10) // Pagination
  .query(); // Execute and return ReactiveQueryResult

CRUD Operations

// Create
await db.query.thread.createRemote({ title: 'Hello', content: 'World' });

// Read (with live updates)
const liveQuery = await db.query.thread.find().query();

// Update
await db.query.thread.updateRemote(recordId, { title: 'Updated' });

// Delete
await db.query.thread.deleteRemote(recordId);

Reactive Updates

const liveQuery = await db.query.thread.find().query();

createEffect(() => {
  // Spread to create new reference and trigger reactivity
  setThreads([...liveQuery.data]);
});

onCleanup(() => {
  liveQuery.kill(); // Always cleanup!
});

Key Features

Query Deduplication

Multiple components with identical queries automatically share a single remote subscription:

// Component A
const query1 = await db.query.thread.find({ status: 'active' }).query();

// Component B (elsewhere in your app)
const query2 = await db.query.thread.find({ status: 'active' }).query();

// ✨ Only ONE remote subscription is created!
// Both components update simultaneously when data changes

Automatic Cache Synchronization

When you create, update, or delete records on the remote server:

  1. Change is made on remote SurrealDB
  2. Live query detects the change
  3. Syncer updates local cache automatically
  4. All affected queries re-hydrate from cache
  5. UI updates reactively

Type Safety

// Full TypeScript support
const [thread] = await db.query.thread.createRemote({
  title: 'Hello', // ✅ Type-checked
  content: 'World', // ✅ Type-checked
  invalidField: 'oops', // ❌ TypeScript error!
});

// Autocomplete works everywhere
const liveQuery = await db.query.thread
  .find({ status: 'active' }) // ✅ Status field is type-checked
  .orderBy('created_at', 'desc'); // ✅ Field names autocompleted

Documentation

Example

See the complete example application in /example/app-solid demonstrating:

  • User authentication
  • Thread creation and listing
  • Comments with live updates
  • Real-time synchronization
  • Proper cleanup and error handling

Performance

Query Deduplication Benefits

Without Deduplication:
- 100 components with same query = 100 WebSocket subscriptions
- Each update processed 100 times

With Deduplication:
- 100 components with same query = 1 WebSocket subscription
- Each update processed once
- Savings: 99% reduction in network traffic and CPU usage

Memory Usage

Very efficient memory footprint:

  • Per unique query: ~1-2 KB
  • Local cache: Shared across all queries
  • Typical app (10 unique queries): ~20-30 KB

Best Practices

  1. Always cleanup: Call liveQuery.kill() in onCleanup()
  2. Use createRemote(): For CRUD operations to ensure sync
  3. Spread arrays: [...liveQuery.data] to trigger reactivity
  4. Use createEffect(): To reactively update signals
  5. Paginate large lists: Use .limit() and .offset()
  6. Handle errors: Wrap async operations in try-catch

Troubleshooting

Data Not Updating

// ❌ Wrong - won't trigger updates
const threads = liveQuery.data;

// ✅ Correct - creates new reference
createEffect(() => {
  setThreads([...liveQuery.data]);
});

Memory Leaks

// ❌ Wrong - memory leak!
onMount(async () => {
  const liveQuery = await db.query.thread.find().query();
  // Missing cleanup
});

// ✅ Correct - properly cleaned up
onMount(async () => {
  const liveQuery = await db.query.thread.find().query();

  onCleanup(() => {
    liveQuery.kill(); // Essential!
  });
});

"No syncer available" Warning

Make sure you have remoteUrl in your config:

export const dbConfig: SyncedDbConfig<Schema> = {
  // ... other config
  remoteUrl: 'http://localhost:8000', // Don't forget this!
};

Requirements

  • Solid.js 1.8+
  • SurrealDB 2.0+
  • Modern browser with IndexedDB support

License

MIT

Contributing

Contributions are welcome! Please read the architecture documentation to understand how the system works before making changes.

Acknowledgments

Built with:

  • SurrealDB - The ultimate database
  • Solid.js - Simple and performant reactivity
  • Valtio - Proxy-based state management