@cuboapp/api-backend 1.0.38 → 3.0.0

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,110 @@
1
+ import type {
2
+ CuboCrdtServer,
3
+ CuboCrdtServerDocumentOrigin,
4
+ CuboCrdtServerSubscribe,
5
+ CuboCrdtSocketClient
6
+ } from '@cuboapp/crdt'
7
+ import { CuboApiEntitiesMap, CuboEntity } from '@cuboapp/types'
8
+ import type { WsServer, WsServerSocket } from '@cuboapp/ws'
9
+
10
+ import type { CuboBackendApi } from '../core'
11
+
12
+ /** Тип фабрики, импортируемый только как тип (рантайм-зависимость опциональна). */
13
+ type CreateCrdtServer = typeof import('@cuboapp/crdt')['createCrdtServer']
14
+
15
+ /**
16
+ * Встроенный адаптер cubo-crdt. Инкапсулирует всю обвязку, которую раньше
17
+ * приходилось писать в каждом приложении руками (old.ts):
18
+ * - fetchRows: подписка тянет строки через api.getMany
19
+ * - storeRow: дебаунс-сохранение состояния дока обратно в БД через api.updateOne
20
+ * - checkRowIsSutable: фильтрация строк под подписку (augmentation.filterRowForCrdtEvent)
21
+ * - push create/update/delete: автоматически из CRUD-методов ядра
22
+ *
23
+ * Включается одной опцией `crdt: { ws }` у CuboBackendApi и работает для всех
24
+ * сущностей с флагом `entity.crdt === true`.
25
+ */
26
+ export class CuboCrdtAdapter<T extends CuboApiEntitiesMap<T>, A> {
27
+ public readonly server: CuboCrdtServer<T, A>
28
+
29
+ constructor(
30
+ private api: CuboBackendApi<T, A>,
31
+ ws: WsServer,
32
+ createCrdtServer: CreateCrdtServer,
33
+ private debug = false
34
+ ) {
35
+ const entities = (this.api.options.entities || []).filter((e) => e.crdt).map((e) => e.alias as Extract<keyof T, string>)
36
+
37
+ this.server = createCrdtServer<T, A>({
38
+ ws,
39
+ entities,
40
+ debug: this.debug,
41
+
42
+ // подписка -> строки из БД (хуки CRUD пропускаем, чтобы не зациклить)
43
+ fetchRows: async (client, subscribe: CuboCrdtServerSubscribe) => {
44
+ const { rows } = await this.api.getMany(subscribe.entity as Extract<keyof T, string>, { query: subscribe.filters }, {
45
+ auth: (client as CuboCrdtSocketClient<A>).auth,
46
+ excludeHooks: ['afterCrdtCreate', 'afterCrdtUpdate']
47
+ })
48
+
49
+ return rows as T[Extract<keyof T, string>][]
50
+ },
51
+
52
+ // сохранение состояния дока обратно в БД (origin прокидываем, чтобы не пушить заново)
53
+ storeRow: async (entity, entity_id, body, { origin, client }) => {
54
+ await this.api.updateOne(entity as Extract<keyof T, string>, { query: { id: entity_id }, body }, {
55
+ crdt: origin,
56
+ performer_id: (client as any)?.auth?.user_id,
57
+ auth: (client as any)?.auth,
58
+ excludeHooks: ['afterCrdtUpdate']
59
+ })
60
+ },
61
+
62
+ // фильтрация строки под конкретную подписку
63
+ checkRowIsSutable: (base, { entity, subscribe, row }) => {
64
+ const augmentation = this.api.options.augmentations?.[entity as Extract<keyof T, string>]
65
+ if (augmentation?.filterRowForCrdtEvent) {
66
+ return augmentation.filterRowForCrdtEvent(row, subscribe.filters || {})
67
+ }
68
+ return base
69
+ }
70
+ })
71
+ }
72
+
73
+ public async init() {
74
+ await this.server.init()
75
+ }
76
+
77
+ public async destroy() {
78
+ await this.server.destroy()
79
+ }
80
+
81
+ // lifecycle клиентов — приложение вызывает из ws onConnect/onDisconnect
82
+ public addClient(client: WsServerSocket<{ auth?: A }>) {
83
+ this.server.addClient(client)
84
+ }
85
+
86
+ public removeClient(client: WsServerSocket) {
87
+ this.server.removeClient(client)
88
+ }
89
+
90
+ /** Поддерживается ли CRDT для сущности. */
91
+ public isSupported(entity: CuboEntity): boolean {
92
+ return !!entity.crdt
93
+ }
94
+
95
+ // пуш изменений документов из CRUD-методов ядра
96
+ public pushCreate(entity: CuboEntity, row: any) {
97
+ if (!entity.crdt) return
98
+ this.server.ensureDocument(entity.alias as Extract<keyof T, string>, row)
99
+ }
100
+
101
+ public pushUpdate(entity: CuboEntity, row: any) {
102
+ if (!entity.crdt) return
103
+ this.server.ensureDocument(entity.alias as Extract<keyof T, string>, row)
104
+ }
105
+
106
+ public pushDelete(entity: CuboEntity, row: any, origin?: CuboCrdtServerDocumentOrigin) {
107
+ if (!entity.crdt) return
108
+ this.server.deleteDocument(entity.alias as Extract<keyof T, string>, row, origin)
109
+ }
110
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Слой диалектов БД. Раньше SQL-генерация была заточена под Postgres
3
+ * (`ilike`, `returning`). Теперь различия спрятаны за интерфейсом CuboDialect,
4
+ * и движок одинаково работает с Postgres и MySQL.
5
+ *
6
+ * Сам @cuboapp/database уже умеет insert/update/delete под оба диалекта;
7
+ * здесь покрываются различия SELECT-билдера (в первую очередь ILIKE и
8
+ * пакетные insert/update).
9
+ */
10
+ export type CuboDialectName = 'postgres' | 'mysql'
11
+
12
+ export interface CuboDialect {
13
+ name: CuboDialectName
14
+
15
+ /** Регистронезависимый LIKE. Postgres — нативный ILIKE; MySQL — LOWER() LIKE LOWER(). */
16
+ ilike(field: string, param: string, isNot: boolean): string
17
+
18
+ /** Поддерживает ли RETURNING (Postgres). У MySQL — insertId / lastval. */
19
+ supportsReturning: boolean
20
+ }
21
+
22
+ export const postgresDialect: CuboDialect = {
23
+ name: 'postgres',
24
+ supportsReturning: true,
25
+ ilike: (field, param, isNot) => `${field} ${isNot ? 'not ilike' : 'ilike'} :${param}`
26
+ }
27
+
28
+ export const mysqlDialect: CuboDialect = {
29
+ name: 'mysql',
30
+ supportsReturning: false,
31
+ ilike: (field, param, isNot) => `lower(${field}) ${isNot ? 'not like' : 'like'} lower(:${param})`
32
+ }
33
+
34
+ export function getDialect(name?: string | null): CuboDialect {
35
+ return name === 'mysql' ? mysqlDialect : postgresDialect
36
+ }
@@ -3,7 +3,7 @@ import { Database, createDatabase } from '@cuboapp/database'
3
3
  import { CuboApiEntitiesMap, CuboEntity, CuboEntityField, CuboEntityFieldExtra, CuboEntityFieldType } from '@cuboapp/types'
4
4
  import { cloneDeep } from '@cuboapp/utils'
5
5
 
6
- import { CuboBackendApi } from '../..'
6
+ import type { CuboBackendApi } from '../core'
7
7
  import {
8
8
  CUBO_CRUD_DEFAULT_FIELDS,
9
9
  CUBO_CRUD_DEFAULT_PAGE_LIMIT,
@@ -12,19 +12,25 @@ import {
12
12
  CUBO_CRUD_QUERY_KEY_REGEX,
13
13
  CUBO_CRUD_QUERY_SYMBOL_NOT
14
14
  } from '../constants'
15
+ import { CuboDialect, getDialect } from '../dialects'
15
16
  import { CuboBackendApiDbConnectionType, CuboCrudFindQuery, CuboCrudRequest } from '../types'
16
17
 
17
18
  import { ApiHelpersConvert } from './convert'
18
19
  import { ApiHelpersData } from './data'
19
20
  import { ApiHelpersWithes } from './withes'
20
21
 
21
- export class ApiHelpers<T extends CuboApiEntitiesMap<T>, A> {
22
+ export class ApiHelpers<T extends CuboApiEntitiesMap<T>, A = unknown> {
22
23
  constructor(public api: CuboBackendApi<T, A>) {}
23
24
 
24
25
  public withes = new ApiHelpersWithes<T>(this)
25
26
  public convert = new ApiHelpersConvert<T>(this)
26
27
  public data = new ApiHelpersData<T>()
27
28
 
29
+ /** Текущий диалект БД (postgres по умолчанию). */
30
+ public get dialect(): CuboDialect {
31
+ return getDialect(this.api.options.db?.options?.dialect as string | undefined)
32
+ }
33
+
28
34
  private cache: Record<string, any> = {}
29
35
  private fetching: Record<string, Promise<any> | null> = {}
30
36
  private connections: Partial<Record<CuboBackendApiDbConnectionType, Database>> = {}
@@ -237,7 +243,8 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>, A> {
237
243
  // console.log(field, condition, value)
238
244
  switch (condition) {
239
245
  case CUBO_CRUD_QUERY_CONDITION.ILIKE:
240
- conditions.push(`${field} ${is_not ? 'not ilike' : 'ilike'} :${replacement}`)
246
+ // диалект-зависимо: postgres -> ILIKE, mysql -> LOWER() LIKE LOWER()
247
+ conditions.push(this.dialect.ilike(field, replacement, is_not))
241
248
  replacements[replacement] = '%' + value + '%'
242
249
  break
243
250
  case CUBO_CRUD_QUERY_CONDITION.LIKE:
@@ -376,7 +383,7 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>, A> {
376
383
  const havings = dto.havings ?? []
377
384
  const sorts = dto.sorts ?? []
378
385
  const replacements = dto.replacements ?? {}
379
- const groupBy = dto.groupBy ?? undefined
386
+ const groupBy = dto.groupBy ?? []
380
387
 
381
388
  const { sort, limit: _limit, page, with: _withes, ...query } = cloneDeep(req.query || {})
382
389
 
@@ -452,7 +459,7 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>, A> {
452
459
 
453
460
  let offsetString = ''
454
461
  let limitString = ''
455
- let groupByString = queryDto?.groupBy ?? ''
462
+ const groupByString = groupBy.length ? 'group by ' + groupBy.join(', ') : ''
456
463
 
457
464
  if (!isCount && perPage > 0) {
458
465
  if (filtersOffset > 0) {
@@ -2,7 +2,7 @@ import { CuboApiEntitiesMap, CuboApiEntityDefaultUsersKeys, CuboEntity, CuboEnti
2
2
  import { cloneDeep } from '@cuboapp/utils'
3
3
 
4
4
  import { CUBO_CRUD_DEFAULT_FIELDS, CUBO_CRUD_QUERY_KEY_REGEX } from '../constants'
5
- import { CuboCrudFindQuery, CuboCrudMethodOptions, CuboCrudRequest, CuboCrudWith } from '../types'
5
+ import { CuboCrudFindQuery, CuboCrudMethodOptions, CuboCrudRequest, CuboCrudWith, normalizeCuboCrudQueryOptions } from '../types'
6
6
 
7
7
  import { ApiHelpers } from '.'
8
8
 
@@ -20,7 +20,7 @@ export class ApiHelpersWithes<T extends CuboApiEntitiesMap<T>> {
20
20
  sorts?: string[]
21
21
  conditions?: string[]
22
22
  havings?: string[]
23
- groupBy?: string
23
+ groupBy?: string[]
24
24
  conditionKey?: string
25
25
  conditionValue?: any
26
26
  sortKey?: string
@@ -141,7 +141,7 @@ export class ApiHelpersWithes<T extends CuboApiEntitiesMap<T>> {
141
141
  withes,
142
142
  joins: opts.joins,
143
143
  conditions: opts.conditions || undefined,
144
- groupBy: opts.groupBy || undefined,
144
+ groupBy: opts.groupBy ?? [],
145
145
  havings: opts.havings || undefined,
146
146
  replacements: opts.replacements || undefined
147
147
  }
@@ -226,19 +226,23 @@ export class ApiHelpersWithes<T extends CuboApiEntitiesMap<T>> {
226
226
  public async prepare(req: CuboCrudRequest, entity: CuboEntity, opts?: CuboCrudMethodOptions): Promise<Partial<CuboCrudFindQuery>> {
227
227
  const { with: __withes, sort: _sort, limit: _limit, ...other } = req.query || {}
228
228
 
229
+ // нормализуем один раз: дальше работаем с гарантированными массивами (deep-copy,
230
+ // поэтому push'и ниже не затрагивают исходные queryOptions)
231
+ const queryOptions = normalizeCuboCrudQueryOptions(opts?.queryOptions)
232
+
229
233
  const _withes = (__withes || '').split(',').filter((i: string) => i !== '')
230
234
 
231
- if (opts?.queryOptions?.withes?.length) {
232
- _withes.push(...(opts?.queryOptions?.withes || []).filter((i: string) => i !== ''))
235
+ if (queryOptions.withes.length) {
236
+ _withes.push(...queryOptions.withes.filter((i: string) => i !== ''))
233
237
  }
234
238
 
235
- const joins: string[] = [...(opts?.queryOptions?.joins || [])]
236
- const selects: string[] = [...(opts?.queryOptions?.selects || [])]
237
- const conditions: string[] = [...(opts?.queryOptions?.conditions || [])]
238
- const havings: string[] = [...(opts?.queryOptions?.havings || [])]
239
- const replacements: Record<string, any> = { ...(opts?.queryOptions?.replacements || {}) }
240
- const groupBy: string = opts?.queryOptions?.groupBy || undefined
241
- const sorts: string[] = opts?.queryOptions?.sorts || []
239
+ const joins = queryOptions.joins
240
+ const selects = queryOptions.selects
241
+ const conditions = queryOptions.conditions
242
+ const havings = queryOptions.havings
243
+ const replacements = queryOptions.replacements
244
+ const groupBy = queryOptions.groupBy
245
+ const sorts = queryOptions.sorts
242
246
 
243
247
  const withes: CuboCrudWith[] = []
244
248
 
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Единый источник правды по именам хуков. Раньше список ключей `excludeHooks`
3
+ * дублировался руками в типе CuboCrudMethodOptions и легко рассинхронизировался.
4
+ */
5
+ export const CUBO_HOOKS = [
6
+ 'beforeGetMany',
7
+ 'afterGetMany',
8
+ 'beforeGetOne',
9
+ 'afterGetOne',
10
+ 'beforeCreate',
11
+ 'afterCreate',
12
+ 'beforeUpdate',
13
+ 'afterUpdate',
14
+ 'beforeDelete',
15
+ 'afterDelete',
16
+ 'afterCrdtCreate',
17
+ 'afterCrdtUpdate',
18
+ 'baseHooks'
19
+ ] as const
20
+
21
+ export type CuboHookName = (typeof CUBO_HOOKS)[number]
@@ -0,0 +1,76 @@
1
+ import { CuboApiEntitiesMap } from '@cuboapp/types'
2
+
3
+ import { CuboAugmentation, CuboAugmentationsStore } from './types'
4
+
5
+ /**
6
+ * Identity-хелпер: фиксирует тип строки сущности (и auth) ОДИН раз, после чего
7
+ * в каждом хуке `ctx` выводится контекстно — аннотировать `CuboHookCtx<...>`
8
+ * больше не нужно.
9
+ *
10
+ * @example
11
+ * export const taskAugmentation = defineAugmentation<Task, Auth>({
12
+ * async beforeGetMany({ req, queryOptions }) { // ← всё типизировано без аннотаций
13
+ * // ctx.queryOptions уже нормализован: массивы/объекты гарантированно есть,
14
+ * // можно сразу мутировать без `|| []` / `|| {}`
15
+ * if (req.query?.company_id) queryOptions.replacements.company_id = Number(req.query.company_id)
16
+ * return queryOptions
17
+ * }
18
+ * })
19
+ */
20
+ export function defineAugmentation<R, A = unknown>(augmentation: CuboAugmentation<R, A>): CuboAugmentation<R, A> {
21
+ return augmentation
22
+ }
23
+
24
+ /** Тип функции-хука аугментации по ключу `K` (без `undefined`). */
25
+ export type CuboAugmentationHook<R, A, K extends keyof CuboAugmentation<R, A>> = NonNullable<CuboAugmentation<R, A>[K]>
26
+
27
+ /**
28
+ * Identity-хелпер для ОДНОГО хука: фиксирует тип строки (R) и auth (A), а тип
29
+ * `callback` выводится из переданного `key` — например, для `'beforeGetMany'`
30
+ * это будет ровно сигнатура `beforeGetMany`. Возвращает типизированную функцию
31
+ * хука: её можно положить в нужный ключ аугментации или переиспользовать.
32
+ *
33
+ * Реализовано перегрузками (overloads): один вызов, R/A задаются явно, нужная
34
+ * сигнатура выбирается по строковому литералу `key`.
35
+ *
36
+ * @example
37
+ * const beforeGetMany = defineAugmentationHook<Task, Auth>('beforeGetMany', ({ req, queryOptions }) => {
38
+ * if (req.query?.company_id) queryOptions.conditions.push('t1.company_id = :company_id')
39
+ * return queryOptions
40
+ * })
41
+ *
42
+ * export const taskAugmentation: CuboAugmentation<Task, Auth> = { beforeGetMany }
43
+ */
44
+ export function defineAugmentationHook<R, A = unknown>(key: 'beforeGetMany', callback: CuboAugmentationHook<R, A, 'beforeGetMany'>): CuboAugmentationHook<R, A, 'beforeGetMany'>
45
+ export function defineAugmentationHook<R, A = unknown>(key: 'beforeGetOne', callback: CuboAugmentationHook<R, A, 'beforeGetOne'>): CuboAugmentationHook<R, A, 'beforeGetOne'>
46
+ export function defineAugmentationHook<R, A = unknown>(key: 'beforeCreate', callback: CuboAugmentationHook<R, A, 'beforeCreate'>): CuboAugmentationHook<R, A, 'beforeCreate'>
47
+ export function defineAugmentationHook<R, A = unknown>(key: 'beforeUpdate', callback: CuboAugmentationHook<R, A, 'beforeUpdate'>): CuboAugmentationHook<R, A, 'beforeUpdate'>
48
+ export function defineAugmentationHook<R, A = unknown>(key: 'beforeDelete', callback: CuboAugmentationHook<R, A, 'beforeDelete'>): CuboAugmentationHook<R, A, 'beforeDelete'>
49
+ export function defineAugmentationHook<R, A = unknown>(key: 'afterGetMany', callback: CuboAugmentationHook<R, A, 'afterGetMany'>): CuboAugmentationHook<R, A, 'afterGetMany'>
50
+ export function defineAugmentationHook<R, A = unknown>(key: 'afterGetOne', callback: CuboAugmentationHook<R, A, 'afterGetOne'>): CuboAugmentationHook<R, A, 'afterGetOne'>
51
+ export function defineAugmentationHook<R, A = unknown>(key: 'afterCreate', callback: CuboAugmentationHook<R, A, 'afterCreate'>): CuboAugmentationHook<R, A, 'afterCreate'>
52
+ export function defineAugmentationHook<R, A = unknown>(key: 'afterUpdate', callback: CuboAugmentationHook<R, A, 'afterUpdate'>): CuboAugmentationHook<R, A, 'afterUpdate'>
53
+ export function defineAugmentationHook<R, A = unknown>(key: 'afterDelete', callback: CuboAugmentationHook<R, A, 'afterDelete'>): CuboAugmentationHook<R, A, 'afterDelete'>
54
+ export function defineAugmentationHook<R, A = unknown>(key: 'afterCrdtCreate', callback: CuboAugmentationHook<R, A, 'afterCrdtCreate'>): CuboAugmentationHook<R, A, 'afterCrdtCreate'>
55
+ export function defineAugmentationHook<R, A = unknown>(key: 'afterCrdtUpdate', callback: CuboAugmentationHook<R, A, 'afterCrdtUpdate'>): CuboAugmentationHook<R, A, 'afterCrdtUpdate'>
56
+ export function defineAugmentationHook<R, A = unknown>(key: 'can', callback: CuboAugmentationHook<R, A, 'can'>): CuboAugmentationHook<R, A, 'can'>
57
+ export function defineAugmentationHook<R, A = unknown>(key: 'filterRowForCrdtEvent', callback: CuboAugmentationHook<R, A, 'filterRowForCrdtEvent'>): CuboAugmentationHook<R, A, 'filterRowForCrdtEvent'>
58
+ export function defineAugmentationHook<R, A = unknown>(key: keyof CuboAugmentation<R, A>, callback: any): any {
59
+ return callback
60
+ }
61
+
62
+ /**
63
+ * То же, но сразу для всей карты аугментаций. Внутри каждой сущности `ctx`
64
+ * хуков типизируется по её строке автоматически.
65
+ *
66
+ * @example
67
+ * export const augmentations = defineAugmentations<Entities, Auth>({
68
+ * task: { async beforeGetMany({ req, queryOptions }) { ... } },
69
+ * user: { async afterGetOne({ row }) { ... } }
70
+ * })
71
+ */
72
+ export function defineAugmentations<T extends CuboApiEntitiesMap<T>, A = unknown>(
73
+ store: Partial<CuboAugmentationsStore<T, A>>
74
+ ): Partial<CuboAugmentationsStore<T, A>> {
75
+ return store
76
+ }
@@ -0,0 +1,3 @@
1
+ export * from './constants'
2
+ export * from './types'
3
+ export * from './define'
@@ -0,0 +1,52 @@
1
+ import { CuboApiEntitiesMap, CuboEntity } from '@cuboapp/types'
2
+
3
+ import { CuboCrudAction, CuboCrudGetManyResponse, CuboCrudRequest } from '../types/basic'
4
+ import { CuboCrudQueryOptions, CuboCrudQueryOptionsInput } from '../types/db'
5
+
6
+ export type MaybePromise<X> = X | Promise<X>
7
+
8
+ /**
9
+ * Контекст хука. `queryOptions` — это per-call РАБОЧАЯ КОПИЯ опций: каждый вызов
10
+ * получает свой свежий объект, так что состояние НЕ «накапливается» и не
11
+ * протекает между вызовами (внутренние getOne после create/update чужие опции
12
+ * не наследуют). Хук может либо мутировать эту копию, либо вернуть свой объект
13
+ * опций — движок возьмёт возвращённое значение (если оно есть), иначе текущую
14
+ * (возможно, изменённую) копию.
15
+ */
16
+ export type CuboHookCtx<R, A = unknown> = {
17
+ entity: CuboEntity
18
+ req: CuboCrudRequest
19
+ auth?: A
20
+ performer_id?: number
21
+ queryOptions: CuboCrudQueryOptions
22
+ }
23
+
24
+ export interface CuboAugmentation<R, A = unknown> {
25
+ // before*: формируют query-опции (мутируют ctx.queryOptions и/или возвращают патч опций)
26
+ beforeGetMany?(ctx: CuboHookCtx<R, A>): MaybePromise<CuboCrudQueryOptionsInput | void>
27
+ beforeGetOne?(ctx: CuboHookCtx<R, A>): MaybePromise<CuboCrudQueryOptionsInput | void>
28
+ beforeCreate?(ctx: CuboHookCtx<R, A>): MaybePromise<CuboCrudQueryOptionsInput | void>
29
+ beforeUpdate?(ctx: CuboHookCtx<R, A> & { row: R }): MaybePromise<CuboCrudQueryOptionsInput | void>
30
+ beforeDelete?(ctx: CuboHookCtx<R, A> & { row: R }): MaybePromise<CuboCrudQueryOptionsInput | void>
31
+
32
+ // after*: трансформируют результат
33
+ afterGetMany?(ctx: CuboHookCtx<R, A> & { result: CuboCrudGetManyResponse<R> }): MaybePromise<CuboCrudGetManyResponse<R>>
34
+ afterGetOne?(ctx: CuboHookCtx<R, A> & { row: R }): MaybePromise<R | undefined>
35
+ afterCreate?(ctx: CuboHookCtx<R, A> & { row: R }): MaybePromise<R>
36
+ afterUpdate?(ctx: CuboHookCtx<R, A> & { row: R; prev: R }): MaybePromise<R>
37
+ afterDelete?(ctx: CuboHookCtx<R, A> & { row: R }): MaybePromise<boolean>
38
+
39
+ // crdt-хуки (вызываются адаптером cubo-crdt)
40
+ afterCrdtCreate?(ctx: CuboHookCtx<R, A> & { row: R }): MaybePromise<void>
41
+ afterCrdtUpdate?(ctx: CuboHookCtx<R, A> & { row: R }): MaybePromise<void>
42
+
43
+ // авторизация конкретного действия над строкой
44
+ can?(action: CuboCrudAction, ctx: CuboHookCtx<R, A> & { row?: R }): MaybePromise<boolean>
45
+
46
+ // фильтр строки под CRDT-подписку
47
+ filterRowForCrdtEvent?(row: R, filters: Record<string, any>): boolean
48
+ }
49
+
50
+ export type CuboAugmentationsStore<T extends CuboApiEntitiesMap<T>, A = unknown> = {
51
+ [K in Extract<keyof T, string>]?: CuboAugmentation<T[K], A>
52
+ }