feat: implement Redis Streams support with stream checkpoints and update history
- Added Redis Streams operations to the message bus interface and implementation. - Introduced StreamCheckpoint model to track last processed stream entry per document. - Implemented UpsertStreamCheckpoint and GetStreamCheckpoint methods in the Postgres store. - Created document_update_history table for storing update payloads for recovery and replay. - Developed update persist worker to handle Redis Stream updates and persist them to Postgres. - Enhanced Docker Compose configuration for Redis with persistence. - Updated frontend API to support fetching document state with optional share token. - Added connection stability monitoring in the Yjs document hook.
This commit is contained in:
@@ -53,8 +53,11 @@ export const documentsApi = {
|
||||
},
|
||||
|
||||
// Get document Yjs state
|
||||
getState: async (id: string): Promise<Uint8Array> => {
|
||||
const response = await authFetch(`${API_BASE_URL}/documents/${id}/state`);
|
||||
getState: async (id: string, shareToken?: string): Promise<Uint8Array> => {
|
||||
const url = shareToken
|
||||
? `${API_BASE_URL}/documents/${id}/state?share=${shareToken}`
|
||||
: `${API_BASE_URL}/documents/${id}/state`;
|
||||
const response = await authFetch(url);
|
||||
if (!response.ok) throw new Error("Failed to fetch document state");
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return new Uint8Array(arrayBuffer);
|
||||
@@ -167,4 +170,4 @@ export const versionsApi = {
|
||||
if (!response.ok) throw new Error('Failed to restore version');
|
||||
return response.json();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -157,10 +157,34 @@ export const useYjsDocument = (documentId: string, shareToken?: string) => {
|
||||
setSynced(true);
|
||||
});
|
||||
|
||||
// Connection stability monitoring with reconnection limits
|
||||
let reconnectCount = 0;
|
||||
const maxReconnects = 10;
|
||||
|
||||
yjsProviders.websocketProvider.on(
|
||||
"status",
|
||||
(event: { status: string }) => {
|
||||
console.log("WebSocket status:", event.status);
|
||||
|
||||
if (event.status === "disconnected") {
|
||||
reconnectCount++;
|
||||
if (reconnectCount >= maxReconnects) {
|
||||
console.error(
|
||||
"Max reconnection attempts reached. Please refresh the page."
|
||||
);
|
||||
// Could optionally show a user notification here
|
||||
} else {
|
||||
console.log(
|
||||
`Reconnection attempt ${reconnectCount}/${maxReconnects}`
|
||||
);
|
||||
}
|
||||
} else if (event.status === "connected") {
|
||||
// Reset counter on successful connection
|
||||
if (reconnectCount > 0) {
|
||||
console.log("Reconnected successfully, resetting counter");
|
||||
}
|
||||
reconnectCount = 0;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const createYjsDocument = async (
|
||||
|
||||
// Load initial state from database BEFORE connecting providers
|
||||
try {
|
||||
const state = await documentsApi.getState(documentId);
|
||||
const state = await documentsApi.getState(documentId, shareToken);
|
||||
if (state && state.length > 0) {
|
||||
Y.applyUpdate(ydoc, state);
|
||||
console.log('✓ Loaded document state from database');
|
||||
@@ -51,7 +51,10 @@ export const createYjsDocument = async (
|
||||
wsUrl,
|
||||
documentId,
|
||||
ydoc,
|
||||
{ params: wsParams }
|
||||
{
|
||||
params: wsParams,
|
||||
maxBackoffTime: 10000, // Max 10s between reconnect attempts
|
||||
}
|
||||
);
|
||||
|
||||
// Awareness for cursors and presence
|
||||
|
||||
Reference in New Issue
Block a user