@cuboapp/crdt 1.0.1 → 1.0.3
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 +2 -2
- package/src/client/index.ts +31 -8
- package/src/index.ts +46 -8
- package/src/yjs/index.ts +29 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cuboapp/crdt",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "CRDT for CUBO",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"repository": "git@github.com:cuboapp/crdt.git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@cuboapp/types": "^2.0.11",
|
|
15
15
|
"@cuboapp/utils": "1.0.9",
|
|
16
|
-
"@cuboapp/ws": "1.0.
|
|
16
|
+
"@cuboapp/ws": "1.0.4",
|
|
17
17
|
"ws": "^8.19.0",
|
|
18
18
|
"yjs": "^13.6.29"
|
|
19
19
|
},
|
package/src/client/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { EventEmitter } from '@cuboapp/utils'
|
|
2
2
|
import { createWsClient, WsClient } from '@cuboapp/ws'
|
|
3
|
+
import { applyUpdate, Doc } from 'yjs'
|
|
3
4
|
|
|
4
5
|
export type CuboCrdtClientOptions = {
|
|
5
6
|
host?: string
|
|
6
7
|
port?: number
|
|
8
|
+
protocol?: 'wss' | 'ws' | string
|
|
7
9
|
authToken?: () => Promise<string>
|
|
8
10
|
debug?: boolean
|
|
9
11
|
}
|
|
@@ -18,6 +20,7 @@ export class CuboCrdtClient {
|
|
|
18
20
|
this.ws = createWsClient({
|
|
19
21
|
host: this.opts.host,
|
|
20
22
|
port: this.opts.port,
|
|
23
|
+
protocol: this.opts.protocol,
|
|
21
24
|
autoReconnect: true,
|
|
22
25
|
pingInterval: 5000,
|
|
23
26
|
authToken: this.opts.authToken ? () => this.opts.authToken!() : () => ''
|
|
@@ -42,25 +45,37 @@ export class CuboCrdtClient {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
async subscribe(
|
|
48
|
+
async subscribe({ entity, id, filters }: { entity: string; id?: number; filters?: any }, cb: any) {
|
|
46
49
|
if (this.opts.debug) {
|
|
47
|
-
console.log('
|
|
50
|
+
console.log('subscribeEntity', { entity, id })
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
filters = filters || {}
|
|
54
|
+
if (id) {
|
|
55
|
+
filters.id = id
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const event = `crdt:${entity}`
|
|
59
|
+
// const event = id ? `crdt:${entity}:${id}` : `crdt:${entity}`
|
|
60
|
+
|
|
50
61
|
this.ee.addListener(event, cb)
|
|
51
62
|
this.ws.request({ method: 'crdt:subscribe', data: { event, filters } })
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
async unsubscribe(
|
|
65
|
+
async unsubscribe({ entity, id, filters }: { entity: string; id?: number; filters?: any }, cb: any) {
|
|
55
66
|
if (this.opts.debug) {
|
|
56
|
-
console.log('
|
|
67
|
+
console.log('subscribeEntity', { entity, id })
|
|
57
68
|
}
|
|
69
|
+
|
|
70
|
+
const event = `crdt:${entity}`
|
|
71
|
+
// const event = id ? `crdt:${entity}:${id}` : `crdt:${entity}`
|
|
72
|
+
|
|
58
73
|
this.ee.removeListener(event, cb)
|
|
59
|
-
this.ws.request({ method: 'crdt:unsubscribe', data: { event } })
|
|
74
|
+
this.ws.request({ method: 'crdt:unsubscribe', data: { event, filters } })
|
|
60
75
|
}
|
|
61
76
|
|
|
62
|
-
async openDocument
|
|
63
|
-
const state = await this.ws.request(
|
|
77
|
+
async openDocument(entity: string, id: number, doc: Doc): Promise<void> {
|
|
78
|
+
const state: any = await this.ws.request(
|
|
64
79
|
{
|
|
65
80
|
method: 'crdt:open',
|
|
66
81
|
data: { name: `${entity}:${id}` }
|
|
@@ -68,7 +83,13 @@ export class CuboCrdtClient {
|
|
|
68
83
|
{ wait: true }
|
|
69
84
|
)
|
|
70
85
|
|
|
71
|
-
|
|
86
|
+
this.ee.addListener(`crdt:update:${entity}:${id}`, (ctx) => {
|
|
87
|
+
applyUpdate(doc, new Uint8Array(ctx.data))
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
this.ws.request({ method: 'crdt:subscribe', data: { crdt: { entity, id } } })
|
|
91
|
+
|
|
92
|
+
applyUpdate(doc, new Uint8Array(state))
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
async closeDocument(entity: string, id: number) {
|
|
@@ -79,5 +100,7 @@ export class CuboCrdtClient {
|
|
|
79
100
|
},
|
|
80
101
|
{ wait: true }
|
|
81
102
|
)
|
|
103
|
+
|
|
104
|
+
this.ee.removeAllListeners(`crdt:document:${entity}:${id}:update`)
|
|
82
105
|
}
|
|
83
106
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,29 +21,43 @@ export class CuboCrdt<
|
|
|
21
21
|
K extends string = Extract<keyof T, string>,
|
|
22
22
|
ID extends string = `${K}:${number | string}`
|
|
23
23
|
> {
|
|
24
|
-
|
|
24
|
+
public documents = new Map<ID, CuboCrdtDocument>()
|
|
25
25
|
private loading = new Map<ID, Promise<CuboCrdtDocument>>()
|
|
26
26
|
|
|
27
27
|
public ee = new EventEmitter()
|
|
28
28
|
|
|
29
29
|
constructor(private options: CuboCrdtOptions<A>) {}
|
|
30
30
|
|
|
31
|
-
public async start() {
|
|
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
|
+
}
|
|
32
45
|
|
|
33
46
|
public async stop() {}
|
|
34
47
|
|
|
35
|
-
public async getDocument(name: ID) {
|
|
48
|
+
public async getDocument(name: ID, autoCreate = true) {
|
|
36
49
|
// существующий документ
|
|
37
50
|
if (!this.documents.has(name)) {
|
|
38
|
-
if (this.
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
if (!this.loading.has(name) && autoCreate) {
|
|
52
|
+
if (this.options.debug) {
|
|
53
|
+
console.log('CRDT create document load start', name)
|
|
54
|
+
}
|
|
41
55
|
|
|
42
|
-
if (!this.loading.has(name)) {
|
|
43
56
|
this.loading.set(
|
|
44
57
|
name,
|
|
45
58
|
new Promise(async (resolve) => {
|
|
46
59
|
const document = new CuboCrdtDocument({
|
|
60
|
+
name,
|
|
47
61
|
autoRemove: true,
|
|
48
62
|
debug: this.options.debug,
|
|
49
63
|
initialState: () => {
|
|
@@ -61,12 +75,36 @@ export class CuboCrdt<
|
|
|
61
75
|
)
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
const document = await this.loading.get(name)
|
|
79
|
+
|
|
80
|
+
this.loading.delete(name)
|
|
81
|
+
this.documents.set(name, document)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (this.options.debug) {
|
|
85
|
+
console.log('CRDT create document load completed', name, this.documents.size)
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
return this.documents.get(name)
|
|
68
89
|
}
|
|
69
90
|
|
|
91
|
+
public async removeDocument(name: ID) {
|
|
92
|
+
if (this.options.debug) {
|
|
93
|
+
console.log('CRDT remove document', name)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.loading.has(name)) {
|
|
97
|
+
try {
|
|
98
|
+
Promise.reject(this.loading.get(name))
|
|
99
|
+
this.loading.delete(name)
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (this.documents.has(name)) {
|
|
104
|
+
this.documents.delete(name)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
70
108
|
public async updateDocument(name: ID, dto: object) {
|
|
71
109
|
const document = await this.getDocument(name)
|
|
72
110
|
|
package/src/yjs/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type WsServerSocket } from '@cuboapp/ws'
|
|
2
|
-
import { Doc } from 'yjs'
|
|
2
|
+
import { Doc, encodeStateAsUpdate } from 'yjs'
|
|
3
3
|
|
|
4
4
|
export type CuboCrdtDocumentHooks = {
|
|
5
5
|
onBeforeDestroy?: () => Promise<void> | void
|
|
@@ -7,6 +7,7 @@ export type CuboCrdtDocumentHooks = {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export type CuboCrdtDocumentOptions = CuboCrdtDocumentHooks & {
|
|
10
|
+
name: string
|
|
10
11
|
autoRemove?: boolean
|
|
11
12
|
debug?: boolean
|
|
12
13
|
initial?: any
|
|
@@ -27,8 +28,7 @@ export class CuboCrdtDocument {
|
|
|
27
28
|
private ydoc: Doc
|
|
28
29
|
public clients = new Set<WsServerSocket>()
|
|
29
30
|
|
|
30
|
-
constructor(public opts
|
|
31
|
-
opts = opts || {}
|
|
31
|
+
constructor(public opts: CuboCrdtDocumentOptions) {
|
|
32
32
|
opts.autoRemove = opts?.autoRemove ?? true
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -38,36 +38,48 @@ export class CuboCrdtDocument {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
get stateAsUpdate() {
|
|
42
|
+
return encodeStateAsUpdate(this.ydoc)
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
async init() {
|
|
46
|
+
console.log('init doc', this.opts.name)
|
|
47
|
+
|
|
42
48
|
this.ydoc = new Doc({
|
|
43
49
|
...(this.opts?.yjsOptions || {}),
|
|
44
50
|
autoLoad: false,
|
|
45
51
|
shouldLoad: false
|
|
46
52
|
})
|
|
47
53
|
|
|
48
|
-
const state = this.opts?.initialState ? await this.opts?.initialState() : {}
|
|
54
|
+
const state = this.opts?.initialState ? (await this.opts?.initialState()) || {} : {}
|
|
55
|
+
|
|
56
|
+
// console.log('initial state', state)
|
|
49
57
|
|
|
50
58
|
return new Promise<void>((resolve) => {
|
|
51
|
-
this.ydoc.once('
|
|
52
|
-
this.ydoc.on('
|
|
59
|
+
this.ydoc.once('update', () => {
|
|
60
|
+
this.ydoc.on('update', (data) => {
|
|
61
|
+
// console.log('document updated', this.opts.name, this.getMap().toJSON())
|
|
62
|
+
|
|
53
63
|
this.opts?.onUpdate?.({ data, document: this })
|
|
54
64
|
})
|
|
55
65
|
|
|
56
66
|
resolve()
|
|
57
67
|
})
|
|
58
68
|
|
|
59
|
-
this.
|
|
60
|
-
const map = t.doc.getMap()
|
|
69
|
+
const map = this.getMap()
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
this.ydoc.transact(() => {
|
|
72
|
+
if (state) {
|
|
73
|
+
Object.entries(state).forEach(([key, value]) => {
|
|
74
|
+
map.set(key, value)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
65
77
|
})
|
|
66
78
|
})
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
getMap(
|
|
70
|
-
return this.ydoc.getMap(
|
|
81
|
+
getMap() {
|
|
82
|
+
return this.ydoc.getMap()
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
async hasClient(client: WsServerSocket) {
|
|
@@ -83,10 +95,12 @@ export class CuboCrdtDocument {
|
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
async write(dto: object) {
|
|
98
|
+
console.log('CRDT write document', this.opts.name, dto)
|
|
99
|
+
|
|
86
100
|
return new Promise<void>(async (resolve) => {
|
|
87
|
-
this.
|
|
88
|
-
const map = doc.getMap()
|
|
101
|
+
const map = this.getMap()
|
|
89
102
|
|
|
103
|
+
this.ydoc.transact(() => {
|
|
90
104
|
Object.entries(dto).forEach(([key, value]) => {
|
|
91
105
|
map.set(key, value)
|
|
92
106
|
})
|