@cuboapp/crdt 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,132 +1,4 @@
1
- import { type CuboApiEntitiesMap } from '@cuboapp/types'
2
- import { EventEmitter } from '@cuboapp/utils'
3
-
4
- import { CuboCrdtOptions } from './types'
5
- import { CuboCrdtDocument } from './yjs'
6
-
7
1
  export * from './client'
2
+ export * from './server'
8
3
  export * from './types'
9
4
  export * from './utils'
10
- export * from './yjs'
11
-
12
- export const CUBO_CRDT_EVENT = {
13
- DOCUMENT_UPDATED: 'document:updated',
14
- DOCUMENT_DELETED: 'document:deleted',
15
- DOCUMENT_CREATED: 'document:created'
16
- }
17
-
18
- export class CuboCrdt<
19
- T extends CuboApiEntitiesMap<T>,
20
- A = {},
21
- K extends string = Extract<keyof T, string>,
22
- ID extends string = `${K}:${number | string}`
23
- > {
24
- public documents = new Map<ID, CuboCrdtDocument>()
25
- private loading = new Map<ID, Promise<CuboCrdtDocument>>()
26
-
27
- public ee = new EventEmitter()
28
-
29
- constructor(private options: CuboCrdtOptions<A>) {}
30
-
31
- public async start() {
32
- // setInterval(() => {
33
- // console.log('documents', this.documents.size)
34
- // }, 1000)
35
- // const doc = new Doc()
36
- // const map = doc.getMap('store')
37
- // doc.on('update', (upd) => {
38
- // const map1 = doc.getMap('store')
39
- // console.log('new state', map1.toJSON())
40
- // })
41
- // doc.transact(() => {
42
- // map.set('key', 'value')
43
- // })
44
- }
45
-
46
- public async stop() {}
47
-
48
- public async getDocument(name: ID, autoCreate = true) {
49
- // существующий документ
50
- if (!this.documents.has(name)) {
51
- if (!this.loading.has(name) && autoCreate) {
52
- if (this.options.debug) {
53
- console.log('CRDT create document load start', name)
54
- }
55
-
56
- this.loading.set(
57
- name,
58
- new Promise(async (resolve) => {
59
- const document = new CuboCrdtDocument({
60
- name,
61
- autoRemove: true,
62
- debug: this.options.debug,
63
- initialState: () => {
64
- return this.options.beforeLoadDocument?.(name)
65
- },
66
- onUpdate: ({ data, document }) => {
67
- this.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_UPDATED, { name, data, document })
68
- }
69
- })
70
-
71
- await document.init()
72
-
73
- resolve(document)
74
- })
75
- )
76
- }
77
-
78
- const document = await this.loading.get(name)
79
-
80
- this.loading.delete(name)
81
-
82
- if (document) {
83
- this.documents.set(name, document)
84
- }
85
- }
86
-
87
- if (this.options.debug) {
88
- console.log('CRDT create document load completed', name, this.documents.size)
89
- }
90
-
91
- return this.documents.get(name)
92
- }
93
-
94
- public async removeDocument(name: ID) {
95
- if (this.options.debug) {
96
- console.log('CRDT remove document', name)
97
- }
98
-
99
- if (this.loading.has(name)) {
100
- try {
101
- Promise.reject(this.loading.get(name))
102
- this.loading.delete(name)
103
- } catch {}
104
- }
105
-
106
- if (this.documents.has(name)) {
107
- this.documents.delete(name)
108
- }
109
- }
110
-
111
- public async updateDocument(name: ID, dto: object) {
112
- const document = await this.getDocument(name)
113
-
114
- if (document) {
115
- await document.write(dto)
116
- }
117
-
118
- await this.checkDocumentUnload(name)
119
- }
120
-
121
- public async checkDocumentUnload(name: ID) {
122
- const doc = this.documents.get(name)
123
-
124
- if (doc?.opts?.autoRemove && doc.clients.size === 0) {
125
- if (this.options.debug) {
126
- console.log('CRDT destroy document', name)
127
- }
128
-
129
- this.documents.delete(name)
130
- }
131
- }
132
- }
@@ -0,0 +1,84 @@
1
+ import { debounce, pick } from '@cuboapp/utils'
2
+ import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs'
3
+
4
+ import { CuboCrdtServerDocumentOptions, CuboCrdtServerDocumentOrigin } from '../types'
5
+
6
+ export class CuboCrdtServerDocument {
7
+ private ydoc: Doc
8
+
9
+ // private firstUpdate = false
10
+
11
+ constructor(public opts: CuboCrdtServerDocumentOptions) {
12
+ this.ydoc = new Doc({
13
+ ...(this.opts.yjsOptions || {}),
14
+ autoLoad: false
15
+ })
16
+
17
+ // записываем исходное состояние
18
+ this.write(opts.initialState ?? {})
19
+
20
+ // подписываемся на апдейты
21
+ this.ydoc.on('update', (data, origin: CuboCrdtServerDocumentOrigin) => {
22
+ // if (!this.firstUpdate) {
23
+ // console.log('first update', this.name)
24
+ // this.firstUpdate = true
25
+ // // this.opts?.onCreate?.(data, origin)
26
+ // } else {
27
+ this.opts?.onUpdate?.(data, origin)
28
+ // }
29
+
30
+ // если нужно сохранять стейт документа - вызываем store по дебаунсу
31
+ if (origin?.store) {
32
+ const row = this.getJson()
33
+ const body = pick(row, origin.keys ?? Object.keys(row))
34
+
35
+ if (Object.keys(body)) {
36
+ this.debounceStore(body, origin)
37
+ }
38
+ }
39
+ })
40
+ }
41
+
42
+ public get name() {
43
+ return this.opts.name
44
+ }
45
+
46
+ public get stateAsUpdate() {
47
+ return encodeStateAsUpdate(this.ydoc)
48
+ }
49
+
50
+ applyUpdate(update: Uint8Array, origin?: CuboCrdtServerDocumentOrigin) {
51
+ return applyUpdate(this.ydoc, update, origin)
52
+ }
53
+
54
+ getMap() {
55
+ return this.ydoc.getMap()
56
+ }
57
+
58
+ getJson() {
59
+ return this.getMap().toJSON()
60
+ }
61
+
62
+ async write(dto: object, origin?: CuboCrdtServerDocumentOrigin) {
63
+ const map = this.getMap()
64
+
65
+ this.ydoc.transact(() => {
66
+ Object.entries(dto).forEach(([key, value]) => {
67
+ map.set(key, value)
68
+ })
69
+ }, origin)
70
+ }
71
+
72
+ get id() {
73
+ return this.ydoc.guid
74
+ }
75
+
76
+ destroy() {
77
+ this.ydoc.destroy()
78
+ }
79
+
80
+ public store(body: object, origin: CuboCrdtServerDocumentOrigin) {
81
+ this.opts?.onStore?.(body, origin)
82
+ }
83
+ public debounceStore = debounce(this.store.bind(this), 300)
84
+ }