/** * @sylphx/lens-core - Field Type Builders * * Standalone functions for defining field types in models and return types. * No `t.` prefix needed - import directly. * * @example * ```typescript * import { lens, id, string, int, list, nullable } from '@sylphx/lens-core' * * const { model, query } = lens() * * // Model fields * const User = model('User', { * id: id(), * name: string(), * bio: nullable(string()), * tags: list(string()), * posts: list(() => Post), * }) * * // Return types (same list/nullable!) * const getUsers = query() * .returns(list(User)) * .resolve(...) * ``` */ import type { ModelDef } from "./model.js"; import { BigIntType, BooleanType, BytesType, DateTimeType, DecimalType, EnumType, type FieldType, FloatType, IdType, IntType, JsonType, ObjectType, ScalarType, StringType, TimestampType, } from "./types.js"; // ============================================================================= // Scalar Field Builders // ============================================================================= /** ID field (primary key, string type) */ export function id(): IdType { return new IdType(); } /** String field */ export function string(): StringType { return new StringType(); } /** Integer field */ export function int(): IntType { return new IntType(); } /** Float field */ export function float(): FloatType { return new FloatType(); } /** Boolean field */ export function boolean(): BooleanType { return new BooleanType(); } /** DateTime field (serialized as ISO string) */ export function datetime(): DateTimeType { return new DateTimeType(); } /** Timestamp field (Unix timestamp in milliseconds) */ export function timestamp(): TimestampType { return new TimestampType(); } /** Decimal field (serialized as string for precision) */ export function decimal(): DecimalType { return new DecimalType(); } /** BigInt field (serialized as string for precision) */ export function bigint(): BigIntType { return new BigIntType(); } /** Binary data field (serialized as base64) */ export function bytes(): BytesType { return new BytesType(); } /** JSON field (schemaless, typed as unknown) */ export function json(): JsonType { return new JsonType(); } /** Enum field with specific values */ export function enumType(values: T): EnumType { return new EnumType(values); } /** Typed object field */ export function object(): ObjectType { return new ObjectType(); } /** * Custom scalar type with user-defined serialization. * * @example * ```typescript * const User = model('User', { * id: id(), * location: scalar('Point', { * serialize: (p) => ({ lat: p.lat, lng: p.lng }), * deserialize: (data) => new Point(data.lat, data.lng), * }), * }) * ``` */ export function scalar( name: string, options: { serialize: (value: T) => SerializedT; deserialize: (value: SerializedT) => T; validate?: (value: unknown) => boolean; }, ): ScalarType { return new ScalarType({ name, ...options }); } // ============================================================================= // Model Reference Types // ============================================================================= /** Model-like type that can be referenced */ type ModelLike = ModelDef; /** Lazy model reference (for circular dependencies) */ type LazyModelRef = () => T; // ============================================================================= // Field Definition Type // ============================================================================= /** * Valid field definition types: * - Scalar types: id(), string(), int(), etc. * - Direct model reference: Profile * - Lazy model reference: () => Profile * - List: list(string()), list(Profile), list(() => Post) * - Nullable: nullable(string()), nullable(Profile) */ export type FieldDef = | FieldType | ModelLike | LazyModelRef | ListDef | NullableDef; // ============================================================================= // Unified Symbols (shared between field defs and return types) // ============================================================================= /** Symbol to identify list types */ export const LIST_SYMBOL: unique symbol = Symbol("lens:list"); /** Symbol to identify nullable types */ export const NULLABLE_SYMBOL: unique symbol = Symbol("lens:nullable"); // ============================================================================= // List Type (unified for fields and return types) // ============================================================================= /** * List/array wrapper. * Works for both model fields and return types. */ export interface ListDef { [LIST_SYMBOL]: true; _inner: T; } /** Check if value is a ListDef */ export function isListDef(value: unknown): value is ListDef { return typeof value === "object" && value !== null && LIST_SYMBOL in value; } /** * List/array type. * Works for both model fields and return types. * * @example * ```typescript * // Model fields * tags: list(string()) // string[] * posts: list(Post) // Post[] * posts: list(() => Post) // Post[] (lazy ref for circular deps) * * // Return types * .returns(list(User)) // User[] * ``` */ export function list>(inner: T): ListDef; export function list(inner: T): ListDef; export function list(inner: LazyModelRef): ListDef>; export function list(inner: T): ListDef { return { [LIST_SYMBOL]: true, _inner: inner, }; } // ============================================================================= // Nullable Type (unified for fields and return types) // ============================================================================= /** * Nullable wrapper. * Works for both model fields and return types. */ export interface NullableDef { [NULLABLE_SYMBOL]: true; _inner: T; } /** Check if value is a NullableDef */ export function isNullableDef(value: unknown): value is NullableDef { return typeof value === "object" && value !== null && NULLABLE_SYMBOL in value; } /** * Nullable type. * Works for both model fields and return types. * * @example * ```typescript * // Model fields * bio: nullable(string()) // string | null * profile: nullable(Profile) // Profile | null * tags: nullable(list(string())) // string[] | null * * // Return types * .returns(nullable(User)) // User | null * ``` */ export function nullable>(inner: T): NullableDef; export function nullable(inner: T): NullableDef; export function nullable(inner: LazyModelRef): NullableDef>; export function nullable(inner: ListDef): NullableDef>; export function nullable(inner: T): NullableDef { return { [NULLABLE_SYMBOL]: true, _inner: inner, }; } // ============================================================================= // Utility Types // ============================================================================= /** * Unwrap a wrapped type to get the inner model. */ export type UnwrapType = T extends NullableDef ? UnwrapType : T extends ListDef ? UnwrapType : T; /** * Check if a type is nullable. */ export type IsNullable = T extends NullableDef ? true : false; /** * Check if a type is a list. */ export type IsList = T extends NullableDef ? IsList : T extends ListDef ? true : false; /** * Check if inner type is a lazy reference (function). */ export function isLazyRef(value: unknown): value is () => unknown { return typeof value === "function"; } // ============================================================================= // Type inference helpers // ============================================================================= /** Infer the TypeScript type from a FieldDef */ export type InferFieldDefType = T extends ListDef ? InferFieldDefType[] : T extends NullableDef ? InferFieldDefType | null : T extends FieldType ? V : T extends ModelLike ? T : T extends LazyModelRef ? M : never; // ============================================================================= // Processing helpers (for internal use) // ============================================================================= /** * Process a field definition to extract metadata. * Used internally by model() to understand field structure. */ export function processFieldDef(def: FieldDef): { isNullable: boolean; isList: boolean; isLazy: boolean; innerType: unknown; } { let current: unknown = def; let isNullable = false; let isList = false; // Unwrap nullable if (isNullableDef(current)) { isNullable = true; current = current._inner; } // Unwrap list if (isListDef(current)) { isList = true; current = current._inner; } // Check if lazy reference const isLazy = typeof current === "function"; return { isNullable, isList, isLazy, innerType: current, }; }