Skip to content

Provider

The Provider is the client-side API that manages Y.js document synchronization, awareness, offline persistence, and RPC operations. It wraps a Connection and handles the higher-level document synchronization protocol.

The provider system is built on two main abstractions:

  • Connection: Manages the low-level network connection (WebSocket, HTTP, or fallback), handles reconnection logic, message buffering, and connection state.
  • Provider: Manages Yjs document synchronization, awareness, offline persistence, and RPC operations. It uses a Connection for network communication.
import { Provider } from "teleportal/providers";
// Create a provider with automatic connection
const provider = await Provider.create({
url: "wss://example.com",
document: "my-document-id",
});
// Wait for document to be synced
await provider.synced;
// Access the Yjs document
const ymap = provider.doc.getMap("data");
ymap.set("key", "value");
// Listen to connection state
provider.on("update", (state) => {
console.log("Connection state:", state.type);
});

Will attempt to connect over one connection type, then fallback to another if it fails (while still attempting to re-dial in the background if possible to upgrade to the preferred connection type):

// Provider.create() automatically uses FallbackConnection
const provider = await Provider.create({
url: "wss://example.com", // Tries WebSocket first, then falls back to HTTP if it fails
document: "my-document",
});

The default connection type, sends & receives all messages over the websocket protocol:

import { WebSocketConnection } from "teleportal/providers/websocket";
const connection = new WebSocketConnection({
url: "wss://example.com",
});
const provider = new Provider({
client: connection,
document: "my-document",
});

A fallback for when websockets don’t work (some corporate networks), utilizes HTTP POSTs for writes and a long-running HTTP SSE connection for reads:

import { HttpConnection } from "teleportal/providers/http";
const connection = new HttpConnection({
url: "https://example.com",
});
const provider = new Provider({
client: connection,
document: "my-document",
});
// Access the Y.js document
const ydoc = provider.doc;
// Create Y.js types
const ytext = ydoc.getText("content");
const ymap = ydoc.getMap("data");
const yarray = ydoc.getArray("items");
// Make changes
ytext.insert(0, "Hello, world!");
ymap.set("key", "value");
yarray.push([1, 2, 3]);
// Listen to document updates
ydoc.on("update", (update, origin) => {
console.log("Document updated");
// Changes are automatically synced to other clients
});

The provider includes an Awareness instance for user presence and cursor information:

// Access awareness
const awareness = provider.awareness;
// Set local state
awareness.setLocalStateField("user", {
name: "John Doe",
color: "#ff0000",
});
// Listen to awareness updates
awareness.on("update", ({ added, updated, removed }) => {
console.log("Users changed:", { added, updated, removed });
});

The provider supports Y.js sub-documents and properly lets the ydoc know that it is synced:

// Listen to subdocument events
provider.on("load-subdoc", ({ subdoc, provider: subdocProvider }) => {
console.log("Subdocument loaded:", subdoc.guid);
// subdocProvider is a Provider instance for the subdocument
});
// Access subdocuments
const subdocProvider = provider.subdocs.get("subdoc-guid");
if (subdocProvider) {
await subdocProvider.synced;
}

Efficiently switch between documents while maintaining the same connection:

// Switch to a new document (reuses connection)
const newProvider = provider.switchDocument({
document: "new-document-id",
});
// Old provider is destroyed, new provider is ready
await newProvider.synced;

The provider automatically enables offline persistence by default using IndexedDB:

const provider = await Provider.create({
url: "wss://example.com",
document: "my-document-id",
enableOfflinePersistence: true, // default
indexedDBPrefix: "my-app-", // custom prefix
});
// Document will be loaded from IndexedDB if available
await provider.loaded; // Resolves when local data is loaded
await provider.synced; // Resolves when synced with server

Monitor connection state:

// Get current connection state
const connection = provider.state;
if (connection.type === "connected") {
console.log("Connected!");
} else if (connection.type === "errored") {
console.error("Connection error:", connection.error);
}
// Wait for connection
try {
await provider.synced;
console.log("Fully synced!");
} catch (error) {
console.error("Sync failed:", error);
}

Multiple providers can share the same connection:

// Create a connection
const connection = new WebSocketConnection({
url: "wss://example.com",
});
// Create multiple providers with the same connection
const provider1 = new Provider({
client: connection,
document: "doc-1",
});
const provider2 = new Provider({
client: connection,
document: "doc-2",
});

The provider supports explicit resource management:

// Using explicit resource management
using provider = await Provider.create({
url: "wss://example.com",
document: "my-document",
});
// Provider automatically disposes when exiting scope
// Or manually destroy
provider.destroy({
destroyConnection: true, // default: true
destroyDoc: true, // default: true
});

The provider extends Observable and emits events:

// Subdocument events
provider.on("load-subdoc", ({ subdoc, provider }) => {
console.log("Subdocument loaded");
});
provider.on("unload-subdoc", ({ subdoc, provider }) => {
console.log("Subdocument unloaded");
});
// Connection state events (delegated from connection)
provider.on("update", (state) => {
console.log("Connection state changed:", state.type);
});
  • Server - Learn how the server handles provider connections
  • Transport - Understand the transport layer
  • Milestones - Learn about document versioning