Skip to main content

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 } }
tip

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 });

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);

Full API reference

ElasticCollectionService