@companix/xeo-server 0.0.2

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.
Files changed (72) hide show
  1. package/.eslintrc +54 -0
  2. package/dist/common/decorators.d.ts +3 -0
  3. package/dist/common/decorators.js +14 -0
  4. package/dist/common/decorators.js.map +1 -0
  5. package/dist/common/index.d.ts +3 -0
  6. package/dist/common/index.js +20 -0
  7. package/dist/common/index.js.map +1 -0
  8. package/dist/common/tokens.d.ts +3 -0
  9. package/dist/common/tokens.js +14 -0
  10. package/dist/common/tokens.js.map +1 -0
  11. package/dist/common/utils.d.ts +2 -0
  12. package/dist/common/utils.js +19 -0
  13. package/dist/common/utils.js.map +1 -0
  14. package/dist/constants.d.ts +4 -0
  15. package/dist/constants.js +8 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/driver.module.d.ts +7 -0
  18. package/dist/driver.module.js +41 -0
  19. package/dist/driver.module.js.map +1 -0
  20. package/dist/drivers/collection.driver.d.ts +21 -0
  21. package/dist/drivers/collection.driver.js +101 -0
  22. package/dist/drivers/collection.driver.js.map +1 -0
  23. package/dist/drivers/table.driver.d.ts +13 -0
  24. package/dist/drivers/table.driver.js +79 -0
  25. package/dist/drivers/table.driver.js.map +1 -0
  26. package/dist/factories/definitions.factory.d.ts +11 -0
  27. package/dist/factories/definitions.factory.js +116 -0
  28. package/dist/factories/definitions.factory.js.map +1 -0
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.js +20 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/mongoose-options.interface.d.ts +12 -0
  33. package/dist/mongoose-options.interface.js +3 -0
  34. package/dist/mongoose-options.interface.js.map +1 -0
  35. package/dist/mongoose.module.d.ts +11 -0
  36. package/dist/mongoose.module.js +108 -0
  37. package/dist/mongoose.module.js.map +1 -0
  38. package/dist/storages/data-source.d.ts +10 -0
  39. package/dist/storages/data-source.js +34 -0
  40. package/dist/storages/data-source.js.map +1 -0
  41. package/jest.cases.config.cjs +14 -0
  42. package/lib/common/decorators.ts +17 -0
  43. package/lib/common/index.ts +3 -0
  44. package/lib/common/tokens.ts +17 -0
  45. package/lib/common/utils.ts +29 -0
  46. package/lib/constants.ts +4 -0
  47. package/lib/driver.module.ts +37 -0
  48. package/lib/drivers/collection.driver.ts +157 -0
  49. package/lib/drivers/table.driver.ts +109 -0
  50. package/lib/factories/definitions.factory.ts +129 -0
  51. package/lib/index.ts +3 -0
  52. package/lib/mongoose-options.interface.ts +19 -0
  53. package/lib/mongoose.module.ts +95 -0
  54. package/lib/storages/data-source.ts +37 -0
  55. package/package.json +42 -0
  56. package/tests/app/bootstrap.ts +16 -0
  57. package/tests/app/db.ts +22 -0
  58. package/tests/app/filters.ts +25 -0
  59. package/tests/app/helpers/is-one-of.ts +58 -0
  60. package/tests/app/main.ts +3 -0
  61. package/tests/app/module/app.controller.ts +67 -0
  62. package/tests/app/module/app.dto.ts +394 -0
  63. package/tests/app/module/app.module.ts +12 -0
  64. package/tests/app/module/app.service.ts +76 -0
  65. package/tests/app/root.module.ts +12 -0
  66. package/tests/globals.d.ts +6 -0
  67. package/tests/integrations/cases.test.ts +154 -0
  68. package/tests/integrations/custom.test.ts +69 -0
  69. package/tests/unit/definitions.test.ts +31 -0
  70. package/tsconfig.build.json +9 -0
  71. package/tsconfig.json +17 -0
  72. package/tsconfig.test-app.json +10 -0
