Skip to content

Commit

Permalink
chore: relations & default sort, typo, better var name (#347)
Browse files Browse the repository at this point in the history
While I was going through the code I found that these places where I
felt:

- Rephrasing them make them more readable.
- Renaming the variable made it more accurate and less misleading.
- Adding this missing info to the doc (default sorting criteria for
relation fields) will help other dev's. Personally wrote an e2e test and
it was failing since `nestjs-query` won't sort relation fields by
default (this is not a problem for this lib per se, but it is still a
good idea to mention it in the docs).
  • Loading branch information
TriPSs authored Jan 13, 2025
2 parents edafa5f + 34ecceb commit 09e9eea
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 17 deletions.
12 changes: 12 additions & 0 deletions documentation/docs/graphql/dtos.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,18 @@ export class TodoItemDTO {

```
:::info
When we use the `@Relation` decorator or other [relation decorators](./relations.mdx) you might want to define the default sorting criteria like this:
```ts title="todo-item.dto.ts"
import { SortDirection } from '@ptc-org/nestjs-query-core';
// ...
@Relation('assignee', () => UserDTO, {
defaultSort: [{ field: 'id', direction: SortDirection.ASC }],
})
```
Note that default value for `defaultSort` is `[]`, meaning if you do **not** specify it you will receive the results as determined by your underlying database, which is unreliable and calculated based on how your database engine works.
:::
### Allowed Boolean Expressions
When filtering you can provide `and` and `or` expressions to provide advanced filtering. You can turn off either by
Expand Down
16 changes: 10 additions & 6 deletions packages/query-graphql/src/types/connection/cursor/pager/pager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class CursorPager<DTO> implements Pager<DTO, CursorPagerResult<DTO>> {
const pageInfo = {
startCursor: edges[0]?.cursor,
endCursor: edges[edges.length - 1]?.cursor,
// if we have are going forward and have an extra node or there was a before cursor
// if we re paging forward and have an extra node we have more pages to load. Or there we know that we have a before cursor, thus we have next page
hasNextPage: isForward ? hasExtraNode : hasBefore,
// we have a previous page if we are going backwards and have an extra node.
hasPreviousPage: this.hasPreviousPage(results, pagingMeta)
Expand All @@ -102,12 +102,16 @@ export class CursorPager<DTO> implements Pager<DTO, CursorPagerResult<DTO>> {
return opts.isBackward ? hasExtraNode : !this.strategy.isEmptyCursor(opts)
}

/**
* @description
* It is an empty page if:
*
* 1. We don't have an extra node.
* 2. There were no nodes returned.
* 3. We're paging forward.
* 4. And we don't have an offset.
*/
private isEmptyPage(results: QueryResults<DTO>, pagingMeta: PagingMeta<DTO, CursorPagingOpts<DTO>>): boolean {
// it is an empty page if
// 1. we dont have an extra node
// 2. there were no nodes returned
// 3. we're paging forward
// 4. and we dont have an offset
const { opts } = pagingMeta
const isEmpty = !results.hasExtraNode && !results.nodes.length && pagingMeta.opts.isForward
return isEmpty && this.strategy.isEmptyCursor(opts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export class KeysetPagerStrategy<DTO> implements PagerStrategy<DTO> {
paging.limit += 1
}
const { payload } = opts
// Add 1 to the limit so we will fetch an additional node with the current node
const sorting = this.getSortFields(query, opts)
const filter = mergeFilter(query.filter ?? {}, this.createFieldsFilter(sorting, payload))
const createdQuery = { ...query, filter, sorting, paging }
Expand Down Expand Up @@ -120,6 +119,10 @@ export class KeysetPagerStrategy<DTO> implements PagerStrategy<DTO> {
return { or: oredFilter } as Filter<DTO>
}

/**
* @description
* Strip the default sorting criteria if it is set by the client.
*/
private getSortFields(query: Query<DTO>, opts: KeySetPagingOpts<DTO>): SortField<DTO>[] {
const { sorting = [] } = query
const defaultSort = opts.defaultSort.filter((dsf) => !sorting.some((sf) => dsf.field === sf.field))
Expand Down
6 changes: 3 additions & 3 deletions packages/query-typeorm/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import type { DataSource } from 'typeorm'
import { createTypeOrmQueryServiceProviders } from './providers'

export class NestjsQueryTypeOrmModule {
static forFeature(entities: Class<unknown>[], connection?: DataSource | string): DynamicModule {
const queryServiceProviders = createTypeOrmQueryServiceProviders(entities, connection)
const typeOrmModule = TypeOrmModule.forFeature(entities, connection)
static forFeature(entities: Class<unknown>[], dataSource?: DataSource | string): DynamicModule {
const queryServiceProviders = createTypeOrmQueryServiceProviders(entities, dataSource)
const typeOrmModule = TypeOrmModule.forFeature(entities, dataSource)

return {
imports: [typeOrmModule],
Expand Down
8 changes: 4 additions & 4 deletions packages/query-typeorm/src/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import { TypeOrmQueryService } from './services'

function createTypeOrmQueryServiceProvider<Entity>(
EntityClass: Class<Entity>,
connection?: DataSource | string
dataSource?: DataSource | string
): FactoryProvider {
return {
provide: getQueryServiceToken(EntityClass),
useFactory(repo: Repository<Entity>) {
return new TypeOrmQueryService(repo)
},
inject: [getRepositoryToken(EntityClass, connection)]
inject: [getRepositoryToken(EntityClass, dataSource)]
}
}

export const createTypeOrmQueryServiceProviders = (
entities: Class<unknown>[],
connection?: DataSource | string
): FactoryProvider[] => entities.map((entity) => createTypeOrmQueryServiceProvider(entity, connection))
dataSource?: DataSource | string
): FactoryProvider[] => entities.map((entity) => createTypeOrmQueryServiceProvider(entity, dataSource))
7 changes: 4 additions & 3 deletions packages/query-typeorm/src/query/filter-query.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,11 @@ export class FilterQueryBuilder<Entity> {
}

return sorts.reduce((prevQb, { field, direction, nulls }) => {
let col = alias ? `${alias}.${field as string}` : `${field as string}`
const stringifiedField = String(field)
let col = alias ? `${alias}.${stringifiedField}` : `${stringifiedField}`

if (this.virtualColumns.includes(field as string)) {
col = prevQb.escape(alias ? `${alias}_${field as string}` : `${field as string}`)
if (this.virtualColumns.includes(stringifiedField)) {
col = prevQb.escape(alias ? `${alias}_${stringifiedField}` : `${stringifiedField}`)
}

return prevQb.addOrderBy(col, direction, nulls)
Expand Down

0 comments on commit 09e9eea

Please sign in to comment.