Skip to main content

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: DateTime!
title: String!
body: String!
}

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

# Generated at run-time by Vendure
input BlogPostListOptions

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

When Vendure bootstraps, it will find the BlogPostListOptions 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)
filterObjectHasProperty(filterObject: FP | NullOptionals<FP> | null | undefined, property: keyof FP) => boolean;
build(entity: Type<T>, options: ListQueryOptions<T> = {}, extendedOptions: ExtendedListQueryOptions<T> = {}) => SelectQueryBuilder<T>;
}
  • Implements: OnApplicationBootstrap

constructor

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

filterObjectHasProperty

method
(filterObject: FP | NullOptionals<FP> | null | undefined, property: keyof FP) => boolean

Used to determine whether a list query filter object contains the given property, either at the top level or nested inside a boolean _and or _or expression.

This is useful when a custom property map is used to map a filter field to a related entity, and we need to determine whether the filter object contains that property, which then means we would need to join that relation.

build

method
(entity: Type<T>, options: ListQueryOptions<T> = {}, extendedOptions: ExtendedListQueryOptions<T> = {}) => SelectQueryBuilder<T>

ExtendedListQueryOptions

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

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

relations

property
string[]

channelId

property

where

property
FindOptionsWhere<T>

orderBy

property
FindOneOptions<T>['order']

entityAlias

property
v1.6.0
string

Allows you to specify the alias used for the entity T in the generated SQL query. Defaults to the entity class name lower-cased, i.e. ProductVariant -> 'productvariant'.

ctx

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

customPropertyMap

property
{ [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:

Example

"""
Manually extend the filter & sort inputs to include the new
field that we want to be able to use in building list queries.
"""
input OrderFilterParameter {
customerLastName: StringOperators
}

input OrderSortParameter {
customerLastName: SortOrder
}

Example

const qb = this.listQueryBuilder.build(Order, options, {
relations: ['customer'],
customPropertyMap: {
// Tell TypeORM how to map that custom
// sort/filter field to the property on a
// related entity.
customerLastName: 'customer.lastName',
},
};

We can now use the customerLastName property to filter or sort on the list query:

Example

query {
myOrderQuery(options: {
filter: {
customerLastName: { contains: "sm" }
}
}) {
# ...
}
}

ignoreQueryLimits

property
v2.0.2
boolean
default:
false

When set to true, the configured shopListQueryLimit and adminListQueryLimit values will be ignored, allowing unlimited results to be returned. Use caution when exposing an unlimited list query to the public, as it could become a vector for a denial of service attack if an attacker requests a very large list.