@cheetah.js/orm 0.1.3 → 0.1.5

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 (49) hide show
  1. package/README.md +54 -0
  2. package/dist/SqlBuilder.d.ts +8 -3
  3. package/dist/SqlBuilder.js +29 -1
  4. package/dist/SqlBuilder.js.map +1 -1
  5. package/dist/bun/index.d.ts +3 -0
  6. package/dist/bun/index.js +946 -113
  7. package/dist/bun/index.js.map +28 -14
  8. package/dist/common/email.vo.d.ts +0 -1
  9. package/dist/common/email.vo.js +2 -2
  10. package/dist/common/email.vo.js.map +1 -1
  11. package/dist/common/uuid.d.ts +4 -0
  12. package/dist/common/uuid.js +7 -0
  13. package/dist/common/uuid.js.map +1 -0
  14. package/dist/common/value-object.d.ts +9 -9
  15. package/dist/common/value-object.js +1 -2
  16. package/dist/common/value-object.js.map +1 -1
  17. package/dist/decorators/property.decorator.d.ts +1 -0
  18. package/dist/decorators/property.decorator.js.map +1 -1
  19. package/dist/domain/base-entity.d.ts +4 -3
  20. package/dist/domain/base-entity.js +0 -6
  21. package/dist/domain/base-entity.js.map +1 -1
  22. package/dist/domain/entities.js +3 -1
  23. package/dist/domain/entities.js.map +1 -1
  24. package/dist/driver/driver.interface.d.ts +8 -4
  25. package/dist/driver/pg-driver.js +3 -2
  26. package/dist/driver/pg-driver.js.map +1 -1
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.js +3 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/migration/diff-calculator.js +38 -9
  31. package/dist/migration/diff-calculator.js.map +1 -1
  32. package/dist/utils.js +1 -13
  33. package/dist/utils.js.map +1 -1
  34. package/package.json +12 -1
  35. package/src/SqlBuilder.ts +34 -5
  36. package/src/common/email.vo.ts +3 -3
  37. package/src/common/uuid.ts +8 -0
  38. package/src/common/value-object.ts +11 -12
  39. package/src/decorators/property.decorator.ts +1 -0
  40. package/src/domain/base-entity.ts +2 -10
  41. package/src/domain/entities.ts +3 -1
  42. package/src/driver/driver.interface.ts +13 -4
  43. package/src/driver/pg-driver.ts +5 -3
  44. package/src/index.ts +4 -1
  45. package/src/migration/diff-calculator.ts +41 -12
  46. package/src/utils.ts +1 -16
  47. package/test/domain/base-entity.spec.ts +45 -1
  48. package/test/migration/{migator.spec.ts → migrator.spec.ts} +45 -0
  49. package/test/migration/test.sql +1 -5
@@ -0,0 +1,8 @@
1
+ import { ValueObject } from '@cheetah.js/orm';
2
+
3
+ export class Uuid extends ValueObject<string, Uuid> {
4
+
5
+ protected validate(value: string): boolean {
6
+ return /^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i.test(value);
7
+ }
8
+ }
@@ -5,6 +5,16 @@ type VoExtended<T, Vo> = Vo extends ValueObject<T, Vo>
5
5
  : ValueObject<T, Vo>;
6
6
 
