@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.
@@ -1,9 +1,18 @@
1
1
  import { CuboApiEntitiesMap } from '@cuboapp/types'
2
2
 
3
- import { CuboCrdtClient, CuboCrdtClientOptions, CuboCrdtServer, CuboCrdtServerOptions } from '..'
3
+ import {
4
+ CuboCrdtClient,
5
+ CuboCrdtClientOptions,
6
+ CuboCrdtServer,
7
+ CuboCrdtServerDocumentOrigin,
8
+ CuboCrdtServerOptions,
9
+ CuboCrdtServerSubscribe
10
+ } from '..'
4
11
 
5
- export function createCrdtServer<M extends CuboApiEntitiesMap<M>, A = {}>(opts: CuboCrdtServerOptions<M, A>) {
6
- return new CuboCrdtServer<M, A>(opts)
12
+ export function createCrdtServer<M extends CuboApiEntitiesMap<M>, A = {}, E extends Extract<keyof M, string> = Extract<keyof M, string>>(
13
+ opts: CuboCrdtServerOptions<M, A, E>
14
+ ) {
15
+ return new CuboCrdtServer<M, A, E>(opts)
7
16
  }
8
17
 
9
18
  export function createCrdtClient<M extends CuboApiEntitiesMap<M>>(opts: CuboCrdtClientOptions<M>) {
@@ -30,35 +39,43 @@ export function cleanObject<F>(object: F): any {
30
39
  return object
31
40
  }
32
41
 
33
- export function checkRowIsSutable(row: object, filters?: object) {
42
+ export function checkRowIsSutable(
43
+ row: any,
44
+ filters?: object,
45
+ customKeys?: Record<string, (key: string, row: any, filters?: any) => boolean>
46
+ ) {
34
47
  let sutable = true
35
48
 
36
49
  if (Object.keys(filters || {}).length) {
37
50
  Object.entries(filters || {}).forEach(([key, value]) => {
38
51
  if (!['limit', 'with'].includes(key)) {
39
- const [formula, str] = `${value}`.split(':')
52
+ if (customKeys?.[key]) {
53
+ sutable = customKeys[key](key, row, filters)
54
+ } else {
55
+ const [formula, str] = `${value}`.split(':')
40
56
 
41
- // console.log({ formula, str, value: row[key] })
57
+ // console.log({ formula, str, value: row[key] })
42
58
 
43
- if (!str) {
44
- if (row[key as keyof typeof row] !== value) {
45
- sutable = false
46
- }
47
- } else {
48
- switch (formula) {
49
- case 'in':
50
- if (
51
- !str
52
- .split(',')
53
- .map(Number)
54
- .includes(row[key as keyof typeof row] as any)
55
- ) {
56
- sutable = false
57
- }
58
- break
59
- default:
59
+ if (!str) {
60
+ if (row[key as keyof typeof row] !== value) {
60
61
  sutable = false
61
- break
62
+ }
63
+ } else {
64
+ switch (formula) {
65
+ case 'in':
66
+ if (
67
+ !str
68
+ .split(',')
69
+ .map(Number)
70
+ .includes(row[key as keyof typeof row] as any)
71
+ ) {
72
+ sutable = false
73
+ }
74
+ break
75
+ default:
76
+ sutable = false
77
+ break
78
+ }
62
79
  }
63
80
  }
64
81
  }
@@ -67,3 +84,16 @@ export function checkRowIsSutable(row: object, filters?: object) {
67
84
 
68
85
  return sutable
69
86
  }
87
+
88
+ export function checkSubscribeIsSutable(origin: CuboCrdtServerDocumentOrigin, subscribe?: CuboCrdtServerSubscribe) {
89
+ if (!subscribe) {
90
+ return false
91
+ }
92
+
93
+ const noStrategyResolved = !origin?.expose || origin?.expose === 'all'
94
+ const strategyOtherResolved = (origin?.expose === 'other' && origin.subscribe_id !== subscribe?.id) || false
95
+ const strategyClientResolved = origin?.expose === 'client' && subscribe?.client_id === origin.client_id
96
+ const strategySubscribeResolved = origin?.expose === 'subscribe' && subscribe?.id === origin.subscribe_id
97
+
98
+ return noStrategyResolved || strategyOtherResolved || strategyClientResolved || strategySubscribeResolved
99
+ }
package/src/client/old.ts DELETED
@@ -1,249 +0,0 @@
1
- import { uuid } from '@cuboapp/utils'
2
- import { WsClient } from '@cuboapp/ws'
3
- import { computed, ComputedRef, Reactive, reactive } from 'vue'
4
- import { applyUpdate, Doc } from 'yjs'
5
-
6
- export type CuboCrdtClientStoreElement<M, K> = {
7
- rows: Reactive<Map<number, K extends keyof M ? M[K] : unknown>>
8
- docs: Map<number, Doc>
9
- flags: Reactive<{
10
- inited: boolean
11
- }>
12
- }
13
-
14
- export type CuboCrdtClientSubscribeOptions<S, F> = {
15
- entity_id?: number
16
- storeKey?: S
17
- filters?: F
18
- autoInit?: boolean
19
- onUpdate?: () => void
20
- onInit?: () => void
21
- debug?: boolean
22
- }
23
-
24
- export type CuboCrdtClientSubscribe<M = {}, K = {}, S = {}, F = {}> = {
25
- id: string
26
- storeKey: S
27
- store: CuboCrdtClientStoreElement<M, K>
28
- init: (filters?: F) => void
29
- destroy: (opts?: { clear?: boolean }) => void
30
- }
31
-
32
- export type CuboCrdtClientOptions = {
33
- ws: WsClient
34
- debug?: boolean
35
- }
36
-
37
- export class CuboCrdtClient<M> {
38
- constructor(private opts: CuboCrdtClientOptions) {}
39
-
40
- public store: Partial<{ [K in Extract<keyof M, string>]: CuboCrdtClientStoreElement<M, K> }> = {}
41
-
42
- private get ws() {
43
- return this.opts.ws
44
- }
45
-
46
- private getFilters<F>(filters: F) {
47
- if (Array.isArray(filters)) {
48
- return filters.map(this.getFilters).filter(Boolean)
49
- } else if (typeof filters === 'object' && filters !== null) {
50
- const newObj: any = {}
51
-
52
- for (const key in filters) {
53
- const value = this.getFilters(filters[key])
54
-
55
- if (value !== undefined && value !== null && value !== '' && !(typeof value === 'object' && Object.keys(value).length === 0)) {
56
- newObj[key] = value
57
- }
58
- }
59
-
60
- return newObj
61
- }
62
-
63
- return filters
64
- }
65
-
66
- subscribe<K extends Extract<keyof M, string>, S extends string, F extends Record<string, any>>(
67
- entity: K,
68
- opts?: CuboCrdtClientSubscribeOptions<S, F>
69
- ): CuboCrdtClientSubscribe<M, K, S, F> {
70
- const subscribe_id = uuid()
71
- const storeKey = opts?.storeKey ?? (entity as any)
72
-
73
- if (opts?.debug) {
74
- console.trace('subscribe tasks', subscribe_id, opts)
75
- }
76
-
77
- if (this.store[storeKey] === undefined) {
78
- this.store[storeKey] = {
79
- flags: reactive({
80
- inited: false
81
- }),
82
- rows: reactive(new Map()),
83
- docs: new Map()
84
- }
85
- }
86
-
87
- const onUpdateDocument = (id: number, data: Uint8Array, fireOnUpdate?: boolean) => {
88
- let item: any
89
-
90
- // обновляем документ
91
-
92
- let exDoc = this.store[storeKey].docs.get(id)
93
- if (!exDoc) {
94
- const doc = new Doc()
95
- applyUpdate(doc, data)
96
-
97
- doc.on('update', (update, origin) => {
98
- if (!origin?.no_push) {
99
- this.ws.request({
100
- method: 'crdt:update',
101
- data: {
102
- subscribe_id,
103
- name: `${entity}:${id}`,
104
- update: Array.from(update),
105
- origin
106
- }
107
- })
108
- }
109
- })
110
-
111
- this.store[storeKey].docs.set(id, doc)
112
- exDoc = doc
113
- } else {
114
- applyUpdate(exDoc, data, { no_push: true })
115
- }
116
-
117
- item = exDoc.getMap().toJSON()
118
-
119
- // обновляем строку
120
- this.store[storeKey].rows.set(id, item as any)
121
-
122
- if (opts?.debug) {
123
- console.log('fire onUpdate', { fireOnUpdate })
124
- }
125
-
126
- if (fireOnUpdate !== false) {
127
- opts?.onUpdate?.()
128
- }
129
- }
130
-
131
- const init = (filters?: F) => {
132
- filters = filters !== undefined ? filters : opts?.filters
133
-
134
- if (this.store[storeKey].flags.inited) {
135
- return
136
- }
137
-
138
- // подписываемся на обновления
139
- this.ws.registerHandler('crdt:action:' + subscribe_id, (ctx) => {
140
- const { action, entity_id, data } = ctx.message.data as any
141
-
142
- switch (action) {
143
- case 'update':
144
- onUpdateDocument(entity_id, new Uint8Array(data), true)
145
- break
146
- }
147
- })
148
-
149
- this.ws
150
- .request(
151
- {
152
- method: 'crdt:subscribe',
153
- data: this.getFilters({
154
- id: subscribe_id,
155
- entity,
156
- entity_id: opts?.entity_id,
157
- filters
158
- })
159
- },
160
- true
161
- )
162
- .then((data: any) => {
163
- if (opts?.entity_id) {
164
- onUpdateDocument(data.id, new Uint8Array(data.row), false)
165
- } else {
166
- data.map(({ id, row }: { id: number; row: number[] }) => onUpdateDocument(id, new Uint8Array(row), false))
167
- }
168
-
169
- this.store[storeKey].flags.inited = true
170
- opts?.onInit?.()
171
- })
172
- }
173
-
174
- const destroy = ({ clear }: { clear?: boolean } = {}) => {
175
- if (!this.store[storeKey].flags.inited) {
176
- return
177
- }
178
-
179
- this.ws.request(
180
- {
181
- method: 'crdt:unsubscribe',
182
- data: this.getFilters({
183
- id: subscribe_id,
184
- entity,
185
- entity_id: opts?.entity_id,
186
- filters: opts?.filters
187
- })
188
- },
189
- true
190
- )
191
-
192
- this.ws.deleteHandler('crdt:update:' + subscribe_id)
193
-
194
- if (clear !== false) {
195
- this.store[storeKey] = undefined
196
- }
197
- }
198
-
199
- if (opts?.autoInit !== false) {
200
- init()
201
- }
202
-
203
- return {
204
- id: subscribe_id,
205
- storeKey,
206
- store: this.store[storeKey] as any,
207
- init,
208
- destroy
209
- }
210
- }
211
-
212
- useList<K extends string, T = K extends keyof M ? M[K] : unknown>(key: K): ComputedRef<T[]> {
213
- return computed(() => {
214
- return Array.from(this.store[key as any].rows || []).map((m) => m[1] as T)
215
- })
216
- }
217
-
218
- change<K extends string = Extract<keyof M, string>, T = K extends keyof M ? M[K] : unknown>(
219
- entity: K,
220
- entity_id: number,
221
- dto: Partial<T> & Record<string, any>,
222
- opts?: { strategy?: 'other' | 'all' }
223
- ) {
224
- const row = this.store[entity as any].rows.get(entity_id)
225
- const doc = this.store[entity as any].docs.get(entity_id)
226
-
227
- if (doc) {
228
- const map = doc.getMap()
229
-
230
- const toUpdate = Object.fromEntries(Object.entries(dto).filter(([key, value]) => row[key] !== value))
231
-
232
- if (Object.keys(toUpdate).length) {
233
- doc.transact(
234
- () => {
235
- Object.entries(toUpdate).forEach(([key, value]) => {
236
- map.set(key, value)
237
- row[key] = value
238
- })
239
- },
240
- { with_save: true, strategy: opts?.strategy ?? 'other', keys: Object.keys(toUpdate) }
241
- )
242
- }
243
- }
244
- }
245
-
246
- useRow<K extends Extract<keyof M, string>, T = K extends keyof M ? M[K] : unknown>(entity: K, entity_id: number): ComputedRef<T> {
247
- return computed(() => this.store[entity].rows.get(entity_id)) as ComputedRef<T>
248
- }
249
- }
@@ -1,107 +0,0 @@
1
- import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs'
2
-
3
- export class CuboCrdtDocument {
4
- private ydoc: Doc
5
- private subs = new Map<string, (data: Uint8Array<ArrayBufferLike>, origin?: any) => void>()
6
-
7
- constructor(
8
- public opts: {
9
- name: string
10
- initialState?: any
11
- autoRemove?: boolean
12
- onDelete?: () => void
13
- yjsOptions?: {
14
- guid?: string
15
- collectionid?: string
16
- gc?: boolean
17
- gcFilter?: () => true
18
- meta?: any
19
- }
20
- }
21
- ) {}
22
-
23
- get stateAsUpdate() {
24
- return encodeStateAsUpdate(this.ydoc)
25
- }
26
-
27
- async init() {
28
- this.ydoc = new Doc({
29
- ...(this.opts.yjsOptions || {}),
30
- autoLoad: false
31
- })
32
-
33
- const state = this.opts?.initialState ?? {}
34
-
35
- return new Promise<Uint8Array<ArrayBufferLike>>((resolve) => {
36
- this.ydoc.once('update', () => {
37
- this.ydoc.on('update', (data, origin) => {
38
- this.subs.forEach((sub) => sub(data, origin))
39
- })
40
-
41
- resolve(this.stateAsUpdate)
42
- })
43
-
44
- const map = this.getMap()
45
-
46
- this.ydoc.transact(() => {
47
- if (Object.keys(state).length) {
48
- Object.entries(state).forEach(([key, value]) => {
49
- map.set(key, value)
50
- })
51
- }
52
-
53
- this.getState().setAttribute('inited', 'Y')
54
- })
55
- })
56
- }
57
-
58
- get inited() {
59
- return this.getState().getAttribute('inited') === 'Y'
60
- }
61
-
62
- get id() {
63
- return this.ydoc.guid
64
- }
65
-
66
- applyUpdate(update: Uint8Array, origin?: any) {
67
- return applyUpdate(this.ydoc, update, origin)
68
- }
69
-
70
- subscribe(id: string, cb: (data: Uint8Array<ArrayBufferLike>, origin?: any) => void) {
71
- this.subs.set(id, cb)
72
- }
73
-
74
- unsubscribe(id: string) {
75
- this.subs.delete(id)
76
-
77
- if (!this.subs.size && this.opts?.autoRemove !== false) {
78
- this.opts?.onDelete?.()
79
- }
80
- }
81
-
82
- getState() {
83
- return this.ydoc.getText('state')
84
- }
85
-
86
- getMap() {
87
- return this.ydoc.getMap()
88
- }
89
-
90
- getXmlFragment() {
91
- return this.ydoc.getXmlFragment()
92
- }
93
-
94
- async writeMap(dto: object, origin?: any) {
95
- return new Promise<void>(async (resolve) => {
96
- const map = this.getMap()
97
-
98
- this.ydoc.transact(() => {
99
- Object.entries(dto).forEach(([key, value]) => {
100
- map.set(key, value)
101
- })
102
-
103
- resolve()
104
- }, origin)
105
- })
106
- }
107
- }
@@ -1,49 +0,0 @@
1
- import { CuboCrdtDocument } from './document'
2
-
3
- export * from './document'
4
-
5
- export class CuboCrdtServer<T> {
6
- constructor(private options: { debug?: boolean }) {}
7
-
8
- public documents = new Map<string, CuboCrdtDocument>()
9
- private loading = new Map<string, Promise<CuboCrdtDocument>>()
10
-
11
- public async getDocument(name: string, opts?: { autoCreate?: boolean; initialState?: any }) {
12
- // существующий документ
13
- if (!this.documents.has(name)) {
14
- if (!this.loading.has(name) && opts?.autoCreate !== false) {
15
- if (this.options.debug) {
16
- console.log('CRDT create document load start', name)
17
- }
18
-
19
- this.loading.set(
20
- name,
21
- new Promise(async (resolve) => {
22
- const document = new CuboCrdtDocument({
23
- name,
24
- initialState: opts?.initialState,
25
- autoRemove: true,
26
- onDelete: () => {
27
- this.documents.delete(name)
28
- }
29
- })
30
-
31
- await document.init()
32
-
33
- resolve(document)
34
- })
35
- )
36
- }
37
-
38
- const document = await this.loading.get(name)
39
-
40
- this.loading.delete(name)
41
-
42
- if (document) {
43
- this.documents.set(name, document)
44
- }
45
- }
46
-
47
- return this.documents.get(name)
48
- }
49
- }