@cuboapp/api-backend 1.0.23 → 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 +267 -31
- package/src/types/augmentation.ts +22 -7
- package/src/types/basic.ts +4 -0
- package/src/types/index.ts +3 -4
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,21 +1,26 @@
|
|
|
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 {
|
|
@@ -37,6 +42,239 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
44
|
}
|
|
45
|
+
|
|
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)
|
|
123
|
+
|
|
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
|
|
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))
|
|
277
|
+
}
|
|
40
278
|
} catch (e) {
|
|
41
279
|
console.error('[API-BACKEND] startup error', e)
|
|
42
280
|
}
|
|
@@ -151,7 +389,7 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
151
389
|
// get all joins
|
|
152
390
|
const queryDto = await this.helpers.withes.prepare(req, entity, opts)
|
|
153
391
|
queryDto.withDeleted = opts.queryOptions?.withDeleted
|
|
154
|
-
queryDto.withCrdt = opts.queryOptions?.withCrdt
|
|
392
|
+
// queryDto.withCrdt = opts.queryOptions?.withCrdt
|
|
155
393
|
|
|
156
394
|
// create find query
|
|
157
395
|
const regularQuery = await this.helpers.createFindQuery(req, entity, queryDto)
|
|
@@ -271,17 +509,8 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
271
509
|
response = await augmentation.afterCreate(response, req, opts)
|
|
272
510
|
}
|
|
273
511
|
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const fieldKey = entity.fields.find((f) => f.type === 'binary').alias
|
|
278
|
-
|
|
279
|
-
const db = await this.helpers.getConnection('read')
|
|
280
|
-
await db.update(entity!.alias, 'id=:id', { id: created_id }, { [fieldKey]: state }, opts)
|
|
281
|
-
|
|
282
|
-
if (augmentation?.afterCrdtCreate && !opts?.excludeHooks?.includes('afterCrdtCreate')) {
|
|
283
|
-
augmentation.afterCrdtCreate(response, req, opts)
|
|
284
|
-
}
|
|
512
|
+
if (this.crdt) {
|
|
513
|
+
this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_CREATED, { name: `${entityAlias}:${(response as any).id}` })
|
|
285
514
|
}
|
|
286
515
|
|
|
287
516
|
return response
|
|
@@ -306,6 +535,14 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
306
535
|
throw { status: 404, text: 'Entity not found' }
|
|
307
536
|
}
|
|
308
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
|
+
|
|
309
546
|
const dto = await this.helpers.data.prepare('update', entity!.fields, req.body, opts?.performer_id || 0)
|
|
310
547
|
if (!Object.keys(dto)) {
|
|
311
548
|
throw { status: 400, text: 'No keys to update' }
|
|
@@ -323,13 +560,8 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
323
560
|
response = await augmentation.afterUpdate(response, req, opts)
|
|
324
561
|
}
|
|
325
562
|
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
await this.crdt.updateDocument({ entityAlias, id: item.id, entity }, response, context)
|
|
329
|
-
|
|
330
|
-
if (augmentation?.afterCrdtUpdate && !opts?.excludeHooks?.includes('afterCrdtUpdate')) {
|
|
331
|
-
augmentation.afterCrdtUpdate(response, req, opts)
|
|
332
|
-
}
|
|
563
|
+
if (this.crdt) {
|
|
564
|
+
await this.crdt.updateDocument(`${entityAlias}:${item.id}`, dto)
|
|
333
565
|
}
|
|
334
566
|
|
|
335
567
|
return response
|
|
@@ -365,10 +597,14 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
365
597
|
return augmentation.afterDelete(item, req, opts)
|
|
366
598
|
}
|
|
367
599
|
|
|
600
|
+
if (this.crdt) {
|
|
601
|
+
this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_DELETED, { name: `${entityAlias}:${item.id}` })
|
|
602
|
+
}
|
|
603
|
+
|
|
368
604
|
return true
|
|
369
605
|
}
|
|
370
606
|
}
|
|
371
607
|
|
|
372
|
-
export * from './crdt'
|
|
608
|
+
// export * from './crdt'
|
|
373
609
|
export * from './helpers'
|
|
374
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,15 +1,14 @@
|
|
|
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
|
|
11
|
+
export type CuboBackendApiOptions<T extends CuboApiEntitiesMap<T>, A> = {
|
|
13
12
|
api?: {
|
|
14
13
|
base_url?: string
|
|
15
14
|
headers: Partial<Record<`cubo-${CuboAuthTokenType}`, string>> & Record<string, any>
|
|
@@ -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
|
}
|