@koalarx/nest 4.0.6 → 4.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 (62) hide show
  1. package/cli/constants/cli-project-checklist.js +5 -0
  2. package/cli/constants/core-packages.js +4 -6
  3. package/cli/utils/install-module.js +21 -21
  4. package/cli/utils/parse-new-args.js +0 -1
  5. package/cli/utils/patch-app-test-module.js +2 -1
  6. package/cli/utils/patch-env.js +36 -0
  7. package/cli/utils/patch-main.js +3 -4
  8. package/cli/utils/remove-sample-parts.js +1 -2
  9. package/koala-nest/.env.example +12 -0
  10. package/koala-nest/src/application/common/pagination.request.ts +0 -5
  11. package/koala-nest/src/application/mapping/person.mapper.ts +0 -9
  12. package/koala-nest/src/application/person/jobs/cron/delete-inactive.job.ts +34 -17
  13. package/koala-nest/src/application/person/read-many/read-many-person.response.ts +2 -13
  14. package/koala-nest/src/core/background-services/cron-service/cron-job.handler.base.ts +36 -15
  15. package/koala-nest/src/core/background-services/event-service/event-queue.ts +2 -3
  16. package/koala-nest/src/core/common/list-response.base.ts +10 -0
  17. package/koala-nest/src/core/database/db-context.ts +3 -0
  18. package/koala-nest/src/core/database/entity.ts +9 -0
  19. package/koala-nest/src/core/env.ts +6 -0
  20. package/koala-nest/src/core/http/rate-limit.middleware.ts +36 -0
  21. package/koala-nest/src/core/tools/mapping/auto-map.ts +27 -3
  22. package/koala-nest/src/core/tools/mapping/auto-mapper.ts +25 -11
  23. package/koala-nest/src/core/tools/mapping/mapping-store.ts +13 -15
  24. package/koala-nest/src/core/utils/cron-expression-to-boolean.ts +32 -17
  25. package/koala-nest/src/core/utils/hash-password.ts +7 -2
  26. package/koala-nest/src/core/utils/initialize-undefined-array-props.ts +20 -0
  27. package/koala-nest/src/core/utils/resolve-cors-origins.ts +24 -0
  28. package/koala-nest/src/domain/dtos/pagination.dto.ts +87 -2
  29. package/koala-nest/src/domain/dtos/person-query.dto.ts +8 -0
  30. package/koala-nest/src/domain/entities/person/person-address.ts +2 -1
  31. package/koala-nest/src/domain/entities/person/person-contact.ts +2 -2
  32. package/koala-nest/src/domain/entities/person/person.ts +2 -4
  33. package/koala-nest/src/domain/entities/user/user.ts +1 -1
  34. package/koala-nest/src/host/bootstrap/apply-http-middleware.ts +22 -0
  35. package/koala-nest/src/host/main.ts +14 -14
  36. package/koala-nest/src/infra/common/redis-cache.service.ts +15 -4
  37. package/koala-nest/src/infra/database/data-source-factory.ts +4 -6
  38. package/koala-nest/src/infra/database/migrations/migration-datasource.ts +7 -0
  39. package/koala-nest/src/infra/repositories/person.repository.ts +3 -3
  40. package/koala-nest/src/infra/repositories/repository.base.ts +70 -1
  41. package/koala-nest/src/test/app-auth-test.module.ts +2 -1
  42. package/koala-nest/src/test/app-test.module.ts +2 -1
  43. package/koala-nest/src/test/application/delete-inactive.job.spec.ts +23 -10
  44. package/koala-nest/src/test/application/update-person.handler.spec.ts +42 -0
  45. package/koala-nest/src/test/core/cron-expression-to-boolean.spec.ts +20 -1
  46. package/koala-nest/src/test/core/env.spec.ts +19 -0
  47. package/koala-nest/src/test/core/http/rate-limit.middleware.spec.ts +62 -0
  48. package/koala-nest/src/test/core/initialize-undefined-array-props.spec.ts +63 -0
  49. package/koala-nest/src/test/core/mapping.spec.ts +15 -0
  50. package/koala-nest/src/test/core/pagination-typeorm.spec.ts +74 -0
  51. package/koala-nest/src/test/core/resolve-cors-origins.spec.ts +22 -0
  52. package/koala-nest/src/test/core/sync-improvements.spec.ts +39 -0
  53. package/koala-nest/src/test/e2e-context.ts +4 -1
  54. package/koala-nest/src/test/host/controllers/person/lazy-loading.e2e.spec.ts +16 -20
  55. package/koala-nest/src/test/infra/redis-cache.service.spec.ts +8 -0
  56. package/koala-nest/src/test/setup-e2e.ts +6 -43
  57. package/koala-nest/src/test/utils/configure-test-app.ts +2 -8
  58. package/koala-nest/src/test/utils/create-e2e-database.ts +24 -16
  59. package/koala-nest/src/test/utils/e2e-database-client.ts +2 -2
  60. package/koala-nest/src/test/utils/in-memory-base.repository.ts +80 -0
  61. package/package.json +1 -1
  62. package/koala-nest/src/core/utils/icomparable.ts +0 -1
