npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@getplate/core

v7.2.1

Published

This package contains a variety of useful implementations for NestJS services.

Downloads

19

Readme

@getplate/core

This package contains the core functionality of the Plate NestJS microservices.

Getting Started

To add this to your project: yarn add @getplate/core or npm install @getplate/core.

Features

class-transformer

  • TransformDate decorator, to automatically transform dates to ISO strings and back.

class-validator

  • ClassValidatorError for throwing exceptions (class-validator errors are not actually error classes, so this wrapper makes them errors).
  • AtLeastOneDefined decorator, to validate that at least one of the given properties is defined.
  • ExactlyOneDefined decorator, to validate that exactly one of the given properties is defined.
  • IsFileName decorator, to validate that the given string is a valid file name. Also exported as a regular method.
  • GreaterThan decorator, to validate that the given number is greater than another property.
  • LessThan decorator, to validate that the given number is less than another property.
  • GreaterThanOrEqual decorator, to validate that the given number is greater than or equal to another property.
  • LessThanOrEqual decorator, to validate that the given number is less than or equal to another property.

Configuration

A global NestJS module which loads the environment variables into a configuration object and validates it using class-validator.

Getting started

To use this module, import it into your root module and call ConfigModule#forRoot in the imports array. The Config class will be available globally.
Environment variables must have the same case as the class properties.
A good rule of thumb is to use replace nested class fields with double underscores (e.g. config.foo.bar becomes foo__bar).

Example:

// Environment variables
NODE_ENV = dev
foo__bar = bar
// foo.config.ts
export class FooConfig {
  @IsDefined()
  public readonly bar!: string;
}

// config.ts
export class Config {
  @IsDefined()
  @IsIn(["dev", "test", "production"])
  public readonly nodeEnv!: AavailableNodeEnvs;

  @IsDefined()
  @Type(() => NestedConfig)
  @ValidateNested()
  public readonly foo!: FooConfig;
}

// app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      schema: Config,
    }),
  ],
})
export class AppModule {
}

Changing the configuration

There are a few things to note when changing anything regarding the configuration:

  • Make sure to put any environment variables required to run the service in the .env.sample file, so others can know which environment variables to set up.
  • To parse nested objects from environment variables, use the separator specified in app.module.ts. Example using separator __ (double underscore): db__url turns into config.db.url
  • Nested objects must be defined in the environment variables. Otherwise, the object will not be parsed and the parent config will return undefined. If there are multiple fields in the nested object, only one needs to be defined. The other fields will take the default values given in the config object.
  • The format of the environment variables matters. Keeping them camelCase removes the need for normalization.
  • Dashes/underscores are not parsed, so they must be normalized (like NODE_ENV, which is normalized by default).
  • When normalizing optional fields, make sure to check whether the non-normalized version is undefined to avoid overriding the default value with undefined:
if (config.SOME_OPTIONAL_VAR_WHICH_NEEDS_NORMALIZING !== undefined) {
  config.someOptionalVarWhichNeedsNormalizing =
    config.SOME_OPTIONAL_VAR_WHICH_NEEDS_NORMALIZING;
}

Authentication/Authorization

  • Global AuthorizationModule for initializing the module with default options.
  • Globally enables the AuthorizationGuard. Automatically recognizes CRUD methods and applies the correct permissions. Otherwise, you must manually set the action. For now, allows all microservice messages.
  • Provides a AuthorizationOptions decorator to modify the authorization options for a resolver or route.
  • UnauthorizedError for when users try to access a protected resource without sending a proper Authorization header.
  • ForbiddenError for when users try to access a resource they are not authorized to access.
  • SubjectMiddleware to add the subject (automatically detects users or Api Tokens) to the request scope. Register it like this:
    // app.module.ts
    @Module({
      // Module config
    })
    export class AppModule implements NestModule {
      public configure(consumer: MiddlewareConsumer): void {
        consumer
          .apply(SubjectMiddleware)
          .forRoutes({ path: "/graphql", method: RequestMethod.POST });
      }
    }
  • CurrentSubject decorator to get the subject from the request cross-context. It has the type KratosIdentityTokenPayload or ApiTokenPayload (automatically detected based on Authorization header).

GraphQL

  • Global GraphQLModule for initializing Nest’s GraphQL module with default options (including Apollo Federation 2).
  • Array arguments (Nest’s default ValidationPipe does not work for arrays, and the ParseArrayPipe only works for REST)
    • IArrayArgsType<TInput> interface to use in place of your TInput[] argument.
    • ArrayArgsType<TInput> function to extend your argument class from (see documentation comment for more info).
  • InfoData interface to use as the type of the info argument in your resolvers.

