From 5355474488e7d7433987a89b4b66a38413695946 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 5 Nov 2025 18:14:35 -0800 Subject: [PATCH 1/4] fix(orm): typing issue with relation fields with fk having default value (#371) --- packages/schema/src/schema.ts | 5 +- packages/sdk/src/ts-schema-generator.ts | 15 +++ tests/e2e/orm/client-api/default-auth.test.ts | 21 +++ tests/e2e/orm/schemas/default-auth/input.ts | 70 ++++++++++ tests/e2e/orm/schemas/default-auth/models.ts | 12 ++ tests/e2e/orm/schemas/default-auth/schema.ts | 122 ++++++++++++++++++ .../orm/schemas/default-auth/schema.zmodel | 24 ++++ 7 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/orm/client-api/default-auth.test.ts create mode 100644 tests/e2e/orm/schemas/default-auth/input.ts create mode 100644 tests/e2e/orm/schemas/default-auth/models.ts create mode 100644 tests/e2e/orm/schemas/default-auth/schema.ts create mode 100644 tests/e2e/orm/schemas/default-auth/schema.zmodel 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? +} From 81924d9e55c9b4817b1b7a0d3ad5f3fdbd2318dd Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 5 Nov 2025 19:45:15 -0800 Subject: [PATCH 2/4] fix(tanstack): query result $optimistic typing (#372) * fix(tanstack): query result $optimistic typing * update --- packages/clients/tanstack-query/src/react.ts | 8 +++++--- packages/clients/tanstack-query/src/svelte.ts | 6 ++++-- .../clients/tanstack-query/src/utils/types.ts | 17 +++++++++++++++++ packages/clients/tanstack-query/src/vue.ts | 4 ++-- .../tanstack-query/test/react-typing-test.ts | 4 ++++ .../tanstack-query/test/svelte-typing-test.ts | 5 +++++ .../tanstack-query/test/vue-typing-test.ts | 4 ++++ 7 files changed, 41 insertions(+), 7 deletions(-) diff --git a/packages/clients/tanstack-query/src/react.ts b/packages/clients/tanstack-query/src/react.ts index 15bfbb51..a8f55b9c 100644 --- a/packages/clients/tanstack-query/src/react.ts +++ b/packages/clients/tanstack-query/src/react.ts @@ -57,7 +57,7 @@ import { type ExtraMutationOptions, type ExtraQueryOptions, } from './utils/common'; -import type { TrimDelegateModelOperations } from './utils/types'; +import type { TrimDelegateModelOperations, WithOptimistic } from './utils/types'; export type { FetchFn } from './utils/common'; @@ -93,12 +93,14 @@ function useHooksContext() { export type ModelQueryOptions = Omit, 'queryKey'> & ExtraQueryOptions; -export type ModelQueryResult = UseQueryResult & { queryKey: QueryKey }; +export type ModelQueryResult = UseQueryResult, DefaultError> & { queryKey: QueryKey }; export type ModelSuspenseQueryOptions = Omit, 'queryKey'> & ExtraQueryOptions; -export type ModelSuspenseQueryResult = UseSuspenseQueryResult & { queryKey: QueryKey }; +export type ModelSuspenseQueryResult = UseSuspenseQueryResult, DefaultError> & { + queryKey: QueryKey; +}; export type ModelInfiniteQueryOptions = Omit< UseInfiniteQueryOptions>, diff --git a/packages/clients/tanstack-query/src/svelte.ts b/packages/clients/tanstack-query/src/svelte.ts index 33cf86bd..3cd81476 100644 --- a/packages/clients/tanstack-query/src/svelte.ts +++ b/packages/clients/tanstack-query/src/svelte.ts @@ -54,7 +54,7 @@ import { type ExtraMutationOptions, type ExtraQueryOptions, } from './utils/common'; -import type { TrimDelegateModelOperations } from './utils/types'; +import type { TrimDelegateModelOperations, WithOptimistic } from './utils/types'; export type { FetchFn } from './utils/common'; @@ -91,7 +91,9 @@ function getQuerySettings() { export type ModelQueryOptions = Omit, 'queryKey'> & ExtraQueryOptions; -export type ModelQueryResult = Readable> & { queryKey: QueryKey }>; +export type ModelQueryResult = Readable< + UnwrapStore, DefaultError>> & { queryKey: QueryKey } +>; export type ModelInfiniteQueryOptions = Omit< CreateInfiniteQueryOptions>, diff --git a/packages/clients/tanstack-query/src/utils/types.ts b/packages/clients/tanstack-query/src/utils/types.ts index ac7829e9..7a5f32ef 100644 --- a/packages/clients/tanstack-query/src/utils/types.ts +++ b/packages/clients/tanstack-query/src/utils/types.ts @@ -30,3 +30,20 @@ export type TrimDelegateModelOperations< Model extends GetModels, T extends Record, > = IsDelegateModel extends true ? Omit : T; + +export type WithOptimistic = + T extends Array + ? Array< + U & { + /** + * Indicates if the item is in an optimistic update state + */ + $optimistic?: boolean; + } + > + : T & { + /** + * Indicates if the item is in an optimistic update state + */ + $optimistic?: boolean; + }; diff --git a/packages/clients/tanstack-query/src/vue.ts b/packages/clients/tanstack-query/src/vue.ts index 8239ce97..2623f014 100644 --- a/packages/clients/tanstack-query/src/vue.ts +++ b/packages/clients/tanstack-query/src/vue.ts @@ -52,7 +52,7 @@ import { type ExtraMutationOptions, type ExtraQueryOptions, } from './utils/common'; -import type { TrimDelegateModelOperations } from './utils/types'; +import type { TrimDelegateModelOperations, WithOptimistic } from './utils/types'; export type { FetchFn } from './utils/common'; export const VueQueryContextKey = 'zenstack-vue-query-context'; @@ -86,7 +86,7 @@ export type ModelQueryOptions = MaybeRefOrGetter< Omit>, 'queryKey'> & ExtraQueryOptions >; -export type ModelQueryResult = UseQueryReturnType & { queryKey: QueryKey }; +export type ModelQueryResult = UseQueryReturnType, DefaultError> & { queryKey: QueryKey }; export type ModelInfiniteQueryOptions = MaybeRefOrGetter< Omit>>, 'queryKey' | 'initialPageParam'> diff --git a/packages/clients/tanstack-query/test/react-typing-test.ts b/packages/clients/tanstack-query/test/react-typing-test.ts index 02d7a2e3..8f57ec67 100644 --- a/packages/clients/tanstack-query/test/react-typing-test.ts +++ b/packages/clients/tanstack-query/test/react-typing-test.ts @@ -16,8 +16,10 @@ check(client.user.useFindUnique({ select: { email: true } }).data.name); check(client.user.useFindUnique({ where: { id: '1' }, include: { posts: true } }).data?.posts[0]?.title); check(client.user.useFindFirst().data?.email); +check(client.user.useFindFirst().data?.$optimistic); check(client.user.useFindMany().data?.[0]?.email); +check(client.user.useFindMany().data?.[0]?.$optimistic); check(client.user.useInfiniteFindMany().data?.pages[0]?.[0]?.email); check( @@ -28,6 +30,8 @@ check( }, ).data?.pages[1]?.[0]?.email, ); +// @ts-expect-error +check(client.user.useInfiniteFindMany().data?.pages[0]?.[0]?.$optimistic); check(client.user.useSuspenseFindMany().data[0]?.email); check(client.user.useSuspenseInfiniteFindMany().data.pages[0]?.[0]?.email); diff --git a/packages/clients/tanstack-query/test/svelte-typing-test.ts b/packages/clients/tanstack-query/test/svelte-typing-test.ts index 62735112..dff02583 100644 --- a/packages/clients/tanstack-query/test/svelte-typing-test.ts +++ b/packages/clients/tanstack-query/test/svelte-typing-test.ts @@ -17,8 +17,11 @@ check(get(client.user.useFindUnique({ select: { email: true } })).data.name); check(get(client.user.useFindUnique({ where: { id: '1' }, include: { posts: true } })).data?.posts[0]?.title); check(get(client.user.useFindFirst()).data?.email); +check(get(client.user.useFindFirst()).data?.$optimistic); check(get(client.user.useFindMany()).data?.[0]?.email); +check(get(client.user.useFindMany()).data?.[0]?.$optimistic); + check(get(client.user.useInfiniteFindMany()).data?.pages[0]?.[0]?.email); check( get( @@ -30,6 +33,8 @@ check( ), ).data?.pages[1]?.[0]?.email, ); +// @ts-expect-error +check(get(client.user.useInfiniteFindMany()).data?.pages[0]?.[0]?.$optimistic); check(get(client.user.useCount()).data?.toFixed(2)); check(get(client.user.useCount({ select: { email: true } })).data?.email.toFixed(2)); diff --git a/packages/clients/tanstack-query/test/vue-typing-test.ts b/packages/clients/tanstack-query/test/vue-typing-test.ts index 07b980c5..f134378c 100644 --- a/packages/clients/tanstack-query/test/vue-typing-test.ts +++ b/packages/clients/tanstack-query/test/vue-typing-test.ts @@ -16,8 +16,10 @@ check(client.user.useFindUnique({ select: { email: true } }).data.name); check(client.user.useFindUnique({ where: { id: '1' }, include: { posts: true } }).data.value?.posts[0]?.title); check(client.user.useFindFirst().data.value?.email); +check(client.user.useFindFirst().data.value?.$optimistic); check(client.user.useFindMany().data.value?.[0]?.email); +check(client.user.useFindMany().data.value?.[0]?.$optimistic); check(client.user.useInfiniteFindMany().data.value?.pages[0]?.[0]?.email); check( @@ -28,6 +30,8 @@ check( }, ).data.value?.pages[1]?.[0]?.email, ); +// @ts-expect-error +check(client.user.useInfiniteFindMany().data.value?.pages[0]?.[0]?.$optimistic); check(client.user.useCount().data.value?.toFixed(2)); check(client.user.useCount({ select: { email: true } }).data.value?.email.toFixed(2)); From 56acc1cd5e5061397311e9e91f39ed6c46ec5e7a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:50:22 -0800 Subject: [PATCH 3/4] chore: bump version 3.0.0-beta.19 (#374) Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- package.json | 2 +- packages/cli/package.json | 2 +- packages/clients/tanstack-query/package.json | 2 +- packages/common-helpers/package.json | 2 +- packages/config/eslint-config/package.json | 2 +- packages/config/typescript-config/package.json | 2 +- packages/config/vitest-config/package.json | 2 +- packages/create-zenstack/package.json | 2 +- packages/dialects/sql.js/package.json | 2 +- packages/language/package.json | 2 +- packages/orm/package.json | 2 +- packages/plugins/policy/package.json | 2 +- packages/schema/package.json | 2 +- packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- packages/zod/package.json | 2 +- samples/next.js/package.json | 2 +- samples/orm/package.json | 2 +- tests/e2e/package.json | 2 +- tests/regression/package.json | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index eca11cee..8ba3e8ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-v3", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack", "packageManager": "pnpm@10.20.0", "scripts": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 0fcf9242..2fb5bbc7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack CLI", "description": "FullStack database toolkit with built-in access control and automatic API generation.", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "type": "module", "author": { "name": "ZenStack Team" diff --git a/packages/clients/tanstack-query/package.json b/packages/clients/tanstack-query/package.json index 60810ad0..b139544f 100644 --- a/packages/clients/tanstack-query/package.json +++ b/packages/clients/tanstack-query/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/tanstack-query", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "TanStack Query Client for consuming ZenStack v3's CRUD service", "main": "index.js", "type": "module", diff --git a/packages/common-helpers/package.json b/packages/common-helpers/package.json index 7a76561b..8660a14b 100644 --- a/packages/common-helpers/package.json +++ b/packages/common-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/common-helpers", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack Common Helpers", "type": "module", "scripts": { diff --git a/packages/config/eslint-config/package.json b/packages/config/eslint-config/package.json index 9e8c6eb3..4e334630 100644 --- a/packages/config/eslint-config/package.json +++ b/packages/config/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/eslint-config", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "type": "module", "private": true, "license": "MIT" diff --git a/packages/config/typescript-config/package.json b/packages/config/typescript-config/package.json index db7ca194..59948ded 100644 --- a/packages/config/typescript-config/package.json +++ b/packages/config/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/typescript-config", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "private": true, "license": "MIT" } diff --git a/packages/config/vitest-config/package.json b/packages/config/vitest-config/package.json index 40631187..665adccc 100644 --- a/packages/config/vitest-config/package.json +++ b/packages/config/vitest-config/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/vitest-config", "type": "module", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "private": true, "license": "MIT", "exports": { diff --git a/packages/create-zenstack/package.json b/packages/create-zenstack/package.json index a53ba53a..29ec8bbb 100644 --- a/packages/create-zenstack/package.json +++ b/packages/create-zenstack/package.json @@ -1,6 +1,6 @@ { "name": "create-zenstack", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "Create a new ZenStack project", "type": "module", "scripts": { diff --git a/packages/dialects/sql.js/package.json b/packages/dialects/sql.js/package.json index 14566cbc..dff554c4 100644 --- a/packages/dialects/sql.js/package.json +++ b/packages/dialects/sql.js/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/kysely-sql-js", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "Kysely dialect for sql.js", "type": "module", "scripts": { diff --git a/packages/language/package.json b/packages/language/package.json index d8fa2c21..aadc945d 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/language", "description": "ZenStack ZModel language specification", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "license": "MIT", "author": "ZenStack Team", "files": [ diff --git a/packages/orm/package.json b/packages/orm/package.json index bca7e4c4..fa5dbc4b 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/orm", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack ORM", "type": "module", "scripts": { diff --git a/packages/plugins/policy/package.json b/packages/plugins/policy/package.json index 8ac4783a..0d5a3cbf 100644 --- a/packages/plugins/policy/package.json +++ b/packages/plugins/policy/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/plugin-policy", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack Policy Plugin", "type": "module", "scripts": { diff --git a/packages/schema/package.json b/packages/schema/package.json index 9e3d7953..8e1ddf32 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/schema", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack Runtime Schema", "type": "module", "scripts": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 2b23750b..c74e1d3f 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack SDK", "type": "module", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 5d5dd970..ef152890 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack automatic CRUD API handlers and server adapters", "type": "module", "scripts": { diff --git a/packages/testtools/package.json b/packages/testtools/package.json index ff3d7b95..6b3d40f3 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "ZenStack Test Tools", "type": "module", "scripts": { diff --git a/packages/zod/package.json b/packages/zod/package.json index 9ef0386e..592445f2 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/zod", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "", "type": "module", "main": "index.js", diff --git a/samples/next.js/package.json b/samples/next.js/package.json index eb446539..690215fd 100644 --- a/samples/next.js/package.json +++ b/samples/next.js/package.json @@ -1,6 +1,6 @@ { "name": "next.js", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "private": true, "scripts": { "generate": "zen generate --lite", diff --git a/samples/orm/package.json b/samples/orm/package.json index c1d61b9c..bd06e556 100644 --- a/samples/orm/package.json +++ b/samples/orm/package.json @@ -1,6 +1,6 @@ { "name": "sample-blog", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "description": "", "main": "index.js", "private": true, diff --git a/tests/e2e/package.json b/tests/e2e/package.json index c30b1cbf..3cd81ee0 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -1,6 +1,6 @@ { "name": "e2e", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "private": true, "type": "module", "scripts": { diff --git a/tests/regression/package.json b/tests/regression/package.json index 420b56ac..664d1990 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -1,6 +1,6 @@ { "name": "regression", - "version": "3.0.0-beta.18", + "version": "3.0.0-beta.19", "private": true, "type": "module", "scripts": { From 2946115491a1242a5ed50b03d0218bf0d232f114 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 5 Nov 2025 22:07:49 -0800 Subject: [PATCH 4/4] fix(tanstack): extra optimistic typing fix (#376) --- .../clients/tanstack-query/src/utils/types.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/clients/tanstack-query/src/utils/types.ts b/packages/clients/tanstack-query/src/utils/types.ts index 7a5f32ef..b6616588 100644 --- a/packages/clients/tanstack-query/src/utils/types.ts +++ b/packages/clients/tanstack-query/src/utils/types.ts @@ -31,19 +31,13 @@ export type TrimDelegateModelOperations< T extends Record, > = IsDelegateModel extends true ? Omit : T; -export type WithOptimistic = - T extends Array - ? Array< - U & { - /** - * Indicates if the item is in an optimistic update state - */ - $optimistic?: boolean; - } - > - : T & { - /** - * Indicates if the item is in an optimistic update state - */ - $optimistic?: boolean; - }; +type WithOptimisticFlag = T extends object + ? T & { + /** + * Indicates if the item is in an optimistic update state + */ + $optimistic?: boolean; + } + : T; + +export type WithOptimistic = T extends Array ? Array> : WithOptimisticFlag;