Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/pinia-orm/src/model/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ export interface InheritanceTypes {
[key: string]: typeof Model
}

export type WithKeys<T> = { [P in keyof T]: T[P] extends (Model | null) | Model[] ? P & string : never }[keyof T]
// export type WithKeys<T> = { [P in keyof T]: T[P] extends Model[] ? P : never }[keyof T];
export type WithKeys<T> = { [P in keyof T]-?: NonNullable<T[P]> extends Model | Model[] ? P & string : never }[keyof T]

export class Model {
declare _meta: undefined | MetaValues
Expand Down
2 changes: 1 addition & 1 deletion packages/pinia-orm/src/query/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface Where<T = Model> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export type NonMethodKeys<T> = { [P in keyof T]: T[P] extends Function ? never : P }[keyof T]
export type GetElementType<T extends unknown[] | unknown> = T extends (infer U)[] ? U : T
export type UltimateKeys<M> = { [T in keyof M]: M[T] extends Model | Model[] | null ? GetElementType<NonNullable<M[T]>> : never }
export type UltimateKeys<M> = { [T in keyof M]-?: NonNullable<M[T]> extends Model | Model[] ? GetElementType<NonNullable<M[T]>> : never }
export type WherePrimaryClosure<T> = (model: T) => boolean

export type WhereSecondaryClosure<T> = (value: T) => boolean
Expand Down
12 changes: 6 additions & 6 deletions packages/pinia-orm/src/query/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,14 @@ export class Query<M extends Model = Model> {
/**
* Add a "where has" clause to the query.
*/
whereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): this {
whereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): this {
return this.where(this.getFieldWhereForRelations(relation, callback, operator, count))
}

/**
* Add an "or where has" clause to the query.
*/
orWhereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): this {
orWhereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): this {
return this.orWhere(this.getFieldWhereForRelations(relation, callback, operator, count))
}

Expand Down Expand Up @@ -334,14 +334,14 @@ export class Query<M extends Model = Model> {
/**
* Add a "where doesn't have" clause to the query.
*/
whereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): this {
whereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): this {
return this.where(this.getFieldWhereForRelations(relation, callback, '=', 0))
}

/**
* Add an "or where doesn't have" clause to the query.
*/
orWhereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): this {
orWhereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): this {
return this.orWhere(this.getFieldWhereForRelations(relation, callback, '=', 0))
}

Expand Down Expand Up @@ -386,7 +386,7 @@ export class Query<M extends Model = Model> {
/**
* Set the relationships that should be eager loaded.
*/
with<T extends WithKeys<M>>(name: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): this {
with<T extends WithKeys<M>>(name: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): this {
this.getNewHydrated = true
// @ts-expect-error name type can be used
this.eagerLoad[name] = callback
Expand Down Expand Up @@ -434,7 +434,7 @@ export class Query<M extends Model = Model> {
/**
* Get where closure for relations
*/
protected getFieldWhereForRelations<T extends WithKeys<M>>(relation: T | (string & {}), callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): WherePrimaryClosure<M> {
protected getFieldWhereForRelations<T extends WithKeys<M>>(relation: T | (string & {}), callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): WherePrimaryClosure<M> {
const modelIdsByRelation = this.newQuery(this.model.$entity()).with(relation, callback).get(false)
.filter((model) => {
const modelRelation = model[relation as T]
Expand Down
10 changes: 5 additions & 5 deletions packages/pinia-orm/src/repository/Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,14 +248,14 @@ export class Repository<M extends Model = Model> {
/**
* Add a "where has" clause to the query.
*/
whereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): Query<M> {
whereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): Query<M> {
return this.query().whereHas<T>(relation, callback, operator, count)
}

/**
* Add an "or where has" clause to the query.
*/
orWhereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): Query<M> {
orWhereHas<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }, operator?: string | number, count?: number): Query<M> {
return this.query().orWhereHas(relation, callback, operator, count)
}

Expand Down Expand Up @@ -290,14 +290,14 @@ export class Repository<M extends Model = Model> {
/**
* Add a "where doesn't have" clause to the query.
*/
whereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): Query<M> {
whereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): Query<M> {
return this.query().whereDoesntHave(relation, callback)
}

/**
* Add an "or where doesn't have" clause to the query.
*/
orWhereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): Query<M> {
orWhereDoesntHave<T extends WithKeys<M>>(relation: T | string & {}, callback: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : () => void = () => { }): Query<M> {
return this.query().orWhereDoesntHave(relation, callback)
}

Expand Down Expand Up @@ -353,7 +353,7 @@ export class Repository<M extends Model = Model> {
/**
* Set the relationships that should be eager loaded.
*/
with<T extends WithKeys<M>>(name: string & {} | T, callback?: M[T] extends Model | Model[] | null ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : never): Query<M> {
with<T extends WithKeys<M>>(name: string & {} | T, callback?: M[T] extends Model | Model[] | null | undefined ? EagerLoadConstraint<GetElementType<NonNullable<M[T]>>> : never): Query<M> {
return this.query().with(name, callback)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'

import { Model, useRepo } from '../../../../src'
import { Attr, BelongsToMany, HasOne, Num, Str } from '../../../../src/decorators'
import { Attr, BelongsToMany, HasMany, HasOne, Num, Str } from '../../../../src/decorators'

describe('feature/relations/constraints/constraints', () => {
class Type extends Model {
Expand Down Expand Up @@ -153,4 +153,39 @@ describe('feature/relations/constraints/constraints', () => {
expect(users[0].roles.length).toBe(3)
expect(users2[0].roles).toBe(undefined)
})

it('can add constraints to a relationship declared as optional', () => {
class Comment extends Model {
static entity = 'comments'

@Attr() declare id: number
@Attr() declare postId: number
@Str('') declare body: string
}

class Post extends Model {
static entity = 'posts'

@Attr() declare id: number
@Str('') declare title: string

@HasMany(() => Comment, 'postId')
declare comments?: Comment[]
}

const postsRepo = useRepo(Post)

postsRepo.save([
{ id: 1, title: 'A', comments: [{ id: 1, body: 'first' }, { id: 2, body: 'second' }] },
])

const post = postsRepo
.with('comments', (query) => {
query.where('body', 'second')
})
.first()

expect(post?.comments?.length).toBe(1)
expect(post?.comments?.[0].body).toBe('second')
})
})
Loading