diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 230b162ff..225785e1e 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -968,12 +968,14 @@ export type SelectIncludeOmit< */ omit?: (OmitInput & ExtResultSelectOmitFields) | null; } & (AllowRelation extends true - ? { - /** - * Specifies relations to be included in the query result. All scalar fields are included. - */ - include?: IncludeInput | null; - } + ? HasRelations extends true + ? { + /** + * Specifies relations to be included in the query result. All scalar fields are included. + */ + include?: IncludeInput | null; + } + : {} : {}); export type SelectInput< @@ -2390,6 +2392,13 @@ type HasToManyRelations> = RelationFields< + Schema, + Model +> extends never + ? false + : true; + type EnumValue> = GetEnum[keyof GetEnum< Schema, Enum diff --git a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts index 94f29b20a..6a12e9e6b 100644 --- a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts +++ b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts @@ -245,7 +245,7 @@ export abstract class LateralJoinDialectBase extends B ); } - if (typeof payload === 'object' && payload.include && typeof payload.include === 'object') { + if (typeof payload === 'object' && 'include' in payload && payload.include && typeof payload.include === 'object') { // include relation fields Object.assign( @@ -270,7 +270,7 @@ export abstract class LateralJoinDialectBase extends B ) { let result = query; if (typeof payload === 'object') { - const selectInclude = payload.include ?? payload.select; + const selectInclude = ('include' in payload ? payload.include : undefined) ?? payload.select; if (selectInclude && typeof selectInclude === 'object') { Object.entries(selectInclude) .filter(([, value]) => value) diff --git a/packages/orm/src/client/crud/dialects/sqlite.ts b/packages/orm/src/client/crud/dialects/sqlite.ts index c302cc716..1cd4a164d 100644 --- a/packages/orm/src/client/crud/dialects/sqlite.ts +++ b/packages/orm/src/client/crud/dialects/sqlite.ts @@ -307,7 +307,7 @@ export class SqliteCrudDialect extends BaseCrudDialect ); } - if (typeof payload === 'object' && payload.include && typeof payload.include === 'object') { + if (typeof payload === 'object' && 'include' in payload && payload.include && typeof payload.include === 'object') { // include relation fields objArgs.push( ...Object.entries(payload.include) diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index 186af02c3..55070fcfb 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -36,7 +36,7 @@ import { TransactionIsolationLevel, type ClientContract } from '../contract'; import { getCrudDialect } from '../crud/dialects'; import type { BaseCrudDialect } from '../crud/dialects/base-dialect'; import { createDBQueryError, createInternalError, ORMError } from '../errors'; -import type { AfterEntityMutationCallback, OnKyselyQueryCallback } from '../plugin'; +import type { AfterEntityMutationCallback, BeforeEntityMutationCallback, OnKyselyQueryCallback } from '../plugin'; import { requireIdFields, stripAlias } from '../query-utils'; import { QueryNameMapper } from './name-mapper'; import { TempAliasTransformer } from './temp-alias-transformer'; @@ -355,7 +355,8 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { continue; } - await onEntityMutation.beforeEntityMutation({ + // tsc perf + await (onEntityMutation.beforeEntityMutation as BeforeEntityMutationCallback)({ model: mutationInfo.model, action: mutationInfo.action, queryNode, diff --git a/tests/e2e/orm/schemas/typing/typecheck.ts b/tests/e2e/orm/schemas/typing/typecheck.ts index f73d4927f..aba6de1ab 100644 --- a/tests/e2e/orm/schemas/typing/typecheck.ts +++ b/tests/e2e/orm/schemas/typing/typecheck.ts @@ -1,4 +1,4 @@ -import { ZenStackClient } from '@zenstackhq/orm'; +import { ZenStackClient, type FindManyArgs } from '@zenstackhq/orm'; import SQLite from 'better-sqlite3'; import { SqliteDialect } from 'kysely'; import { Role, Status, type Identity, type IdentityProvider } from './models'; @@ -679,3 +679,28 @@ function typeDefs() { } main(); + +// Type test: `include` should not be allowed for models without relations +{ + type NoRelationsSchema = { + provider: { type: 'sqlite' }; + models: { + Dummy: { + name: 'Dummy'; + fields: { + id: { name: 'id'; type: 'Int'; id: true }; + name: { name: 'name'; type: 'String' }; + }; + idFields: ['id']; + uniqueFields: { id: { type: 'Int' } }; + }; + }; + enums: {}; + typeDefs: {}; + plugins: {}; + }; + type DummyFindManyArgs = FindManyArgs; + // @ts-expect-error include should not be allowed for models without relations + const _testIncludeNotAllowed: DummyFindManyArgs = { include: { abcdefg: true } }; + void _testIncludeNotAllowed; +}