Skip to content

feat(query-mikro-orm): implement missing QueryService methods#453

Open
jtomaszewski wants to merge 1 commit into
TriPSs:masterfrom
fullstackhouse:mikro-orm-query-service-methods
Open

feat(query-mikro-orm): implement missing QueryService methods#453
jtomaszewski wants to merge 1 commit into
TriPSs:masterfrom
fullstackhouse:mikro-orm-query-service-methods

Conversation

@jtomaszewski

@jtomaszewski jtomaszewski commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Implements core CRUD operations in MikroOrmQueryService: createOne, createMany, updateOne, updateMany, deleteOne, deleteMany
  • Adds aggregate() method with groupBy support
  • Adds count() method for totalCount in GraphQL connections
  • Includes comprehensive test coverage

Test plan

  • Unit tests added for all new methods
  • Run existing test suite to verify no regressions

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Mikro-ORM support with comprehensive data operations including single and bulk create, update, and delete functionality.
    • Introduced aggregation and analytics capabilities supporting count, sum, average, maximum, minimum calculations with filtering and grouping.
  • Tests

    • Added extensive test coverage for Mikro-ORM query service operations and error handling scenarios.

@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This change introduces Jest configuration for the Mikro-ORM package and adds comprehensive CRUD (create, update, delete) and aggregation operations to the MikroOrmQueryService class, alongside extensive test coverage validating persistence, filtering, error handling, and in-memory aggregations.

Changes

Cohort / File(s) Summary
Jest Configuration
jest.config.ts, jest.preset.js
Updated Jest projects array to include packages/query-mikro-orm and added module path mapping for @ptc-org/nestjs-query-mikro-orm to enable test discovery and module resolution for the new package.
MikroORM Query Service Implementation
packages/query-mikro-orm/src/services/mikro-orm-query.service.ts
Added seven new public methods: createOne, createMany, updateOne, updateMany, deleteOne, deleteMany, and aggregate. Includes persistence via persistAndFlush/removeAndFlush, bulk updates via nativeUpdate, in-memory aggregation computation, and explicit error handling for unsupported options (useSoftDelete, withDeleted).
MikroORM Query Service Tests
packages/query-mikro-orm/__tests__/services/mikro-orm-query.service.spec.ts
New comprehensive test suite covering all CRUD methods and aggregate operations, including persistence verification, filtering/grouping, edge cases (non-existent entities, unsupported options), and aggregation functions (count, sum, avg, max, min).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MikroOrmQueryService
    participant EntityManager
    participant Database
    
    rect rgba(100, 150, 200, 0.5)
    Note over Client,Database: Create Operation
    Client->>MikroOrmQueryService: createOne(record)
    MikroOrmQueryService->>EntityManager: create(entityData)
    MikroOrmQueryService->>EntityManager: persistAndFlush()
    EntityManager->>Database: INSERT
    Database-->>EntityManager: persisted entity
    MikroOrmQueryService->>MikroOrmQueryService: convert to DTO (assembler)
    MikroOrmQueryService-->>Client: DTO
    end
Loading
sequenceDiagram
    participant Client
    participant MikroOrmQueryService
    participant Repository
    participant EntityManager
    participant Database
    
    rect rgba(150, 100, 200, 0.5)
    Note over Client,Database: Update Operation
    Client->>MikroOrmQueryService: updateOne(id, update)
    MikroOrmQueryService->>Repository: findOne(id)
    Repository->>Database: SELECT
    Database-->>Repository: entity
    MikroOrmQueryService->>EntityManager: wrap(entity).assign(update)
    MikroOrmQueryService->>EntityManager: flush()
    EntityManager->>Database: UPDATE
    Database-->>EntityManager: updated entity
    MikroOrmQueryService->>MikroOrmQueryService: convert to DTO
    MikroOrmQueryService-->>Client: DTO
    end
