diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index e8beefc9..5dc9efc4 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -52,6 +52,7 @@ export type RelationInfo = { name?: string; fields?: string[]; references?: string[]; + hasDefault?: boolean; opposite?: string; onDelete?: CascadeAction; onUpdate?: CascadeAction; @@ -253,7 +254,9 @@ export type FieldHasDefault< ? true : GetModelField['updatedAt'] extends true ? true - : false; + : GetModelField['relation'] extends { hasDefault: true } + ? true + : false; export type FieldIsRelationArray< Schema extends SchemaDef, diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index f56b4e23..821e0bd6 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -707,12 +707,16 @@ export class TsSchemaGenerator { } const relation = getAttribute(field, '@relation'); + const fkFields: string[] = []; if (relation) { for (const arg of relation.args) { const param = arg.$resolvedParam.name; if (param === 'fields' || param === 'references') { const fieldNames = this.getReferenceNames(arg.value); if (fieldNames) { + if (param === 'fields') { + fkFields.push(...fieldNames); + } relationFields.push( ts.factory.createPropertyAssignment( param, @@ -733,6 +737,17 @@ export class TsSchemaGenerator { } } + // check if all fk fields have default values + if (fkFields.length > 0) { + const allHaveDefault = fkFields.every((fieldName) => { + const fieldDef = field.$container.fields.find((f) => f.name === fieldName); + return fieldDef && hasAttribute(fieldDef, '@default'); + }); + if (allHaveDefault) { + relationFields.push(ts.factory.createPropertyAssignment('hasDefault', ts.factory.createTrue())); + } + } + return ts.factory.createObjectLiteralExpression(relationFields); } diff --git a/tests/e2e/orm/client-api/default-auth.test.ts b/tests/e2e/orm/client-api/default-auth.test.ts new file mode 100644 index 00000000..54d1261b --- /dev/null +++ b/tests/e2e/orm/client-api/default-auth.test.ts @@ -0,0 +1,21 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; +import { schema } from '../schemas/default-auth/schema'; + +describe('Auth as default value tests', () => { + it('should create without requiring the default auth field', async () => { + const db = await createTestClient(schema); + const user1 = await db.user.create({ data: {} }); + await expect(db.$setAuth(user1).profile.create({ data: { bio: 'My bio' } })).resolves.toMatchObject({ + userId: user1.id, + }); + + const address = await db.address.create({ data: { city: 'Seattle ' } }); + const user2 = await db.user.create({ data: {} }); + await expect( + db.$setAuth(user2).profile.create({ data: { bio: 'My bio', address: { connect: { id: address.id } } } }), + ).resolves.toMatchObject({ + userId: user2.id, + }); + }); +}); diff --git a/tests/e2e/orm/schemas/default-auth/input.ts b/tests/e2e/orm/schemas/default-auth/input.ts new file mode 100644 index 00000000..5ebdd080 --- /dev/null +++ b/tests/e2e/orm/schemas/default-auth/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 } from "@zenstackhq/orm"; +import type { SimplifiedModelResult as $SimplifiedModelResult, SelectIncludeOmit as $SelectIncludeOmit } from "@zenstackhq/orm"; +export type UserFindManyArgs = $FindManyArgs<$Schema, "User">; +export type UserFindUniqueArgs = $FindUniqueArgs<$Schema, "User">; +export type UserFindFirstArgs = $FindFirstArgs<$Schema, "User">; +export type UserCreateArgs = $CreateArgs<$Schema, "User">; +export type UserCreateManyArgs = $CreateManyArgs<$Schema, "User">; +export type UserCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "User">; +export type UserUpdateArgs = $UpdateArgs<$Schema, "User">; +export type UserUpdateManyArgs = $UpdateManyArgs<$Schema, "User">; +export type UserUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "User">; +export type UserUpsertArgs = $UpsertArgs<$Schema, "User">; +export type UserDeleteArgs = $DeleteArgs<$Schema, "User">; +export type UserDeleteManyArgs = $DeleteManyArgs<$Schema, "User">; +export type UserCountArgs = $CountArgs<$Schema, "User">; +export type UserAggregateArgs = $AggregateArgs<$Schema, "User">; +export type UserGroupByArgs = $GroupByArgs<$Schema, "User">; +export type UserWhereInput = $WhereInput<$Schema, "User">; +export type UserSelect = $SelectInput<$Schema, "User">; +export type UserInclude = $IncludeInput<$Schema, "User">; +export type UserOmit = $OmitInput<$Schema, "User">; +export type UserGetPayload> = $SimplifiedModelResult<$Schema, "User", Args>; +export type ProfileFindManyArgs = $FindManyArgs<$Schema, "Profile">; +export type ProfileFindUniqueArgs = $FindUniqueArgs<$Schema, "Profile">; +export type ProfileFindFirstArgs = $FindFirstArgs<$Schema, "Profile">; +export type ProfileCreateArgs = $CreateArgs<$Schema, "Profile">; +export type ProfileCreateManyArgs = $CreateManyArgs<$Schema, "Profile">; +export type ProfileCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Profile">; +export type ProfileUpdateArgs = $UpdateArgs<$Schema, "Profile">; +export type ProfileUpdateManyArgs = $UpdateManyArgs<$Schema, "Profile">; +export type ProfileUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Profile">; +export type ProfileUpsertArgs = $UpsertArgs<$Schema, "Profile">; +export type ProfileDeleteArgs = $DeleteArgs<$Schema, "Profile">; +export type ProfileDeleteManyArgs = $DeleteManyArgs<$Schema, "Profile">; +export type ProfileCountArgs = $CountArgs<$Schema, "Profile">; +export type ProfileAggregateArgs = $AggregateArgs<$Schema, "Profile">; +export type ProfileGroupByArgs = $GroupByArgs<$Schema, "Profile">; +export type ProfileWhereInput = $WhereInput<$Schema, "Profile">; +export type ProfileSelect = $SelectInput<$Schema, "Profile">; +export type ProfileInclude = $IncludeInput<$Schema, "Profile">; +export type ProfileOmit = $OmitInput<$Schema, "Profile">; +export type ProfileGetPayload> = $SimplifiedModelResult<$Schema, "Profile", Args>; +export type AddressFindManyArgs = $FindManyArgs<$Schema, "Address">; +export type AddressFindUniqueArgs = $FindUniqueArgs<$Schema, "Address">; +export type AddressFindFirstArgs = $FindFirstArgs<$Schema, "Address">; +export type AddressCreateArgs = $CreateArgs<$Schema, "Address">; +export type AddressCreateManyArgs = $CreateManyArgs<$Schema, "Address">; +export type AddressCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Address">; +export type AddressUpdateArgs = $UpdateArgs<$Schema, "Address">; +export type AddressUpdateManyArgs = $UpdateManyArgs<$Schema, "Address">; +export type AddressUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Address">; +export type AddressUpsertArgs = $UpsertArgs<$Schema, "Address">; +export type AddressDeleteArgs = $DeleteArgs<$Schema, "Address">; +export type AddressDeleteManyArgs = $DeleteManyArgs<$Schema, "Address">; +export type AddressCountArgs = $CountArgs<$Schema, "Address">; +export type AddressAggregateArgs = $AggregateArgs<$Schema, "Address">; +export type AddressGroupByArgs = $GroupByArgs<$Schema, "Address">; +export type AddressWhereInput = $WhereInput<$Schema, "Address">; +export type AddressSelect = $SelectInput<$Schema, "Address">; +export type AddressInclude = $IncludeInput<$Schema, "Address">; +export type AddressOmit = $OmitInput<$Schema, "Address">; +export type AddressGetPayload> = $SimplifiedModelResult<$Schema, "Address", Args>; diff --git a/tests/e2e/orm/schemas/default-auth/models.ts b/tests/e2e/orm/schemas/default-auth/models.ts new file mode 100644 index 00000000..3f7f08e6 --- /dev/null +++ b/tests/e2e/orm/schemas/default-auth/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 User = $ModelResult<$Schema, "User">; +export type Profile = $ModelResult<$Schema, "Profile">; +export type Address = $ModelResult<$Schema, "Address">; diff --git a/tests/e2e/orm/schemas/default-auth/schema.ts b/tests/e2e/orm/schemas/default-auth/schema.ts new file mode 100644 index 00000000..42035b8b --- /dev/null +++ b/tests/e2e/orm/schemas/default-auth/schema.ts @@ -0,0 +1,122 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// 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 const schema = { + provider: { + type: "sqlite" + }, + models: { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + profile: { + name: "profile", + type: "Profile", + optional: true, + relation: { opposite: "user" } + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, + Profile: { + name: "Profile", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + bio: { + name: "bio", + type: "String", + optional: true + }, + userId: { + name: "userId", + type: "Int", + unique: true, + attributes: [{ name: "@unique" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.member(ExpressionUtils.call("auth"), ["id"]) }] }], + default: ExpressionUtils.member(ExpressionUtils.call("auth"), ["id"]), + foreignKeyFor: [ + "user" + ] + }, + user: { + name: "user", + type: "User", + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array([ExpressionUtils.field("userId")]) }, { name: "references", value: ExpressionUtils.array([ExpressionUtils.field("id")]) }] }], + relation: { opposite: "profile", fields: ["userId"], references: ["id"], hasDefault: true } + }, + address: { + name: "address", + type: "Address", + optional: true, + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array([ExpressionUtils.field("addressId")]) }, { name: "references", value: ExpressionUtils.array([ExpressionUtils.field("id")]) }] }], + relation: { opposite: "profile", fields: ["addressId"], references: ["id"] } + }, + addressId: { + name: "addressId", + type: "Int", + unique: true, + optional: true, + attributes: [{ name: "@unique" }], + foreignKeyFor: [ + "address" + ] + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" }, + userId: { type: "Int" }, + addressId: { type: "Int" } + } + }, + Address: { + name: "Address", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + city: { + name: "city", + type: "String" + }, + profile: { + name: "profile", + type: "Profile", + optional: true, + relation: { opposite: "address" } + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + } + }, + authType: "User", + plugins: {} +} as const satisfies SchemaDef; +export type SchemaType = typeof schema; diff --git a/tests/e2e/orm/schemas/default-auth/schema.zmodel b/tests/e2e/orm/schemas/default-auth/schema.zmodel new file mode 100644 index 00000000..c7bf21f9 --- /dev/null +++ b/tests/e2e/orm/schemas/default-auth/schema.zmodel @@ -0,0 +1,24 @@ +datasource db { + provider = "sqlite" + url = "file:./dev.db" +} + +model User { + id Int @id @default(autoincrement()) + profile Profile? +} + +model Profile { + id Int @id @default(autoincrement()) + bio String? + userId Int @unique @default(auth().id) + user User @relation(fields: [userId], references: [id]) + address Address? @relation(fields: [addressId], references: [id]) + addressId Int? @unique +} + +model Address { + id Int @id @default(autoincrement()) + city String + profile Profile? +}