@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuboapp/api-backend",
3
- "version": "1.0.9",
3
+ "version": "1.0.12",
4
4
  "description": "Backend Api for CuboApp",
5
5
  "main": "src/index.ts",
6
6
  "repository": "git@github.com:cuboapp/api-backend.git",
@@ -8,15 +8,17 @@
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "dependencies": {
11
- "@cuboapp/constants": "2.0.4",
12
- "@cuboapp/database": "1.0.1",
13
- "@cuboapp/types": "2.0.5",
11
+ "@cuboapp/constants": "^2.0.5",
12
+ "@cuboapp/database": "^1.0.1",
13
+ "@cuboapp/types": "^2.0.6",
14
14
  "@cuboapp/utils": "1.0.4",
15
+ "@hocuspocus/extension-database": "^3.2.3",
16
+ "@hocuspocus/server": "^3.2.3",
15
17
  "pg": "^8.16.3",
16
- "sequelize": "^6.37.7"
18
+ "yjs": "^13.6.27"
17
19
  },
18
20
  "devDependencies": {
19
- "@types/node": "^22.0.0",
20
- "typescript": "^5.8.2"
21
+ "@types/node": "^24.10.1",
22
+ "typescript": "^5.9.3"
21
23
  }
22
24
  }
@@ -18,7 +18,8 @@ export const CUBO_CRUD_QUERY_CONDITION = {
18
18
  GT: 'gt',
19
19
  LT: 'lt',
20
20
  GTE: 'gte',
21
- LTE: 'lte'
21
+ LTE: 'lte',
22
+ BTW: 'btw'
22
23
  }
23
24
 
