@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.
- package/cli/constants/cli-project-checklist.js +5 -0
- package/cli/constants/core-packages.js +4 -6
- package/cli/utils/install-module.js +21 -21
- package/cli/utils/parse-new-args.js +0 -1
- package/cli/utils/patch-app-test-module.js +2 -1
- package/cli/utils/patch-env.js +36 -0
- package/cli/utils/patch-main.js +3 -4
- package/cli/utils/remove-sample-parts.js +1 -2
- package/koala-nest/.env.example +12 -0
- package/koala-nest/src/application/common/pagination.request.ts +0 -5
- package/koala-nest/src/application/mapping/person.mapper.ts +0 -9
- package/koala-nest/src/application/person/jobs/cron/delete-inactive.job.ts +34 -17
- package/koala-nest/src/application/person/read-many/read-many-person.response.ts +2 -13
- package/koala-nest/src/core/background-services/cron-service/cron-job.handler.base.ts +36 -15
- package/koala-nest/src/core/background-services/event-service/event-queue.ts +2 -3
- package/koala-nest/src/core/common/list-response.base.ts +10 -0
- package/koala-nest/src/core/database/db-context.ts +3 -0
- package/koala-nest/src/core/database/entity.ts +9 -0
- package/koala-nest/src/core/env.ts +6 -0
- package/koala-nest/src/core/http/rate-limit.middleware.ts +36 -0
- package/koala-nest/src/core/tools/mapping/auto-map.ts +27 -3
- package/koala-nest/src/core/tools/mapping/auto-mapper.ts +25 -11
- package/koala-nest/src/core/tools/mapping/mapping-store.ts +13 -15
- package/koala-nest/src/core/utils/cron-expression-to-boolean.ts +32 -17
- package/koala-nest/src/core/utils/hash-password.ts +7 -2
- package/koala-nest/src/core/utils/initialize-undefined-array-props.ts +20 -0
- package/koala-nest/src/core/utils/resolve-cors-origins.ts +24 -0
- package/koala-nest/src/domain/dtos/pagination.dto.ts +87 -2
- package/koala-nest/src/domain/dtos/person-query.dto.ts +8 -0
- package/koala-nest/src/domain/entities/person/person-address.ts +2 -1
- package/koala-nest/src/domain/entities/person/person-contact.ts +2 -2
- package/koala-nest/src/domain/entities/person/person.ts +2 -4
- package/koala-nest/src/domain/entities/user/user.ts +1 -1
- package/koala-nest/src/host/bootstrap/apply-http-middleware.ts +22 -0
- package/koala-nest/src/host/main.ts +14 -14
- package/koala-nest/src/infra/common/redis-cache.service.ts +15 -4
- package/koala-nest/src/infra/database/data-source-factory.ts +4 -6
- package/koala-nest/src/infra/database/migrations/migration-datasource.ts +7 -0
- package/koala-nest/src/infra/repositories/person.repository.ts +3 -3
- package/koala-nest/src/infra/repositories/repository.base.ts +70 -1
- package/koala-nest/src/test/app-auth-test.module.ts +2 -1
- package/koala-nest/src/test/app-test.module.ts +2 -1
- package/koala-nest/src/test/application/delete-inactive.job.spec.ts +23 -10
- package/koala-nest/src/test/application/update-person.handler.spec.ts +42 -0
- package/koala-nest/src/test/core/cron-expression-to-boolean.spec.ts +20 -1
- package/koala-nest/src/test/core/env.spec.ts +19 -0
- package/koala-nest/src/test/core/http/rate-limit.middleware.spec.ts +62 -0
- package/koala-nest/src/test/core/initialize-undefined-array-props.spec.ts +63 -0
- package/koala-nest/src/test/core/mapping.spec.ts +15 -0
- package/koala-nest/src/test/core/pagination-typeorm.spec.ts +74 -0
- package/koala-nest/src/test/core/resolve-cors-origins.spec.ts +22 -0
- package/koala-nest/src/test/core/sync-improvements.spec.ts +39 -0
- package/koala-nest/src/test/e2e-context.ts +4 -1
- package/koala-nest/src/test/host/controllers/person/lazy-loading.e2e.spec.ts +16 -20
- package/koala-nest/src/test/infra/redis-cache.service.spec.ts +8 -0
- package/koala-nest/src/test/setup-e2e.ts +6 -43
- package/koala-nest/src/test/utils/configure-test-app.ts +2 -8
- package/koala-nest/src/test/utils/create-e2e-database.ts +24 -16
- package/koala-nest/src/test/utils/e2e-database-client.ts +2 -2
- package/koala-nest/src/test/utils/in-memory-base.repository.ts +80 -0
- package/package.json +1 -1
- 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
|
-
|
|
22
|
-
|
|
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
|
|
29
|
-
this.
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
|
23
|
-
const
|
|
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:
|
|
34
|
-
|
|
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,
|
|
38
|
+
const { url, schemaName } = generateUniqueSchemaConfig();
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
setE2EDatabaseContext(url, schemaName);
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
const client = new clientInstance(url, schemaId);
|
|
42
|
+
const client = new clientInstance(url, schemaName);
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
try {
|
|
45
|
+
await client.createSchema(schemaName);
|
|
49
46
|
|
|
50
|
-
const env = {
|
|
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,
|
|
63
|
+
return { client, schemaName };
|
|
62
64
|
} catch (error) {
|
|
63
|
-
|
|
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
|
|
8
|
-
abstract
|
|
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 +0,0 @@
|
|
|
1
|
-
export type IComparableId = string | number;
|