@cuboapp/api-backend 1.0.25 → 1.0.26

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/src/old.ts ADDED
@@ -0,0 +1,744 @@
1
+ import { CuboCrdtDocument, CuboCrdtServer, CuboCrdtSubescribe } from '@cuboapp/crdt'
2
+ import { QueryTypes } from '@cuboapp/database'
3
+ import { CuboApiEntitiesMap } from '@cuboapp/types'
4
+ import { cloneDeep, pick } from '@cuboapp/utils'
5
+ import { createWsServer, WsServer, WsServerSocket } from '@cuboapp/ws'
6
+
7
+ import { CuboCrdtServerSubscribe } from '@cuboapp/crdt/src/server/types'
8
+ import { ApiHelpers } from './helpers'
9
+ import { CuboBackendApiAuth, CuboBackendApiOptions, CuboCrudGetManyResponse, CuboCrudMethodOptions, CuboCrudRequest } from './types'
10
+
11
+ type CuboBackendWsServer<A> = { auth: A }
12
+
13
+ type CuboBackendApiSubscribes<A, T> = Map<
14
+ string,
15
+ Map<WsServerSocket<{ auth: A }>, Map<string, CuboCrdtSubescribe<T> & { docs: Set<CuboCrdtDocument> }>>
16
+ >
17
+
18
+ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>, A extends unknown> {
19
+ public crdt?: CuboCrdtServer<T>
20
+ public ws?: WsServer<CuboBackendWsServer<A>>
21
+
22
+ public subscribes: CuboBackendApiSubscribes<A, T> = new Map()
23
+
24
+ constructor(public options: CuboBackendApiOptions<T, A>) {}
25
+
26
+ public auth: CuboBackendApiAuth
27
+ public helpers = new ApiHelpers<T, A>(this)
28
+
29
+ async init() {
30
+ try {
31
+ if (this.options.api !== undefined) {
32
+ this.auth = await this.request<{ variables: Record<string, any> }>('/auth/me?with=variables', {
33
+ withAuth: true
34
+ })
35
+
36
+ if (!this.auth) {
37
+ throw { code: 403, text: 'Авторизация Cubo-Backend не пройдена' }
38
+ }
39
+
40
+ if (this.auth.variables?.db !== undefined) {
41
+ this.options.db = this.options.db || {}
42
+
43
+ this.options.db.options = {
44
+ ...(this.options.db.options || {}),
45
+ ...(this.auth.variables?.db || {})
46
+ }
47
+ }
48
+ }
49
+
50
+ if (this.options.crdt !== undefined) {
51
+ this.ws = createWsServer<CuboBackendWsServer<A>>({
52
+ host: this.options.crdt.host,
53
+ port: this.options.crdt.port,
54
+ debug: this.options.crdt.debug,
55
+ auth:
56
+ this.options.crdt.authHandler !== undefined
57
+ ? {
58
+ default: true,
59
+ handler: async (ctx) => {
60
+ ctx.request.auth = await this.options.crdt.authHandler!(ctx)
61
+ }
62
+ }
63
+ : undefined,
64
+
65
+ onConnect: (client) => this.crdt.addClient(client),
66
+ onDisconnect: (client) => this.crdt.removeClient(client)
67
+ })
68
+
69
+ this.crdt = new CuboCrdtServer<T>({
70
+ debug: this.options.crdt.debug,
71
+ ws: this.ws,
72
+ entities: (this.options.entities?.map((e) => e.alias) || []) as any[],
73
+ fetchRows: async (client: WsServerSocket, sub: CuboCrdtServerSubscribe) => {
74
+ return this.getMany(
75
+ sub.entity as Extract<keyof T, string>,
76
+ { query: sub.filters },
77
+ { queryOptions: { extra: { auth: client.auth } } }
78
+ ).then((res) => res.rows)
79
+ }
80
+ })
81
+
82
+ const getSub = async (client: WsServerSocket<CuboBackendWsServer<A>>, dto: CuboCrdtSubescribe<T>, opts?: { create?: boolean }) => {
83
+ const subKey = `${dto.entity}`
84
+
85
+ let baseSub = this.subscribes.get(subKey)
86
+ if (!baseSub) {
87
+ if (opts?.create !== false) {
88
+ baseSub = this.subscribes.set(subKey, new Map()).get(subKey)
89
+ } else {
90
+ return { subKey }
91
+ }
92
+ }
93
+
94
+ let clientSub = baseSub.get(client)
95
+ if (!clientSub) {
96
+ if (opts?.create !== false) {
97
+ clientSub = baseSub.set(client, new Map()).get(client)
98
+ } else {
99
+ return { baseSub, subKey }
100
+ }
101
+ }
102
+
103
+ const sub = clientSub.get(dto.id)
104
+ if (!sub) {
105
+ if (opts?.create !== false) {
106
+ return {
107
+ sub: clientSub.set(dto.id, { ...dto, docs: new Set() }).get(dto.id),
108
+ clientSub,
109
+ baseSub,
110
+ subKey
111
+ }
112
+ } else {
113
+ return {
114
+ clientSub,
115
+ baseSub,
116
+ subKey
117
+ }
118
+ }
119
+ }
120
+
121
+ return { sub, clientSub, baseSub, subKey }
122
+ }
123
+
124
+ this.ws.registerHandler<{ subscribe_id: string; name: string; update: Uint8Array; origin?: any }>(
125
+ 'crdt:update',
126
+ async ({ client, message }) => {
127
+ const { name, update, origin, subscribe_id } = message.data
128
+ // const { sub, subKey } = await getSub(client, message.data)
129
+
130
+ const doc = await this.crdt.getDocument(name, { autoCreate: false })
131
+ // console.log('[crdt:update] - received', message)
132
+
133
+ if (doc) {
134
+ doc.applyUpdate(
135
+ new Uint8Array(update),
136
+ JSON.parse(
137
+ JSON.stringify({
138
+ ...(origin || {}),
139
+ subscribe_id
140
+ })
141
+ )
142
+ )
143
+ }
144
+ }
145
+ )
146
+
147
+ const crdtSubscribe = async (
148
+ entity: T[Extract<keyof T, string>],
149
+ sub: CuboCrdtSubescribe<T> & {
150
+ docs: Set<CuboCrdtDocument>
151
+ },
152
+ client: WsServerSocket
153
+ ) => {
154
+ const entity_id = (entity as any).id
155
+
156
+ const doc = await this.crdt.getDocument(`${sub.entity}:${entity_id}`, { initialState: JSON.parse(JSON.stringify(entity)) })
157
+ doc.subscribe(sub.id, (data, origin) => {
158
+ const noStrategyResolved = !origin?.strategy || origin?.strategy === 'all'
159
+ const strategyExcludedResolved = (origin?.strategy === 'other' && origin.subscribe_id !== sub.id) || false
160
+
161
+ const row = doc.getMap().toJSON()
162
+
163
+ // console.log(
164
+ // '[crdt:update] - push to client',
165
+ // { subscribe_id: sub.id, origin, data: data.length },
166
+ // { noStrategyResolved, strategyExcludedResolved }
167
+ // )
168
+
169
+ if (noStrategyResolved || strategyExcludedResolved) {
170
+ const isSutable = subFilterCheck(row, sub.filters || {})
171
+
172
+ let action = 'update'
173
+ if (!isSutable) {
174
+ action = !sub.docs?.has(doc) ? 'create' : 'delete'
175
+ }
176
+
177
+ client.send(
178
+ JSON.stringify({
179
+ method: 'crdt:action:' + sub.id,
180
+ data: {
181
+ action,
182
+ entity_id,
183
+ entity: sub.entity,
184
+ data: Array.from(data)
185
+ }
186
+ })
187
+ )
188
+ }
189
+
190
+ if (origin?.with_save) {
191
+ const body = pick(doc.getMap().toJSON(), origin.keys || [])
192
+
193
+ if (Object.keys(body)) {
194
+ console.log('call updateOne', sub.entity, entity_id)
195
+ // todo: debounce update
196
+ this.updateOne(
197
+ sub.entity,
198
+ {
199
+ query: { id: entity_id },
200
+ body
201
+ },
202
+ { debounce: 300, log: false, extra: { auth: client.auth } }
203
+ ).catch((e) => {
204
+ console.log(e)
205
+ })
206
+ }
207
+ }
208
+ })
209
+ sub.docs.add(doc)
210
+
211
+ return {
212
+ id: entity_id,
213
+ row: Array.from(doc.stateAsUpdate)
214
+ }
215
+ }
216
+
217
+ this.ws.registerHandler<CuboCrdtSubescribe<T>>('crdt:subscribe', async ({ client, message }) => {
218
+ const { sub, subKey } = await getSub(client, message.data)
219
+
220
+ if (!sub) {
221
+ throw 'Подписка не определена: "' + subKey + '"'
222
+ }
223
+
224
+ if (sub.entity_id) {
225
+ const entity = await this.getOne(
226
+ sub.entity,
227
+ { query: { ...(sub.filters || {}), id: sub.entity_id } },
228
+ { queryOptions: { extra: { auth: client.auth } } }
229
+ )
230
+
231
+ return crdtSubscribe(entity, sub, client)
232
+ } else {
233
+ const { rows } = await this.getMany(sub.entity, { query: sub.filters || {} }, { extra: { auth: client.auth } })
234
+
235
+ return Promise.all(rows.map((row) => crdtSubscribe(row, sub, client)))
236
+ }
237
+ })
238
+
239
+ this.ws.registerHandler<CuboCrdtSubescribe<T>>('crdt:unsubscribe', async ({ client, message }) => {
240
+ const { sub, clientSub } = await getSub(client, message.data)
241
+
242
+ if (sub) {
243
+ sub.docs?.forEach((doc) => doc?.unsubscribe(sub.id))
244
+
245
+ clientSub.delete(sub.id)
246
+ }
247
+
248
+ return true
249
+ })
250
+
251
+ const subFilterCheck = (row: object, filters?: object) => {
252
+ let sutable = true
253
+
254
+ if (Object.keys(filters || {}).length) {
255
+ Object.entries(filters).forEach(([key, value]) => {
256
+ if (!['limit', 'with'].includes(key)) {
257
+ const [formula, str] = `${value}`.split(':')
258
+
259
+ // console.log({ formula, str, value: row[key] })
260
+
261
+ if (!str) {
262
+ if (row[key] !== value) {
263
+ sutable = false
264
+ }
265
+ } else {
266
+ switch (formula) {
267
+ case 'in':
268
+ if (
269
+ !str
270
+ .split(',')
271
+ .map(Number)
272
+ .includes(row[key] as number)
273
+ ) {
274
+ sutable = false
275
+ }
276
+ break
277
+ default:
278
+ sutable = false
279
+ break
280
+ }
281
+ }
282
+ }
283
+ })
284
+ }
285
+
286
+ return sutable
287
+ }
288
+
289
+ await this.ws.start()
290
+
291
+ this.ws.httpServer.registerRoute('GET', '/clients', () => {
292
+ return {
293
+ documents: this.crdt.documents.size,
294
+ subescribes: Object.fromEntries(
295
+ Array.from(this.subscribes).map(([key, clients]) => {
296
+ return [
297
+ key,
298
+ {
299
+ count: clients.size,
300
+ clients: Object.fromEntries(
301
+ Array.from(clients).map(([client, sub]) => {
302
+ return [
303
+ client.id,
304
+ {
305
+ auth: (client.auth as any)?.user?.name,
306
+ subscribes: Object.fromEntries(Array.from(sub))
307
+ }
308
+ ]
309
+ })
310
+ )
311
+ }
312
+ ]
313
+ })
314
+ ),
315
+ clients: this.ws.clients.size
316
+ }
317
+ })
318
+ }
319
+ } catch (e) {
320
+ console.error('[API-BACKEND] startup error', e)
321
+ }
322
+ }
323
+
324
+ async destroy() {
325
+ const read = await this.helpers.getConnection('read', false)
326
+ const write = await this.helpers.getConnection('write', false)
327
+
328
+ if (read) {
329
+ await read.disconnect()
330
+ }
331
+
332
+ if (write) {
333
+ await write.disconnect()
334
+ }
335
+ }
336
+
337
+ // private debouncers = new Map<string, { createOne: any; updateOne: any; deleteOne: any }>()
338
+ // private createDebouncers() {
339
+ // this.options.entities.forEach((entity) => {
340
+ // this.debouncers.set(entity.alias, {
341
+ // createOne: (wait: number, req: any, opts?: any) => {
342
+ // return debounce(this.createOne.bind(this, entity.alias, req, { ...(opts || {}), debounce: undefined }), wait)
343
+ // },
344
+ // updateOne: (wait: number, req: any, opts?: any) => {
345
+ // return debounce(this.updateOne.bind(this, entity.alias, req, { ...(opts || {}), debounce: undefined }), wait)
346
+ // },
347
+ // deleteOne: (wait: number, req: any, opts?: any) => {
348
+ // return debounce(this.deleteOne.bind(this, entity.alias, req, { ...(opts || {}), debounce: undefined }), wait)
349
+ // }
350
+ // })
351
+ // })
352
+ // }
353
+
354
+ private debounces = new Map<`${string}:${number}`, { started: number; promise: Promise<any> }>()
355
+
356
+ public async request<T>(url: string, opts?: RequestInit & { debug?: boolean; withAuth?: boolean; withoutContentType?: boolean }) {
357
+ const baseUrl = this.options?.api?.base_url || 'https://api.cubo.sh'
358
+
359
+ const headers: Record<string, any> = {
360
+ ...(opts?.headers || {})
361
+ }
362
+
363
+ if (opts?.withAuth) {
364
+ Object.assign(headers, this.options.api?.headers || {})
365
+ }
366
+
367
+ if (!headers['Content-Type'] && !opts?.withoutContentType) {
368
+ headers['Accept'] = 'application/json'
369
+ headers['Content-Type'] = 'application/json'
370
+ }
371
+
372
+ const requestUrl = baseUrl.replace(/\/$/, '') + '/' + url.replace(/^\//, '')
373
+ const requestOpts = {
374
+ ...(opts || {}),
375
+ headers
376
+ }
377
+
378
+ try {
379
+ const response = await fetch(requestUrl, requestOpts)
380
+
381
+ if ([201, 200].includes(response.status)) {
382
+ try {
383
+ const json = await response.json()
384
+
385
+ return json as Promise<T>
386
+ } catch (e) {
387
+ const text = await response.text()
388
+
389
+ try {
390
+ const json = text ? JSON.parse(text) : null
391
+
392
+ return json as Promise<T>
393
+ } catch {
394
+ throw { status: 500, error: 'Invalid response', text }
395
+ }
396
+ }
397
+ } else {
398
+ throw {
399
+ status: response.status,
400
+ error: response.statusText,
401
+ text: await response.text()
402
+ }
403
+ }
404
+ } catch (e) {
405
+ if (opts?.debug) {
406
+ console.log(
407
+ '[API BACKEND]',
408
+ JSON.stringify(
409
+ {
410
+ request: {
411
+ url: requestUrl,
412
+ opts: requestOpts
413
+ },
414
+ error: e
415
+ },
416
+ null,
417
+ 2
418
+ )
419
+ )
420
+ }
421
+
422
+ throw e
423
+ }
424
+ }
425
+
426
+ private async getEntity(entityAlias: Extract<keyof T, string>) {
427
+ const entity = await this.helpers.getEntityByAlias(entityAlias)
428
+ if (!entity) {
429
+ throw new Error('Entity no found: "' + String(entityAlias) + '"')
430
+ }
431
+
432
+ return entity
433
+ }
434
+
435
+ private async crdtAfterEvent<K extends Extract<keyof T, string>>(
436
+ event: 'update',
437
+ entity: K,
438
+ newVal?: T[K],
439
+ oldVal?: T[K],
440
+ origin?: CuboCrudMethodOptions['crdt']
441
+ ) {
442
+ const entity_id = newVal ? (newVal as any).id : (oldVal as any)?.id
443
+
444
+ const doc = await this.crdt.getDocument(`${entity}:${entity_id}`, { autoCreate: false })
445
+
446
+ if (doc) {
447
+ switch (event) {
448
+ case 'update':
449
+ {
450
+ const diff = Object.fromEntries(
451
+ Object.entries(newVal)
452
+ .map(([key, value]) => {
453
+ if (value !== oldVal[key]) {
454
+ return [key, value]
455
+ }
456
+
457
+ return undefined
458
+ })
459
+ .filter((i) => !!i)
460
+ )
461
+ // console.log('call crdtAfterEvent', { entity, diff })
462
+
463
+ if (Object.keys(diff).length) {
464
+ doc.writeMap(diff, origin)
465
+ }
466
+ }
467
+ break
468
+ }
469
+ }
470
+ }
471
+
472
+ async getMany<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
473
+ entityAlias: K,
474
+ req?: CuboCrudRequest,
475
+ opts?: CuboCrudMethodOptions
476
+ ): Promise<CuboCrudGetManyResponse<T[K]>> {
477
+ opts = opts || {}
478
+ req = req || {}
479
+ const entity = await this.getEntity(entityAlias)
480
+ const augmentation = this.options?.augmentations?.[entityAlias]
481
+
482
+ if (augmentation?.beforeGetMany) {
483
+ opts.queryOptions = await augmentation.beforeGetMany(req, opts)
484
+ }
485
+
486
+ // get all joins
487
+ const queryDto = await this.helpers.withes.prepare(req, entity, opts)
488
+ queryDto.withDeleted = opts.queryOptions?.withDeleted
489
+ // queryDto.withCrdt = opts.queryOptions?.withCrdt
490
+
491
+ // create find query
492
+ const regularQuery = await this.helpers.createFindQuery(req, entity, queryDto)
493
+ const countQuery = await this.helpers.createFindQuery(req, entity, queryDto, true)
494
+
495
+ const db = await this.helpers.getConnection('write')
496
+
497
+ if (opts?.log) {
498
+ console.log(`QUERY:getMany to "${String(entityAlias)}":`, regularQuery.sql, regularQuery.replacements, { req, opts, queryDto })
499
+ }
500
+
501
+ const rows = await db.connection.query<any>(regularQuery.sql!, {
502
+ type: QueryTypes.SELECT,
503
+ replacements: regularQuery.replacements,
504
+ transaction: opts?.transaction
505
+ })
506
+ const [totals] = await db.connection.query<any>(countQuery.sql!, {
507
+ type: QueryTypes.SELECT,
508
+ replacements: countQuery.replacements,
509
+ transaction: opts?.transaction
510
+ })
511
+
512
+ const response = {
513
+ rows: await this.helpers.convert.prepareAll(req, entity, rows, regularQuery),
514
+ totals: {
515
+ count: totals.total !== undefined ? +totals.total : 0
516
+ }
517
+ }
518
+
519
+ if (augmentation?.afterGetMany && !opts?.excludeHooks?.includes('afterGetMany')) {
520
+ return augmentation.afterGetMany(response, req, opts)
521
+ }
522
+
523
+ return response
524
+ }
525
+
526
+ async getOne<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
527
+ entityAlias: K,
528
+ req: CuboCrudRequest,
529
+ opts?: CuboCrudMethodOptions
530
+ ): Promise<T[K] | undefined> {
531
+ opts = opts || {}
532
+
533
+ const entity = await this.getEntity(entityAlias)
534
+ const augmentation = this.options?.augmentations?.[entityAlias]
535
+ if (augmentation?.beforeGetOne) {
536
+ opts.queryOptions = await augmentation.beforeGetOne(req, opts)
537
+ }
538
+
539
+ // get all joins
540
+ const queryDto = await this.helpers.withes.prepare(req, entity, opts)
541
+ queryDto.withDeleted = opts.queryOptions?.withDeleted
542
+ queryDto.withCrdt = opts.queryOptions?.withCrdt
543
+ queryDto.limit = 1
544
+
545
+ const query = await this.helpers.createFindQuery(req, entity, queryDto)
546
+
547
+ const db = await this.helpers.getConnection('read')
548
+
549
+ const [row] = await db.connection.query<any>(query.sql!, {
550
+ type: QueryTypes.SELECT,
551
+ replacements: query.replacements,
552
+ transaction: opts?.transaction
553
+ })
554
+
555
+ if (opts?.log) {
556
+ console.log(`QUERY:getOne to "${String(entityAlias)}":`, query.sql, query.replacements)
557
+ }
558
+
559
+ if (!row) {
560
+ return null as any
561
+ // throw { status: 404, message: 'Element "' + entityAlias + '" not found', details: req }
562
+ }
563
+
564
+ const response = await this.helpers.convert.prepareOne(req, entity, row, query)
565
+
566
+ if (augmentation?.afterGetOne && !opts?.excludeHooks?.includes('afterGetOne')) {
567
+ return augmentation.afterGetOne(response, req, opts)
568
+ }
569
+
570
+ return response
571
+ }
572
+
573
+ async createOne<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
574
+ entityAlias: K,
575
+ req: CuboCrudRequest,
576
+ opts?: CuboCrudMethodOptions
577
+ ): Promise<T[K]> {
578
+ opts = opts || {}
579
+ const entity = await this.helpers.getEntityByAlias(entityAlias)
580
+ const augmentation = this.options?.augmentations?.[entityAlias]
581
+
582
+ if (augmentation?.beforeCreate) {
583
+ opts.queryOptions = await augmentation.beforeCreate(req, opts)
584
+ }
585
+
586
+ const dto = await this.helpers.data.prepare('create', entity!.fields, req.body, opts?.performer_id || 0)
587
+ if (!Object.keys(dto)) {
588
+ throw { status: 400, text: 'No keys to update' }
589
+ }
590
+
591
+ // const prepared = dbPrepareInsertQueryString(dto)
592
+ const db = await this.helpers.getConnection('write')
593
+ const [created_id] = await db.create(entity.alias, dto, { transaction: opts?.transaction, log: opts?.log })
594
+
595
+ if (!created_id) {
596
+ throw { status: 400, text: 'Unable to create entity' }
597
+ }
598
+
599
+ let response = await this.getOne<K>(entityAlias, { query: { id: created_id } }, opts)
600
+
601
+ if (!response) {
602
+ throw { status: 400, text: 'Unable to find entity after create' }
603
+ }
604
+
605
+ if (augmentation?.afterCreate && !opts?.excludeHooks?.includes('afterCreate')) {
606
+ response = await augmentation.afterCreate(response, req, opts)
607
+ }
608
+
609
+ if (this.crdt) {
610
+ // this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_CREATED, { name: `${entityAlias}:${(response as any).id}` })
611
+ }
612
+
613
+ return response
614
+ }
615
+
616
+ async updateOne<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
617
+ entityAlias: K,
618
+ req: CuboCrudRequest,
619
+ opts?: CuboCrudMethodOptions
620
+ ): Promise<T[K] | undefined> {
621
+ if (opts?.debounce) {
622
+ if (!req.query?.id) {
623
+ throw 'Debounce "updateOne" allowed only with "id"'
624
+ }
625
+ console.log('call debounced updateOne')
626
+
627
+ const key = `${entityAlias}:${+req.query.id}` as `${string}:${number}`
628
+ let ex = this.debounces.get(key)
629
+ if (ex) {
630
+ if (ex.started > +new Date() - opts.debounce) {
631
+ console.log('clear execution')
632
+ Promise.reject(ex.promise)
633
+ } else {
634
+ return ex.promise
635
+ }
636
+ }
637
+
638
+ return new Promise((resolve, reject) => {
639
+ setTimeout(() => {
640
+ this.updateOne(entityAlias, req, { ...(opts || {}), debounce: undefined })
641
+ .then(resolve)
642
+ .catch(reject)
643
+ }, opts.debounce)
644
+ })
645
+ }
646
+
647
+ opts = opts || {}
648
+ const entity = await this.helpers.getEntityByAlias(entityAlias)
649
+ const augmentation = this.options?.augmentations?.[entityAlias]
650
+
651
+ if (req.query?.crdt_exclude) {
652
+ opts = opts || {}
653
+ opts.crdt = {
654
+ exclude: req.query.crdt_exclude
655
+ }
656
+
657
+ delete req.query.crdt_exclude
658
+ }
659
+
660
+ if (augmentation?.beforeUpdate && !opts?.excludeHooks?.includes('beforeUpdate')) {
661
+ opts.queryOptions = await augmentation.beforeUpdate(req, opts)
662
+ }
663
+
664
+ const item: any = await this.getOne(entityAlias, req, opts)
665
+
666
+ if (!item) {
667
+ throw { status: 404, text: 'Entity not found' }
668
+ }
669
+
670
+ const initialEntity = cloneDeep(item)
671
+
672
+ try {
673
+ if (!!augmentation?.can) {
674
+ await augmentation.can('update', { entity, row: item, auth: opts?.extra?.auth, req })
675
+ }
676
+ } catch {
677
+ throw { status: 403, text: 'Updating entity forbidden' }
678
+ }
679
+
680
+ const dto = await this.helpers.data.prepare('update', entity!.fields, req.body, opts?.performer_id || 0)
681
+ if (!Object.keys(dto)) {
682
+ throw { status: 400, text: 'No keys to update' }
683
+ }
684
+
685
+ const db = await this.helpers.getConnection('write')
686
+ await db.update(entity.alias, 'id = :id', { id: item.id }, dto, { transaction: opts?.transaction, log: opts?.log })
687
+
688
+ let response = await this.getOne<K>(entityAlias, req, opts)
689
+ if (!response) {
690
+ throw { status: 400, text: 'Unable to find entity after update' }
691
+ }
692
+
693
+ if (augmentation?.afterUpdate && !opts?.excludeHooks?.includes('afterUpdate')) {
694
+ response = await augmentation.afterUpdate(response, req, opts)
695
+ }
696
+
697
+ if (this.crdt && opts?.crdt !== undefined) {
698
+ // console.log('on update crdt', entity.alias, opts?.crdt)
699
+ await this.crdtAfterEvent('update', entityAlias, response, initialEntity, opts.crdt)
700
+ }
701
+
702
+ return response
703
+ }
704
+
705
+ async deleteOne<K extends Extract<keyof T, string>>(
706
+ entityAlias: K,
707
+ req: CuboCrudRequest,
708
+ opts?: CuboCrudMethodOptions
709
+ ): Promise<boolean> {
710
+ opts = opts || {}
711
+ const entity = await this.helpers.getEntityByAlias(entityAlias)
712
+ const augmentation = this.options?.augmentations?.[entityAlias]
713
+
714
+ if (augmentation?.beforeDelete) {
715
+ opts.queryOptions = await augmentation.beforeDelete(req, opts)
716
+ }
717
+
718
+ const item: any = await this.getOne(entityAlias, req, opts)
719
+ if (!item) {
720
+ throw { status: 404, text: 'Entity not found' }
721
+ }
722
+
723
+ const dto = await this.helpers.data.prepare('delete', entity!.fields, req.body || {}, opts?.performer_id)
724
+ if (!Object.keys(dto)) {
725
+ throw { status: 400, text: 'No keys to update' }
726
+ }
727
+
728
+ const db = await this.helpers.getConnection('write')
729
+ await db.update(entity.alias, 'id = :id', { id: item.id }, dto, { transaction: opts?.transaction, log: opts?.log })
730
+
731
+ if (augmentation?.afterDelete && !opts?.excludeHooks?.includes('afterDelete')) {
732
+ return augmentation.afterDelete(item, req, opts)
733
+ }
734
+
735
+ if (this.crdt) {
736
+ // this.crdt.ee.emit(CUBO_CRDT_EVENT.DOCUMENT_DELETED, { name: `${entityAlias}:${item.id}` })
737
+ }
738
+
739
+ return true
740
+ }
741
+ }
742
+
743
+ export * from './helpers'
744
+ export * from './types'