Skip to content

Commit a2e4c6c

Browse files
committed
feat(query): add secondary closure support for the where clause
1 parent 6fba9bc commit a2e4c6c

File tree

5 files changed

+81
-18
lines changed

5 files changed

+81
-18
lines changed

src/query/Options.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1+
import { Model } from '../model/Model'
12
import { Query } from './Query'
23

3-
export interface Where {
4-
field: string | number
5-
value: any
4+
export type WhereSecondaryClosure<M extends Model, K extends keyof M> = (
5+
value: M[K]
6+
) => boolean
7+
8+
export interface Where<M extends Model, T extends keyof M> {
9+
field: T
10+
value: WhereSecondaryClosure<M, T> | M[T] | M[T][]
611
boolean: 'and' | 'or'
712
}
813

9-
export interface WhereGroup {
10-
and?: Where[]
11-
or?: Where[]
14+
export interface WhereGroup<M extends Model, T extends keyof M> {
15+
and?: Where<M, T>[]
16+
or?: Where<M, T>[]
1217
}
1318

1419
export interface Order {

src/query/Query.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { Store } from 'vuex'
2-
import { isArray, isEmpty, orderBy, groupBy } from '../support/Utils'
2+
import {
3+
isArray,
4+
isFunction,
5+
isEmpty,
6+
orderBy,
7+
groupBy
8+
} from '../support/Utils'
39
import { Element, Item, Collection } from '../data/Data'
410
import { Relation } from '../model/attributes/relations/Relation'
511
import { Model } from '../model/Model'
612
import { Connection } from '../connection/Connection'
713
import {
814
Where,
15+
WhereSecondaryClosure,
916
Order,
1017
OrderDirection,
1118
EagerLoad,
@@ -26,7 +33,7 @@ export class Query<M extends Model = Model> {
2633
/**
2734
* The where constraints for the query.
2835
*/
29-
protected wheres: Where[] = []
36+
protected wheres: Where<M, any>[] = []
3037

3138
/**
3239
* The orderings for the query.
@@ -73,7 +80,9 @@ export class Query<M extends Model = Model> {
7380
/**
7481
* Add a basic where clause to the query.
7582
*/
76-
where(field: string, value: any): this {
83+
where<T extends keyof M>(field: T, value: WhereSecondaryClosure<M, T>): this
84+
where<T extends keyof M>(field: T, value: M[T] | M[T][]): this
85+
where(field: any, value: any): any {
7786
this.wheres.push({ field, value, boolean: 'and' })
7887

7988
return this
@@ -82,7 +91,7 @@ export class Query<M extends Model = Model> {
8291
/**
8392
* Add a "where in" clause to the query.
8493
*/
85-
whereIn(field: string, values: any[]): this {
94+
whereIn<T extends keyof M>(field: T, values: M[T][]): this {
8695
this.wheres.push({ field, value: values, boolean: 'and' })
8796

8897
return this
@@ -91,14 +100,16 @@ export class Query<M extends Model = Model> {
91100
/**
92101
* Add a where clause on the primary key to the query.
93102
*/
94-
whereId(ids: string | number | (string | number)[]): this {
95-
return this.where(this.model.$getPrimaryKey(), ids)
103+
whereId<T extends keyof M>(ids: M[T] | M[T][]): this {
104+
return this.where(this.model.$getPrimaryKey() as T, ids)
96105
}
97106

98107
/**
99108
* Add an "or where" clause to the query.
100109
*/
101-
orWhere(field: string, value: any): Query<M> {
110+
orWhere<T extends keyof M>(field: T, value: WhereSecondaryClosure<M, T>): this
111+
orWhere<T extends keyof M>(field: T, value: M[T] | M[T][]): this
112+
orWhere(field: any, value: any): any {
102113
this.wheres.push({ field, value, boolean: 'or' })
103114

104115
return this
@@ -261,11 +272,15 @@ export class Query<M extends Model = Model> {
261272
/**
262273
* The function to compare where clause to the given model.
263274
*/
264-
protected whereComparator(model: M, where: Where): boolean {
275+
protected whereComparator(model: M, where: Where<M, any>): boolean {
265276
if (isArray(where.value)) {
266277
return where.value.includes(model[where.field])
267278
}
268279

280+
if (isFunction(where.value)) {
281+
return where.value(model[where.field])
282+
}
283+
269284
return model[where.field] === where.value
270285
}
271286

@@ -396,7 +411,7 @@ export class Query<M extends Model = Model> {
396411
/**
397412
* Destroy the models for the given id.
398413
*/
399-
async destroyMany(ids: (string | number)[]): Promise<Collection<M>> {
414+
async destroyMany<T extends keyof M>(ids: M[T][]): Promise<Collection<M>> {
400415
return this.whereId(ids).delete()
401416
}
402417

src/repository/Repository.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { Model } from '../model/Model'
1212
import { Interpretation } from '../interpretation/Interpretation'
1313
import { Query } from '../query/Query'
14-
import { OrderDirection } from '../query/Options'
14+
import { WhereSecondaryClosure, OrderDirection } from '../query/Options'
1515

1616
export type PersistMethod = 'insert' | 'merge'
1717

@@ -66,14 +66,24 @@ export class Repository<M extends Model> {
6666
/**
6767
* Add a basic where clause to the query.
6868
*/
69-
where(field: string, value: any): Query<M> {
69+
where<T extends keyof M>(
70+
field: T,
71+
value: WhereSecondaryClosure<M, T>
72+
): Query<M>
73+
where<T extends keyof M>(field: T, value: M[T] | M[T][]): Query<M>
74+
where(field: any, value: any): any {
7075
return this.query().where(field, value)
7176
}
7277

7378
/**
7479
* Add an "or where" clause to the query.
7580
*/
76-
orWhere(field: string, value: any): Query<M> {
81+
orWhere<T extends keyof M>(
82+
field: T,
83+
value: WhereSecondaryClosure<M, T>
84+
): Query<M>
85+
orWhere<T extends keyof M>(field: T, value: M[T] | M[T][]): Query<M>
86+
orWhere(field: any, value: any): any {
7787
return this.query().orWhere(field, value)
7888
}
7989

src/support/Utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ export function isArray(value: any): value is any[] {
2525
return Array.isArray(value)
2626
}
2727

28+
/**
29+
* Check if the given value is the type of array.
30+
*/
31+
export function isFunction(value: any): value is Function {
32+
return typeof value === 'function'
33+
}
34+
2835
/**
2936
* Check if the given array or object is empty.
3037
*/

test/feature/repository/retrieves_where.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,30 @@ describe('feature/repository/retrieves_where', () => {
6161
assertInstanceOf(users, User)
6262
assertModels(users, expected)
6363
})
64+
65+
it('can filter records by passing closure as a value', () => {
66+
const store = createStore([User])
67+
68+
fillState(store, {
69+
users: {
70+
1: { id: 1, name: 'John Doe', age: 30 },
71+
2: { id: 2, name: 'Jane Doe', age: 30 },
72+
3: { id: 3, name: 'Johnny Doe', age: 20 }
73+
}
74+
})
75+
76+
const users = store
77+
.$repo(User)
78+
.where('age', (value) => value === 30)
79+
.get()
80+
81+
const expected = [
82+
{ id: 1, name: 'John Doe', age: 30 },
83+
{ id: 2, name: 'Jane Doe', age: 30 }
84+
]
85+
86+
expect(users.length).toBe(2)
87+
assertInstanceOf(users, User)
88+
assertModels(users, expected)
89+
})
6490
})

0 commit comments

Comments
 (0)