/** * @sylphx/lens-core - Type Inference Utilities * * Powerful type inference from schema definitions. * Enables full end-to-end type safety. */ import type { ArrayType, BelongsToType, EntityDefinition, EnumType, FieldDefinition, FieldType, HasManyType, HasOneType, ObjectType, SchemaDefinition, } from "./types.js"; // ============================================================================= // Scalar Type Inference // ============================================================================= /** Type mapping from field _type to TypeScript type */ type ScalarTypeMap = { id: string; string: string; int: number; float: number; decimal: number; boolean: boolean; datetime: Date; date: Date; bigint: bigint; bytes: Uint8Array; json: unknown; }; /** Infer base TypeScript type from field's _type property */ type InferBaseType = T extends { _type: infer Type extends keyof ScalarTypeMap; } ? ScalarTypeMap[Type] : T extends EnumType ? V[number] : T extends ObjectType ? O : T extends ArrayType ? I[] : never; /** Apply optional/nullable modifiers to base type */ type ApplyModifiers = T extends { _optional: true } ? Base | undefined : T extends { _nullable: true } ? Base | null : Base; /** Infer TypeScript type from a scalar field type (handles optional/nullable) */ export type InferScalar = ApplyModifiers, T>; // ============================================================================= // Relation Type Inference // ============================================================================= /** Infer the target entity name from a relation type */ export type InferRelationTarget = T extends { target: infer Target } ? Target : never; /** Check if a field is a relation */ export type IsRelation = T extends { _relationKind: "hasOne" | "hasMany" | "belongsTo" } ? true : false; /** Check if a field is hasMany */ export type IsHasMany = T extends { _relationKind: "hasMany" } ? true : false; /** Check if a field is hasOne */ export type IsHasOne = T extends { _relationKind: "hasOne" } ? true : false; /** Check if a field is belongsTo */ export type IsBelongsTo = T extends { _relationKind: "belongsTo" } ? true : false; // ============================================================================= // Field Categorization // ============================================================================= /** Extract scalar field keys from entity definition */ export type ScalarFields = { [K in keyof E]: IsRelation extends true ? never : K; }[keyof E]; /** Extract relation field keys from entity definition */ export type RelationFields = { [K in keyof E]: IsRelation extends true ? K : never; }[keyof E]; // ============================================================================= // Entity Type Inference // ============================================================================= /** Infer full entity type from definition, resolving relations within schema */ export type InferEntity = { // Scalar fields [K in ScalarFields]: InferFieldType; } & { // Relation fields [K in RelationFields]: InferRelationType; }; /** Infer field type (scalar or relation) */ export type InferFieldType< F extends FieldDefinition, S extends SchemaDefinition, > = IsRelation extends true ? InferRelationType : InferScalarWithNullable; /** Infer scalar type with nullable support */ export type InferScalarWithNullable = F extends { _nullable: true } ? InferScalar | null : InferScalar; /** Infer relation type, resolving to target entity if schema provided */ export type InferRelationType = [S] extends [ never, ] ? // No schema context - return placeholder F extends HasManyType ? Array<{ __entity: Target }> : F extends HasOneType ? { __entity: Target } | null : F extends BelongsToType ? { __entity: Target } : never : // With schema context - resolve to actual entity type F extends HasManyType ? Target extends keyof S ? Array> : never : F extends HasOneType ? Target extends keyof S ? InferEntity | null : never : F extends BelongsToType ? Target extends keyof S ? InferEntity : never : never; // ============================================================================= // Field Selection Type Inference // ============================================================================= /** Field arguments type (for computed/relation fields with args) */ export type FieldArgs = Record; /** Scalar field selection options (for fields with arguments) */ export type ScalarSelectOptions = { /** Field arguments (GraphQL-style) */ args?: FieldArgs; }; /** Nested relation selection options */ export type RelationSelectOptions< Target extends string, S extends SchemaDefinition, > = Target extends keyof S ? { /** Field arguments (GraphQL-style) */ args?: FieldArgs; /** Nested field selection */ select?: Select; /** Limit results (for hasMany) */ take?: number; /** Skip results (for hasMany) */ skip?: number; } : never; /** Selection object type with type-safe nested relations */ export type Select = { [K in keyof E]?: IsRelation extends true ? // For relations, allow nested selection or true true | RelationSelectOptions & string, S> : // For scalars, allow true or options with args true | ScalarSelectOptions; }; /** Infer selected type from selection */ export type InferSelected< E extends EntityDefinition, Sel extends Select, S extends SchemaDefinition = never, > = { [K in keyof Sel & keyof E]: Sel[K] extends true ? // Direct selection (true) InferFieldType : Sel[K] extends { select: infer NestedSel } ? // Nested selection with select property E[K] extends HasManyType ? Target extends keyof S ? NestedSel extends Select ? Array> : never : never : E[K] extends HasOneType ? Target extends keyof S ? NestedSel extends Select ? InferSelected | null : never : never : E[K] extends BelongsToType ? Target extends keyof S ? NestedSel extends Select ? InferSelected : never : never : never : Sel[K] extends { args: FieldArgs } ? // Selection with args but no nested select - return field type InferFieldType : // Relation without nested select returns full entity InferFieldType; }; // ============================================================================= // Schema Type Inference // ============================================================================= /** Infer all entity types from schema */ export type InferSchemaEntities = { [K in keyof S]: InferEntity; }; /** Get entity names from schema */ export type EntityNames = keyof S & string; /** Get entity type by name */ export type EntityType = InferEntity; // ============================================================================= // Input Types (for mutations) // ============================================================================= /** Check if a field is nullable or has a default */ type IsOptionalField = F extends { _nullable: true } ? true : F extends { _default: unknown } ? true : false; /** Extract required scalar fields (not id, not nullable, no default) */ type RequiredScalarFields = { [K in ScalarFields as K extends "id" ? never : IsOptionalField extends true ? never : K]: InferScalar; }; /** Extract optional scalar fields (nullable or has default) */ type OptionalScalarFields = { [K in ScalarFields as K extends "id" ? never : IsOptionalField extends true ? K : never]?: InferScalarWithNullable; }; /** Create input type with proper optional handling */ export type CreateInput< E extends EntityDefinition, _S extends SchemaDefinition = never, > = RequiredScalarFields & OptionalScalarFields & { [K in RelationFields]?: E[K] extends BelongsToType ? string // Foreign key ID : never; }; /** Update input type (id required, all else optional) */ export type UpdateInput = { id: string; } & Partial>; /** Delete input type */ export type DeleteInput = { id: string; }; // ============================================================================= // Utility Types // ============================================================================= /** Make specific keys required */ export type RequireKeys = T & { [P in K]-?: T[P] }; /** Make specific keys optional */ export type OptionalKeys = Omit & { [P in K]?: T[P] }; /** Deep partial type */ export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; } : T;