npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@syrup-js/adapter-dexie

v0.0.2

Published

A powerful Dexie.js adapter for Syrup that enables seamless local-first development with automatic entity synchronization and persistence. This adapter integrates Syrup's entity system with Dexie.js, providing robust client-side storage with built-in sync

Readme

@syrup-js/adapter-dexie

A powerful Dexie.js adapter for Syrup that enables seamless local-first development with automatic entity synchronization and persistence. This adapter integrates Syrup's entity system with Dexie.js, providing robust client-side storage with built-in sync lifecycle management.

Architecture Overview

graph TD
    subgraph Application
        A[Client Code]
        B[Entity Models]
        R[Remote API Client]
    end

    subgraph Syrup Core
        C[Entity System]
        D[Sync Engine]
        E[Decorators]
        M[Entity Metadata]
    end

    subgraph Dexie Adapter
        F[Middleware Layer]
        G[Hook System]
        H[Entity Serialization]
        I[Query Transformation]
        L[Debug Logger]
    end

    subgraph Storage
        J[Dexie.js]
        K[IndexedDB]
    end

    A --> B
    B --> C
    C --> D
    C --> E
    E --> M

    F --> G
    F --> H
    F --> I
    F --> L

    B --> F
    C --> F
    D --> G
    D --> R

    F --> J
    J --> K

    classDef core fill:#f9f,stroke:#333,stroke-width:2px;
    classDef adapter fill:#bbf,stroke:#333,stroke-width:2px;
    classDef storage fill:#dfd,stroke:#333,stroke-width:2px;

    class C,D,E,M core;
    class F,G,H,I,L adapter;
    class J,K storage;

Features

  • 🔄 Automatic entity serialization and deserialization
  • 🎣 Built-in lifecycle hooks for sync operations
  • 🛡️ Type-safe entity handling
  • ⚡ Optimistic UI support
  • 🔍 Transparent query result transformation
  • 🎯 Zero-config entity table handling
  • 🔌 Middleware-based integration with Dexie.js
  • 🐛 Debug mode for development

Data Flow

flowchart TD
    A[Client Code] --> B{Operation Type}
    B -->|Query| C[Dexie Middleware]
    B -->|Mutation| D[Dexie Middleware]

    C --> E{Entity Table?}
    D --> F{Entity Table?}

    E -->|Yes| G[Execute Query]
    E -->|No| H[Raw Query]

    F -->|Yes| I[Serialize Data]
    F -->|No| J[Raw Mutation]

    G --> K[Wrap Results in Entity]
    H --> L[Return Raw Data]

    I --> M[Execute Mutation]
    J --> N[Execute Raw Mutation]

    K --> O[Final Result]
    L --> O
    M --> P{Is Create/Update?}
    N --> O

    P -->|Yes| Q[Execute Remote Sync]
    P -->|No| O
    Q --> O

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#dfd,stroke:#333,stroke-width:2px
    style D fill:#dfd,stroke:#333,stroke-width:2px
    style G fill:#ffd,stroke:#333,stroke-width:2px
    style I fill:#ffd,stroke:#333,stroke-width:2px
    style Q fill:#f9f,stroke:#333,stroke-width:2px

Installation

npm install @syrup-js/adapter-dexie dexie@^4.0.11
# or
yarn add @syrup-js/adapter-dexie dexie@^4.0.11
# or
pnpm add @syrup-js/adapter-dexie dexie@^4.0.11

Note: This package requires @syrup-js/core as a peer dependency.

Quick Start

import { createDexieAdapter } from '@syrup-js/adapter-dexie';
import Dexie, { type Table } from 'dexie';
import { Entity, entity, sync, type SyncState } from '@syrup-js/core';
import { apiClient } from './api';

// 1. Define your entity
@entity({
	// If you have an API endpoint, you can use the endpoint property:
	endpoint: '/api/todos',
	// Otherwise, implement custom remote sync functions:
	createFn: (todo) =>
		apiClient.todos.create(todo.serialize({ removeSync: true })),
	updateFn: (todo) =>
		apiClient.todos.update(todo.serialize({ removeSync: true })),
	// Optional debug mode
	debug: true
})
export class Todo extends Entity {
	@sync() text: string;
	@sync() done: boolean;
	@sync({ local: true }) lastViewed: Date; // Won't sync to remote

	constructor(args: {
		text: string;
		done: boolean;
		id?: string;
		sync?: SyncState;
	}) {
		super();
		this.text = args.text;
		this.done = args.done;
		this.id = args.id || crypto.randomUUID();
		this.lastViewed = new Date();
		if (args.sync) {
			this.sync = args.sync;
		}
	}
}

// 2. Create your database
class TodoDb extends Dexie {
	todos!: Table<Todo, string>;

