import { BaseModelField } from '../../ModelField'; import { ModelRelationshipField, ModelRelationshipFieldParamShape, } from '../../ModelRelationshipField'; import { EnumType } from '../../EnumType'; import { CustomType } from '../../CustomType'; import { RefType, RefTypeParamShape } from '../../RefType'; import { ResolveRef } from './ResolveRef'; import { LazyLoader } from '../../runtime'; import type { ModelTypeParamShape } from '../../ModelType'; type ExtendsNever = [T] extends [never] ? true : false; /** * Takes a `ReturnType` and turns it into a client-consumable type. Fields * definitions (e.g., `a.string()`) are turned into the client facing types (e.g., `string`), * `a.ref()` fields will be resolved, and relationships (e.g., `a.belongsTo()`) will be * turned into `LazyLoader` fields (e.g., `post.comments({...})`). * * The first type parameter (`Bag`) should always just be the top-level `ClientSchema` that * references and related model definitions can be resolved against. */ export type ResolveFields, T> = ShallowPretty< { [K in keyof T as IsRequired extends true ? K : never]: ResolveIndividualField; } & { [K in keyof T as IsRequired extends true ? never : K]+?: ResolveIndividualField; } >; export type FlatResolveFields< Bag extends Record, T, FlatModelName extends keyof Bag & string = never, > = ShallowPretty<{ [K in keyof T]: ResolveIndividualField; }>; // TODO: Remove ShallowPretty from this layer of resolution. Re-incorporate prettification // down the line *as-needed*. Performing this *here* is somehow essential to getting 2 unit // tests to pass, but hurts performance significantly. E.g., p50/operations/p50-prod-CRUDL.bench.ts // goes from `783705` to `1046408`. type ShallowPretty = { [K in keyof T]: T[K]; }; export type ResolveIndividualField< Bag extends Record, T, FlatModelName extends keyof Bag & string = never, > = T extends BaseModelField ? FieldShape : T extends RefType ? ResolveRef : T extends ModelRelationshipField ? ResolveRelationship : T extends CustomType ? ResolveFields | null : T extends EnumType ? values[number] | null : never; /** * This mapped type eliminates redundant recursive types when * generating the ['__meta']['flatModel'] type that serves as the * basis for custom selection set path type generation * * It drops belongsTo relational fields that match the source model * * For example, assuming the typical Post->Comment bi-directional hasMany relationship, * The generated structure will be * { * id: string; * title: string; * createdAt: string; * updatedAt: string; * comments: { * id: string; * createdAt: string; * updatedAt: string; * content: string; * postId: string; * ~~post~~ is dropped because data would be the same as top level object * }[] * } * */ type ShortCircuitBiDirectionalRelationship< Model extends Record, ParentModelName extends string, Raw extends ModelTypeParamShape['fields'], > = { [Field in keyof Model as Field extends keyof Raw ? Raw[Field] extends ModelRelationshipField< infer RelationshipShape, any, any, any > ? RelationshipShape['relationshipType'] extends 'belongsTo' ? RelationshipShape['relatedModel'] extends ParentModelName ? never : Field : Field : Field : Field]: Model[Field]; }; type ResolveRelationship< Bag extends Record, RelationshipShape extends ModelRelationshipFieldParamShape, ParentModelName extends keyof Bag & string = never, > = ExtendsNever extends true ? DependentLazyLoaderOpIsAvailable extends true ? LazyLoader< RelationshipShape['valueRequired'] extends true ? Bag[RelationshipShape['relatedModel']]['type'] : Bag[RelationshipShape['relatedModel']]['type'] | null, RelationshipShape['array'] > : never : // Array-ing inline here vs. (inside of ShortCircuitBiDirectionalRelationship or in a separate conditional type) is significantly more performant RelationshipShape['array'] extends true ? Array< ShortCircuitBiDirectionalRelationship< Bag[RelationshipShape['relatedModel']]['__meta']['flatModel'], ParentModelName, Bag[RelationshipShape['relatedModel']]['__meta']['rawType']['fields'] > > : ShortCircuitBiDirectionalRelationship< Bag[RelationshipShape['relatedModel']]['__meta']['flatModel'], ParentModelName, Bag[RelationshipShape['relatedModel']]['__meta']['rawType']['fields'] >; type DependentLazyLoaderOpIsAvailable< Bag extends Record, RelationshipShape extends ModelRelationshipFieldParamShape, > = RelationshipShape['relationshipType'] extends 'hasOne' | 'hasMany' ? // hasOne and hasMany depend on `list` 'list' extends keyof Bag[RelationshipShape['relatedModel']]['__meta']['disabledOperations'] ? false : true : // the relationship is a belongsTo, which depends on `get` 'get' extends keyof Bag[RelationshipShape['relatedModel']]['__meta']['disabledOperations'] ? false : true; type IsRequired = T extends BaseModelField ? null extends FieldShape ? false : true : T extends RefType ? IsRefRequired : T extends ModelRelationshipField ? true : T extends CustomType | EnumType ? false : never; type IsRefRequired = T['array'] extends true ? T['arrayRequired'] : T['valueRequired'];