@companix/xeo-server 0.0.2 → 0.0.3

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,8 +1,11 @@
1
1
  {
2
2
  "name": "@companix/xeo-server",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
+ "files": [
7
+ "dist"
8
+ ],
6
9
  "scripts": {
7
10
  "echo": "echo \"@companix/xeo-server\"",
8
11
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
package/.eslintrc DELETED
@@ -1,54 +0,0 @@
1
- {
2
- "env": {
3
- "node": true,
4
- "browser": true,
5
- "es6": true
6
- },
7
- "extends": [
8
- "plugin:react/recommended",
9
- "plugin:react-hooks/recommended",
10
- "react-app"
11
- ],
12
- "parser": "@typescript-eslint/parser",
13
- "plugins": ["react"],
14
- "parserOptions": {
15
- "sourceType": "module",
16
- "requireConfigFile": false,
17
- "ecmaFeatures": {
18
- "jsx": true
19
- }
20
- },
21
- "settings": {
22
- "react": {
23
- "version": "detect"
24
- }
25
- },
26
- "rules": {
27
- "no-var": "error",
28
- "typescript-eslint/typescript-estree": "off",
29
- "react-hooks/exhaustive-deps": "off",
30
- "react/react-in-jsx-scope": "off",
31
- "react/no-children-prop": "off",
32
- "react/display-name": "off",
33
- "jsx-a11y/aria-role": "off",
34
- "react/prop-types": "off",
35
- "react/jsx-no-target-blank": "off",
36
- "import/no-anonymous-default-export": "off",
37
- "import/no-extraneous-dependencies": [
38
- "error",
39
- {
40
- "devDependencies": true,
41
- "peerDependencies": false
42
- }
43
- ],
44
- "no-throw-literal": "off",
45
- "react/no-multi-comp": [2, { "ignoreStateless": true }],
46
- "no-restricted-syntax": [
47
- "error",
48
- {
49
- "selector": "CallExpression[arguments.length=1][callee.property.name='reduce']",
50
- "message": "Provide initialValue to .reduce()."
51
- }
52
- ]
53
- }
54
- }
@@ -1,14 +0,0 @@
1
- module.exports = {
2
- testEnvironment: 'node',
3
- roots: ['<rootDir>/tests/integrations'],
4
- testMatch: ['**/cases.test.ts'],
5
- moduleFileExtensions: ['ts', 'js', 'json'],
6
- transform: {
7
- '^.+\\.ts$': [
8
- 'ts-jest',
9
- {
10
- tsconfig: '<rootDir>/tsconfig.test-app.json'
11
- }
12
- ]
13
- }
14
- }
@@ -1,17 +0,0 @@
1
- import { Inject } from '@nestjs/common'
2
- import { getConnectionToken, getDataSourceToken } from './tokens'
3
- import { CollectionScheme, DataScheme } from '@companix/xeo-scheme'
4
-
5
- /**
6
- * @publicApi
7
- */
8
- export const InjectDataSource = (dataSource: DataScheme<CollectionScheme>) => {
9
- return Inject(getDataSourceToken(dataSource))
10
- }
11
-
12
- /**
13
- * @publicApi
14
- */
15
- export const InjectConnection = () => {
16
- return Inject(getConnectionToken())
17
- }
@@ -1,3 +0,0 @@
1
- export * from './decorators'
2
- export * from './tokens'
3
- export * from './utils'
@@ -1,17 +0,0 @@
1
- import { DEFAULT_DB_CONNECTION } from '../constants'
2
- import { CollectionScheme, DataScheme } from '@companix/xeo-scheme'
3
- import { DataSourceStorage } from '../storages/data-source'
4
-
5
- /**
6
- * @publicApi
7
- */
8
- export const getConnectionToken = (name?: string) => {
9
- return name && name !== DEFAULT_DB_CONNECTION ? `${name}Connection` : DEFAULT_DB_CONNECTION
10
- }
11
-
12
- /**
13
- * @publicApi
14
- */
15
- export const getDataSourceToken = (dataScheme: DataScheme<CollectionScheme>) => {
16
- return DataSourceStorage.getProviderToken(dataScheme)
17
- }
@@ -1,29 +0,0 @@
1
- import { Logger } from '@nestjs/common'
2
- import { Observable } from 'rxjs'
3
- import { delay, retryWhen, scan } from 'rxjs/operators'
4
-
5
- export const handleRetry = (retryAttempts = 9, retryDelay = 3000, verboseRetryLog = false) => {
6
- const logger = new Logger('MongooseModule')
7
-
8
- return <T>(source: Observable<T>) =>
9
- source.pipe(
10
- retryWhen((e) =>
11
- e.pipe(
12
- scan((errorCount, error) => {
13
- const verboseMessage = verboseRetryLog ? ` Message: ${error.message}.` : ''
14
- const retryMessage = retryAttempts > 0 ? ` Retrying (${errorCount + 1})...` : ''
15
-
16
- logger.error(
17
- ['Unable to connect to the database.', verboseMessage, retryMessage].join(''),
18
- error.stack
19
- )
20
- if (errorCount + 1 >= retryAttempts) {
21
- throw error
22
- }
23
- return errorCount + 1
24
- }, 0),
25
- delay(retryDelay)
26
- )
27
- )
28
- )
29
- }
package/lib/constants.ts DELETED
@@ -1,4 +0,0 @@
1
- export const DEFAULT_DB_CONNECTION = 'XeoDatabaseConnection'
2
- export const MONGOOSE_MODULE_OPTIONS = 'XeoMongooseModuleOptions'
3
- export const MONGOOSE_CONNECTION_NAME = 'XeoMongooseConnectionName'
4
- export const DATA_SOURCE_TOKEN = `XEO_DATA_SOURCE`
@@ -1,37 +0,0 @@
1
- import { Provider } from '@nestjs/common'
2
- import { DynamicModule, Module } from '@nestjs/common'
3
- import { getConnectionToken, getDataSourceToken } from './common/tokens'
4
- import { Connection } from 'mongoose'
5
- import { CollectionScheme, DataScheme, DataSource } from '@companix/xeo-scheme'
6
- import { MongooseCoreModule } from './mongoose.module'
7
- import { MongooseModuleOptions } from './mongoose-options.interface'
8
- import { DataSourceStorage } from './storages/data-source'
9
-
10
- /**
11
- * @publicApi
12
- */
13
- @Module({})
14
- export class MongooseDriverModule {
15
- static forRoot(uri: string, options: MongooseModuleOptions = {}): DynamicModule {
16
- return {
17
- module: MongooseDriverModule,
18
- imports: [MongooseCoreModule.forRoot(uri, options)]
19
- }
20
- }
21
-
22
- static forFeature(dataSource: DataScheme<CollectionScheme>, connectionName?: string): DynamicModule {
23
- const provider: Provider = {
24
- provide: getDataSourceToken(dataSource),
25
- useFactory: (connection: Connection): DataSource<CollectionScheme> => {
26
- return DataSourceStorage.getSource(dataSource, connection)
27
- },
28
- inject: [getConnectionToken(connectionName)]
29
- }
30
-
31
- return {
32
- module: MongooseDriverModule,
33
- providers: [provider],
34
- exports: [provider]
35
- }
36
- }
37
- }
@@ -1,157 +0,0 @@
1
- import {
2
- CollectionDriver,
3
- CollectionDriverParams,
4
- CollectionScheme,
5
- DataScheme
6
- } from '@companix/xeo-scheme'
7
- import { MongoRelationsTable } from './table.driver'
8
- import { Connection, Model, Schema } from 'mongoose'
9
- import { DefinitionsFactory } from '../factories/definitions.factory'
10
- import { Logger } from '@nestjs/common'
11
-
12
- interface DiscriminatedModels {
13
- [model: string]: {
14
- discriminatorKey: string
15
- models: {
16
- [value: string]: Model<any>
17
- }
18
- }
19
- }
20
-
21
- export class MongoCollectionDriver<T extends CollectionScheme> implements CollectionDriver {
22
- private collections: { [model: string]: Model<any> } = {}
23
- private discriminatedModels: DiscriminatedModels = {}
24
-
25
- public tables: MongoRelationsTable<T>
26
-
27
- constructor(private dataScheme: DataScheme<T>, private connection: Connection) {
28
- Logger.log('Driver bootstrap', 'MongoDriver')
29
-
30
- this.tables = new MongoRelationsTable(dataScheme, connection)
31
-
32
- const factory = new DefinitionsFactory(dataScheme)
33
-
34
- for (const name in dataScheme.collections) {
35
- const model = dataScheme.collections[name].name
36
- const scheme = dataScheme.models[model].scheme
37
-
38
- const baseDefinition = factory.createForScheme(scheme)
39
-
40
- if (scheme.type === 'base') {
41
- this.collections[model] = this.useModel(model, new Schema(baseDefinition))
42
- }
43
-
44
- if (scheme.type === 'discriminated') {
45
- this.collections[model] = this.useModel(
46
- model,
47
- new Schema(baseDefinition, {
48
- discriminatorKey: scheme.discriminatorKey
49
- })
50
- )
51
-
52
- this.discriminatedModels[model] = {
53
- discriminatorKey: scheme.discriminatorKey,
54
- models: {}
55
- }
56
-
57
- for (const discriminator of scheme.discriminators) {
58
- const discriminatedModel = this.collections[model].discriminator(
59
- discriminator.name,
60
- new Schema(factory.createDefinitionScheme(discriminator)),
61
- discriminator.value
62
- )
63
-
64
- this.discriminatedModels[model].models[discriminator.value] = discriminatedModel
65
- }
66
- }
67
- }
68
- }
69
-
70
- async getAll({ model }: CollectionDriverParams.Model) {
71
- return this.collections[model].find().lean().exec()
72
- }
73
-
74
- async get({ model, id }: CollectionDriverParams.Record) {
75
- return this.collections[model].findOne({ [this.getIdentifierKey(model)]: id }).lean()
76
- }
77
-
78
- async create({ model, data }: CollectionDriverParams.Create) {
79
- await new this.collections[model](data).save()
80
- }
81
-
82
- async remove({ model, id }: CollectionDriverParams.Record) {
83
- await this.collections[model].deleteOne({ [this.getIdentifierKey(model)]: id }).exec()
84
- }
85
-
86
- // при update для discriminated collection нельзя всегда использовать только this.collections[model]
87
- // нужно сначала получить документ по id, узнать его discriminatorKey (type), и если это дискриминатор, выполнять updateOne() через соответствующую discriminator model
88
- async update({ model, id, patches }: CollectionDriverParams.Update) {
89
- const identifierKey = this.getIdentifierKey(model)
90
- const collection = await this.getCollection({ model, id })
91
-
92
- const responses = await Promise.all(
93
- patches.map(async (patch) => {
94
- switch (patch.type) {
95
- case 'set': {
96
- return collection.updateOne(
97
- { [identifierKey]: id },
98
- { $set: { [patch.address]: patch.value } }
99
- )
100
- }
101
- case 'push': {
102
- return collection.updateOne(
103
- { [identifierKey]: id },
104
- { $push: { [patch.address]: { $each: patch.items } } }
105
- )
106
- }
107
- case 'pull': {
108
- return collection.updateOne(
109
- { [identifierKey]: id },
110
- { $pull: { [patch.address]: { $in: patch.items } } }
111
- )
112
- }
113
- }
114
- })
115
- )
116
-
117
- // check transaction
118
- for (const response of responses) {
119
- if (!response.acknowledged) {
120
- console.log('MongoDB Write Warning', { model, id, patches }, responses)
121
- }
122
- }
123
- }
124
-
125
- private async getCollection({ model, id }: CollectionDriverParams.Record) {
126
- if (this.discriminatedModels[model]) {
127
- const target = await this.get({ model, id })
128
- const discriminatorValue = target[this.discriminatedModels[model].discriminatorKey] as string
129
-
130
- return this.discriminatedModels[model].models[discriminatorValue]
131
- }
132
-
133
- return this.collections[model]
134
- }
135
-
136
- async exists({ model, id }: CollectionDriverParams.Record) {
137
- const result = await this.collections[model].exists({
138
- [this.getIdentifierKey(model)]: id
139
- })
140
-
141
- return result !== null
142
- }
143
-
144
- private getIdentifierKey(model: string) {
145
- return this.dataScheme.models[model].scheme.identifier.propertyKey
146
- }
147
-
148
- private useModel<T>(model: string, schema: Schema): Model<T> {
149
- return this.connection.models[model] ?? this.connection.model(model, schema)
150
- }
151
- }
152
-
153
- export const createMongoDriver = (connection: Connection) => {
154
- return <T extends CollectionScheme>(dataScheme: DataScheme<T>) => {
155
- return new MongoCollectionDriver(dataScheme, connection)
156
- }
157
- }
@@ -1,109 +0,0 @@
1
- import {
2
- CollectionScheme,
3
- DataScheme,
4
- IType,
5
- RelationRecord,
6
- RelationsTableInfo,
7
- TableDriver,
8
- TableRow,
9
- TableRelationSlice
10
- } from '@companix/xeo-scheme'
11
- import { Connection, Model, Schema, SchemaDefinition } from 'mongoose'
12
-
13
- class MongoTableStore {
14
- private modelKey: { [model: string]: 'm1' | 'm2' } = {}
15
- private keyMirror = { m1: 'm2' as 'm2', m2: 'm1' as 'm1' }
16
-
17
- private model: Model<TableRow>
18
-
19
- constructor({ rules, tableName }: RelationsTableInfo, private connection: Connection) {
20
- this.modelKey[rules.m1] = 'm1'
21
- this.modelKey[rules.m2] = 'm2'
22
- this.model = this.useModel<TableRow>(tableName, new Schema(this.getSchemeDefinition()))
23
- }
24
-
25
- private getSchemeDefinition(): SchemaDefinition {
26
- return {
27
- m1: { type: Schema.Types.Mixed, index: true, required: true },
28
- m2: { type: Schema.Types.Mixed, index: true, required: true }
29
- }
30
- }
31
-
32
- private getRow({ modelId, modelSide, oppositeId }: RelationRecord) {
33
- const key = this.modelKey[modelSide]
34
- const row = { [key]: modelId, [this.keyMirror[key]]: oppositeId } as unknown as TableRow
35
-
36
- return row
37
- }
38
-
39
- async addRow(row: RelationRecord) {
40
- await this.model.create(this.getRow(row))
41
- }
42
-
43
- async removeRow(row: RelationRecord) {
44
- await this.model.deleteOne(this.getRow(row)).exec()
45
- }
46
-
47
- async removeModel(model: string, modelId: IType) {
48
- await this.model.deleteMany({ [this.modelKey[model]]: modelId }).exec()
49
- }
50
-
51
- async getRelations(model: string, modelId: IType) {
52
- const key = this.modelKey[model]
53
- const oppositeKey = this.keyMirror[key]
54
-
55
- const rows = await this.model
56
- .find({ [key]: modelId })
57
- .select({ [oppositeKey]: 1, _id: 0 })
58
- .lean()
59
- .exec()
60
-
61
- return rows.map((row) => row[oppositeKey] as IType)
62
- }
63
-
64
- private useModel<T>(model: string, schema: Schema): Model<T> {
65
- return this.connection.models[model] ?? this.connection.model(model, schema)
66
- }
67
-
68
- getStore() {
69
- return this.model.find().lean().exec()
70
- }
71
- }
72
-
73
- export class MongoRelationsTable<T extends CollectionScheme> implements TableDriver {
74
- private readonly tables: { [tableName: string]: MongoTableStore } = {}
75
-
76
- constructor(dataScheme: DataScheme<T>, connection: Connection) {
77
- for (const table of dataScheme.tables) {
78
- this.tables[table.tableName] = new MongoTableStore(table, connection)
79
- }
80
- }
81
-
82
- async createRecord(row: RelationRecord) {
83
- await this.tables[row.tableName].addRow(row)
84
- }
85
-
86
- async removeRecord(row: RelationRecord) {
87
- await this.tables[row.tableName].removeRow(row)
88
- }
89
-
90
- async removeRecordsByModel(row: TableRelationSlice) {
91
- await this.tables[row.tableName].removeModel(row.modelSide, row.modelId)
92
- }
93
-
94
- async getRecords({ tableName, modelSide, modelId }: TableRelationSlice) {
95
- return this.tables[tableName].getRelations(modelSide, modelId)
96
- }
97
-
98
- // development
99
-
100
- async getTables() {
101
- const state: { [name: string]: TableRow[] } = {}
102
-
103
- for (const tableName in this.tables) {
104
- state[tableName] = await this.tables[tableName].getStore()
105
- }
106
-
107
- return state
108
- }
109
- }
@@ -1,129 +0,0 @@
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 DELETED
@@ -1,3 +0,0 @@
1
- export * from './common'
2
- export * from './drivers/table.driver'
3
- export * from './driver.module'
@@ -1,19 +0,0 @@
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
- }