chore: integrated collab version into YDoc

This commit is contained in:
Bartosz Sypytkowski
2026-02-05 05:52:26 +01:00
parent fa31e607fb
commit eadd2dd1c3
8 changed files with 64 additions and 52 deletions

View File

@@ -11,7 +11,7 @@ import {
workspaceMemberProfileSchema,
WorkspaceMemberProfileTable,
} from '@/application/db/tables/workspace_member_profiles';
import { VersionedDoc, YDoc } from '@/application/types';
import { YDoc } from '@/application/types';
import { Log } from '@/utils/log';
type DexieTables = ViewMetasTable & UserTable & rowTable & WorkspaceMemberProfileTable & VersionsTable;
@@ -130,10 +130,10 @@ export interface OpenCollabOptions {
/**
* Open the collaboration database, and return a function to close it
*/
export async function openCollabDB(name: string, options: OpenCollabOptions = {}): Promise<VersionedDoc> {
export async function openCollabDB(name: string, options: OpenCollabOptions = {}): Promise<YDoc> {
const doc = new Y.Doc({
guid: name,
});
}) as YDoc;
await ensureYjsStores(name);
@@ -148,6 +148,7 @@ export async function openCollabDB(name: string, options: OpenCollabOptions = {}
}
version = options.expectedVersion;
doc.version = version;
provider.on('synced', () => {
if (!openedSet.has(name)) {
@@ -157,7 +158,7 @@ export async function openCollabDB(name: string, options: OpenCollabOptions = {}
await provider.whenSynced;
return { doc, version };
return doc;
}
export async function openCollabDBWithProvider(

View File

@@ -194,7 +194,7 @@ export async function getPublishView<
) {
const name = `${namespace}_${publishName}`;
const { doc } = await openCollabDB(name);
const doc = await openCollabDB(name);
const exist = (await hasViewMetaCache(name)) && hasCollabCache(doc);
let didRevalidate = false;
@@ -251,7 +251,7 @@ export async function getPageDoc<
rows?: Record<RowId, number[]>;
}
>(fetcher: Fetcher<T>, name: string, strategy: StrategyType = StrategyType.CACHE_AND_NETWORK) {
const { doc, version } = await openCollabDB(name);
const doc = await openCollabDB(name);
const exist = hasCollabCache(doc);
let didRevalidate = false;
@@ -295,7 +295,7 @@ export async function getPageDoc<
});
}
return { doc, version };
return doc;
}
async function updateRows(collab: YDoc, rows: Record<RowId, number[]>) {
@@ -399,7 +399,7 @@ export async function revalidatePublishView<
if (subDocuments) {
for (const [key, value] of Object.entries(subDocuments)) {
const { doc } = await openCollabDB(key);
const doc = await openCollabDB(key);
applyYDoc(doc, new Uint8Array(value));
}

View File

@@ -897,7 +897,7 @@ export async function databaseBlobDiff(
return database_blob.DatabaseBlobDiffResponse.decode(bytes);
}
export async function getCollabVersions(workspaceId: string, objectId: string, since: Date | undefined) {
export async function getCollabVersions(workspaceId: string, objectId: string, since?: Date) {
const url = `/api/workspace/${workspaceId}/collab/${objectId}/history`;
const from = since?.getTime() || null;
const response = await axiosInstance?.get<{
@@ -908,7 +908,8 @@ export async function getCollabVersions(workspaceId: string, objectId: string, s
name: string | null,
created_at: string,
created_by: number | null,
is_deleted: boolean
is_deleted: boolean,
editors: number[],
}[];
message: string;
}>(url, {
@@ -927,7 +928,8 @@ export async function getCollabVersions(workspaceId: string, objectId: string, s
parentId: data.parent,
name: data.name,
createdAt: new Date(data.created_at),
isDeleted: data.is_deleted
isDeleted: data.is_deleted,
editors: data.editors,
};
});
}

View File

@@ -60,7 +60,8 @@ import {
UploadPublishNamespacePayload,
ViewIconType,
WorkspaceMember,
YjsEditorKey
YDoc,
YjsEditorKey,
} from '@/application/types';
import { applyYDoc } from '@/application/ydoc/apply';
import { RepeatedChatMessage } from '@/components/chat';
@@ -86,6 +87,14 @@ export class AFClientService implements AFService {
APIService.initAPIService(config.cloudConfig);
}
async createRowDoc(rowKey: string): Promise<YDoc> {
throw new Error('Not implemented');
}
deleteRowDoc(rowKey: string){
throw new Error('Not implemented');
}
getAxiosInstance() {
return APIService.getAxiosInstance();
}
@@ -193,7 +202,7 @@ export class AFClientService implements AFService {
}
async getPublishRowDocument(viewId: string) {
const { doc } = await openCollabDB(viewId);
const doc = await openCollabDB(viewId);
if (hasCollabCache(doc)) {
return doc;
@@ -536,7 +545,7 @@ export class AFClientService implements AFService {
const isLoaded = this.viewLoaded.has(name);
const { doc, version } = await getPageDoc(
const doc = await getPageDoc(
async () => {
try {
return await fetchPageCollab(workspaceId, viewId);
@@ -561,7 +570,7 @@ export class AFClientService implements AFService {
this.viewLoaded.add(name);
}
return { doc, version };
return doc;
}
async getInvitation(invitationId: string) {
@@ -625,10 +634,7 @@ export class AFClientService implements AFService {
return APIService.uploadDatabaseCsvImportFile(presignedUrl, file, onProgress);
}
async getDatabaseCsvImportStatus(
workspaceId: string,
taskId: string
): Promise<DatabaseCsvImportStatusResponse> {
async getDatabaseCsvImportStatus(workspaceId: string, taskId: string): Promise<DatabaseCsvImportStatusResponse> {
return APIService.getDatabaseCsvImportStatus(workspaceId, taskId);
}
@@ -648,7 +654,11 @@ export class AFClientService implements AFService {
return APIService.addAppPage(workspaceId, parentViewId, payload);
}
async createDatabaseView(workspaceId: string, viewId: string, payload: CreateDatabaseViewPayload): Promise<CreateDatabaseViewResponse> {
async createDatabaseView(
workspaceId: string,
viewId: string,
payload: CreateDatabaseViewPayload
): Promise<CreateDatabaseViewResponse> {
return APIService.createDatabaseView(workspaceId, viewId, payload);
}
@@ -774,16 +784,20 @@ export class AFClientService implements AFService {
return APIService.getMentionableUsers(workspaceId);
}
async updatePageMention(workspaceId: string, viewId: string, data: {
person_id: string;
block_id?: string | null;
row_id?: string | null;
require_notification: boolean;
view_name: string;
ancestors?: string[] | null;
view_layout?: number | null;
is_row_document?: boolean;
}) {
async updatePageMention(
workspaceId: string,
viewId: string,
data: {
person_id: string;
block_id?: string | null;
row_id?: string | null;
require_notification: boolean;
view_name: string;
ancestors?: string[] | null;
view_layout?: number | null;
is_row_document?: boolean;
}
) {
return APIService.updatePageMention(workspaceId, viewId, data);
}

View File

@@ -51,7 +51,6 @@ import {
UploadPublishNamespacePayload,
User,
UserWorkspaceInfo,
VersionedDoc,
View,
ViewIconType,
Workspace,
@@ -123,7 +122,7 @@ export interface WorkspaceService {
}
export interface AppService {
getPageDoc: (workspaceId: string, viewId: string, errorCallback?: (error: { code: number }) => void) => Promise<VersionedDoc>;
getPageDoc: (workspaceId: string, viewId: string, errorCallback?: (error: { code: number }) => void) => Promise<YDoc>;
createRowDoc: (rowKey: string) => Promise<YDoc>;
deleteRowDoc: (rowKey: string) => void;
getAppDatabaseViewRelations: (workspaceId: string, databaseStorageId: string) => Promise<DatabaseRelations>;

View File

@@ -428,6 +428,11 @@ export interface YDoc extends Y.Doc {
*/
object_id?: string;
/**
* Collab version for this document.
*/
version?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getMap(key: YjsEditorKey.data_section): YSharedRoot | any;
}
@@ -1069,11 +1074,6 @@ export interface View {
access_level?: AccessLevel;
}
export interface VersionedDoc {
doc: Y.Doc;
version: string | null;
}
export interface UpdatePublishConfigPayload {
comments_enabled?: boolean;
duplicate_enabled?: boolean;

View File

@@ -57,10 +57,10 @@ export function useViewOperations() {
// Register workspace database document for sync
const registerWorkspaceDatabaseDoc = useCallback(
async (workspaceId: string, databaseStorageId: string) => {
const { doc, version } = await openCollabDB(databaseStorageId);
const doc = await openCollabDB(databaseStorageId);
doc.guid = databaseStorageId;
const { doc: workspaceDatabaseDoc } = registerSyncContext({ doc, collabType: Types.WorkspaceDatabase, version });
const { doc: workspaceDatabaseDoc } = registerSyncContext({ doc, collabType: Types.WorkspaceDatabase });
workspaceDatabaseDocMapRef.current.clear();
workspaceDatabaseDocMapRef.current.set(workspaceId, workspaceDatabaseDoc);
@@ -393,7 +393,7 @@ export function useViewOperations() {
hasAwareness: !!awareness,
});
const syncContext = registerSyncContext({ doc, collabType, awareness, version });
const syncContext = registerSyncContext({ doc, collabType, awareness });
docWithMeta._syncBound = true;
@@ -436,8 +436,7 @@ export function useViewOperations() {
});
const syncContext = registerSyncContext({
doc,
collabType: Types.DatabaseRow,
version: null, // atm. versions are not used for database rows
collabType: Types.DatabaseRow
});
createdRowKeys.current.push(rowKey);

View File

@@ -11,7 +11,7 @@ import { openCollabDB } from '@/application/db';
import * as http from '@/application/services/js-services/http/http_api';
import { collabFullSyncBatch } from '@/application/services/js-services/http/http_api';
import { handleMessage, initSync, SyncContext } from '@/application/services/js-services/sync-protocol';
import { Types, User } from '@/application/types';
import { Types, User, YDoc } from '@/application/types';
import { useCurrentUser } from '@/components/main/app.hooks';
import { AppflowyWebSocketType } from '@/components/ws/useAppflowyWebSocket';
import { BroadcastChannelType } from '@/components/ws/useBroadcastChannel';
@@ -25,10 +25,9 @@ export interface RegisterSyncContext {
* The Y.Doc instance to be used for collaboration.
* It must have a valid guid (UUID v4).
*/
doc: Y.Doc;
doc: YDoc;
awareness?: awarenessProtocol.Awareness;
collabType: Types;
version: string | null;
emit?: (reply: messages.IMessage) => void;
}
@@ -396,14 +395,13 @@ export const useSync = (ws: AppflowyWebSocketType, bc: BroadcastChannelType, eve
// remove stale persisted data for older version and reinitialize it
await deleteDB(context.doc.guid);
const { doc } = await openCollabDB(context.doc.guid, { expectedVersion: newVersion, currentUser: user?.uid });
const doc = await openCollabDB(context.doc.guid, { expectedVersion: newVersion, currentUser: user?.uid });
const awareness = new awarenessProtocol.Awareness(doc);
context = registerSyncContext({
doc: awareness.doc,
awareness,
collabType: context.collabType,
version: newVersion || null,
collabType: context.collabType
});
}
@@ -459,16 +457,15 @@ export const useSync = (ws: AppflowyWebSocketType, bc: BroadcastChannelType, eve
context.doc.emit('reset', [context, version])
context.doc.destroy();
const { doc } = await openCollabDB(context.doc.guid, { expectedVersion: version, currentUser: currentUser.uid });
const doc = await openCollabDB(context.doc.guid, { expectedVersion: version, currentUser: currentUser.uid });
Y.applyUpdate(doc, docState);
const awareness = new awarenessProtocol.Awareness(doc);
registerSyncContext({
doc: awareness.doc,
doc,
awareness,
collabType: context.collabType,
version,
collabType: context.collabType
});
}
}, [registeredContexts, currentUser, registerSyncContext]);