Skip to content

Commit 8d2aad3

Browse files
committed
fix: address PR comments and various refactor
1 parent 968220c commit 8d2aad3

13 files changed

Lines changed: 157 additions & 57 deletions

File tree

packages/clients/tanstack-query/src/common/types.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import type { FetchFn } from '@zenstackhq/client-helpers/fetch';
33
import type {
44
GetProcedureNames,
55
GetSlicedOperations,
6-
OperationsIneligibleForDelegateModels,
6+
ModelAllowsCreate,
7+
OperationsRequiringCreate,
78
ProcedureFunc,
89
QueryOptions,
910
} from '@zenstackhq/orm';
10-
import type { GetModels, IsDelegateModel, SchemaDef } from '@zenstackhq/schema';
11+
import type { GetModels, SchemaDef } from '@zenstackhq/schema';
1112

1213
/**
1314
* Context type for configuring the hooks.
@@ -59,8 +60,8 @@ export type ExtraMutationOptions = {
5960
optimisticDataProvider?: OptimisticDataProvider;
6061
} & QueryContext;
6162

62-
type HooksOperationsIneligibleForDelegateModels = OperationsIneligibleForDelegateModels extends any
63-
? `use${Capitalize<OperationsIneligibleForDelegateModels>}`
63+
type HooksOperationsRequiringCreate = OperationsRequiringCreate extends any
64+
? `use${Capitalize<OperationsRequiringCreate>}`
6465
: never;
6566

6667
type Modifiers = '' | 'Suspense' | 'Infinite' | 'SuspenseInfinite';
@@ -76,12 +77,12 @@ export type TrimSlicedOperations<
7677
> = {
7778
// trim operations based on slicing options
7879
[Key in keyof T as Key extends `use${Modifiers}${Capitalize<GetSlicedOperations<Schema, Model, Options>>}`
79-
? IsDelegateModel<Schema, Model> extends true
80-
? // trim operations ineligible for delegate models
81-
Key extends HooksOperationsIneligibleForDelegateModels
82-
? never
83-
: Key
84-
: Key
80+
? ModelAllowsCreate<Schema, Model> extends true
81+
? Key
82+
: // trim create operations for models that don't allow create
83+
Key extends HooksOperationsRequiringCreate
84+
? never
85+
: Key
8586
: never]: T[Key];
8687
};
8788

packages/orm/src/client/client-impl.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { SchemaDbPusher } from './helpers/schema-db-pusher';
3939
import type { ClientOptions, ProceduresOptions } from './options';
4040
import type { AnyPlugin } from './plugin';
4141
import { createZenStackPromise, type ZenStackPromise } from './promise';
42+
import { fieldHasDefaultValue, isUnsupportedField, requireModel } from './query-utils';
4243
import { ResultProcessor } from './result-processor';
4344

4445
/**
@@ -821,5 +822,13 @@ function createModelCrudHandler(
821822
}
822823
}
823824

825+
// Remove create/upsert operations for models with required Unsupported fields
826+
const modelDef = requireModel(client.$schema, model);
827+
if (Object.values(modelDef.fields).some((f) => isUnsupportedField(f) && !f.optional && !fieldHasDefaultValue(f))) {
828+
for (const op of ['create', 'createMany', 'createManyAndReturn', 'upsert'] as const) {
829+
delete (operations as any)[op];
830+
}
831+
}
832+
824833
return operations as ModelOperations<any, any>;
825834
}

packages/orm/src/client/contract.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
type FieldIsArray,
33
type GetModels,
44
type GetTypeDefs,
5-
type IsDelegateModel,
65
type ProcedureDef,
76
type RelationFields,
87
type RelationFieldType,
@@ -47,7 +46,7 @@ import type {
4746
GetSlicedModels,
4847
GetSlicedOperations,
4948
GetSlicedProcedures,
50-
ModelHasRequiredUnsupportedField,
49+
ModelAllowsCreate,
5150
} from './type-utils';
5251
import type { ZodSchemaFactory } from './zod/factory';
5352

@@ -289,10 +288,8 @@ type SliceOperations<
289288
// keep only operations included by slicing options
290289
[Key in keyof T as Key extends GetSlicedOperations<Schema, Model, Options> ? Key : never]: T[Key];
291290
},
292-
// exclude operations not applicable to delegate models
293-
| (IsDelegateModel<Schema, Model> extends true ? OperationsIneligibleForDelegateModels : never)
294-
// exclude create operations for models with required Unsupported fields
295-
| (ModelHasRequiredUnsupportedField<Schema, Model> extends true ? OperationsIneligibleForUnsupportedModels : never)
291+
// exclude create operations for models that don't allow create (delegate models, required Unsupported fields)
292+
| (ModelAllowsCreate<Schema, Model> extends true ? never : OperationsRequiringCreate)
296293
>;
297294

298295
export type AllModelOperations<
@@ -887,9 +884,7 @@ type CommonModelOperations<
887884
): ZenStackPromise<Schema, boolean>;
888885
};
889886

890-
export type OperationsIneligibleForDelegateModels = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert';
891-
892-
export type OperationsIneligibleForUnsupportedModels = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert';
887+
export type OperationsRequiringCreate = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert';
893888

894889
export type ModelOperations<
895890
Schema extends SchemaDef,

packages/orm/src/client/crud-types.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
FieldHasDefault,
77
FieldIsArray,
88
FieldIsDelegateDiscriminator,
9-
FieldIsDelegateRelation,
109
FieldIsRelation,
1110
FieldType,
1211
ForeignKeyFields,
@@ -60,7 +59,7 @@ import type {
6059
import type { FilterKind, QueryOptions } from './options';
6160
import type { ExtQueryArgsBase } from './plugin';
6261
import type { ToKyselySchema } from './query-builder';
63-
import type { GetSlicedFilterKindsForField, GetSlicedModels } from './type-utils';
62+
import type { GetSlicedFilterKindsForField, GetSlicedModels, ModelAllowsCreate } from './type-utils';
6463

6564
//#region Query results
6665

@@ -1331,6 +1330,15 @@ type CreateFKPayload<Schema extends SchemaDef, Model extends GetModels<Schema>>
13311330
}
13321331
>;
13331332

1333+
type RelationModelAllowsCreate<
1334+
Schema extends SchemaDef,
1335+
Model extends GetModels<Schema>,
1336+
Field extends RelationFields<Schema, Model>,
1337+
> =
1338+
GetModelFieldType<Schema, Model, Field> extends GetModels<Schema>
1339+
? ModelAllowsCreate<Schema, GetModelFieldType<Schema, Model, Field>>
1340+
: false;
1341+
13341342
type CreateRelationFieldPayload<
13351343
Schema extends SchemaDef,
13361344
Model extends GetModels<Schema>,
@@ -1360,8 +1368,8 @@ type CreateRelationFieldPayload<
13601368
},
13611369
// no "createMany" for non-array fields
13621370
| (FieldIsArray<Schema, Model, Field> extends true ? never : 'createMany')
1363-
// exclude operations not applicable to delegate models
1364-
| (FieldIsDelegateRelation<Schema, Model, Field> extends true ? 'create' | 'createMany' | 'connectOrCreate' : never)
1371+
// exclude create operations for models that don't allow create
1372+
| (RelationModelAllowsCreate<Schema, Model, Field> extends true ? never : 'create' | 'createMany' | 'connectOrCreate')
13651373
>;
13661374

13671375
type CreateRelationPayload<
@@ -1715,10 +1723,8 @@ type ToManyRelationUpdateInput<
17151723
*/
17161724
set?: SetRelationInput<Schema, Model, Field, Options>;
17171725
},
1718-
// exclude
1719-
FieldIsDelegateRelation<Schema, Model, Field> extends true
1720-
? 'create' | 'createMany' | 'connectOrCreate' | 'upsert'
1721-
: never
1726+
// exclude create operations for models that don't allow create
1727+
| (RelationModelAllowsCreate<Schema, Model, Field> extends true ? never : 'create' | 'createMany' | 'connectOrCreate' | 'upsert')
17221728
>;
17231729

