import type { MODEL_DEFINITION } from './define-model'; import type { Validator } from '@stacksjs/validation'; /** * Extract the raw ModelDefinition from a defineModel() return value. * Uses the getDefinition() accessor that defineModel() provides. */ export type Def = T extends { readonly [MODEL_DEFINITION]: infer TDefinition } ? TDefinition : T extends { getDefinition: () => infer TDefinition } ? TDefinition : never; /** * Extract foreign key columns from belongsTo relations. * e.g., belongsTo: ['Customer', 'Coupon'] → { customer_id: number, coupon_id: number } */ declare type SnakeCase = S extends `${infer TFirst}${infer TRest}` ? TFirst extends Lowercase ? `${TFirst}${SnakeCase}` : `_${Lowercase}${SnakeCase}` : S; declare type BelongsToForeignKeyOf = TEntry extends string ? `${SnakeCase>}_id` : TEntry extends { readonly foreignKey: infer TForeignKey extends string } ? TForeignKey : TEntry extends { readonly model: infer TModel extends string } ? `${SnakeCase>}_id` : never; declare type BelongsToForeignKeyNames = TDef extends { readonly belongsTo: infer TRelations } ? TRelations extends readonly (infer TEntry)[] ? BelongsToForeignKeyOf : TRelations extends Readonly> ? BelongsToForeignKeyOf : never : never; export type BelongsToForeignKeys = { [TKey in BelongsToForeignKeyNames]: number } declare type DefinitionAttributes = TDef extends { readonly attributes: infer TAttributes } ? TAttributes : never; declare type AttributeKeys = keyof DefinitionAttributes & string; declare type PrimitiveType = TType extends 'string' ? string : TType extends 'number' ? number : TType extends 'boolean' ? boolean : TType extends 'date' ? Date : TType extends 'json' ? Record : TType extends readonly (infer TValue)[] ? TValue : TType extends Validator ? TValue : unknown; declare type WidenDefault = TValue extends string ? string : TValue extends number ? number : TValue extends boolean ? boolean : TValue; declare type AttributeValue = TAttribute extends { readonly type: infer TType } ? PrimitiveType : TAttribute extends { readonly factory: (...args: never[]) => infer TValue } ? TValue : TAttribute extends { readonly validation: { readonly rule: infer TRule } } ? TRule extends Validator ? TValue : unknown : TAttribute extends { readonly default: infer TDefault } ? WidenDefault : unknown; /** * An attribute is nullable when it says so outright, or when `required: false` * marks it optional. The latter is what makes the generated column nullable, so * the row type has to agree with the schema the migration emits. */ declare type IsNullableAttribute = TAttribute extends { readonly nullable: true } ? true : TAttribute extends { readonly required: false } ? true : false; declare type DeclaredAttributes = { [TKey in AttributeKeys]: IsNullableAttribute[TKey]> extends true ? AttributeValue[TKey]> | null : AttributeValue[TKey]> } declare type SnakeCaseAttributes = { [TKey in AttributeKeys as SnakeCase]: DeclaredAttributes[TKey] } declare type PrimaryKey = TDef extends { readonly primaryKey: infer TKey extends string } ? TKey : 'id'; declare type TraitFields = { [TKey in PrimaryKey]: number } & (TDef extends { readonly traits: { readonly useUuid: true } } ? { uuid: string } : {}) & (TDef extends { readonly traits: { readonly useTimestamps: true } } ? { created_at: string updated_at: string | null } : {}) & (TDef extends { readonly traits: { readonly timestampable: true | object } } ? { created_at: string updated_at: string | null } : {}) & (TDef extends { readonly traits: { readonly useSoftDeletes: true } } ? { deleted_at: string | null } : {}) & (TDef extends { readonly traits: { readonly softDeletable: true | object } } ? { deleted_at: string | null } : {}) & (TDef extends { readonly traits: { readonly useAuth: true | object } } ? { two_factor_secret: string | null public_key: string | null } : {}) & (TDef extends { readonly traits: { readonly billable: true } } ? { stripe_id: string | null } : {}); declare type InferredModelRow = DeclaredAttributes & SnakeCaseAttributes & Omit, AttributeKeys | SnakeCase>> & Omit, AttributeKeys | SnakeCase>>; declare type FillableKeys = { [TKey in AttributeKeys]: DefinitionAttributes[TKey] extends { readonly fillable: true } ? TKey : never }[AttributeKeys]; declare type OptionalFillableKeys = { [TKey in FillableKeys]: DefinitionAttributes[TKey] extends { readonly nullable: true } | { readonly default: unknown } ? TKey : never }[FillableKeys]; /** * Full database row type: model attributes + system fields (id, uuid, timestamps) + FK columns. * * @example * import type { ModelRow } from '@stacksjs/orm' * import type Post from '../models/Post' * type PostJsonResponse = ModelRow */ export type ModelRow = InferredModelRow>; /** * Same as {@link ModelRow} but with every field optional. Useful for * partial-projection reads (`select('id', 'name')`) and test fixtures * that don't bother populating every column. */ export type ModelRowLoose = Partial>; /** * Insertable data type: model attributes + FK columns, all optional. * * @example * import type { NewModelData } from '@stacksjs/orm' * import type Post from '../models/Post' * type NewPost = NewModelData */ export type NewModelData = Partial>; /** * Strict insertable shape: only attributes marked `fillable: true` in * the model definition (plus belongsTo foreign keys), partial because * many fillable columns have factory defaults at the DB layer. * * Use this when you want compile-time enforcement that consumers * can't pass non-fillable fields to `create()` / `insert()`. * {@link NewModelData} is the looser sibling that allows any attribute. */ export type ModelCreateData = Partial & BelongsToForeignKeys>>; /** Loose variant of {@link ModelCreateData} — same shape as {@link NewModelData}, aliased for naming-parity with the row types. */ export type ModelCreateDataLoose = NewModelData; /** * Updateable data type: model attributes + FK columns, all optional. * * @example * import type { UpdateModelData } from '@stacksjs/orm' * import type Post from '../models/Post' * type PostUpdate = UpdateModelData */ export type UpdateModelData = Partial>; /** Attribute values accepted by mass-assignment writes. */ export type InferFillableAttributes = { [TKey in Exclude>, OptionalFillableKeys>>]: DeclaredAttributes>[TKey] } & { [TKey in OptionalFillableKeys>]?: DeclaredAttributes>[TKey] } /** * Every valid column name for the model (attributes + system fields * added by traits like `id`, `uuid`, `created_at`). Useful for * constraining query builders that accept a `column` parameter. */ export type InferColumnNames = AttributeKeys> | SnakeCase>> | PrimaryKey> | BelongsToForeignKeyNames> | keyof TraitFields>; /** * Attribute keys whose `type` is declared as `'number'` in the model * definition. Used to constrain aggregate methods (`sum`, `avg`, * `min`, `max`) so they can't be called against string columns. * * Models that don't declare an explicit `type` per attribute (the * common case — most validation rules are inferred from * `schema.number()` chains, not declared on `type`) fall back to * `AttributeKeys>` here. Tighten by declaring `type: 'number'` * on the attribute spec when narrowing matters. */ export type InferNumericColumns = { [TKey in AttributeKeys>]: AttributeValue>[TKey]> extends number ? TKey : never }[AttributeKeys>];