Skip to content

Latest commit

 

History

History
331 lines (254 loc) · 13.8 KB

File metadata and controls

331 lines (254 loc) · 13.8 KB

NOTES

some other projects and files that help

  • /home/mario/Development/@Koakh/node-modules/@koakh/@NestJsPackages/TypescriptNestJsPackageJwtAuthenticationLdap/nestjs-package-jwt-authentication-ldap-consumer/src/consumer-app/consumer-app.module.ts

AFTER SO MANY HOURS the start solving is using @Global on SurrealDbModule

  • packages/app-lib/src/surrealdb/surrealdb.module.ts
// required @Global() to override errors of 
// ERROR [ExceptionHandler] Nest can't resolve dependencies of the DbService (?). Please make sure that the argument SurrealDbService at index [0] is available in the DbModule context.
@Global()
@Module({
  providers: [
    SurrealDbService,
    ...createNestSurrealDbModuleProviders,

Problems

Graphql Error Handling

the trick to work with gql and http errors at same time is in files

  • packages/app-gql/src/app.module.ts to return minimal error message
if (err.message) {
  return new Error(err.message);
}
  • packages/app-gql/src/common/filters/all-exceptions.filter.ts
if ((ctx as any).contextType === 'graphql') {
  throw exception;
}    

How to use Generic @InputType() in BaseResolver

Hi, I followed GraphQL Class Inheritance and implemented a BaseResolver, and everything works well, I can extended it and have all the resolvers working, all @Query, @Mutation and @Subscription that don't need specfic @InputType() generics, like create and update in @Mutation's, for ex

@InputType()
export class CreateRecipeInput extends BaseCreateEntityInput {
...
@InputType()
export class UpdateRecipeInput extends BaseUpdateEntityInput {

the problem is that I can't figure out the right way to pass the generic concrete type (CreateRecipeInput, UpdateRecipeInput) in function BaseResolver, I try hard and try everything I can't remember

my implementation with a non working createEntity because it uses specific @InputType() class

export function BaseResolver<T extends Type<BaseEntity>>(classRef: T): any {
  @Resolver({ isAbstract: true })
  abstract class BaseResolverHost {
    protected pubSub = new PubSub();

    constructor(private readonly serviceRef: BaseService<Type<BaseEntity>, BaseFindAllArgs, BaseCreateEntityInput, BaseUpdateEntityInput>) { }

    // BOF DON'T WORK because I Can't pass the specfic @InputType() in generic function BaseResolver
    @Mutation(() => [classRef], { name: `create${classRef.name}` })
    async createEntity(@Args(`create${classRef.name}Input`) createEntityInput: K) {
      const entity = await this.serviceRef.create(createEntityInput);
      pubSub.publish(`${classRef.name.toLowerCase()}Added`, { entityAdded: entity });
      return entity;
    }
    // EOF DON'T WORK because I Can't pass the specfic @InputType() in generic function BaseResolver

    @Query(() => classRef, { name: `findOne${classRef.name}` })
    async findOne(@Args('id') id: string) {
      return this.serviceRef.findOne(id);
    }

    @Query(() => [classRef], { name: `findMany${classRef.name}s` })
    async findMany(@Args() args: BaseFindAllArgs): Promise<any[]> {
      return this.serviceRef.findMany(args);
    }

    @Mutation(() => Boolean, { name: `remove${classRef.name}` })
    async removeEntity(@Args('id') id: string) {
      this.pubSub.publish(`${classRef.name.toLowerCase()}Deleted`, { [`${classRef.name.toLowerCase()}Deleted`]: id });
      return this.serviceRef.remove(id);
    }

    @Subscription(() => classRef, { name: `${classRef.name.toLowerCase()}Added` })
    entityAdded() {
      return this.pubSub.asyncIterator(`${classRef.name.toLowerCase()}Added`);
    }

    @Subscription(() => classRef, { name: `${classRef.name.toLowerCase()}Updated` })
    entityUpdated() {
      return this.pubSub.asyncIterator(`${classRef.name.toLowerCase()}Updated`);
    }

    @Subscription(() => String, { name: `${classRef.name.toLowerCase()}Deleted` })
    entityDeleted() {
      return this.pubSub.asyncIterator(`${classRef.name.toLowerCase()}Deleted`);
    }
  }
  return BaseResolverHost;
}

the question is how can I pass the generic concrete types @InputType() CreateRecipeInput and @InputType() UpdateRecipeInput into function BaseResolver?

what I really want to do is have a BaseResolver that implement the full "crud" for it's extended classes. the only that changes is the Dto @InputType, I think is awesome if we can do it, because this way we don't DRY same thing in every resolvers again and again

thanks

if I use for ex K extends BaseCreateEntityInput with createEntityInput: K it gives me the error Error: Undefined type error. Make sure you are providing an explicit type for the "create" (parameter at index [0]) of the "BaseResolverHost" class. on boot

export function BaseResolver<T extends Type<BaseEntity>, K extends BaseCreateEntityInput>(classRef: T): any {
  @Resolver({ isAbstract: true })
  abstract class BaseResolverHost {
    ...
    @Mutation(() => [classRef], { name: `create${classRef.name}` })
    async createEntity(@Args(`create${classRef.name}Input`) createEntityInput: K) {

with and without implicit generic BaseResolver<Type<Restaurant>, CreateRecipeInput> in extends BaseResolver

@Resolver(() => Restaurant)
export class RestaurantsResolver extends BaseResolver<Type<Restaurant>, CreateRestaurantInput>(Restaurant) {

if I change createEntityInput: K with createEntityInput: CreateRestaurantInput obvious it works, but useless

Solution for BaseResolver with DTO's (createEntity and updateEntity) with generics

Nice Post WITH THE SOLUTION [Startup MVP recipes #6] GraphQL Resolver inheritance and a CRUD base resolver with generics - James Zhang

the trick is using CreateClassRefInput: Type<K> with PascalCase because its a Type, and next use the , { type: () => CreateClassRefInput }) createClassRefInput: K and this.serviceRef.create(createClassRefInput) and it starts to work as expected, awesome, pure magic

export function BaseResolver<T extends Type<BaseEntity>, K extends BaseCreateEntityInput>(classRef: T, CreateClassRefInput: Type<K>): any {
  @Resolver({ isAbstract: true })
  abstract class BaseResolverHost {
    protected pubSub = new PubSub();

    constructor(private readonly serviceRef: BaseService<Type<BaseEntity>, BaseFindAllArgs, BaseCreateEntityInput, BaseUpdateEntityInput>) { }

    @Mutation(() => [classRef], { name: `create${classRef.name}` })
    async createEntity(@Args(`create${classRef.name}Input`, { type: () => CreateClassRefInput }) createClassRefInput: K) {
      const entity = await this.serviceRef.create(createClassRefInput);
      this.pubSub.publish(`${classRef.name.toLowerCase()}Added`, { entityAdded: entity });
      return entity;
    }

and in subclass we use BaseResolver<Type<Restaurant>, CreateRestaurantInput>(Restaurant, CreateRestaurantInput), or let TS infer its generic type with BaseResolver(Restaurant, CreateRestaurantInput)

@Resolver(() => Restaurant)
export class RestaurantsResolver extends BaseResolver(Restaurant, CreateRestaurantInput) {
  // we omit implicit <Type<Restaurant>, CreateRestaurantInput>, and let TS infer it :)
  constructor(private readonly restaurantsService: RestaurantsService, private readonly recipesService: RecipesService) {
    super(restaurantsService);
  }

Resolve N+1 Problem

create a restaurant and 5 recipes, all with same restaurant, create 1+5 request, 1 for recipes, and the other 5 to resolve the same restaurant

[2022-09-26 22:36:56] DEBUG surrealdb::dbs Executing: SELECT * FROM type::table($table) LIMIT 10 START 0
[2022-09-26 22:36:56] DEBUG surrealdb::dbs Executing: SELECT * FROM $what
[2022-09-26 22:36:56] DEBUG surrealdb::dbs Executing: SELECT * FROM $what
[2022-09-26 22:36:56] DEBUG surrealdb::dbs Executing: SELECT * FROM $what
[2022-09-26 22:36:56] DEBUG surrealdb::dbs Executing: SELECT * FROM $what
[2022-09-26 22:36:56] DEBUG surrealdb::dbs Executing: SELECT * FROM $what
$ cd packages/app-gql
# require force to work with nestjs 9
$ npm install @tracworx/nestjs-dataloader --force

Fix Error #1: NestJS/GraphQL/DataLoader】Context creation failed: dataloader_1.default is not a constructor

import * as DataLoader from 'dataloader';
// instead of
import DataLoader from 'dataloader';

Fix #2: DataLoader with Arrays ex

Your batch load function should return a Promise with an array. As the error indicates, the length of the keys passed to the batch load function must match the length of this result array. If your Loader is fetching an array of items for each key, then the Promise must resolve to an array of arrays.

const backLoadFn = (keys) => {
  return Promise.all(keys.map(key => {
    return Model.findAll({ where: { key } })
  }))
}
query findManyRestaurants($skip: Int, $take: Int) {
  findManyRestaurants(skip: $skip, take: $take) {
    id
    # this is a array
    recipes {
      id
      title
    }
  }
}

npm ERR! need auth This command requires you to be logged in to https://registry.yarnpkg.com/

$ yarn start:app-lib:publish
...
npm ERR! code ENEEDAUTH
npm ERR! need auth This command requires you to be logged in to https://registry.yarnpkg.com/
npm ERR! need auth You need to authorize this machine using `npm adduser`

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/mario/.npm/_logs/2022-10-11T21_10_15_256Z-debug-0.log
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

to fix just use npm publish

$ cd packages/app-lib
$ npm publish
# outcome
npm notice Publishing to https://registry.npmjs.org/
+ @koakh/nestjs-surrealdb@0.1.5

Force Consumer Apps to use AppLib package

in case of consumer apps don't have the last updates from app-lib use

$ rm packages/app-lib/dist/ -R 
$ rm packages/app-rst/node_modules/@koakh -R
$ rm packages/app-gql/node_modules/@koakh -R

$ yarn app-lib:dev
$ yarn install --mode=skip-build
$ yarn start:app-rst
$ yarn start:app-gql

Fix: npm error code EINVALIDPACKAGENAME

$ npm run version:patch
v0.1.18
npm error code EINVALIDPACKAGENAME
npm error Invalid package name "// in case of error: npm error Unsupported URL Type "workspace:": workspace:^, comment bellow line and use npm version patch" of package "// in case of error: npm error Unsupported URL Type "workspace:": workspace:^, comment bellow line and use npm version patch@": name can only contain URL-friendly characters.
  • packages/app-rst/package.json
"dependencies": {
    "// in case of error: npm error Unsupported URL Type \"workspace:\": workspace:^, remove this line and bellow line and use npm version:patch": "",
    // "@koakh/nestjs-surrealdb": "workspace:^",
}

fix removing both lines,

after this both commands works flawless version:patch and version:publish

SurrealDB NestJS Type Safety: Claude help on use Generics with SurrealDb SDK, DTO Validations

Reconnect

required bump "surrealdb": "^1.3.2" to "surrealdb": "v2.0.0-alpha.4"

$ yarn add surrealdb@v2.0.0-alpha.4 --mode=skip-build