diff --git a/scripts/generate-pcd-types.ts b/scripts/generate-pcd-types.ts index 00eaa11..9172947 100644 --- a/scripts/generate-pcd-types.ts +++ b/scripts/generate-pcd-types.ts @@ -11,6 +11,7 @@ */ import { Database } from '@jsr/db__sqlite'; +import { columnTypeOverrides } from './pcd-type-overrides.ts'; // ============================================================================ // CONFIGURATION @@ -257,26 +258,34 @@ function sqliteTypeToTs(sqliteType: string, nullable: boolean): string { /** * Get the semantic TypeScript type for a column - * Uses CHECK constraints for union types and naming patterns for booleans + * Priority: 1) Manual overrides, 2) CHECK constraints, 3) Boolean patterns, 4) SQLite type */ function getSemanticType( + tableName: string, column: ColumnInfo, checkConstraints: CheckConstraint[], nullable: boolean ): string { - // Check if this column has a CHECK IN constraint (union type) + // 1. Check for manual type override (for columns that store numbers but need string types) + const overrideKey = `${tableName}.${column.name}`; + const override = columnTypeOverrides[overrideKey]; + if (override) { + return nullable ? `(${override}) | null` : override; + } + + // 2. Check if this column has a CHECK IN constraint (union type) const constraint = checkConstraints.find((c) => c.column === column.name); if (constraint && constraint.values.length > 0) { const unionType = constraint.values.map((v) => `'${v}'`).join(' | '); return nullable ? `(${unionType}) | null` : unionType; } - // Check if this is a boolean column based on naming patterns + // 3. Check if this is a boolean column based on naming patterns if (isBooleanColumn(column.name, column.type)) { return nullable ? 'boolean | null' : 'boolean'; } - // Fall back to standard SQLite type mapping + // 4. Fall back to standard SQLite type mapping return sqliteTypeToTs(column.type, nullable); } @@ -378,7 +387,7 @@ function generateRowType(table: TableInfo): string { for (const column of table.columns) { const nullable = isNullable(column); - const tsType = getSemanticType(column, table.checkConstraints, nullable); + const tsType = getSemanticType(table.name, column, table.checkConstraints, nullable); lines.push(`\t${column.name}: ${tsType};`); } diff --git a/scripts/pcd-type-overrides.ts b/scripts/pcd-type-overrides.ts new file mode 100644 index 0000000..cc86431 --- /dev/null +++ b/scripts/pcd-type-overrides.ts @@ -0,0 +1,59 @@ +/** + * PCD Type Overrides + * + * Manual type overrides for columns that store numeric values in the DB + * but need semantic string types (because that's what the API expects). + * + * Format: 'table_name.column_name': 'TypeScript type' + * + * Note: Columns with CHECK constraints in the schema don't need overrides - + * the generator parses those automatically. + */ + +export const columnTypeOverrides: Record = { + // Sonarr stores these as integers (0-5) but API expects semantic strings + 'sonarr_naming.colon_replacement_format': + "'delete' | 'dash' | 'spaceDash' | 'spaceDashSpace' | 'smart' | 'custom'", + 'sonarr_naming.multi_episode_style': + "'extend' | 'duplicate' | 'repeat' | 'scene' | 'range' | 'prefixedRange'" +}; + +/** + * DB value mappings for converting between numeric DB values and semantic strings. + * Used by query read/write functions. + */ +export const colonReplacementFromDb: Record = { + 0: 'delete', + 1: 'dash', + 2: 'spaceDash', + 3: 'spaceDashSpace', + 4: 'smart', + 5: 'custom' +}; + +export const colonReplacementToDb: Record = { + delete: 0, + dash: 1, + spaceDash: 2, + spaceDashSpace: 3, + smart: 4, + custom: 5 +}; + +export const multiEpisodeStyleFromDb: Record = { + 0: 'extend', + 1: 'duplicate', + 2: 'repeat', + 3: 'scene', + 4: 'range', + 5: 'prefixedRange' +}; + +export const multiEpisodeStyleToDb: Record = { + extend: 0, + duplicate: 1, + repeat: 2, + scene: 3, + range: 4, + prefixedRange: 5 +}; diff --git a/src/lib/shared/pcd/types.ts b/src/lib/shared/pcd/types.ts index 7d7c5e0..4290b4a 100644 --- a/src/lib/shared/pcd/types.ts +++ b/src/lib/shared/pcd/types.ts @@ -4,7 +4,7 @@ * AUTO-GENERATED - DO NOT EDIT MANUALLY * * Generated from: https://github.com/Dictionarry-Hub/schema/blob/local/ops/0.schema.sql - * Generated at: 2026-01-27T11:52:25.907Z + * Generated at: 2026-01-27T12:38:46.562Z * * To regenerate: deno task generate:pcd-types --version=local */ @@ -579,7 +579,7 @@ export interface RadarrNamingRow { movie_format: string; movie_folder_format: string; replace_illegal_characters: boolean; - colon_replacement_format: string; + colon_replacement_format: 'delete' | 'dash' | 'spaceDash' | 'spaceDashSpace' | 'smart'; created_at: string; updated_at: string; } @@ -593,16 +593,16 @@ export interface SonarrNamingRow { series_folder_format: string; season_folder_format: string; replace_illegal_characters: boolean; - colon_replacement_format: number; + colon_replacement_format: 'delete' | 'dash' | 'spaceDash' | 'spaceDashSpace' | 'smart' | 'custom'; custom_colon_replacement_format: string | null; - multi_episode_style: number; + multi_episode_style: 'extend' | 'duplicate' | 'repeat' | 'scene' | 'range' | 'prefixedRange'; created_at: string; updated_at: string; } export interface RadarrMediaSettingsRow { name: string | null; - propers_repacks: string; + propers_repacks: 'doNotPrefer' | 'preferAndUpgrade' | 'doNotUpgradeAutomatically'; enable_media_info: boolean; created_at: string; updated_at: string; @@ -610,7 +610,7 @@ export interface RadarrMediaSettingsRow { export interface SonarrMediaSettingsRow { name: string | null; - propers_repacks: string; + propers_repacks: 'doNotPrefer' | 'preferAndUpgrade' | 'doNotUpgradeAutomatically'; enable_media_info: boolean; created_at: string; updated_at: string;