DEV Community

wes5510
wes5510

Posted on • Edited on

1 1

Yjs + typegoose = yTypegoose

TL;DR

typegoose와 Yjs를 같이 사용할 수 있는 라이브러리를 만듬(유지보수 안함)

본론

처음에 Notion, Roam Research, Workflowy같은 어플리케이션을 만들다가 이전에 긱뉴스에서 본 CRDT 관련 글이 인상깊어서 적용해보기로 했다.
yjs는 그 자체로 CRDT 기능에 충실했고 군더더기 없이 심플했다. 그리고 websocket, indexeddb 등을 지원하기 때문에 레퍼런스가 훌륭하다고 생각했다.
처음에는 y-leveldb와 mongodown 을 섞어서 사용하고 있었지만 오류가 많았다.(이 글을 쓰기 오래전에 만들어서 정확히 생각이 안나지만…^^;) 오류를 고치고 고치고 고치다가 결국 어떻게 돌아가는 지 대략 이해하게 되어서 yTypegoose를 만들어서 사용하게 되었다.

  • 사용 방법
yTypegoose.createModel('nodes');

const docId = 'node-1';
const doc = new Y.Doc();
await yTypegoose.storeUpdate(docId, Y.encodeStateAsUpdate(doc));
Enter fullscreen mode Exit fullscreen mode
  • yTypegoose.ts
import * as Y from 'yjs';
import { getModelForClass, prop, ReturnModelType } from '@typegoose/typegoose';
const Y_TYPEGOOSE = {
DEFAULT_COLLECTION_NAME: 'ydoc',
TYPE: {
STATE_VECTOR: 'sv',
UPDATE: 'update',
META: 'meta',
},
};
enum Type {
STATE_VECTOR = 'sv',
UPDATE = 'update',
META = 'meta',
}
class YDoc {
@prop({ required: true, index: true })
name!: string;
@prop({ required: true, enum: Type })
type!: string;
@prop({ required: true })
value!: Buffer;
}
let YDocModel: ReturnModelType<typeof YDoc>;
const createModel = (
collection = Y_TYPEGOOSE.DEFAULT_COLLECTION_NAME,
): void => {
YDocModel = getModelForClass(YDoc, {
schemaOptions: { collection },
});
};
const getUpdates = async (name: string): Promise<Uint8Array[]> => {
const updates = await YDocModel.find({ name, type: 'update' })
.sort({ _id: -1 })
.select({ value: 1 });
return updates.map((update) => new Uint8Array(update.value));
};
const getYDoc = async (name: string): Promise<Y.Doc> => {
const updates = await getUpdates(name);
const ydoc = new Y.Doc();
ydoc.transact(() => {
for (let i = 0; i < updates.length; i++) {
Y.applyUpdate(ydoc, updates[i]);
}
});
return ydoc;
};
const isExistsDoc = async (name: string): Promise<boolean> => {
const ret = await YDocModel.exists({ name });
return ret;
};
const storeUpdate = async (name: string, value: Uint8Array): Promise<void> => {
const existsDoc = await isExistsDoc(name);
if (!existsDoc) {
const ydoc = new Y.Doc();
Y.applyUpdate(ydoc, value);
const sv = Y.encodeStateVector(ydoc);
await new YDocModel({
name,
type: Y_TYPEGOOSE.TYPE.STATE_VECTOR,
value: Buffer.from(sv),
}).save();
}
await new YDocModel({
name,
type: Y_TYPEGOOSE.TYPE.UPDATE,
value: Buffer.from(value),
}).save();
};
const clearDocument = async (name: string): Promise<void> => {
await YDocModel.deleteMany({ name });
};
export default {
createModel,
getYDoc,
storeUpdate,
clearDocument,
};
view raw yTypegoose.ts hosted with ❤ by GitHub

결론

CRDT를 구현하려면 Yjs를 사용하는 걸 추천한다. 사용하기 쉽고 레퍼런스도 많았다. 그리고 Typegoose와 Yjs를 사용하려면 yTypegoose.ts를 참고하는 것도 나쁘지 않다. 아 그리고 지금 유지보수를 안해서 참고만 하는 게 좋을 거 같다.

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more