feat(pcd): add PCD type generator and corresponding types for database schema

This commit is contained in:
Sam Chau
2026-01-27 21:32:31 +10:30
parent 2e36df30e5
commit d079c0dfd7
3 changed files with 1231 additions and 0 deletions

View File

@@ -46,6 +46,7 @@
"test": "deno run -A scripts/test.ts",
"test:watch": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env --watch",
"generate:api-types": "npx openapi-typescript docs/api/v1/openapi.yaml -o src/lib/api/v1.d.ts",
"generate:pcd-types": "deno run -A scripts/generate-pcd-types.ts",
"docker:build": "docker compose -f compose.dev.yml build --no-cache",
"docker:up": "docker compose -f compose.dev.yml up --build",
"docker:down": "docker compose -f compose.dev.yml down",

View File

@@ -0,0 +1,542 @@
/**
* PCD Type Generator
*
* Generates TypeScript types from the PCD schema SQL.
* Uses SQLite introspection to ensure types match the actual schema.
*
* Usage:
* deno task generate:pcd-types # Uses default version (1.0.0)
* deno task generate:pcd-types --version=1.1.0 # Uses specific version
* deno task generate:pcd-types --local=/path/to/schema.sql # Uses local file
*/
import { Database } from '@jsr/db__sqlite';
// ============================================================================
// CONFIGURATION
// ============================================================================
const SCHEMA_REPO = 'Dictionarry-Hub/schema';
const DEFAULT_VERSION = '1.0.0'; // Schema versions are branch names (e.g., 1.0.0, 1.1.0)
const SCHEMA_PATH = 'ops/0.schema.sql';
const OUTPUT_DIR = './src/lib/shared/pcd';
const OUTPUT_PATH = `${OUTPUT_DIR}/types.ts`;
// ============================================================================
// CLI ARGUMENT PARSING
// ============================================================================
interface CliArgs {
version: string;
localPath?: string;
help: boolean;
}
function parseArgs(): CliArgs {
const args: CliArgs = {
version: DEFAULT_VERSION,
help: false
};
for (const arg of Deno.args) {
if (arg === '--help' || arg === '-h') {
args.help = true;
} else if (arg.startsWith('--version=')) {
args.version = arg.slice('--version='.length);
} else if (arg.startsWith('--local=')) {
args.localPath = arg.slice('--local='.length);
}
}
return args;
}
function printHelp(): void {
console.log(`
PCD Type Generator
Generates TypeScript types from the PCD schema SQL.
USAGE:
deno task generate:pcd-types [OPTIONS]
OPTIONS:
--version=<ver> Use specific schema version/branch (default: ${DEFAULT_VERSION})
--local=<path> Use local schema file instead of fetching from GitHub
--help, -h Show this help message
EXAMPLES:
deno task generate:pcd-types # Fetch version ${DEFAULT_VERSION}
deno task generate:pcd-types --version=1.1.0 # Fetch version 1.1.0
deno task generate:pcd-types --local=./schema.sql # Use local file
OUTPUT:
${OUTPUT_PATH}
`);
}
// ============================================================================
// SCHEMA FETCHING
// ============================================================================
async function fetchSchemaFromGitHub(version: string): Promise<string> {
const url = `https://raw.githubusercontent.com/${SCHEMA_REPO}/${version}/${SCHEMA_PATH}`;
console.log(`Fetching schema from: ${url}`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch schema: ${response.status} ${response.statusText}`);
}
return await response.text();
}
async function loadSchemaFromFile(path: string): Promise<string> {
console.log(`Loading schema from: ${path}`);
return await Deno.readTextFile(path);
}
// ============================================================================
// SQLITE INTROSPECTION
// ============================================================================
interface ColumnInfo {
cid: number;
name: string;
type: string;
notnull: number;
dflt_value: string | null;
pk: number;
}
interface ForeignKeyInfo {
id: number;
seq: number;
table: string;
from: string;
to: string;
on_update: string;
on_delete: string;
}
interface TableInfo {
name: string;
columns: ColumnInfo[];
foreignKeys: ForeignKeyInfo[];
}
function introspectDatabase(db: Database): TableInfo[] {
// Get all table names
const tables = db
.prepare(
`SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
ORDER BY name`
)
.all() as { name: string }[];
const tableInfos: TableInfo[] = [];
for (const { name } of tables) {
// Get column info
const columns = db.prepare(`PRAGMA table_info('${name}')`).all() as ColumnInfo[];
// Get foreign key info
const foreignKeys = db.prepare(`PRAGMA foreign_key_list('${name}')`).all() as ForeignKeyInfo[];
tableInfos.push({ name, columns, foreignKeys });
}
return tableInfos;
}
// ============================================================================
// TYPE GENERATION
// ============================================================================
/**
* Map SQLite types to TypeScript types
*/
function sqliteTypeToTs(sqliteType: string, nullable: boolean): string {
const type = sqliteType.toUpperCase();
let tsType: string;
if (type.includes('INT')) {
tsType = 'number';
} else if (type.includes('CHAR') || type.includes('TEXT') || type.includes('CLOB')) {
tsType = 'string';
} else if (type.includes('REAL') || type.includes('FLOA') || type.includes('DOUB')) {
tsType = 'number';
} else if (type.includes('BLOB')) {
tsType = 'Uint8Array';
} else if (type === '' || type === 'NUMERIC') {
// SQLite allows untyped columns
tsType = 'unknown';
} else {
// Default to string for VARCHAR, etc.
tsType = 'string';
}
return nullable ? `${tsType} | null` : tsType;
}
/**
* Convert snake_case to PascalCase
*/
function toPascalCase(str: string): string {
return str
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('');
}
/**
* Check if a column has a default value or is auto-generated
*/
function isGenerated(column: ColumnInfo): boolean {
// Primary key with INTEGER (autoincrement in SQLite)
if (column.pk === 1 && column.type.toUpperCase().includes('INTEGER')) {
return true;
}
// Has a default value
if (column.dflt_value !== null) {
return true;
}
return false;
}
/**
* Check if a column is actually nullable
* Primary key INTEGER columns are never nullable in practice
*/
function isNullable(column: ColumnInfo): boolean {
// Primary key INTEGER columns are autoincrement and never null
if (column.pk === 1 && column.type.toUpperCase().includes('INTEGER')) {
return false;
}
return column.notnull === 0;
}
/**
* Generate TypeScript interface for a table
*/
function generateTableInterface(table: TableInfo): string {
const interfaceName = `${toPascalCase(table.name)}Table`;
const lines: string[] = [];
lines.push(`export interface ${interfaceName} {`);
for (const column of table.columns) {
const nullable = isNullable(column);
const tsType = sqliteTypeToTs(column.type, nullable);
const generated = isGenerated(column);
if (generated) {
// Wrap in Generated<T> for auto-generated columns
const baseType = nullable ? tsType.replace(' | null', '') : tsType;
lines.push(`\t${column.name}: Generated<${baseType}>${nullable ? ' | null' : ''};`);
} else {
lines.push(`\t${column.name}: ${tsType};`);
}
}
lines.push('}');
return lines.join('\n');
}
/**
* Generate the database interface that maps table names to interfaces
*/
function generateDatabaseInterface(tables: TableInfo[]): string {
const lines: string[] = [];
lines.push('export interface PCDDatabase {');
for (const table of tables) {
const interfaceName = `${toPascalCase(table.name)}Table`;
lines.push(`\t${table.name}: ${interfaceName};`);
}
lines.push('}');
return lines.join('\n');
}
/**
* Generate row types (non-Generated versions for query results)
*/
function generateRowType(table: TableInfo): string {
const rowTypeName = `${toPascalCase(table.name)}Row`;
const lines: string[] = [];
lines.push(`export interface ${rowTypeName} {`);
for (const column of table.columns) {
const nullable = isNullable(column);
const tsType = sqliteTypeToTs(column.type, nullable);
lines.push(`\t${column.name}: ${tsType};`);
}
lines.push('}');
return lines.join('\n');
}
/**
* Generate the complete types file
*/
function generateTypesFile(tables: TableInfo[], version: string): string {
const lines: string[] = [];
// Header
lines.push(`/**
* PCD Database Schema Types
*
* AUTO-GENERATED - DO NOT EDIT MANUALLY
*
* Generated from: https://github.com/${SCHEMA_REPO}/blob/${version}/${SCHEMA_PATH}
* Generated at: ${new Date().toISOString()}
*
* To regenerate: deno task generate:pcd-types --version=${version}
*/
import type { Generated } from 'kysely';
`);
// Group tables by entity - ordered by importance/usage
const qualityProfileTables = [
'quality_profiles',
'quality_profile_tags',
'quality_groups',
'quality_group_members',
'quality_profile_qualities',
'quality_profile_languages',
'quality_profile_custom_formats',
'test_entities',
'test_releases'
];
const customFormatTables = [
'custom_formats',
'custom_format_tags',
'custom_format_conditions',
'custom_format_tests'
// condition_* tables added dynamically below
];
const regexTables = ['regular_expressions', 'regular_expression_tags'];
const delayProfileTables = ['delay_profiles', 'delay_profile_tags'];
const mediaManagementTables = [
'radarr_naming',
'sonarr_naming',
'radarr_media_settings',
'sonarr_media_settings',
'radarr_quality_definitions',
'sonarr_quality_definitions'
];
const coreTables = ['tags', 'languages', 'qualities', 'quality_api_mappings'];
// Categories in display order
const categories = [
'QUALITY PROFILES',
'CUSTOM FORMATS',
'REGULAR EXPRESSIONS',
'DELAY PROFILES',
'MEDIA MANAGEMENT',
'CORE'
] as const;
const categorized = new Map<string, TableInfo[]>();
for (const cat of categories) {
categorized.set(cat, []);
}
for (const table of tables) {
if (qualityProfileTables.includes(table.name)) {
categorized.get('QUALITY PROFILES')!.push(table);
} else if (customFormatTables.includes(table.name) || table.name.startsWith('condition_')) {
categorized.get('CUSTOM FORMATS')!.push(table);
} else if (regexTables.includes(table.name)) {
categorized.get('REGULAR EXPRESSIONS')!.push(table);
} else if (delayProfileTables.includes(table.name)) {
categorized.get('DELAY PROFILES')!.push(table);
} else if (mediaManagementTables.includes(table.name)) {
categorized.get('MEDIA MANAGEMENT')!.push(table);
} else if (coreTables.includes(table.name)) {
categorized.get('CORE')!.push(table);
} else {
// Unknown tables go to CORE as fallback
console.warn(`Unknown table: ${table.name} - adding to CORE`);
categorized.get('CORE')!.push(table);
}
}
// Sort tables within each category by the predefined order
const sortByOrder = (tables: TableInfo[], order: string[]): TableInfo[] => {
return tables.sort((a, b) => {
const aIndex = order.indexOf(a.name);
const bIndex = order.indexOf(b.name);
// Tables in the order list come first, sorted by their position
// Tables not in the list (like condition_*) come after, sorted alphabetically
if (aIndex === -1 && bIndex === -1) return a.name.localeCompare(b.name);
if (aIndex === -1) return 1;
if (bIndex === -1) return -1;
return aIndex - bIndex;
});
};
categorized.set('QUALITY PROFILES', sortByOrder(categorized.get('QUALITY PROFILES')!, qualityProfileTables));
categorized.set('CUSTOM FORMATS', sortByOrder(categorized.get('CUSTOM FORMATS')!, customFormatTables));
categorized.set('REGULAR EXPRESSIONS', sortByOrder(categorized.get('REGULAR EXPRESSIONS')!, regexTables));
categorized.set('DELAY PROFILES', sortByOrder(categorized.get('DELAY PROFILES')!, delayProfileTables));
categorized.set('MEDIA MANAGEMENT', sortByOrder(categorized.get('MEDIA MANAGEMENT')!, mediaManagementTables));
categorized.set('CORE', sortByOrder(categorized.get('CORE')!, coreTables));
// Generate Kysely table interfaces
lines.push('// ============================================================================');
lines.push('// KYSELY TABLE INTERFACES');
lines.push('// ============================================================================');
lines.push('// Use these with Kysely for type-safe queries with Generated<T> support');
lines.push('');
for (const [category, categoryTables] of categorized) {
if (categoryTables.length === 0) continue;
lines.push(`// ${category}`);
lines.push('');
for (const table of categoryTables) {
lines.push(generateTableInterface(table));
lines.push('');
}
}
// Generate database interface
lines.push('// ============================================================================');
lines.push('// DATABASE INTERFACE');
lines.push('// ============================================================================');
lines.push('');
lines.push(generateDatabaseInterface(tables));
lines.push('');
// Generate row types
lines.push('// ============================================================================');
lines.push('// ROW TYPES (Query Results)');
lines.push('// ============================================================================');
lines.push('// Use these for query result types (no Generated<T> wrapper)');
lines.push('');
for (const [category, categoryTables] of categorized) {
if (categoryTables.length === 0) continue;
lines.push(`// ${category}`);
lines.push('');
for (const table of categoryTables) {
lines.push(generateRowType(table));
lines.push('');
}
}
// Generate helper types
lines.push('// ============================================================================');
lines.push('// HELPER TYPES');
lines.push('// ============================================================================');
lines.push('');
lines.push('/** Extract insertable type from a table (Generated fields become optional) */');
lines.push('export type Insertable<T> = {');
lines.push('\t[K in keyof T]: T[K] extends Generated<infer U>');
lines.push('\t\t? U | undefined');
lines.push('\t\t: T[K] extends Generated<infer U> | null');
lines.push('\t\t\t? U | null | undefined');
lines.push('\t\t\t: T[K];');
lines.push('};');
lines.push('');
lines.push('/** Extract selectable type from a table (Generated<T> becomes T) */');
lines.push('export type Selectable<T> = {');
lines.push('\t[K in keyof T]: T[K] extends Generated<infer U>');
lines.push('\t\t? U');
lines.push('\t\t: T[K] extends Generated<infer U> | null');
lines.push('\t\t\t? U | null');
lines.push('\t\t\t: T[K];');
lines.push('};');
lines.push('');
return lines.join('\n');
}
// ============================================================================
// MAIN
// ============================================================================
async function main(): Promise<void> {
const args = parseArgs();
if (args.help) {
printHelp();
Deno.exit(0);
}
try {
// Load schema
let schemaSql: string;
let sourceVersion = args.version;
if (args.localPath) {
schemaSql = await loadSchemaFromFile(args.localPath);
sourceVersion = 'local';
} else {
schemaSql = await fetchSchemaFromGitHub(args.version);
}
console.log(`Schema loaded (${schemaSql.length} bytes)`);
// Create in-memory database and run schema
console.log('Creating database and applying schema...');
const db = new Database(':memory:');
try {
db.exec(schemaSql);
} catch (error) {
console.error('Failed to execute schema SQL:', error);
Deno.exit(1);
}
// Introspect database
console.log('Introspecting database structure...');
const tables = introspectDatabase(db);
console.log(`Found ${tables.length} tables`);
// Generate types
console.log('Generating TypeScript types...');
const typesContent = generateTypesFile(tables, sourceVersion);
// Ensure output directory exists
await Deno.mkdir(OUTPUT_DIR, { recursive: true });
// Write output
await Deno.writeTextFile(OUTPUT_PATH, typesContent);
console.log(`\nTypes written to: ${OUTPUT_PATH}`);
// Summary
console.log('\nGenerated types for:');
for (const table of tables) {
console.log(` - ${table.name} (${table.columns.length} columns)`);
}
db.close();
} catch (error) {
console.error('Error:', error);
Deno.exit(1);
}
}
main();

688
src/lib/shared/pcd/types.ts Normal file
View File

@@ -0,0 +1,688 @@
/**
* PCD Database Schema Types
*
* AUTO-GENERATED - DO NOT EDIT MANUALLY
*
* Generated from: https://github.com/Dictionarry-Hub/schema/blob/1.0.0/ops/0.schema.sql
* Generated at: 2026-01-27T11:00:15.108Z
*
* To regenerate: deno task generate:pcd-types --version=1.0.0
*/
import type { Generated } from 'kysely';
// ============================================================================
// KYSELY TABLE INTERFACES
// ============================================================================
// Use these with Kysely for type-safe queries with Generated<T> support
// QUALITY PROFILES
export interface QualityProfilesTable {
id: Generated<number>;
name: string;
description: string | null;
upgrades_allowed: Generated<number>;
minimum_custom_format_score: Generated<number>;
upgrade_until_score: Generated<number>;
upgrade_score_increment: Generated<number>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface QualityProfileTagsTable {
quality_profile_name: string;
tag_name: string;
}
export interface QualityGroupsTable {
id: Generated<number>;
quality_profile_name: string;
name: string;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface QualityGroupMembersTable {
quality_profile_name: string;
quality_group_name: string;
quality_name: string;
}
export interface QualityProfileQualitiesTable {
id: Generated<number>;
quality_profile_name: string;
quality_name: string | null;
quality_group_name: string | null;
position: number;
enabled: Generated<number>;
upgrade_until: Generated<number>;
}
export interface QualityProfileLanguagesTable {
quality_profile_name: string;
language_name: string;
type: Generated<string>;
}
export interface QualityProfileCustomFormatsTable {
quality_profile_name: string;
custom_format_name: string;
arr_type: string;
score: number;
}
export interface TestEntitiesTable {
id: Generated<number>;
type: string;
tmdb_id: number;
title: string;
year: number | null;
poster_path: string | null;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface TestReleasesTable {
id: Generated<number>;
entity_type: string;
entity_tmdb_id: number;
title: string;
size_bytes: number | null;
languages: Generated<string>;
indexers: Generated<string>;
flags: Generated<string>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
// CUSTOM FORMATS
export interface CustomFormatsTable {
id: Generated<number>;
name: string;
description: string | null;
include_in_rename: Generated<number>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface CustomFormatTagsTable {
custom_format_name: string;
tag_name: string;
}
export interface CustomFormatConditionsTable {
id: Generated<number>;
custom_format_name: string;
name: string;
type: string;
arr_type: Generated<string>;
negate: Generated<number>;
required: Generated<number>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface CustomFormatTestsTable {
id: Generated<number>;
custom_format_name: string;
title: string;
type: string;
should_match: number;
description: string | null;
created_at: Generated<string>;
}
export interface ConditionIndexerFlagsTable {
custom_format_name: string;
condition_name: string;
flag: string;
}
export interface ConditionLanguagesTable {
custom_format_name: string;
condition_name: string;
language_name: string;
except_language: Generated<number>;
}
export interface ConditionPatternsTable {
custom_format_name: string;
condition_name: string;
regular_expression_name: string;
}
export interface ConditionQualityModifiersTable {
custom_format_name: string;
condition_name: string;
quality_modifier: string;
}
export interface ConditionReleaseTypesTable {
custom_format_name: string;
condition_name: string;
release_type: string;
}
export interface ConditionResolutionsTable {
custom_format_name: string;
condition_name: string;
resolution: string;
}
export interface ConditionSizesTable {
custom_format_name: string;
condition_name: string;
min_bytes: number | null;
max_bytes: number | null;
}
export interface ConditionSourcesTable {
custom_format_name: string;
condition_name: string;
source: string;
}
export interface ConditionYearsTable {
custom_format_name: string;
condition_name: string;
min_year: number | null;
max_year: number | null;
}
// REGULAR EXPRESSIONS
export interface RegularExpressionsTable {
id: Generated<number>;
name: string;
pattern: string;
regex101_id: string | null;
description: string | null;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface RegularExpressionTagsTable {
regular_expression_name: string;
tag_name: string;
}
// DELAY PROFILES
export interface DelayProfilesTable {
id: Generated<number>;
name: string;
preferred_protocol: string;
usenet_delay: number | null;
torrent_delay: number | null;
bypass_if_highest_quality: Generated<number>;
bypass_if_above_custom_format_score: Generated<number>;
minimum_custom_format_score: number | null;
created_at: Generated<string>;
updated_at: Generated<string>;
}
// MEDIA MANAGEMENT
export interface RadarrNamingTable {
name: string | null;
rename: Generated<number>;
movie_format: string;
movie_folder_format: string;
replace_illegal_characters: Generated<number>;
colon_replacement_format: Generated<string>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface SonarrNamingTable {
name: string | null;
rename: Generated<number>;
standard_episode_format: string;
daily_episode_format: string;
anime_episode_format: string;
series_folder_format: string;
season_folder_format: string;
replace_illegal_characters: Generated<number>;
colon_replacement_format: Generated<number>;
custom_colon_replacement_format: string | null;
multi_episode_style: Generated<number>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface RadarrMediaSettingsTable {
name: string | null;
propers_repacks: Generated<string>;
enable_media_info: Generated<number>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface SonarrMediaSettingsTable {
name: string | null;
propers_repacks: Generated<string>;
enable_media_info: Generated<number>;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface RadarrQualityDefinitionsTable {
name: string;
quality_name: string;
min_size: Generated<number>;
max_size: number;
preferred_size: number;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface SonarrQualityDefinitionsTable {
name: string;
quality_name: string;
min_size: Generated<number>;
max_size: number;
preferred_size: number;
created_at: Generated<string>;
updated_at: Generated<string>;
}
// CORE
export interface TagsTable {
id: Generated<number>;
name: string;
created_at: Generated<string>;
}
export interface LanguagesTable {
id: Generated<number>;
name: string;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface QualitiesTable {
id: Generated<number>;
name: string;
created_at: Generated<string>;
updated_at: Generated<string>;
}
export interface QualityApiMappingsTable {
quality_name: string;
arr_type: string;
api_name: string;
created_at: Generated<string>;
}
// ============================================================================
// DATABASE INTERFACE
// ============================================================================
export interface PCDDatabase {
condition_indexer_flags: ConditionIndexerFlagsTable;
condition_languages: ConditionLanguagesTable;
condition_patterns: ConditionPatternsTable;
condition_quality_modifiers: ConditionQualityModifiersTable;
condition_release_types: ConditionReleaseTypesTable;
condition_resolutions: ConditionResolutionsTable;
condition_sizes: ConditionSizesTable;
condition_sources: ConditionSourcesTable;
condition_years: ConditionYearsTable;
custom_format_conditions: CustomFormatConditionsTable;
custom_format_tags: CustomFormatTagsTable;
custom_format_tests: CustomFormatTestsTable;
custom_formats: CustomFormatsTable;
delay_profiles: DelayProfilesTable;
languages: LanguagesTable;
qualities: QualitiesTable;
quality_api_mappings: QualityApiMappingsTable;
quality_group_members: QualityGroupMembersTable;
quality_groups: QualityGroupsTable;
quality_profile_custom_formats: QualityProfileCustomFormatsTable;
quality_profile_languages: QualityProfileLanguagesTable;
quality_profile_qualities: QualityProfileQualitiesTable;
quality_profile_tags: QualityProfileTagsTable;
quality_profiles: QualityProfilesTable;
radarr_media_settings: RadarrMediaSettingsTable;
radarr_naming: RadarrNamingTable;
radarr_quality_definitions: RadarrQualityDefinitionsTable;
regular_expression_tags: RegularExpressionTagsTable;
regular_expressions: RegularExpressionsTable;
sonarr_media_settings: SonarrMediaSettingsTable;
sonarr_naming: SonarrNamingTable;
sonarr_quality_definitions: SonarrQualityDefinitionsTable;
tags: TagsTable;
test_entities: TestEntitiesTable;
test_releases: TestReleasesTable;
}
// ============================================================================
// ROW TYPES (Query Results)
// ============================================================================
// Use these for query result types (no Generated<T> wrapper)
// QUALITY PROFILES
export interface QualityProfilesRow {
id: number;
name: string;
description: string | null;
upgrades_allowed: number;
minimum_custom_format_score: number;
upgrade_until_score: number;
upgrade_score_increment: number;
created_at: string;
updated_at: string;
}
export interface QualityProfileTagsRow {
quality_profile_name: string;
tag_name: string;
}
export interface QualityGroupsRow {
id: number;
quality_profile_name: string;
name: string;
created_at: string;
updated_at: string;
}
export interface QualityGroupMembersRow {
quality_profile_name: string;
quality_group_name: string;
quality_name: string;
}
export interface QualityProfileQualitiesRow {
id: number;
quality_profile_name: string;
quality_name: string | null;
quality_group_name: string | null;
position: number;
enabled: number;
upgrade_until: number;
}
export interface QualityProfileLanguagesRow {
quality_profile_name: string;
language_name: string;
type: string;
}
export interface QualityProfileCustomFormatsRow {
quality_profile_name: string;
custom_format_name: string;
arr_type: string;
score: number;
}
export interface TestEntitiesRow {
id: number;
type: string;
tmdb_id: number;
title: string;
year: number | null;
poster_path: string | null;
created_at: string;
updated_at: string;
}
export interface TestReleasesRow {
id: number;
entity_type: string;
entity_tmdb_id: number;
title: string;
size_bytes: number | null;
languages: string;
indexers: string;
flags: string;
created_at: string;
updated_at: string;
}
// CUSTOM FORMATS
export interface CustomFormatsRow {
id: number;
name: string;
description: string | null;
include_in_rename: number;
created_at: string;
updated_at: string;
}
export interface CustomFormatTagsRow {
custom_format_name: string;
tag_name: string;
}
export interface CustomFormatConditionsRow {
id: number;
custom_format_name: string;
name: string;
type: string;
arr_type: string;
negate: number;
required: number;
created_at: string;
updated_at: string;
}
export interface CustomFormatTestsRow {
id: number;
custom_format_name: string;
title: string;
type: string;
should_match: number;
description: string | null;
created_at: string;
}
export interface ConditionIndexerFlagsRow {
custom_format_name: string;
condition_name: string;
flag: string;
}
export interface ConditionLanguagesRow {
custom_format_name: string;
condition_name: string;
language_name: string;
except_language: number;
}
export interface ConditionPatternsRow {
custom_format_name: string;
condition_name: string;
regular_expression_name: string;
}
export interface ConditionQualityModifiersRow {
custom_format_name: string;
condition_name: string;
quality_modifier: string;
}
export interface ConditionReleaseTypesRow {
custom_format_name: string;
condition_name: string;
release_type: string;
}
export interface ConditionResolutionsRow {
custom_format_name: string;
condition_name: string;
resolution: string;
}
export interface ConditionSizesRow {
custom_format_name: string;
condition_name: string;
min_bytes: number | null;
max_bytes: number | null;
}
export interface ConditionSourcesRow {
custom_format_name: string;
condition_name: string;
source: string;
}
export interface ConditionYearsRow {
custom_format_name: string;
condition_name: string;
min_year: number | null;
max_year: number | null;
}
// REGULAR EXPRESSIONS
export interface RegularExpressionsRow {
id: number;
name: string;
pattern: string;
regex101_id: string | null;
description: string | null;
created_at: string;
updated_at: string;
}
export interface RegularExpressionTagsRow {
regular_expression_name: string;
tag_name: string;
}
// DELAY PROFILES
export interface DelayProfilesRow {
id: number;
name: string;
preferred_protocol: string;
usenet_delay: number | null;
torrent_delay: number | null;
bypass_if_highest_quality: number;
bypass_if_above_custom_format_score: number;
minimum_custom_format_score: number | null;
created_at: string;
updated_at: string;
}
// MEDIA MANAGEMENT
export interface RadarrNamingRow {
name: string | null;
rename: number;
movie_format: string;
movie_folder_format: string;
replace_illegal_characters: number;
colon_replacement_format: string;
created_at: string;
updated_at: string;
}
export interface SonarrNamingRow {
name: string | null;
rename: number;
standard_episode_format: string;
daily_episode_format: string;
anime_episode_format: string;
series_folder_format: string;
season_folder_format: string;
replace_illegal_characters: number;
colon_replacement_format: number;
custom_colon_replacement_format: string | null;
multi_episode_style: number;
created_at: string;
updated_at: string;
}
export interface RadarrMediaSettingsRow {
name: string | null;
propers_repacks: string;
enable_media_info: number;
created_at: string;
updated_at: string;
}
export interface SonarrMediaSettingsRow {
name: string | null;
propers_repacks: string;
enable_media_info: number;
created_at: string;
updated_at: string;
}
export interface RadarrQualityDefinitionsRow {
name: string;
quality_name: string;
min_size: number;
max_size: number;
preferred_size: number;
created_at: string;
updated_at: string;
}
export interface SonarrQualityDefinitionsRow {
name: string;
quality_name: string;
min_size: number;
max_size: number;
preferred_size: number;
created_at: string;
updated_at: string;
}
// CORE
export interface TagsRow {
id: number;
name: string;
created_at: string;
}
export interface LanguagesRow {
id: number;
name: string;
created_at: string;
updated_at: string;
}
export interface QualitiesRow {
id: number;
name: string;
created_at: string;
updated_at: string;
}
export interface QualityApiMappingsRow {
quality_name: string;
arr_type: string;
api_name: string;
created_at: string;
}
// ============================================================================
// HELPER TYPES
// ============================================================================
/** Extract insertable type from a table (Generated fields become optional) */
export type Insertable<T> = {
[K in keyof T]: T[K] extends Generated<infer U>
? U | undefined
: T[K] extends Generated<infer U> | null
? U | null | undefined
: T[K];
};
/** Extract selectable type from a table (Generated<T> becomes T) */
export type Selectable<T> = {
[K in keyof T]: T[K] extends Generated<infer U>
? U
: T[K] extends Generated<infer U> | null
? U | null
: T[K];
};