17241730
type ToOneRelationUpdateInput<
@@ -1765,7 +1771,8 @@ type ToOneRelationUpdateInput<
17651771
delete?: NestedDeleteInput<Schema, Model, Field, Options>;
17661772
}
17671773
: {}),
1768-
FieldIsDelegateRelation<Schema, Model, Field> extends true ? 'create' | 'connectOrCreate' | 'upsert' : never
1774+
// exclude create operations for models that don't allow create
1775+
| (RelationModelAllowsCreate<Schema, Model, Field> extends true ? never : 'create' | 'connectOrCreate' | 'upsert')
17691776
>;
17701777

17711778
// #endregion

packages/orm/src/client/crud/operations/base.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import type { ToKysely } from '../../query-builder';
3737
import {
3838
ensureArray,
3939
extractIdFields,
40-
fieldHasDefaultValue,
4140
flattenCompoundUniqueFilters,
4241
getDiscriminatorField,
4342
getField,
@@ -48,7 +47,6 @@ import {
4847
isForeignKeyField,
4948
isRelationField,
5049
isScalarField,
51-
isUnsupportedField,
5250
requireField,
5351
requireIdFields,
5452
requireModel,
@@ -2509,16 +2507,6 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
25092507
return newArgs;
25102508
}
25112509

2512-
protected checkNoRequiredUnsupportedFields() {
2513-
const modelDef = requireModel(this.schema, this.model);
2514-
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
2515-
if (isUnsupportedField(fieldDef) && !fieldDef.optional && !fieldHasDefaultValue(fieldDef)) {
2516-
throw createNotSupportedError(
2517-
`Model "${this.model}" has a required Unsupported field "${fieldName}" and cannot be created/upserted through the ORM client`,
2518-
);
2519-
}
2520-
}
2521-
}
25222510