Ordering

  • OrderDirection enum to determine ascending or descending ordering.
  • OrderOptionsInput input to use as argument in your resolver.

Example:

@Resolver()
export class SomeResolver {
  @Query()
  public async findAll(
    @Args("orderBy", {
      nullable: true,
    })
      orderOptions: OrderOptionsInput
  ): Promise<SomeModelConnection> {
    // Your logic here
  }
}

Filtering

  • FilterField decorator to mark fields as filterable. Use @FilterField({ isRelation: true }) for relations.
  • PRN_FILTER_OPTIONS object to use in @FilterField for PRN fields.
  • Filter function which returns a generated filter input class.
  • PrimitiveFilter union, combining all primitive filter classes (also exported individually).

Example:

@ObjectType()
export class User {
  @Field()
  @FilterField()
  public id!: string;

  @Field()
  @FilterField()
  public name!: string;

  @Field(() => [User])
  @FilterField({isRelation: true})
  public friends!: User[];
}

export const UsersFilterInput = Filter(User);

Pagination

  • Cursor for storing and generating the data about cursors.
  • PageInfo payload class.
  • Paginated function to inherit from to create the paginated payload (see the example in the documentation comment).
  • Connection interface to serve as the root type of a paginated response (includes an Edge interface to define edges).
  • PaginationOptionsArgs input class for receiving pagination arguments.

Example:

export class User {
  @Field()
  public id!: string;

  @Field()
  public name!: string;
}

@ObjectType()
export class UsersConnection extends Paginated(User) {
}

findAll resolver example

@ObjectType()
export class User {
  @Field()
  @FilterField()
  public id!: string;

  @Field()
  @FilterField()
  public name!: string;

  @Field(() => [User])
  @FilterField({isRelation: true})
  public friends!: User[];
}

export const UsersFilterInput = Filter(User);

@ObjectType()
export class UsersConnection extends Paginated(User) {
}

@ArgsType()
export class FindAllUsersArgs implements FindAllArgs<User> {
  @Field(() => PaginationOptionsInput)
  @IsDefined()
  @ValidateNested()
  public readonly paginate!: PaginationOptionsInput;

  @Field(() => OrderOptionsInput, {
    nullable: true,
    defaultValue: {},
  })
  @IsDefined()
  @ValidateNested()
  public readonly orderBy!: OrderOptionsInput;

  @Field(() => [UsersFilterInput], {nullable: true})
  @IsOptional()
  @Type(() => UsersFilterInput)
  @ValidateNested({each: true})
  public readonly where?: FilterInput<User>[];
}

@Resolver(() => User)
export class UsersResolver {
  @Query(() => UsersConnection, {
    name: "users",
    description: `Orders and paginates all Users.`,
  })
  public override async findAll(
    @Args() {paginate, orderBy, where}: FindAllUsersArgs
  ): Promise<Connection<Users>> {
    // Implementation...
  }
}

PRN helpers

  • PrnScalar so the consuming service can just use PRN classes as Field types (this is already registered in the GraphQLModule, so you do not need to register it yourself)

  • PrnField to mark an id field as a PRN. See the decorator’s description for more information. WARNING: This requires you to generate the PRN field using @ResolveField in the model’s resolver. Example:

    // some.model.ts
    @ObjectType()
    export class SomeModel {
      @PrnField() // Automatically converts `id` to `prn`
      public id!: string;
    }
    
    // some.resolver.ts
    @Resolver(() => SomeModel)
    export class SomeResolver {
      @ResolveField(() => PRN)
      public prn(@Parent() entity: SomeModel, @Context() context: any): string {
        return new PRN(
          this.config.partition,
          entity.organizationId,
          ServiceAbbreviation.YOUR_SERVICE,
          "some-model",
          entity.id
        ).toString();
      }
    }
  • PrnArgs to use an id parameter in a resolver. See the decorator’s description for more information.

Federation

We use Apollo Federation 2 for our GraphQL subgraphs. See the NestJS documentation for the basics.

  • Reference interface to use as return type in @ResolveField method for federated types and in @ResolveReference when resolving a federated type (see example below). This contains the __typename and the prn field. For composite keys, inherit from the base Reference class and add the additional fields.

Example:

// federated.model.ts
@ObjectType()
@Directive(`key(fields: "prn compositeId")`)
export class FederatedModel {
  @PrnField()
  public id!: string;

  @Field()
  public compositeId!: string;
}

// federated.resolver.ts
@Resolver(() => FederatedModel)
export class FederatedModelResolver {
  @ResolveReference()
  public async resolveReference(reference: Reference): Promise<Asset> {
    // Your fetching logic here
  }
}

