import type { LocalizedString } from "./i18n"; // --------------------------------------------------------------------------- // Type unions // --------------------------------------------------------------------------- /** Built-in property types supported by Omnify. */ export type BuiltInPropertyType = | "String" | "TinyInt" | "Int" | "BigInt" | "Float" | "Decimal" | "Boolean" | "Text" | "MediumText" | "LongText" | "Date" | "Time" | "Timestamp" | "DateTime" | "Json" | "Email" | "Password" | "File" | "Point" | "Coordinates" | "Enum" | "EnumRef" | "Association" | "Uuid"; /** Relationship cardinality types. */ export type AssociationRelation = | "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany" | "MorphTo" | "MorphOne" | "MorphMany" | "MorphToMany" | "MorphedByMany"; /** Referential action for foreign key constraints. */ export type ReferentialAction = | "CASCADE" | "SET NULL" | "SET DEFAULT" | "RESTRICT" | "NO ACTION"; /** Schema kind determines how a schema is processed. */ export type SchemaKind = "object" | "enum" | "partial" | "extend" | "pivot"; /** Primary key column type. */ export type IdType = "BigInt" | "Int" | "Uuid" | "Ulid" | "String"; /** Database index type. */ export type IndexType = "btree" | "hash" | "fulltext" | "spatial" | "gin" | "gist"; // --------------------------------------------------------------------------- // Interfaces // --------------------------------------------------------------------------- /** An enum value with optional label and extra properties. */ export interface InlineEnumValue { /** Enum value stored in database. */ value: string; /** Human-readable label (supports multi-language). */ label?: LocalizedString; /** Additional metadata. */ extra?: Record; } /** Per-field overrides for compound types (e.g. JapaneseName sub-fields). */ export interface CompoundFieldOverride { nullable?: boolean; hidden?: boolean; fillable?: boolean; /** Override length for string fields. */ length?: number; displayName?: LocalizedString; placeholder?: LocalizedString; } /** * Field definition on a pivot table. * * @deprecated Removed in v5.0.0 — `pivotFields:` on a M2M association * is rejected by validation. Use an explicit `kind: pivot` schema * (auto-created by `omnify generate`) and put extra columns under its * `properties:` block. The interface is retained for IDE auto-complete * compatibility on legacy YAML so users see the deprecation in their * editor, then get the actionable hard error from the CLI. */ export interface PivotFieldDefinition { type: BuiltInPropertyType; nullable?: boolean; default?: unknown; length?: number; unsigned?: boolean; /** Inline enum values or reference. */ enum?: (string | InlineEnumValue)[] | string; displayName?: LocalizedString; } /** * Application-level validation rules. Single source of truth for both Laravel validation and Zod schemas. * Rules are **additive** — generators infer base rules from type, `rules:` adds/overrides on top. * * **Mutually exclusive groups** (using more than one triggers a validation error): * - `lowercase` + `uppercase` * - `alpha` / `alphaNum` / `alphaDash` / `numeric` (pick one) * - `url` + `uuid` * - `min` + `gt` (both define lower bound) * - `max` + `lt` (both define upper bound) * - `between` + any of `min`/`max`/`gt`/`lt` * - `digits` + `digitsBetween` * * **Redundancy warnings**: * - `ip` + `ipv4` or `ip` + `ipv6` → ipv4/ipv6 is redundant * * **Cross-field**: `maxLength` vs property `length` — if both set and differ, omnify warns. */ export interface ValidationRules { /** [All types] Override required/nullable inference. */ required?: boolean; // ── String rules (apply to: String, Email, Password, Text, MediumText, LongText, Uuid) ── /** Minimum string length. Must be >= 0. Must be <= maxLength when both set. */ minLength?: number; /** Maximum string length. Must be >= 1. Overrides length-based max. */ maxLength?: number; /** Must be valid URL. Mutually exclusive with uuid. */ url?: boolean; /** Must be valid UUID. Mutually exclusive with url. */ uuid?: boolean; /** Must be valid IP (v4 or v6). Makes ipv4/ipv6 redundant. */ ip?: boolean; /** Must be valid IPv4. Redundant when ip is set. */ ipv4?: boolean; /** Must be valid IPv6. Redundant when ip is set. */ ipv6?: boolean; /** Only letters (a-z, A-Z). Mutually exclusive with alphaNum, alphaDash, numeric. */ alpha?: boolean; /** Only letters and numbers. Mutually exclusive with alpha, alphaDash, numeric. */ alphaNum?: boolean; /** Letters, numbers, dash, underscore. Mutually exclusive with alpha, alphaNum, numeric. */ alphaDash?: boolean; /** Only digits (0-9) as string. Mutually exclusive with alpha, alphaNum, alphaDash. */ numeric?: boolean; /** Exactly N digits. Must be >= 1. Mutually exclusive with digitsBetween. */ digits?: number; /** Digit count between [min, max]. Both >= 1, min <= max. Mutually exclusive with digits. */ digitsBetween?: [number, number]; /** Must start with prefix(es). */ startsWith?: string | string[]; /** Must end with suffix(es). */ endsWith?: string | string[]; /** Must be entirely lowercase. Mutually exclusive with uppercase. */ lowercase?: boolean; /** Must be entirely uppercase. Mutually exclusive with lowercase. */ uppercase?: boolean; // ── Numeric rules (apply to: TinyInt, Int, BigInt, Float, Decimal) ── /** Minimum value (inclusive). Must be <= max. Mutually exclusive with gt. Redundant with between. */ min?: number; /** Maximum value (inclusive). Must be >= min. Mutually exclusive with lt. Redundant with between. */ max?: number; /** Value between [min, max] inclusive. min <= max. Do not combine with min/max/gt/lt. */ between?: [number, number]; /** Greater than (exclusive). Must be < lt. Mutually exclusive with min. Redundant with between. */ gt?: number; /** Less than (exclusive). Must be > gt. Mutually exclusive with max. Redundant with between. */ lt?: number; /** Must be a multiple of value. Must be > 0. */ multipleOf?: number; // ── Array rules (apply to: Json) ── /** Minimum items. Must be >= 0. Must be <= arrayMax when both set. */ arrayMin?: number; /** Maximum items. Must be >= 1. Must be >= arrayMin when both set. */ arrayMax?: number; } /** Flat property definition covering all property types. */ export interface PropertyDefinition { type: BuiltInPropertyType; displayName?: LocalizedString; nullable?: boolean; default?: unknown; unique?: boolean; description?: LocalizedString; renamedFrom?: string; rules?: ValidationRules; primary?: boolean; hidden?: boolean; fillable?: boolean; /** Database-generated stored column expression. Generated columns are read-only. */ storedAs?: string; /** * Mark field as translatable (Astrotomic/laravel-translatable). * When true: * - A `{model}_translations` table is auto-generated with FK + locale + translatable columns * - The field remains in the main table as fallback value * - Base model gets `use Translatable` trait + `implements TranslatableContract` * - A `{ModelName}Translation` model is generated with `$timestamps = false` * * Only allowed on: String, Text, MediumText, LongText, Email, EnumRef, Json. */ translatable?: boolean; placeholder?: LocalizedString; /** Per-field overrides for compound types. */ fields?: Record; /** * Inner type for typed JSON arrays. When set on a Json property * (e.g. `items: "String"`), the Go target emits `[]string` instead * of plain `string`. The DDL column type stays JSON / TEXT — only * Go-side codegen is affected. Issue #103 follow-up. */ items?: "String" | "Text" | "Email" | "Phone" | "Slug" | "Url" | "Uuid" | "TinyInt" | "Int" | "BigInt" | "Float" | "Decimal" | "Boolean"; // String-specific length?: number; // Numeric-specific unsigned?: boolean; precision?: number; scale?: number; // Timestamp-specific useCurrent?: boolean; useCurrentOnUpdate?: boolean; // Enum-specific (array for inline Enum, string for EnumRef) enum?: (string | InlineEnumValue)[] | string; // File-specific multiple?: boolean; maxFiles?: number; accept?: string[]; maxSize?: number; // Association-specific relation?: AssociationRelation; target?: string; targets?: string[]; morphName?: string; inversedBy?: string; mappedBy?: string; onDelete?: ReferentialAction; onUpdate?: ReferentialAction; owning?: boolean; joinTable?: string; /** * Override the auto-derived FK column name. Default: * `_id` (e.g. property `createdBy` → `created_by_id`). * Use to keep legacy column names when porting tables to omnify * (#103 Gap 7). On `kind: pivot`, an Association property whose * target matches a `pivotFor` entry uses this override to replace * the auto-derived `_id` column (#103 Bug D). */ column?: string; /** @deprecated Removed in v5.0.0 — declare extra pivot columns on the * explicit `kind: pivot` schema (auto-created by `omnify generate`) * instead. Validation rejects YAML that still uses this field. */ pivotFields?: Record; /** @deprecated Removed in v5.0.0 — set `options.id` on the explicit * `kind: pivot` schema instead. Validation rejects YAML that still * uses this field. */ pivotId?: IdType; targetNamespace?: string; idType?: IdType; } /** Database index definition. */ export interface IndexDefinition { columns: string[]; unique?: boolean; name?: string; type?: IndexType; } /** Schema-level configuration options. */ export interface SchemaOptions { /** ID column: true (BigInt), false (no ID), or type string (BigInt, Int, Uuid, String). */ id?: boolean | IdType; /** Custom primary key — single column or composite. */ primaryKey?: string | string[]; timestamps?: boolean; softDelete?: boolean; /** Unique constraints — column list or array of column lists. */ unique?: string[] | string[][]; indexes?: IndexDefinition[]; tableName?: string; authenticatable?: boolean; hidden?: boolean; /** Enable nested set tree structure (_lft, _rgt, parent_id columns + NodeTrait). */ nestedSet?: boolean; } // --------------------------------------------------------------------------- // Policy types (Cedar-style ABAC) // --------------------------------------------------------------------------- /** Operand type in a policy condition. */ export type PolicyOperandType = | "property" | "variable" | "literal" | "set" | "cidr" | "path"; /** An operand in a policy condition expression. */ export interface ConditionOperand { type: PolicyOperandType; /** Name for property/variable/cidr/path operands. */ name?: string; /** Value for literal operands. */ value?: string | number | boolean; /** Items for set operands. */ items?: string[]; } /** A single condition (left operator right). */ export interface SingleCondition { operator: string; left: ConditionOperand; right: ConditionOperand; } /** AND group of conditions. */ export interface PolicyConditionAnd { operator: "and"; conditions: SingleCondition[]; } /** A policy when clause — single condition or AND group. */ export type PolicyWhen = SingleCondition | PolicyConditionAnd; /** A single policy rule (permit or forbid). */ export interface PolicyDefinition { effect: "permit" | "forbid"; actions: string[]; when?: PolicyWhen; desc?: string; } /** Complete schema definition (corresponds to a single YAML file). */ export interface SchemaDefinition { connection?: string; kind?: SchemaKind; /** Target schema name for partial/extend schemas. */ target?: string; /** Priority for partial merging (default 50). */ priority?: number; /** Schema names this pivot is for. */ pivotFor?: string[]; displayName?: LocalizedString; titleIndex?: string; group?: string; options?: SchemaOptions; properties?: Record; /** Enum values (only when kind is "enum"). */ values?: (string | InlineEnumValue)[]; /** Cedar-style ABAC policy definitions (only for kind: object). */ policies?: PolicyDefinition[]; } /** A schema with metadata from loading. */ export interface LoadedSchema extends SchemaDefinition { name: string; filePath: string; relativePath: string; } /** Map of schema name to loaded schema. */ export type SchemaCollection = Record;