@cuboapp/crdt 1.0.5 → 1.0.6
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 +350 -72
- package/src/client/old.ts +249 -0
- package/src/client/types/document.ts +22 -0
- package/src/client/types/index.ts +51 -0
- package/src/client/types/store.ts +15 -0
- package/src/client/types/utils.ts +35 -0
- package/src/constants/index.ts +5 -0
- package/src/index.ts +1 -129
- package/src/server/document/index.ts +91 -0
- package/src/server/index.ts +449 -0
- package/src/server/old/document/index.ts +107 -0
- package/src/server/old/index.ts +49 -0
- package/src/server/types/document.ts +54 -0
- package/src/server/types/index.ts +22 -0
- package/src/server/types/subscribe.ts +15 -0
- package/src/types/index.ts +3 -9
- package/src/utils/index.ts +65 -3
- package/src/yjs/index.ts +0 -118
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cuboapp/crdt",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "CRDT for CUBO",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"repository": "git@github.com:cuboapp/crdt.git",
|
|
@@ -11,12 +11,16 @@
|
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@cuboapp/types": "^2.0.
|
|
15
|
-
"@cuboapp/utils": "1.0.
|
|
16
|
-
"@cuboapp/ws": "1.0.
|
|
14
|
+
"@cuboapp/types": "^2.0.12",
|
|
15
|
+
"@cuboapp/utils": "1.0.10",
|
|
16
|
+
"@cuboapp/ws": "1.0.5",
|
|
17
17
|
"ws": "^8.19.0",
|
|
18
18
|
"yjs": "^13.6.29"
|
|
19
19
|
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"vue": "^3.5.26",
|
|
22
|
+
"vue-router": "^4.6.3"
|
|
23
|
+
},
|
|
20
24
|
"devDependencies": {
|
|
21
25
|
"@types/node": "^24.10.1",
|
|
22
26
|
"@types/ws": "^8.18.1",
|
package/src/client/index.ts
CHANGED
|
@@ -1,106 +1,384 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { keyBy, pick, uuid } from '@cuboapp/utils'
|
|
2
|
+
import { computed, reactive } from 'vue'
|
|
3
3
|
import { applyUpdate, Doc } from 'yjs'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
protocol?: 'wss' | 'ws' | string
|
|
9
|
-
authToken?: () => Promise<string>
|
|
10
|
-
debug?: boolean
|
|
11
|
-
}
|
|
5
|
+
import { CUBO_CRDT_EVENT } from '../constants'
|
|
6
|
+
import { CuboCrdtServerDocumentIncomingAction } from '../server'
|
|
7
|
+
import { CuboCrdtAction, CuboCrdtKey } from '../types'
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
import {
|
|
10
|
+
CuboCrdtClientDocOrigin,
|
|
11
|
+
CuboCrdtClientDocUpdateOptions,
|
|
12
|
+
CuboCrdtClientList,
|
|
13
|
+
CuboCrdtClientOptions,
|
|
14
|
+
CuboCrdtClientRow,
|
|
15
|
+
CuboCrdtClientStore,
|
|
16
|
+
CuboCrdtClientSubscribeEvent,
|
|
17
|
+
CuboCrdtClientUseOptions
|
|
18
|
+
} from './types'
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
export * from './types'
|
|
21
|
+
|
|
22
|
+
export class CuboCrdtClient<M> {
|
|
23
|
+
public store: CuboCrdtClientStore<M> = {}
|
|
24
|
+
|
|
25
|
+
private listeners: Map<string, Map<string, (ctx: CuboCrdtClientSubscribeEvent) => void>> = new Map()
|
|
26
|
+
|
|
27
|
+
constructor(private opts: CuboCrdtClientOptions<M>) {
|
|
28
|
+
opts.entities.forEach((e) => this.listeners.set(e, new Map()))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async start() {
|
|
32
|
+
this.ws.registerHandler(CUBO_CRDT_EVENT.EVENT, ({ message }) => {
|
|
33
|
+
let events: CuboCrdtClientSubscribeEvent[] = message.data as any
|
|
34
|
+
if (!Array.isArray(events)) {
|
|
35
|
+
events = [events as any]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// todo: perform events through queue in await mode
|
|
39
|
+
events.forEach((event) => {
|
|
40
|
+
const listeners = this.listeners.get(event.entity)
|
|
41
|
+
|
|
42
|
+
if (this.debug) {
|
|
43
|
+
console.log('[CRDT] incoming event', { listeners: listeners?.size, event })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (event.subscribe_id) {
|
|
47
|
+
const listener = listeners?.get(event.subscribe_id)
|
|
48
|
+
if (!listener) {
|
|
49
|
+
console.warn('[CRDT] listener not found:', event.subscribe_id, event)
|
|
50
|
+
} else {
|
|
51
|
+
listener(event)
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
listeners?.forEach((cb) => cb(event))
|
|
55
|
+
}
|
|
56
|
+
})
|
|
27
57
|
})
|
|
28
58
|
}
|
|
29
59
|
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
async stop() {
|
|
61
|
+
this.ws.deleteHandler(CUBO_CRDT_EVENT.EVENT)
|
|
32
62
|
|
|
33
|
-
this.
|
|
63
|
+
this.listeners.clear()
|
|
34
64
|
}
|
|
35
65
|
|
|
36
|
-
public
|
|
37
|
-
|
|
38
|
-
await this.ws.disconnect()
|
|
39
|
-
} catch {}
|
|
66
|
+
public useComputedList<K extends Extract<keyof M, string>>(entity: K) {
|
|
67
|
+
return computed(() => (this.store[entity]?.state.rows || []) as M[K][])
|
|
40
68
|
}
|
|
41
69
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
public useList<K extends Extract<keyof M, string>, T = CuboCrdtKey<K, M>>(
|
|
71
|
+
entity: K | string,
|
|
72
|
+
opts?: CuboCrdtClientUseOptions
|
|
73
|
+
): CuboCrdtClientList<T> {
|
|
74
|
+
const storeKey = opts?.storeKey ?? `${entity}`
|
|
75
|
+
const filters = opts?.filters || {}
|
|
76
|
+
|
|
77
|
+
const subscribe_id = uuid()
|
|
78
|
+
|
|
79
|
+
// создаём хранилище
|
|
80
|
+
if (this.store[storeKey] === undefined) {
|
|
81
|
+
const state = reactive({
|
|
82
|
+
rows: [],
|
|
83
|
+
subscribed: false
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
this.store[storeKey] = {
|
|
87
|
+
state,
|
|
88
|
+
docs: new Map()
|
|
89
|
+
}
|
|
45
90
|
}
|
|
46
|
-
}
|
|
47
91
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
// подписка
|
|
93
|
+
const subscribe = (filters?: Record<string, any>) => {
|
|
94
|
+
if (this.store[storeKey]?.state.subscribed) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ставим сразу флаг, потому что не может быть ошибок (считаем так)
|
|
99
|
+
this.store[storeKey]!.state.subscribed = true
|
|
100
|
+
|
|
101
|
+
filters = filters !== undefined ? filters : opts?.filters
|
|
102
|
+
|
|
103
|
+
if (this.debug) {
|
|
104
|
+
console.log('[CRDT] subscribe', { entity, opts, filters })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// подписываемся на фронте
|
|
108
|
+
this.listeners.get(entity)?.set(subscribe_id, (ctx) =>
|
|
109
|
+
this.onIncomingUpdate(ctx, {
|
|
110
|
+
subscribe_id,
|
|
111
|
+
storeKey,
|
|
112
|
+
...(pick(opts || {}, ['onBeforeCreate', 'onBeforeUpdate', 'onBeforeDelete', 'onAfterCreate', 'onAfterUpdate', 'onAfterDelete']) ||
|
|
113
|
+
{})
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// подписываемся на бэке
|
|
118
|
+
this.ws.request({ method: CUBO_CRDT_EVENT.SUBSCRIBE, data: { subscribe_id, entity, filters } })
|
|
51
119
|
}
|
|
52
120
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
121
|
+
// отписка
|
|
122
|
+
const unsubscribe = (clear?: boolean) => {
|
|
123
|
+
if (this.debug) {
|
|
124
|
+
console.log('[CRDT] unsubscribe', { entity, opts, filters })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// отписываемся на бэке
|
|
128
|
+
this.ws.request({ method: CUBO_CRDT_EVENT.UNSUBSCRIBE, data: { subscribe_id } })
|
|
129
|
+
|
|
130
|
+
// отписываемся на фронте
|
|
131
|
+
this.listeners.get(entity)?.delete(subscribe_id)
|
|
132
|
+
|
|
133
|
+
// очищаем стор
|
|
134
|
+
if (clear !== false) {
|
|
135
|
+
this.store[storeKey] = undefined
|
|
136
|
+
}
|
|
56
137
|
}
|
|
57
138
|
|
|
58
|
-
|
|
59
|
-
|
|
139
|
+
// авто-подписка
|
|
140
|
+
if (opts?.autoSubscribe !== false) {
|
|
141
|
+
subscribe()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
subscribe_id,
|
|
146
|
+
storeKey,
|
|
147
|
+
subscribe,
|
|
148
|
+
unsubscribe,
|
|
149
|
+
docs: computed(() => this.store[storeKey]?.docs),
|
|
150
|
+
subscribed: () => computed(() => this.store[storeKey]?.state.subscribed || false),
|
|
151
|
+
rows: () => computed(() => this.store[storeKey]?.state.rows as T[]),
|
|
152
|
+
rowsById: () => computed(() => keyBy(this.store[storeKey]?.state.rows as T[], 'id' as keyof T))
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public useRow<K extends Extract<keyof M, string>, T = CuboCrdtKey<K, M>>(
|
|
157
|
+
entity: K | string,
|
|
158
|
+
id: number,
|
|
159
|
+
opts?: CuboCrdtClientUseOptions
|
|
160
|
+
): CuboCrdtClientRow<T> {
|
|
161
|
+
// console.log('store key', opts?.storeKey ?? `${entity}:${id}`)
|
|
60
162
|
|
|
61
|
-
this.
|
|
62
|
-
|
|
163
|
+
const { subscribe_id, storeKey, docs, subscribe, unsubscribe, subscribed } = this.useList(entity, {
|
|
164
|
+
...opts,
|
|
165
|
+
filters: opts?.filters ?? { id },
|
|
166
|
+
storeKey: opts?.storeKey ?? `${entity}:${id}`
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
subscribe_id,
|
|
171
|
+
storeKey,
|
|
172
|
+
doc: computed(() => docs.value?.get(id)),
|
|
173
|
+
subscribe,
|
|
174
|
+
unsubscribe,
|
|
175
|
+
subscribed,
|
|
176
|
+
row: () => {
|
|
177
|
+
return computed(() => {
|
|
178
|
+
return (this.store[storeKey]?.state.rows?.[0] ?? null) as T
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
63
182
|
}
|
|
64
183
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
184
|
+
public update<K extends string>(
|
|
185
|
+
entity: K,
|
|
186
|
+
entity_id: number,
|
|
187
|
+
dto: K extends Extract<keyof M, string> ? Partial<M[K]> : object,
|
|
188
|
+
opts?: CuboCrdtClientDocOrigin
|
|
189
|
+
) {
|
|
190
|
+
const cardRow = this.store[`${entity}:${entity_id}`]?.state.rows?.[0]
|
|
191
|
+
const listRow = this.store[entity]?.state?.rows.find((r: any) => r.id === entity_id)
|
|
192
|
+
const cardDoc = this.store[`${entity}:${entity_id}`]?.docs.get(entity_id)
|
|
193
|
+
const listDoc = this.store[entity]?.docs.get(entity_id)
|
|
194
|
+
|
|
195
|
+
// обновляем документы
|
|
196
|
+
for (const doc of [cardDoc, listDoc]) {
|
|
197
|
+
if (doc) {
|
|
198
|
+
const map = doc.getMap()
|
|
199
|
+
|
|
200
|
+
const row = (cardRow || listRow) as any
|
|
201
|
+
const toUpdate = Object.fromEntries(Object.entries(dto).filter(([key, value]) => row[key] !== value))
|
|
202
|
+
|
|
203
|
+
if (Object.keys(toUpdate).length) {
|
|
204
|
+
const origin: CuboCrdtClientDocOrigin = {
|
|
205
|
+
store: opts?.store ?? true,
|
|
206
|
+
expose: opts?.expose ?? 'other',
|
|
207
|
+
keys: opts?.keys ?? Object.keys(toUpdate),
|
|
208
|
+
react: true // отправляем на бэк
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
doc.transact(() => {
|
|
212
|
+
Object.entries(toUpdate).forEach(([key, value]) => {
|
|
213
|
+
map.set(key, value)
|
|
214
|
+
})
|
|
215
|
+
}, origin)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
68
218
|
}
|
|
69
219
|
|
|
70
|
-
|
|
71
|
-
|
|
220
|
+
// обновляем реактивку
|
|
221
|
+
for (const row of [cardRow, listRow]) {
|
|
222
|
+
if (row) {
|
|
223
|
+
const toUpdate = Object.fromEntries(Object.entries(dto).filter(([key, value]) => row[key as keyof typeof row] !== value))
|
|
72
224
|
|
|
73
|
-
|
|
74
|
-
|
|
225
|
+
if (Object.keys(toUpdate).length) {
|
|
226
|
+
Object.entries(toUpdate).forEach(([key, value]) => {
|
|
227
|
+
row[key as keyof typeof row] = value
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
75
232
|
}
|
|
76
233
|
|
|
77
|
-
async
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
234
|
+
private async onDocumentCreate(update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
|
|
235
|
+
if (opts?.onBeforeCreate) {
|
|
236
|
+
const result = await opts.onBeforeCreate(update, ctx, opts)
|
|
237
|
+
if (!result) {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const doc = new Doc()
|
|
243
|
+
applyUpdate(doc, update, { react: false })
|
|
244
|
+
|
|
245
|
+
// подписываемся на обновления документа
|
|
246
|
+
doc.on('update', (update, origin: CuboCrdtClientDocOrigin) => {
|
|
247
|
+
// react = false, если это апдейт с бэка
|
|
248
|
+
if (origin?.react !== false) {
|
|
249
|
+
const data: CuboCrdtServerDocumentIncomingAction = {
|
|
250
|
+
action: 'update',
|
|
251
|
+
entity: ctx.entity,
|
|
252
|
+
entity_id: ctx.entity_id,
|
|
253
|
+
data: Array.from(update),
|
|
254
|
+
origin: {
|
|
255
|
+
...pick(origin || {}, ['store', 'keys', 'expose']),
|
|
256
|
+
subscribe_id: opts?.subscribe_id
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this.ws.request({
|
|
261
|
+
method: CUBO_CRDT_EVENT.EVENT,
|
|
262
|
+
data
|
|
263
|
+
})
|
|
264
|
+
}
|
|
88
265
|
})
|
|
89
266
|
|
|
90
|
-
|
|
267
|
+
// добавляем документ в хранилище документов
|
|
268
|
+
this.store[opts.storeKey]?.docs.set(ctx.entity_id, doc)
|
|
269
|
+
|
|
270
|
+
// добавляем документ в реактивное хранилище
|
|
271
|
+
const json: any = doc.getMap().toJSON()
|
|
272
|
+
const index = this.store[opts.storeKey]?.state.rows.findIndex((r: any) => r.id === ctx.entity_id)
|
|
273
|
+
if (index !== undefined && index >= 0) {
|
|
274
|
+
if (this.debug) {
|
|
275
|
+
console.warn('[CRDT] onDocumentCreate - row already exists: "' + ctx.entity_id + '"', { ctx, opts })
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.store[opts.storeKey]!.state.rows[index] = json
|
|
279
|
+
} else {
|
|
280
|
+
this.store[opts.storeKey]?.state.rows.push(json)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (opts?.onAfterCreate) {
|
|
284
|
+
await opts.onAfterCreate(doc, update, ctx, opts)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async onDocumentUpdate(doc: Doc, update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
|
|
289
|
+
if (opts?.onBeforeUpdate) {
|
|
290
|
+
const result = await opts.onBeforeUpdate(doc, update, ctx, opts)
|
|
291
|
+
if (!result) {
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// обновляем yjs-ный документ
|
|
297
|
+
applyUpdate(doc, update, { react: false })
|
|
298
|
+
|
|
299
|
+
// обновляем документ в реактивном хранилище
|
|
300
|
+
const json: any = doc.getMap().toJSON()
|
|
301
|
+
const index = this.store[opts.storeKey]?.state.rows.findIndex((r: any) => r.id === ctx.entity_id)
|
|
302
|
+
if (index !== undefined && index >= 0) {
|
|
303
|
+
this.store[opts.storeKey]!.state.rows[index] = json
|
|
304
|
+
} else {
|
|
305
|
+
if (this.debug) {
|
|
306
|
+
console.warn('[CRDT] onDocumentUpdate - row not exists: "' + ctx.entity_id + '"', { ctx, opts })
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
this.store[opts.storeKey]!.state.rows.push(json)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (opts?.onAfterUpdate) {
|
|
313
|
+
await opts.onAfterUpdate(doc, update, ctx, opts)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private async onDocumentDelete(doc: Doc, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
|
|
318
|
+
if (opts?.onBeforeDelete) {
|
|
319
|
+
const result = await opts.onBeforeDelete(doc, ctx, opts)
|
|
320
|
+
if (!result) {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// console.log('onDocumentDelete', ctx, opts)
|
|
326
|
+
|
|
327
|
+
doc.destroy()
|
|
328
|
+
this.store[opts.storeKey]?.docs.delete(ctx.entity_id)
|
|
329
|
+
|
|
330
|
+
const index = this.store[opts.storeKey]?.state.rows.findIndex((r: any) => r.id === ctx.entity_id)
|
|
331
|
+
if (index !== undefined && index >= 0) {
|
|
332
|
+
this.store[opts.storeKey]?.state.rows.splice(index, 1)
|
|
333
|
+
} else {
|
|
334
|
+
console.log('row not found', index, ctx, opts)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (opts?.onAfterDelete) {
|
|
338
|
+
await opts.onAfterDelete(doc, ctx, opts)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private onIncomingUpdate(ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
|
|
343
|
+
if (this.debug) {
|
|
344
|
+
console.log('[CRDT] call event listener', ctx, opts)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!this.store[opts.storeKey]) {
|
|
348
|
+
console.warn('[CRDT] on incoming update, store key not found: "' + opts.storeKey + '"')
|
|
349
|
+
}
|
|
91
350
|
|
|
92
|
-
|
|
351
|
+
const doc = this.store[opts.storeKey]?.docs.get(ctx.entity_id)
|
|
352
|
+
const action: CuboCrdtAction = !doc ? 'create' : ctx.action === 'delete' ? 'delete' : 'update'
|
|
353
|
+
|
|
354
|
+
// console.log('action', action)
|
|
355
|
+
|
|
356
|
+
switch (action) {
|
|
357
|
+
case 'create':
|
|
358
|
+
{
|
|
359
|
+
const update = new Uint8Array(ctx.data as any)
|
|
360
|
+
this.onDocumentCreate(update, ctx, opts)
|
|
361
|
+
}
|
|
362
|
+
break
|
|
363
|
+
case 'update':
|
|
364
|
+
if (doc) {
|
|
365
|
+
const update = new Uint8Array(ctx.data as any)
|
|
366
|
+
this.onDocumentUpdate(doc, update, ctx, opts)
|
|
367
|
+
}
|
|
368
|
+
break
|
|
369
|
+
case 'delete':
|
|
370
|
+
if (doc) {
|
|
371
|
+
this.onDocumentDelete(doc, ctx, opts)
|
|
372
|
+
}
|
|
373
|
+
break
|
|
374
|
+
}
|
|
93
375
|
}
|
|
94
376
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
method: 'crdt:close',
|
|
99
|
-
data: { name: `${entity}:${id}` }
|
|
100
|
-
},
|
|
101
|
-
{ wait: true }
|
|
102
|
-
)
|
|
377
|
+
private get ws() {
|
|
378
|
+
return this.opts.ws
|
|
379
|
+
}
|
|
103
380
|
|
|
104
|
-
|
|
381
|
+
private get debug() {
|
|
382
|
+
return this.opts.debug
|
|
105
383
|
}
|
|
106
384
|
}
|