Skip to content

Commit e94f2f6

Browse files
committed
Export filterQuery function (fix #10)
1 parent 5effd0f commit e94f2f6

File tree

17 files changed

+590
-72
lines changed

17 files changed

+590
-72
lines changed

.changeset/serious-experts-wait.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"objection-graphql-resolver": minor
3+
"orchid-graphql": minor
4+
"graphql-orm": minor
5+
---
6+
7+
Export `filterQuery` function.

packages/base/src/filter/filter.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { OrmAdapter } from "../orm/orm"
2+
import { isPlainObject } from "../utils/is-plain-object"
3+
4+
// TODO: per-field definitions
5+
export type FilterConfig = boolean
6+
7+
export type FieldFilterScalarValue = null | string | number | boolean
8+
export type FieldFilterValue = FieldFilterScalarValue | Exclude<FieldFilterScalarValue, null>[]
9+
export type FilterValue = { [property: string]: FieldFilterValue }
10+
11+
export type FilterQueryOptions<Orm extends OrmAdapter, Context = unknown> = {
12+
modifiers?: FilterModifiers<Orm, Context>
13+
} & (Context extends undefined ? {
14+
context?: Context
15+
} : {
16+
context: Context
17+
})
18+
19+
export type FilterModifier<Orm extends OrmAdapter, Context> = (
20+
query: Orm["Query"],
21+
value: any,
22+
context: Context,
23+
) => Orm["Query"]
24+
25+
export type FilterModifiers<Orm extends OrmAdapter, Context> = Record<string, FilterModifier<Orm, Context>>
26+
27+
export function filterQuery<Orm extends OrmAdapter, Context>(
28+
orm: Orm,
29+
query: Orm["Query"],
30+
filter: FilterValue,
31+
options?: FilterQueryOptions<Orm, Context>,
32+
): Orm["Query"] {
33+
const { context, modifiers } = options ?? {}
34+
35+
if (!filter) {
36+
return query
37+
}
38+
if (!isPlainObject(filter)) {
39+
throw new Error(`Invalid filter: ${filter}, must be plain object.`)
40+
}
41+
for (const [field, value] of Object.entries(filter)) {
42+
if (value === undefined) {
43+
// Support optional GraphQL arguments in filter
44+
continue
45+
}
46+
if (modifiers?.[field]) {
47+
query = modifiers[field](query, value, context!) // TODO improve types, get rid of !
48+
} else {
49+
const [table_field, op0] = field.split("__")
50+
const op = op0?.toLowerCase()
51+
// TODO: validate op
52+
query = orm.where(query, table_field, op, value)
53+
}
54+
}
55+
return query
56+
}

packages/base/src/filter/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./filter"

packages/base/src/filters/filters.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

packages/base/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./filter"
12
export { OrmAdapter, OrmModifier, SortOrder } from "./orm/orm"
23
export { Paginator } from "./paginators/base"
34
export { defineCursorPaginator } from "./paginators/cursor"

packages/base/src/resolvers/graph.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { GraphQLResolveInfo } from "graphql"
22
import type { ResolveTree } from "graphql-parse-resolve-info"
33
import { parseResolveInfo } from "graphql-parse-resolve-info"
44

5-
import type { FiltersDef } from "../filters/filters"
5+
import type { FilterConfig } from "../filter"
66
import type { OrmAdapter } from "../orm/orm"
77
import type { Paginator } from "../paginators/base"
88

@@ -16,7 +16,7 @@ export type GraphResolverOptions<Orm extends OrmAdapter, Context> = Pick<
1616
export interface GraphResolveOptions<Context> {
1717
info: GraphQLResolveInfo
1818
context: Context
19-
filters?: FiltersDef
19+
filters?: FilterConfig
2020
/** Subfield path if not resolving root query, e.g.: ['user'] */
2121
path?: string[]
2222
}

packages/base/src/resolvers/relation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FiltersDef } from "../filters/filters"
1+
import type { FilterConfig } from "../filter"
22
import type { OrmAdapter } from "../orm/orm"
33

