@cheetah.js/orm 0.1.0

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 (93) hide show
  1. package/README.md +228 -0
  2. package/build.ts +14 -0
  3. package/cheetah.config.ts +14 -0
  4. package/dist/SqlBuilder.d.ts +57 -0
  5. package/dist/SqlBuilder.js +436 -0
  6. package/dist/SqlBuilder.js.map +1 -0
  7. package/dist/bun/index.d.ts +13 -0
  8. package/dist/bun/index.js +224319 -0
  9. package/dist/bun/index.js.map +306 -0
  10. package/dist/cheetah.d.ts +1 -0
  11. package/dist/cheetah.js +24 -0
  12. package/dist/cheetah.js.map +1 -0
  13. package/dist/constants.d.ts +4 -0
  14. package/dist/constants.js +5 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/decorators/entity.decorator.d.ts +3 -0
  17. package/dist/decorators/entity.decorator.js +10 -0
  18. package/dist/decorators/entity.decorator.js.map +1 -0
  19. package/dist/decorators/index.decorator.d.ts +3 -0
  20. package/dist/decorators/index.decorator.js +17 -0
  21. package/dist/decorators/index.decorator.js.map +1 -0
  22. package/dist/decorators/one-many.decorator.d.ts +3 -0
  23. package/dist/decorators/one-many.decorator.js +17 -0
  24. package/dist/decorators/one-many.decorator.js.map +1 -0
  25. package/dist/decorators/primary-key.decorator.d.ts +2 -0
  26. package/dist/decorators/primary-key.decorator.js +6 -0
  27. package/dist/decorators/primary-key.decorator.js.map +1 -0
  28. package/dist/decorators/property.decorator.d.ts +15 -0
  29. package/dist/decorators/property.decorator.js +25 -0
  30. package/dist/decorators/property.decorator.js.map +1 -0
  31. package/dist/domain/base-entity.d.ts +42 -0
  32. package/dist/domain/base-entity.js +106 -0
  33. package/dist/domain/base-entity.js.map +1 -0
  34. package/dist/domain/collection.d.ts +6 -0
  35. package/dist/domain/collection.js +11 -0
  36. package/dist/domain/collection.js.map +1 -0
  37. package/dist/domain/entities.d.ts +40 -0
  38. package/dist/domain/entities.js +137 -0
  39. package/dist/domain/entities.js.map +1 -0
  40. package/dist/domain/reference.d.ts +4 -0
  41. package/dist/domain/reference.js +7 -0
  42. package/dist/domain/reference.js.map +1 -0
  43. package/dist/driver/driver.interface.d.ts +270 -0
  44. package/dist/driver/driver.interface.js +2 -0
  45. package/dist/driver/driver.interface.js.map +1 -0
  46. package/dist/driver/pg-driver.d.ts +43 -0
  47. package/dist/driver/pg-driver.js +255 -0
  48. package/dist/driver/pg-driver.js.map +1 -0
  49. package/dist/index.d.ts +13 -0
  50. package/dist/index.js +16 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/migration/diff-calculator.d.ts +17 -0
  53. package/dist/migration/diff-calculator.js +230 -0
  54. package/dist/migration/diff-calculator.js.map +1 -0
  55. package/dist/migration/migrator.d.ts +18 -0
  56. package/dist/migration/migrator.js +233 -0
  57. package/dist/migration/migrator.js.map +1 -0
  58. package/dist/orm.d.ts +14 -0
  59. package/dist/orm.js +23 -0
  60. package/dist/orm.js.map +1 -0
  61. package/dist/orm.service.d.ts +8 -0
  62. package/dist/orm.service.js +115 -0
  63. package/dist/orm.service.js.map +1 -0
  64. package/dist/utils.d.ts +1 -0
  65. package/dist/utils.js +16 -0
  66. package/dist/utils.js.map +1 -0
  67. package/package.json +50 -0
  68. package/src/SqlBuilder.ts +542 -0
  69. package/src/cheetah.ts +28 -0
  70. package/src/constants.ts +4 -0
  71. package/src/decorators/entity.decorator.ts +10 -0
  72. package/src/decorators/index.decorator.ts +18 -0
  73. package/src/decorators/one-many.decorator.ts +19 -0
  74. package/src/decorators/primary-key.decorator.ts +6 -0
  75. package/src/decorators/property.decorator.ts +41 -0
  76. package/src/domain/base-entity.ts +149 -0
  77. package/src/domain/collection.ts +10 -0
  78. package/src/domain/entities.ts +159 -0
  79. package/src/domain/reference.ts +5 -0
  80. package/src/driver/driver.interface.ts +331 -0
  81. package/src/driver/pg-driver.ts +308 -0
  82. package/src/index.ts +17 -0
  83. package/src/migration/diff-calculator.ts +258 -0
  84. package/src/migration/migrator.ts +278 -0
  85. package/src/orm.service.ts +115 -0
  86. package/src/orm.ts +30 -0
  87. package/src/utils.ts +18 -0
  88. package/test/domain/base-entity.spec.ts +463 -0
  89. package/test/migration/.sql +5 -0
  90. package/test/migration/cheetah.config.ts +13 -0
  91. package/test/migration/migator.spec.ts +251 -0
  92. package/test/migration/test.sql +5 -0
  93. package/test/node-database.ts +32 -0
