@cuboapp/api-backend 1.0.22 → 1.0.24

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/api-backend",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Backend Api for CuboApp",
5
5
  "main": "src/index.ts",
6
6
  "repository": "git@github.com:cuboapp/api-backend.git",
@@ -8,14 +8,16 @@
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "dependencies": {
11
- "@cuboapp/constants": "^2.0.5",
12
- "@cuboapp/database": "^1.0.2",
13
- "@cuboapp/types": "^2.0.6",
14
- "@cuboapp/utils": "1.0.4",
15
- "@hocuspocus/extension-database": "^3.2.3",
16
- "@hocuspocus/server": "^3.2.3",
11
+ "@cuboapp/constants": "^2.0.6",
12
+ "@cuboapp/crdt": "1.0.2",
13
+ "@cuboapp/database": "^1.0.3",
14
+ "@cuboapp/types": "^2.0.11",
15
+ "@cuboapp/utils": "1.0.9",
16
+ "@cuboapp/ws": "1.0.4",
17
+ "@hocuspocus/extension-database": "^3.4.3",
18
+ "@hocuspocus/server": "^3.4.3",
17
19
  "pg": "^8.16.3",
18
- "yjs": "^13.6.27"
20
+ "yjs": "^13.6.29"
19
21
  },
20
22
  "devDependencies": {
21
23
  "@types/node": "^24.10.1",
@@ -8,6 +8,8 @@ export const CUBO_CRUD_QUERY_SYMBOL_NOT = 'not'
8
8
  export const CUBO_CRUD_DEFAULT_PAGE_LIMIT = 25
9
9
  export const CUBO_CRUD_MAX_PAGE_LIMIT = 500
10
10
 
11
+ export const CUBO_CRUD_ACTION = ['create', 'read', 'update', 'delete'] as const
12
+
11
13
  export const CUBO_CRUD_QUERY_CONDITION = {
12
14
  EQ: 'eq',
13
15
  EX: 'ex',
package/src/crdt/index.ts CHANGED
@@ -123,21 +123,25 @@ export class CuboCrdt<T extends CuboApiEntitiesMap<T>> {
123
123
 
124
124
  body.modified_at = new Date().toISOString()
125
125
 
126
- await this.api.updateOne(
127
- entityAlias as Extract<keyof T, string>,
128
- {
129
- query: { id },
130
- body
131
- },
132
- {
133
- crdt: false,
134
- extra: {
135
- auth: context.auth
126
+ await this.api
127
+ .updateOne(
128
+ entityAlias as Extract<keyof T, string>,
129
+ {
130
+ query: { id },
131
+ body
136
132
  },
137
- excludeHooks: [],
138
- queryOptions: { withDeleted: true, withCrdt: true }
139
- }
140
- )
133
+ {
134
+ crdt: false,
135
+ extra: {
136
+ auth: context.auth
137
+ },
138
+ excludeHooks: [],
139
+ queryOptions: { withDeleted: true, withCrdt: true }
140
+ }
141
+ )
142
+ .catch((e) => {
143
+ console.log('crdt on store error', { body })
144
+ })
141
145
  }
142
146
  })
143
147
  ],
@@ -18,8 +18,8 @@ import { ApiHelpersData } from './data'
18
18
 
19
19
  import { ApiHelpersWithes } from './withes'
20
20
 
