Yjs PostgreSQL Persistence
y-postgresql is a community-maintained PostgreSQL database adapter designed to provide persistent storage for Yjs documents, commonly used in conjunction with a `y-websocket` server for real-time collaborative applications. As of version `1.0.1`, it offers features for storing Yjs updates and retrieving full document states from PostgreSQL. The package differentiates itself by providing a robust, battle-tested persistence solution for PostgreSQL users within the Yjs ecosystem, handling the serialization and deserialization of Yjs document updates directly. While it is not officially supported by the Yjs core team, it maintains compatibility with recent Yjs versions and provides configurable options like table naming, flush size for merging updates, and indexing for performance tuning. Release cadence is independent of Yjs core, typically driven by community contributions and specific feature requirements or bug fixes related to PostgreSQL integration.
Common errors
-
Cannot find module 'y-postgresql' or its corresponding type declarations.
cause Incorrect module resolution for an ESM-only package in a CommonJS context, or missing Node.js 16+ environment. Could also be a TypeScript configuration issue.fixEnsure your `tsconfig.json` has `"module": "NodeNext"` or `"ESNext"` and `"moduleResolution": "NodeNext"`. Also, confirm Node.js version 16 or newer is in use. For JavaScript, ensure your file uses `.mjs` extension or your `package.json` has `"type": "module"`. -
TypeError: PostgresqlPersistence.build is not a function
cause Attempting to instantiate `PostgresqlPersistence` directly with `new` instead of using the asynchronous static `build` factory method.fixAlways use `await PostgresqlPersistence.build(...)` to create an instance, as it is an asynchronous factory method, not a direct constructor. -
Error: connect ECONNREFUSED ::1:5432 (or similar database connection error)
cause The PostgreSQL database server is not running, is inaccessible from the application's host, or the connection parameters (host, port, user, password, database) are incorrect.fixVerify that your PostgreSQL server is running and accessible from the application. Double-check all connection options passed to `PostgresqlPersistence.build`, especially environment variables like `PG_HOST`, `PG_PORT`, `PG_USER`, `PG_PASSWORD`, and `PG_DATABASE`.
Warnings
- gotcha This package is not officially supported by the Yjs team, meaning ongoing maintenance and compatibility with future Yjs versions are not guaranteed by the core Yjs developers.
- breaking The package requires Node.js version 16 or newer. Running on older Node.js versions will result in runtime errors due to reliance on newer JavaScript features and module resolution.
- gotcha The default `useIndex: false` for the PostgreSQL table means that read operations, especially `getYDoc`, might become slow for applications with many documents or large update histories, as no index is created on the `docname` column.
- gotcha Properly implementing the `bindState` and `writeState` methods within the `setPersistence` callback is crucial for ensuring data durability. Forgetting to listen to `ydoc.on('update', ...)` within `bindState` can lead to data loss if the server crashes before updates are flushed.
Install
-
npm install y-postgresql -
yarn add y-postgresql -
pnpm add y-postgresql
Imports
- PostgresqlPersistence
const PostgresqlPersistence = require('y-postgresql');import { PostgresqlPersistence } from 'y-postgresql'; - PostgresqlPersistenceOptions
import type { PostgresqlPersistenceOptions } from 'y-postgresql'; - PostgresqlConnectionOptions
import type { PostgresqlConnectionOptions } from 'y-postgresql';
Quickstart
import 'dotenv/config';
import http from 'http';
import { WebSocketServer } from 'ws';
import * as Y from 'yjs';
// Assuming these utilities are available from y-websocket or a local setup
import { setPersistence, setupWSConnection } from './websocket/utils.js';
import { PostgresqlPersistence } from 'y-postgresql';
const server = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('okay');
});
const wss = new WebSocketServer({ server });
wss.on('connection', setupWSConnection); // Use the y-websocket setup utility
async function startPersistence() {
const pgdb = await PostgresqlPersistence.build(
{
host: process.env.PG_HOST ?? 'localhost',
port: parseInt(process.env.PG_PORT ?? '5432', 10),
database: process.env.PG_DATABASE ?? 'yjs_db',
user: process.env.PG_USER ?? 'postgres',
password: process.env.PG_PASSWORD ?? '',
},
{ tableName: 'yjs-documents', useIndex: true, flushSize: 200 },
);
setPersistence({
bindState: async (docName, ydoc) => {
const persistedYdoc = await pgdb.getYDoc(docName);
Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(persistedYdoc));
ydoc.on('update', async (update: Uint8Array) => {
pgdb.storeUpdate(docName, update);
});
},
writeState: async (docName, ydoc) => {
// Optional: Ensure all updates are flushed before document destroy
return new Promise((resolve) => resolve());
},
});
server.listen(process.env.PORT ?? 8080, () => {
console.log(`y-websocket server with y-postgresql persistence listening on port: ${process.env.PORT ?? 8080}`);
});
}
startPersistence().catch(console.error);