ListQueryBuilder

ListQueryBuilder

This helper class is used when fetching entities the database from queries which return a PaginatedList type. These queries all follow the same format:

In the GraphQL definition, they return a type which implements the Node interface, and the query returns a type which implements the PaginatedList interface:

type BlogPost implements Node {
  id: ID!
  published: DataTime!
  title: String!
  body: String!
}

type BlogPostList implements PaginatedList {
  items: [BlogPost!]!
  totalItems: Int!
}

# Generated at run-time by Vendure
input ProductListOptions

extend type Query {
   blogPosts(options: BlogPostListOptions): BlogPostList!
}

When Vendure bootstraps, it will find the ProductListOptions input and, because it is used in a query returning a PaginatedList type, it knows that it should dynamically generate this input. This means all primitive field of the BlogPost type (namely, “published”, “title” and “body”) will have filter and sort inputs created for them, as well a skip and take fields for pagination.

Your resolver function will then look like this:

@Resolver()
export class BlogPostResolver
  constructor(private blogPostService: BlogPostService) {}

  @Query()
  async blogPosts(
    @Ctx() ctx: RequestContext,
    @Args() args: any,
  ): Promise<PaginatedList<BlogPost>> {
    return this.blogPostService.findAll(ctx, args.options || undefined);
  }
}

and the corresponding service will use the ListQueryBuilder:

@Injectable()
export class BlogPostService {
  constructor(private listQueryBuilder: ListQueryBuilder) {}

  findAll(ctx: RequestContext, options?: ListQueryOptions<BlogPost>) {
    return this.listQueryBuilder
      .build(BlogPost, options)
      .getManyAndCount()
      .then(async ([items, totalItems]) => {
        return { items, totalItems };
      });
  }
}

Signature

class ListQueryBuilder implements OnApplicationBootstrap {
  constructor(connection: TransactionalConnection, configService: ConfigService)
  build(entity: Type<T>, options: ListQueryOptions<T> = {}, extendedOptions: ExtendedListQueryOptions<T> = {}) => SelectQueryBuilder<T>;
}

Implements

  • OnApplicationBootstrap

Members

constructor

method
type:
(connection: TransactionalConnection, configService: ConfigService) => ListQueryBuilder

build

method
type:
(entity: Type<T>, options: ListQueryOptions<T> = {}, extendedOptions: ExtendedListQueryOptions<T> = {}) => SelectQueryBuilder<T>
Creates and configures a SelectQueryBuilder for queries that return paginated lists of entities.

ExtendedListQueryOptions

Options which can be passed to the ListQueryBuilder’s build() method.

Signature

type ExtendedListQueryOptions<T extends VendureEntity> = {
  relations?: string[];
  channelId?: ID;
  where?: FindConditions<T>;
  orderBy?: FindOneOptions<T>['order'];
  ctx?: RequestContext;
  customPropertyMap?: { [name: string]: string };
}

Members

relations

property
type:
string[]

channelId

property
type:
ID

where

property
type:
FindConditions<T>

orderBy

property
type:
FindOneOptions<T>['order']

ctx

property
When a RequestContext is passed, then the query will be executed as part of any outer transaction.

customPropertyMap

property
type:
{ [name: string]: string }

One of the main tasks of the ListQueryBuilder is to auto-generate filter and sort queries based on the available columns of a given entity. However, it may also be sometimes desirable to allow filter/sort on a property of a relation. In this case, the customPropertyMap can be used to define a property of the options.sort or options.filter which does not correspond to a direct column of the current entity, and then provide a mapping to the related property to be sorted/filtered.

Example: we want to allow sort/filter by and Order’s customerLastName. The actual lastName property is not a column in the Order table, it exists on the Customer entity, and Order has a relation to Customer via Order.customer. Therefore we can define a customPropertyMap like this:

const qb = this.listQueryBuilder.build(Order, options, {
  relations: ['customer'],
  customPropertyMap: {
      customerLastName: 'customer.lastName',
  },
};