I´m trying to setup a STI inheritance hierarchy, similar to described on the official docs here, but getting the following error
TypeError: Cannot read property 'ownColumns' of undefined
at /Users/luizrolim/workspace/nqa/src/metadata-builder/EntityMetadataBuilder.ts:320:64
at Array.map (<anonymous>)
at EntityMetadataBuilder.computeEntityMetadataStep1 (/Users/luizrolim/workspace/nqa/src/metadata-builder/EntityMetadataBuilder.ts:316:14)
at /Users/luizrolim/workspace/nqa/src/metadata-builder/EntityMetadataBuilder.ts:108:45
at Array.forEach (<anonymous>)
Here are me entities:
#ChildEntity()
export class ChildExam extends BaseExam {
}
#Entity('exm_exams')
#TableInheritance({ column: { type: 'varchar', name: 'type' } })
export abstract class BaseExam extends NQBaseEntity {
#Column()
public alias: string
#Column()
public description: string
}
import { BaseEntity, Column, PrimaryGeneratedColumn } from 'typeorm'
import { DateColumns } from '#infra/util/db/DateColumns'
export abstract class NQBaseEntity extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number
#Column(type => DateColumns)
public dates: DateColumns
}
I am running at "typeorm": "^0.2.12",
I also ran into this error when trying to make Single Table Inheritance work:
(node:22416) UnhandledPromiseRejectionWarning: TypeError: Cannot read
property 'ownColumns' of undefined at
metadata-builder\EntityMetadataBuilder.ts:356:64
My problem was, that I only listed my child entities in the connection options but I forgot to also add my parent entity.
Here is how I solved it (using TypeORM v0.3.0-rc.19):
ParentEntity.ts
import {BaseEntity, Entity} from 'typeorm';
#Entity()
#TableInheritance({ column: { type: 'varchar', name: 'type' } })
export abstract class ParentEntity extends BaseEntity {
// ...
}
ChildEntity.ts
import {ChildEntity} from 'typeorm';
#ChildEntity()
export class ChildEntity extends ParentEntity {
constructor() {
super();
}
// ...
}
initDatabase.ts
import 'reflect-metadata';
import {Connection, createConnection} from 'typeorm';
import {SqliteConnectionOptions} from 'typeorm/driver/sqlite/SqliteConnectionOptions';
import {PostgresConnectionOptions} from 'typeorm/driver/postgres/PostgresConnectionOptions';
import {ConnectionOptions} from 'typeorm/connection/ConnectionOptions';
import {ParentEntity, ChildEntity} from './entity/index';
function getConnectionOptions(env?: string): ConnectionOptions {
// Here is the important part! Listing both, parent and child entity!
const entities = [ParentEntity, ChildEntity];
const test: SqliteConnectionOptions = {
database: ':memory:',
dropSchema: true,
entities,
logging: false,
name: 'default',
synchronize: true,
type: 'sqlite',
};
const development: SqliteConnectionOptions = {
database: 'test.db3',
type: 'sqlite',
};
const production: PostgresConnectionOptions = {
type: 'postgres',
url: process.env.DATABASE_URL,
};
const devProd = {
name: 'default',
entities,
logging: false,
migrations: [],
subscribers: [],
synchronize: true,
};
switch (env) {
case 'production':
return Object.assign(production, devProd);
case 'test':
return test;
default:
return Object.assign(development, devProd);
}
}
export default function initDatabase(): Promise<Connection> {
const options = getConnectionOptions(process.env.NODE_ENV);
return createConnection(options);
}
In my application I now have to call await initDatabase() at first, to properly initialize the database schema.
You should make class BaseExam not abstract to make it work.
Related
I would like to inject a service into a typeorm migration, so that I can perform data migration based on some logic within a service:
import { MigrationInterface, QueryRunner, Repository } from 'typeorm';
import { MyService } from '../../services/MyService.service';
import { MyEntity } from '../../entities/MyEntity.entity';
export class MyEntityMigration12345678
implements MigrationInterface
{
name = 'MyEntityMigration12345678';
constructor(
private readonly myService: MyService,
) {}
public async up(queryRunner: QueryRunner): Promise<void> {
const myEntityRepository: Repository<MyEntity> =
queryRunner.connection.getRepository<MyEntity>(MyEntity);
const entities = await myEntityRepository.findBy({
myColumn: '',
});
for (const entity of entities) {
const columnValue = this.myService.getColumnValue(myEntity.id);
await myEntityRepository.save({
...entity,
myColumn: columnValue,
});
}
}
// ...
}
Nevertheless
myService is undefined, and
myEntityRepository.findBy(.) gets stuck.
How can I do a migration based on business logic?
Thanks!
One option would be to write whatever query myService.getColumn value does inside your migration. If you're hell bent on using Nest's DI inside your migration then you could do something like this:
import { NestFactory } from '#nestjs/core';
import { MigrationInterface, QueryRunner, Repository } from 'typeorm';
import { AppModule } from '../../app.module'; // assumed path
import { MyService } from '../../services/MyService.service';
import { MyEntity } from '../../entities/MyEntity.entity';
export class MyEntityMigration12345678
implements MigrationInterface
{
name = 'MyEntityMigration12345678';
public async up(queryRunner: QueryRunner): Promise<void> {
const myEntityRepository: Repository<MyEntity> =
queryRunner.connection.getRepository<MyEntity>(MyEntity);
const entities = await myEntityRepository.findBy({
myColumn: '',
});
const appCtx = await NestFactory.createApplicationContext(AppModule);
const myService = app.get(MyService, { strict: false });
for (const entity of entities) {
const columnValue = myService.getColumnValue(myEntity.id);
await myEntityRepository.save({
...entity,
myColumn: columnValue,
});
}
await app.close();
}
// ...
}
You can't use injection inside the migration because the class itself is managed by TypeORM. You can, as shown above, create a NestApplicationContext instance and get the MyService instance from that. This only works, by the way, if MyService is REQUEST scoped
Is there a way of using mapped-types with swagger and class-transformer
Got a example of whats going wrong here:
https://stackblitz.com/edit/nestjs-starter-demo-aq1sw2?file=src/dtos/user-descriptor.dto.ts
The DTOs:
ProjectDescriptorDto
// import { PickType } from '#nestjs/mapped-types';
import { PickType } from '#nestjs/swagger';
import { ProjectDto } from './project.dto';
export class ProjectDescriptorDto extends PickType(ProjectDto, [
'id',
'title',
] as const) {}
ProjectDto
import { Expose, Type } from 'class-transformer';
import { UserDescriptorDto } from './user-descriptor.dto';
export class ProjectDto {
#Expose()
id: number;
#Expose()
description: string;
#Expose()
#Type(() => UserDescriptorDto)
starredBy: UserDescriptorDto[];
#Expose()
title: string;
}
UserDescriptorDto
// import { PickType } from '#nestjs/mapped-types';
import { PickType } from '#nestjs/swagger';
import { UserDto } from './user.dto';
export class UserDescriptorDto extends PickType(UserDto, [
'id',
'firstName',
'lastName',
'email',
] as const) {}
UserDto
import { ProjectDescriptorDto } from './project-descriptor.dto';
import { Type } from 'class-transformer';
import { ApiProperty } from '#nestjs/swagger';
export class UserDto {
id: number;
firstName: string;
lastName: string;
email: string;
#ApiProperty({
isArray: true,
type: ProjectDescriptorDto,
})
#Type(() => ProjectDescriptorDto)
favourites: ProjectDescriptorDto[];
}
So in the example using PickType from #nestjs/mapped-types compiles the code but it won't generate the correct swagger specs for the extended class
Using PickType from #nestjs/swagger is required to generate the swagger spec correctly but in combination with #Type() decorator from the class-transform package, the code won't compile correctly:
TypeError: Cannot read properties of undefined (reading 'prototype')
at Object.PickType (/home/projects/nestjs-starter-demo-aq1sw2/node_modules/#nestjs/swagger/dist/type-helpers/pick-type.helper.js:13:38)
at Object.eval (/home/projects/nestjs-starter-demo-aq1sw2/dist/dtos/project-descriptor.dto.js:7:46)
at Object.function (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:11:119417)
at Module._compile (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:6:167880)
at Object.Module._extensions..js (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:6:168239)
at Module.load (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:6:166317)
at Function.Module._load (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:6:163857)
at Module.require (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:6:166635)
at i (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:6:435073)
at _0x4139bb (https://nestjs-starter-demo-aq1sw2.jw.staticblitz.com/blitz.88a7151d177878d00bf438b30e057ed5805fdcd2.js:11:119029)
So the question is; is there a way around this using mapped-types or will I instead have to not extend the descriptors. The current work-around is to use:
SomeDescriptorDto implements Pick<SomeDto, 'id' | ...> {
id: number;
// variables
}
In my NestJS project I use TypeORM with external schema definition. I want to use a ValueTransformer to encrypt sensitive data in some database columns, e.g. the email of the user should be encrypted.
The ValueTransformer depends on an encryption service from another module. I can't figure out how to inject that service via DI.
export const UserSchema = new EntitySchema<User>(<EntitySchemaOptions<User>>{
name: 'user',
columns: {
email: {
type: 'varchar',
nullable: false,
transformer: new EncryptedValueTransformer()
} as EntitySchemaColumnOptions,
},
});
export class EncryptedValueTransformer implements ValueTransformer {
#Inject()
private cryptoService: CryptoService
public to(value: unknown): string | unknown {
// error: cryptoService undefined
return this.cryptoService.encrypt(value);
}
public from(value: unknown): unknown {
// error: cryptoService undefined
return this.cryptoService.decrypt(value);
}
}
#Injectable()
export class CryptoService {
public constructor(#Inject('CONFIG_OPTIONS') private options: CryptoModuleOptions) {
// options contain a secret key from process.env
}
public encrypt(data: string): string | undefined | null { ... }
public decrypt(data: string): string | undefined | null { ... }
}
#Module({})
export class CryptoModule {
public static register(options: CryptoModuleOptions): DynamicModule {
return {
module: CryptoModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
CryptoService,
],
exports: [CryptoService],
};
}
}
#Module({
imports: [
DomainModule,
CryptoModule.register({ secretKey: process.env.ENCRYPTION_KEY }),
TypeOrmModule.forFeature([
UserSchema,
UserTypeOrmRepository,
]),
],
providers: [
UserRepositoryProvider,
UserRepositoryTypeOrmAdapter,
],
exports: [
UserRepositoryProvider,
UserRepositoryTypeOrmAdapter,
],
})
export class PersistenceModule {}
With the above code the CryptoService instance in EncryptedValueTransformer is undefined. I searched related issues. According to this post https://stackoverflow.com/a/57593989/11964644 NestJS needs some context for DI to work. If that context is not given, you can manually provide the dependency.
My workaround now is this:
export class EncryptedValueTransformer implements ValueTransformer {
private cryptoService: CryptoService
public constructor() {
// tightly coupled now an process.env.ENCRYPTION_KEY still undefined at instantiation time
this.cryptoService = new CryptoService({ secretKey: process.env.ENCRYPTION_KEY });
}
}
But with this workaround the process.env variable is not yet resolvable at the point where the class is being instantiated, so I need to modify CryptoService in a way that it reads the env variable at runtime itself. And at this point the CryptoService is not reusable anymore.
How can I inject the CryptoService into the EncryptedValueTransformer or into UserSchema with this external schema setup? Or any better way to solve this?
I'm trying to use NgRx/effects for my material autocompleted in angular 8.
I have created store, action, effects and reducers but I'm not getting the state after successfully calling the api. The api is returning the correct value.
Action file
import { Action } from '#ngrx/store';
import {IProviderSearchObject} from '../../models/provider.type';
export enum ProviderSearchActionTypes {
SearchProvidersRequest = '[SEARCH_PROVIDER] REQUEST',
SearchProvidersSuccess = '[SEARCH_PROVIDER] SUCCESS',
SearchProvidersFail = '[SEARCH_PROVIDER] FAILED'
}
export class ProviderSearchAction implements Action {
type: string;
payload: {
isRequesting: boolean,
providers: Array<IProviderSearchObject>,
error: boolean,
searchPhrase: string
};
}
export class SearchProvidersRequest implements Action {
readonly type = ProviderSearchActionTypes.SearchProvidersRequest;
constructor(readonly payload: {isRequesting: boolean, searchPhrase: string}) {}
}
export class SearchProvidersSuccess implements Action {
readonly type = ProviderSearchActionTypes.SearchProvidersSuccess;
constructor(readonly payload: {isRequesting: boolean, providers: Array<IProviderSearchObject>}) {}
}
export class SearchProvidersFail implements Action {
readonly type = ProviderSearchActionTypes.SearchProvidersFail;
constructor(readonly payload: {error: boolean}) {}
}
export type ProviderSearchActions = SearchProvidersRequest | SearchProvidersSuccess | SearchProvidersFail;
reducer file
import {IProviderSearchObject} from '../../models/provider.type';
import {ProviderSearchAction, ProviderSearchActionTypes} from '../actions/provider-search.action';
export interface IProviderSearchState {
isRequesting: boolean;
providers: Array<IProviderSearchObject> | null;
error: boolean;
}
const initialProviderSearchState: IProviderSearchState = {
isRequesting: false,
providers: null,
error: false
};
export function providerSearchReducer(state: IProviderSearchState = initialProviderSearchState, action: ProviderSearchAction): IProviderSearchState {
console.log(action, state);
switch (action.type) {
case ProviderSearchActionTypes.SearchProvidersRequest:
return {
isRequesting: true,
providers: null,
error: false
};
case ProviderSearchActionTypes.SearchProvidersSuccess:
return {
isRequesting: false,
providers: action.payload.providers,
error: false
};
case ProviderSearchActionTypes.SearchProvidersFail:
return {
isRequesting: false,
providers: null,
error: true
}
default:
return state;
}
}
import {ActionReducerMap, MetaReducer} from '#ngrx/store';
import {IProviderSearchState, providerSearchReducer} from './provider-search.reducer';
export interface IAppState {
providerSearch: IProviderSearchState;
}
export const reducers: ActionReducerMap<IAppState> = {
providerSearch: providerSearchReducer
};
export const selectProviderSearch = (state: IAppState) => state.providerSearch.providers;
export const metaReducers: MetaReducer<any>[] = [];
Effects file
import {Actions, Effect, ofType} from '#ngrx/effects';
import {IAppState} from '../reducers';
import {ProviderSearchService} from '../../modules/provider-search/services/provider-search.service';
import {Store} from '#ngrx/store';
import {ProviderSearchActionTypes, SearchProvidersSuccess, SearchProvidersFail} from '../actions/provider-search.action';
import {catchError, map, switchMap} from 'rxjs/operators';
import { of } from 'rxjs';
import {IProviderSearchObject} from '../../models/provider.type';
import {Injectable} from '#angular/core';
#Injectable()
export class ProviderSearchEffects {
constructor(private actions$: Actions,
private store: Store<IAppState>,
private providerSearchService: ProviderSearchService) {}
#Effect()
searchProvider$ = this.actions$
.pipe(
ofType<any>(ProviderSearchActionTypes.SearchProvidersRequest),
map(action => action.payload),
switchMap((action) => {
return this.providerSearchService.getProviderByPhrase(action.searchPhrase).pipe(
map((data: Array<IProviderSearchObject>) => new SearchProvidersSuccess({isRequesting: false, providers: data})),
catchError(error => of(new SearchProvidersFail({error: true})))
);
})
);
}
import { ProviderSearchEffects } from './provider-search.effects';
export const effects: Array<any> = [ProviderSearchEffects];
Service file
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
import {environment} from './../../../../environments/environment';
import { Store } from '#ngrx/store';
import * as ProviderSearchAction from '../../../store/actions/provider-search.action';
import {IAppState} from '../../../store/reducers';
import {Observable} from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ProviderSearchService {
constructor(
private http: HttpClient,
private store: Store<IAppState>
) { }
public getProviderByPhrase = (searchPhrase: string): Observable<any> => {
return this.http.get('https://mydummyapi.com?q=' + searchPhrase);
}
public searchProviderByTermSearch = (searchPhrase: string): any => {
return this.store.dispatch(new ProviderSearchAction.SearchProvidersRequest({isRequesting: true, searchPhrase}));
}
}
Component file
ngOnInit() {
this.providerSearchControl.valueChanges
.pipe(
debounceTime(500),
tap(() => {
this.isLoading = true;
}),
switchMap((value: string) => this.providerSearchService.searchProviderByTermSearch(value))
.pipe(
finalize(() => {
this.isLoading = false;
})
)
)
)
.subscribe((data: Array<IProviderSearchObject>) => {
console.log(data);
if (data && data.length > 0) {
this.providerSearchResult = data;
}
});
}
When the user start typing the autocomplete field then searchProviderByTermSearch method is invoked inside the service file and that dispatches the action.
But after [SEARCH_PROVIDER] SUCCESS call is made nothing is happening.
A store.dispatch is a void, it does not return a value.
The data from the subscribe code, is not an Array<IProviderSearchObject>.
Your flow should be:
dispatch fetch
call service in effects
dispatch fetch success/failure
update state via reducer
read data with selectors
update component
https://ngrx.io/guide/store#diagram
I'm trying to integrate my REST API (NestJS) with new Neo4j database with GraphQL queries. Anybody succeed? Thanks in advance
EDIT 1: (I added my code)
import { Resolver } from "#nestjs/graphql";
import { Query, forwardRef, Inject, Logger } from "#nestjs/common";
import { Neo4jService } from "src/shared/neo4j/neoj4.service";
import { GraphModelService } from "./models/model.service";
import { Movie } from "src/graphql.schema";
#Resolver('Movie')
export class GraphService {
constructor(private readonly _neo4jService: Neo4jService) {}
#Query()
async getMovie() {
console.log("hello");
return neo4jgraphql(/*i don't know how get the query and params*/);
}
}
I am using a NestInterceptor to accomplish this:
#Injectable()
export class Neo4JGraphQLInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
const ctx = GqlExecutionContext.create(context);
return neo4jgraphql(
ctx.getRoot(),
ctx.getArgs(),
ctx.getContext(),
ctx.getInfo(),
);
}
}
To use it in your Resolver:
#Resolver('Movie')
#UseInterceptors(Neo4JGraphQLInterceptor)
export class MovieResolver {}
My GraphQLModule is configured like this:
#Module({
imports: [
GraphQLModule.forRoot({
typePaths: ['./**/*.gql'],
transformSchema: augmentSchema,
context: {
driver: neo4j.driver(
'bolt://neo:7687',
neo4j.auth.basic('neo4j', 'password1234'),
),
},
}),
],
controllers: [...],
providers: [..., MovieResolver, Neo4JGraphQLInterceptor],
})
Note the usage of transformSchema: augmentSchema to enable auto-generated mutations and queries (GRANDStack: Schema Augmentation)
Hope that helps a bit!
This is what works for me...not as elegant as I would like but it works; I want to have only one service/provider accessing my db not the service from each module even though that works also. So I am sticking with the Nest format of myModule->myResolver->myService-->Neo4jService. So Neo4jService is injected in all xService(s). I am using neo4jGraphql and augmentSchema and Cypher when necessary.
Code:
**appmodule.ts**
....
import { makeExecutableSchema } from 'graphql-tools';
import { v1 as neo4j } from 'neo4j-driver';
import { augmentTypeDefs, augmentSchema } from 'neo4j-graphql-js';
import { Neo4jService } from './neo4j/neo4j.service';
import { MyModule } from './my/my.module';
import { MyResolver } from './my/my.resolver';
import { MyService } from './my/my.service';
....
import { typeDefs } from './generate-schema'; // SDL type file
...
const driver = neo4j.driver('bolt://localhost:3000', neo4j.auth.basic('neo4j', 'neo4j'))
const schema = makeExecutableSchema({
typeDefs: augmentTypeDefs(typeDefs),
});
const augmentedSchema = augmentSchema(schema); // Now we have an augmented schema
#Module({
imports: [
MyModule,
GraphQLModule.forRoot({
schema: augmentedSchema,
context: {
driver,
},
}),
],
controllers: [],
providers: [ Neo4jService,
myResolver,
],
})
export class AppModule {}
**myResolver.ts**
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { MyService } from './my.service';
#Resolver('My')
export class MyResolver {
constructor(
private readonly myService: MyService) {}
#Query()
async getData(object, params, ctx, resolveInfo) {
return await this.myService.getData(object, params, ctx, resolveInfo);
}
*//Notice I am just passing the graphql params, etc to the myService*
}
**myService.ts**
import { Injectable } from '#nestjs/common';
import { Neo4jService } from '../neo4j/neo4j.service';
#Injectable()
export class MyService {
constructor(private neo4jService: Neo4jService) {}
async getData(object, params, ctx, resolveInfo) {
return await this.neo4jService.getData(object, params, ctx, resolveInfo);
}
*// Again I am just passing the graphql params, etc to the neo4jService*
}
**neo4jService.ts**
import { Injectable } from '#nestjs/common';
import { neo4jgraphql } from 'neo4j-graphql-js';
#Injectable()
export class Neo4jService {
getData(object, params, ctx, resolveInfo) {
return neo4jgraphql(object, params, ctx, resolveInfo);
}
.....
......
}
So basically I postponed using neo4jgraphql until we arrive at neo4jService. Now all my DB calls are here.....as I said not elegant but it works.
Challenges: Graphql generate would not accept #relation...I found out that a change was made and now you need augmentTypeDefs.
...hope this helps
EDIT
Nestjs takes an awful long time to process the augmentSchema...so I would recommend skipping it..for now
Here is an example i created for (NestJS + GraphQL + Neo4j). I hope if this may help!
NestJS + GraphQL + Neo4j
I have not worked on GraphQL, but I know there is an npm package(Neo4j-graphql-js) to translate GraphQL queries into Cypher queries. It makes it easier to use GraphQL and Neo4j together.
Also, check GRANDstack it is a full-stack development integration for building Graph-based applications.
I suggest you to visit Neo4j Community.