@cuboapp/api-backend 1.0.9 → 1.0.12

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/index.ts CHANGED
@@ -1,57 +1,49 @@
1
- import { Database, Options, QueryTypes } from '@cuboapp/database'
2
- import { CuboApiEntitiesMap, CuboEntity } from '@cuboapp/types'
1
+ import { QueryTypes } from '@cuboapp/database'
2
+ import { CuboApiEntitiesMap } from '@cuboapp/types'
3
3
 
4
+ import { CuboCrdt } from './crdt'
4
5
  import { ApiHelpers } from './helpers'
5
- import { CuboCrudAugmentationsStore, CuboCrudGetManyResponse, CuboCrudMethodOptions, CuboCrudRequest } from './types'
6
-
7
- export * from './types'
8
- export * from './utils'
9
-
10
- export type CuboBackendApiOptions<T extends CuboApiEntitiesMap<T>> = {
11
- api: {
12
- base_url: string
13
- token: string
14
- }
15
-
16
- db: {
17
- connection?: Database
18
- options?: Options
19
- }
20
-
21
- entities: CuboEntity[]
22
-
23
- auth?: {
24
- base_url?: string
25
- }
26
-
27
- augmentations?: Partial<CuboCrudAugmentationsStore<T>>
28
- }
6
+ import { CuboBackendApiAuth, CuboBackendApiOptions, CuboCrudGetManyResponse, CuboCrudMethodOptions, CuboCrudRequest } from './types'
29
7
 
30
8
  export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
31
- constructor(public options: CuboBackendApiOptions<T>) {}
9
+ constructor(public options: CuboBackendApiOptions<T>) {
10
+ if (options.crdt) {
11
+ this.crdt = new CuboCrdt<T>(this)
12
+ this.crdt.listen()
13
+ }
14
+ }
32
15
 
16
+ public auth: CuboBackendApiAuth
17
+ public crdt?: CuboCrdt<T>
33
18
  public helpers = new ApiHelpers<T>(this)
34
19
 
35
20
  async init() {
36
- const auth = await this.authRequest('/me')
21
+ try {
22
+ this.auth = await this.request<{ variables: Record<string, any> }>('/auth/me?with=variables')
37
23
 
38
- if (!auth) {
39
- throw { code: 403, text: 'Авторизация Cubo-Backend не пройдена' }
40
- }
41
- }
24
+ if (!this.auth) {
25
+ throw { code: 403, text: 'Авторизация Cubo-Backend не пройдена' }
26
+ }
42
27
 
43
- async apiRequest<T>(url: string, opts?: RequestInit) {
44
- return this.request<T>(`https://${this.options.api.base_url}`, url, opts)
45
- }
28
+ if (this.auth.variables.db !== undefined) {
29
+ this.options.db = this.options.db || {}
46
30
 
47
- async authRequest<T>(url: string, opts?: RequestInit) {
48
- return this.request<T>(this.options?.auth?.base_url || 'https://auth.cubo.sh', url, opts)
31
+ this.options.db.options = {
32
+ ...(this.options.db.options || {}),
33
+ ...(this.auth.variables.db || {})
34
+ }
35
+ }
36
+ } catch (e) {
37
+ console.error('[API-BACKEND] startup error', e)
38
+ }
49
39
  }
50
40
 
