🚨 Shai-Hulud Strikes Again:834 Packages Compromised.Technical Analysis →
Socket
Book a DemoInstallSign in
Socket

y-dexie

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

y-dexie

Integration of Y.js with Dexie

latest
Source
npmnpm
Version
4.2.1
Version published
Maintainers
1
Created
Source

Integration of Dexie.js and Y.js

Install

npm install dexie
npm install yjs
npm install y-dexie

Database Declaration

import { Dexie, EntityTable } from 'dexie';
import yDexie from 'y-dexie';
import type * as Y from 'yjs';

interface Friend {
    id: number;
    name: string;
    age: number;
    notes: Y.Doc;
}

const db = new Dexie('myDB', { addons: [yDexie] }) as Dexie & {
  friends: EntityTable<Friend, 'id'>
}

db.version(1).stores({
  friends: `
    ++id,
    name,
    age,
    notes: Y.Doc`, // each friend as a 'notes' document
});

When an Y.Doc property has been declared, every object will contain that property. It will be there using a property at the prototype level. The physical notes property is persisted in its own indexedDB table, unlike name, id and age which are stored physically on the object.

Every time you retrieve a database object, its Y.Doc properties will be available. Y.Doc properties can never be null or undefined. However, actual storage of the document data will not be created until someone accesses the document and manipulates it.

Basic Document Access

import { db } from './db.js';
import { DexieYProvider } from 'y-dexie';

// 1. Fetch an object
const friend = await db.friends.get(friendId);
// 2. Get a reference to the notes Y.Doc
const doc = friend.notes;
// 3. Aquire a DexieYProvider
const provider = DexieYProvider.load(doc);
// 4. Load the document
await provider.whenLoaded;

// Manipulate
doc.getText().insert(0, 'hello world');
doc.getMap('myMap').set('key', 'value');
...

// Read contents
const rootText = doc.getText(); // 'hello world'
const subMap = doc.getMap('myMap').get('key'); // 'value'

// 5. When done using the document, release it
DexieYProvider.release(doc); // Decreases ref-count and destroys doc if not accessed anymore.

Using the using keyword (ES2023)

DexieYProvider supports the new using keyword available in Typescript and the most modern web frameworks:

using provider = DexieYProvider.load(doc);
await provider.whenLoaded;
...

The above line is equivalent to the following:

const provider = DexieYProvider.load(doc);
try {
    await provider.whenLoaded;
    ...
} finally {
    DexieYProvider.release(doc);
}

Notices:

DexieYProvider.load() and DexieYProvider.release() maintains a reference counter on the document it loads and releases. When the reference count reaches zero, doc.destroy() is called on the Y.Doc instance.

Y.Doc properties are declared at prototype level - they are not own properties and not physically located on their host object.

Calling friend.notes twice will return the same Y.Doc instance unless the first one has been destroyed, then the second access will return a new Y.Doc instance representing the same document.

Rules for Y properties on objects

  • Y properties are never nullish. If declared in the dexie schema, they'll exist on all objects from all queries (toArray(), get() etc).
  • Y properties are not own properties. They are set on the prototype of the returned object.
  • Y properties are readonly, except when adding new objects to a table (using table.add() or bulkAdd(). Only then, it's possible to create a new Y.Doc() instance - empty or with content - and put it on the property of the object being inserted to the database)
  • If providing a custom Y.Doc to add() or bulkAdd() its udates will be cloned when added.
  • If not providing the Y.Doc or setting the Y property to null when adding objects, there will still be an Y.Doc property on query results of the object, since Y props are defined by the schema and cannot be null or undefined.
  • Y properties on dexie objects are readonly. You can not replace them with another document or update them using table.update() or collection.modify(). The only way to update Y.Docs is via the Y.Doc methods on the document instance.
  • Y properties are not loaded until using DexieYProvider.load() or the new react hook useDocument()
  • Y.Doc instances are kept in a global cache integrated with FinalizationRegistry. First time you access the getter, it will be created, and will stay in cache until it's garbage collected. This means that you'll always get the same Y.Doc instance when querying the same Y property of a the same object. This holds true even if the there are multiple obj instances representing the same ID in the database. All of these will hold one single instance of the Y.Doc because the cache is connected to the primary key of the parent object.

How it works

Internally, every declared Y property generates a dedicated table for Y.js updates tied to the parent table and the property name. Whenever a document is updated, a new entry in this table is added.

DexieYProvider is responsible of loading and observing updates in both directions.

Integrations

Y.js allows multiple providers on the same document. It is possible to combine DexieYProvider with other providers, but it is also possible for dexie addons to extend the provider behavior - such as adding awareness and sync.

Adding sync and awareness

The dexie-cloud-addon integrates with y-dexie and extends the existing DexieYProvider to become a provider also for sync and awareness. Just like other data, Y.Docs will sync to Dexie Cloud Server. A websocket connection will propagate awareness and updates between clients.

Using with React

New hook useDocument() makes use of DexieYProvider as a hook rather than loading and releasing imperatively.


import { useLiveQuery, useDocument } from 'dexie-react-hooks';

function MyComponent(friendId: number) {
  // Query comment object:
  const friend = useLiveQuery(() => db.friends.get(friendId));

  // Use it's document property (friend is undefined on intial render)
  const provider = useDocument(friend?.notes);

  // Pass provider and document to some Y.js compliant code in the ecosystem of such (unless undefined)...
  return provider
    ? <NotesEditor doc={friend.notes} provider={provider} />
    : null;
}

In the sample above, the NotesEditor component could represent any react component backed by the ecosystem of text editors supporting Y.js, such as TipTap or Prosemirror.

Example Applications

Dexie Cloud Starter

This application showcases the following:

  • Collaborative text editing with y-dexie and tiptap
  • Sync and awareness with dexie cloud
  • Full-text search with lunr

FAQs

Package last updated on 18 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts