@@ -410,6 +410,9 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
410410 }
411411 }
412412
413+ // Shared filter schemas for where inputs
414+ this . generateFilterSchemas ( schemas ) ;
415+
413416 // Per-model schemas
414417 for ( const modelName of getIncludedModels ( this . schema , this . queryOptions ) ) {
415418 const modelDef = this . schema . models [ modelName ] ! ;
@@ -440,12 +443,21 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
440443 schemas [ `${ modelName } GroupByArgs` ] = this . buildGroupByArgsSchema ( modelName ) ;
441444 schemas [ `${ modelName } ExistsArgs` ] = this . buildExistsArgsSchema ( modelName ) ;
442445 schemas [ `${ modelName } Response` ] = this . buildResponseSchema ( modelName ) ;
443- schemas [ `${ modelName } CountAggregateOutputType` ] = this . buildCountAggregateOutputTypeSchema ( modelName , modelDef ) ;
446+ schemas [ `${ modelName } CountAggregateOutputType` ] = this . buildCountAggregateOutputTypeSchema (
447+ modelName ,
448+ modelDef ,
449+ ) ;
444450 schemas [ `${ modelName } MinAggregateOutputType` ] = this . buildMinAggregateOutputTypeSchema ( modelName , modelDef ) ;
445451 schemas [ `${ modelName } MaxAggregateOutputType` ] = this . buildMaxAggregateOutputTypeSchema ( modelName , modelDef ) ;
446452 if ( this . modelHasNumericFields ( modelDef ) ) {
447- schemas [ `${ modelName } AvgAggregateOutputType` ] = this . buildAvgAggregateOutputTypeSchema ( modelName , modelDef ) ;
448- schemas [ `${ modelName } SumAggregateOutputType` ] = this . buildSumAggregateOutputTypeSchema ( modelName , modelDef ) ;
453+ schemas [ `${ modelName } AvgAggregateOutputType` ] = this . buildAvgAggregateOutputTypeSchema (
454+ modelName ,
455+ modelDef ,
456+ ) ;
457+ schemas [ `${ modelName } SumAggregateOutputType` ] = this . buildSumAggregateOutputTypeSchema (
458+ modelName ,
459+ modelDef ,
460+ ) ;
449461 }
450462 schemas [ `Aggregate${ modelName } ` ] = this . buildAggregateSchema ( modelName , modelDef ) ;
451463 schemas [ `${ modelName } GroupByOutputType` ] = this . buildGroupByOutputTypeSchema ( modelName , modelDef ) ;
@@ -572,18 +584,11 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
572584 }
573585
574586 private buildCreateInputSchema ( _modelName : string , modelDef : ModelDef ) : SchemaObject {
575- const idFieldNames = new Set ( modelDef . idFields ) ;
576587 const properties : Record < string , SchemaObject | ReferenceObject > = { } ;
577588 const required : string [ ] = [ ] ;
578589
579590 for ( const [ fieldName , fieldDef ] of Object . entries ( modelDef . fields ) ) {
580591 if ( fieldDef . relation ) continue ;
581- if ( fieldDef . foreignKeyFor ) continue ;
582- if ( fieldDef . omit ) continue ;
583- if ( fieldDef . updatedAt ) continue ;
584- // Skip auto-generated id fields
585- if ( idFieldNames . has ( fieldName ) && fieldDef . default !== undefined ) continue ;
586-
587592 properties [ fieldName ] = this . typeToSchema ( fieldDef . type ) ;
588593 if ( ! fieldDef . optional && fieldDef . default === undefined && ! fieldDef . array ) {
589594 required . push ( fieldName ) ;
@@ -602,10 +607,6 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
602607
603608 for ( const [ fieldName , fieldDef ] of Object . entries ( modelDef . fields ) ) {
604609 if ( fieldDef . relation ) continue ;
605- if ( fieldDef . foreignKeyFor ) continue ;
606- if ( fieldDef . omit ) continue ;
607- if ( fieldDef . updatedAt ) continue ;
608-
609610 properties [ fieldName ] = this . typeToSchema ( fieldDef . type ) ;
610611 }
611612
@@ -645,7 +646,6 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
645646
646647 for ( const [ fieldName , fieldDef ] of Object . entries ( modelDef . fields ) ) {
647648 if ( fieldDef . relation ) continue ;
648- if ( fieldDef . omit ) continue ;
649649 const filterSchema = this . buildFieldFilterSchema ( modelName , fieldName , fieldDef ) ;
650650 if ( filterSchema ) {
651651 properties [ fieldName ] = filterSchema ;
@@ -840,7 +840,8 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
840840
841841 private modelHasNumericFields ( modelDef : ModelDef ) : boolean {
842842 return Object . values ( modelDef . fields ) . some (
843- ( f ) => ! f . relation && ( f . type === 'Int' || f . type === 'Float' || f . type === 'BigInt' || f . type === 'Decimal' )
843+ ( f ) =>
844+ ! f . relation && ( f . type === 'Int' || f . type === 'Float' || f . type === 'BigInt' || f . type === 'Decimal' ) ,
844845 ) ;
845846 }
846847
@@ -879,7 +880,12 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
879880 const properties : Record < string , SchemaObject > = { } ;
880881 for ( const [ fieldName , fieldDef ] of Object . entries ( modelDef . fields ) ) {
881882 if ( fieldDef . relation ) continue ;
882- if ( fieldDef . type !== 'Int' && fieldDef . type !== 'Float' && fieldDef . type !== 'BigInt' && fieldDef . type !== 'Decimal' )
883+ if (
884+ fieldDef . type !== 'Int' &&
885+ fieldDef . type !== 'Float' &&
886+ fieldDef . type !== 'BigInt' &&
887+ fieldDef . type !== 'Decimal'
888+ )
883889 continue ;
884890 // avg always returns a float
885891 properties [ fieldName ] = { oneOf : [ { type : 'null' as const } , { type : 'number' } ] } ;
@@ -891,7 +897,12 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
891897 const properties : Record < string , SchemaObject | ReferenceObject > = { } ;
892898 for ( const [ fieldName , fieldDef ] of Object . entries ( modelDef . fields ) ) {
893899 if ( fieldDef . relation ) continue ;
894- if ( fieldDef . type !== 'Int' && fieldDef . type !== 'Float' && fieldDef . type !== 'BigInt' && fieldDef . type !== 'Decimal' )
900+ if (
901+ fieldDef . type !== 'Int' &&
902+ fieldDef . type !== 'Float' &&
903+ fieldDef . type !== 'BigInt' &&
904+ fieldDef . type !== 'Decimal'
905+ )
895906 continue ;
896907 // sum preserves the original type
897908 properties [ fieldName ] = { oneOf : [ { type : 'null' as const } , this . typeToSchema ( fieldDef . type ) ] } ;
@@ -902,7 +913,10 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
902913 private buildAggregateSchema ( modelName : string , modelDef : ModelDef ) : SchemaObject {
903914 const properties : Record < string , SchemaObject > = {
904915 _count : {
905- oneOf : [ { type : 'null' as const } , { $ref : `#/components/schemas/${ modelName } CountAggregateOutputType` } ] ,
916+ oneOf : [
917+ { type : 'null' as const } ,
918+ { $ref : `#/components/schemas/${ modelName } CountAggregateOutputType` } ,
919+ ] ,
906920 } ,
907921 _min : {
908922 oneOf : [ { type : 'null' as const } , { $ref : `#/components/schemas/${ modelName } MinAggregateOutputType` } ] ,
@@ -1017,13 +1031,54 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
10171031 return baseSchema ;
10181032 }
10191033
1020- private buildFieldFilterSchema ( modelName : string , fieldName : string , fieldDef : FieldDef ) : SchemaObject | undefined {
1021- const baseSchema = this . typeToSchema ( fieldDef . type ) ;
1034+ /**
1035+ * Generates shared filter schemas for all field types used across models.
1036+ */
1037+ private generateFilterSchemas ( schemas : Record < string , SchemaObject | ReferenceObject > ) : void {
1038+ const filters = new Map < string , { type : string ; array : boolean } > ( ) ;
1039+
1040+ for ( const modelName of getIncludedModels ( this . schema , this . queryOptions ) ) {
1041+ const modelDef = this . schema . models [ modelName ] ! ;
1042+ for ( const [ , fieldDef ] of Object . entries ( modelDef . fields ) ) {
1043+ if ( fieldDef . relation ) continue ;
1044+ const name = this . filterSchemaName ( fieldDef . type , ! ! fieldDef . array ) ;
1045+ if ( ! filters . has ( name ) ) {
1046+ filters . set ( name , { type : fieldDef . type , array : ! ! fieldDef . array } ) ;
1047+ }
1048+ }
1049+ }
1050+
1051+ for ( const [ name , { type, array } ] of filters ) {
1052+ schemas [ name ] = this . buildFilterSchema ( type , array ) ! ;
1053+ }
1054+ }
1055+
1056+ /**
1057+ * Returns the schema name for a shared filter (e.g. "_StringFilter", "_IntListFilter").
1058+ */
1059+ private filterSchemaName ( type : string , array : boolean ) : string {
1060+ return array ? `_${ type } ListFilter` : `_${ type } Filter` ;
1061+ }
1062+
1063+ /**
1064+ * Builds a filter schema for a given field type. When `fieldContext` is provided,
1065+ * only filter kinds that pass `isFilterKindIncluded` are included; otherwise all
1066+ * applicable filter kinds are included (used for shared filter schemas).
1067+ */
1068+ private buildFilterSchema (
1069+ type : string ,
1070+ array : boolean ,
1071+ fieldContext ?: { modelName : string ; fieldName : string } ,
1072+ ) : SchemaObject | undefined {
1073+ const includeKind = ( kind : string ) =>
1074+ ! fieldContext ||
1075+ isFilterKindIncluded ( fieldContext . modelName , fieldContext . fieldName , kind , this . queryOptions ) ;
1076+
1077+ const baseSchema = this . typeToSchema ( type ) ;
10221078 const filterProps : Record < string , SchemaObject | ReferenceObject > = { } ;
1023- const type = fieldDef . type ;
10241079
10251080 // Equality operators
1026- if ( isFilterKindIncluded ( modelName , fieldName , 'Equality' , this . queryOptions ) ) {
1081+ if ( includeKind ( 'Equality' ) ) {
10271082 filterProps [ 'equals' ] = baseSchema ;
10281083 filterProps [ 'not' ] = baseSchema ;
10291084 filterProps [ 'in' ] = { type : 'array' , items : baseSchema } ;
@@ -1033,7 +1088,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
10331088 // Range operators (numeric/datetime types)
10341089 if (
10351090 ( type === 'Int' || type === 'Float' || type === 'BigInt' || type === 'Decimal' || type === 'DateTime' ) &&
1036- isFilterKindIncluded ( modelName , fieldName , 'Range' , this . queryOptions )
1091+ includeKind ( 'Range' )
10371092 ) {
10381093 filterProps [ 'lt' ] = baseSchema ;
10391094 filterProps [ 'lte' ] = baseSchema ;
@@ -1042,15 +1097,15 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
10421097 }
10431098
10441099 // Like operators (String type)
1045- if ( type === 'String' && isFilterKindIncluded ( modelName , fieldName , 'Like' , this . queryOptions ) ) {
1100+ if ( type === 'String' && includeKind ( 'Like' ) ) {
10461101 filterProps [ 'contains' ] = { type : 'string' } ;
10471102 filterProps [ 'startsWith' ] = { type : 'string' } ;
10481103 filterProps [ 'endsWith' ] = { type : 'string' } ;
10491104 filterProps [ 'mode' ] = { type : 'string' , enum : [ 'default' , 'insensitive' ] } ;
10501105 }
10511106
10521107 // List operators (array fields)
1053- if ( fieldDef . array && isFilterKindIncluded ( modelName , fieldName , 'List' , this . queryOptions ) ) {
1108+ if ( array && includeKind ( 'List' ) ) {
10541109 filterProps [ 'has' ] = baseSchema ;
10551110 filterProps [ 'hasEvery' ] = { type : 'array' , items : baseSchema } ;
10561111 filterProps [ 'hasSome' ] = { type : 'array' , items : baseSchema } ;
@@ -1062,13 +1117,43 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
10621117 const filterObject : SchemaObject = { type : 'object' , properties : filterProps } ;
10631118
10641119 // If Equality is included, allow shorthand (direct value) via oneOf
1065- if ( isFilterKindIncluded ( modelName , fieldName , 'Equality' , this . queryOptions ) ) {
1120+ if ( includeKind ( 'Equality' ) ) {
10661121 return { oneOf : [ baseSchema , filterObject ] } ;
10671122 }
10681123
10691124 return filterObject ;
10701125 }
10711126
1127+ /**
1128+ * Returns true if no field-level filter slicing is configured for this model/field.
1129+ */
1130+ private hasDefaultFilters ( modelName : string , fieldName : string ) : boolean {
1131+ const slicing = this . queryOptions ?. slicing ;
1132+ if ( ! slicing ?. models ) return true ;
1133+
1134+ const modelKey = lowerCaseFirst ( modelName ) ;
1135+ const modelSlicing = ( slicing . models as Record < string , any > ) [ modelKey ] ?? ( slicing . models as any ) . $all ;
1136+ if ( ! modelSlicing ?. fields ) return true ;
1137+
1138+ const fieldSlicing = modelSlicing . fields [ fieldName ] ?? modelSlicing . fields . $all ;
1139+ return ! fieldSlicing ;
1140+ }
1141+
1142+ private buildFieldFilterSchema (
1143+ modelName : string ,
1144+ fieldName : string ,
1145+ fieldDef : FieldDef ,
1146+ ) : SchemaObject | ReferenceObject | undefined {
1147+ // If no slicing customization, reference the shared filter schema
1148+ if ( this . hasDefaultFilters ( modelName , fieldName ) ) {
1149+ const name = this . filterSchemaName ( fieldDef . type , ! ! fieldDef . array ) ;
1150+ return { $ref : `#/components/schemas/${ name } ` } ;
1151+ }
1152+
1153+ // Slicing is active — build inline filter with only included filter kinds
1154+ return this . buildFilterSchema ( fieldDef . type , ! ! fieldDef . array , { modelName, fieldName } ) ;
1155+ }
1156+
10721157 private typeToSchema ( type : string ) : SchemaObject | ReferenceObject {
10731158 switch ( type ) {
10741159 case 'String' :
0 commit comments