fix(realtime): use number[] for transport but ArrayBuffer for database

Co-authored-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2025-05-19 11:37:22 +02:00
parent 21a1f35281
commit 4869f3310c
No known key found for this signature in database
GPG key ID: DB99ADDDC5C0AF82
9 changed files with 37 additions and 28 deletions

View file

@ -31,7 +31,7 @@ export class RealtimeNoteStore {
const realtimeNote = new RealtimeNote( const realtimeNote = new RealtimeNote(
noteId, noteId,
initialTextContent, initialTextContent,
initialYjsState, initialYjsState ? Array.from(new Uint8Array(initialYjsState)) : undefined,
); );
realtimeNote.on('destroy', () => { realtimeNote.on('destroy', () => {
this.noteIdToRealtimeNote.delete(noteId); this.noteIdToRealtimeNote.delete(noteId);

View file

@ -49,7 +49,7 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
realtimeNote.getRealtimeDoc().getCurrentContent(), realtimeNote.getRealtimeDoc().getCurrentContent(),
false, false,
undefined, undefined,
realtimeNote.getRealtimeDoc().encodeStateAsUpdate(), new Uint8Array(realtimeNote.getRealtimeDoc().encodeStateAsUpdate()),
) )
.then(() => { .then(() => {
realtimeNote.announceMetadataUpdate(); realtimeNote.announceMetadataUpdate();

View file

@ -34,7 +34,7 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
constructor( constructor(
private readonly noteId: number, private readonly noteId: number,
initialTextContent: string, initialTextContent: string,
initialYjsState?: ArrayBuffer, initialYjsState?: number[],
) { ) {
super(); super();
this.logger = new Logger(`${RealtimeNote.name} ${noteId}`); this.logger = new Logger(`${RealtimeNote.name} ${noteId}`);

View file

@ -29,8 +29,8 @@ export enum ConnectionStateEvent {
} }
export interface MessagePayloads { export interface MessagePayloads {
[MessageType.NOTE_CONTENT_STATE_REQUEST]: ArrayBuffer [MessageType.NOTE_CONTENT_STATE_REQUEST]: number[]
[MessageType.NOTE_CONTENT_UPDATE]: ArrayBuffer [MessageType.NOTE_CONTENT_UPDATE]: number[]
[MessageType.REALTIME_USER_STATE_SET]: { [MessageType.REALTIME_USER_STATE_SET]: {
users: RealtimeUser[] users: RealtimeUser[]
ownUser: { ownUser: {

View file

@ -21,12 +21,12 @@ describe('realtime doc', () => {
it('restores a yjs state vector update correctly', () => { it('restores a yjs state vector update correctly', () => {
const realtimeDoc = new RealtimeDoc( const realtimeDoc = new RealtimeDoc(
'notTheVectorText', 'notTheVectorText',
new Uint8Array([ [
1, 1, 221, 208, 165, 230, 3, 0, 4, 1, 15, 109, 97, 114, 107, 100, 111, 1, 1, 221, 208, 165, 230, 3, 0, 4, 1, 15, 109, 97, 114, 107, 100, 111,
119, 110, 67, 111, 110, 116, 101, 110, 116, 32, 116, 101, 120, 116, 67, 119, 110, 67, 111, 110, 116, 101, 110, 116, 32, 116, 101, 120, 116, 67,
111, 110, 116, 101, 110, 116, 70, 114, 111, 109, 83, 116, 97, 116, 101, 111, 110, 116, 101, 110, 116, 70, 114, 111, 109, 83, 116, 97, 116, 101,
86, 101, 99, 116, 111, 114, 85, 112, 100, 97, 116, 101, 0, 86, 101, 99, 116, 111, 114, 85, 112, 100, 97, 116, 101, 0,
]), ],
) )
expect(realtimeDoc.getCurrentContent()).toBe( expect(realtimeDoc.getCurrentContent()).toBe(

View file

@ -16,7 +16,7 @@ import {
const MARKDOWN_CONTENT_CHANNEL_NAME = 'markdownContent' const MARKDOWN_CONTENT_CHANNEL_NAME = 'markdownContent'
export interface RealtimeDocEvents extends EventMap { export interface RealtimeDocEvents extends EventMap {
update: (update: ArrayBuffer, origin: unknown) => void update: (update: number[], origin: unknown) => void
} }
/** /**
@ -37,16 +37,26 @@ export class RealtimeDoc extends EventEmitter2<RealtimeDocEvents> {
* @param initialTextContent the initial text content of the {@link Doc YDoc} * @param initialTextContent the initial text content of the {@link Doc YDoc}
* @param initialYjsState the initial yjs state. If provided this will be used instead of the text content * @param initialYjsState the initial yjs state. If provided this will be used instead of the text content
*/ */
constructor(initialTextContent?: string, initialYjsState?: ArrayBuffer) { constructor(initialTextContent?: string, initialYjsState?: number[]) {
super() super()
if (initialYjsState) { console.debug(
'Creating new RealtimeDoc',
'initialYjsState',
initialYjsState,
'initialTextContent',
initialTextContent,
)
if (initialYjsState !== undefined) {
console.debug('Applying update')
this.applyUpdate(initialYjsState, this) this.applyUpdate(initialYjsState, this)
} else if (initialTextContent) { } else if (initialTextContent !== undefined) {
console.debug('Setting initial text content')
this.getMarkdownContentChannel().insert(0, initialTextContent) this.getMarkdownContentChannel().insert(0, initialTextContent)
} }
console.debug('Setting up listeners')
this.docUpdateListener = (update, origin) => { this.docUpdateListener = (update, origin) => {
this.emit('update', update, origin) this.emit('update', Array.from(update), origin)
} }
this.doc.on('update', this.docUpdateListener) this.doc.on('update', this.docUpdateListener)
} }
@ -77,13 +87,12 @@ export class RealtimeDoc extends EventEmitter2<RealtimeDocEvents> {
* *
* @param encodedTargetStateVector The current state vector of the other y-doc. If provided the update will contain only the differences. * @param encodedTargetStateVector The current state vector of the other y-doc. If provided the update will contain only the differences.
*/ */
public encodeStateAsUpdate( public encodeStateAsUpdate(encodedTargetStateVector?: number[]): number[] {
encodedTargetStateVector?: ArrayBuffer, const update =
): ArrayBuffer { encodedTargetStateVector !== undefined
const update = encodedTargetStateVector
? new Uint8Array(encodedTargetStateVector) ? new Uint8Array(encodedTargetStateVector)
: undefined : undefined
return encodeStateAsUpdate(this.doc, update) return Array.from(encodeStateAsUpdate(this.doc, update))
} }
public destroy(): void { public destroy(): void {
@ -97,11 +106,11 @@ export class RealtimeDoc extends EventEmitter2<RealtimeDocEvents> {
* @param payload The update to apply * @param payload The update to apply
* @param origin A reference that triggered the update * @param origin A reference that triggered the update
*/ */
public applyUpdate(payload: ArrayBuffer, origin: unknown): void { public applyUpdate(payload: number[], origin: unknown): void {
applyUpdate(this.doc, new Uint8Array(payload), origin) applyUpdate(this.doc, new Uint8Array(payload), origin)
} }
public encodeStateVector(): ArrayBuffer { public encodeStateVector(): number[] {
return encodeStateVector(this.doc) return Array.from(encodeStateVector(this.doc))
} }
} }

View file

@ -104,7 +104,7 @@ describe('y-doc-sync-adapter', () => {
console.log('s>2 is connected'), console.log('s>2 is connected'),
) )
docServer.on('update', (update: ArrayBuffer, origin: unknown) => { docServer.on('update', (update: number[], origin: unknown) => {
const message: Message<MessageType.NOTE_CONTENT_UPDATE> = { const message: Message<MessageType.NOTE_CONTENT_UPDATE> = {
type: MessageType.NOTE_CONTENT_UPDATE, type: MessageType.NOTE_CONTENT_UPDATE,
payload: update, payload: update,
@ -118,12 +118,12 @@ describe('y-doc-sync-adapter', () => {
messageTransporterServerTo2.sendMessage(message) messageTransporterServerTo2.sendMessage(message)
} }
}) })
docClient1.on('update', (update: ArrayBuffer, origin: unknown) => { docClient1.on('update', (update: number[], origin: unknown) => {
if (origin !== messageTransporterClient1) { if (origin !== messageTransporterClient1) {
console.log('YDoc on client 1 updated. Sending to Server') console.log('YDoc on client 1 updated. Sending to Server')
} }
}) })
docClient2.on('update', (update: ArrayBuffer, origin: unknown) => { docClient2.on('update', (update: number[], origin: unknown) => {
if (origin !== messageTransporterClient2) { if (origin !== messageTransporterClient2) {
console.log('YDoc on client 2 updated. Sending to Server') console.log('YDoc on client 2 updated. Sending to Server')
} }

View file

@ -97,11 +97,11 @@ export abstract class YDocSyncAdapter {
} }
} }
protected applyIncomingUpdatePayload(update: ArrayBuffer): void { protected applyIncomingUpdatePayload(update: number[]): void {
this.doc.applyUpdate(update, this) this.doc.applyUpdate(update, this)
} }
private distributeDocUpdate(update: ArrayBuffer, origin: unknown): void { private distributeDocUpdate(update: number[], origin: unknown): void {
if (!this.isSynced() || origin === this) { if (!this.isSynced() || origin === this) {
return return
} }

View file

@ -17,7 +17,7 @@ export class YDocSyncServerAdapter extends YDocSyncAdapter {
this.markAsSynced() this.markAsSynced()
} }
protected applyIncomingUpdatePayload(update: ArrayBuffer): void { protected applyIncomingUpdatePayload(update: number[]): void {
if (!this.acceptEditsProvider()) { if (!this.acceptEditsProvider()) {
return return
} }