24
25
  export const CUBO_CRUD_DEFAULT_FIELDS: Pick<CuboEntityField, 'type' | 'alias' | 'extra'>[] = [
@@ -0,0 +1,418 @@
1
+ import { QueryTypes } from '@cuboapp/database'
2
+ import { CuboApiEntitiesMap, CuboEntity } from '@cuboapp/types'
3
+ import { Database } from '@hocuspocus/extension-database'
4
+ import {
5
+ beforeSyncPayload,
6
+ Document,
7
+ Hocuspocus,
8
+ onChangePayload,
9
+ onLoadDocumentPayload,
10
+ onStoreDocumentPayload,
11
+ Server
12
+ } from '@hocuspocus/server'
13
+ import { IncomingHttpHeaders } from 'http'
14
+ import * as Y from 'yjs'
15
+
16
+ import { CuboBackendApi } from '..'
17
+ import { DirectConnection } from './types'
18
+
19
+ export * from './types'
20
+
21
+ export class CuboCrdt<T extends CuboApiEntitiesMap<T>> {
22
+ public server: Server
23
+ private documentPrefix = 'cubo'
24
+ private defaultCrdtFieldAlias = 'crdt_data'
25
+
26
+ constructor(public api: CuboBackendApi<T>) {
27
+ this.init()
28
+ }
29
+
30
+ private init() {
31
+ const { onAuthenticate, onLoadDocument, onChange, onStoreDocument, afterLoadDocument, connected, ...config } = this.api.options.crdt
32
+
33
+ this.server = new Server({
34
+ port: config.port,
35
+ extensions: [
36
+ new Database({
37
+ fetch: async ({ documentName, context, document, instance, requestHeaders, requestParameters, socketId, connectionConfig }) => {
38
+ try {
39
+ const [prefix] = documentName.split('.')
40
+ if (prefix !== this.documentPrefix) {
41
+ return null
42
+ }
43
+
44
+ const { entityAlias, id } = this.parseDocumentName(documentName)
45
+
46
+ console.log('fetch', entityAlias, id)
47
+ if (id <= 0) {
48
+ return null
49
+ }
50
+
51
+ const entity = await this.api.helpers.getEntityByAlias(entityAlias as keyof T)
52
+ if (!entity) {
53
+ throw new Error(`Entity not found: "${entityAlias}"`)
54
+ }
55
+
56
+ const isSupported = this.isCrdtSupported(entity)
57
+ if (!isSupported) {
58
+ throw new Error(`Entity "${entityAlias}" does not support CRDT`)
59
+ }
60
+
61
+ const fieldKey = this.getCrdtField(entity)?.alias
62
+
63
+ if (!fieldKey) {
64
+ return null
65
+ }
66
+
67
+ const db = await this.api.helpers.getConnection('write')
68
+
69
+ const [data] = await db.connection.query(`SELECT ${fieldKey} from ${entityAlias} where id=:id`, {
70
+ type: QueryTypes.SELECT,
71
+ replacements: {
72
+ id
73
+ }
74
+ })
75
+
76
+ if (!data) {
77
+ throw new Error(`Entity "${entityAlias}" with id=${id} not found`)
78
+ }
79
+
80
+ const raw = (data as any)[fieldKey]
81
+ // console.log('raw', raw)
82
+
83
+ if (!raw) {
84
+ return null
85
+ }
86
+
87
+ return raw
88
+ } catch (error) {
89
+ console.log('error', error)
90
+ }
91
+ },
92
+ store: async ({ documentName, document, state, context }) => {
93
+ const [prefix] = documentName.split('.')
94
+ if (prefix !== this.documentPrefix) {
95
+ return null
96
+ }
97
+
98
+ const { entityAlias, id } = this.parseDocumentName(documentName)
99
+
100
+ if (id <= 0) {
101
+ return
102
+ }
103
+
104
+ const entity = await this.api.helpers.getEntityByAlias(entityAlias as keyof T)
105
+ if (!entity) {
106
+ throw new Error(`Entity not found: "${entityAlias}"`)
107
+ }
108
+
109
+ const isSupported = this.isCrdtSupported(entity)
110
+ if (!isSupported) {
111
+ throw new Error(`Entity "${entityAlias}" does not support CRDT`)
112
+ }
113
+
114
+ const entityMap = document.getMap(entityAlias)
115
+ const body: Record<string, any> = {}
116
+
117
+ const fieldKey = this.getCrdtField(entity)?.alias
118
+
119
+ for (const [key, value] of entityMap.entries()) {
120
+ if (key === fieldKey) {
121
+ continue
122
+ }
123
+
124
+ body[key] = value
125
+ }
126
+
127
+ // console.log('state', state)
128
+
129
+ body[fieldKey] = state
130
+
131
+ body.modified_at = new Date().toISOString()
132
+
133
+ await this.api.updateOne(
134
+ entityAlias as Extract<keyof T, string>,
135
+ {
136
+ query: { id },
137
+ body
138
+ },
139
+ {
140
+ crdt: false,
141
+ extra: {
142
+ auth: context.auth
143
+ },
144
+ excludeHooks: [],
145
+ queryOptions: { withDeleted: true }
146
+ }
147
+ )
148
+ }
149
+ })
150
+ ],
151
+ onAuthenticate: async (data) => {
152
+ try {
153
+ return onAuthenticate?.(this.api, data)
154
+ } catch (err) {
155
+ throw new Error('Ошибка авторизации: ' + err)
156
+ }
157
+ },
158
+ onLoadDocument: async (data) => {
159
+ const { documentName } = data
160
+
161
+ const [prefix] = documentName.split('.')
162
+ if (prefix === this.documentPrefix) {
163
+ const { entityAlias, id } = this.parseDocumentName(documentName)
164
+
165
+ if (entityAlias) {
166
+ const augmentation = this.api.options.augmentations?.[entityAlias]
167
+
168
+ if (augmentation?.onLoadDocument) {
169
+ await augmentation.onLoadDocument(data)
170
+ }
171
+ }
172
+ }
173
+
174
+ return onLoadDocument?.(this.api, data)
175
+ },
176
+ afterLoadDocument: async (data) => {
177
+ const { documentName, document, instance } = data
178
+
179
+ return afterLoadDocument?.(this.api, data)
180
+ },
181
+ connected: async (data) => {
182
+ const { documentName, instance } = data
183
+
184
+ const [prefix] = documentName?.split('.')
185
+ if (prefix === this.documentPrefix) {
186
+ const document = instance.documents.get(documentName)
187
+
188
+ const { entityAlias, id } = this.parseDocumentName(documentName)
189
+
190
+ if (entityAlias && document) {
191
+ const entityMap = document.getMap(entityAlias)
192
+ const id = entityMap.get('id')
193
+
194
+ if (!id) {
195
+ document.connections.forEach(({ connection }) => {
196
+ connection.close({
197
+ reason: 'Document not loaded',
198
+ code: 500
199
+ })
200
+ })
201
+ instance.unloadDocument(document)
202
+ }
203
+ }
204
+ }
205
+
206
+ return connected?.(this.api, data)
207
+ },
208
+ onChange: async (data) => {
209
+ const { documentName } = data
210
+
211
+ const [prefix] = documentName.split('.')
212
+ if (prefix === this.documentPrefix) {
213
+ const { entityAlias, id } = this.parseDocumentName(documentName)
214
+
215
+ if (entityAlias) {
216
+ const augmentation = this.api.options.augmentations?.[entityAlias]
217
+
218
+ if (augmentation?.onChangeDocument) {
219
+ await augmentation.onChangeDocument(data)
220
+ }
221
+ }
222
+ }
223
+
224
+ return onChange?.(this.api, data)
225
+ },
226
+ onStoreDocument: async (data) => {
227
+ const { documentName } = data
228
+
229
+ const [prefix] = documentName.split('.')
230
+ if (prefix === this.documentPrefix) {
231
+ const { entityAlias, id } = this.parseDocumentName(documentName)
232
+
233
+ if (entityAlias) {
234
+ const augmentation = this.api.options.augmentations?.[entityAlias]
235
+
236
+ if (augmentation?.onStoreDocument) {
237
+ await augmentation.onStoreDocument(data)
238
+ }
239
+ }
240
+ }
241
+
242
+ return onStoreDocument?.(this.api, data)
243
+ },
244
+
245
+ ...config
246
+ })
247
+ }
248
+
249
+ public async listen() {
250
+ await this.server.listen()
251
+ }
252
+
253
+ private parseDocumentName(documentName: string) {
254
+ const parts = documentName.split('.')
255
+
256
+ const [prefix, entityAlias, idRaw] = parts
257
+
258
+ if (prefix !== this.documentPrefix) {
259
+ throw new Error(`Invalid prefix: "${prefix}", expected "${this.documentPrefix}"`)
260
+ }
261
+
262
+ if (!entityAlias) {
263
+ throw new Error(`Missing entity alias in documentName: "${documentName}"`)
264
+ }
265
+
266
+ const id = Number(idRaw)
267
+ if (Number.isNaN(id)) {
268
+ throw new Error(`Invalid id in documentName: "${documentName}"`)
269
+ }
270
+
271
+ return { entityAlias: entityAlias as Extract<keyof T, string>, id }
272
+ }
273
+
274
+ public createDocumentName(entityAlias: string, id: number | string, prefix?: string) {
275
+ const parts = [prefix || this.documentPrefix, entityAlias, id]
276
+
277
+ return parts.join('.')
278
+ }
279
+
280
+ public isCrdtSupported(entity: CuboEntity) {
281
+ const hasCrdtField = entity.fields.some((f) => f.alias === this.api.options.crdt?.fieldAlias || this.defaultCrdtFieldAlias)
282
+
283
+ return hasCrdtField
284
+ }
285
+
286
+ public getCrdtField(entity: CuboEntity) {
287
+ const crdtField = entity.fields.find((f) => f.alias === this.api.options.crdt?.fieldAlias || this.defaultCrdtFieldAlias)
288
+
289
+ return crdtField
290
+ }
291
+
292
+ public getDocumentByEntity(entityAlias: string, id: number, requestHeaders: IncomingHttpHeaders) {
293
+ const documentName = this.createDocumentName(entityAlias, id)
294
+ const fakeSocketId = 'api-' + crypto.randomUUID()
295
+
296
+ return this.server.hocuspocus.createDocument(documentName, { headers: requestHeaders, url: '/collaboration' }, fakeSocketId, {
297
+ readOnly: false,
298
+ isAuthenticated: false
299
+ })
300
+ }
301
+
302
+ public async openDirectConnection(entityAlias: string, id: number, context?: any) {
303
+ const documentName = this.createDocumentName(entityAlias, id)
304
+ if (!documentName) {
305
+ return
306
+ }
307
+
308
+ return this.server.hocuspocus.openDirectConnection(documentName, context)
309
+ }
310
+
311
+ public async connectionDisconnect(connection: DirectConnection) {
312
+ const { document, instance, context } = connection
313
+
314
+ if (document) {
315
+ document.removeDirectConnection()
316
+
317
+ await instance.storeDocumentHooks(document, {
318
+ clientsCount: document.getConnectionsCount(),
319
+ context: context,
320
+ document: document,
321
+ documentName: document.name,
322
+ instance: instance,
323
+ requestHeaders: {},
324
+ requestParameters: new URLSearchParams(),
325
+ socketId: 'server'
326
+ })
327
+
328
+ // // If the direct connection was the only connection to the document
329
+ // // then we should trigger the onDisconnect hook for
330
+ // // this doc and unload the document
331
+ // if (document.getConnectionsCount() === 0) {
332
+ // await instance.hooks('onDisconnect', {
333
+ // instance: instance,
334
+ // clientsCount: document.getConnectionsCount(),
335
+ // context: context,
336
+ // document: document,
337
+ // socketId: 'server',
338
+ // documentName: document.name,
339
+ // requestHeaders: {},
340
+ // requestParameters: new URLSearchParams()
341
+ // })
342
+
343
+ // await instance.unloadDocument(document)
344
+ // }
345
+
346
+ connection.document = null
347
+ }
348
+ }
349
+
350
+ public async updateDocument(params: { entityAlias: string; id: number; entity: CuboEntity }, dto: Record<string, any>, context?: any) {
351
+ const { entityAlias, id, entity } = params
352
+
353
+ const connection = await this.openDirectConnection(entityAlias, id, context)
354
+ if (!connection) {
355
+ return
356
+ }
357
+
358
+ const { document } = connection
359
+ if (!document) {
360
+ return
361
+ }
362
+
363
+ const entityMap = document.getMap(entityAlias)
364
+
365
+ const fieldKey = this.getCrdtField(entity)?.alias
366
+
367
+ document.transact(() => {
368
+ for (const [key, value] of Object.entries(dto)) {
369
+ if (key === fieldKey) {
370
+ continue
371
+ }
372
+
373
+ let preparedValue = value
374
+
375
+ if (value instanceof Date) {
376
+ preparedValue = value.toISOString()
377
+ }
378
+
379
+ entityMap.set(key, preparedValue)
380
+ }
381
+ })
382
+
383
+ return this.connectionDisconnect(connection)
384
+ }
385
+
386
+ public async createState(params: { entityAlias: string; id: number; entity: CuboEntity }, dto: Record<string, any>) {
387
+ const { entityAlias, id, entity } = params
388
+
389
+ const document = new Y.Doc()
390
+ if (!document) {
391
+ return
392
+ }
393
+
394
+ const entityMap = document.getMap(entityAlias)
395
+
396
+ const fieldKey = this.getCrdtField(entity)?.alias
397
+
398
+ document.transact(() => {
399
+ for (const [key, value] of Object.entries(dto)) {
400
+ if (key === fieldKey) {
401
+ continue
402
+ }
403
+
404
+ let preparedValue = value
405
+
406
+ if (value instanceof Date) {
407
+ preparedValue = value.toISOString()
408
+ }
409
+
410
+ entityMap.set(key, preparedValue)
411
+ }
412
+ })
413
+
414
+ return Buffer.from(Y.encodeStateAsUpdate(document))
415
+ }
416
+ }
417
+
418
+ export { beforeSyncPayload, DirectConnection, Document, Hocuspocus, onChangePayload, onLoadDocumentPayload, onStoreDocumentPayload }
@@ -0,0 +1,34 @@
1
+ import { CuboApiEntitiesMap } from '@cuboapp/types'
2
+ import {
3
+ DirectConnection as DirectConnectionInterface,
4
+ Document,
5
+ Hocuspocus,
6
+ ServerConfiguration,
7
+ afterLoadDocumentPayload,
8
+ connectedPayload,
9
+ onAuthenticatePayload,
10
+ onChangePayload,
11
+ onLoadDocumentPayload,
12
+ onStoreDocumentPayload
13
+ } from '@hocuspocus/server'
14
+
15
+ import { CuboBackendApi } from '..'
16
+
17
+ export type CuboCrdtConfiguration<T extends CuboApiEntitiesMap<T>> = Omit<
18
+ Partial<ServerConfiguration>,
19
+ 'onAuthenticate' | 'onLoadDocument' | 'onChange' | 'onStoreDocument' | 'afterLoadDocument' | 'connected'
20
+ > & {
21
+ onAuthenticate?: (api: CuboBackendApi<T>, data: onAuthenticatePayload) => Promise<any>
22
+ onLoadDocument?: (api: CuboBackendApi<T>, data: onLoadDocumentPayload) => Promise<any>
23
+ onChange?: (api: CuboBackendApi<T>, data: onChangePayload) => Promise<any>
24
+ onStoreDocument?: (api: CuboBackendApi<T>, data: onStoreDocumentPayload) => Promise<any>
25
+ afterLoadDocument?: (api: CuboBackendApi<T>, data: afterLoadDocumentPayload) => Promise<any>
26
+ connected?: (api: CuboBackendApi<T>, data: connectedPayload) => Promise<any>
27
+ fieldAlias?: string
28
+ }
29
+
30
+ export type DirectConnection = {
31
+ document: Document | null
32
+ instance: Hocuspocus
33
+ context: any
34
+ } & DirectConnectionInterface
@@ -1,10 +1,10 @@
1
1
  import { CuboApiEntitiesMap, CuboEntity, CuboEntityField } from '@cuboapp/types'
2
2
  import { get, set, unset } from '@cuboapp/utils'
3
+ import { CUBO_ENTITY_FIELD_TYPE } from '@cuboapp/constants'
3
4
 
4
5
  import { CUBO_CRUD_DEFAULT_FIELDS } from '../constants'
5
6
  import { CuboCrudFindQuery, CuboCrudRequest } from '../types'
6
7
 
7
- import { CUBO_ENTITY_FIELD_TYPE } from '@cuboapp/constants'
8
8
  import { ApiHelpers } from '.'
9
9
 
10
10
  export class ApiHelpersConvert<T extends CuboApiEntitiesMap<T>> {
@@ -98,6 +98,7 @@ export class ApiHelpersConvert<T extends CuboApiEntitiesMap<T>> {
98
98
  console.warn('field type "file" is not implemented yet')
99
99
  return undefined
100
100
  case CUBO_ENTITY_FIELD_TYPE.JSON:
101
+ case CUBO_ENTITY_FIELD_TYPE.BINARY:
101
102
  return value
102
103
  case CUBO_ENTITY_FIELD_TYPE.NUMBER:
103
104
  return ![undefined, null].includes(value) ? parseFloat(value) : null
@@ -128,6 +128,7 @@ export class ApiHelpersData<T extends CuboApiEntitiesMap<T>> {
128
128
  errors.push(`"${key}": file field type is not already implemented`)
129
129
  break
130
130
  case CUBO_ENTITY_FIELD_TYPE.JSON:
131
+ case CUBO_ENTITY_FIELD_TYPE.BINARY:
131
132
  if (value === null) {
132
133
  prepared[key] = value
133
134
  } else if (Buffer.byteLength(JSON.stringify(value)) >= 65535 * 50) {
@@ -1,8 +1,8 @@
1
1
  import { CUBO_ENTITY_FIELD_TYPE } from '@cuboapp/constants'
2
- import { Database, createDatabase, type Options } from '@cuboapp/database'
2
+ import { Database, createDatabase } from '@cuboapp/database'
3
3
  import { CuboApiEntitiesMap, CuboEntity, CuboEntityField, CuboEntityFieldExtra, CuboEntityFieldType } from '@cuboapp/types'
4
4
 
5
- import { CuboBackendApi, CuboBackendApiDbConnectionType, CuboCrudFindQuery, CuboCrudRequest } from '../..'
5
+ import { CuboBackendApi } from '../..'
6
6
  import {
7
7
  CUBO_CRUD_DEFAULT_FIELDS,
8
8
  CUBO_CRUD_DEFAULT_PAGE_LIMIT,
@@ -11,6 +11,7 @@ import {
11
11
  CUBO_CRUD_QUERY_KEY_REGEX,
12
12
  CUBO_CRUD_QUERY_SYMBOL_NOT
13
13
  } from '../constants'
14
+ import { CuboBackendApiDbConnectionType, CuboCrudFindQuery, CuboCrudRequest } from '../types'
14
15
 
15
16
  import { ApiHelpersConvert } from './convert'
16
17
  import { ApiHelpersData } from './data'
@@ -65,7 +66,10 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>> {
65
66
  // condition parts
66
67
  switch (parts.length) {
67
68
  case 3:
68
- if (parts[0] !== CUBO_CRUD_QUERY_SYMBOL_NOT) {
69
+ if (parts[0] === 'btw') {
70
+ condition = parts[0]
71
+ value = `${parts[1]}:${parts[2]}`
72
+ } else if (parts[0] !== CUBO_CRUD_QUERY_SYMBOL_NOT) {
69
73
  throw new Error(`"${field.alias}": three-level value allow only "not" condition at first level (${value})`)
70
74
  } else if (!Object.values(CUBO_CRUD_QUERY_CONDITION).includes(parts[1])) {
71
75
  throw new Error(`"${field.alias}": incorrect condition "${parts[1]}" in second level (${value})`)
@@ -140,9 +144,18 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>> {
140
144
  } else {
141
145
  if ([CUBO_CRUD_QUERY_CONDITION.IN, CUBO_CRUD_QUERY_CONDITION.NIN].includes(condition)) {
142
146
  value = value.split(',').map(Number)
147
+ } else if (condition === CUBO_CRUD_QUERY_CONDITION.BTW) {
148
+ const [fromStr, toStr] = value.split(':')
149
+ const from = precision === 0 ? parseInt(fromStr) : parseFloat(fromStr)
150
+ const to = precision === 0 ? parseInt(toStr) : parseFloat(toStr)
151
+
152
+ if (isNaN(from) || isNaN(to)) {
153
+ throw new Error(`"${field.alias}": both values for "btw" must be valid numbers`)
154
+ }
155
+
156
+ value = precision > 0 ? `${from.toFixed(precision)}:${to.toFixed(precision)}` : `${from}:${to}`
143
157
  } else {
144
158
  const potentialValue = precision === 0 ? parseInt(value) : parseFloat(value)
145
-
146
159
  if (isNaN(potentialValue)) {
147
160
  throw new Error('"' + field.alias + '" must be number')
148
161
  } else {
@@ -211,7 +224,7 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>> {
211
224
  ): { conditions: string[]; replacements: Record<string, any> } {
212
225
  const conditions: string[] = []
213
226
  const replacements: Record<string, any> = {}
214
-
227
+ // console.log(field, condition, value)
215
228
  switch (condition) {
216
229
  case CUBO_CRUD_QUERY_CONDITION.ILIKE:
217
230
  conditions.push(`${field} ${is_not ? 'not ilike' : 'ilike'} :${replacement}`)
@@ -253,6 +266,22 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>> {
253
266
  conditions.push(`${field} ${is_not ? '>=' : '<='} :${replacement}`)
254
267
  replacements[replacement] = value
255
268
  break
269
+ case CUBO_CRUD_QUERY_CONDITION.BTW: {
270
+ const [from, to] = String(value).split(':')
271
+ const fromKey = `${replacement}_from`
272
+ const toKey = `${replacement}_to`
273
+
274
+ if (!from || !to) {
275
+ throw new Error(`Invalid "btw" value: "${value}"`)
276
+ }
277
+
278
+ const expr = `${field} >= :${fromKey} AND ${field} <= :${toKey}`
279
+ conditions.push(is_not ? `NOT (${expr})` : `(${expr})`)
280
+
281
+ replacements[fromKey] = from
282
+ replacements[toKey] = to
283
+ break
284
+ }
256
285
  case CUBO_CRUD_QUERY_CONDITION.EQ:
257
286
  default:
258
287
  if (value === null) {
@@ -287,75 +316,13 @@ export class ApiHelpers<T extends CuboApiEntitiesMap<T>> {
287
316
  }
288
317
 
289
318
  this.connecting[type] = new Promise(async (success) => {
290
- let database: string = ''
291
- let host: string = ''
292
- let port: number = 0
293
- let username: string = ''
294
- let password: string = ''
295
- let schema: string = ''
296
- let dialect: string = ''
297
- let dialectModule: any = undefined
298
-
299
- if (!this.api.options.db.options) {
300
- throw 'DB connection is not configured'
301
- }
302
-
303
- const dbConfig = this.api.options.db.options
304
-
305
- let cfg: Options = {}
306
- if (dbConfig) {
307
- if (dbConfig[type]) {
308
- cfg = dbConfig[type]
309
- } else {
310
- cfg = dbConfig as Options
311
- }
312
- }
313
-
314
- database = cfg.database as string
315
- dialectModule = cfg.dialectModule
316
- host = cfg.host as string
317
- port = cfg.port as number
318
- username = cfg.username as string
319
- password = cfg.password as string
320
- schema = cfg.schema as string
321
- dialect = cfg.dialect as string
322
-
323
- const config: Options = {
324
- host,
325
- port,
326
- username,
327
- password,
328
- database,
329
- dialect: dialect as any,
330
- timezone: '+03:00',
331
- logging: false,
332
- schema
333
- }
334
-
335
- if (dialectModule) {
336
- config.dialectModule = dialectModule
337
- }
338
-
339
- // cert for yandex cloud
340
- if (/yandexcloud/.test(config.host!)) {
341
- config.dialectOptions = {
342
- ...(config.dialectOptions || {}),
343
- ...{
344
- ssl: {
345
- ca: await fetch('https://storage.yandexcloud.net/cloud-certs/CA.pem').then((res) => res.text()),
346
- rejectUnauthorized: true,
347
- target_session_attrs: 'read-write',
348
- require: true
349
- }
350
- }
351
- }
352
- }
319
+ const { iaas, pk_key, pk_type, ...config } = this.api.options.db.options
353
320
 
354
- this.connections[type] = createDatabase(config)
321
+ this.connections[type] = createDatabase(config, JSON.parse(JSON.stringify({ iaas, pk_key, pk_type })))
355
322
  await this.connections[type].connect()
356
323
 
357
- if (dialect === 'postgres') {
358
- await this.connections[type].connection.query(`SET schema '${schema}';`)
324
+ if (config.dialect === 'postgres' && config.schema) {
325
+ await this.connections[type].connection.query(`SET schema '${config.schema}';`)
359
326
  }
360
327
 
361
328
  success(this.connections[type])