	constructor() {
		super('TodoDb');
		this.version(1).stores({
			todos: '&id, text, done' // Define indexes
		});
	}
}

// 3. Initialize the adapter
const db = createDexieAdapter({
	db: new TodoDb(),
	entities: { todos: Todo }
});

export { db };

Use Dexie's liveQuery for optimistic UI (svelte example below, but should work more or less the same in any framework)

<script lang="ts">
    import {db} from '$lib/wherever'
    const todosQuery = liveQuery(() => db.todos.toArray());
    const todos = $derived($todosQuery ?? []);

	let todoText = $state('');
	let todoDone = $state(false);
</script>

<input type="text" bind:value={todoText} />
<input type="checkbox" bind:checked={todoDone} />
<button
	onclick={() => {
	    // no need to call db.put!! instance is automatically created in Dexie
	    // and synced to remote whenever Todo is instantiated
		new Todo({ text: todoText, done: todoDone });
		todoText = '';
		todoDone = false;
	}}>Add</button
>
{#each todos as todo}
	<div class="todo">
		<div>ID: {todo.id}</div>
		<div>Sync Status: {todo.sync.status}</div>
		<div>
			TEXT:
			<input
				type="text"
				class={{ error: todo.sync.error?.type === 'validation' && todo.sync.error.fields.text }}
				bind:value={
					() => todo.text,
					(value) => {
						todo.text = value;
					}
				}
			/>
		</div>
		<div>DONE: {todo.done}</div>
		<button
			onclick={() => {
				todo.done = !todo.done;
			}}>Toggle Done</button
		>
		{#if todo.sync.status === 'error' && todo.sync.error}
			<div class="error">
				<div>Error Type: {todo.sync.error.type}</div>
				<div>Error Message: {todo.sync.error.message}</div>
			</div>
		{/if}
	</div>
{/each}

Sync Lifecycle

sequenceDiagram
    participant C as Client
    participant E as Entity
    participant H as Hooks
    participant D as Dexie DB
    participant R as Remote API
    participant L as Debug Logger

    Note over C,R: Normal Sync Flow

    C->>E: Update Entity

    alt Debug Mode Enabled
        E->>L: Log Entity State
    end

    E->>H: Trigger beforeSync
    H->>D: Save Initial State

    alt Create/Update Operation
        E->>R: Sync to Remote

        alt Successful Sync
            R-->>E: Sync Success
            E->>H: Trigger afterSync
            H->>D: Update Local State
        else Sync Failed
            R-->>E: Sync Error
            E->>H: Trigger onError
            H->>D: Save Error State
        end
    end

    alt Debug Mode Enabled
        E->>L: Log Final State
    end

    E-->>C: Return Result

Advanced Usage

Working with Non-Entity Tables

Tables not configured with an entity class are treated as regular Dexie tables:

// Regular table operations work normally
await db.table('regular_table').put({ id: 1, data: 'test' });

// Entity tables get automatic wrapping and serialization
const todo = await db.table('todos').get(1); // Returns Todo instance

Performance Optimization

The adapter uses a middleware approach that adds minimal overhead:

  • Entity wrapping occurs only for configured entity tables
  • Serialization happens only during mutations
  • Hooks are asynchronous and support concurrent operations
  • Local-only properties skip remote sync

Best Practices

  1. Entity Design

    • Extend the Entity base class
    • Use @sync() for properties that need synchronization
    • Use @sync({ local: true }) for local-only properties
    • Keep entities focused and single-responsibility
    • Implement proper validation in constructors
    • Include proper typing for constructor arguments
  2. Remote Sync Implementation

    • Implement both createFn and updateFn
    • Handle errors appropriately in sync functions
    • Use serialize({ removeSync: true }) for remote APIs
    • Consider retry strategies for network issues
    • Implement proper error mapping
  3. Table Configuration

    • Use clear, semantic table names
    • Define appropriate indexes for query patterns
    • Consider compound indexes for complex queries
    • Document table schema in database class
    • Match index definitions with queried properties

Troubleshooting

Common issues and solutions:

  1. Entity Not Wrapping

    • Verify entity extends Entity base class
    • Check @entity() decorator is applied
    • Ensure table name matches configuration
    • Verify entity class is exported
    • Check constructor implementation
  2. Sync Hooks Not Firing

    • Check @sync() decorators are applied
    • Verify hook configuration
    • Ensure async/await is used properly
    • Check entity table configuration
    • Verify createFn/updateFn implementation
  3. Type Errors

    • Update to latest TypeScript version
    • Verify generic type constraints
    • Check entity interface implementation
    • Ensure proper type exports
    • Verify constructor argument types
  4. Debugging Tips

    • Enable debug mode on entities
    • Check browser console for logs
    • Verify IndexedDB contents
    • Monitor network requests
    • Check entity state transitions

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

MIT