ueberdosis / hocuspocus

The CRDT Yjs WebSocket backend for conflict-free real-time collaboration in your app.
https://tiptap.dev/docs/hocuspocus/introduction
MIT License
1.34k stars 128 forks source link

Allow to use name as guid #798

Closed linspw closed 7 months ago

linspw commented 8 months ago

In my perspective this implementation can be one step in direction of allow subdocuments.

"A simple method to implement lazy-loading documents is to create a provider instance to the doc.guid-room once a document is loaded:"

doc.on('subdocs', ({ loaded }) => {
  loaded.forEach(subdoc => {
    new WebrtcProvider(subdoc.guid, subdoc)
  })
})

https://docs.yjs.dev/api/subdocuments

With this I managed to create a subdocument system with hocuspocus (my implementation):

import { shallowReactive } from 'vue'
import { useHocusRoomManager } from '../utils'
import * as Y from 'yjs'
import { tryOnUnmounted } from '@vueuse/core'

interface State {
  childrenSessions: Map<string, any>
  rootSession: any
}

export const useCollabObject = (roomName: string) => {
  const hocusRoomManager = useHocusRoomManager()

  const yDoc = new Y.Doc({ guid: roomName })

  const rootSession = hocusRoomManager.useSession(yDoc.guid, { doc: yDoc })

  const state: State = {
    childrenSessions: shallowReactive(new Map()),
    rootSession: shallowReactive(rootSession),
  }

  // const yMap = yDoc.getMap<Y.Doc>('files')

  yDoc.on(
    'subdocs',
    ({
      loaded,
      removed,
      added,
    }: {
      loaded: Y.Doc[]
      removed: Y.Doc[]
      added: Y.Doc[]
    }) => {
      added.forEach((subdoc: Y.Doc) => {
        subdoc.load()
      })

      loaded.forEach((subDoc: Y.Doc) => {
        state.childrenSessions.set(
          subDoc.guid,
          hocusRoomManager.useSession(subDoc.guid, { doc: subDoc }),
        )
      })

      removed.forEach((subdoc: Y.Doc) => {
        hocusRoomManager.unuseSession(subdoc.guid)
        state.childrenSessions.delete(subdoc.guid)
      })
    },
  )

  tryOnUnmounted(() => {
    hocusRoomManager.unuseSession(yDoc.guid)
  })

  return { state }
}
janthurau commented 8 months ago

hey @linspw, thanks for your PR! The frontend part indeed looks like it should work, as basically you just need to watch the ydoc events and then fetch the requested subdocuments via the provider. However, I'm not sure why you want to use a custom guid for this? If they have the same guid, they will automatically sync, so eventually the data would be duplicated on the database (as far as I understand).

I started on a really simple subdoc implementation quite a while ago here, but unfortunately havent had time to finish that.

Let me know what you think!

linspw commented 8 months ago

Hi @janthurau , thanks for the answer, I tried without using the guid id and the synchronization didn't happen for some reason, but when using the guid with the name this made the synchronization happen, I don't know why even with the server loading the document without the guide didn't work.

I will send my server test code:

import { Hocuspocus } from '@hocuspocus/server'
import * as Y from 'yjs'
// @ts-ignore

// Configure the server …
const server = new Hocuspocus({
  port: 3030,
  async onAuthenticate(data) {
    if (data.documentName === 'hocus-room_3') {
      throw new Error('Unable to connect')
    }
    return {}
  },

  async onLoadDocument(data) {
    const yDoc = new Y.Doc({ guid: data.documentName })

    if (data.documentName.includes('folder')) {
      yDoc.getText().insert(0, 'some initial content')
    }

    if (data.documentName.includes('space')) {
      const folder = yDoc.getMap('files')

      const subDoc = new Y.Doc({ guid: 'folder_1' })

      folder.set('folder_1', subDoc)
    }

    return yDoc
  },
})

// … and run it!
server.listen()
linspw commented 8 months ago

Hello @janthurau , I sent a new implementation suggestion allowing more flexible specific changes for those who want to customize YDoc.

Please tell me if it doesn't make sense or if there's something wrong. 🙏🏻

janthurau commented 7 months ago

cool! A hook looks good, as this will also allow modifying other yjs options based on the document name or other parameters. Will merge this and release as a pre-version, so you can check if your subdoc approach works :)

janthurau commented 7 months ago

@linspw https://www.npmjs.com/package/@hocuspocus/server/v/2.12.1-rc.0