Querying
All read methods on ElasticCollectionService accept a shared set of options for filtering, shaping, and paginating results.
Filtering
OPRA filter string
The simplest way to filter is with an OPRA filter string:
const products = await svc.findMany({ filter: 'category = "electronics"' });
const products = await svc.findMany({ filter: 'price > 100 and inStock = true' });
const products = await svc.findMany({ filter: 'rate > 5' });
Elasticsearch query DSL
For full control, pass a raw Elasticsearch query DSL object:
const products = await svc.findMany({
filter: {
bool: {
must: [
{ term: { category: 'electronics' } },
{ range: { price: { gte: 100, lte: 500 } } },
],
},
},
});
// Full-text search
const products = await svc.findMany({
filter: {
match: { name: 'wireless headphones' },
},
});
documentFilter — global index-level constraint
documentFilter is applied to every read and write operation on a service instance. Use it to implement multi-tenancy, soft deletes, or any constraint that must never be bypassed:
export class ProductsService extends ElasticCollectionService<Product> {
constructor(client: Client) {
super(Product, {
client,
documentFilter: (_, _this) => `tenantId = "${_this.context.tenantId}"`,
});
}
}
It can also be a raw query DSL object:
documentFilter: { term: { active: true } }
Use documentFilter for constraints that must always hold. Use the method-level filter option for ad-hoc query criteria.
Projection
Control which fields are returned by passing an array of field names.
const products = await svc.findMany({
filter: 'inStock = true',
projection: ['_id', 'name', 'price'],
});
// TypeScript return type narrows to PartialDTO<Product> when projection is supplied
const partial = await svc.get(id, { projection: ['_id', 'name'] });
partial.name; // ✓
partial.price; // ✗ — not in projection, TypeScript error
Sorting
Pass an array of field names. Prefix with - for descending order.
const products = await svc.findMany({
sort: ['category', '-price'], // category ASC, price DESC
});
Pagination
Use limit and skip together, or call findManyWithCount to get the total in one round trip.
const PAGE_SIZE = 20;
const page = 3;
const { items, count, relation } = await svc.findManyWithCount({
filter: 'inStock = true',
sort: ['-price'],
limit: PAGE_SIZE,
skip: (page - 1) * PAGE_SIZE,
});
console.log(`Page ${page} of ${Math.ceil(count / PAGE_SIZE)}`);
// relation: 'eq' (exact) or 'gte' (Elasticsearch estimate for large result sets)
findMany applies defaultLimit (default 10) when limit is not specified. Set it in the constructor to change the service default:
super(Product, { defaultLimit: 50 });
Raw search
Use searchRaw when you need full access to the Elasticsearch search API — aggregations, highlighting, suggester, custom scoring, etc.:
const response = await svc.searchRaw({
index: 'products',
query: {
multi_match: {
query: 'wireless audio',
fields: ['name', 'description'],
},
},
aggs: {
categories: {
terms: { field: 'category.keyword' },
},
},
highlight: {
fields: { name: {} },
},
});
Checking existence
// Does this specific document exist?
const exists = await svc.exists(id);
// Does any document match a filter?
const hasStock = await svc.existsOne({ filter: 'inStock = true' });
// Assert existence — throws ResourceNotAvailableError if not found
await svc.assert(id);