@@ -35,6 +35,14 @@ class RedisStub {
35
35
  );
36
36
  }
37
37
 
38
+ scan(cursor: string, ...args: unknown[]) {
39
+ const pattern = String(args[1] ?? '');
40
+ const prefix = pattern.replace(/\*$/, '');
41
+ const keys = [...this.store.keys()].filter((key) => key.startsWith(prefix));
42
+
43
+ return Promise.resolve(['0', keys]);
44
+ }
45
+
38
46
  disconnect() {}
39
47
  }
40
48
 
@@ -1,60 +1,23 @@
1
1
  import 'reflect-metadata';
2
- import { delay } from '@koalarx/utils/KlDelay';
3
2
  import { createE2EDatabase } from '@/test/utils/create-e2e-database';
4
3
  import { E2EDatabaseClient } from '@/test/utils/e2e-database-client';
5
4
  import { Pool } from 'pg';
6
5
 
7
6
  class E2EPostgresClient extends E2EDatabaseClient {
8
- private baseUrl: URL;
9
-
10
7
  public pool: Pool;
11
8
 
12
9
  constructor(url: string, schemaName: string) {
13
10
  super(url, schemaName);
14
-
15
- this.baseUrl = new URL(this.url);
16
- this.baseUrl.pathname = `/${this.schemaName}`;
17
-
18
- this.pool = this.createSession();
11
+ this.pool = new Pool({ connectionString: url });
19
12
  }
20
13
 
21
- private createSession(idleTimeout?: number) {
22
- return new Pool({
23
- connectionString: this.baseUrl.toString(),
24
- ...(idleTimeout ? { idleTimeoutMillis: idleTimeout } : {}),
25
- });
14
+ async createSchema(schemaName: string): Promise<void> {
15
+ await this.pool.query(`CREATE SCHEMA "${schemaName}"`);
26
16
  }
27
17
 
28
- async createDatabase(schemaName: string): Promise<void> {
29
- this.baseUrl.pathname = '/postgres';
30
-
31
- const pool = this.createSession();
32
-
33
- await pool.query(`CREATE DATABASE "${schemaName}"`);
34
- await pool.end();
35
-
36
- this.baseUrl.pathname = `/${schemaName}`;
37
- }
38
-
39
- async dropDatabase(): Promise<void> {
18
+ async dropSchema(): Promise<void> {
19
+ await this.pool.query(`DROP SCHEMA IF EXISTS "${this.schemaName}" CASCADE`);
40
20
  await this.pool.end();
41
-
42
- await delay(1000);
43
-
44
- this.baseUrl.pathname = '/postgres';
45
- const pool = this.createSession(100);
46
-
47
- await pool.query(`
48
- SELECT pg_terminate_backend(pg_stat_activity.pid)
49
- FROM pg_stat_activity
50
- WHERE pg_stat_activity.datname = '${this.schemaName}'
51
- AND pid <> pg_backend_pid()
52
- `);
53
-
54
- await delay(500);
55
-
56
- await pool.query(`DROP DATABASE IF EXISTS "${this.schemaName}"`);
57
- await pool.end();
58
21
  }
59
22
  }
60
23
 
@@ -62,5 +25,5 @@ const { client } = await createE2EDatabase(E2EPostgresClient);
62
25
  export const pgClient = client;
63
26
 
64
27
  afterAll(async () => {
65
- await pgClient.dropDatabase();
28
+ await pgClient.dropSchema();
66
29
  });
@@ -1,17 +1,11 @@
1
+ import { applyHttpMiddleware } from '@/host/bootstrap/apply-http-middleware';
1
2
  import { ErrorsFilter } from '@/host/filters/errors.filter';
2
3
  import { ILoggingService } from '@/domain/common/ilogging.service';
3
4
  import { INestApplication } from '@nestjs/common';
4
5
  import { HttpAdapterHost } from '@nestjs/core';
5
- import cookieParser from 'cookie-parser';
6
6
 
7
7
  export function setupTestApp(app: INestApplication) {
8
- app.use(cookieParser());
9
-
10
- app.enableCors({
11
- credentials: true,
12
- origin: true,
13
- optionsSuccessStatus: 200,
14
- });
8
+ applyHttpMiddleware(app);
15
9
 
16
10
  const { httpAdapter } = app.get(HttpAdapterHost);
17
11
  const loggingService = app.get(ILoggingService);
@@ -2,7 +2,7 @@ import { Type } from '@nestjs/common';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { readFileSync } from 'node:fs';
5
- import { setE2EDatabaseUrl } from '../e2e-context';
5
+ import { setE2EDatabaseContext } from '../e2e-context';
6
6
  import { E2EDatabaseClient } from './e2e-database-client';
7
7
 
8
8
  function resolveMigrationRunner(): string {
@@ -19,35 +19,37 @@ function resolveMigrationRunner(): string {
19
19
  return 'node --import ts-node/register/transpile-only';
20
20
  }
21
21
 
22
- function generateUniqueDatabaseURL() {
23
- const schemaId = randomUUID();
22
+ function generateUniqueSchemaConfig() {
23
+ const schemaName = `e2e_${randomUUID().replace(/-/g, '_')}`;
24
24
 
25
25
  if (!process.env.DATABASE_URL) {
26
26
  throw new Error('Please provide a DATABASE_URL environment variable');
27
27
  }
28
28
 
29
- const url = new URL(process.env.DATABASE_URL);
30
- url.pathname = `/${schemaId}`;
31
-
32
29
  return {
33
- url: url.toString(),
34
- schemaId,
30
+ url: process.env.DATABASE_URL,
31
+ schemaName,
35
32
  };
36
33
  }
37
34
 
38
35
  export async function createE2EDatabase<T extends E2EDatabaseClient>(
39
36
  clientInstance: Type<T>,
40
37
  ) {
41
- const { url, schemaId } = generateUniqueDatabaseURL();
38
+ const { url, schemaName } = generateUniqueSchemaConfig();
42
39
 
43
- setE2EDatabaseUrl(url);
40
+ setE2EDatabaseContext(url, schemaName);
44
41
 
45
- try {
46
- const client = new clientInstance(url, schemaId);
42
+ const client = new clientInstance(url, schemaName);
47
43
 
48
- await client.createDatabase(schemaId);
44
+ try {
45
+ await client.createSchema(schemaName);
49
46
 
50
- const env = { ...process.env, DATABASE_URL: url, NODE_ENV: 'test' };
47
+ const env = {
48
+ ...process.env,
49
+ DATABASE_URL: url,
50
+ DATABASE_SCHEMA: schemaName,
51
+ NODE_ENV: 'test',
52
+ };
51
53
  const migrationRunner = resolveMigrationRunner();
52
54
  execSync(
53
55
  `${migrationRunner} ./node_modules/typeorm/cli.js migration:run -d ./src/infra/database/migrations/migration-datasource.ts`,
@@ -58,9 +60,15 @@ export async function createE2EDatabase<T extends E2EDatabaseClient>(
58
60
  },
59
61
  );
60
62
 
61
- return { client, schemaId };
63
+ return { client, schemaName };
62
64
  } catch (error) {
63
- console.error('Erro ao criar banco de dados e2e:', error);
65
+ try {
66
+ await client.dropSchema();
67
+ } catch {
68
+ // schema pode não ter sido criado
69
+ }
70
+
71
+ console.error('Erro ao preparar schema e2e:', error);
64
72
  throw error;
65
73
  }
66
74
  }
@@ -4,6 +4,6 @@ export abstract class E2EDatabaseClient {
4
4
  protected readonly schemaName: string,
5
5
  ) {}
6
6
 
7
- abstract createDatabase(schemaName: string): Promise<void>;
8
- abstract dropDatabase(): Promise<void>;
7
+ abstract createSchema(schemaName: string): Promise<void>;
8
+ abstract dropSchema(): Promise<void>;
9
9
  }
@@ -0,0 +1,80 @@
1
+ import { PaginationDto } from '@/domain/dtos/pagination.dto';
2
+ import { ListResponseBase } from '@/core/common/list-response.base';
3
+ import { QUERY_FILTER_PARAMS } from '@/core/constants/query-params';
4
+ import { KlArray } from '@koalarx/utils/KlArray';
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ export abstract class InMemoryBaseRepository<
8
+ TClass extends { id?: number | string | null },
9
+ > {
10
+ protected items: TClass[] = [];
11
+
12
+ constructor(private readonly typeId: 'number' | 'string' = 'number') {}
13
+
14
+ protected async findById(id: number | string) {
15
+ return this.items.find((item) => item.id === id) ?? null;
16
+ }
17
+
18
+ protected async findMany<T extends PaginationDto>(
19
+ query: T,
20
+ predicate?: (value: TClass, index: number, array: TClass[]) => unknown,
21
+ ) {
22
+ const page = query.page ?? QUERY_FILTER_PARAMS.page;
23
+ const limit = query.limit ?? QUERY_FILTER_PARAMS.limit;
24
+
25
+ return new KlArray(predicate ? this.items.filter(predicate) : this.items)
26
+ .orderBy(query.orderBy ?? '', query.direction)
27
+ .slice(page * limit, (page + 1) * limit);
28
+ }
29
+
30
+ protected async findManyAndCount<T extends PaginationDto>(
31
+ query: T,
32
+ predicate?: (value: TClass, index: number, array: TClass[]) => unknown,
33
+ ): Promise<ListResponseBase<TClass>> {
34
+ const filtered = predicate ? this.items.filter(predicate) : this.items;
35
+ const page = query.page ?? QUERY_FILTER_PARAMS.page;
36
+ const limit = query.limit ?? QUERY_FILTER_PARAMS.limit;
37
+ const items = new KlArray(filtered)
38
+ .orderBy(query.orderBy ?? '', query.direction)
39
+ .slice(page * limit, (page + 1) * limit);
40
+
41
+ return ListResponseBase.from({ items, count: filtered.length });
42
+ }
43
+
44
+ protected async saveChanges(
45
+ item: TClass,
46
+ updateWhere?: (item: TClass) => boolean,
47
+ ) {
48
+ if (!item.id) {
49
+ const id = this.typeId === 'number' ? this.getNewId() : randomUUID();
50
+ (item as { id: number | string }).id = id;
51
+ this.items.push(item);
52
+ return { id };
53
+ }
54
+
55
+ const predicate = updateWhere ?? ((stored) => stored.id === item.id);
56
+ const itemIndex = this.items.findIndex(predicate);
57
+
58
+ if (itemIndex > -1) {
59
+ this.items[itemIndex] = item;
60
+ } else {
61
+ this.items.push(item);
62
+ }
63
+ }
64
+
65
+ protected async remove(
66
+ predicate: (value: TClass, index: number, obj: TClass[]) => unknown,
67
+ ) {
68
+ const itemIndex = this.items.findIndex(predicate);
69
+ if (itemIndex > -1) this.items.splice(itemIndex, 1);
70
+ }
71
+
72
+ private getNewId() {
73
+ if (this.typeId === 'number') {
74
+ const lastId = new KlArray(this.items).orderBy('id', 'desc')[0]?.id;
75
+ return lastId ? (lastId as number) + 1 : 1;
76
+ }
77
+
78
+ return randomUUID();
79
+ }
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koalarx/nest",
3
- "version": "4.0.6",
3
+ "version": "4.1.0",
4
4
  "description": "CLI para criar APIs NestJS com arquitetura DDD — copia módulos prontos para o seu repositório.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- export type IComparableId = string | number;