import * as Y from 'yjs'; import {YEvent} from "yjs"; import {pushable} from "it-pushable"; import {transformAsyncIterable, type EventMessage} from "@/iterator"; import {YDocStream} from "./ydoc"; import {yMapIterate} from "./map"; import {yArrayIterator} from "./array"; type SSEIterator = AsyncGenerator; type ComponentIterator = SSEIterator export class YDocSseHtmx { private docStream: YDocStream; constructor(public base:string,public doc:Y.Doc = new Y.Doc()) { console.log("new doc", doc.guid); this.docStream = new YDocStream(this.doc ); } async * subdocs(swap:string, abortSignal?:AbortSignal):SSEIterator { console.log("subdocs", this.doc.guid, swap); for await (const subdoc of this.docStream.subdocs(abortSignal)) { const path = `${swap}-${subdoc.guid}`; if(subdoc.action === "add") { yield { data: `
`, event: swap, } } if(subdoc.action === "delete") { yield { data: ``, event: path, } } } } async * docSse(): SSEIterator { const p = pushable({objectMode: true}); for (const [key,value] of Array.from(this.docStream.doc.share.entries()).sort(([a], [b]) => a.localeCompare(b))) { // console.log("entry", key, value.toJSON()); const iterator =this.component(key, value); (async () => { for await (const event of iterator) { p.push(event); } })().catch(console.error); } // yield* this.subdocs(`${this.doc.guid}:subdocs`, abortSignal) for await (const entry of p) { yield entry; } } async * component(component: string, type: Y.AbstractType>): ComponentIterator { const doc=this.doc; console.log(`***************************************${component}***************************************`); console.log("length", type._length); const abstract= doc.get(component); const value = abstract.toJSON(); console.log("value", value); const path = `${doc.guid}-${component === "" ? "root" : component}`; yield { data: `
`, event: `${doc.guid}`, id: path, } if(component && component !== "") { yield { data: `${component}:`, event: path, id: `${path}:label` } } async function* toHtmxArray(stream: ReturnType): ComponentIterator { console.log("toHtmxArray", stream.raw.length); let i = 0; for await (const item of stream) { yield { data: `
${item instanceof String ? item as string : JSON.stringify(item) }
`, event: path, id: `${path}-${i}` } } } async function* toHtmxMap(stream: ReturnType): ComponentIterator { for await (const [key, {action, newValue}] of stream) { const entry= `${path}-${key}`; if (action === "add") { yield { data: `
                                    ${key}: 
                                    
                                         ${newValue instanceof Object ? JSON.stringify(newValue) : newValue}
                                    
`, event: path, id: entry, } } if (action === "update") { yield { data: newValue instanceof Object ? JSON.stringify(newValue) : newValue as string, event: `${path}-${key}-value`, } } if (action === "delete") { yield { data: ``, event: `${path}-${key}`, id: `${path}-${key}-delete` } } } } if (type._length || value instanceof Array) { yield* toHtmxArray(yArrayIterator(type.doc!.getArray(component))); } else { yield* toHtmxMap( yMapIterate(type.doc!.getMap(component))); } } sse( ) { const stream = this.docSse.bind(this); const encoder = new TextEncoder(); return new ReadableStream({ start(controller) { async function pump() { for await (const line of transformAsyncIterable(stream())) { controller.enqueue(encoder.encode(line)); } } pump().then(r => console.log("done", r)).catch(e => console.error("error", e)); } }) } }