@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.
@@ -0,0 +1,582 @@
1
+ import { cloneDeep } from '@cuboapp/utils'
2
+ import { WsServerSocket } from '@cuboapp/ws'
3
+
4
+ import { CUBO_CRDT_EVENT } from '../constants'
5
+ import { CuboCrdtAction } from '../types'
6
+ import { checkRowIsSutable } from '../utils'
7
+
8
+ import { CuboCrdtClientDocOrigin } from '../client'
9
+ import { CuboCrdtServerDocument } from './document'
10
+ import {
11
+ CuboCrdtServerDocumentIncomingAction,
12
+ CuboCrdtServerDocumentOrigin,
13
+ CuboCrdtServerOptions,
14
+ CuboCrdtServerSubscribe,
15
+ CuboCrdtServerSubscribeDto,
16
+ CuboCrdtServerUnsubscribeDto,
17
+ CuboCrdtSocketClient
18
+ } from './types'
19
+
20
+ export * from './document'
21
+ export * from './types'
22
+
23
+ export class CuboCrdtServer<M, A = {}, E extends Extract<keyof M, string> = Extract<keyof M, string>> {
24
+ constructor(private options: CuboCrdtServerOptions<M, A, E>) {}
25
+
26
+ private clients = new Map<string, CuboCrdtSocketClient<A>>()
27
+ private queue = new Map<string, Set<any>>()
28
+ private sendTimeouts = new Map<string, any>()
29
+
30
+ private subscribes = new Map<string, CuboCrdtServerSubscribe>()
31
+ private subscribesByClient = new Map<string, Set<string>>()
32
+ private subscribesByEntity = new Map<E, Set<string>>()
33
+ private subscribesIniting = new Map<string, Promise<void>>()
34
+ private subscribesUpgrading = new Map<string, Promise<void>>()
35
+
36
+ private documents = new Map<string, CuboCrdtServerDocument>()
37
+ private subscribesByDocument = new Map<string, Set<string>>()
38
+
39
+ public async init() {
40
+ this.entities.forEach((e) => this.subscribesByEntity.set(e, new Set()))
41
+
42
+ this.ws.registerHandler(CUBO_CRDT_EVENT.SUBSCRIBE, async ({ client, message }) => {
43
+ const { subscribe_id: id, entity, filters } = message.data as CuboCrdtServerSubscribeDto
44
+
45
+ if (this.debug) {
46
+ console.log('[CRDT] subscribe', entity, filters)
47
+ }
48
+
49
+ const subscribe = {
50
+ id,
51
+ client_id: client.id,
52
+ entity,
53
+ filters
54
+ }
55
+
56
+ this.subscribes.set(id, subscribe)
57
+
58
+ // создаём мапу подписок по клиенту
59
+ this.subscribesByClient.get(client.id)?.add(id)
60
+
61
+ // создаём мапу подписок по сущности
62
+ this.subscribesByEntity.get(entity as E)?.add(id)
63
+
64
+ // инициализируем подписку
65
+ this.initSubscribe(client, subscribe)
66
+ })
67
+
68
+ this.ws.registerHandler(CUBO_CRDT_EVENT.UPGRADE, async ({ client, message }) => {
69
+ const { subscribe_id: id, entity, filters } = message.data as CuboCrdtServerSubscribeDto
70
+
71
+ if (!this.debug) {
72
+ console.log('[CRDT] upgrade', entity, filters)
73
+ }
74
+
75
+ const subscribe = this.subscribes.get(id)
76
+ if (!subscribe) {
77
+ return
78
+ }
79
+
80
+ // обновляем подписку
81
+ this.upgradeSubscribe(client, subscribe, { filters })
82
+ })
83
+
84
+ this.ws.registerHandler(CUBO_CRDT_EVENT.UNSUBSCRIBE, async ({ message }) => {
85
+ const { subscribe_id } = message.data as CuboCrdtServerUnsubscribeDto
86
+
87
+ this.cleanSubscribe(subscribe_id)
88
+ })
89
+
90
+ this.ws.registerHandler(CUBO_CRDT_EVENT.EVENT, async ({ message }) => {
91
+ let data = message.data as any
92
+ if (!Array.isArray(data)) {
93
+ data = [data]
94
+ }
95
+
96
+ if (this.debug) {
97
+ console.log('[CRDT] incoming event', data)
98
+ }
99
+
100
+ data.forEach((row: any) => {
101
+ const update = new Uint8Array(row.data)
102
+
103
+ switch (row.action) {
104
+ case 'create':
105
+ this.onDocumentExternalCreate(update, row)
106
+ break
107
+ case 'update':
108
+ this.onDocumentExternalUpdate(update, row)
109
+ break
110
+ case 'delete':
111
+ this.onDocumentExternalDelete(update, row)
112
+ break
113
+ }
114
+ })
115
+ })
116
+
117
+ this.ws.registerHttpHandler('GET', '/stats', () => {
118
+ return {
119
+ clients: this.clients.size,
120
+ subscribes: {
121
+ size: this.subscribes.size,
122
+ byClient: this.subscribesByClient.size,
123
+ byEntity: Array.from(this.subscribesByEntity)
124
+ .map((m) => ({
125
+ entity: m[0],
126
+ size: m[1].size
127
+ }))
128
+ .filter((s) => s.size > 0)
129
+ },
130
+ documents: {
131
+ size: this.documents.size,
132
+ rows: Array.from(this.documents).map((d) => ({
133
+ id: d[0],
134
+ subs: Array.from(this.subscribesByDocument.get(d[0]) || [])
135
+ }))
136
+ }
137
+ }
138
+ })
139
+ }
140
+
141
+ public async destroy() {
142
+ this.ws.deleteHandler(CUBO_CRDT_EVENT.SUBSCRIBE)
143
+ this.ws.deleteHandler(CUBO_CRDT_EVENT.UNSUBSCRIBE)
144
+ this.ws.deleteHandler(CUBO_CRDT_EVENT.EVENT)
145
+
146
+ this.subscribes.forEach((sub) => this.ws.deleteHandler(sub.id))
147
+ }
148
+
149
+ public addClient(client: WsServerSocket<{ auth?: A }>) {
150
+ // добавляем клиента
151
+ this.clients.set(client.id, client)
152
+
153
+ // создаем мапу подписок по клиенту
154
+ this.subscribesByClient.set(client.id, new Set())
155
+
156
+ // создаём очередь под клиента
157
+ this.queue.set(client.id, new Set())
158
+ }
159
+
160
+ public removeClient(client: WsServerSocket) {
161
+ // удаляем все подписки по клиенту
162
+ this.subscribesByClient.get(client.id)?.forEach((subscribe_id) => this.cleanSubscribe(subscribe_id))
163
+
164
+ // удаляем мапу подписок по клиенту
165
+ this.subscribesByClient.delete(client.id)
166
+
167
+ // удаляем очередь под клиента
168
+ this.queue.delete(client.id)
169
+
170
+ // и в принципе клиента
171
+ this.clients.delete(client.id)
172
+ }
173
+
174
+ private sendQueueToClient(client: CuboCrdtSocketClient<A>) {
175
+ const queue = this.queue.get(client.id)
176
+
177
+ // const data = Array.from(queue)
178
+ const data = [...new Map((Array.from(queue) || []).map((item) => [JSON.stringify(item), item])).values()]
179
+
180
+ queue.clear()
181
+
182
+ client.send(
183
+ JSON.stringify({
184
+ method: CUBO_CRDT_EVENT.EVENT,
185
+ data
186
+ })
187
+ )
188
+
189
+ queue.clear()
190
+ }
191
+
192
+ public sendToClient(client_id: string, data: any) {
193
+ const client = this.clients.get(client_id)
194
+ if (!client) {
195
+ return
196
+ }
197
+
198
+ // сбрасываем таймаут если он есть
199
+ clearTimeout(this.sendTimeouts.get(client_id))
200
+
201
+ // добавляем в очередь
202
+ this.queue.get(client_id).add(data)
203
+
204
+ // если очередь >10 то сразу отправляем
205
+ if (this.queue.size > 10) {
206
+ this.sendQueueToClient(client)
207
+ }
208
+ // если нет - ставим таймаут
209
+ else {
210
+ this.sendTimeouts.set(
211
+ client_id,
212
+ setTimeout(() => this.sendQueueToClient(client), 100)
213
+ )
214
+ }
215
+ }
216
+
217
+ // если у документа нет подписок - сносим его
218
+ private checkDocumentNeedRemove(name: string) {
219
+ if (!this.subscribesByDocument.get(name)?.size) {
220
+ if (this.debug) {
221
+ console.log('[CRDT] delete document', name)
222
+ }
223
+
224
+ this.documents.get(name)?.destroy()
225
+ this.documents.delete(name)
226
+ }
227
+ }
228
+
229
+ // очистка подписки (при отписке клиента - через emit-метод или options-хук)
230
+ private cleanSubscribe(subscribe_id: string) {
231
+ const subscribe = this.subscribes.get(subscribe_id)
232
+ if (subscribe) {
233
+ if (this.debug) {
234
+ console.log('[CRDT] unsubscribe', subscribe.entity, subscribe.filters)
235
+ }
236
+
237
+ // удаляем подписку по сущности
238
+ this.subscribesByEntity.get(subscribe.entity as E)?.delete(subscribe.id)
239
+
240
+ // отписываем все документы от подписки
241
+ this.subscribesByDocument.forEach((subscribes, docName) => {
242
+ subscribes.delete(subscribe_id)
243
+
244
+ this.checkDocumentNeedRemove(docName)
245
+ })
246
+
247
+ // удаляем подписки
248
+ this.subscribes.delete(subscribe_id)
249
+ this.subscribesByClient.get(subscribe.client_id)?.delete(subscribe_id)
250
+ this.subscribesByEntity.get(subscribe.entity as E)?.delete(subscribe_id)
251
+ }
252
+ }
253
+
254
+ public getDocument(entity: string, entity_id: number) {
255
+ return this.documents.get(`${entity}:${entity_id}`)
256
+ }
257
+
258
+ public getSutableSubscribes(entity: string, row: any) {
259
+ return Array.from(this.subscribesByEntity.get(entity as E) || [])
260
+ .filter((m) => {
261
+ const subscribe = this.subscribes.get(m[1])
262
+
263
+ let state = subscribe && checkRowIsSutable(row, subscribe)
264
+ if (this.options.checkRowIsSutable) {
265
+ state = this.options.checkRowIsSutable(state, { entity: entity as any, subscribe, row })
266
+ }
267
+
268
+ return state
269
+ })
270
+ .map((m) => m[1])
271
+ }
272
+
273
+ public ensureDocument(entity: E, row: any) {
274
+ // todo: врапнуть всё это в Promise
275
+ const entity_id = row.id
276
+ const documentName = `${entity}:${entity_id}`
277
+
278
+ // проверяем документ
279
+ let document = this.documents.get(documentName)
280
+ if (!document) {
281
+ document = new CuboCrdtServerDocument({
282
+ name: documentName,
283
+ initialState: row,
284
+ onUpdate: (data: Uint8Array, origin?: CuboCrdtServerDocumentOrigin) => {
285
+ // пушим обновление документа
286
+ // console.log('push document update', document.name)
287
+
288
+ this.pushDocumentAction('update', entity, document!, data, origin)
289
+ },
290
+ onStore: (item: object, origin: CuboCrdtServerDocumentOrigin) => {
291
+ const subscribe = origin.subscribe_id && this.subscribes.get(origin.subscribe_id)
292
+ const client = subscribe && this.clients.get(subscribe.client_id)
293
+
294
+ if (client) {
295
+ return this.options?.storeRow?.(entity as any, entity_id, item as any, { client, origin })
296
+ }
297
+ }
298
+ })
299
+
300
+ // создаём документ
301
+ this.documents.set(documentName, document)
302
+
303
+ // создаём пул подписок по документу
304
+ this.subscribesByDocument.set(documentName, new Set())
305
+
306
+ // пушим создание документа
307
+ this.pushDocumentAction('create', entity, document, document.stateAsUpdate)
308
+
309
+ // console.log('create document', documentName)
310
+ } else {
311
+ // console.log('update document', documentName)
312
+
313
+ // пушим обновление документа
314
+ this.pushDocumentAction('upsert', entity, document, document.stateAsUpdate)
315
+ }
316
+
317
+ return document
318
+ }
319
+
320
+ public deleteDocument(entity: E, row: any, origin?: CuboCrdtClientDocOrigin) {
321
+ if (this.debug) {
322
+ console.log('[CRDT] delete document', entity, row.id, origin)
323
+ }
324
+
325
+ const documentName = `${entity}:${row.id}`
326
+ const document = this.documents.get(documentName)
327
+
328
+ if (document) {
329
+ // сначала пушим удаление документа
330
+ this.pushDocumentAction('delete', entity, document, null, origin)
331
+
332
+ // удаляем документ
333
+ this.documents.delete(documentName)
334
+
335
+ // дестроим документ
336
+ document.destroy()
337
+ }
338
+ }
339
+
340
+ private onDocumentExternalCreate(update: Uint8Array, action: CuboCrdtServerDocumentIncomingAction) {
341
+ console.warn('[CRDT] onDocumentExternalCreate not implemented')
342
+ }
343
+
344
+ private onDocumentExternalUpdate(update: Uint8Array, action: CuboCrdtServerDocumentIncomingAction) {
345
+ const document = this.getDocument(action.entity, action.entity_id)
346
+
347
+ // console.log('onDocumentExternalUpdate', action)
348
+
349
+ if (!document) {
350
+ console.warn('[CRDT] onDocumentExternalUpdate - document not found:', action.entity, action.entity_id)
351
+ } else {
352
+ document.applyUpdate(new Uint8Array(update), action.origin)
353
+ }
354
+ }
355
+
356
+ private onDocumentExternalDelete(update: Uint8Array, action: CuboCrdtServerDocumentIncomingAction) {
357
+ console.warn('[CRDT] onDocumentExternalDelete not implemented')
358
+ }
359
+
360
+ private checkSubscribeIsSutable(origin: CuboCrdtServerDocumentOrigin, subscribe?: CuboCrdtServerSubscribe) {
361
+ if (!subscribe) {
362
+ return false
363
+ }
364
+
365
+ const noStrategyResolved = !origin?.expose || origin?.expose === 'all'
366
+ const strategyOtherResolved = (origin?.expose === 'other' && origin.subscribe_id !== subscribe?.id) || false
367
+ const strategyClientResolved = origin?.expose === 'client' && subscribe?.client_id === origin.client_id
368
+ const strategySubscribeResolved = origin?.expose === 'subscribe' && subscribe?.id === origin.subscribe_id
369
+
370
+ return noStrategyResolved || strategyOtherResolved || strategyClientResolved || strategySubscribeResolved
371
+ }
372
+
373
+ private checkRowIsSutable(row: any, subscribe: CuboCrdtServerSubscribe, entity: E) {
374
+ const baseState = checkRowIsSutable(row, subscribe.filters || {})
375
+
376
+ if (!this.options.checkRowIsSutable) {
377
+ return baseState
378
+ }
379
+
380
+ return this.options.checkRowIsSutable(baseState, { entity, subscribe, row: row as any })
381
+ }
382
+
383
+ private pushDocumentAction(
384
+ action: CuboCrdtAction,
385
+ entity: E,
386
+ document: CuboCrdtServerDocument,
387
+ data?: Uint8Array | null,
388
+ origin?: CuboCrdtServerDocumentOrigin
389
+ ) {
390
+ const row = document.getJson()
391
+
392
+ // берём все подписки по сущности
393
+ const sutableSubscribes = Array.from(this.subscribesByEntity.get(entity))
394
+ .map((subscribe_id) => {
395
+ const subscribe = this.subscribes.get(subscribe_id)
396
+
397
+ const subscribeSutable = this.checkSubscribeIsSutable(origin, subscribe)
398
+ const documentExistsInSubscribe = this.subscribesByDocument.get(document.name)?.has(subscribe.id)
399
+ const rowSutable = this.checkRowIsSutable(row, subscribe, entity)
400
+
401
+ const sutable = subscribeSutable && (rowSutable || documentExistsInSubscribe)
402
+
403
+ if (sutable) {
404
+ if (this.debug && !rowSutable) {
405
+ console.warn('row is not sutable for subscribe', {
406
+ entity,
407
+ row,
408
+ subscribe,
409
+ subscribeSutable,
410
+ documentExistsInSubscribe,
411
+ rowSutable
412
+ })
413
+ }
414
+
415
+ let sutableAction = action
416
+ let sutableData: any = data ? Array.from(data as any).slice(0) : undefined
417
+
418
+ if (!rowSutable && action === 'update') {
419
+ if (this.debug) {
420
+ console.log('switch action', action + ' -> delete', { name: document.name, subscribe: subscribe.id })
421
+ }
422
+
423
+ sutableAction = 'delete'
424
+ sutableData = null
425
+ } else if (action === 'update' && !documentExistsInSubscribe) {
426
+ if (this.debug) {
427
+ console.log('switch action', 'update -> create', { name: document.name, subscribe: subscribe.id })
428
+ }
429
+
430
+ sutableAction = 'create'
431
+ sutableData = Array.from(document.stateAsUpdate)
432
+ } else if (action === 'upsert' && !documentExistsInSubscribe) {
433
+ if (this.debug) {
434
+ console.log('switch action', 'upsert -> create', { name: document.name, subscribe: subscribe.id })
435
+ }
436
+
437
+ sutableAction = 'create'
438
+ sutableData = Array.from(document.stateAsUpdate)
439
+ } else if (action === 'delete' && !documentExistsInSubscribe) {
440
+ if (this.debug) {
441
+ console.warn('document to delete is not exist', { name: document.name, subscribe: subscribe.id })
442
+ }
443
+ } else if (action === 'create' && documentExistsInSubscribe) {
444
+ if (this.debug) {
445
+ console.warn('document to create is already exists in subscribe', { name: document.name, subscribe: subscribe.id })
446
+ }
447
+ sutableAction = 'update'
448
+ }
449
+
450
+ return {
451
+ subscribe,
452
+ action: sutableAction,
453
+ data: sutableData
454
+ }
455
+ }
456
+
457
+ return null
458
+ })
459
+ .filter((i) => !!i) as { subscribe: CuboCrdtServerSubscribe; action: CuboCrdtAction; data: undefined | number[] }[]
460
+
461
+ if (sutableSubscribes.length) {
462
+ const documentSubscribes = this.subscribesByDocument.get(document.name)
463
+
464
+ sutableSubscribes.forEach(({ subscribe, action, data }) => {
465
+ this.sendToClient(subscribe.client_id, {
466
+ action,
467
+ entity: subscribe.entity,
468
+ entity_id: row.id,
469
+ data,
470
+ subscribe_id: subscribe.id
471
+ })
472
+
473
+ switch (action) {
474
+ case 'create':
475
+ case 'upsert':
476
+ case 'update':
477
+ documentSubscribes.add(subscribe.id)
478
+ break
479
+ case 'delete':
480
+ documentSubscribes.delete(subscribe.id)
481
+ this.checkDocumentNeedRemove(document.name)
482
+ break
483
+ }
484
+ })
485
+ }
486
+ }
487
+
488
+ private async pushSubscribeDocuments(client: WsServerSocket, subscribe: CuboCrdtServerSubscribe) {
489
+ // получаем список всех документов
490
+ const rows = await this.options.fetchRows?.(client, subscribe)
491
+
492
+ // пушим их в сокеты
493
+ for (const row of rows || []) {
494
+ // this.ensureDocument(subscribe.entity as E, row, { subscribes_ids: [subscribe.id] })
495
+ this.ensureDocument(subscribe.entity as E, row)
496
+ }
497
+ }
498
+
499
+ // апгрейд подписки (фильтры поменялись например)
500
+ private async upgradeSubscribe(
501
+ client: WsServerSocket,
502
+ subscribe: CuboCrdtServerSubscribe,
503
+ upgrade: Partial<Pick<CuboCrdtServerSubscribe, 'filters'>>
504
+ ) {
505
+ if (this.subscribesUpgrading.has(subscribe.id)) {
506
+ return this.subscribesUpgrading.get(subscribe.id)
507
+ }
508
+
509
+ this.subscribesUpgrading.set(
510
+ subscribe.id,
511
+ new Promise<void>(async (resolve, reject) => {
512
+ try {
513
+ if (this.debug) {
514
+ console.log('[CRDT] upgrade subscribe ' + client.id, subscribe)
515
+ }
516
+
517
+ if (upgrade.filters) {
518
+ subscribe.filters = cloneDeep(upgrade.filters || {})
519
+ }
520
+
521
+ await this.pushSubscribeDocuments(client, subscribe)
522
+
523
+ resolve()
524
+ } catch (e) {
525
+ if (this.debug) {
526
+ console.error('[CRDT] upgrade subscribe', e)
527
+ }
528
+
529
+ reject(e)
530
+ } finally {
531
+ this.subscribesUpgrading.delete(subscribe.id)
532
+ }
533
+ })
534
+ )
535
+
536
+ return this.subscribesUpgrading.get(subscribe.id)
537
+ }
538
+
539
+ // инциализация подписки
540
+ private async initSubscribe(client: WsServerSocket, subscribe: CuboCrdtServerSubscribe) {
541
+ if (this.subscribesIniting.has(subscribe.id)) {
542
+ return this.subscribesIniting.get(subscribe.id)
543
+ }
544
+
545
+ this.subscribesIniting.set(
546
+ subscribe.id,
547
+ new Promise<void>(async (resolve, reject) => {
548
+ try {
549
+ if (this.debug) {
550
+ console.log('[CRDT] init subscribe ' + client.id, subscribe)
551
+ }
552
+
553
+ await this.pushSubscribeDocuments(client, subscribe)
554
+
555
+ resolve()
556
+ } catch (e) {
557
+ if (this.debug) {
558
+ console.error('[CRDT] init subscribe', e)
559
+ }
560
+
561
+ reject(e)
562
+ } finally {
563
+ this.subscribesIniting.delete(subscribe.id)
564
+ }
565
+ })
566
+ )
567
+
568
+ return this.subscribesIniting.get(subscribe.id)
569
+ }
570
+
571
+ private get debug() {
572
+ return this.options.debug
573
+ }
574
+
575
+ private get ws() {
576
+ return this.options.ws
577
+ }
578
+
579
+ private get entities() {
580
+ return this.options.entities
581
+ }
582
+ }
@@ -0,0 +1,56 @@
1
+ import { CuboCrdtAction, CuboCrdtExposeStrategy } from '../../types'
2
+
3
+ export type CuboCrdtServerDocumentOptions = {
4
+ name: string
5
+ onStore?: (body: object, origin: CuboCrdtServerDocumentOrigin) => void
6
+ // onCreate?: (data: Uint8Array, origin?: CuboCrdtServerDocumentOrigin) => void
7
+ onUpdate?: (data: Uint8Array, origin?: CuboCrdtServerDocumentOrigin) => void
8
+ // onInit?: (document: CuboCrdtServerDocument) => void
9
+
10
+ initialState?: object
11
+
12
+ yjsOptions?: {
13
+ guid?: string
14
+ collectionid?: string
15
+ gc?: boolean
16
+ gcFilter?: () => true
17
+ meta?: any
18
+ }
19
+ }
20
+
21
+ export type CuboCrdtServerDocumentIncomingAction = {
22
+ // действие
23
+ action: CuboCrdtAction
24
+
25
+ // сущность
26
+ entity: string
27
+
28
+ // ид сущности
29
+ entity_id: number
30
+
31
+ // контент апдейта
32
+ data: number[]
33
+
34
+ // ориджин
35
+ origin: CuboCrdtServerDocumentOrigin
36
+ }
37
+
38
+ export type CuboCrdtServerDocumentOrigin = {
39
+ // сохранять ли на бэке в дебаунсе
40
+ store?: boolean
41
+
42
+ // реагировать ли в onUpdate (на бэке)
43
+ react?: boolean
44
+
45
+ // обновлённые ключи
46
+ keys?: string[]
47
+
48
+ // куда раскатывать обновления - всем или всем кроме себя
49
+ expose?: CuboCrdtExposeStrategy
50
+
51
+ // id подписки-исходника
52
+ subscribe_id?: string
53
+
54
+ // id клиента
55
+ client_id?: string
56
+ }
@@ -0,0 +1,25 @@
1
+ import { WsServer, WsServerSocket } from '@cuboapp/ws'
2
+ import { CuboCrdtServerDocumentOrigin } from './document'
3
+ import { CuboCrdtServerSubscribe } from './subscribe'
4
+
5
+ export * from './document'
6
+ export * from './subscribe'
7
+
8
+ export type CuboCrdtServerOptions<M, A, E extends Extract<keyof M, string>> = {
9
+ ws: WsServer
10
+ entities: E[]
11
+ debug?: boolean
12
+ fetchRows?: (client: WsServerSocket, subscribe: CuboCrdtServerSubscribe) => Promise<M[E][]>
13
+ checkRowIsSutable?: (baseState: boolean, ctx: { entity: E; subscribe: CuboCrdtServerSubscribe; row: any }) => boolean
14
+ storeRow?: <K extends E>(
15
+ entity: K,
16
+ entity_id: number,
17
+ row: M[K],
18
+ opts: {
19
+ client: CuboCrdtSocketClient<A>
20
+ origin: CuboCrdtServerDocumentOrigin
21
+ }
22
+ ) => Promise<void> | void
23
+ }
24
+
25
+ export type CuboCrdtSocketClient<A> = WsServerSocket<{ auth?: A }>
@@ -0,0 +1,15 @@
1
+ export type CuboCrdtServerUnsubscribeDto = {
2
+ subscribe_id: string
3
+ }
4
+ export type CuboCrdtServerSubscribeDto = {
5
+ subscribe_id: string
6
+ entity: string
7
+ filters: {
8
+ [K in 'id' | string]: any
9
+ }
10
+ }
11
+
12
+ export type CuboCrdtServerSubscribe = {
13
+ id: string
14
+ client_id: string
15
+ } & Pick<CuboCrdtServerSubscribeDto, 'entity' | 'filters'>
@@ -1,11 +1,5 @@
1
- import { WsServer, WsServerRequest } from '@cuboapp/ws'
1
+ export type CuboCrdtKey<K, M> = K extends Extract<keyof M, string> ? M[K] : any
2
2
 
3
- export type CuboCrdtOptions<A> = {
4
- host?: string
5
- port?: number
6
- debug?: boolean
3
+ export type CuboCrdtAction = 'create' | 'upsert' | 'update' | 'delete'
7
4
 
8
- authHandler?: (ctx: { server: WsServer<{ auth: A }>; request: WsServerRequest<{ auth: A }> }) => Promise<A> | A
9
-
10
- beforeLoadDocument?: (name: string) => Promise<any> | any
11
- }
5
+ export type CuboCrdtExposeStrategy = 'all' | 'other' | 'subscribe' | 'client'