@@ -0,0 +1,542 @@
1
+ import {
2
+ AutoPath,
3
+ DriverInterface,
4
+ FilterQuery,
5
+ QueryOrderMap,
6
+ Relationship,
7
+ Statement,
8
+ } from './driver/driver.interface';
9
+ import { EntityStorage, Options } from './domain/entities';
10
+ import { Orm } from '../src';
11
+ import { LoggerService } from '@cheetah.js/core';
12
+
13
+ export class SqlBuilder<T> {
14
+ private readonly driver: DriverInterface;
15
+
16
+ private entityStorage: EntityStorage;
17
+ private statements: Statement<T> = {};
18
+ private entity!: Options;
19
+ private model!: new () => T;
20
+
21
+ private aliases: Set<string> = new Set();
22
+ private lastKeyNotOperator = '';
23
+ private logger: LoggerService;
24
+ private updatedColumns: any[] = [];
25
+
26
+ constructor(model: new () => T) {
27
+ const orm = Orm.getInstance();
28
+ this.driver = orm.driverInstance;
29
+ this.logger = orm.logger;
30
+ this.entityStorage = EntityStorage.getInstance()
31
+
32
+ this.getEntity(model);
33
+ }
34
+
35
+ select(columns?: AutoPath<T, never, '*'>[]): SqlBuilder<T> {
36
+ const tableName = this.entity.tableName || (this.model as Function).name.toLowerCase();
37
+ const schema = this.entity.schema || 'public';
38
+
39
+ this.statements.statement = 'select';
40
+
41
+ this.statements.columns = columns;
42
+ this.statements.alias = this.getAlias(tableName)
43
+ this.statements.table = `"${schema}"."${tableName}"`;
44
+ return this;
45
+ }
46
+
47
+ insert(values: Partial<T>): SqlBuilder<T> {
48
+ const {tableName, schema} = this.getTableName();
49
+
50
+ this.statements.statement = 'insert';
51
+ this.statements.alias = this.getAlias(tableName)
52
+ this.statements.table = `"${schema}"."${tableName}"`;
53
+ this.statements.values = this.withUpdatedValues(this.withDefaultValues(values, this.entity), this.entity);
54
+ return this;
55
+ }
56
+
57
+ update(values: Partial<T>): SqlBuilder<T> {
58
+ const {tableName, schema} = this.getTableName();
59
+
60
+ this.statements.statement = 'update';
61
+ this.statements.alias = this.getAlias(tableName)
62
+ this.statements.table = `${schema}.${tableName}`;
63
+ this.statements.values = this.withUpdatedValues(values, this.entity);
64
+ return this;
65
+ }
66
+
67
+ where(where: FilterQuery<T>): SqlBuilder<T> {
68
+ if (typeof where === 'undefined' || Object.entries(where).length === 0) {
69
+ return this;
70
+ }
71
+
72
+ this.statements.where = this.conditionToSql(where, this.statements.alias!);
73
+ return this;
74
+ }
75
+
76
+ orderBy(orderBy: (QueryOrderMap<T> & {
77
+ 0?: never;
78
+ }) | QueryOrderMap<T>[]): SqlBuilder<T> {
79
+ if (typeof orderBy === 'undefined') {
80
+ return this;
81
+ }
82
+
83
+ this.statements.orderBy = this.objectToStringMap(orderBy);
84
+ return this;
85
+ }
86
+
87
+ limit(limit: number | undefined): SqlBuilder<T> {
88
+ this.statements.limit = limit;
89
+ return this;
90
+ }
91
+
92
+ offset(offset: number | undefined): SqlBuilder<T> {
93
+ this.statements.offset = offset;
94
+ return this;
95
+ }
96
+
97
+ async executeAndReturnFirst(): Promise<T | undefined> {
98
+ this.statements.limit = 1;
99
+
100
+ const result = await this.execute();
101
+
102
+ if (result.query.rows.length === 0) {
103
+ return undefined;
104
+ }
105
+
106
+ return this.transformToModel(result.query.rows[0]);
107
+ }
108
+
109
+ async executeAndReturnFirstOrFail(): Promise<T> {
110
+ this.statements.limit = 1;
111
+
112
+ const result = await this.execute();
113
+
114
+ if (result.query.rows.length === 0) {
115
+ throw new Error('Result not found')
116
+ }
117
+
118
+ return this.transformToModel(result.query.rows[0]);
119
+ }
120
+
121
+ // TODO: Corrigir tipagem do retorno de acordo com o driver
122
+ async executeAndReturnAll(): Promise<T[]> {
123
+ const result = await this.execute()
124
+
125
+ if (result.query.rows.length === 0) {
126
+ return [];
127
+ }
128
+
129
+ return result.query.rows.map((row: any) => this.transformToModel(row));
130
+ }
131
+
132
+ async execute(): Promise<{ query: any, startTime: number, sql: string }> {
133
+ if (!this.statements.columns) {
134
+ let columns: any[] = [
135
+ ...this.getColumnsEntity((this.model as Function), this.statements.alias!),
136
+ ];
137
+
138
+ if (this.statements.join) {
139
+ columns = [
140
+ ...columns,
141
+ ...this.statements.join.flatMap(join => this.getColumnsEntity(join.joinEntity!, join.joinAlias)),
142
+ ]
143
+ }
144
+
145
+ this.statements.columns = columns
146
+ } else {
147
+ // @ts-ignore
148
+ this.statements.columns = this.statements.columns.map((column: string) => {
149
+ return this.discoverColumnAlias(column)
150
+ }).flat();
151
+ }
152
+
153
+ this.statements.columns!.push(...this.updatedColumns)
154
+
155
+ const result = await this.driver.executeStatement(this.statements);
156
+ // console.log(result.query.rows);
157
+ this.logger.debug(`SQL: ${result.sql} [${Date.now() - result.startTime}ms]`)
158
+
159
+ return result;
160
+ }
161
+
162
+ startTransaction(): Promise<void> {
163
+ return this.driver.startTransaction();
164
+ }
165
+
166
+ commit(): Promise<void> {
167
+ return this.driver.commitTransaction();
168
+ }
169
+
170
+ rollback(): Promise<void> {
171
+ return this.driver.rollbackTransaction();
172
+ }
173
+
174
+ // TODO: transform transaction queries into just 1 query
175
+ async inTransaction<T>(callback: (builder: SqlBuilder<T>) => Promise<T>): Promise<T> {
176
+ await this.startTransaction();
177
+ try {
178
+ // @ts-ignore
179
+ const result = await callback(this);
180
+ await this.commit();
181
+ return result;
182
+ } catch (e) {
183
+ await this.rollback();
184
+ throw e;
185
+ }
186
+ }
187
+
188
+ private objectToStringMap(obj: any, parentKey: string = ''): string[] {
189
+ let result: string[] = [];
190
+
191
+ for (let key in obj) {
192
+ if (obj.hasOwnProperty(key)) {
193
+ let fullKey = parentKey ? `${parentKey}.${key}` : key;
194
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
195
+ result = result.concat(this.objectToStringMap(obj[key], fullKey));
196
+ } else {
197
+ result.push(`${this.discoverColumnAlias(fullKey, true)} ${obj[key]}`);
198
+ }
199
+ }
200
+ }
201
+
202
+ return result;
203
+ }
204
+
205
+ private discoverColumnAlias(column: string, onlyAlias = false): string {
206
+ if (!column.includes('.')) {
207
+ if (onlyAlias) {
208
+ return `${this.statements.alias!}."${column}"`
209
+ }
210
+ return `${this.statements.alias!}."${column}" as ${this.statements.alias!}_${column}`;
211
+ }
212
+
213
+ if (typeof this.statements.join === 'undefined') {
214
+ throw new Error('Join not found');
215
+ }
216
+
217
+ const entities = column.split('.');
218
+ let lastEntity = this.model as Function;
219
+ let lastAlias = this.statements.alias!;
220
+
221
+ const relationsMap = new Map(this.entity.relations.map(rel => [rel.propertyKey, rel]));
222
+ const joinMap = new Map(this.statements.join!.map(join => [join.joinProperty, join]));
223
+
224
+ for (let i = 0; i < entities.length; i++) {
225
+ if (i === 0) {
226
+ const relation = relationsMap.get(entities[i]);
227
+ lastEntity = relation?.entity() as Function;
228
+ // @ts-ignore
229
+ lastAlias = joinMap.get(entities[i])?.joinAlias;
230
+ } else {
231
+ if ((i + 1) === entities.length) {
232
+ if (onlyAlias) {
233
+ return `${lastAlias}."${entities[i]}"`
234
+ }
235
+ return `${lastAlias}."${entities[i]}" as ${lastAlias}_${entities[i]}`;
236
+ }
237
+
238
+ const lastStatement = joinMap.get(entities[i]);
239
+ lastEntity = lastStatement?.joinEntity as Function;
240
+ // @ts-ignore
241
+ lastAlias = lastStatement?.joinAlias;
242
+ }
243
+ }
244
+
245
+ return '';
246
+ }
247
+
248
+ private getTableName() {
249
+ const tableName = this.entity.tableName || (this.model as Function).name.toLowerCase();
250
+ const schema = this.entity.schema || 'public';
251
+ return {tableName, schema};
252
+ }
253
+
254
+ private addSimpleConditionToSql(key: string, value: any, alias: string | null = null, operator: string = '='): string {
255
+ const aliasToUse = alias || this.statements.alias;
256
+ const valueByType = (typeof value === 'string') ? `'${value}'` : value;
257
+
258
+ return `${aliasToUse}.${key} ${operator} ${valueByType}`;
259
+ }
260
+
261
+ private addInConditionToSql(key: string, values: any[], alias: string | null = null): string {
262
+ const aliasToUse = alias || this.statements.alias;
263
+ return `${aliasToUse}.${key} IN (${values.map(val => (typeof val === 'string') ? `'${val}'` : val).join(", ")})`;
264
+ }
265
+
266
+ private addLogicalOperatorToSql(conditions: string[], operator: 'AND' | 'OR'): string {
267
+ return `(${conditions.join(` ${operator} `)})`;
268
+ }
269
+
270
+ private conditionToSql(condition: FilterQuery<T>, alias: string): string {
271
+ const sqlParts = [];
272
+ const operators = ['$eq', '$ne', '$in', '$nin', '$like', '$gt', '$gte', '$lt', '$lte', '$and', '$or'];
273
+
274
+ for (let [key, value] of Object.entries(condition)) {
275
+
276
+ if (!operators.includes(key)) {
277
+ this.lastKeyNotOperator = key;
278
+ }
279
+
280
+ const relationShip = this.entity.relations?.find(rel => rel.propertyKey === key);
281
+ if (relationShip) {
282
+ sqlParts.push(this.applyJoin(relationShip, value, alias));
283
+ } else if (typeof value !== 'object' || value === null) {
284
+ if (key === '$eq') {
285
+ sqlParts.push(this.addSimpleConditionToSql(this.lastKeyNotOperator, value, alias, '='));
286
+ continue;
287
+ }
288
+
289
+ sqlParts.push(this.addSimpleConditionToSql(key, value, alias));
290
+ } else if (!(operators.includes(key)) && Array.isArray(value)) {
291
+ sqlParts.push(this.addInConditionToSql(key, value, alias));
292
+ } else {
293
+ if (['$or', '$and'].includes(key)) {
294
+ sqlParts.push(this.addLogicalOperatorToSql(value.map((cond: any) => this.conditionToSql(cond, alias)), key.toUpperCase().replace('$', '') as 'AND' | 'OR'));
295
+ }
296
+
297
+ for (const operator of operators) {
298
+ if (operator in value) {
299
+ switch (operator) {
300
+ case '$eq':
301
+ sqlParts.push(this.addSimpleConditionToSql(key, value['$eq'], alias, '='));
302
+ break;
303
+ case '$ne':
304
+ sqlParts.push(this.addSimpleConditionToSql(key, value['$ne'], alias, '!='));
305
+ break;
306
+ case '$in':
307
+ sqlParts.push(this.addInConditionToSql(key, value['$in'], alias));
308
+ break;
309
+ case '$nin':
310
+ sqlParts.push(`${alias}.${key} NOT IN (${value['$nin'].map((val: any) => this.t(val)).join(", ")})`);
311
+ break;
312
+ case '$like':
313
+ sqlParts.push(`${alias}.${key} LIKE '${value['$like']}'`);
314
+ break;
315
+ case '$gt':
316
+ sqlParts.push(`${alias}.${key} > ${value['$gt']}`);
317
+ break;
318
+ case '$gte':
319
+ sqlParts.push(`${alias}.${key} >= ${value['$gte']}`);
320
+ break;
321
+ case '$lt':
322
+ sqlParts.push(`${alias}.${key} < ${value['$lt']}`);
323
+ break;
324
+ case '$lte':
325
+ sqlParts.push(`${alias}.${key} <= ${value['$lte']}`);
326
+ break;
327
+ case '$and':
328
+ case '$or':
329
+ const parts = value[operator].map((cond: any) => this.conditionToSql(cond, alias))
330
+ sqlParts.push(this.addLogicalOperatorToSql(parts, operator.toUpperCase().replace('$', '') as 'AND' | 'OR'));
331
+ break;
332
+ }
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ return this.addLogicalOperatorToSql(sqlParts, 'AND');
339
+ }
340
+
341
+ private t(value: any) {
342
+ return (typeof value === 'string') ? `'${value}'` : value;
343
+ }
344
+
345
+ private applyJoin(relationShip: Relationship<any>, value: FilterQuery<any>, alias: string) {
346
+ const {tableName, schema} = this.getTableName();
347
+ const {
348
+ tableName: joinTableName,
349
+ schema: joinSchema,
350
+ } = this.entityStorage.get((relationShip.entity() as Function)) || {
351
+ tableName: (relationShip.entity() as Function).name.toLowerCase(),
352
+ schema: 'public',
353
+ };
354
+ let originPrimaryKey = 'id';
355
+ for (const prop in this.entity.showProperties) {
356
+ if (this.entity.showProperties[prop].options.isPrimary) {
357
+ originPrimaryKey = prop;
358
+ break;
359
+ }
360
+ }
361
+ const joinAlias = `${this.getAlias(joinTableName)}`;
362
+ const joinWhere = this.conditionToSql(value, joinAlias)
363
+ this.statements.join = this.statements.join || [];
364
+ let on = '';
365
+
366
+ switch (relationShip.relation) {
367
+ case "one-to-many":
368
+ on = `${joinAlias}.${this.getFkKey(relationShip)} = ${alias}.${originPrimaryKey}`;
369
+ break;
370
+ case "many-to-one":
371
+ on = `${alias}.${relationShip.propertyKey as string} = ${joinAlias}.${this.getFkKey(relationShip)}`;
372
+ break;
373
+ }
374
+
375
+ this.statements.join.push({
376
+ joinAlias: joinAlias,
377
+ joinTable: joinTableName,
378
+ joinSchema: joinSchema || 'public',
379
+ joinWhere: joinWhere,
380
+ joinProperty: relationShip.propertyKey as string,
381
+ originAlias: alias,
382
+ originSchema: schema,
383
+ originTable: tableName,
384
+ propertyKey: relationShip.propertyKey,
385
+ joinEntity: (relationShip.entity() as Function),
386
+ type: 'LEFT',
387
+ // @ts-ignore
388
+ on,
389
+ originalEntity: relationShip.originalEntity as Function,
390
+ })
391
+
392
+ return joinWhere;
393
+ }
394
+
395
+ private getFkKey(relationShip: Relationship<any>): string | number {
396
+ // se for nullable, deverá retornar o primary key da entidade target
397
+ if (typeof relationShip.fkKey === 'undefined') {
398
+ return 'id'; // TODO: Pegar dinamicamente o primary key da entidade target
399
+ }
400
+
401
+ // se o fkKey é uma função, ele retornará a propriedade da entidade que é a chave estrangeira
402
+ // precisamos pegar o nome dessa propriedade
403
+ if (typeof relationShip.fkKey === 'string') {
404
+ return relationShip.fkKey;
405
+ }
406
+
407
+ const match = /\.(?<propriedade>[\w]+)/.exec(relationShip.fkKey.toString());
408
+ return match ? match.groups!.propriedade : '';
409
+ }
410
+
411
+ // private conditionLogicalOperatorToSql<T extends typeof BaseEntity>(conditions: Condition<T>[], operator: 'AND' | 'OR'): string {
412
+ // const sqlParts = conditions.map(cond => this.conditionToSql(cond));
413
+ // return this.addLogicalOperatorToSql(sqlParts, operator);
414
+ // }
415
+
416
+
417
+ private getEntity(model: new () => T) {
418
+ const entity = this.entityStorage.get((model as Function));
419
+ this.model = model;
420
+
421
+ if (!entity) {
422
+ throw new Error('Entity not found');
423
+ }
424
+
425
+ this.entity = entity;
426
+ }
427
+
428
+ private transformToModel<T>(data: any): T {
429
+ const instance = new (this.model as any)()
430
+
431
+ instance.$_isPersisted = true;
432
+ const entitiesByAlias: { [key: string]: Function } = {
433
+ [this.statements.alias!]: instance,
434
+ }
435
+ const entitiesOptions: Map<string, Options> = new Map();
436
+ entitiesOptions.set(this.statements.alias!, this.entityStorage.get(instance.constructor)!)
437
+
438
+ if (this.statements.join) {
439
+ this.statements.join.forEach(join => {
440
+ const joinInstance = new (join.joinEntity! as any)();
441
+ joinInstance.$_isPersisted = true;
442
+ entitiesByAlias[join.joinAlias] = joinInstance;
443
+ entitiesOptions.set(join.joinAlias, this.entityStorage.get(joinInstance.constructor)!)
444
+ })
445
+ }
446
+
447
+ Object.entries(data).forEach(([key, value]) => {
448
+ const [alias, prop] = key.split('_');
449
+ const entity = entitiesByAlias[alias];
450
+ if (!entity) {
451
+ return;
452
+ }
453
+
454
+ if (entitiesOptions.get(alias)!.showProperties.hasOwnProperty(prop)) {
455
+ // @ts-ignore
456
+ entity[prop] = value;
457
+ }
458
+ });
459
+
460
+ if (this.statements.join) {
461
+ this.statements.join.forEach(join => {
462
+ const {joinAlias, originAlias, propertyKey} = join;
463
+ const originEntity = entitiesByAlias[originAlias];
464
+ const joinEntity = entitiesByAlias[joinAlias];
465
+ const property = entitiesOptions.get(originAlias)!.relations.find(rel => rel.propertyKey === propertyKey);
466
+
467
+ if (!originEntity || !joinEntity) {
468
+ return;
469
+ }
470
+
471
+ // @ts-ignore
472
+ originEntity[propertyKey] = (property.type === Array) ? (originEntity[propertyKey]) ? [...originEntity[propertyKey], joinEntity] : [joinEntity] : joinEntity;
473
+ })
474
+ }
475
+
476
+ return instance;
477
+ }
478
+
479
+ /**
480
+ * Retrieves an alias for a given table name.
481
+ *
482
+ * @param {string} tableName - The name of the table.
483
+ * @private
484
+ * @returns {string} - The alias for the table name.
485
+ */
486
+ private getAlias(tableName: string): string {
487
+ const alias = tableName.split('').shift() || '';
488
+
489
+ let counter = 1;
490
+ let uniqueAlias = `${alias}${counter}`;
491
+
492
+ while (this.aliases.has(uniqueAlias)) {
493
+ counter++;
494
+ uniqueAlias = `${alias}${counter}`;
495
+ }
496
+
497
+ this.aliases.add(uniqueAlias);
498
+ return uniqueAlias;
499
+ }
500
+
501
+ private getColumnsEntity(entity: Function, alias: string): string [] {
502
+ const e = this.entityStorage.get(entity);
503
+ if (!e) {
504
+ throw new Error('Entity not found');
505
+ }
506
+
507
+ return Object.keys(e.showProperties).map(key => `${alias}."${key}" as "${alias}_${key}"`);
508
+ }
509
+
510
+ private withDefaultValues(values: any, entityOptions: Options) {
511
+ const property = Object.entries(entityOptions.showProperties).filter(([_, value]) => value.options.onInsert);
512
+ const defaultProperties = Object.entries(entityOptions.showProperties).filter(([_, value]) => value.options.default);
513
+
514
+ for (const [key, property] of defaultProperties) {
515
+ if (typeof values[key] === 'undefined') {
516
+ if (typeof property.options.default === 'function') {
517
+ values[key] = eval(property.options.default());
518
+ } else {
519
+ values[key] = eval(property.options.default);
520
+ }
521
+ }
522
+ }
523
+
524
+ property.forEach(([key, property]) => {
525
+ values[key] = property.options.onInsert!();
526
+ this.updatedColumns.push(`${this.statements.alias}."${key}" as "${this.statements.alias}_${key}"`)
527
+ });
528
+
529
+ return values;
530
+ }
531
+
532
+ private withUpdatedValues(values: any, entityOptions: Options) {
533
+ const property = Object.entries(entityOptions.showProperties).filter(([_, value]) => value.options.onUpdate);
534
+
535
+ property.forEach(([key, property]) => {
536
+ values[key] = property.options.onUpdate!();
537
+ this.updatedColumns.push(`${this.statements.alias}."${key}" as "${this.statements.alias}_${key}"`)
538
+ });
539
+
540
+ return values;
541
+ }
542
+ }
package/src/cheetah.ts ADDED
@@ -0,0 +1,28 @@
1
+ import {Migrator} from "./migration/migrator";
2
+ import { Command } from 'commander';
3
+
4
+ const program = new Command();
5
+
6
+ program
7
+ .name('cheetah-cli')
8
+ .description('CLI to Cheetah.js ORM ')
9
+
10
+ program.command('migration:generate')
11
+ .description('generate a new migration file with a diff')
12
+ .action(async (str, options) => {
13
+ const migrator = new Migrator()
14
+ await migrator.initConfigFile()
15
+ await migrator.createMigration()
16
+ process.exit(0)
17
+ });
18
+
19
+ program.command('migration:run')
20
+ .description('run all pending migrations')
21
+ .action(async (str, options) => {
22
+ const migrator = new Migrator()
23
+ await migrator.initConfigFile()
24
+ await migrator.migrate()
25
+ process.exit(0)
26
+ });
27
+
28
+ program.parse();
@@ -0,0 +1,4 @@
1
+ export const ENTITIES = 'cheetah:entities';
2
+ export const PROPERTIES = 'cheetah:properties';
3
+ export const PROPERTIES_METADATA = 'cheetah:properties:metadata';
4
+ export const PROPERTIES_RELATIONS = 'cheetah:properties:relations';
@@ -0,0 +1,10 @@
1
+ import { ENTITIES } from '../constants';
2
+ import { Metadata } from '@cheetah.js/core';
3
+
4
+ export function Entity(options?: {tableName?: string}): ClassDecorator {
5
+ return (target) => {
6
+ const entities = Metadata.get(ENTITIES, Reflect) || [];
7
+ entities.push({target, options});
8
+ Metadata.set(ENTITIES, entities, Reflect)
9
+ };
10
+ }
@@ -0,0 +1,18 @@
1
+ import { Metadata } from '@cheetah.js/core';
2
+
3
+ export function Index<T>(options?: { properties: (keyof T)[] }): PropertyDecorator {
4
+ return (target: any, propertyKey: symbol | string) => {
5
+ const indexes: {name: string, properties: string[]}[] = Metadata.get('indexes', target.constructor) || [];
6
+ let index: {name: string, properties: string[]};
7
+
8
+ if (options && options.properties) {
9
+ const properties = options.properties as any;
10
+ index = {name: `${properties.join('_')}_index`, properties: options.properties as any};
11
+ } else {
12
+ index = {name: `${propertyKey as string}_index`, properties: [propertyKey as string]} ;
13
+ }
14
+
15
+ indexes.push(index);
16
+ Metadata.set('indexes', indexes, target.constructor);
17
+ };
18
+ }
@@ -0,0 +1,19 @@
1
+ import { PROPERTIES_RELATIONS } from '../constants';
2
+ import { EntityName, Relationship } from '../driver/driver.interface';
3
+ import { Metadata } from '@cheetah.js/core';
4
+
5
+ export function OneToMany<T>(entity: () => EntityName<T>, fkKey: (string & keyof T) | ((e: T) => any)): PropertyDecorator {
6
+ return (target, propertyKey) => {
7
+ const existing: Relationship<T>[] = Metadata.get(PROPERTIES_RELATIONS, target.constructor) || [];
8
+ existing.push({relation: 'one-to-many', propertyKey, isRelation: true, entity, fkKey, type: Metadata.getType(target, propertyKey)});
9
+ Metadata.set(PROPERTIES_RELATIONS, existing, target.constructor);
10
+ };
11
+ }
12
+
13
+ export function ManyToOne<T>(entity: () => EntityName<T>): PropertyDecorator {
14
+ return (target, propertyKey) => {
15
+ const existing: Relationship<T>[] = Metadata.get(PROPERTIES_RELATIONS, target.constructor) || [];
16
+ existing.push({relation: 'many-to-one', propertyKey, isRelation: true, entity, type: Metadata.getType(target, propertyKey)});
17
+ Metadata.set(PROPERTIES_RELATIONS, existing, target.constructor);
18
+ };
19
+ }
@@ -0,0 +1,6 @@
1
+ import { Property, PropertyOptions } from './property.decorator';
2
+
3
+ export function PrimaryKey(options?: Omit<PropertyOptions, 'isPrimary'>): PropertyDecorator {
4
+ const isPrimary = true;
5
+ return Property({ ...options, isPrimary, unique: true });
6
+ }
@@ -0,0 +1,41 @@
1
+ import { PROPERTIES, PROPERTIES_METADATA } from '../constants';
2
+ import { getDefaultLength } from '../utils';
3
+ import { Metadata } from '@cheetah.js/core';
4
+
5
+ export type PropertyOptions = {
6
+ isPrimary?: boolean;
7
+ nullable?: boolean;
8
+ default?: any;
9
+ length?: number;
10
+ unique?: boolean;
11
+ autoIncrement?: boolean;
12
+ onUpdate?: () => any;
13
+ onInsert?: () => any;
14
+ }
15
+
16
+ export type Prop = { propertyKey: any, options: PropertyOptions | undefined };
17
+
18
+ export function Property(options?: PropertyOptions): PropertyDecorator {
19
+ return (target, propertyKey) => {
20
+ const properties: Prop[] = Metadata.get(PROPERTIES, target.constructor) || [];
21
+ const type = Metadata.getType(target, propertyKey);
22
+ const length = (options && options.length) || getDefaultLength(type.name);
23
+ options = {length, ...options};
24
+ properties.push({propertyKey, options});
25
+ Metadata.set(PROPERTIES, properties, target.constructor);
26
+
27
+ if (options.isPrimary) {
28
+ const indexes: {name: string, properties: string[]}[] = Metadata.get('indexes', target.constructor) || [];
29
+ indexes.push({name: `[TABLE]_pkey`, properties: [propertyKey as string]});
30
+ Metadata.set('indexes', indexes, target.constructor);
31
+ }
32
+
33
+ properties.forEach((property: Prop) => {
34
+ const types = Metadata.get(PROPERTIES_METADATA, target.constructor) || {};
35
+ const type = Metadata.getType(target, property.propertyKey);
36
+ types[property.propertyKey] = {type, options: property.options};
37
+ Metadata.set(PROPERTIES_METADATA, types, target.constructor);
38
+ });
39
+ };
40
+ }
41
+