44
import type {
@@ -10,7 +10,7 @@ import type { TableResolveModifier } from "./table"
1010

1111
export interface RelationResolverOptions<Orm extends OrmAdapter, Context>
1212
extends Omit<FieldResolverOptions<Orm, Context>, "modify"> {
13-
filters?: FiltersDef
13+
filters?: FilterConfig
1414
modify?: TableResolveModifier<Orm, Context>
1515
}
1616

packages/base/src/resolvers/table.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { ApplyFiltersModifier } from "../filters/filters"
2-
import { apply_filters } from "../filters/filters"
1+
import type { FilterModifier, FilterValue } from "../filter"
2+
import { filterQuery } from "../filter"
33
import type { OrmAdapter } from "../orm/orm"
44
import { run_after_query } from "../utils/run-after"
55

@@ -14,7 +14,7 @@ export interface TableResolverOptions<Orm extends OrmAdapter, Context> {
1414
/** allow to filter all table fields without explicitly listing them */
1515
allowAllFilters?: boolean
1616
fields?: Record<string, SimpleFieldResolver<Orm, Context>>
17-
modifiers?: Record<string, ApplyFiltersModifier<Orm, Context>>
17+
modifiers?: Record<string, TableFilterModifier<Orm, Context>>
1818
modify?: TableResolveModifier<Orm, Context>
1919
transform?(instance: any, context: any): void | PromiseLike<void>
2020
}
@@ -36,11 +36,13 @@ export interface TableResolveContext<Orm extends OrmAdapter, Context>
3636
type: string
3737
}
3838

39+
export type TableFilterModifier<Orm extends OrmAdapter, Context> = FilterModifier<Orm, TableResolveContext<Orm, Context>>
40+
3941
export class TableResolver<Orm extends OrmAdapter, Context> {
4042
// Introspection results
4143
readonly relations: Set<string>
4244
readonly virtual_fields: Set<string>
43-
readonly modifiers?: Record<string, ApplyFiltersModifier<Orm, Context>>
45+
readonly modifiers?: Record<string, TableFilterModifier<Orm, Context>>
4446

4547
readonly table_field_resolvers?: Record<string, FieldResolver<Orm, Context>>
4648

@@ -121,17 +123,17 @@ export class TableResolver<Orm extends OrmAdapter, Context> {
121123
}
122124
}
123125

124-
const allow_all_filters
125-
= this.options.allowAllFilters ?? graph.options.allowAllFilters
126-
127-
const effective_filters = allow_all_filters ? true : filters
126+
const filter = context.tree.args?.filter
127+
if (filter) {
128+
const allowAllFilters = this.options.allowAllFilters ?? graph.options.allowAllFilters
129+
const filterConfig = allowAllFilters ? true : filters
128130

129-
if (effective_filters) {
130-
query = apply_filters<Orm, Context>(query, {
131-
filters: effective_filters,
132-
modifiers: this.modifiers,
133-
context,
134-
})
131+
if (filterConfig) {
132+
query = filterQuery<Orm, TableResolveContext<Orm, Context>>(context.graph.orm, query, filter as FilterValue, {
133+
modifiers: this.modifiers,
134+
context,
135+
})
136+
}
135137
}
136138

137139
if (transform) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export function is_plain_object(obj: any): obj is Record<string, any> {
1+
export function isPlainObject(obj: any): obj is Record<string, any> {
22
return typeof obj === "object" && !Array.isArray(obj)
33
}

packages/objection/docs/filters.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,25 @@ query get_all_posts {
124124
```
125125

126126
Modifier filters take precedence over database field filters.
127+
128+
## Reusing Filters for External Queries
129+
130+
There are cases where you need to apply the same filtering logic across multiple queries—even those not originating from GraphQL. For example, you might want to return a filtered list of objects, and then meta data about that filtered list of objects.
131+
132+
To achieve this, use the `filterQuery` function:
133+
134+
```ts
135+
import { filterQuery } from "objection-graphql-resolver"
136+
137+
const posts = await filterQuery(
138+
// Base query to filter
139+
PostModel.query(),
140+
// Filter object, e.g. { public: true }
141+
filterObject,
142+
// Option - not required
143+
{
144+
modifiers, // Additional model modifiers
145+
context, // Pass to modifiers
146+
}
147+
)
148+
```

0 commit comments

Comments
 (0)