7
7
  export abstract class ValueObject<T, Vo> {
8
+ /**
9
+ * Validates the value of the Value Object.
10
+ * It is abstract so that each Value Object can implement its own validation.
11
+ * It is protected from being called directly.
12
+ *
13
+ * @param value
14
+ * @protected
15
+ */
16
+ protected abstract validate(value: T): boolean;
17
+
8
18
  /**
9
19
  * Valor do Value Object.
10
20
  * É privado para não ser alterado diretamente.
@@ -16,8 +26,7 @@ export abstract class ValueObject<T, Vo> {
16
26
 
17
27
  constructor(value: T) {
18
28
  if (!this.validate(value)) {
19
- //@ts-ignore
20
- throw new HttpException(`Invalid value for ${this.name}`, 400);
29
+ throw new HttpException(`Invalid value for ${this.constructor.name}`, 400);
21
30
  }
22
31
 
23
32
  this.setValue(value);
@@ -55,16 +64,6 @@ export abstract class ValueObject<T, Vo> {
55
64
  return this.getValue() === vo.getValue();
56
65
  }
57
66
 
58
- /**
59
- * Validates the value of the Value Object.
60
- * It is abstract so that each Value Object can implement its own validation.
61
- * It is protected from being called directly.
62
- *
63
- * @param value
64
- * @protected
65
- */
66
- protected abstract validate(value: T): boolean;
67
-
68
67
  /**
69
68
  * Sets the value of the Value Object.
70
69
  *
@@ -8,6 +8,7 @@ export type PropertyOptions = {
8
8
  default?: any;
9
9
  length?: number;
10
10
  unique?: boolean;
11
+ dbType?: 'varchar' | 'text' | 'int' | 'bigint' | 'float' | 'double' | 'decimal' | 'date' | 'datetime' | 'time' | 'timestamp' | 'boolean' | 'json' | 'jsonb' | 'enum' | 'array' | 'uuid';
11
12
  autoIncrement?: boolean;
12
13
  onUpdate?: () => any;
13
14
  onInsert?: () => any;
@@ -1,5 +1,5 @@
1
1
  import { SqlBuilder } from '../SqlBuilder';
2
- import { FilterQuery, FindOneOption, FindOptions, InstanceOf } from '../driver/driver.interface';
2
+ import { FilterQuery, FindOneOption, FindOptions, InstanceOf, ValueOrInstance } from '../driver/driver.interface';
3
3
 
4
4
  export abstract class BaseEntity {
5
5
  private _oldValues: any = {};
@@ -112,21 +112,13 @@ export abstract class BaseEntity {
112
112
 
113
113
  static async create<T extends BaseEntity>(
114
114
  this: { new(): T } & typeof BaseEntity,
115
- where: Partial<InstanceOf<T>>,
115
+ where: Partial<{ [K in keyof T]: ValueOrInstance<T[K]> }>,
116
116
  ): Promise<T> {
117
117
  return this.createQueryBuilder<T>()
118
118
  .insert(where)
119
119
  .executeAndReturnFirstOrFail();
120
120
  }
121
121
 
122
- static getTableName(): string {
123
- if ('tableName' in this) {
124
- return (this as any).tableName;
125
- }
126
-
127
- return this.name.toLowerCase();
128
- }
129
-
130
122
  public async save() {
131
123
  const qb = this.createQueryBuilder()
132
124
 
@@ -72,9 +72,10 @@ export class EntityStorage {
72
72
  let properties: ColumnsInfo[] = Object.entries(values.showProperties).map(([key, value]) => {
73
73
  return {
74
74
  name: key,
75
- type: value.type.name,
75
+ type: value.options.dbType ?? value.type.name,
76
76
  nullable: value.options?.nullable,
77
77
  default: value.options?.default,
78
+ autoIncrement: value.options?.autoIncrement,
78
79
  primary: value.options?.isPrimary,
79
80
  unique: value.options?.unique,
80
81
  length: value.options?.length,
@@ -91,6 +92,7 @@ export class EntityStorage {
91
92
  unique: relation.unique,
92
93
  length: relation.length || getDefaultLength(type),
93
94
  default: relation.default,
95
+ autoIncrement: relation.autoIncrement,
94
96
  primary: relation.isPrimary,
95
97
  foreignKeys: [
96
98
  {
@@ -1,6 +1,7 @@
1
1
  import { PropertyOptions } from '../decorators/property.decorator';
2
2
  import { Collection } from '../domain/collection';
3
3
  import { Reference } from '../domain/reference';
4
+ import { ValueObject } from '../common/value-object';
4
5
 
5
6
  export interface DriverInterface {
6
7
  connectionString: string;
@@ -37,6 +38,9 @@ export interface DriverInterface {
37
38
  rollbackTransaction(): Promise<void>;
38
39
  }
39
40
 
41
+ // @ts-ignore
42
+ export type ValueOrInstance<T> = T extends ValueObject<any, any> ? T | T['value'] : NonNullable<T>;
43
+
40
44
  export type SnapshotConstraintInfo = {
41
45
  indexName: string;
42
46
  consDef: string;
@@ -158,6 +162,7 @@ export type ColumnsInfo = {
158
162
  default?: string | null;
159
163
  primary?: boolean;
160
164
  unique?: boolean;
165
+ autoIncrement?: boolean;
161
166
  length?: number;
162
167
  foreignKeys?: ForeignKeyInfo[];
163
168
  }
@@ -175,6 +180,7 @@ export type ColDiff = {
175
180
  primary?: boolean;
176
181
  unique?: boolean;
177
182
  nullable?: boolean;
183
+ autoIncrement?: boolean;
178
184
  foreignKeys?: ForeignKeyInfo[];
179
185
  };
180
186
  }
@@ -226,17 +232,19 @@ export type OperatorMap<T> = {
226
232
  $like?: string;
227
233
 
228
234
  };
229
- export type ExcludeFunctions<T, K extends keyof T> = T[K] extends Function ? never : (K extends symbol ? never : K);
235
+ export type ExcludeFunctions<T, K extends keyof T> = T[K] extends Function ? ValueOrInstance<T> : (K extends symbol ? never : K);
230
236
  export type Scalar = boolean | number | string | bigint | symbol | Date | RegExp | Uint8Array | {
231
237
  toHexString(): string;
232
238
  };
233
239
  //TODO: editar
234
- export type ExpandProperty<T> = T extends (infer U)[] ? NonNullable<U> : NonNullable<T> ;
235
- export type ExpandScalar<T> = null | (T extends string ? T | RegExp : T extends Date ? Date | string : T);
240
+ export type ExpandProperty<T> = T extends (infer U)[] ? ValueOrInstance<U> : ValueOrInstance<T> ;
241
+ export type ExpandScalar<T> = null | ValueOrInstance<T> | (T extends string ? T | RegExp : T extends Date ? Date | string : T);
236
242
  type ExpandObject<T> = T extends object ? T extends Scalar ? never : {
243
+ // @ts-ignore
237
244
  -readonly [K in keyof T as ExcludeFunctions<T, K>]?: Query<ExpandProperty<T[K]>> | FilterValue<ExpandProperty<T[K]>> | null;
238
245
  } : never;
239
246
  export type EntityProps<T> = {
247
+ // @ts-ignore
240
248
  -readonly [K in keyof T as ExcludeFunctions<T, K>]?: T[K];
241
249
  };
242
250
  export type Query<T> = T extends object ? T extends Scalar ? never : FilterQuery<T> : FilterValue<T>;
@@ -245,7 +253,7 @@ export type FilterValue<T> = OperatorMap<FilterValue2<T>> | FilterValue2<T> | Fi
245
253
  export type EntityClass<T> = Function & { prototype: T };
246
254
  export type EntityName<T> = string | EntityClass<T> | { name: string };
247
255
  export type ObjectQuery<T> = ExpandObject<T> & OperatorMap<T>;
248
- export type FilterQuery<T> = ObjectQuery<T> | NonNullable<ExpandScalar<Primary<T>>> | NonNullable<EntityProps<T> & OperatorMap<T>> | FilterQuery<T>[];
256
+ export type FilterQuery<T> = ValueOrInstance<T> | ObjectQuery<T> | NonNullable<ExpandScalar<Primary<T>>> | NonNullable<EntityProps<T> & OperatorMap<T>> | FilterQuery<T>[];
249
257
 
250
258
  export type Relationship<T> = {
251
259
  isRelation?: boolean;
@@ -315,6 +323,7 @@ export declare enum QueryOrderNumeric {
315
323
  export type QueryOrderKeysFlat = QueryOrder | QueryOrderNumeric | keyof typeof QueryOrder;
316
324
  export type QueryOrderKeys<T> = QueryOrderKeysFlat | QueryOrderMap<T>;
317
325
  export type QueryOrderMap<T> = {
326
+ // @ts-ignore
318
327
  [K in keyof T as ExcludeFunctions<T, K>]?: QueryOrderKeys<ExpandProperty<T[K]>>;
319
328
  };
320
329
  export type EntityField<T, P extends string = never> = AutoPath<T, P, '*'>;
@@ -29,9 +29,11 @@ export class PgDriver implements DriverInterface {
29
29
 
30
30
  getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]) {
31
31
  return `CREATE TABLE "${schema}"."${tableName}" (${creates.map(colDiff => {
32
- let sql = `"${colDiff.colName}" ${colDiff.colType}${(colDiff.colLength ? `(${colDiff.colLength})` : '')}`;
33
-
34
- if (!colDiff.colChanges?.nullable) {
32
+ const isAutoIncrement = colDiff.colChanges?.autoIncrement;
33
+
34
+ let sql = `"${colDiff.colName}" ${isAutoIncrement ? 'SERIAL' : colDiff.colType + (colDiff.colLength ? `(${colDiff.colLength})` : '')}`;
35
+
36
+ if (!isAutoIncrement && !colDiff.colChanges?.nullable) {
35
37
  sql += ' NOT NULL';
36
38
  }
37
39
 
package/src/index.ts CHANGED
@@ -9,4 +9,7 @@ export * from './domain/base-entity'
9
9
  export * from './driver/pg-driver'
10
10
  export * from './utils'
11
11
  export * from './driver/driver.interface'
12
- export * from './entry'
12
+ export * from './entry'
13
+ export * from './common/value-object'
14
+ export * from './common/email.vo'
15
+ export * from './common/uuid'
@@ -77,11 +77,14 @@ export class DiffCalculator {
77
77
 
78
78
  private checkIndexes(bdTable: SnapshotTable | undefined, entityTable: SnapshotTable | undefined, colDiffs: ColDiff[]) {
79
79
  if ((bdTable && bdTable.indexes) || (entityTable && entityTable.indexes)) {
80
- if (!bdTable || !bdTable.indexes){
80
+ if (!bdTable || !bdTable.indexes) {
81
81
  colDiffs.push({
82
82
  actionType: 'INDEX',
83
83
  colName: '*',
84
- indexTables: entityTable!.indexes.map(index => ({name: index.indexName, properties: index.columnName.split(',')})),
84
+ indexTables: entityTable!.indexes.map(index => ({
85
+ name: index.indexName,
86
+ properties: index.columnName.split(','),
87
+ })),
85
88
  });
86
89
  }
87
90
 
@@ -89,7 +92,7 @@ export class DiffCalculator {
89
92
  colDiffs.push({
90
93
  actionType: 'INDEX',
91
94
  colName: '*',
92
- indexTables: bdTable!.indexes.map(index => ({ name: index.indexName })),
95
+ indexTables: bdTable!.indexes.map(index => ({name: index.indexName})),
93
96
  });
94
97
  }
95
98
  }
@@ -119,13 +122,15 @@ export class DiffCalculator {
119
122
  }
120
123
 
121
124
  private createNewColumn(entityCol: ColumnsInfo, colDiffs: ColDiff[]): ColDiff[] {
125
+ const colType = this.convertEntityTypeToSqlType(entityCol.type);
122
126
 
123
127
  colDiffs.push({
124
128
  actionType: 'CREATE',
125
129
  colName: entityCol.name,
126
- colType: this.convertEntityTypeToSqlType(entityCol.type),
127
- colLength: entityCol.length,
130
+ colType: colType.type,
131
+ colLength: entityCol.length ?? colType.len,
128
132
  colChanges: {
133
+ autoIncrement: entityCol.autoIncrement,
129
134
  default: entityCol.default,
130
135
  primary: entityCol.primary,
131
136
  unique: entityCol.unique,
@@ -138,12 +143,15 @@ export class DiffCalculator {
138
143
  }
139
144
 
140
145
  private diffColumnType(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
141
- if (bdCol.type !== this.convertEntityTypeToSqlType(entityCol.type) || bdCol.length !== entityCol.length) {
146
+ const colT = this.convertEntityTypeToSqlType(entityCol.type);
147
+ const colType = colT.type;
148
+ const length = entityCol.length ?? colT.len;
149
+ if (bdCol.type !== colType || bdCol.length !== length) {
142
150
  colDiffs.push({
143
151
  actionType: 'ALTER',
144
152
  colName: entityCol.name,
145
- colType: this.convertEntityTypeToSqlType(entityCol.type),
146
- colLength: entityCol.length,
153
+ colType: colType,
154
+ colLength: length,
147
155
  });
148
156
  }
149
157
  }
@@ -244,14 +252,35 @@ export class DiffCalculator {
244
252
  }
245
253
 
246
254
  // TODO: Precisa ser de acordo com o driver
247
- private convertEntityTypeToSqlType(entityType: string): string {
255
+ // adicionar 'varchar' | 'text' | 'int' | 'bigint' | 'float' | 'double' | 'decimal' | 'date' | 'datetime' | 'time' | 'timestamp' | 'boolean' | 'json' | 'jsonb' | 'enum' | 'array' | 'uuid'
256
+ private convertEntityTypeToSqlType(entityType: string): { type: string, len?: number } {
248
257
  switch (entityType) {
249
258
  case "Number":
250
- return "numeric";
259
+ case 'int':
260
+ return {type: 'numeric', len: 11};
261
+ case 'bigint':
262
+ return {type: 'bigint'};
263
+ case 'float':
264
+ return {type: 'float4'};
265
+ case 'double':
266
+ return {type: 'float8'};
267
+ case 'decimal':
268
+ return {type: 'decimal'};
251
269
  case "String":
252
- return "character varying";
270
+ case "varchar":
271
+ return {type: 'character varying', len: 255};
272
+ case "Boolean":
273
+ return {type: "boolean"};
274
+ case "Date":
275
+ return {type: "timestamp"};
276
+ case "Object":
277
+ return {type: "json"};
278
+ case 'uuid':
279
+ return {type: 'uuid'};
280
+ case 'text':
281
+ return {type: 'text'};
253
282
  default:
254
- return "character varying"
283
+ return {type: "character varying", len: 255};
255
284
  //... mais casos aqui ...
256
285
  }
257
286
  }
package/src/utils.ts CHANGED
@@ -1,18 +1,3 @@
1
1
  export function getDefaultLength(type: string): number {
2
- if (type === 'String') {
3
- return 255;
4
- }
5
-
6
- if (type === 'Number') {
7
- return 11;
8
- }
9
-
10
- if (type === 'Boolean') {
11
- return 1;
12
- }
13
-
14
- if (type === 'Date') {
15
- return 6;
16
- }
17
- return 255;
2
+ return null;
18
3
  }
@@ -1,6 +1,7 @@
1
- import { afterEach, beforeEach, describe, expect, jest, test, setSystemTime } from 'bun:test'
1
+ import { afterEach, beforeEach, describe, expect, jest, setSystemTime, test } from 'bun:test'
2
2
  import { app, execute, mockLogger, purgeDatabase, startDatabase } from '../node-database';
3
3
  import { BaseEntity, Entity, OneToMany, PrimaryKey, Property } from '../../src';
4
+ import { Email } from '../../src/common/email.vo';
4
5
 
5
6
  @Entity()
6
7
  class UserTest extends BaseEntity {
@@ -14,6 +15,15 @@ class UserTest extends BaseEntity {
14
15
  updatedAt: Date;
15
16
  }
16
17
 
18
+ @Entity()
19
+ class UserValue extends BaseEntity {
20
+ @PrimaryKey()
21
+ id: number;
22
+
23
+ @Property()
24
+ email: Email
25
+ }
26
+
17
27
  describe('Creation, update and deletion of entities', () => {
18
28
 
19
29
  const DLL = `
@@ -454,6 +464,40 @@ describe('Creation, update and deletion of entities', () => {
454
464
  expect(created!.updatedAt).toEqual(dateNow);
455
465
  })
456
466
 
467
+ test('When have a column with value-object', async() => {
468
+ const DLL = `
469
+ CREATE TABLE "uservalue"
470
+ (
471
+ "id" SERIAL PRIMARY KEY,
472
+ "email" varchar(255) NOT NULL
473
+ );
474
+ `;
475
+
476
+ await purgeDatabase()
477
+ await startDatabase(import.meta.path)
478
+ await execute(DLL)
479
+
480
+ Entity()(UserValue)
481
+ const created = await UserValue.create({
482
+ id: 1,
483
+ email: Email.from('test@test.com')
484
+ })
485
+
486
+ const find = await UserValue.findOne({
487
+ email: Email.from('test@test.com'),
488
+ id: 1
489
+ })
490
+
491
+ expect(created).toBeInstanceOf(UserValue);
492
+ expect(created!.id).toEqual(1);
493
+ expect(created!.email).toEqual(Email.from('test@test.com'));
494
+
495
+ expect(find).toBeInstanceOf(UserValue);
496
+ expect(find!.id).toEqual(1);
497
+ expect(find!.email).toEqual(Email.from('test@test.com'));
498
+ })
499
+
500
+
457
501
  async function createUser() {
458
502
  return User.create({
459
503
  email: 'test@test.com',
@@ -9,6 +9,7 @@ import * as fs from 'fs';
9
9
  import { Metadata } from '@cheetah.js/core';
10
10
  import { ENTITIES } from '../../src/constants';
11
11
  import { Index } from '../../src/decorators/index.decorator';
12
+ import { Email } from '../../src/common/email.vo';
12
13
 
13
14
  describe('Migration', () => {
14
15
 
@@ -248,4 +249,48 @@ describe('Migration', () => {
248
249
  expect(migrationContent.split('\n').length).toEqual(5)
249
250
  await execute(migrationContent);
250
251
  });
252
+
253
+ it('should create with value-objects', async () => {
254
+ class User extends BaseEntity {
255
+ @PrimaryKey()
256
+ id: number;
257
+
258
+ @Property({ dbType: 'text' })
259
+ email: Email;
260
+ }
261
+
262
+ Entity()(User);
263
+
264
+ const migrator = new Migrator();
265
+ await migrator.initConfigFile();
266
+ await migrator.createMigration( 'test');
267
+ const migrationFilePath = path.join(__dirname, '/test.sql');
268
+ const migrationContent = fs.readFileSync(migrationFilePath, {encoding: 'utf-8'});
269
+
270
+ expect(migrationContent).toContain("CREATE TABLE \"public\".\"user\" (\"id\" numeric(11) NOT NULL PRIMARY KEY UNIQUE,\"email\" text NOT NULL);")
271
+ expect(migrationContent.split('\n').length).toEqual(1)
272
+ await execute(migrationContent);
273
+ });
274
+
275
+ it('should create with auto-increment', async () => {
276
+ class User extends BaseEntity {
277
+ @PrimaryKey({ autoIncrement: true })
278
+ id: number;
279
+
280
+ @Property({ dbType: 'text' })
281
+ email: Email;
282
+ }
283
+
284
+ Entity()(User);
285
+
286
+ const migrator = new Migrator();
287
+ await migrator.initConfigFile();
288
+ await migrator.createMigration( 'test');
289
+ const migrationFilePath = path.join(__dirname, '/test.sql');
290
+ const migrationContent = fs.readFileSync(migrationFilePath, {encoding: 'utf-8'});
291
+
292
+ expect(migrationContent).toContain("CREATE TABLE \"public\".\"user\" (\"id\" SERIAL PRIMARY KEY UNIQUE,\"email\" text NOT NULL);")
293
+ expect(migrationContent.split('\n').length).toEqual(1)
294
+ await execute(migrationContent);
295
+ });
251
296
  })
@@ -1,5 +1 @@
1
- CREATE TABLE "public"."user" ("id" numeric(11) NOT NULL PRIMARY KEY UNIQUE,"email" character varying(255) NOT NULL);
2
- CREATE INDEX "id_email_index" ON "public"."user" ("id", "email");
3
- CREATE INDEX "email_index" ON "public"."user" ("email");
4
- CREATE TABLE "public"."address" ("id" numeric(11) NOT NULL PRIMARY KEY UNIQUE,"user" numeric(11) NOT NULL);
5
- ALTER TABLE "public"."address" ADD CONSTRAINT "address_user_fk" FOREIGN KEY ("user") REFERENCES "user" ("id");
1
+ CREATE TABLE "public"."user" ("id" SERIAL PRIMARY KEY UNIQUE,"email" text NOT NULL);