@@ -0,0 +1,129 @@
1
+ import {
2
+ CollectionScheme,
3
+ DataScheme,
4
+ ModelProperties,
5
+ PropertyRelation,
6
+ PropertyMetadata,
7
+ ModelScheme
8
+ } from '@companix/xeo-scheme'
9
+ import type { SchemaDefinition, SchemaDefinitionProperty } from 'mongoose'
10
+ import { Schema } from 'mongoose'
11
+
12
+ const TypesTranslator = {
13
+ string: String,
14
+ number: Number,
15
+ json: Object,
16
+ boolean: Boolean
17
+ }
18
+
19
+ export class DefinitionsFactory<T extends CollectionScheme> {
20
+ constructor(private dataScheme: DataScheme<T>) {}
21
+
22
+ createForCollection(name: keyof T): SchemaDefinition {
23
+ return this.createForScheme(this.dataScheme.models[this.dataScheme.collections[name].name].scheme)
24
+ }
25
+
26
+ createForScheme(scheme: ModelScheme): SchemaDefinition {
27
+ return {
28
+ [scheme.identifier.propertyKey]: {
29
+ index: true,
30
+ type: TypesTranslator[scheme.identifier.options.type],
31
+ unique: true
32
+ },
33
+ ...this.createDefinitionScheme(scheme)
34
+ }
35
+ }
36
+
37
+ createDefinitionScheme({ properties, references }: ModelProperties): SchemaDefinition {
38
+ const map: SchemaDefinition = {}
39
+
40
+ for (const property of properties) {
41
+ map[property.propertyKey] = this.getPropertyDefinition(property)
42
+ }
43
+
44
+ for (const reference of references) {
45
+ map[reference.propertyKey] = this.getRelationDefinition(reference)
46
+ }
47
+
48
+ return map
49
+ }
50
+
51
+ private getPropertyDefinition({ primitive }: PropertyMetadata): SchemaDefinitionProperty {
52
+ switch (primitive.type) {
53
+ case 'embedded': {
54
+ return {
55
+ _id: false, // prevent creating _id (без указания mongodb создавал бы _id для всех вложенных объектов)
56
+ type: this.createDefinitionScheme(primitive),
57
+ required: true
58
+ }
59
+ }
60
+ case 'array': {
61
+ return {
62
+ type: [TypesTranslator[primitive.itemType]],
63
+ required: true
64
+ }
65
+ }
66
+ case 'literal': {
67
+ return {
68
+ type: Schema.Types.Mixed, // сузить до String и Number
69
+ enum: primitive.values,
70
+ required: !primitive.nullable
71
+ }
72
+ }
73
+ case 'enum': {
74
+ return {
75
+ type: [String], // может быть и Number
76
+ enum: primitive.values,
77
+ required: !primitive.nullable
78
+ }
79
+ }
80
+ default: {
81
+ return {
82
+ type: TypesTranslator[primitive.type]
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ private getRelationDefinition(reference: PropertyRelation): SchemaDefinitionProperty {
89
+ const refModel = this.dataScheme.metadata.getModelSchemaByTarget(reference.referenceModel)
90
+
91
+ switch (reference.refType) {
92
+ case 'reference-to': {
93
+ return {
94
+ type: TypesTranslator[refModel.identifier.options.type],
95
+ required: (reference.options?.onRefDeleting ?? 'restrict') === 'restrict'
96
+ }
97
+ }
98
+ case 'reference-set': {
99
+ return {
100
+ type: [TypesTranslator[refModel.identifier.options.type]]
101
+ }
102
+ }
103
+ case 'belongs-to': {
104
+ return {
105
+ type: TypesTranslator[refModel.identifier.options.type],
106
+ required: true
107
+ }
108
+ }
109
+ case 'has-many': {
110
+ return {
111
+ type: [TypesTranslator[refModel.identifier.options.type]],
112
+ required: true
113
+ }
114
+ }
115
+ case 'owner': {
116
+ return {
117
+ type: TypesTranslator[refModel.identifier.options.type],
118
+ required: true
119
+ }
120
+ }
121
+ case 'owner-fallback': {
122
+ return {
123
+ type: TypesTranslator[refModel.identifier.options.type],
124
+ refType: 'owner-fallback'
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
package/lib/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './common'
2
+ export * from './drivers/table.driver'
3
+ export * from './driver.module'
@@ -0,0 +1,19 @@
1
+ import { ConnectOptions, Connection, MongooseError } from 'mongoose'
2
+
3
+ /**
4
+ * @publicApi
5
+ */
6
+ export interface MongooseModuleOptions extends ConnectOptions {
7
+ uri?: string
8
+ retryAttempts?: number
9
+ retryDelay?: number
10
+ connectionName?: string
11
+ connectionFactory?: (connection: any, name: string) => any
12
+ connectionErrorFactory?: (error: MongooseError) => MongooseError
13
+ lazyConnection?: boolean
14
+ onConnectionCreate?: (connection: Connection) => void
15
+ /**
16
+ * If `true`, will show verbose error messages on each connection retry.
17
+ */
18
+ verboseRetryLog?: boolean
19
+ }
@@ -0,0 +1,95 @@
1
+ import * as mongoose from 'mongoose'
2
+ import { DynamicModule, Global, Inject, Module, OnApplicationShutdown } from '@nestjs/common'
3
+ import { ModuleRef } from '@nestjs/core'
4
+ import { ConnectOptions, Connection } from 'mongoose'
5
+ import { defer, lastValueFrom } from 'rxjs'
6
+ import { catchError } from 'rxjs/operators'
7
+ import { MONGOOSE_CONNECTION_NAME } from './constants'
8
+ import { getConnectionToken, handleRetry } from './common'
9
+ import { MongooseModuleOptions } from './mongoose-options.interface'
10
+
11
+ @Global()
12
+ @Module({})
13
+ export class MongooseCoreModule implements OnApplicationShutdown {
14
+ constructor(
15
+ @Inject(MONGOOSE_CONNECTION_NAME) private readonly connectionName: string,
16
+ private readonly moduleRef: ModuleRef
17
+ ) {}
18
+
19
+ static forRoot(uri: string, options: MongooseModuleOptions = {}): DynamicModule {
20
+ const {
21
+ retryAttempts,
22
+ retryDelay,
23
+ connectionName,
24
+ connectionFactory,
25
+ connectionErrorFactory,
26
+ lazyConnection,
27
+ onConnectionCreate,
28
+ verboseRetryLog,
29
+ ...mongooseOptions
30
+ } = options
31
+
32
+ const mongooseConnectionFactory = connectionFactory || ((connection) => connection)
33
+
34
+ const mongooseConnectionError = connectionErrorFactory || ((error) => error)
35
+
36
+ const mongooseConnectionName = getConnectionToken(connectionName)
37
+
38
+ const mongooseConnectionNameProvider = {
39
+ provide: MONGOOSE_CONNECTION_NAME,
40
+ useValue: mongooseConnectionName
41
+ }
42
+
43
+ const connectionProvider = {
44
+ provide: mongooseConnectionName,
45
+ useFactory: async (): Promise<any> =>
46
+ await lastValueFrom(
47
+ defer(async () =>
48
+ mongooseConnectionFactory(
49
+ await this.createMongooseConnection(uri, mongooseOptions, {
50
+ lazyConnection,
51
+ onConnectionCreate
52
+ }),
53
+ mongooseConnectionName
54
+ )
55
+ ).pipe(
56
+ handleRetry(retryAttempts, retryDelay, verboseRetryLog),
57
+ catchError((error) => {
58
+ throw mongooseConnectionError(error)
59
+ })
60
+ )
61
+ )
62
+ }
63
+ return {
64
+ module: MongooseCoreModule,
65
+ providers: [connectionProvider, mongooseConnectionNameProvider],
66
+ exports: [connectionProvider]
67
+ }
68
+ }
69
+
70
+ private static async createMongooseConnection(
71
+ uri: string,
72
+ mongooseOptions: ConnectOptions,
73
+ factoryOptions: {
74
+ lazyConnection?: boolean
75
+ onConnectionCreate?: MongooseModuleOptions['onConnectionCreate']
76
+ }
77
+ ): Promise<Connection> {
78
+ const connection = mongoose.createConnection(uri, mongooseOptions)
79
+
80
+ if (factoryOptions?.lazyConnection) {
81
+ return connection
82
+ }
83
+
84
+ factoryOptions?.onConnectionCreate?.(connection)
85
+
86
+ return connection.asPromise()
87
+ }
88
+
89
+ async onApplicationShutdown() {
90
+ const connection = this.moduleRef.get<any>(this.connectionName)
91
+ if (connection) {
92
+ await connection.close()
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,37 @@
1
+ import { CollectionScheme, DataScheme, DataSource } from '@companix/xeo-scheme'
2
+ import { Connection } from 'mongoose'
3
+ import { createMongoDriver } from '../drivers/collection.driver'
4
+ import { DATA_SOURCE_TOKEN } from '../constants'
5
+
6
+ class DataSourceStorageService {
7
+ private dataSource: DataSource<CollectionScheme> | null = null
8
+ private dataScheme: DataScheme<CollectionScheme> | null = null
9
+
10
+ getProviderToken(dataScheme: DataScheme<CollectionScheme>) {
11
+ if (this.dataScheme === null) {
12
+ this.dataScheme = dataScheme
13
+ }
14
+
15
+ if (this.dataScheme !== dataScheme) {
16
+ throw new Error(`[MongoDriver] driver cannot work with several dataSchemes`)
17
+ }
18
+
19
+ return DATA_SOURCE_TOKEN
20
+ }
21
+
22
+ getSource(dataScheme: DataScheme<CollectionScheme>, connection: Connection) {
23
+ if (this.dataScheme !== dataScheme) {
24
+ throw new Error(`[MongoDriver] driver cannot work with several dataSchemes`)
25
+ }
26
+
27
+ if (!this.dataSource) {
28
+ this.dataSource = new DataSource(dataScheme, {
29
+ createDriver: createMongoDriver(connection)
30
+ })
31
+ }
32
+
33
+ return this.dataSource
34
+ }
35
+ }
36
+
37
+ export const DataSourceStorage = new DataSourceStorageService()
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@companix/xeo-server",
3
+ "version": "0.0.2",
4
+ "main": "dist/index.js",
5
+ "types": "./dist/index.d.ts",
6
+ "scripts": {
7
+ "echo": "echo \"@companix/xeo-server\"",
8
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
9
+ "test:unit": "tsc --noEmit && tsx ./tests/unit/definitions.ts",
10
+ "test:app": "nest start --watch --builder tsc --path tsconfig.test-app.json --sourceRoot tests/app --entryFile main",
11
+ "test:custom": "nest start --watch --builder tsc --path tsconfig.test-app.json --sourceRoot tests --entryFile integrations/custom.test",
12
+ "test:cases": "jest --config ./jest.cases.config.cjs --runInBand"
13
+ },
14
+ "devDependencies": {
15
+ "@nestjs/common": "^11.1.17",
16
+ "@nestjs/core": "^11.1.17",
17
+ "@nestjs/platform-express": "^11.1.12",
18
+ "@types/jest": "^30.0.0",
19
+ "class-transformer": "^0.5.1",
20
+ "class-validator": "^0.15.1",
21
+ "eslint-config-react-app": "^7.0.1",
22
+ "eslint-plugin-import": "^2.29.1",
23
+ "eslint-plugin-react": "^7.34.3",
24
+ "eslint-plugin-react-hooks": "^4.6.2",
25
+ "eslint-webpack-plugin": "^4.2.0",
26
+ "jest": "^30.3.0",
27
+ "mongoose": "^9.3.3",
28
+ "rxjs": "^7.8.2",
29
+ "ts-jest": "^29.4.6",
30
+ "zod": "^4.3.6"
31
+ },
32
+ "dependencies": {
33
+ "@companix/utils-js": "*",
34
+ "@companix/xeo-scheme": "*"
35
+ },
36
+ "peerDependencies": {
37
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
38
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
39
+ "mongoose": "^9.0.0",
40
+ "rxjs": "^7.0.0"
41
+ }
42
+ }
@@ -0,0 +1,16 @@
1
+ import { NestFactory } from '@nestjs/core'
2
+ import { RootModule } from './root.module'
3
+ import { ValidationPipe } from '@nestjs/common'
4
+ import { AllExceptionsFilter } from './filters'
5
+
6
+ export const bootstrap = async (port = 3111) => {
7
+ const app = await NestFactory.create(RootModule)
8
+
9
+ app.useGlobalFilters(new AllExceptionsFilter())
10
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }))
11
+
12
+ await app.listen(port)
13
+ console.log(`Application is running on: http://localhost:${port}/app`)
14
+
15
+ return app
16
+ }
@@ -0,0 +1,22 @@
1
+ import { MongooseModuleOptions } from '../../lib/mongoose-options.interface'
2
+
3
+ const DB_NAME = 'xeo-test'
4
+ const DB_PORT = 27017
5
+ const DB_AUTH = 'admin'
6
+ const DB_PASS = 'example'
7
+ const DB_HOST = '127.0.0.1'
8
+ const DB_CERT = ''
9
+ const DB_USER = 'root'
10
+
11
+ export const getMongoConnectionURL = (): string => {
12
+ return `mongodb://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/`
13
+ }
14
+
15
+ export const getMongoConnectionOptions = (): MongooseModuleOptions => {
16
+ return {
17
+ tls: DB_CERT ? true : false,
18
+ tlsCAFile: DB_CERT,
19
+ dbName: DB_NAME,
20
+ authSource: DB_AUTH
21
+ }
22
+ }
@@ -0,0 +1,25 @@
1
+ import { CoreError } from '@companix/xeo-scheme'
2
+ import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'
3
+
4
+ @Catch()
5
+ export class AllExceptionsFilter implements ExceptionFilter {
6
+ catch(exception: any, host: ArgumentsHost) {
7
+ const ctx = host.switchToHttp()
8
+ const response = ctx.getResponse()
9
+
10
+ // core conflicts
11
+ if (exception instanceof CoreError) {
12
+ console.log('core exception:', exception)
13
+ response.status(HttpStatus.BAD_REQUEST).json(exception)
14
+ return
15
+ }
16
+
17
+ // internal server errors
18
+ if (exception instanceof HttpException) {
19
+ response.status(exception.getStatus()).json(exception.getResponse())
20
+ return
21
+ }
22
+
23
+ response.status(HttpStatus.INTERNAL_SERVER_ERROR).json(exception?.response)
24
+ }
25
+ }
@@ -0,0 +1,58 @@
1
+ import { plainToInstance } from 'class-transformer'
2
+ import {
3
+ ValidatorConstraint,
4
+ ValidatorConstraintInterface,
5
+ ValidationArguments,
6
+ validate,
7
+ ValidationOptions,
8
+ registerDecorator
9
+ } from 'class-validator'
10
+
11
+ @ValidatorConstraint({ async: true })
12
+ export class IsOneOfDtoConstraint implements ValidatorConstraintInterface {
13
+ async validate(value: any, args: ValidationArguments) {
14
+ const [dtos, discriptor] = args.constraints as [any[], string]
15
+
16
+ if (!value) {
17
+ return false
18
+ }
19
+
20
+ const dto = dtos.find((some) => {
21
+ const classDescriptor = new some()[discriptor]
22
+
23
+ if (classDescriptor) {
24
+ return classDescriptor === value[discriptor]
25
+ }
26
+
27
+ console.warn('WARN: Set descriptor value to:', some)
28
+ })
29
+
30
+ if (dto) {
31
+ const errors = await validate(plainToInstance(dto, value))
32
+
33
+ if (errors.length === 0) {
34
+ return true
35
+ }
36
+
37
+ console.error('ERRORs: ', dto, errors)
38
+ }
39
+
40
+ return false
41
+ }
42
+
43
+ defaultMessage(args: ValidationArguments) {
44
+ return 'Data does not match'
45
+ }
46
+ }
47
+
48
+ export function IsOneOf(dtos: [Function[], string], validationOptions?: ValidationOptions) {
49
+ return function (object: Object, propertyName: string) {
50
+ registerDecorator({
51
+ target: object.constructor,
52
+ propertyName: propertyName,
53
+ options: validationOptions,
54
+ constraints: dtos,
55
+ validator: IsOneOfDtoConstraint
56
+ })
57
+ }
58
+ }
@@ -0,0 +1,3 @@
1
+ import { bootstrap } from './bootstrap'
2
+
3
+ bootstrap()
@@ -0,0 +1,67 @@
1
+ import { Body, Controller, Get, Post } from '@nestjs/common'
2
+ import {
3
+ CreateBankCardDto,
4
+ CreateBankDetailDto,
5
+ CreateDictionaryDto,
6
+ CreateOptionDto,
7
+ CreateRoleDto,
8
+ CreateScanDto,
9
+ CreateWorkerDto,
10
+ UpdateOptionDto
11
+ } from './app.dto'
12
+ import { AppService } from './app.service'
13
+
14
+ @Controller('app')
15
+ export class AppController {
16
+ constructor(private readonly appService: AppService) {}
17
+
18
+ @Post('addWorker')
19
+ async addWorker(@Body() dto: CreateWorkerDto) {
20
+ return this.appService.addWorker(dto)
21
+ }
22
+
23
+ @Post('addScan')
24
+ async addScan(@Body() dto: CreateScanDto) {
25
+ return this.appService.addScan(dto)
26
+ }
27
+
28
+ @Post('addBankCard')
29
+ async addBankCard(@Body() dto: CreateBankCardDto) {
30
+ return this.appService.addBankCard(dto)
31
+ }
32
+
33
+ @Post('addBankDetail')
34
+ async addBankDetail(@Body() dto: CreateBankDetailDto) {
35
+ return this.appService.addBankDetail(dto)
36
+ }
37
+
38
+ @Post('addRole')
39
+ async addRole(@Body() dto: CreateRoleDto) {
40
+ return this.appService.addRole(dto)
41
+ }
42
+
43
+ @Post('addDictionary')
44
+ async addDictionary(@Body() dto: CreateDictionaryDto) {
45
+ return this.appService.addDictionary(dto)
46
+ }
47
+
48
+ @Post('addOption')
49
+ async addOption(@Body() dto: CreateOptionDto) {
50
+ return this.appService.addOption(dto)
51
+ }
52
+
53
+ @Post('updateOption')
54
+ async updateOption(@Body() dto: UpdateOptionDto) {
55
+ return this.appService.updateOption(dto)
56
+ }
57
+
58
+ @Get()
59
+ async getState() {
60
+ return this.appService.getState()
61
+ }
62
+
63
+ @Get('tables')
64
+ async getTables() {
65
+ return this.appService.getTables()
66
+ }
67
+ }