Loading
sequenceDiagram
    participant Client
    participant MikroOrmQueryService
    participant Repository
    participant Database
    
    rect rgba(200, 150, 100, 0.5)
    Note over Client,Database: Aggregate Operation
    Client->>MikroOrmQueryService: aggregate(filter, aggregateQuery)
    MikroOrmQueryService->>MikroOrmQueryService: validate (reject withDeleted)
    MikroOrmQueryService->>Repository: findAll(where)
    Repository->>Database: SELECT
    Database-->>Repository: matching entities
    MikroOrmQueryService->>MikroOrmQueryService: computeAggregateInMemory()
    MikroOrmQueryService->>MikroOrmQueryService: compute count/sum/avg/max/min/groupBy
    MikroOrmQueryService-->>Client: AggregateResponse[]
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

Poem

🐰 A service blooms with create, read, delete—
Aggregate queries make logic complete!
Mikro-ORM's strength in each test we celebrate,
Jest configs wired, our data flows straight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing missing QueryService methods in the query-mikro-orm package.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add implementations for core CRUD and aggregate operations:
- createOne/createMany
- updateOne/updateMany
- deleteOne/deleteMany
- aggregate with groupBy support
- count method for totalCount in GraphQL connections

Cherry-picked from tournee branch, excluding cursor+offset pagination changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jtomaszewski jtomaszewski force-pushed the mikro-orm-query-service-methods branch from 9ec64e2 to f9fbaae Compare March 21, 2026 23:32
@jtomaszewski jtomaszewski marked this pull request as ready for review March 21, 2026 23:33
@nx-cloud

nx-cloud Bot commented Mar 21, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit f9fbaae

Command Status Duration Result
nx run-many --target=lint --all ❌ Failed 8s View ↗
nx run-many --target=test --all ✅ Succeeded 2m 36s View ↗
nx run-many --target=e2e --all ✅ Succeeded 2m 1s View ↗
nx run-many --target=build --all ✅ Succeeded <1s View ↗
nx run workspace:version ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-21 23:39:39 UTC

@pkg-pr-new

pkg-pr-new Bot commented Mar 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ptc-org/nestjs-query-core

npm i https://pkg.pr.new/@ptc-org/nestjs-query-core@453

@ptc-org/nestjs-query-graphql

npm i https://pkg.pr.new/@ptc-org/nestjs-query-graphql@453

@ptc-org/nestjs-query-mikro-orm

npm i https://pkg.pr.new/@ptc-org/nestjs-query-mikro-orm@453

@ptc-org/nestjs-query-mongoose

npm i https://pkg.pr.new/@ptc-org/nestjs-query-mongoose@453

@ptc-org/nestjs-query-sequelize

npm i https://pkg.pr.new/@ptc-org/nestjs-query-sequelize@453

@ptc-org/nestjs-query-typegoose

npm i https://pkg.pr.new/@ptc-org/nestjs-query-typegoose@453

@ptc-org/nestjs-query-typeorm

npm i https://pkg.pr.new/@ptc-org/nestjs-query-typeorm@453

commit: f9fbaae

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
packages/query-mikro-orm/src/services/mikro-orm-query.service.ts (3)

99-109: Consider using assembler for input conversion.

When DTO and Entity differ and an assembler is provided, the input record (typed as DeepPartial<DTO>) is passed directly to em.create without conversion to Entity shape. If the DTO/Entity schemas differ significantly, this could cause runtime issues.