21
- export class ApiHelpers<T extends CuboApiEntitiesMap<T>> {
22
- constructor(public api: CuboBackendApi<T>) {}
21
+ export class ApiHelpers<T extends CuboApiEntitiesMap<T>, A> {
22
+ constructor(public api: CuboBackendApi<T, A>) {}
23
23
 
24
24
  public withes = new ApiHelpersWithes<T>(this)
25
25
  public convert = new ApiHelpersConvert<T>(this)
@@ -450,11 +450,11 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>> {
450
450
 
451
451
  let entityFields = entity.fields
452
452
 
453
- if (this.api.crdt?.isCrdtSupported(entity) && !withCrdt) {
454
- const crdtKey = this.api.crdt?.getCrdtField(entity)
453
+ // if (this.api.crdt?.isCrdtSupported(entity) && !withCrdt) {
454
+ // const crdtKey = this.api.crdt?.getCrdtField(entity)
455
455
 
456
- entityFields = entityFields.filter((f) => f.alias !== crdtKey?.alias)
457
- }
456
+ // entityFields = entityFields.filter((f) => f.alias !== crdtKey?.alias)
457
+ // }
458
458
 
459
459
  selects.push(...entityFields.map((f) => `t1.${f.alias}`))
460
460
 
package/src/index.ts CHANGED
@@ -1,39 +1,279 @@
1
+ import { CUBO_CRDT_EVENT, CuboCrdt } from '@cuboapp/crdt'
1
2
  import { QueryTypes } from '@cuboapp/database'
2
3
  import { CuboApiEntitiesMap } from '@cuboapp/types'
4
+ import { createWsServer, WsServer } from '@cuboapp/ws'
3
5
 
4
- import { CuboCrdt } from './crdt'
5
6
  import { ApiHelpers } from './helpers'
6
- import { CuboBackendApiAuth, CuboBackendApiOptions, CuboCrudGetManyResponse, CuboCrudMethodOptions, CuboCrudRequest } from './types'
7
+ import {
8
+ CuboBackendApiAuth,
9
+ CuboBackendApiOptions,
10
+ CuboCrudAction,
11
+ CuboCrudGetManyResponse,
12
+ CuboCrudMethodOptions,
13
+ CuboCrudRequest
14
+ } from './types'
7
15
 
8
- export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
9
- constructor(public options: CuboBackendApiOptions<T>) {
10
- if (options.crdt) {
11
- this.crdt = new CuboCrdt<T>(this)
12
- this.crdt.listen()
13
- }
14
- }
16
+ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>, A extends unknown> {
17
+ public crdt?: CuboCrdt<T, A>
18
+ public ws?: WsServer<{ auth: A; subscribes: Map<string, any[]> }>
19
+
20
+ constructor(public options: CuboBackendApiOptions<T, A>) {}
15
21
 
16
22
  public auth: CuboBackendApiAuth
17
- public crdt?: CuboCrdt<T>
18
- public helpers = new ApiHelpers<T>(this)
23
+ public helpers = new ApiHelpers<T, A>(this)
19
24
 
20
25
  async init() {
21
26
  try {
22
- this.auth = await this.request<{ variables: Record<string, any> }>('/auth/me?with=variables', {
23
- withAuth: true
24
- })
27
+ if (this.options.api !== undefined) {
28
+ this.auth = await this.request<{ variables: Record<string, any> }>('/auth/me?with=variables', {
29
+ withAuth: true
30
+ })
31
+
32
+ if (!this.auth) {
33
+ throw { code: 403, text: 'Авторизация Cubo-Backend не пройдена' }
34
+ }
35
+
36
+ if (this.auth.variables?.db !== undefined) {
37
+ this.options.db = this.options.db || {}
25
38
 
26
- if (!this.auth) {
27
- throw { code: 403, text: 'Авторизация Cubo-Backend не пройдена' }
39
+ this.options.db.options = {
40
+ ...(this.options.db.options || {}),
41
+ ...(this.auth.variables?.db || {})
42
+ }
43
+ }
28
44
  }
29
45
 
30
- if (this.auth.variables?.db !== undefined) {
31
- this.options.db = this.options.db || {}
46
+ if (this.options.crdt !== undefined) {
47
+ type ID = `${Extract<keyof T, string>}:${number | string}`
48
+
49
+ this.crdt = new CuboCrdt<T, A>({
50
+ ...this.options.crdt,
51
+ beforeLoadDocument: async (name: ID) => {
52
+ const [entityAlias, id] = name.split(':') as [Extract<keyof T, string>, string]
53
+
54
+ // console.log('beforeLoadDocument', { entityAlias, id })
55
+
56
+ return this.getOne(entityAlias, { query: { id: +id } })
57
+ }
58
+ })
59
+
60
+ this.ws = createWsServer<{ auth: A; subscribes: Map<string, any[]> }>({
61
+ host: this.options.crdt.host,
62
+ port: this.options.crdt.port,
63
+ debug: this.options.crdt.debug,
64
+ auth:
65
+ this.options.crdt.authHandler !== undefined
66
+ ? {
67
+ default: true,
68
+ handler: async (ctx) => {
69
+ ctx.request.auth = await this.options.crdt.authHandler!(ctx)
70
+ }
71
+ }
72
+ : undefined,
73
+
74
+ onDisconnect: (socket) => {
75
+ this.crdt.documents.forEach((doc) => {
76
+ doc.removeClient(socket)
77
+
78
+ this.crdt.checkDocumentUnload(doc.opts.name as any)
79
+ })
80
+ }
81
+ })
82
+
83
+ await this.ws.start()
84
+ await this.crdt.start()
85
+
86
+ this.ws.registerHandler<{ name: ID }>('crdt:open', async ({ client, message }) => {
87
+ const { name } = message.data || {}
88
+ if (!name) {
89
+ throw 'Имя документа обязательно: "name"'
90
+ }
91
+
92
+ console.log('open document', name)
93
+
94
+ try {
95
+ const document = await this.crdt.getDocument(name)
96
+ document.addClient(client)
97
+
98
+ if (this.options.crdt.debug) {
99
+ console.log('document', name, 'subscribed', document.stat)
100
+ }
101
+
102
+ return Array.from(document.stateAsUpdate)
103
+ } catch (e) {
104
+ console.error('CRDT open document', e)
105
+ throw e
106
+ }
107
+ })
108
+
109
+ this.ws.registerHandler<{ name: ID }>('crdt:close', async ({ client, message }) => {
110
+ const { name } = message.data || {}
111
+ if (!name) {
112
+ throw 'Имя документа обязательно: "name"'
113
+ }
114
+
115
+ const document = await this.crdt.getDocument(name)
116
+ document.removeClient(client)
117
+
118
+ if (this.options.crdt.debug) {
119
+ console.log('document', name, 'unsubscribed', document.stat)
120
+ }
121
+
122
+ await this.crdt.checkDocumentUnload(name)
32
123
 
33
- this.options.db.options = {
34
- ...(this.options.db.options || {}),
35
- ...(this.auth.variables?.db || {})
124
+ return true
125
+ })
126
+
127
+ this.ws.registerHandler<string>('crdt:subscribe', async ({ client, message }) => {
128
+ let { crdt, event, filters } = message.data as any
129
+ filters = filters || {}
130
+
131
+ if (crdt) {
132
+ event = `crdt:update:${crdt.entity}:${crdt.id}`
133
+ filters = { id: crdt.id }
134
+
135
+ // console.log('subscribe to crdt', event, filters)
136
+ }
137
+
138
+ client.subscribes = client.subscribes || new Map()
139
+ const subs = client.subscribes.get(event) || []
140
+ const ex = subs.find((s) => JSON.stringify(s) === JSON.stringify(filters))
141
+ if (!ex) {
142
+ subs.push(filters)
143
+ }
144
+ client.subscribes.set(event, subs)
145
+
146
+ if (this.options.crdt.debug) {
147
+ console.log(
148
+ 'crdt:subscribe',
149
+ client.id,
150
+ JSON.stringify({ event, filters }),
151
+ Array.from(client.subscribes).map((s) => [s[0], JSON.stringify(s[1])])
152
+ )
153
+ }
154
+ })
155
+
156
+ this.ws.registerHandler<string>('crdt:unsubscribe', async ({ client, message }) => {
157
+ let { event, filters } = message.data as any
158
+ filters = filters || {}
159
+
160
+ client.subscribes = client.subscribes || new Map()
161
+ const subs = client.subscribes.get(event) || []
162
+ const exIndex = subs.findIndex((s) => JSON.stringify(s) === JSON.stringify(filters))
163
+ if (exIndex >= 0) {
164
+ subs.splice(exIndex)
165
+ }
166
+ if (!subs.length) {
167
+ client.subscribes.delete(event)
168
+ } else {
169
+ client.subscribes.set(event, subs)
170
+ }
171
+
172
+ if (this.options.crdt.debug) {
173
+ console.log('crdt:unsubscribe', client.id, { event, filters }, Array.from(client.subscribes))
174
+ }
175
+ })
176
+
177
+ const subFilterCheck = (row: any, filters: any) => {
178
+ let sutable = true
179
+
180
+ Object.entries(filters).forEach(([key, value]) => {
181
+ if (!['limit', 'with'].includes(key)) {
182
+ const [formula, str] = `${value}`.split(':')
183
+
184
+ console.log({ formula, str, value: row[key] })
185
+
186
+ if (!str) {
187
+ if (row[key] !== value) {
188
+ sutable = false
189
+ }
190
+ } else {
191
+ switch (formula) {
192
+ case 'in':
193
+ if (
194
+ !str
195
+ .split(',')
196
+ .map(Number)
197
+ .includes(row[key] as number)
198
+ ) {
199
+ sutable = false
200
+ }
201
+ break
202
+ default:
203
+ sutable = false
204
+ break
205
+ }
206
+ }
207
+ }
208
+ })
209
+
210
+ return sutable
36
211
  }
212
+
213
+ const onDocumentEvent = async (name: string, action: CuboCrudAction, data?: Uint8Array) => {
214
+ const [entityAlias, id] = name.split(':') as [Extract<keyof T, string>, string]
215
+
216
+ const entity = await this.helpers.getEntityByAlias(entityAlias)
217
+
218
+ const row = await this.getOne(entityAlias, { query: { id: +id } }, { queryOptions: { withDeleted: true } })
219
+
220
+ const a = this.options?.augmentations?.[entityAlias]
221
+ const filterCheck = a?.filterRowForCrdtEvent ?? subFilterCheck
222
+
223
+ try {
224
+ for (const client of Array.from(this.ws?.clients)) {
225
+ const hasCardSub = client.subscribes?.get(`crdt:${entityAlias}:${id}`)
226
+ const hasEntitySub = client.subscribes?.get(`crdt:${entityAlias}`)
227
+ const hasCrdtSub = client.subscribes?.get(`crdt:update:${entityAlias}:${id}`)
228
+
229
+ const needCardPush = !hasCardSub?.length || !!hasCardSub.find((f) => !!filterCheck(row, f)) || false
230
+ const needEntityPush = !hasEntitySub?.length || !!hasEntitySub.find((f) => !!filterCheck(row, f)) || false
231
+ const needCrdtPush = !hasCrdtSub?.length || !!hasCrdtSub.find((f) => !!filterCheck(row, f)) || false
232
+
233
+ // console.log(id, name, action, {
234
+ // hasCardSub,
235
+ // hasEntitySub,
236
+ // hasCrdtSub,
237
+ // needCardPush,
238
+ // needEntityPush,
239
+ // needCrdtPush,
240
+ // filterCheck
241
+ // })
242
+
243
+ if (needCrdtPush) {
244
+ client.send(JSON.stringify({ method: 'message', event: `crdt:update:${entityAlias}:${id}`, data: Array.from(data) }))
245
+ }
246
+
247
+ if (needCardPush || needEntityPush) {
248
+ if (!a?.can || (await a.can<A>(action, { auth: client.auth, entity, row }))) {
249
+ const dto = {
250
+ event: `crdt:${entityAlias}`,
251
+ action,
252
+ id: id ? +id : null,
253
+ row
254
+ }
255
+
256
+ if (a?.sendCrdtEvent) {
257
+ await a.sendCrdtEvent({ client, auth: client.auth, entity, dto })
258
+ } else {
259
+ client.send(JSON.stringify({ method: 'message', ...dto }))
260
+ }
261
+ } else {
262
+ console.log('sutable pushes cannot', name)
263
+ }
264
+ } else {
265
+ console.log('no sutable pushes', name)
266
+ }
267
+ }
268
+ } catch (e) {
269
+ console.log(e)
270
+ }
271
+ }
272
+
273
+ // document events
274
+ this.crdt.ee.addListener(CUBO_CRDT_EVENT.DOCUMENT_UPDATED, (ctx) => onDocumentEvent(ctx.name, 'update', ctx.data))
275
+ this.crdt.ee.addListener(CUBO_CRDT_EVENT.DOCUMENT_CREATED, (ctx) => onDocumentEvent(ctx.name, 'create', ctx.data))
276
+ this.crdt.ee.addListener(CUBO_CRDT_EVENT.DOCUMENT_DELETED, (ctx) => onDocumentEvent(ctx.name, 'delete', ctx.data))
37
277
  }
38
278
  } catch (e) {
39
279
  console.error('[API-BACKEND] startup error', e)
@@ -149,7 +389,7 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
149
389
  // get all joins
150
390
  const queryDto = await this.helpers.withes.prepare(req, entity, opts)
151
391
  queryDto.withDeleted = opts.queryOptions?.withDeleted
152
- queryDto.withCrdt = opts.queryOptions?.withCrdt
392
+ // queryDto.withCrdt = opts.queryOptions?.withCrdt
153
393
 
154
394
  // create find query
155
395
  const regularQuery = await this.helpers.createFindQuery(req, entity, queryDto)
@@ -269,17 +509,8 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
269
509
  response = await augmentation.afterCreate(response, req, opts)
270
510
  }
271
511
 
272
- if (opts.crdt !== false && this.crdt?.isCrdtSupported(entity)) {
273
- const state = await this.crdt.createState({ entityAlias, id: created_id, entity }, response)
274
-
275
- const fieldKey = entity.fields.find((f) => f.type === 'binary').alias
276
-
277
- const db = await this.helpers.getConnection('read')
278
- await db.update(entity!.alias, 'id=:id', { id: created_id }, { [fieldKey]: state }, opts)
279
-
280
- if (augmentation?.afterCrdtCreate && !opts?.excludeHooks?.includes('afterCrdtCreate')) {
281
- augmentation.afterCrdtCreate(response, req, opts)
282
- }
512
+ if (this.crdt) {
513
+ this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_CREATED, { name: `${entityAlias}:${(response as any).id}` })
283
514
  }
284
515
 
285
516
  return response
@@ -304,6 +535,14 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
304
535
  throw { status: 404, text: 'Entity not found' }
305
536
  }
306
537
 
538
+ try {
539
+ if (!!augmentation?.can) {
540
+ await augmentation.can('update', { entity, row: item, auth: opts?.extra?.auth, req })
541
+ }
542
+ } catch {
543
+ throw { status: 403, text: 'Updating entity forbidden' }
544
+ }
545
+
307
546
  const dto = await this.helpers.data.prepare('update', entity!.fields, req.body, opts?.performer_id || 0)
308
547
  if (!Object.keys(dto)) {
309
548
  throw { status: 400, text: 'No keys to update' }
@@ -321,13 +560,8 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
321
560
  response = await augmentation.afterUpdate(response, req, opts)
322
561
  }
323
562
 
324
- if (opts.crdt !== false && this.crdt?.isCrdtSupported(entity)) {
325
- const context = { auth: opts?.extra?.auth }
326
- await this.crdt.updateDocument({ entityAlias, id: item.id, entity }, response, context)
327
-
328
- if (augmentation?.afterCrdtUpdate && !opts?.excludeHooks?.includes('afterCrdtUpdate')) {
329
- augmentation.afterCrdtUpdate(response, req, opts)
330
- }
563
+ if (this.crdt) {
564
+ await this.crdt.updateDocument(`${entityAlias}:${item.id}`, dto)
331
565
  }
332
566
 
333
567
  return response
@@ -363,10 +597,14 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
363
597
  return augmentation.afterDelete(item, req, opts)
364
598
  }
365
599
 
600
+ if (this.crdt) {
601
+ this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_DELETED, { name: `${entityAlias}:${item.id}` })
602
+ }
603
+
366
604
  return true
367
605
  }
368
606
  }
369
607
 
370
- export * from './crdt'
608
+ // export * from './crdt'
371
609
  export * from './helpers'
372
610
  export * from './types'
@@ -1,14 +1,28 @@
1
- import { CuboApiEntitiesMap } from '@cuboapp/types'
1
+ import { CuboApiEntitiesMap, CuboEntity } from '@cuboapp/types'
2
2
 
3
- import { CuboCrudGetManyResponse, CuboCrudRequest } from './basic'
3
+ import { WsServerSocket } from '@cuboapp/ws'
4
+ import { CuboCrudAction, CuboCrudGetManyResponse, CuboCrudRequest } from './basic'
4
5
  import { CuboCrudMethodOptions, CuboCrudQueryOptions } from './db'
5
- import { onChangePayload, onLoadDocumentPayload, onStoreDocumentPayload } from '@hocuspocus/server'
6
6
 
7
7
  export type CuboCrudAugmentationsStore<T extends CuboApiEntitiesMap<T>> = {
8
8
  [K in Extract<keyof T, string>]?: CuboCrudAugmentationInstance<T[K]>
9
9
  }
10
10
 
11
- export interface CuboCrudAugmentationInstance<T = unknown> {
11
+ export type CuboCrudCrdtEvent<A, T> = {
12
+ client: WsServerSocket
13
+ auth: A
14
+ entity: CuboEntity
15
+ req?: CuboCrudRequest
16
+ dto: {
17
+ // type: string
18
+ event: string
19
+ action: CuboCrudAction
20
+ row: T
21
+ id: number
22
+ }
23
+ }
24
+
25
+ export interface CuboCrudAugmentationInstance<T> {
12
26
  beforeGetMany?: (req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<CuboCrudQueryOptions>
13
27
  afterGetMany?: (
14
28
  result: CuboCrudGetManyResponse<T>,
@@ -30,7 +44,8 @@ export interface CuboCrudAugmentationInstance<T = unknown> {
30
44
  beforeDelete?: (req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<CuboCrudQueryOptions>
31
45
  afterDelete?: (result: T, req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<boolean>
32
46
 
33
- onLoadDocument?: (data: onLoadDocumentPayload) => Promise<void>
34
- onChangeDocument?: (data: onChangePayload) => Promise<void>
35
- onStoreDocument?: (data: onStoreDocumentPayload) => Promise<void>
47
+ filterRowForCrdtEvent?: (row: T, filters: any) => boolean
48
+ sendCrdtEvent?: <A>(context: CuboCrudCrdtEvent<A, T>) => void | Promise<void>
49
+
50
+ can?: <A>(action: CuboCrudAction, context: { auth: A; entity: CuboEntity; row: T; req?: CuboCrudRequest }) => boolean | Promise<boolean>
36
51
  }
@@ -1,6 +1,10 @@
1
1
  import { type Transaction } from '@cuboapp/database'
2
2
  import { CuboEntity, CuboEntityField } from '@cuboapp/types'
3
3
 
4
+ import { CUBO_CRUD_ACTION } from '@/constants'
5
+
6
+ export type CuboCrudAction = (typeof CUBO_CRUD_ACTION)[number]
7
+
4
8
  export type CuboCrudWith = {
5
9
  entity: CuboEntity | { id: undefined; alias: string; fields: CuboEntityField[] }
6
10
  key: string
@@ -1,16 +1,15 @@
1
+ import { CuboCrdtOptions } from '@cuboapp/crdt'
1
2
  import { Database, DatabaseOptions, Options } from '@cuboapp/database'
2
3
  import { CuboApiEntitiesMap, CuboAuthTokenType, CuboEntity } from '@cuboapp/types'
3
4
 
4
- import { CuboCrdtConfiguration } from '../crdt'
5
-
6
5
  import { CuboCrudAugmentationsStore } from './augmentation'
7
6
 
8
7
  export * from './augmentation'
9
8
  export * from './basic'
10
9
  export * from './db'
11
10
 
12
- export type CuboBackendApiOptions<T extends CuboApiEntitiesMap<T>> = {
13
- api: {
11
+ export type CuboBackendApiOptions<T extends CuboApiEntitiesMap<T>, A> = {
12
+ api?: {
14
13
  base_url?: string
15
14
  headers: Partial<Record<`cubo-${CuboAuthTokenType}`, string>> & Record<string, any>
16
15
  }
@@ -22,7 +21,7 @@ export type CuboBackendApiOptions<T extends CuboApiEntitiesMap<T>> = {
22
21
  options?: Options & DatabaseOptions
23
22
  }
24
23
 
25
- crdt?: CuboCrdtConfiguration<T>
24
+ crdt?: Omit<CuboCrdtOptions<A>, 'beforeLoadDocument'>
26
25
 
27
26
  augmentations?: Partial<CuboCrudAugmentationsStore<T>>
28
27
  }