@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/package.json +8 -4
- package/src/client/index.ts +381 -72
- package/src/client/types/document.ts +22 -0
- package/src/client/types/index.ts +53 -0
- package/src/client/types/store.ts +15 -0
- package/src/client/types/utils.ts +35 -0
- package/src/constants/index.ts +6 -0
- package/src/index.ts +1 -129
- package/src/server/document/index.ts +84 -0
- package/src/server/index.ts +582 -0
- package/src/server/types/document.ts +56 -0
- package/src/server/types/index.ts +25 -0
- package/src/server/types/subscribe.ts +15 -0
- package/src/types/index.ts +3 -9
- package/src/utils/index.ts +95 -3
- package/src/yjs/index.ts +0 -118
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
|
+
}
|