Proposed fix to convert input
   async createOne(record: DeepPartial<DTO>): Promise<DTO> {
     const em = this.repo.getEntityManager()
+    const entityData = this.assembler 
+      ? await this.assembler.convertToEntity(record as unknown as DTO)
+      : record
     // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-    const entity = em.create(this.repo.getEntityName(), record as any)
+    const entity = em.create(this.repo.getEntityName(), entityData as any)
     await em.persistAndFlush(entity)

     if (this.assembler) {
       return this.assembler.convertToDTO(entity as Entity)
     }
     return entity as unknown as DTO
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-mikro-orm/src/services/mikro-orm-query.service.ts` around
lines 99 - 109, The createOne method currently passes the incoming
DeepPartial<DTO> record straight into em.create; when DTO and Entity differ and
an assembler exists you should first convert the DTO-shaped input into an
Entity-shaped object using the assembler (e.g. call
assembler.convertToEntity(record) or similar) and pass that converted
entity-data into em.create (then persistAndFlush and continue returning
assembler.convertToDTO(entity)); update createOne to branch: if (this.assembler)
use the assembler to convert input before em.create and to convert the persisted
entity back to DTO afterwards.

267-272: COUNT semantics differ from SQL.

The implementation returns entities.length for all count fields, meaning COUNT(fieldA) equals COUNT(fieldB) regardless of NULL values. In standard SQL, COUNT(field) counts non-NULL values for that specific field.

If SQL-compatible behavior is needed:

SQL-compatible COUNT implementation
     if (aggregateQuery.count) {
       response.count = {}
       for (const { field } of aggregateQuery.count) {
-        ;(response.count as Record<string, number>)[String(field)] = entities.length
+        const count = entities.filter((e) => {
+          const val = (e as Record<string, unknown>)[String(field)]
+          return val !== undefined && val !== null
+        }).length
+        ;(response.count as Record<string, number>)[String(field)] = count
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-mikro-orm/src/services/mikro-orm-query.service.ts` around
lines 267 - 272, The current COUNT handling in mikro-orm-query.service.ts always
sets each COUNT(field) to entities.length; change it to compute SQL-compatible
per-field non-NULL counts by iterating entities and incrementing a counter only
when the entity's value for that field is not null/undefined. In the block that
processes aggregateQuery.count (where response.count is set), replace the
constant assignment with per-field counters keyed by String(field) and for each
entity check the field value (support nested paths if your codebase uses them)
and increment only when value !== null && value !== undefined; finally assign
those counters into response.count so COUNT(field) reflects non-NULL occurrences
for that specific field.

123-141: Same input conversion concern applies to update methods.

Both updateOne and updateMany pass update: DeepPartial<DTO> directly to MikroORM operations without assembler conversion. If using an assembler with differing DTO/Entity shapes, consider converting the update payload as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-mikro-orm/src/services/mikro-orm-query.service.ts` around
lines 123 - 141, updateOne and updateMany currently pass the DTO-shaped update
payload straight to MikroORM, which will break if an assembler maps between DTO
and Entity shapes; before calling wrap(entity).assign(...) in updateOne and
before em.nativeUpdate(...) in updateMany, convert the incoming update payload
using the assembler (e.g. call the assembler's convertToEntity/convertToModel
method or a mapping helper) to produce an Entity-shaped partial, then pass that
converted object to assign/nativeUpdate, keeping existing casts only where
necessary; reference updateOne, updateMany, assembler, wrap(...).assign and
em.nativeUpdate when making the change.
packages/query-mikro-orm/__tests__/services/mikro-orm-query.service.spec.ts (1)

554-557: Consider using strict equality for clearer intent.

Using Boolean() conversion works here but can be fragile. If boolType were 0 or an empty string, it would incorrectly match the "false" group. Direct comparison is clearer:

Suggested improvement
-      const trueGroup = result.find((r) => Boolean(r.groupBy?.boolType) === true)
-      const falseGroup = result.find((r) => Boolean(r.groupBy?.boolType) === false)
+      const trueGroup = result.find((r) => r.groupBy?.boolType === true)
+      const falseGroup = result.find((r) => r.groupBy?.boolType === false)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-mikro-orm/__tests__/services/mikro-orm-query.service.spec.ts`
around lines 554 - 557, The test is using Boolean(result.groupBy?.boolType)
which can misclassify values like 0 or ''—update the two finds to compare the
property directly using strict equality against true and false (e.g., use
result.find(r => r.groupBy?.boolType === true) and result.find(r =>
r.groupBy?.boolType === false)) so intent is explicit; adjust references to
trueGroup and falseGroup accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/query-mikro-orm/src/services/mikro-orm-query.service.ts`:
- Around line 174-225: The code builds SQL select clauses and groupByFields
(using em.getMetadata(), tableName, selects, groupByFields) but never executes
them — instead it loads all entities with this.repo.findAll() and calls
this.computeAggregateInMemory; either remove that dead build-up or execute a
DB-level aggregate: replace the this.repo.findAll()/computeAggregateInMemory
path by creating a MikroORM query using this.repo.getEntityManager() or
this.repo.createQueryBuilder(), apply the converted where from
convertFilter(filter), add the constructed selects as projections and apply
groupBy for groupByFields, execute the query and return the DB aggregation
results; if you choose to remove the dead code, delete the unused variables and
loops (tableName, selects, groupByFields and all aggregate building) and keep
the in-memory path (this.repo.findAll + computeAggregateInMemory).

---

Nitpick comments:
In `@packages/query-mikro-orm/__tests__/services/mikro-orm-query.service.spec.ts`:
- Around line 554-557: The test is using Boolean(result.groupBy?.boolType) which
can misclassify values like 0 or ''—update the two finds to compare the property
directly using strict equality against true and false (e.g., use result.find(r
=> r.groupBy?.boolType === true) and result.find(r => r.groupBy?.boolType ===
false)) so intent is explicit; adjust references to trueGroup and falseGroup
accordingly.

In `@packages/query-mikro-orm/src/services/mikro-orm-query.service.ts`:
- Around line 99-109: The createOne method currently passes the incoming
DeepPartial<DTO> record straight into em.create; when DTO and Entity differ and
an assembler exists you should first convert the DTO-shaped input into an
Entity-shaped object using the assembler (e.g. call
assembler.convertToEntity(record) or similar) and pass that converted
entity-data into em.create (then persistAndFlush and continue returning
assembler.convertToDTO(entity)); update createOne to branch: if (this.assembler)
use the assembler to convert input before em.create and to convert the persisted
entity back to DTO afterwards.
- Around line 267-272: The current COUNT handling in mikro-orm-query.service.ts
always sets each COUNT(field) to entities.length; change it to compute
SQL-compatible per-field non-NULL counts by iterating entities and incrementing
a counter only when the entity's value for that field is not null/undefined. In
the block that processes aggregateQuery.count (where response.count is set),
replace the constant assignment with per-field counters keyed by String(field)
and for each entity check the field value (support nested paths if your codebase
uses them) and increment only when value !== null && value !== undefined;
finally assign those counters into response.count so COUNT(field) reflects
non-NULL occurrences for that specific field.
- Around line 123-141: updateOne and updateMany currently pass the DTO-shaped
update payload straight to MikroORM, which will break if an assembler maps
between DTO and Entity shapes; before calling wrap(entity).assign(...) in
updateOne and before em.nativeUpdate(...) in updateMany, convert the incoming
update payload using the assembler (e.g. call the assembler's
convertToEntity/convertToModel method or a mapping helper) to produce an
Entity-shaped partial, then pass that converted object to assign/nativeUpdate,
keeping existing casts only where necessary; reference updateOne, updateMany,
assembler, wrap(...).assign and em.nativeUpdate when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 126cd06e-1a1d-4c68-99c3-e5b46ccd09c5

📥 Commits

Reviewing files that changed from the base of the PR and between e21819f and f9fbaae.

📒 Files selected for processing (4)
  • jest.config.ts
  • jest.preset.js
  • packages/query-mikro-orm/__tests__/services/mikro-orm-query.service.spec.ts
  • packages/query-mikro-orm/src/services/mikro-orm-query.service.ts

Comment on lines +174 to +225
const em = this.repo.getEntityManager()
const meta = em.getMetadata().get(this.repo.getEntityName())
const tableName = meta.tableName
const where = this.convertFilter(filter)

const selects: string[] = []
const groupByFields: string[] = []

if (aggregateQuery.count) {
for (const { field } of aggregateQuery.count) {
selects.push(`COUNT(${String(field)}) as count_${String(field)}`)
}
}

if (aggregateQuery.sum) {
for (const { field } of aggregateQuery.sum) {
selects.push(`SUM(${String(field)}) as sum_${String(field)}`)
}
}

if (aggregateQuery.avg) {
for (const { field } of aggregateQuery.avg) {
selects.push(`AVG(${String(field)}) as avg_${String(field)}`)
}
}

if (aggregateQuery.max) {
for (const { field } of aggregateQuery.max) {
selects.push(`MAX(${String(field)}) as max_${String(field)}`)
}
}

if (aggregateQuery.min) {
for (const { field } of aggregateQuery.min) {
selects.push(`MIN(${String(field)}) as min_${String(field)}`)
}
}

if (aggregateQuery.groupBy) {
for (const { field } of aggregateQuery.groupBy) {
const fieldName = String(field)
selects.push(`${fieldName} as groupBy_${fieldName}`)
groupByFields.push(fieldName)
}
}

if (selects.length === 0) {
return []
}

const entities = await this.repo.findAll({ where })
return this.computeAggregateInMemory(entities, aggregateQuery)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Dead code: SQL select statements are built but never executed.

Lines 175-218 construct selects, groupByFields, and extract tableName, but these variables are never used. Instead, line 224 fetches all entities and line 225 computes aggregates in-memory.

Either remove the dead code or implement database-level aggregation using the constructed query.

Remove unused variables
   async aggregate(
     filter: Filter<DTO>,
     aggregateQuery: AggregateQuery<DTO>,
     opts?: AggregateOptions
   ): Promise<AggregateResponse<DTO>[]> {
     if (opts?.withDeleted) {
       throw new Error('MikroOrmQueryService does not support withDeleted on aggregate')
     }

-    const em = this.repo.getEntityManager()
-    const meta = em.getMetadata().get(this.repo.getEntityName())
-    const tableName = meta.tableName
     const where = this.convertFilter(filter)
-
-    const selects: string[] = []
-    const groupByFields: string[] = []
-
-    if (aggregateQuery.count) {
-      for (const { field } of aggregateQuery.count) {
-        selects.push(`COUNT(${String(field)}) as count_${String(field)}`)
-      }
-    }
-
-    if (aggregateQuery.sum) {
-      for (const { field } of aggregateQuery.sum) {
-        selects.push(`SUM(${String(field)}) as sum_${String(field)}`)
-      }
-    }
-
-    if (aggregateQuery.avg) {
-      for (const { field } of aggregateQuery.avg) {
-        selects.push(`AVG(${String(field)}) as avg_${String(field)}`)
-      }
-    }
-
-    if (aggregateQuery.max) {
-      for (const { field } of aggregateQuery.max) {
-        selects.push(`MAX(${String(field)}) as max_${String(field)}`)
-      }
-    }
-
-    if (aggregateQuery.min) {
-      for (const { field } of aggregateQuery.min) {
-        selects.push(`MIN(${String(field)}) as min_${String(field)}`)
-      }
-    }
-
-    if (aggregateQuery.groupBy) {
-      for (const { field } of aggregateQuery.groupBy) {
-        const fieldName = String(field)
-        selects.push(`${fieldName} as groupBy_${fieldName}`)
-        groupByFields.push(fieldName)
-      }
-    }

     if (!aggregateQuery.count && !aggregateQuery.sum && !aggregateQuery.avg && 
         !aggregateQuery.max && !aggregateQuery.min && !aggregateQuery.groupBy) {
       return []
     }

     const entities = await this.repo.findAll({ where })
     return this.computeAggregateInMemory(entities, aggregateQuery)
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-mikro-orm/src/services/mikro-orm-query.service.ts` around
lines 174 - 225, The code builds SQL select clauses and groupByFields (using
em.getMetadata(), tableName, selects, groupByFields) but never executes them —
instead it loads all entities with this.repo.findAll() and calls
this.computeAggregateInMemory; either remove that dead build-up or execute a
DB-level aggregate: replace the this.repo.findAll()/computeAggregateInMemory
path by creating a MikroORM query using this.repo.getEntityManager() or
this.repo.createQueryBuilder(), apply the converted where from
convertFilter(filter), add the constructed selects as projections and apply
groupBy for groupByFields, execute the query and return the DB aggregation
results; if you choose to remove the dead code, delete the unused variables and
loops (tableName, selects, groupByFields and all aggregate building) and keep
the in-memory path (this.repo.findAll + computeAggregateInMemory).

Comment on lines +224 to +225
const entities = await this.repo.findAll({ where })
return this.computeAggregateInMemory(entities, aggregateQuery)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

MikroORM QueryBuilder aggregate functions COUNT SUM AVG

💡 Result:

MikroORM QueryBuilder supports aggregate functions COUNT, SUM, and AVG primarily through raw SQL expressions in the select clause and dedicated methods for COUNT. For COUNT: - Use qb.count to create a count query: const count = await em.createQueryBuilder(User).count.where({ isActive: true }).execute('get'); // returns { count: number } - Or qb.getCount on a cloned QB without limit/offset. - Raw: qb.select([raw('count() as count'), 'status']).groupBy('status') For SUM and AVG (and other aggregates like MIN/MAX): - Use raw SQL expressions with raw or sql... helpers in select: qb.select([raw('sum(t.amount) as total'), 'u.id as id']).leftJoin('u.jncSales', 't') - Or newer sql template: qb.select(['u.id as id', sqlsum(t.amount).as('total')]) - Combine with groupBy('status') and having({ total: { $gt: 10000 } }) for filtering aggregates. - Execute with .execute('get') for single object or 'all' for array. Examples: const total = await em.createQueryBuilder(Order).select('sum(amount) as total').where({ status: 'completed' }).execute('get'); const stats = await em.createQueryBuilder(Order).select(['status', 'count() as count', 'sum(amount) as total', 'avg(amount) as avg']).groupBy('status').execute; No dedicated qb.sum or qb.avg methods exist; use raw SQL for these. Always wrap aggregate functions in raw or sql to avoid quoting issues. Works with joins, where, groupBy, having, orderBy, limit.

Citations:


🏁 Script executed:

cd packages/query-mikro-orm && find . -name "*.ts" | head -20

Repository: TriPSs/nestjs-query

Length of output: 575


🏁 Script executed:

cat -n packages/query-mikro-orm/src/services/mikro-orm-query.service.ts | sed -n '200,250p'

Repository: TriPSs/nestjs-query

Length of output: 2051


🏁 Script executed:

grep -n "computeAggregateInMemory" packages/query-mikro-orm/src/services/mikro-orm-query.service.ts -A 30 | head -50

Repository: TriPSs/nestjs-query

Length of output: 1515


🏁 Script executed:

rg "class MikroOrmQueryService|aggregateQuery" packages/query-mikro-orm/src/services/ -B 2 -A 2

Repository: TriPSs/nestjs-query

Length of output: 11076


🏁 Script executed:

cat packages/query-mikro-orm/__tests__/services/mikro-orm-query.service.spec.ts | grep -A 20 "aggregate"

Repository: TriPSs/nestjs-query

Length of output: 2808


🏁 Script executed:

grep -n "aggregate" packages/query-mikro-orm/__tests__/services/mikro-orm-query.service.spec.ts | head -30

Repository: TriPSs/nestjs-query

Length of output: 1108


Replace in-memory aggregation with MikroORM QueryBuilder for better scalability.

The current implementation fetches all matching entities into memory and computes aggregates in JavaScript. For large result sets, this causes:

  • Excessive memory consumption (loading millions of rows)
  • Slower performance compared to database-level aggregation
  • Risk of OOM errors

Use MikroORM's QueryBuilder with select() for raw aggregate functions (COUNT, SUM, AVG, MAX, MIN), groupBy(), and having() for filtering. Example:

qb.select('status', 'count(*) as count', 'sum(amount) as total')
  .groupBy('status')
  .execute('all')

Note: Lines 191-218 build SQL aggregate selections that are never used; they can be removed after migrating to QueryBuilder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant