For the complete documentation index, see llms.txt. This page is also available as Markdown.

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/soap 0.10.0 (interface) and @soapjs/soap-node-mongo 0.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 RepositoryQuery and is called from one repository method — never smuggled through as any in 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) and RepositoryQuery (see Using QueryBuilder); use native() only when the abstraction genuinely can't express the query.

Last updated