@rws-framework/db 1.0.1
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/.bin/add-v.sh +10 -0
- package/.bin/emerge.sh +11 -0
- package/.eslintrc.json +53 -0
- package/README.md +275 -0
- package/package.json +25 -0
- package/src/decorators/InverseRelation.ts +35 -0
- package/src/decorators/InverseTimeSeries.ts +22 -0
- package/src/decorators/Relation.ts +37 -0
- package/src/decorators/TrackType.ts +56 -0
- package/src/decorators/index.ts +8 -0
- package/src/helper/DbHelper.ts +138 -0
- package/src/helper/FieldsHelper.ts +35 -0
- package/src/index.ts +33 -0
- package/src/models/TimeSeriesModel.ts +18 -0
- package/src/models/_model.ts +680 -0
- package/src/services/DBService.ts +253 -0
- package/src/types/DbConfigHandler.ts +11 -0
- package/src/types/FindParams.ts +7 -0
- package/src/types/IRWSModel.ts +3 -0
- package/src/types/ITimeSeries.ts +6 -0
- package/tsconfig.json +24 -0
package/.bin/add-v.sh
ADDED
package/.bin/emerge.sh
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
if ! command -v emerge >/dev/null 2>&1; then
|
|
4
|
+
echo "emerge command does not exist. Installing."
|
|
5
|
+
apt-get install graphviz graphviz-dev
|
|
6
|
+
pip install emerge-viz
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
mkdir -p ./.emerge-vis-output/rws-server
|
|
10
|
+
|
|
11
|
+
emerge -c ./.emerge-typescript-template.yaml
|
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": false,
|
|
4
|
+
"es2021": true
|
|
5
|
+
},
|
|
6
|
+
"plugins": ["@typescript-eslint", "unused-imports"],
|
|
7
|
+
"extends": [
|
|
8
|
+
"eslint:recommended",
|
|
9
|
+
"plugin:@typescript-eslint/recommended"
|
|
10
|
+
],
|
|
11
|
+
"parser": "@typescript-eslint/parser",
|
|
12
|
+
"parserOptions": {
|
|
13
|
+
"ecmaVersion": "latest",
|
|
14
|
+
"sourceType": "module",
|
|
15
|
+
"project": "./tsconfig.json"
|
|
16
|
+
},
|
|
17
|
+
"ignorePatterns": ["*.js"],
|
|
18
|
+
"rules": {
|
|
19
|
+
"no-unused-vars": "off",
|
|
20
|
+
"no-case-declarations": "off",
|
|
21
|
+
"no-prototype-builtins": "off",
|
|
22
|
+
"unused-imports/no-unused-imports": "error",
|
|
23
|
+
"unused-imports/no-unused-vars": ["warn", { "vars": "all", "args": "none"}],
|
|
24
|
+
"@typescript-eslint/no-var-requires": "off",
|
|
25
|
+
"@typescript-eslint/semi": ["error", "always"],
|
|
26
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
27
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
28
|
+
"@typescript-eslint/no-this-alias": "off",
|
|
29
|
+
"@typescript-eslint/type-annotation-spacing": ["error", {
|
|
30
|
+
"before": false,
|
|
31
|
+
"after": true,
|
|
32
|
+
"overrides": {
|
|
33
|
+
"arrow": {
|
|
34
|
+
"before": true, // Space before the arrow function's arrow
|
|
35
|
+
"after": true // Space after the arrow function's arrow
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}],
|
|
39
|
+
"indent": [
|
|
40
|
+
"error",
|
|
41
|
+
4
|
|
42
|
+
],
|
|
43
|
+
"linebreak-style": [
|
|
44
|
+
"error",
|
|
45
|
+
"unix"
|
|
46
|
+
],
|
|
47
|
+
"quotes": [
|
|
48
|
+
"error",
|
|
49
|
+
"single"
|
|
50
|
+
],
|
|
51
|
+
"semi": "off"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Models
|
|
2
|
+
|
|
3
|
+
RWS models are converted to Prisma schemas and are wrapping around generated PrismaClient providing complete typing and better Relation handling + TimeSeries in future minor versions.
|
|
4
|
+
|
|
5
|
+
## Models index file
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import ApiKey from "./ApiKey";
|
|
9
|
+
import User from "./User";
|
|
10
|
+
|
|
11
|
+
export const models = [ User, ApiKey];
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Example user model
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { RWSannotations, RWSModel } from '@rws-framework/server';
|
|
19
|
+
|
|
20
|
+
import IUser from './interfaces/IUser';
|
|
21
|
+
import 'reflect-metadata';
|
|
22
|
+
|
|
23
|
+
import ApiKey from './ApiKey';
|
|
24
|
+
import IApiKey from './interfaces/IApiKey';
|
|
25
|
+
const { RWSTrackType, InverseRelation } = RWSannotations.modelAnnotations;
|
|
26
|
+
|
|
27
|
+
class User extends RWSModel<User> implements IUser {
|
|
28
|
+
@RWSTrackType(String)
|
|
29
|
+
username: string;
|
|
30
|
+
|
|
31
|
+
@RWSTrackType(String) // Can also handle Object and Number
|
|
32
|
+
passwd: string;
|
|
33
|
+
|
|
34
|
+
@RWSTrackType(Boolean)
|
|
35
|
+
active: boolean;
|
|
36
|
+
|
|
37
|
+
@RWSTrackType(Date, { required: true })
|
|
38
|
+
created_at: Date;
|
|
39
|
+
|
|
40
|
+
@RWSTrackType(Date)
|
|
41
|
+
updated_at: Date;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every relation and inverse relation decorator
|
|
45
|
+
* uses arrow function model passing
|
|
46
|
+
**/
|
|
47
|
+
@InverseRelation(() => ApiKey, () => User)
|
|
48
|
+
apiKeys: IApiKey[];
|
|
49
|
+
|
|
50
|
+
static _collection = 'user';
|
|
51
|
+
|
|
52
|
+
static _RELATIONS = {
|
|
53
|
+
transcriptions: true,
|
|
54
|
+
apiKeys: true
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
static _CUT_KEYS = ['passwd'];
|
|
58
|
+
|
|
59
|
+
constructor(data?: IUser) {
|
|
60
|
+
super(data);
|
|
61
|
+
|
|
62
|
+
if(!this.created_at){
|
|
63
|
+
this.created_at = new Date();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
addMessage(message: string){
|
|
68
|
+
this.messages.push(message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//Must export default for automated DI / build work.
|
|
73
|
+
export default User;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Relations
|
|
77
|
+
|
|
78
|
+
***Basic many to one relation***
|
|
79
|
+
```typescript
|
|
80
|
+
import { RWSannotations, RWSModel } from '@rws-framework/server';
|
|
81
|
+
|
|
82
|
+
import 'reflect-metadata';
|
|
83
|
+
import User from './User';
|
|
84
|
+
import IApiKey from './interfaces/IApiKey';
|
|
85
|
+
const { RWSTrackType, Relation } = RWSannotations.modelAnnotations;
|
|
86
|
+
|
|
87
|
+
class ApiKey extends RWSModel<ApiKey> implements IApiKey {
|
|
88
|
+
static _RELATIONS = {
|
|
89
|
+
user: true,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
@Relation(() => User, true) // second attribute is required = false
|
|
93
|
+
user: User;
|
|
94
|
+
|
|
95
|
+
@RWSTrackType(Object)
|
|
96
|
+
keyval: string;
|
|
97
|
+
|
|
98
|
+
@RWSTrackType(Date, { required: true })
|
|
99
|
+
created_at: Date;
|
|
100
|
+
|
|
101
|
+
@RWSTrackType(Date)
|
|
102
|
+
updated_at: Date;
|
|
103
|
+
|
|
104
|
+
static _collection = 'api_keys';
|
|
105
|
+
|
|
106
|
+
constructor(data?: IApiKey) {
|
|
107
|
+
super(data);
|
|
108
|
+
|
|
109
|
+
if(!this.created_at){
|
|
110
|
+
this.created_at = new Date();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.updated_at = new Date();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default ApiKey;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
***Relation decorator*** (many-to-one)
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import 'reflect-metadata';
|
|
124
|
+
import Model, { OpModelType } from '../_model';
|
|
125
|
+
|
|
126
|
+
interface IRelationOpts {
|
|
127
|
+
required?: boolean
|
|
128
|
+
key?: string
|
|
129
|
+
relationField?: string
|
|
130
|
+
relatedToField?: string
|
|
131
|
+
relatedTo: OpModelType<Model<any>>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function Relation(theModel: () => OpModelType<Model<any>>, required: boolean = false, relationField: string = null, relatedToField: string = 'id') {
|
|
135
|
+
return function(target: any, key: string) {
|
|
136
|
+
// Store the promise in metadata immediately
|
|
137
|
+
const metadataPromise = Promise.resolve().then(() => {
|
|
138
|
+
const relatedTo = theModel();
|
|
139
|
+
const metaOpts: IRelationOpts = {required, relatedTo, relatedToField};
|
|
140
|
+
if(!relationField){
|
|
141
|
+
metaOpts.relationField = relatedTo._collection + '_id';
|
|
142
|
+
} else{
|
|
143
|
+
metaOpts.relationField = relationField;
|
|
144
|
+
}
|
|
145
|
+
metaOpts.key = key;
|
|
146
|
+
return metaOpts;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Store both the promise and the key information
|
|
150
|
+
Reflect.defineMetadata(`Relation:${key}`, {
|
|
151
|
+
promise: metadataPromise,
|
|
152
|
+
key
|
|
153
|
+
}, target);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
export default Relation;
|
|
159
|
+
export {IRelationOpts};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
***Inverse relation decorator*** (one-to-many)
|
|
163
|
+
```typescript
|
|
164
|
+
import 'reflect-metadata';
|
|
165
|
+
import Model, { OpModelType } from '../_model';
|
|
166
|
+
|
|
167
|
+
interface InverseRelationOpts{
|
|
168
|
+
key: string,
|
|
169
|
+
inversionModel: OpModelType<Model<any>>,
|
|
170
|
+
foreignKey: string
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function InverseRelation(inversionModel: () => OpModelType<Model<any>>, sourceModel: () => OpModelType<Model<any>>, foreignKey: string = null) {
|
|
174
|
+
return function(target: any, key: string) {
|
|
175
|
+
// Store the promise in metadata immediately
|
|
176
|
+
const metadataPromise = Promise.resolve().then(() => {
|
|
177
|
+
const model = inversionModel();
|
|
178
|
+
const source = sourceModel();
|
|
179
|
+
|
|
180
|
+
const metaOpts: InverseRelationOpts = {
|
|
181
|
+
key,
|
|
182
|
+
inversionModel: model,
|
|
183
|
+
foreignKey: foreignKey ? foreignKey : `${source._collection}_id`
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return metaOpts;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Store both the promise and the key information
|
|
190
|
+
Reflect.defineMetadata(`InverseRelation:${key}`, {
|
|
191
|
+
promise: metadataPromise,
|
|
192
|
+
key
|
|
193
|
+
}, target);
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default InverseRelation;
|
|
198
|
+
export {InverseRelationOpts};
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
## RWS Model to prisma conversion
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
### Init CLI
|
|
206
|
+
Basic CLI command that executes **generateModelSections()** from conversion script is
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
yarn rws init
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
Code for RWS to prisma conversion from "@rws-framework/server" package:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
async function generateModelSections<T extends Model<T>>(model: OpModelType<T>): Promise<string> {
|
|
217
|
+
let section = '';
|
|
218
|
+
const modelMetadatas: Record<string, {annotationType: string, metadata: any}> = await Model.getModelAnnotations(model);
|
|
219
|
+
|
|
220
|
+
const modelName: string = (model as any)._collection;
|
|
221
|
+
|
|
222
|
+
section += `model ${modelName} {\n`;
|
|
223
|
+
section += '\tid String @map("_id") @id @default(auto()) @db.ObjectId\n';
|
|
224
|
+
|
|
225
|
+
for (const key in modelMetadatas) {
|
|
226
|
+
const modelMetadata: IMetaOpts = modelMetadatas[key].metadata;
|
|
227
|
+
const requiredString = modelMetadata.required ? '' : '?';
|
|
228
|
+
const annotationType: string = modelMetadatas[key].annotationType;
|
|
229
|
+
|
|
230
|
+
if(key === 'id'){
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if(annotationType === 'Relation'){
|
|
235
|
+
const relatedModel = modelMetadata.relatedTo as OpModelType<T>;
|
|
236
|
+
// Handle direct relation (many-to-one or one-to-one)
|
|
237
|
+
section += `\t${key} ${relatedModel._collection}${requiredString} @relation("${modelName}_${relatedModel._collection}", fields: [${modelMetadata.relationField}], references: [${modelMetadata.relatedToField}], onDelete: Cascade)\n`;
|
|
238
|
+
section += `\t${modelMetadata.relationField} String${requiredString} @db.ObjectId\n`;
|
|
239
|
+
} else if (annotationType === 'InverseRelation'){
|
|
240
|
+
// Handle inverse relation (one-to-many or one-to-one)
|
|
241
|
+
section += `\t${key} ${modelMetadata.inversionModel._collection}[] @relation("${modelMetadata.inversionModel._collection}_${modelName}")\n`;
|
|
242
|
+
} else if (annotationType === 'InverseTimeSeries'){
|
|
243
|
+
section += `\t${key} String[] @db.ObjectId\n`;
|
|
244
|
+
} else if (annotationType === 'TrackType'){
|
|
245
|
+
const tags: string[] = modelMetadata.tags.map((item: string) => '@' + item);
|
|
246
|
+
section += `\t${key} ${toConfigCase(modelMetadata)}${requiredString} ${tags.join(' ')}\n`;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
section += '}\n';
|
|
251
|
+
return section;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function toConfigCase(modelType: any): string {
|
|
255
|
+
const type = modelType.type;
|
|
256
|
+
const input = type.name;
|
|
257
|
+
|
|
258
|
+
if(input == 'Number'){
|
|
259
|
+
return 'Int';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if(input == 'Object'){
|
|
263
|
+
return 'Json';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if(input == 'Date'){
|
|
267
|
+
return 'DateTime';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
const firstChar = input.charAt(0).toUpperCase();
|
|
272
|
+
const restOfString = input.slice(1);
|
|
273
|
+
return firstChar + restOfString;
|
|
274
|
+
}
|
|
275
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rws-framework/db",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"description": "",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"scripts": {},
|
|
8
|
+
"author": "papablack",
|
|
9
|
+
"license": "ISC",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@rws-framework/console": "*",
|
|
12
|
+
"@prisma/client": "^5.1.1",
|
|
13
|
+
"@mongodb-js/zstd": "^1.2.0",
|
|
14
|
+
"mongodb": "^6.8.1",
|
|
15
|
+
"mongodb-client-encryption": "^6.1.1",
|
|
16
|
+
"xml2js": "^0.6.2"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/xml2js": "^0.4.14"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/rws-framework/db.git"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { RWSModel, OpModelType } from '../models/_model';
|
|
3
|
+
|
|
4
|
+
interface InverseRelationOpts{
|
|
5
|
+
key: string,
|
|
6
|
+
inversionModel: OpModelType<RWSModel<any>>,
|
|
7
|
+
foreignKey: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function InverseRelation(inversionModel: () => OpModelType<RWSModel<any>>, sourceModel: () => OpModelType<RWSModel<any>>, foreignKey: string = null) {
|
|
11
|
+
return function(target: any, key: string) {
|
|
12
|
+
// Store the promise in metadata immediately
|
|
13
|
+
const metadataPromise = Promise.resolve().then(() => {
|
|
14
|
+
const model = inversionModel();
|
|
15
|
+
const source = sourceModel();
|
|
16
|
+
|
|
17
|
+
const metaOpts: InverseRelationOpts = {
|
|
18
|
+
key,
|
|
19
|
+
inversionModel: model,
|
|
20
|
+
foreignKey: foreignKey ? foreignKey : `${source._collection}_id`
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return metaOpts;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Store both the promise and the key information
|
|
27
|
+
Reflect.defineMetadata(`InverseRelation:${key}`, {
|
|
28
|
+
promise: metadataPromise,
|
|
29
|
+
key
|
|
30
|
+
}, target);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default InverseRelation;
|
|
35
|
+
export {InverseRelationOpts};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
interface InverseTimeSeriesOpts{
|
|
4
|
+
timeSeriesModel: string
|
|
5
|
+
hydrationField: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function InverseTimeSeries(timeSeriesModel: string, hydrationField: string) {
|
|
9
|
+
|
|
10
|
+
const metaOpts: InverseTimeSeriesOpts = {
|
|
11
|
+
timeSeriesModel: timeSeriesModel,
|
|
12
|
+
hydrationField: hydrationField
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
return function(target: any, key: string) {
|
|
17
|
+
Reflect.defineMetadata(`InverseTimeSeries:${key}`, metaOpts, target);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default InverseTimeSeries;
|
|
22
|
+
export {InverseTimeSeriesOpts};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { RWSModel, OpModelType } from '../models/_model';
|
|
3
|
+
|
|
4
|
+
interface IRelationOpts {
|
|
5
|
+
required?: boolean
|
|
6
|
+
key?: string
|
|
7
|
+
relationField?: string
|
|
8
|
+
relatedToField?: string
|
|
9
|
+
relatedTo: OpModelType<RWSModel<any>>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function Relation(theModel: () => OpModelType<RWSModel<any>>, required: boolean = false, relationField: string = null, relatedToField: string = 'id') {
|
|
13
|
+
return function(target: any, key: string) {
|
|
14
|
+
// Store the promise in metadata immediately
|
|
15
|
+
const metadataPromise = Promise.resolve().then(() => {
|
|
16
|
+
const relatedTo = theModel();
|
|
17
|
+
const metaOpts: IRelationOpts = {required, relatedTo, relatedToField};
|
|
18
|
+
if(!relationField){
|
|
19
|
+
metaOpts.relationField = relatedTo._collection + '_id';
|
|
20
|
+
} else{
|
|
21
|
+
metaOpts.relationField = relationField;
|
|
22
|
+
}
|
|
23
|
+
metaOpts.key = key;
|
|
24
|
+
return metaOpts;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Store both the promise and the key information
|
|
28
|
+
Reflect.defineMetadata(`Relation:${key}`, {
|
|
29
|
+
promise: metadataPromise,
|
|
30
|
+
key
|
|
31
|
+
}, target);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export default Relation;
|
|
37
|
+
export {IRelationOpts};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { OpModelType } from '../models/_model';
|
|
3
|
+
|
|
4
|
+
interface ITrackerOpts{
|
|
5
|
+
required?: boolean,
|
|
6
|
+
relationField?: string
|
|
7
|
+
relatedToField?: string,
|
|
8
|
+
relatedTo?: OpModelType<any>,
|
|
9
|
+
inversionModel?: OpModelType<any>,
|
|
10
|
+
relationName?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface IMetaOpts extends ITrackerOpts{
|
|
14
|
+
type: any,
|
|
15
|
+
tags: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function TrackType(type: any, opts: ITrackerOpts | null = null, tags: string[] = []) {
|
|
19
|
+
if(!opts){
|
|
20
|
+
opts = {
|
|
21
|
+
required: false
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const required = opts.required;
|
|
26
|
+
|
|
27
|
+
const metaOpts: IMetaOpts = {type, tags, required};
|
|
28
|
+
|
|
29
|
+
if(opts.relatedToField && opts.relatedTo){
|
|
30
|
+
metaOpts.relatedToField = opts.relatedToField;
|
|
31
|
+
metaOpts.relatedTo = opts.relatedTo;
|
|
32
|
+
|
|
33
|
+
if(!opts.relationField){
|
|
34
|
+
metaOpts.relationField = opts.relatedTo + '_id';
|
|
35
|
+
} else{
|
|
36
|
+
metaOpts.relationField = opts.relationField;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if(opts.inversionModel){
|
|
41
|
+
metaOpts.inversionModel = opts.inversionModel;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//const resolvedType = typeof type === 'function' ? type() : type;
|
|
45
|
+
|
|
46
|
+
if(type._collection){
|
|
47
|
+
metaOpts.type = (type as any);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return function(target: any, key: string) {
|
|
51
|
+
Reflect.defineMetadata(`TrackType:${key}`, metaOpts, target);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default TrackType;
|
|
56
|
+
export {IMetaOpts, ITrackerOpts};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import InverseRelation from './InverseRelation';
|
|
2
|
+
import Relation from './Relation';
|
|
3
|
+
import TrackType, { IMetaOpts } from './TrackType';
|
|
4
|
+
import InverseTimeSeries from './InverseTimeSeries';
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
InverseRelation, Relation, TrackType, InverseTimeSeries, IMetaOpts
|
|
8
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { rwsShell, rwsPath } from '@rws-framework/console';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
import { IDbConfigHandler } from '../types/DbConfigHandler';
|
|
7
|
+
import { IMetaOpts, OpModelType, RWSModel } from '../models/_model';
|
|
8
|
+
import TimeSeriesModel from '../models/TimeSeriesModel';
|
|
9
|
+
import { DBService } from '../services/DBService';
|
|
10
|
+
|
|
11
|
+
const log = console.log;
|
|
12
|
+
const workspaceRoot = rwsPath.findRootWorkspacePath();
|
|
13
|
+
const moduleDir = path.resolve(workspaceRoot, 'node_modules', '@rws-framework', 'db');
|
|
14
|
+
|
|
15
|
+
export class DbHelper {
|
|
16
|
+
static async installPrisma(configService: IDbConfigHandler, dbService: DBService, leaveFile = false): Promise<void>
|
|
17
|
+
{
|
|
18
|
+
const dbUrl = configService.get('mongo_url');
|
|
19
|
+
const dbType = 'mongodb';
|
|
20
|
+
|
|
21
|
+
let template: string = `generator client {\n
|
|
22
|
+
provider = "prisma-client-js"\n
|
|
23
|
+
}\n\n`;
|
|
24
|
+
|
|
25
|
+
template += `\ndatasource db {\n
|
|
26
|
+
provider = "${dbType}"\n
|
|
27
|
+
url = env("DATABASE_URL")\n
|
|
28
|
+
}\n\n`;
|
|
29
|
+
|
|
30
|
+
const dbModels: OpModelType<unknown>[] | null = configService.get('db_models');
|
|
31
|
+
|
|
32
|
+
if(dbModels){
|
|
33
|
+
|
|
34
|
+
for (const model of dbModels){
|
|
35
|
+
const modelSection = await DbHelper.generateModelSections(model);
|
|
36
|
+
|
|
37
|
+
template += '\n\n' + modelSection;
|
|
38
|
+
|
|
39
|
+
log('RWS SCHEMA BUILD', chalk.blue('Building DB Model'), model.name);
|
|
40
|
+
|
|
41
|
+
if(RWSModel.isSubclass(model as any, TimeSeriesModel)){
|
|
42
|
+
dbService.collectionExists(model._collection).then((exists: boolean) => {
|
|
43
|
+
if (exists){
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
log(chalk.green('[RWS Init]') + ` creating TimeSeries type collection from ${model} model`);
|
|
48
|
+
|
|
49
|
+
dbService.createTimeSeriesCollection(model._collection);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const schemaDir = path.join(moduleDir, 'prisma');
|
|
55
|
+
const schemaPath = path.join(schemaDir, 'schema.prisma');
|
|
56
|
+
|
|
57
|
+
if(!fs.existsSync(schemaDir)){
|
|
58
|
+
fs.mkdirSync(schemaDir);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if(fs.existsSync(schemaPath)){
|
|
62
|
+
fs.unlinkSync(schemaPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fs.writeFileSync(schemaPath, template);
|
|
66
|
+
process.env.DB_URL = dbUrl;
|
|
67
|
+
const endPrisma = 'npx prisma';
|
|
68
|
+
await rwsShell.runCommand(`${endPrisma} generate --schema=${schemaPath}`, process.cwd());
|
|
69
|
+
|
|
70
|
+
// leaveFile = true;
|
|
71
|
+
log(chalk.green('[RWS Init]') + ' prisma schema generated from ', schemaPath);
|
|
72
|
+
|
|
73
|
+
if(!leaveFile){
|
|
74
|
+
fs.unlinkSync(schemaPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static async generateModelSections<T extends unknown>(model: OpModelType<T>): Promise<string> {
|
|
80
|
+
let section = '';
|
|
81
|
+
const modelMetadatas: Record<string, {annotationType: string, metadata: any}> = await RWSModel.getModelAnnotations(model);
|
|
82
|
+
|
|
83
|
+
const modelName: string = (model as any)._collection;
|
|
84
|
+
|
|
85
|
+
section += `model ${modelName} {\n`;
|
|
86
|
+
section += '\tid String @map("_id") @id @default(auto()) @db.ObjectId\n';
|
|
87
|
+
|
|
88
|
+
for (const key in modelMetadatas) {
|
|
89
|
+
const modelMetadata: IMetaOpts = modelMetadatas[key].metadata;
|
|
90
|
+
const requiredString = modelMetadata.required ? '' : '?';
|
|
91
|
+
const annotationType: string = modelMetadatas[key].annotationType;
|
|
92
|
+
|
|
93
|
+
if(key === 'id'){
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if(annotationType === 'Relation'){
|
|
98
|
+
const relatedModel = modelMetadata.relatedTo as OpModelType<T>;
|
|
99
|
+
// Handle direct relation (many-to-one or one-to-one)
|
|
100
|
+
section += `\t${key} ${relatedModel._collection}${requiredString} @relation("${modelName}_${relatedModel._collection}", fields: [${modelMetadata.relationField}], references: [${modelMetadata.relatedToField}], onDelete: Cascade)\n`;
|
|
101
|
+
section += `\t${modelMetadata.relationField} String${requiredString} @db.ObjectId\n`;
|
|
102
|
+
} else if (annotationType === 'InverseRelation'){
|
|
103
|
+
// Handle inverse relation (one-to-many or one-to-one)
|
|
104
|
+
section += `\t${key} ${modelMetadata.inversionModel._collection}[] @relation("${modelMetadata.inversionModel._collection}_${modelName}")\n`;
|
|
105
|
+
} else if (annotationType === 'InverseTimeSeries'){
|
|
106
|
+
section += `\t${key} String[] @db.ObjectId\n`;
|
|
107
|
+
} else if (annotationType === 'TrackType'){
|
|
108
|
+
const tags: string[] = modelMetadata.tags.map((item: string) => '@' + item);
|
|
109
|
+
section += `\t${key} ${DbHelper.toConfigCase(modelMetadata)}${requiredString} ${tags.join(' ')}\n`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
section += '}\n';
|
|
114
|
+
return section;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static toConfigCase(modelType: any): string {
|
|
118
|
+
const type = modelType.type;
|
|
119
|
+
const input = type.name;
|
|
120
|
+
|
|
121
|
+
if(input == 'Number'){
|
|
122
|
+
return 'Int';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if(input == 'Object'){
|
|
126
|
+
return 'Json';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if(input == 'Date'){
|
|
130
|
+
return 'DateTime';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
const firstChar = input.charAt(0).toUpperCase();
|
|
135
|
+
const restOfString = input.slice(1);
|
|
136
|
+
return firstChar + restOfString;
|
|
137
|
+
}
|
|
138
|
+
}
|