Using Native Queries
When a query can't be expressed with the Where builder or a RepositoryQuery (aggregations, geo queries, full-text ranking, vendor-specific operators, raw SQL), SoapJS gives you a sanctioned escape hatch: Source.native().
Instead of reaching around the repository to the raw database driver, you wrap the native query in a named RepositoryQuery and hand it to the source. The query stays quarantined in one place, the rest of your code keeps depending on the abstraction.
Available since
@soapjs/soap0.10.0 (interface) and@soapjs/soap-node-mongo0.7.0 (MongoDB implementation).
The pattern
Source.native() executes a client-specific query verbatim. The payload is produced by a RepositoryQuery's build() — so the dialect (a MongoDB pipeline, a SQL string) lives in a named query object, not scattered through your code.
interface Source<DocumentType> {
// ...find / count / aggregate / update / insert / remove
native<ResultType = DocumentType[]>(query: DbQuery | RepositoryQuery): Promise<ResultType>;
}native() bypasses the query factory and the entity mapper — it returns the raw result from the driver. You map it yourself if you need domain entities.
Example — a MongoDB aggregation
Define the native query as a RepositoryQuery. Its build() returns the raw MongoDB pipeline:
import { RepositoryQuery } from '@soapjs/soap';
export interface UniverseStatRow {
_id: { universe: string; alignment: string };
count: number;
}
export class UniverseStatsAggregation extends RepositoryQuery<Record<string, unknown>[]> {
build(): Record<string, unknown>[] {
return [
{ $group: { _id: { universe: '$universe', alignment: '$alignment' }, count: { $sum: 1 } } },
{ $sort: { '_id.universe': 1, '_id.alignment': 1 } },
];
}
}Run it from a repository method through the source — no raw Collection handle:
How the MongoDB source dispatches the payload
MongoSource.native() recognizes the shape produced by build():
build() returns
Runs as
an array
aggregation pipeline (collection.aggregate)
{ kind: 'aggregate', pipeline, options? }
aggregation
{ kind: 'find', filter, options? }
collection.find
{ kind: 'command', command }
db.command
Other databases
native() is part of the Source contract, so every adapter implements it for its own dialect. A SQL source, for example, would accept a RepositoryQuery whose build() returns a parameterized statement:
Guidelines
Keep it named and quarantined. A native query belongs in a dedicated
RepositoryQueryand is called from one repository method — never smuggled throughas anyin a generic query.It is dialect-specific by design. Swapping the database means rewriting the native query object; the repository method signature and the rest of your code stay the same.
It returns raw rows.
native()skips the mapper — map to domain entities in the repository method if needed.Reach for it last. Prefer
Where(see Using Where Conditions) andRepositoryQuery(see Using QueryBuilder); usenative()only when the abstraction genuinely can't express the query.
Last updated