// other.model.ts
@ObjectType()
export class OtherModel {
  @PrnField()
  public id!: string;

  @Field(() => FederatedModel)
  public federatedModel!: FederatedModel;
}

// other.resolver.ts
@Resolver(() => OtherModel)
export class OtherModelResolver {
  @ResolveField(() => FederatedModel)
  public federatedModel(@Parent() entity: OtherModel): Reference {
    return {
      __typename: FederatedModel.name,
      prn: entity.federatedModel.id,
      compositeId: entity.federatedModel.compositeId,
    };
  }
}

For composite keys, it is required to store all identifying fields in the Reference, key directive, and in the database.

Microservices

  • Global MicroservicesWrapperModule to initialize the wrapper. This takes in a server QueueId and optionally a list of client QueueIds.

  • MicroservicesProxy (provider) to send messages to other services. Inject this to send messages.

  • SubscribeToEvent, SubscribeToMessage, and SubscribeToGlobalEvent decorators to add message handlers.

  • Strongly typed messages. To add your own strongly typed message, add the following to this project:

    // messages-data.dto.ts
    export interface YourMessageData {
      // Your data
    }
    
    // message-handlers.interface.ts
    export interface MessageHandlers {
      // Other handlers
    
      onYourMessage?(data: YourMessageData): Promise<YourMessageResponse>;
    }
    
    // microservice-identifiers.enum.ts
    export const enum MessageId {
      // Other messages
      YOUR_MESSAGE = "YOUR_MESSAGE",
    }
    
    // microservices-proxy.ts
    export class MicroservicesProxy {
      // Other methods
    
      public async sendYourMessage(
        data: YourMessageData
      ): Promise<YourMessageResponse> {
        return this.sendMessage(QueueId.YOUR_QUEUE, MessageId.YOUR_MESSAGE, data); // Or sendEvent, sendGlobalEvent
      }
    }

    subscribe to the message in a controller in your microservice consumer like this:

    // some.controller.ts
    @Controller()
    export class SomeController implements MessageHandlers {
      @SubscribeToMessage(MessageId.YOUR_MESSAGE)
      public async onYourMessage(
        data: YourMessageData
      ): Promise<YourMessageResponse> {
        // Handle message
      }
    }

    send the message from a microservice publisher like this:

    // some.service.ts
    @Injectable()
    export class SomeService {
      public constructor(
        private readonly microservicesProxy: MicroservicesProxy
      ) {}
    
      public async someMethod(): Promise<void> {
        const response = await this.microservicesProxy.sendYourMessage({
          // Your data
        });
      }
    }
  • QueueId, MessageId enums for interacting with queues and messages.

  • MessageHandlers interface containing signatures to use when consuming messages (see documentation comment for more information).

  • DTO classes for the data to pass in a message.

Errors

  • NotFoundError for when a resource is requested which does not exist.
  • InvalidArgumentError for when users try to call a resolver or route without the correct arguments.
  • BadRequestError for when users try to call an endpoint with data which is invalid (the InvalidArgumentError is for invalid arguments directly, while BadRequestError is a more high-level constraint being unfulfilled).
  • UnprocessableEntityException for when users try to call an endpoint with data which is invalid ( the InvalidArgumentError is for invalid arguments directly, while UnprocessableEntityException is a more high-level constraint being unfulfilled).

Headers

  • Headers decorator to get one or more headers from the request cross-context.
  • RequiredHeader decorator to get a header from the request cross-context and validates it. If invalid, throws an UnprocessableEntityException.
  • PrnHeader decorator to get a header from the request cross-context and validates it as a PRN. If invalid, throws an UnprocessableEntityException.
  • EnumHeader decorator to get a header from the request cross-context and validates it as an enum. If invalid, throws an UnprocessableEntityException.

Other

  • RequestScope interface for passing data across the whole request.
  • AvailableNodeEnvs enum and isNodeEnv method to make interacting with NODE_ENV easier.
  • SLUG_REGEX string and RegExp object to verify the format of slugs.
  • generateMermaidDependencyGraph function to generate a mermaid dependency graph from a list of classes.
    1. Install the package yarn install --dev nestjs-spelunker
    2. Add const mermaidGraph = generateMermaidDependencyGraph(app); to the end of your main.ts#bootstrap function.
    3. Copy and paste the result of this function into https://mermaid.live or https://draw.io (or any other mermaid graph visualizer).
  • ApiTokenData interface which defines the data that is stored in an Api Token.
  • initTracing function to initialize tracing for the service.