25232511
private doNormalizeArgs(args: unknown) {
25242512
if (args && typeof args === 'object') {

packages/orm/src/client/crud/operations/create.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import { BaseOperationHandler } from './base';
66

77
export class CreateOperationHandler<Schema extends SchemaDef> extends BaseOperationHandler<Schema> {
88
async handle(operation: 'create' | 'createMany' | 'createManyAndReturn', args: unknown | undefined) {
9-
this.checkNoRequiredUnsupportedFields();
10-
119
// normalize args to strip `undefined` fields
1210
const normalizedArgs = this.normalizeArgs(args);
1311

packages/orm/src/client/crud/operations/update.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,6 @@ export class UpdateOperationHandler<Schema extends SchemaDef> extends BaseOperat
137137
}
138138

139139
private async runUpsert(args: any) {
140-
this.checkNoRequiredUnsupportedFields();
141-
142140
// analyze if we need to read back the updated record, or just return the update result
143141
const { needReadBack, selectedFields } = this.needReadBack(args);
144142

packages/orm/src/client/type-utils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FieldDef, GetModel, GetModels, SchemaDef } from '@zenstackhq/schema';
1+
import type { FieldDef, GetModel, GetModels, IsDelegateModel, SchemaDef } from '@zenstackhq/schema';
22
import type { GetProcedureNames } from './crud-types';
33
import type { AllCrudOperations } from './crud/operations/base';
44
import type { FilterKind, QueryOptions, SlicingOptions } from './options';
@@ -24,6 +24,16 @@ export type ModelHasRequiredUnsupportedField<Schema extends SchemaDef, Model ext
2424
? true
2525
: false;
2626

27+
/**
28+
* Checks if a model allows create operations (not a delegate model and has no required Unsupported fields).
29+
*/
30+
export type ModelAllowsCreate<Schema extends SchemaDef, Model extends GetModels<Schema>> =
31+
IsDelegateModel<Schema, Model> extends true
32+
? false
33+
: ModelHasRequiredUnsupportedField<Schema, Model> extends true
34+
? false
35+
: true;
36+
2737
type IsNever<T> = [T] extends [never] ? true : false;
2838

2939
// #region Model slicing

tests/e2e/orm/client-api/unsupported.test.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -240,18 +240,40 @@ describe('Unsupported field exclusion - Zod runtime validation', () => {
240240
).toBeRejectedByValidation();
241241
});
242242

243-
it('blocks create on model with required Unsupported field', async () => {
244-
// @ts-expect-error create should not exist on geoRecord (required Unsupported without default)
245-
await expect(db.geoRecord.create({ data: { title: 'test' } })).rejects.toThrow(
246-
/required Unsupported field/,
247-
);
243+
it('blocks create on model with required Unsupported field', () => {
244+
// create should not exist on geoRecord (required Unsupported without default)
245+
// @ts-expect-error create should not be defined
246+
expect(db.geoRecord.create).toBeUndefined();
248247
});
249248

250-
it('blocks upsert on model with required Unsupported field', async () => {
249+
it('blocks upsert on model with required Unsupported field', () => {
250+
// upsert should not exist on geoRecord (required Unsupported without default)
251+
// @ts-expect-error upsert should not be defined
252+
expect(db.geoRecord.upsert).toBeUndefined();
253+
});
254+
255+
it('rejects nested create for model with required Unsupported field', async () => {
251256
await expect(
252-
// @ts-expect-error upsert should not exist on geoRecord (required Unsupported without default)
253-
db.geoRecord.upsert({ where: { id: 1 }, create: { title: 'test' }, update: { title: 'updated' } }),
254-
).rejects.toThrow(/required Unsupported field/);
257+
db.geoParent.create({
258+
data: {
259+
name: 'parent',
260+
// @ts-expect-error create should not be allowed for GeoRecord (required Unsupported)
261+
records: { create: { title: 'test' } },
262+
},
263+
}),
264+
).toBeRejectedByValidation();
265+
});
266+
267+
it('rejects nested connectOrCreate for model with required Unsupported field', async () => {
268+
await expect(
269+
db.geoParent.create({
270+
data: {
271+
name: 'parent',
272+
// @ts-expect-error connectOrCreate should not be allowed for GeoRecord (required Unsupported)
273+
records: { connectOrCreate: { where: { id: 1 }, create: { title: 'test' } } },
274+
},
275+
}),
276+
).toBeRejectedByValidation();
255277
});
256278

257279
it('allows create on model with required Unsupported field that has default', async () => {

tests/e2e/orm/schemas/unsupported/input.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,27 @@ export type GeoRecordSelect = $SelectInput<$Schema, "GeoRecord">;
5050
export type GeoRecordInclude = $IncludeInput<$Schema, "GeoRecord">;
5151
export type GeoRecordOmit = $OmitInput<$Schema, "GeoRecord">;
5252
export type GeoRecordGetPayload<Args extends $SelectIncludeOmit<$Schema, "GeoRecord", true>, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "GeoRecord", Args, Options>;
53+
export type GeoParentFindManyArgs = $FindManyArgs<$Schema, "GeoParent">;
54+
export type GeoParentFindUniqueArgs = $FindUniqueArgs<$Schema, "GeoParent">;
55+
export type GeoParentFindFirstArgs = $FindFirstArgs<$Schema, "GeoParent">;
56+
export type GeoParentExistsArgs = $ExistsArgs<$Schema, "GeoParent">;
57+
export type GeoParentCreateArgs = $CreateArgs<$Schema, "GeoParent">;
58+
export type GeoParentCreateManyArgs = $CreateManyArgs<$Schema, "GeoParent">;
59+
export type GeoParentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "GeoParent">;
60+
export type GeoParentUpdateArgs = $UpdateArgs<$Schema, "GeoParent">;
61+
export type GeoParentUpdateManyArgs = $UpdateManyArgs<$Schema, "GeoParent">;
62+
export type GeoParentUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "GeoParent">;
63+
export type GeoParentUpsertArgs = $UpsertArgs<$Schema, "GeoParent">;
64+
export type GeoParentDeleteArgs = $DeleteArgs<$Schema, "GeoParent">;
65+
export type GeoParentDeleteManyArgs = $DeleteManyArgs<$Schema, "GeoParent">;
66+
export type GeoParentCountArgs = $CountArgs<$Schema, "GeoParent">;
67+
export type GeoParentAggregateArgs = $AggregateArgs<$Schema, "GeoParent">;
68+
export type GeoParentGroupByArgs = $GroupByArgs<$Schema, "GeoParent">;
69+
export type GeoParentWhereInput = $WhereInput<$Schema, "GeoParent">;
70+
export type GeoParentSelect = $SelectInput<$Schema, "GeoParent">;
71+
export type GeoParentInclude = $IncludeInput<$Schema, "GeoParent">;
72+
export type GeoParentOmit = $OmitInput<$Schema, "GeoParent">;
73+
export type GeoParentGetPayload<Args extends $SelectIncludeOmit<$Schema, "GeoParent", true>, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "GeoParent", Args, Options>;
5374
export type GeoRecordWithDefaultFindManyArgs = $FindManyArgs<$Schema, "GeoRecordWithDefault">;
5475
export type GeoRecordWithDefaultFindUniqueArgs = $FindUniqueArgs<$Schema, "GeoRecordWithDefault">;
5576
export type GeoRecordWithDefaultFindFirstArgs = $FindFirstArgs<$Schema, "GeoRecordWithDefault">;

0 commit comments

Comments
 (0)