@cuboapp/crdt 1.0.6 → 1.0.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuboapp/crdt",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "CRDT for CUBO",
5
5
  "main": "src/index.ts",
6
6
  "repository": "git@github.com:cuboapp/crdt.git",
@@ -4,7 +4,7 @@ import { applyUpdate, Doc } from 'yjs'
4
4
 
5
5
  import { CUBO_CRDT_EVENT } from '../constants'
6
6
  import { CuboCrdtServerDocumentIncomingAction } from '../server'
7
- import { CuboCrdtAction, CuboCrdtKey } from '../types'
7
+ import { CuboCrdtKey } from '../types'
8
8
 
9
9
  import {
10
10
  CuboCrdtClientDocOrigin,
@@ -67,6 +67,12 @@ export class CuboCrdtClient<M> {
67
67
  return computed(() => (this.store[entity]?.state.rows || []) as M[K][])
68
68
  }
69
69
 
70
+ public useComputedRow<K extends Extract<keyof M, string>>(entity: K, entity_id: number) {
71
+ return computed(() => {
72
+ return ((this.store[entity]?.state.rows || []) as M[K][]).find((row: any) => row.id === entity_id)
73
+ })
74
+ }
75
+
70
76
  public useList<K extends Extract<keyof M, string>, T = CuboCrdtKey<K, M>>(
71
77
  entity: K | string,
72
78
  opts?: CuboCrdtClientUseOptions
@@ -105,14 +111,14 @@ export class CuboCrdtClient<M> {
105
111
  }
106
112
 
107
113
  // подписываемся на фронте
108
- this.listeners.get(entity)?.set(subscribe_id, (ctx) =>
114
+ this.listeners.get(entity)?.set(subscribe_id, (ctx) => {
109
115
  this.onIncomingUpdate(ctx, {
110
116
  subscribe_id,
111
117
  storeKey,
112
118
  ...(pick(opts || {}, ['onBeforeCreate', 'onBeforeUpdate', 'onBeforeDelete', 'onAfterCreate', 'onAfterUpdate', 'onAfterDelete']) ||
113
119
  {})
114
120
  })
115
- )
121
+ })
116
122
 
117
123
  // подписываемся на бэке
118
124
  this.ws.request({ method: CUBO_CRDT_EVENT.SUBSCRIBE, data: { subscribe_id, entity, filters } })
@@ -136,6 +142,12 @@ export class CuboCrdtClient<M> {
136
142
  }
137
143
  }
138
144
 
145
+ // апгрейд
146
+ const upgrade = (filters?: Record<string, any>) => {
147
+ // подписываемся на бэке
148
+ this.upgrade(subscribe_id, filters)
149
+ }
150
+
139
151
  // авто-подписка
140
152
  if (opts?.autoSubscribe !== false) {
141
153
  subscribe()
@@ -145,6 +157,7 @@ export class CuboCrdtClient<M> {
145
157
  subscribe_id,
146
158
  storeKey,
147
159
  subscribe,
160
+ upgrade,
148
161
  unsubscribe,
149
162
  docs: computed(() => this.store[storeKey]?.docs),
150
163
  subscribed: () => computed(() => this.store[storeKey]?.state.subscribed || false),
@@ -160,7 +173,7 @@ export class CuboCrdtClient<M> {
160
173
  ): CuboCrdtClientRow<T> {
161
174
  // console.log('store key', opts?.storeKey ?? `${entity}:${id}`)
162
175
 
163
- const { subscribe_id, storeKey, docs, subscribe, unsubscribe, subscribed } = this.useList(entity, {
176
+ const { subscribe_id, storeKey, docs, subscribe, upgrade, unsubscribe, subscribed } = this.useList(entity, {
164
177
  ...opts,
165
178
  filters: opts?.filters ?? { id },
166
179
  storeKey: opts?.storeKey ?? `${entity}:${id}`
@@ -171,6 +184,7 @@ export class CuboCrdtClient<M> {
171
184
  storeKey,
172
185
  doc: computed(() => docs.value?.get(id)),
173
186
  subscribe,
187
+ upgrade,
174
188
  unsubscribe,
175
189
  subscribed,
176
190
  row: () => {
@@ -231,7 +245,21 @@ export class CuboCrdtClient<M> {
231
245
  }
232
246
  }
233
247
 
234
- private async onDocumentCreate(update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
248
+ public upgrade(subscribe_id: string, filters?: Record<string, any>) {
249
+ this.ws.request({ method: CUBO_CRDT_EVENT.UPGRADE, data: { subscribe_id, filters } })
250
+ }
251
+
252
+ private async onDocumentCreate(ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
253
+ let doc = this.store[opts.storeKey]?.docs.get(ctx.entity_id)
254
+ if (doc) {
255
+ console.warn('[CRDT] onDocumentCreate: doc already exists', { ctx, opts })
256
+ return
257
+ } else {
258
+ doc = new Doc()
259
+ }
260
+
261
+ const update = new Uint8Array(ctx.data as any)
262
+
235
263
  if (opts?.onBeforeCreate) {
236
264
  const result = await opts.onBeforeCreate(update, ctx, opts)
237
265
  if (!result) {
@@ -239,7 +267,6 @@ export class CuboCrdtClient<M> {
239
267
  }
240
268
  }
241
269
 
242
- const doc = new Doc()
243
270
  applyUpdate(doc, update, { react: false })
244
271
 
245
272
  // подписываемся на обновления документа
@@ -285,7 +312,15 @@ export class CuboCrdtClient<M> {
285
312
  }
286
313
  }
287
314
 
288
- private async onDocumentUpdate(doc: Doc, update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
315
+ private async onDocumentUpdate(ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
316
+ const doc = this.store[opts.storeKey]?.docs.get(ctx.entity_id)
317
+ if (!doc) {
318
+ console.warn('[CRDT] onDocumentUpdate: doc not exists', { ctx, opts })
319
+ return
320
+ }
321
+
322
+ const update = new Uint8Array(ctx.data as any)
323
+
289
324
  if (opts?.onBeforeUpdate) {
290
325
  const result = await opts.onBeforeUpdate(doc, update, ctx, opts)
291
326
  if (!result) {
@@ -314,7 +349,13 @@ export class CuboCrdtClient<M> {
314
349
  }
315
350
  }
316
351
 
317
- private async onDocumentDelete(doc: Doc, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
352
+ private async onDocumentDelete(ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) {
353
+ const doc = this.store[opts.storeKey]?.docs.get(ctx.entity_id)
354
+ if (!doc) {
355
+ console.warn('[CRDT] onDocumentDelete: doc not exists', { ctx, opts })
356
+ return
357
+ }
358
+
318
359
  if (opts?.onBeforeDelete) {
319
360
  const result = await opts.onBeforeDelete(doc, ctx, opts)
320
361
  if (!result) {
@@ -331,7 +372,9 @@ export class CuboCrdtClient<M> {
331
372
  if (index !== undefined && index >= 0) {
332
373
  this.store[opts.storeKey]?.state.rows.splice(index, 1)
333
374
  } else {
334
- console.log('row not found', index, ctx, opts)
375
+ if (this.debug) {
376
+ console.log('[CRDT] row not found', index, ctx, opts)
377
+ }
335
378
  }
336
379
 
337
380
  if (opts?.onAfterDelete) {
@@ -348,28 +391,16 @@ export class CuboCrdtClient<M> {
348
391
  console.warn('[CRDT] on incoming update, store key not found: "' + opts.storeKey + '"')
349
392
  }
350
393
 
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) {
394
+ switch (ctx.action) {
357
395
  case 'create':
358
- {
359
- const update = new Uint8Array(ctx.data as any)
360
- this.onDocumentCreate(update, ctx, opts)
361
- }
396
+ this.onDocumentCreate(ctx, opts)
362
397
  break
363
398
  case 'update':
364
- if (doc) {
365
- const update = new Uint8Array(ctx.data as any)
366
- this.onDocumentUpdate(doc, update, ctx, opts)
367
- }
399
+ case 'upsert':
400
+ this.onDocumentUpdate(ctx, opts)
368
401
  break
369
402
  case 'delete':
370
- if (doc) {
371
- this.onDocumentDelete(doc, ctx, opts)
372
- }
403
+ this.onDocumentDelete(ctx, opts)
373
404
  break
374
405
  }
375
406
  }
@@ -1,6 +1,6 @@
1
- import { WsClient } from '@cuboapp/ws'
1
+ import { type WsClient } from '@cuboapp/ws'
2
2
  import { ComputedRef } from 'vue'
3
- import { Doc } from 'yjs'
3
+ import { type Doc } from 'yjs'
4
4
 
5
5
  import { CuboCrdtAction } from '../../types'
6
6
 
@@ -34,6 +34,7 @@ export type CuboCrdtClientList<T> = {
34
34
  storeKey: string
35
35
  docs: ComputedRef<Map<number, Doc> | undefined>
36
36
  subscribe: (filters?: any) => void
37
+ upgrade: (filters?: any) => void
37
38
  unsubscribe: (clear?: boolean) => void
38
39
  subscribed: () => ComputedRef<boolean>
39
40
  rows: () => ComputedRef<T[]>
@@ -45,6 +46,7 @@ export type CuboCrdtClientRow<T> = {
45
46
  storeKey: string
46
47
  doc: ComputedRef<Doc | undefined>
47
48
  subscribe: () => void
49
+ upgrade: (filters?: any) => void
48
50
  unsubscribe: (clear?: boolean) => void
49
51
  subscribed: () => ComputedRef<boolean>
50
52
  row: () => ComputedRef<T>
@@ -1,5 +1,6 @@
1
1
  export const CUBO_CRDT_EVENT = {
2
2
  EVENT: 'crdt:event',
3
3
  SUBSCRIBE: 'crdt:subscribe',
4
+ UPGRADE: 'crdt:upgrade',
4
5
  UNSUBSCRIBE: 'crdt:unsubscribe'
5
6
  }
@@ -1,14 +1,12 @@
1
1
  import { debounce, pick } from '@cuboapp/utils'
2
2
  import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs'
3
3
 
4
- import { CuboCrdtExposeStrategy } from '../../types'
5
-
6
4
  import { CuboCrdtServerDocumentOptions, CuboCrdtServerDocumentOrigin } from '../types'
7
5
 
8
6
  export class CuboCrdtServerDocument {
9
7
  private ydoc: Doc
10
8
 
11
- private firstUpdate = true
9
+ // private firstUpdate = false
12
10
 
13
11
  constructor(public opts: CuboCrdtServerDocumentOptions) {
14
12
  this.ydoc = new Doc({
@@ -16,14 +14,20 @@ export class CuboCrdtServerDocument {
16
14
  autoLoad: false
17
15
  })
18
16
 
19
- this.ydoc.on('update', (data, origin: CuboCrdtServerDocumentOrigin) => {
20
- if (this.firstUpdate) {
21
- this.firstUpdate = true
22
- this.opts?.onCreate?.(data, origin)
23
- } else {
24
- this.opts?.onUpdate?.(data, origin)
25
- }
17
+ // записываем исходное состояние
18
+ this.write(opts.initialState ?? {})
26
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 по дебаунсу
27
31
  if (origin?.store) {
28
32
  const row = this.getJson()
29
33
  const body = pick(row, origin.keys ?? Object.keys(row))
@@ -73,17 +77,6 @@ export class CuboCrdtServerDocument {
73
77
  this.ydoc.destroy()
74
78
  }
75
79
 
76
- private _inited = false
77
- public init(state?: object, expose: CuboCrdtExposeStrategy = 'all') {
78
- if (this._inited) {
79
- return
80
- }
81
-
82
- this._inited = true
83
-
84
- this.write(state ?? {}, { expose })
85
- }
86
-
87
80
  public store(body: object, origin: CuboCrdtServerDocumentOrigin) {
88
81
  this.opts?.onStore?.(body, origin)
89
82
  }