Skip to content

Commit 4696f51

Browse files
committed
feat(json-api-nestjs): Microro orm
Create orm methode, create general transform service, use this service for swagger and zod
1 parent 18f4a0c commit 4696f51

File tree

46 files changed

+1058
-781
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1058
-781
lines changed

apps/json-api-server/src/app/resources/controllers/entity-orm.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ import { BookList as tBookList } from '@nestjs-json-api/typeorm-database';
55
import { BookList as mkBookList } from '@nestjs-json-api/microorm-database';
66

77
const Users = process.env['ORM_TYPE'] === 'typeorm' ? tUsers : mkUsers;
8-
const BookList = process.env['ORM_TYPE'] === 'typeorm' ? tBookList : tBookList;
8+
const BookList = process.env['ORM_TYPE'] === 'typeorm' ? tBookList : mkBookList;
99

1010
export { Users, BookList };

apps/json-api-server/src/app/resources/controllers/extend-book-list/extend-book-list.controller.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ParseUUIDPipe } from '@nestjs/common';
22
import { BookList } from '../entity-orm';
33
import { JsonApi, JsonBaseController } from '@klerick/json-api-nestjs';
44

5-
@JsonApi(BookList as typeof BookList, {
5+
@JsonApi(BookList as any, {
66
pipeForId: ParseUUIDPipe,
77
overrideRoute: 'override-book-list',
88
allowMethod: ['getOne', 'postOne', 'deleteOne'],

libs/json-api/json-api-nestjs-shared/src/lib/types/response-body.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
TypeOfArray,
66
ValueOf,
77
} from '.';
8+
import { Collection } from '@mikro-orm/core';
89

910
export type PageProps = {
1011
totalItems: number;
@@ -30,8 +31,14 @@ export type Attributes<D> = {
3031
[P in EntityProps<D>]?: D[P] extends EntityField ? D[P] : TypeOfArray<D[P]>;
3132
};
3233

34+
export type DataResult<E, S = string> = E extends unknown[]
35+
? MainData<S>[]
36+
: E extends Collection<any>
37+
? MainData<S>[]
38+
: MainData<S> | null;
39+
3340
export type Data<E, S = string> = {
34-
data?: E extends unknown[] ? MainData<S>[] : MainData<S> | null;
41+
data?: DataResult<E, S>;
3542
};
3643

3744
export type Relationships<T> = {

libs/json-api/json-api-nestjs-shared/src/lib/types/utils-string.type.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Collection } from '@mikro-orm/core';
2+
13
export type KebabCase<S> = S extends `${infer C}${infer T}`
24
? KebabCase<T> extends infer U
35
? U extends string
@@ -15,6 +17,10 @@ export type KebabToCamelCase<S extends string> =
1517
? `${Capitalize<T>}${Capitalize<KebabToCamelCase<U>>}`
1618
: S;
1719

18-
export type TypeOfArray<T> = T extends (infer U)[] ? U : T;
20+
export type TypeOfArray<T> = T extends (infer U)[]
21+
? U
22+
: T extends Collection<infer U>
23+
? U
24+
: T;
1925

2026
export type ValueOf<T> = T[keyof T];

libs/json-api/json-api-nestjs/src/lib/constants/di.ts

+2
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ export const ZOD_PATCH_RELATIONSHIP_SCHEMA = Symbol(
2828
);
2929
export const CURRENT_DATA_SOURCE_TOKEN = Symbol('CURRENT_DATA_SOURCE_TOKEN');
3030
export const CURRENT_ENTITY_REPOSITORY = Symbol('CURRENT_ENTITY_REPOSITORY');
31+
32+
export const ENTITY_MAP_PROPS = Symbol('ENTITY_MAP_PROPS');

libs/json-api/json-api-nestjs/src/lib/mock-utils/index.ts

+27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@ import { DataType, IMemoryDb, newDb } from 'pg-mem';
22
import { readFileSync } from 'fs';
33
import { join } from 'path';
44
import { v4 } from 'uuid';
5+
// @ts-ignore
6+
import type { PGlite } from '@electric-sql/pglite';
7+
8+
export async function createAndPullSchemaBasePgLite(): Promise<PGlite> {
9+
const db = await Promise.all([
10+
import('@electric-sql/pglite'),
11+
// @ts-ignore
12+
import('@electric-sql/pglite/contrib/uuid_ossp'),
13+
]).then(
14+
([{ PGlite }, { uuid_ossp }]) =>
15+
new PGlite({
16+
extensions: { uuid_ossp },
17+
database: 'pgLite',
18+
username: 'postgres',
19+
})
20+
);
21+
22+
// await db.exec(
23+
// 'CREATE SCHEMA IF NOT EXISTS public; SET search_path TO public;'
24+
// );
25+
26+
// const dump = readFileSync(join(__dirname, 'db-for-test'), {
27+
// encoding: 'utf8',
28+
// });
29+
// await db.exec(dump);
30+
return db;
31+
}
532

633
export function createAndPullSchemaBase(): IMemoryDb {
734
const dump = readFileSync(join(__dirname, 'db-for-test'), {

libs/json-api/json-api-nestjs/src/lib/mock-utils/microrom/entities/notes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Users, IUsers } from './index';
88
export class Notes {
99
@PrimaryKey({
1010
type: 'uuid',
11-
defaultRaw: 'uuid_generate_v4()',
11+
defaultRaw: 'gen_random_uuid()',
1212
})
1313
public id!: string;
1414

libs/json-api/json-api-nestjs/src/lib/mock-utils/microrom/index.ts

+116-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { DynamicModule } from '@nestjs/common';
2+
import { Test, TestingModule } from '@nestjs/testing';
23
import { MikroOrmModule } from '@mikro-orm/nestjs';
3-
import { MikroORM } from '@mikro-orm/core';
4+
import { EntityManager, MikroORM } from '@mikro-orm/core';
45
import { PostgreSqlDriver } from '@mikro-orm/postgresql';
6+
import { SqlHighlighter } from '@mikro-orm/sql-highlighter';
7+
import { QueryField } from '@klerick/json-api-nestjs-shared';
58

69
import { IMemoryDb } from 'pg-mem';
710

@@ -13,20 +16,54 @@ import {
1316
UserGroups,
1417
Users,
1518
} from './entities';
19+
import { ObjectLiteral } from '../../types';
20+
import { Query } from '../../modules/mixin/zod';
21+
22+
import {
23+
CurrentEntityManager,
24+
CurrentEntityMetadata,
25+
CurrentEntityRepository,
26+
CurrentMicroOrmProvider,
27+
OrmServiceFactory,
28+
EntityPropsMap,
29+
} from '../../modules/micro-orm/factory';
30+
import { MicroOrmUtilService } from '../../modules/micro-orm/service/micro-orm-util.service';
31+
import { CURRENT_ENTITY, GLOBAL_MODULE_OPTIONS_TOKEN } from '../../constants';
1632

1733
export * from './entities';
1834
export * from './utils';
1935

2036
export const entities = [Users, UserGroups, Roles, Comments, Addresses, Notes];
2137

38+
import { sharedConnect, initMikroOrm, pullAllData } from './utils';
39+
import { DEFAULT_ARRAY_TYPE } from '../../modules/micro-orm/constants';
40+
import { JsonApiTransformerService } from '../../modules/mixin/service/json-api-transformer.service';
41+
42+
export function mockDbPgLiteTestModule(dbName = `test_db_${Date.now()}`) {
43+
const mikroORM = {
44+
provide: MikroORM,
45+
useFactory: async () => {
46+
const knexInst = await sharedConnect();
47+
return initMikroOrm(knexInst, dbName);
48+
},
49+
};
50+
return {
51+
module: MikroOrmModule,
52+
providers: [mikroORM],
53+
exports: [mikroORM],
54+
};
55+
}
56+
2257
export function mockDBTestModule(db: IMemoryDb): DynamicModule {
2358
const mikroORM = {
2459
provide: MikroORM,
2560
useFactory: () =>
2661
db.adapters.createMikroOrm({
62+
highlighter: new SqlHighlighter(),
2763
entities: [Users, UserGroups, Roles, Comments, Addresses, Notes],
2864
driver: PostgreSqlDriver,
2965
allowGlobalContext: true,
66+
debug: ['query', 'query-params'],
3067
}),
3168
};
3269
return {
@@ -35,3 +72,81 @@ export function mockDBTestModule(db: IMemoryDb): DynamicModule {
3572
exports: [mikroORM],
3673
};
3774
}
75+
const readOnlyDbName = `readonly_db_${Date.now()}`;
76+
77+
export function dbRandomName(readOnly = false) {
78+
if (readOnly) {
79+
return readOnlyDbName;
80+
}
81+
return `test_db_${Date.now()}`;
82+
}
83+
84+
export async function pullData(em: EntityManager, count = 1) {
85+
for (let i = 0; i < count; i++) {
86+
await pullAllData(em);
87+
}
88+
}
89+
90+
export function getModuleForPgLite<E extends ObjectLiteral>(
91+
entity: E,
92+
dbName = `test_db_${Date.now()}`
93+
): Promise<TestingModule> {
94+
return Test.createTestingModule({
95+
imports: [mockDbPgLiteTestModule(dbName)],
96+
providers: [
97+
CurrentMicroOrmProvider(),
98+
CurrentEntityManager(),
99+
CurrentEntityMetadata(),
100+
CurrentEntityRepository(entity),
101+
MicroOrmUtilService,
102+
{
103+
provide: CURRENT_ENTITY,
104+
useValue: entity,
105+
},
106+
OrmServiceFactory(),
107+
EntityPropsMap(entities as any),
108+
{
109+
provide: GLOBAL_MODULE_OPTIONS_TOKEN,
110+
useValue: { options: { arrayType: DEFAULT_ARRAY_TYPE } },
111+
},
112+
JsonApiTransformerService,
113+
],
114+
}).compile();
115+
}
116+
117+
export function getModuleFor<E extends ObjectLiteral>(
118+
db: IMemoryDb,
119+
entity: E
120+
): Promise<TestingModule> {
121+
return Test.createTestingModule({
122+
imports: [mockDBTestModule(db)],
123+
providers: [
124+
CurrentMicroOrmProvider(),
125+
CurrentEntityManager(),
126+
CurrentEntityMetadata(),
127+
CurrentEntityRepository(entity),
128+
MicroOrmUtilService,
129+
{
130+
provide: CURRENT_ENTITY,
131+
useValue: entity,
132+
},
133+
OrmServiceFactory(),
134+
],
135+
}).compile();
136+
}
137+
138+
export function getDefaultQuery<R extends ObjectLiteral>(): Query<R> {
139+
return {
140+
[QueryField.filter]: {
141+
relation: null,
142+
target: null,
143+
},
144+
[QueryField.fields]: null,
145+
[QueryField.include]: null,
146+
[QueryField.sort]: null,
147+
[QueryField.page]: {
148+
size: 1,
149+
number: 1,
150+
},
151+
} satisfies Query<R>;
152+
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './provider-entities';
22
export * from './pull-data';
3+
export * from './init-db';

libs/json-api/json-api-nestjs/src/lib/mock-utils/microrom/utils/pull-data.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export async function pullNote() {
3838
export async function pullRole() {
3939
const role = new Roles();
4040
role.key = faker.string.alphanumeric(5);
41-
role.name = faker.string.alphanumeric(5);
41+
role.name = faker.word.words();
4242
return role;
4343
}
4444

@@ -104,6 +104,7 @@ export async function pullAllData(em: EntityManager) {
104104

105105
managerUser.addresses = address2;
106106
managerUser.userGroup = userGroup3;
107+
managerUser.roles.add(role1, role2);
107108

108109
await em.persistAndFlush([
109110
user,

libs/json-api/json-api-nestjs/src/lib/modules/atomic-operation/factory/zod-input-operation.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { FactoryProvider } from '@nestjs/common';
22
import { MAP_CONTROLLER_ENTITY, ZOD_INPUT_OPERATION } from '../constants';
33
import { MapController } from '../types';
44
import { zodInputOperation, ZodInputOperation } from '../utils';
5-
import { FIELD_FOR_ENTITY } from '../../../constants';
6-
import { GetFieldForEntity } from '../../mixin/types';
7-
import { ObjectLiteral } from '../../../types';
5+
import { ENTITY_MAP_PROPS } from '../../../constants';
6+
import { ZodEntityProps } from '../../mixin/types';
7+
import { EntityClass, ObjectLiteral } from '../../../types';
88

99
export function ZodInputOperation<E extends ObjectLiteral>(): FactoryProvider<
1010
ZodInputOperation<E>
@@ -13,10 +13,10 @@ export function ZodInputOperation<E extends ObjectLiteral>(): FactoryProvider<
1313
provide: ZOD_INPUT_OPERATION,
1414
useFactory(
1515
mapController: MapController<E>,
16-
getField: GetFieldForEntity<E>
16+
entityMapProps: Map<EntityClass<E>, ZodEntityProps<E>>
1717
) {
18-
return zodInputOperation(mapController, getField);
18+
return zodInputOperation(mapController, entityMapProps);
1919
},
20-
inject: [MAP_CONTROLLER_ENTITY, FIELD_FOR_ENTITY],
20+
inject: [MAP_CONTROLLER_ENTITY, ENTITY_MAP_PROPS],
2121
};
2222
}

libs/json-api/json-api-nestjs/src/lib/modules/atomic-operation/utils/zod/zod-helper.spec.ts

+27-14
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ import {
1414
ZodUpdate,
1515
} from './zod-helper';
1616
import { Users } from '../../../../mock-utils/typeorm';
17-
import { FIELD_FOR_ENTITY } from '../../../../constants';
17+
import { ENTITY_MAP_PROPS, FIELD_FOR_ENTITY } from '../../../../constants';
1818
import { JsonBaseController } from '../../../mixin/controller/json-base.controller';
1919
import { MapController } from '../../types';
2020
import { KEY_MAIN_INPUT_SCHEMA } from '../../constants';
21-
import { GetFieldForEntity, TupleOfEntityRelation } from '../../../mixin/types';
21+
import {
22+
GetFieldForEntity,
23+
TupleOfEntityRelation,
24+
ZodEntityProps,
25+
} from '../../../mixin/types';
26+
import { EntityClass } from '../../../../types';
2227

2328
describe('ZodHelperSpec', () => {
2429
afterEach(() => {
@@ -444,26 +449,34 @@ describe('ZodHelperSpec', () => {
444449
});
445450
});
446451
describe('zodInputOperation', () => {
447-
let getField: GetFieldForEntity<Users>;
452+
let getField: Map<EntityClass<any>, ZodEntityProps<any>>;
448453
beforeAll(async () => {
449454
const module: TestingModule = await Test.createTestingModule({
450455
providers: [
451456
{
452-
provide: FIELD_FOR_ENTITY,
453-
useValue: () => ({
454-
relations: [
455-
'userGroup',
456-
'notes',
457-
'comments',
458-
'roles',
459-
'manager',
460-
'addresses',
457+
provide: ENTITY_MAP_PROPS,
458+
useValue: new Map([
459+
[
460+
Users,
461+
{
462+
relations: [
463+
'userGroup',
464+
'notes',
465+
'comments',
466+
'roles',
467+
'manager',
468+
'addresses',
469+
],
470+
},
461471
],
462-
}),
472+
]),
463473
},
464474
],
465475
}).compile();
466-
getField = module.get<GetFieldForEntity<Users>>(FIELD_FOR_ENTITY);
476+
getField =
477+
module.get<Map<EntityClass<any>, ZodEntityProps<any>>>(
478+
ENTITY_MAP_PROPS
479+
);
467480
});
468481

469482
it('should be correct', () => {

0 commit comments

Comments
 (0)