51
- private async request<T>(baseUrl: string, url: string, opts?: RequestInit & { withoutContentType?: boolean }) {
41
+ public async request<T>(url: string, opts?: RequestInit & { debug?: boolean; withoutContentType?: boolean }) {
42
+ const baseUrl = this.options?.api?.base_url || 'https://api.cubo.sh'
43
+
52
44
  const headers: Record<string, any> = {
53
45
  ...(opts?.headers || {}),
54
- Authorization: this.options.api.token || ''
46
+ ...(this.options.api?.headers || {})
55
47
  }
56
48
 
57
49
  if (!headers['Content-Type'] && !opts?.withoutContentType) {
@@ -59,33 +51,57 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
59
51
  headers['Content-Type'] = 'application/json'
60
52
  }
61
53
 
62
- const response = await fetch(baseUrl.replace(/\/$/, '') + '/' + url.replace(/^\//, ''), {
54
+ const requestUrl = baseUrl.replace(/\/$/, '') + '/' + url.replace(/^\//, '')
55
+ const requestOpts = {
63
56
  ...(opts || {}),
64
57
  headers
65
- })
66
-
67
- if ([201, 200].includes(response.status)) {
68
- try {
69
- const json = await response.json()
58
+ }
70
59
 
71
- return json as Promise<T>
72
- } catch (e) {
73
- const text = await response.text()
60
+ try {
61
+ const response = await fetch(requestUrl, requestOpts)
74
62
 
63
+ if ([201, 200].includes(response.status)) {
75
64
  try {
76
- const json = text ? JSON.parse(text) : null
65
+ const json = await response.json()
77
66
 
78
67
  return json as Promise<T>
79
- } catch {
80
- throw { status: 500, error: 'Invalid response', text }
68
+ } catch (e) {
69
+ const text = await response.text()
70
+
71
+ try {
72
+ const json = text ? JSON.parse(text) : null
73
+
74
+ return json as Promise<T>
75
+ } catch {
76
+ throw { status: 500, error: 'Invalid response', text }
77
+ }
78
+ }
79
+ } else {
80
+ throw {
81
+ status: response.status,
82
+ error: response.statusText,
83
+ text: await response.text()
81
84
  }
82
85
  }
83
- } else {
84
- throw {
85
- status: response.status,
86
- error: response.statusText,
87
- text: await response.text()
86
+ } catch (e) {
87
+ if (opts?.debug) {
88
+ console.log(
89
+ '[API BACKEND]',
90
+ JSON.stringify(
91
+ {
92
+ request: {
93
+ url: requestUrl,
94
+ opts: requestOpts
95
+ },
96
+ error: e
97
+ },
98
+ null,
99
+ 2
100
+ )
101
+ )
88
102
  }
103
+
104
+ throw e
89
105
  }
90
106
  }
91
107
 
@@ -223,14 +239,27 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
223
239
  throw { status: 400, text: 'Unable to create entity' }
224
240
  }
225
241
 
226
- const response = await this.getOne<K>(entityAlias, { query: { id: created_id } }, opts)
242
+ let response = await this.getOne<K>(entityAlias, { query: { id: created_id } }, opts)
227
243
 
228
244
  if (!response) {
229
245
  throw { status: 400, text: 'Unable to find entity after create' }
230
246
  }
231
247
 
232
248
  if (augmentation?.afterCreate && !opts?.excludeHooks?.includes('afterCreate')) {
233
- return augmentation.afterCreate(response, req, opts)
249
+ response = await augmentation.afterCreate(response, req, opts)
250
+ }
251
+
252
+ if (opts.crdt !== false && this.crdt.isCrdtSupported(entity)) {
253
+ const state = await this.crdt.createState({ entityAlias, id: created_id, entity }, response)
254
+
255
+ const fieldKey = entity.fields.find((f) => f.type === 'binary').alias
256
+
257
+ const db = await this.helpers.getConnection('read')
258
+ await db.update(entity!.alias, 'id=:id', { id: created_id }, { [fieldKey]: state }, opts)
259
+
260
+ if (augmentation?.afterCrdtCreate && !opts?.excludeHooks?.includes('afterCrdtCreate')) {
261
+ augmentation.afterCrdtCreate(response, req, opts)
262
+ }
234
263
  }
235
264
 
236
265
  return response
@@ -263,13 +292,22 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
263
292
  const db = await this.helpers.getConnection('write')
264
293
  await db.update(entity.alias, 'id = :id', { id: item.id }, dto, { transaction: opts?.transaction, log: opts?.log })
265
294
 
266
- const response = await this.getOne<K>(entityAlias, req, opts)
295
+ let response = await this.getOne<K>(entityAlias, req, opts)
267
296
  if (!response) {
268
297
  throw { status: 400, text: 'Unable to find entity after update' }
269
298
  }
270
299
 
271
300
  if (augmentation?.afterUpdate && !opts?.excludeHooks?.includes('afterUpdate')) {
272
- return augmentation.afterUpdate(response, req, opts)
301
+ response = await augmentation.afterUpdate(response, req, opts)
302
+ }
303
+
304
+ if (opts.crdt !== false && this.crdt.isCrdtSupported(entity)) {
305
+ const context = { auth: opts?.extra?.auth }
306
+ await this.crdt.updateDocument({ entityAlias, id: item.id, entity }, response, context)
307
+
308
+ if (augmentation?.afterCrdtUpdate && !opts?.excludeHooks?.includes('afterCrdtUpdate')) {
309
+ augmentation.afterCrdtUpdate(response, req, opts)
310
+ }
273
311
  }
274
312
 
275
313
  return response
@@ -309,4 +347,6 @@ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
309
347
  }
310
348
  }
311
349
 
350
+ export * from './crdt'
312
351
  export * from './helpers'
352
+ export * from './types'
@@ -2,6 +2,7 @@ import { CuboApiEntitiesMap } from '@cuboapp/types'
2
2
 
3
3
  import { CuboCrudGetManyResponse, CuboCrudRequest } from './basic'
4
4
  import { CuboCrudMethodOptions, CuboCrudQueryOptions } from './db'
5
+ import { onChangePayload, onLoadDocumentPayload, onStoreDocumentPayload } from '@hocuspocus/server'
5
6
 
6
7
  export type CuboCrudAugmentationsStore<T extends CuboApiEntitiesMap<T>> = {
7
8
  [K in Extract<keyof T, string>]?: CuboCrudAugmentationInstance<T[K]>
@@ -20,10 +21,16 @@ export interface CuboCrudAugmentationInstance<T = unknown> {
20
21
 
21
22
  beforeCreate?: (req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<CuboCrudQueryOptions>
22
23
  afterCreate?: (result: T, req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<T>
24
+ afterCrdtCreate?: (result: T, req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<void>
23
25
 
24
26
  beforeUpdate?: (req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<CuboCrudQueryOptions>
25
27
  afterUpdate?: (result: T, req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<T>
28
+ afterCrdtUpdate?: (result: T, req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<void>
26
29
 
27
30
  beforeDelete?: (req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<CuboCrudQueryOptions>
28
31
  afterDelete?: (result: T, req: CuboCrudRequest, opts?: CuboCrudMethodOptions) => Promise<boolean>
32
+
33
+ onLoadDocument?: (data: onLoadDocumentPayload) => Promise<void>
34
+ onChangeDocument?: (data: onChangePayload) => Promise<void>
35
+ onStoreDocument?: (data: onStoreDocumentPayload) => Promise<void>
29
36
  }
package/src/types/db.ts CHANGED
@@ -27,9 +27,13 @@ export type CuboCrudMethodOptions<E extends object = {}> = {
27
27
  | 'afterGetMany'
28
28
  | 'afterGetOne'
29
29
  | 'beforeUpdate'
30
+ | 'afterCrdtCreate'
31
+ | 'afterCrdtUpdate'
30
32
  )[]
31
33
 
32
34
  log?: boolean
35
+ replace?: boolean
36
+ crdt?: boolean
33
37
 
34
38
  queryOptions?: CuboCrudQueryOptions
35
39
  } & E
@@ -1,3 +1,31 @@
1
+ import { Database, DatabaseOptions, Options } from '@cuboapp/database'
2
+ import { CuboApiEntitiesMap, CuboAuthTokenType, CuboEntity } from '@cuboapp/types'
3
+
4
+ import { CuboCrdtConfiguration } from '../crdt'
5
+
6
+ import { CuboCrudAugmentationsStore } from './augmentation'
7
+
1
8
  export * from './augmentation'
2
9
  export * from './basic'
3
10
  export * from './db'
11
+
12
+ export type CuboBackendApiOptions<T extends CuboApiEntitiesMap<T>> = {
13
+ api: {
14
+ base_url?: string
15
+ headers: Partial<Record<`cubo-${CuboAuthTokenType}`, string>> & Record<string, any>
16
+ }
17
+
18
+ entities?: CuboEntity[]
19
+
20
+ db?: {
21
+ connection?: Database
22
+ options?: Options & DatabaseOptions
23
+ }
24
+
25
+ crdt?: CuboCrdtConfiguration<T>
26
+ augmentations?: Partial<CuboCrudAugmentationsStore<T>>
27
+ }
28
+
29
+ export type CuboBackendApiAuth = {
30
+ variables: Record<string, any>
31
+ }
@@ -1,18 +0,0 @@
1
- {
2
- "editor.minimap.enabled": false,
3
- "editor.formatOnSave": true,
4
- "editor.codeActionsOnSave": {
5
- "source.fixAll.eslint": "explicit",
6
- "source.fixAll.stylelint": "explicit",
7
- "source.organizeImports": "explicit",
8
- "source.organizeExports": "explicit"
9
- },
10
- "emmet.includeLanguages": {
11
- "javascriptreact": "html",
12
- "typescriptreact": "html"
13
- },
14
- "files.exclude": {
15
- "node_modules/": true
16
- },
17
- "typescript.tsdk": "node_modules/typescript/lib"
18
- }