diff --git a/packages/orm/src/memory-db.ts b/packages/orm/src/memory-db.ts index 872b056d4..809cc01dd 100644 --- a/packages/orm/src/memory-db.ts +++ b/packages/orm/src/memory-db.ts @@ -98,7 +98,7 @@ const find = (adapter: MemoryDatabaseAdapter, classSchema: let filtered = model.filter ? findQueryList(items, model.filter) : items; if (model.hasJoins()) { - throw new Error('MemoryDatabaseAdapter does not support joins. Please use another lightweight adapter like SQLite.'); + console.log('MemoryDatabaseAdapter does not support joins. Please use another lightweight adapter like SQLite.'); } if (model.sort) { diff --git a/packages/orm/src/query.ts b/packages/orm/src/query.ts index 32bf42ed3..7396cfb37 100644 --- a/packages/orm/src/query.ts +++ b/packages/orm/src/query.ts @@ -222,9 +222,11 @@ export class BaseQuery { * * This allows to use more dynamic query composition functions. * + * To support joins queries `AnyQuery` is necessary as query type. + * * @example * ```typescript - * function joinFrontendData(query: Query) { + * function joinFrontendData(query: AnyQuery) { * return query * .useJoinWith('images').select('sort').end() * .useJoinWith('brand').select('id', 'name', 'website').end() @@ -232,13 +234,15 @@ export class BaseQuery { * * const products = await database.query(Product).use(joinFrontendData).find(); * ``` + * @reflection never */ - use(modifier: (query: Q, ...args: A) => R, ...args: A): R { - return modifier(this as any, ...args); + use(modifier: (query: Q, ...args: A) => R, ...args: A): this extends JoinDatabaseQuery ? this : Exclude> { + return modifier(this as any, ...args) as any; } /** * Same as `use`, but the method indicates it is terminating the query. + * @reflection never */ fetch(modifier: (query: Q) => R): R { return modifier(this as any); @@ -275,7 +279,7 @@ export class BaseQuery { } withGroupConcat, AS extends string>(field: K, as?: AS): Replace & { [C in [AS] as `${AS}`]: T[K][] }> { - return this.aggregateField(field, 'group_concat', as); + return this.aggregateField(field, 'group_concat', as) as any; } withCount, AS extends string>(field: K, as?: AS): Replace & { [K in [AS] as `${AS}`]: number }> { @@ -494,7 +498,7 @@ export class BaseQuery { join, ENTITY extends OrmEntity = FindEntity>(field: K, type: 'left' | 'inner' = 'left', populate: boolean = false): this { const propertySchema = this.classSchema.getProperty(field as string); if (!propertySchema.isReference() && !propertySchema.isBackReference()) { - throw new Error(`Field ${String(field)} is not marked as reference. Use @t.reference()`); + throw new Error(`Field ${String(field)} is not marked as reference. Use Reference type`); } const c = this.clone(); @@ -963,3 +967,5 @@ export class JoinDatabaseQuery = JoinDatabaseQuery | Query; diff --git a/packages/orm/tests/query.spec.ts b/packages/orm/tests/query.spec.ts index ff6f78dee..f6a4f74fd 100644 --- a/packages/orm/tests/query.spec.ts +++ b/packages/orm/tests/query.spec.ts @@ -1,9 +1,9 @@ -import { BackReference, deserialize, PrimaryKey } from '@deepkit/type'; +import { BackReference, deserialize, PrimaryKey, Reference } from '@deepkit/type'; import { expect, test } from '@jest/globals'; import { assert, IsExact } from 'conditional-type-checks'; import { Database } from '../src/database.js'; import { MemoryDatabaseAdapter, MemoryQuery } from '../src/memory-db.js'; -import { Query } from '../src/query.js'; +import { AnyQuery, BaseQuery, Query } from '../src/query.js'; import { OrmEntity } from '../src/type.js'; test('query select', async () => { @@ -70,10 +70,17 @@ test('query filter', async () => { }); test('query lift', async () => { + class UserImage { + id!: number & PrimaryKey; + path!: string; + size!: number; + } + class User { id!: number & PrimaryKey; username!: string; openBillings: number = 0; + image?: UserImage & Reference; } const database = new Database(new MemoryDatabaseAdapter()); @@ -108,11 +115,11 @@ test('query lift', async () => { } } - function filterBillingDue(q: Query) { + function filterBillingDue(q: AnyQuery) { return q.filterField('openBillings', { $gt: 0 }); } - function filterMinBilling(q: Query, min: number) { + function filterMinBilling(q: AnyQuery, min: number) { return q.filterField('openBillings', { $gt: min }); } @@ -120,6 +127,10 @@ test('query lift', async () => { return q.findField('username'); } + function filterImageSize(q: AnyQuery) { + return q.filterField('size', { $gt: 0 }); + } + class OverwriteHello extends Query { hello() { return 'nope'; @@ -144,12 +155,12 @@ test('query lift', async () => { { const items = await q.lift(UserQuery).find(); - assert>(true); + assert>(true); } { const items = await q.lift(UserQuery).find(); - assert>(true); + assert>(true); } { @@ -159,7 +170,7 @@ test('query lift', async () => { { const items = await UserQuery.from(q).find(); - assert>(true); + assert>(true); } { @@ -228,6 +239,12 @@ test('query lift', async () => { expect(items).toEqual(['bar']); assert>(true); } + + { + const items = await q.useJoinWith('image').use(filterImageSize).end().fetch(allUserNames); + expect(items).toEqual(['foo', 'bar']); + assert>(true); + } });