@cuboapp/api-backend 1.0.23 → 1.0.25
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 +269 -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.25",
|
|
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.5",
|
|
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,241 @@ 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
|
+
if (this.options.crdt.debug) {
|
|
93
|
+
console.log('open document', name)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const document = await this.crdt.getDocument(name)
|
|
98
|
+
document.addClient(client)
|
|
99
|
+
|
|
100
|
+
if (this.options.crdt.debug) {
|
|
101
|
+
console.log('document', name, 'subscribed', document.stat)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Array.from(document.stateAsUpdate)
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error('CRDT open document', e)
|
|
107
|
+
throw e
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
this.ws.registerHandler<{ name: ID }>('crdt:close', async ({ client, message }) => {
|
|
112
|
+
const { name } = message.data || {}
|
|
113
|
+
if (!name) {
|
|
114
|
+
throw 'Имя документа обязательно: "name"'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const document = await this.crdt.getDocument(name)
|
|
118
|
+
document.removeClient(client)
|
|
119
|
+
|
|
120
|
+
if (this.options.crdt.debug) {
|
|
121
|
+
console.log('document', name, 'unsubscribed', document.stat)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await this.crdt.checkDocumentUnload(name)
|
|
125
|
+
|
|
126
|
+
return true
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
this.ws.registerHandler<string>('crdt:subscribe', async ({ client, message }) => {
|
|
130
|
+
let { crdt, event, filters } = message.data as any
|
|
131
|
+
filters = filters || {}
|
|
132
|
+
|
|
133
|
+
if (crdt) {
|
|
134
|
+
event = `crdt:update:${crdt.entity}:${crdt.id}`
|
|
135
|
+
filters = { id: crdt.id }
|
|
136
|
+
|
|
137
|
+
// console.log('subscribe to crdt', event, filters)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
client.subscribes = client.subscribes || new Map()
|
|
141
|
+
const subs = client.subscribes.get(event) || []
|
|
142
|
+
const ex = subs.find((s) => JSON.stringify(s) === JSON.stringify(filters))
|
|
143
|
+
if (!ex) {
|
|
144
|
+
subs.push(filters)
|
|
145
|
+
}
|
|
146
|
+
client.subscribes.set(event, subs)
|
|
147
|
+
|
|
148
|
+
if (this.options.crdt.debug) {
|
|
149
|
+
console.log(
|
|
150
|
+
'crdt:subscribe',
|
|
151
|
+
client.id,
|
|
152
|
+
JSON.stringify({ event, filters }),
|
|
153
|
+
Array.from(client.subscribes).map((s) => [s[0], JSON.stringify(s[1])])
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
this.ws.registerHandler<string>('crdt:unsubscribe', async ({ client, message }) => {
|
|
159
|
+
let { event, filters } = message.data as any
|
|
160
|
+
filters = filters || {}
|
|
161
|
+
|
|
162
|
+
client.subscribes = client.subscribes || new Map()
|
|
163
|
+
const subs = client.subscribes.get(event) || []
|
|
164
|
+
const exIndex = subs.findIndex((s) => JSON.stringify(s) === JSON.stringify(filters))
|
|
165
|
+
if (exIndex >= 0) {
|
|
166
|
+
subs.splice(exIndex)
|
|
167
|
+
}
|
|
168
|
+
if (!subs.length) {
|
|
169
|
+
client.subscribes.delete(event)
|
|
170
|
+
} else {
|
|
171
|
+
client.subscribes.set(event, subs)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (this.options.crdt.debug) {
|
|
175
|
+
console.log('crdt:unsubscribe', client.id, { event, filters }, Array.from(client.subscribes))
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const subFilterCheck = (row: any, filters: any) => {
|
|
180
|
+
let sutable = true
|
|
181
|
+
|
|
182
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
183
|
+
if (!['limit', 'with'].includes(key)) {
|
|
184
|
+
const [formula, str] = `${value}`.split(':')
|
|
185
|
+
|
|
186
|
+
// console.log({ formula, str, value: row[key] })
|
|
187
|
+
|
|
188
|
+
if (!str) {
|
|
189
|
+
if (row[key] !== value) {
|
|
190
|
+
sutable = false
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
switch (formula) {
|
|
194
|
+
case 'in':
|
|
195
|
+
if (
|
|
196
|
+
!str
|
|
197
|
+
.split(',')
|
|
198
|
+
.map(Number)
|
|
199
|
+
.includes(row[key] as number)
|
|
200
|
+
) {
|
|
201
|
+
sutable = false
|
|
202
|
+
}
|
|
203
|
+
break
|
|
204
|
+
default:
|
|
205
|
+
sutable = false
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
return sutable
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const onDocumentEvent = async (name: string, action: CuboCrudAction, data?: Uint8Array) => {
|
|
216
|
+
const [entityAlias, id] = name.split(':') as [Extract<keyof T, string>, string]
|
|
217
|
+
|
|
218
|
+
const entity = await this.helpers.getEntityByAlias(entityAlias)
|
|
219
|
+
|
|
220
|
+
const row = await this.getOne(entityAlias, { query: { id: +id } }, { queryOptions: { withDeleted: true } })
|
|
221
|
+
|
|
222
|
+
const a = this.options?.augmentations?.[entityAlias]
|
|
223
|
+
const filterCheck = a?.filterRowForCrdtEvent ?? subFilterCheck
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
for (const client of Array.from(this.ws?.clients)) {
|
|
227
|
+
const hasCardSub = client.subscribes?.get(`crdt:${entityAlias}:${id}`)
|
|
228
|
+
const hasEntitySub = client.subscribes?.get(`crdt:${entityAlias}`)
|
|
229
|
+
const hasCrdtSub = client.subscribes?.get(`crdt:update:${entityAlias}:${id}`)
|
|
230
|
+
|
|
231
|
+
const needCardPush = !hasCardSub?.length || !!hasCardSub.find((f) => !!filterCheck(row, f)) || false
|
|
232
|
+
const needEntityPush = !hasEntitySub?.length || !!hasEntitySub.find((f) => !!filterCheck(row, f)) || false
|
|
233
|
+
const needCrdtPush = !hasCrdtSub?.length || !!hasCrdtSub.find((f) => !!filterCheck(row, f)) || false
|
|
234
|
+
|
|
235
|
+
// console.log(id, name, action, {
|
|
236
|
+
// hasCardSub,
|
|
237
|
+
// hasEntitySub,
|
|
238
|
+
// hasCrdtSub,
|
|
239
|
+
// needCardPush,
|
|
240
|
+
// needEntityPush,
|
|
241
|
+
// needCrdtPush,
|
|
242
|
+
// filterCheck
|
|
243
|
+
// })
|
|
244
|
+
|
|
245
|
+
if (needCrdtPush && data?.length) {
|
|
246
|
+
client.send(JSON.stringify({ method: 'message', event: `crdt:update:${entityAlias}:${id}`, data: Array.from(data) }))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (needCardPush || needEntityPush) {
|
|
250
|
+
if (!a?.can || (await a.can<A>(action, { auth: client.auth, entity, row }))) {
|
|
251
|
+
const dto = {
|
|
252
|
+
event: `crdt:${entityAlias}`,
|
|
253
|
+
action,
|
|
254
|
+
id: id ? +id : null,
|
|
255
|
+
row
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (a?.sendCrdtEvent) {
|
|
259
|
+
await a.sendCrdtEvent({ client, auth: client.auth, entity, dto })
|
|
260
|
+
} else {
|
|
261
|
+
client.send(JSON.stringify({ method: 'message', ...dto }))
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
console.log('sutable pushes cannot', name)
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
console.log('no sutable pushes', name)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} catch (e) {
|
|
271
|
+
console.log(e)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// document events
|
|
276
|
+
this.crdt.ee.addListener(CUBO_CRDT_EVENT.DOCUMENT_UPDATED, (ctx) => onDocumentEvent(ctx.name, 'update', ctx.data))
|
|
277
|
+
this.crdt.ee.addListener(CUBO_CRDT_EVENT.DOCUMENT_CREATED, (ctx) => onDocumentEvent(ctx.name, 'create', ctx.data))
|
|
278
|
+
this.crdt.ee.addListener(CUBO_CRDT_EVENT.DOCUMENT_DELETED, (ctx) => onDocumentEvent(ctx.name, 'delete', ctx.data))
|
|
279
|
+
}
|
|
40
280
|
} catch (e) {
|
|
41
281
|
console.error('[API-BACKEND] startup error', e)
|
|
42
282
|
}
|
|
@@ -151,7 +391,7 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
151
391
|
// get all joins
|
|
152
392
|
const queryDto = await this.helpers.withes.prepare(req, entity, opts)
|
|
153
393
|
queryDto.withDeleted = opts.queryOptions?.withDeleted
|
|
154
|
-
queryDto.withCrdt = opts.queryOptions?.withCrdt
|
|
394
|
+
// queryDto.withCrdt = opts.queryOptions?.withCrdt
|
|
155
395
|
|
|
156
396
|
// create find query
|
|
157
397
|
const regularQuery = await this.helpers.createFindQuery(req, entity, queryDto)
|
|
@@ -271,17 +511,8 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
271
511
|
response = await augmentation.afterCreate(response, req, opts)
|
|
272
512
|
}
|
|
273
513
|
|
|
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
|
-
}
|
|
514
|
+
if (this.crdt) {
|
|
515
|
+
this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_CREATED, { name: `${entityAlias}:${(response as any).id}` })
|
|
285
516
|
}
|
|
286
517
|
|
|
287
518
|
return response
|
|
@@ -306,6 +537,14 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
306
537
|
throw { status: 404, text: 'Entity not found' }
|
|
307
538
|
}
|
|
308
539
|
|
|
540
|
+
try {
|
|
541
|
+
if (!!augmentation?.can) {
|
|
542
|
+
await augmentation.can('update', { entity, row: item, auth: opts?.extra?.auth, req })
|
|
543
|
+
}
|
|
544
|
+
} catch {
|
|
545
|
+
throw { status: 403, text: 'Updating entity forbidden' }
|
|
546
|
+
}
|
|
547
|
+
|
|
309
548
|
const dto = await this.helpers.data.prepare('update', entity!.fields, req.body, opts?.performer_id || 0)
|
|
310
549
|
if (!Object.keys(dto)) {
|
|
311
550
|
throw { status: 400, text: 'No keys to update' }
|
|
@@ -323,13 +562,8 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
323
562
|
response = await augmentation.afterUpdate(response, req, opts)
|
|
324
563
|
}
|
|
325
564
|
|
|
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
|
-
}
|
|
565
|
+
if (this.crdt) {
|
|
566
|
+
await this.crdt.updateDocument(`${entityAlias}:${item.id}`, dto)
|
|
333
567
|
}
|
|
334
568
|
|
|
335
569
|
return response
|
|
@@ -365,10 +599,14 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
|
|
|
365
599
|
return augmentation.afterDelete(item, req, opts)
|
|
366
600
|
}
|
|
367
601
|
|
|
602
|
+
if (this.crdt) {
|
|
603
|
+
this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_DELETED, { name: `${entityAlias}:${item.id}` })
|
|
604
|
+
}
|
|
605
|
+
|
|
368
606
|
return true
|
|
369
607
|
}
|
|
370
608
|
}
|
|
371
609
|
|
|
372
|
-
export * from './crdt'
|
|
610
|
+
// export * from './crdt'
|
|
373
611
|
export * from './helpers'
|
|
374
612
|
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
|
}
|