@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 +10 -8
- package/src/constants/index.ts +2 -0
- package/src/crdt/index.ts +18 -14
- package/src/helpers/index.ts +6 -6
- package/src/index.ts +279 -41
- package/src/types/augmentation.ts +22 -7
- package/src/types/basic.ts +4 -0
- package/src/types/index.ts +4 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cuboapp/api-backend",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
12
|
-
"@cuboapp/
|
|
13
|
-
"@cuboapp/
|
|
14
|
-
"@cuboapp/
|
|
15
|
-
"@
|
|
16
|
-
"@
|
|
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.
|
|
20
|
+
"yjs": "^13.6.29"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
23
|
"@types/node": "^24.10.1",
|
package/src/constants/index.ts
CHANGED
|
@@ -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
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
],
|
package/src/helpers/index.ts
CHANGED
|
@@ -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
|
-
|
|
453
|
+
// if (this.api.crdt?.isCrdtSupported(entity) && !withCrdt) {
|
|
454
|
+
// const crdtKey = this.api.crdt?.getCrdtField(entity)
|
|
455
455
|
|
|
456
|
-
|
|
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 {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
39
|
+
this.options.db.options = {
|
|
40
|
+
...(this.options.db.options || {}),
|
|
41
|
+
...(this.auth.variables?.db || {})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
if (this.
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 (
|
|
273
|
-
|
|
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 (
|
|
325
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
}
|
package/src/types/basic.ts
CHANGED
|
@@ -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
|
package/src/types/index.ts
CHANGED
|
@@ -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?:
|
|
24
|
+
crdt?: Omit<CuboCrdtOptions<A>, 'beforeLoadDocument'>
|
|
26
25
|
|
|
27
26
|
augmentations?: Partial<CuboCrudAugmentationsStore<T>>
|
|
28
27
|
}
|