diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index bd11c6220..2c29da5bd 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -36,7 +36,7 @@ export async function run(options: Options) { \`\`\`ts import { ZenStackClient } from '@zenstackhq/orm'; -import { schema } from '${outputPath}/schema'; +import { schema } from '${path.relative('.', outputPath)}/schema'; const client = new ZenStackClient(schema, { dialect: { ... } diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 9704d1739..1fefdc7dd 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -139,50 +139,34 @@ type ModelSelectResult< Options extends QueryOptions, > = { [Key in keyof Select as Select[Key] extends false | undefined - ? never - : Key extends keyof Omit - ? Omit[Key] extends true - ? never - : Key - : Key extends '_count' - ? Select[Key] extends SelectCount - ? Key - : never + ? // not selected + never + : Key extends '_count' + ? // select "_count" + Select[Key] extends SelectCount + ? Key + : never + : Key extends keyof Omit + ? Omit[Key] extends true + ? // omit + never + : Key : Key]: Key extends '_count' - ? SelectCountResult + ? // select "_count" result + SelectCountResult : Key extends NonRelationFields - ? MapModelFieldType + ? // scalar field result + MapModelFieldType : Key extends RelationFields - ? Select[Key] extends FindArgs< + ? // relation field result (recurse) + ModelResult< Schema, RelationFieldType, + Select[Key], + Options, + ModelFieldIsOptional, FieldIsArray > - ? 'select' extends keyof Select[Key] - ? ModelResult< - Schema, - RelationFieldType, - Pick, - Options, - ModelFieldIsOptional, - FieldIsArray - > - : ModelResult< - Schema, - RelationFieldType, - Pick, - Options, - ModelFieldIsOptional, - FieldIsArray - > - : DefaultModelResult< - Schema, - RelationFieldType, - Omit, - Options, - ModelFieldIsOptional, - FieldIsArray - > : never; }; @@ -204,40 +188,29 @@ export type ModelResult< Array = false, > = WrapType< Args extends { - select: infer S; - omit?: infer O; - } + select: infer S extends object; + omit?: infer O extends object; + } & Record ? ModelSelectResult : Args extends { - include: infer I; - omit?: infer O; - } - ? DefaultModelResult & { + include: infer I extends object; + omit?: infer O extends object; + } & Record + ? // select all non-omitted scalar fields + DefaultModelResult & { + // recurse for "include" relations [Key in keyof I & RelationFields as I[Key] extends false | undefined ? never - : Key]: I[Key] extends FindArgs< + : Key]: ModelResult< Schema, RelationFieldType, + I[Key], + Options, + ModelFieldIsOptional, FieldIsArray - > - ? ModelResult< - Schema, - RelationFieldType, - I[Key], - Options, - ModelFieldIsOptional, - FieldIsArray - > - : DefaultModelResult< - Schema, - RelationFieldType, - undefined, - Options, - ModelFieldIsOptional, - FieldIsArray - >; + >; } - : Args extends { omit: infer O } + : Args extends { omit: infer O } & Record ? DefaultModelResult : DefaultModelResult, Optional, diff --git a/tests/e2e/orm/schemas/typing/typecheck.ts b/tests/e2e/orm/schemas/typing/typecheck.ts index 9c6860e3b..976c04c80 100644 --- a/tests/e2e/orm/schemas/typing/typecheck.ts +++ b/tests/e2e/orm/schemas/typing/typecheck.ts @@ -167,6 +167,7 @@ async function find() { select: { posts: { where: { title: 'Foo' }, + take: 1, select: { author: { select: { diff --git a/tests/regression/package.json b/tests/regression/package.json index d750958f7..98f028d0e 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -4,9 +4,9 @@ "private": true, "type": "module", "scripts": { - "build": "pnpm run generate", - "generate": "tsx ../../scripts/test-generate.ts ./test", - "test": "pnpm generate && tsc && vitest run" + "build": "pnpm run test:generate", + "test:generate": "tsx ../../scripts/test-generate.ts ./test", + "test": "pnpm test:generate && tsc && vitest run" }, "dependencies": { "@zenstackhq/testtools": "workspace:*", diff --git a/tests/regression/test/issue-503/input.ts b/tests/regression/test/issue-503/input.ts new file mode 100644 index 000000000..e1e5bb0da --- /dev/null +++ b/tests/regression/test/issue-503/input.ts @@ -0,0 +1,70 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaType as $Schema } from "./schema"; +import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, CreateArgs as $CreateArgs, CreateManyArgs as $CreateManyArgs, CreateManyAndReturnArgs as $CreateManyAndReturnArgs, UpdateArgs as $UpdateArgs, UpdateManyArgs as $UpdateManyArgs, UpdateManyAndReturnArgs as $UpdateManyAndReturnArgs, UpsertArgs as $UpsertArgs, DeleteArgs as $DeleteArgs, DeleteManyArgs as $DeleteManyArgs, CountArgs as $CountArgs, AggregateArgs as $AggregateArgs, GroupByArgs as $GroupByArgs, WhereInput as $WhereInput, SelectInput as $SelectInput, IncludeInput as $IncludeInput, OmitInput as $OmitInput, QueryOptions as $QueryOptions } from "@zenstackhq/orm"; +import type { SimplifiedPlainResult as $Result, SelectIncludeOmit as $SelectIncludeOmit } from "@zenstackhq/orm"; +export type InternalChatFindManyArgs = $FindManyArgs<$Schema, "InternalChat">; +export type InternalChatFindUniqueArgs = $FindUniqueArgs<$Schema, "InternalChat">; +export type InternalChatFindFirstArgs = $FindFirstArgs<$Schema, "InternalChat">; +export type InternalChatCreateArgs = $CreateArgs<$Schema, "InternalChat">; +export type InternalChatCreateManyArgs = $CreateManyArgs<$Schema, "InternalChat">; +export type InternalChatCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "InternalChat">; +export type InternalChatUpdateArgs = $UpdateArgs<$Schema, "InternalChat">; +export type InternalChatUpdateManyArgs = $UpdateManyArgs<$Schema, "InternalChat">; +export type InternalChatUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "InternalChat">; +export type InternalChatUpsertArgs = $UpsertArgs<$Schema, "InternalChat">; +export type InternalChatDeleteArgs = $DeleteArgs<$Schema, "InternalChat">; +export type InternalChatDeleteManyArgs = $DeleteManyArgs<$Schema, "InternalChat">; +export type InternalChatCountArgs = $CountArgs<$Schema, "InternalChat">; +export type InternalChatAggregateArgs = $AggregateArgs<$Schema, "InternalChat">; +export type InternalChatGroupByArgs = $GroupByArgs<$Schema, "InternalChat">; +export type InternalChatWhereInput = $WhereInput<$Schema, "InternalChat">; +export type InternalChatSelect = $SelectInput<$Schema, "InternalChat">; +export type InternalChatInclude = $IncludeInput<$Schema, "InternalChat">; +export type InternalChatOmit = $OmitInput<$Schema, "InternalChat">; +export type InternalChatGetPayload, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "InternalChat", Args, Options>; +export type MessageFindManyArgs = $FindManyArgs<$Schema, "Message">; +export type MessageFindUniqueArgs = $FindUniqueArgs<$Schema, "Message">; +export type MessageFindFirstArgs = $FindFirstArgs<$Schema, "Message">; +export type MessageCreateArgs = $CreateArgs<$Schema, "Message">; +export type MessageCreateManyArgs = $CreateManyArgs<$Schema, "Message">; +export type MessageCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Message">; +export type MessageUpdateArgs = $UpdateArgs<$Schema, "Message">; +export type MessageUpdateManyArgs = $UpdateManyArgs<$Schema, "Message">; +export type MessageUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Message">; +export type MessageUpsertArgs = $UpsertArgs<$Schema, "Message">; +export type MessageDeleteArgs = $DeleteArgs<$Schema, "Message">; +export type MessageDeleteManyArgs = $DeleteManyArgs<$Schema, "Message">; +export type MessageCountArgs = $CountArgs<$Schema, "Message">; +export type MessageAggregateArgs = $AggregateArgs<$Schema, "Message">; +export type MessageGroupByArgs = $GroupByArgs<$Schema, "Message">; +export type MessageWhereInput = $WhereInput<$Schema, "Message">; +export type MessageSelect = $SelectInput<$Schema, "Message">; +export type MessageInclude = $IncludeInput<$Schema, "Message">; +export type MessageOmit = $OmitInput<$Schema, "Message">; +export type MessageGetPayload, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "Message", Args, Options>; +export type MediaFindManyArgs = $FindManyArgs<$Schema, "Media">; +export type MediaFindUniqueArgs = $FindUniqueArgs<$Schema, "Media">; +export type MediaFindFirstArgs = $FindFirstArgs<$Schema, "Media">; +export type MediaCreateArgs = $CreateArgs<$Schema, "Media">; +export type MediaCreateManyArgs = $CreateManyArgs<$Schema, "Media">; +export type MediaCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Media">; +export type MediaUpdateArgs = $UpdateArgs<$Schema, "Media">; +export type MediaUpdateManyArgs = $UpdateManyArgs<$Schema, "Media">; +export type MediaUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Media">; +export type MediaUpsertArgs = $UpsertArgs<$Schema, "Media">; +export type MediaDeleteArgs = $DeleteArgs<$Schema, "Media">; +export type MediaDeleteManyArgs = $DeleteManyArgs<$Schema, "Media">; +export type MediaCountArgs = $CountArgs<$Schema, "Media">; +export type MediaAggregateArgs = $AggregateArgs<$Schema, "Media">; +export type MediaGroupByArgs = $GroupByArgs<$Schema, "Media">; +export type MediaWhereInput = $WhereInput<$Schema, "Media">; +export type MediaSelect = $SelectInput<$Schema, "Media">; +export type MediaInclude = $IncludeInput<$Schema, "Media">; +export type MediaOmit = $OmitInput<$Schema, "Media">; +export type MediaGetPayload, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "Media", Args, Options>; diff --git a/tests/regression/test/issue-503/models.ts b/tests/regression/test/issue-503/models.ts new file mode 100644 index 000000000..e71f3a616 --- /dev/null +++ b/tests/regression/test/issue-503/models.ts @@ -0,0 +1,12 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaType as $Schema } from "./schema"; +import { type ModelResult as $ModelResult } from "@zenstackhq/orm"; +export type InternalChat = $ModelResult<$Schema, "InternalChat">; +export type Message = $ModelResult<$Schema, "Message">; +export type Media = $ModelResult<$Schema, "Media">; diff --git a/tests/regression/test/issue-503/regression.test.ts b/tests/regression/test/issue-503/regression.test.ts new file mode 100644 index 000000000..c31864a70 --- /dev/null +++ b/tests/regression/test/issue-503/regression.test.ts @@ -0,0 +1,31 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; +import { schema } from './schema'; + +describe('Regression tests for issues #503', () => { + it('verifies the issue', async () => { + const db = await createTestClient(schema); + const r = await db.internalChat.create({ + data: { + messages: { + create: { + media: { + create: { + type: 'Image', + }, + }, + }, + }, + }, + select: { + messages: { + take: 1, + include: { + media: true, + }, + }, + }, + }); + expect(r.messages[0]?.media).toMatchObject({ type: 'Image' }); + }); +}); diff --git a/tests/regression/test/issue-503/schema.ts b/tests/regression/test/issue-503/schema.ts new file mode 100644 index 000000000..0c8573144 --- /dev/null +++ b/tests/regression/test/issue-503/schema.ts @@ -0,0 +1,109 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "sqlite" + } as const; + models = { + InternalChat: { + name: "InternalChat", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + messages: { + name: "messages", + type: "Message", + array: true, + relation: { opposite: "chat" } + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, + Message: { + name: "Message", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + chat: { + name: "chat", + type: "InternalChat", + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array([ExpressionUtils.field("chatId")]) }, { name: "references", value: ExpressionUtils.array([ExpressionUtils.field("id")]) }] }], + relation: { opposite: "messages", fields: ["chatId"], references: ["id"] } + }, + chatId: { + name: "chatId", + type: "Int", + foreignKeyFor: [ + "chat" + ] + }, + media: { + name: "media", + type: "Media", + optional: true, + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array([ExpressionUtils.field("mediaId")]) }, { name: "references", value: ExpressionUtils.array([ExpressionUtils.field("id")]) }] }], + relation: { opposite: "messages", fields: ["mediaId"], references: ["id"] } + }, + mediaId: { + name: "mediaId", + type: "Int", + optional: true, + foreignKeyFor: [ + "media" + ] + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, + Media: { + name: "Media", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + type: { + name: "type", + type: "String" + }, + messages: { + name: "messages", + type: "Message", + array: true, + relation: { opposite: "media" } + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + } + } as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/regression/test/issue-503/schema.zmodel b/tests/regression/test/issue-503/schema.zmodel new file mode 100644 index 000000000..7cbe035de --- /dev/null +++ b/tests/regression/test/issue-503/schema.zmodel @@ -0,0 +1,23 @@ +datasource db { + provider = "sqlite" + url = "file:./dev.db" +} + +model InternalChat { + id Int @id @default(autoincrement()) + messages Message[] +} + +model Message { + id Int @id @default(autoincrement()) + chat InternalChat @relation(fields: [chatId], references: [id]) + chatId Int + media Media? @relation(fields: [mediaId], references: [id]) + mediaId Int? +} + +model Media { + id Int @id @default(autoincrement()) + type String + messages Message[] +}