From 0bf9d5e786ff8d3015f221529f41a9ce1ed13a5e Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 6 May 2026 16:04:54 +0000 Subject: [PATCH] refactor(database): Replace relation select type with simpler built type --- src/backend/common/database/drizzle/drizzleTypes.ts | 12 ++++++++++-- src/backend/common/database/drizzle/entityUtils.ts | 4 ++-- .../database/drizzle/repositories/PlayRepository.ts | 8 ++++---- src/backend/scrobblers/AbstractScrobbleClient.ts | 4 ++-- src/backend/tests/scrobbler/TestScrobbler.ts | 4 ++-- src/core/Atomic.ts | 4 ++-- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/backend/common/database/drizzle/drizzleTypes.ts b/src/backend/common/database/drizzle/drizzleTypes.ts index 7a7e3b0d..654c7012 100644 --- a/src/backend/common/database/drizzle/drizzleTypes.ts +++ b/src/backend/common/database/drizzle/drizzleTypes.ts @@ -1,6 +1,7 @@ -import { DBQueryConfig, DBQueryConfigWith, ExtractTablesFromSchema, KnownKeysOnly, RelationFieldsFilterInternals, Many, InferSelectModel, ExtractTablesWithRelations } from "drizzle-orm"; +import { DBQueryConfig, DBQueryConfigWith, ExtractTablesFromSchema, KnownKeysOnly, RelationFieldsFilterInternals, Many, InferSelectModel, ExtractTablesWithRelations, type BuildQueryResult, RelationsFilter } from "drizzle-orm"; import { components, playInputs, plays, queueStates, relations } from "./schema/schema.js"; import {TSchema, TableName, Schema } from "./schema/schema.js"; +import { MarkOptional, MarkRequired } from "ts-essentials"; export type ComponentNew = typeof components.$inferInsert; @@ -14,6 +15,7 @@ export type PlayInputSelect = typeof playInputs.$inferSelect; export type PlaySelect = typeof plays.$inferSelect; export type PlaySelectRel = ModelWithRelations; +export type PlaySelectWithQueueStates = GenericRelationResult<'plays', 'queueStates'>; export type PlayNew = typeof plays.$inferInsert; @@ -31,6 +33,9 @@ export type QueryConfig = DBQueryConfig<"many", TSchema, TS export type FindMany = Pick, DBQueryConfig<"many", TSchema, TSchema[T]>>, 'where' | 'orderBy' | 'limit' | 'offset' | 'extras'> & {with?: FindWith} export type FindOne = Pick, DBQueryConfig<"one", TSchema, TSchema[T]>>, 'where' | 'orderBy' | 'limit' | 'offset' | 'extras'> & {with?: FindWith} export type FindWhere = QueryConfig['where']; +// https://github.com/drizzle-team/drizzle-orm/issues/5218#issuecomment-4154686086 +export type WhereClause = RelationsFilter + export type CompareOp = Pick, 'gt' | 'gte' | 'eq' | 'lt' | 'lte' | 'ne'> export type CompareOpKey = keyof CompareOp; @@ -89,4 +94,7 @@ export type ModelWithRelations = // all relations are are now fully typed and optional -//type FullPlay = ModelWithRelations; \ No newline at end of file +//type FullPlay = ModelWithRelations; + +// https://github.com/drizzle-team/drizzle-orm/issues/695#issuecomment-4389296482 +type GenericRelationResult = BuildQueryResult }>; \ No newline at end of file diff --git a/src/backend/common/database/drizzle/entityUtils.ts b/src/backend/common/database/drizzle/entityUtils.ts index 70a5d84a..beee5eed 100644 --- a/src/backend/common/database/drizzle/entityUtils.ts +++ b/src/backend/common/database/drizzle/entityUtils.ts @@ -1,5 +1,5 @@ import assert from "node:assert"; -import { PlayNew, PlaySelect, PlaySelectRel } from "./drizzleTypes.js"; +import { PlayNew, PlaySelect, PlaySelectWithQueueStates } from "./drizzleTypes.js"; import { PlayInputNew } from "./drizzleTypes.js"; import { QueueStateNew } from "./drizzleTypes.js"; import { ComponentNew } from "./drizzleTypes.js"; @@ -71,7 +71,7 @@ export const hydratePlaySelect = (select: PlaySelect, opts: PlayHydateOptions[] return res; } -export const playSelectToDeadScrobble = (select: MarkRequired): DeadLetterScrobble => { +export const playSelectToDeadScrobble = (select: PlaySelectWithQueueStates): DeadLetterScrobble => { const deadQueue = select.queueStates.find(x => x.queueName === CLIENT_DEAD_QUEUE); return { play: select.play, diff --git a/src/backend/common/database/drizzle/repositories/PlayRepository.ts b/src/backend/common/database/drizzle/repositories/PlayRepository.ts index 3a7bf43f..0b3f4434 100644 --- a/src/backend/common/database/drizzle/repositories/PlayRepository.ts +++ b/src/backend/common/database/drizzle/repositories/PlayRepository.ts @@ -4,7 +4,7 @@ import { loggerNoop } from "../../../MaybeLogger.js"; import { ErrorLike, PlayObject, TA_CLOSE, TA_DEFAULT_ACCURACY, TA_EXACT, TemporalAccuracy } from "../../../../../core/Atomic.js"; import { generateInputEntity, generatePlayEntity, PlayEntityOpts, hydratePlaySelect, PlayHydateOptions } from "../entityUtils.js"; import { playInputs, plays, queueStates, relations } from "../schema/schema.js"; -import { PlayNew, PlaySelect, PlayInputNew, FindWhere, FindMany, CompareOpKey, QueueStateSelect, PlayInputSelect, PlaySelectRel, FindWith } from "../drizzleTypes.js";; +import { PlayNew, PlaySelect, PlayInputNew, FindWhere, FindMany, CompareOpKey, QueueStateSelect, PlayInputSelect, PlaySelectRel, FindWith, PlaySelectWithQueueStates, WhereClause } from "../drizzleTypes.js";; import { MarkOptional, MarkRequired, PathValue } from "ts-essentials"; import { genGroupIdStrFromPlay, removeEmptyArrays, removeUndefinedKeys } from "../../../../utils.js"; import dayjs, { Dayjs } from "dayjs"; @@ -397,7 +397,7 @@ export class DrizzlePlayRepository extends DrizzleBaseRepository<'plays'> { }, }).prepare() - public getQueueNext = async (queueName: string, opts: {order?: 'asc' | 'desc', retries?: number} & ComponentConstrainedRepoOpts = {}): Promise | undefined> => { + public getQueueNext = async (queueName: string, opts: {order?: 'asc' | 'desc', retries?: number} & ComponentConstrainedRepoOpts = {}): Promise => { const { retries = 0, order = 'asc', @@ -538,7 +538,7 @@ export class DrizzlePlayRepository extends DrizzleBaseRepository<'plays'> { return {data: res.map(x => ({...x, play: hydratePlaySelect(x, hydrate)})), meta: {limit, offset}}; } - public checkExisting = async (play: PlayObject, opts: {queueName?: string, states?: PlaySelect['state'][], taAccuracy?: TemporalAccuracy[]} & ComponentConstrainedRepoOpts = {}): Promise | undefined> => { + public checkExisting = async (play: PlayObject, opts: {queueName?: string, states?: PlaySelect['state'][], taAccuracy?: TemporalAccuracy[]} & ComponentConstrainedRepoOpts = {}): Promise => { const { queueName, componentId = this.componentId, @@ -689,7 +689,7 @@ export const buildPlayWith = (args: WithPlayRelation[] | undefined): FindWith<'p return qWith; } -export const buildPlayWhere = (args: PlayWhereOpts): FindWhere<'plays'> => { +export const buildPlayWhere = (args: PlayWhereOpts): WhereClause<'plays'> => { // old way // let where: Parameters<(ReturnType)['query']['plays']['findMany']>[0]['where'] = { // }; diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index 25e7aad1..f1954f09 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -73,7 +73,7 @@ import {isErrorLike, serializeError} from 'serialize-error'; import { DEFAULT_NEW_PADDING, groupPlaysToTimeRanges } from "../utils/ListenFetchUtils.js"; import { spawn, catchAbortError, isAbortError, rethrowAbortError, delay, forever, AbortError, throwIfAborted } from 'abort-controller-x'; import { DrizzlePlayRepository, playToRepositoryCreatePlayOpts, QueryPlaysOpts } from "../common/database/drizzle/repositories/PlayRepository.js"; -import { PlaySelect, PlaySelectRel, QueueStateNew, QueueStateSelect } from "../common/database/drizzle/drizzleTypes.js"; +import { PlaySelect, PlaySelectWithQueueStates, QueueStateNew, QueueStateSelect } from "../common/database/drizzle/drizzleTypes.js"; import { asPlay } from "../../core/PlayMarshalUtils.js"; import { DrizzleQueueRepository } from "../common/database/drizzle/repositories/QueueRepository.js"; import { SourceType } from "../common/infrastructure/config/source/sources.js"; @@ -857,7 +857,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i } } - protected processQueueCurrentScrobble = async (currQueuedPlay: MarkRequired, signal: AbortSignal) => { + protected processQueueCurrentScrobble = async (currQueuedPlay: PlaySelectWithQueueStates, signal: AbortSignal) => { signal.throwIfAborted(); //const currQueuedPlay = await this.playRepo.getQueueNext(CLIENT_INGRESS_QUEUE); // if (currQueuedPlay === undefined) { diff --git a/src/backend/tests/scrobbler/TestScrobbler.ts b/src/backend/tests/scrobbler/TestScrobbler.ts index 9568c3f1..cd0fd82d 100644 --- a/src/backend/tests/scrobbler/TestScrobbler.ts +++ b/src/backend/tests/scrobbler/TestScrobbler.ts @@ -9,7 +9,7 @@ import { TimeRangeListensFetcher } from "../../common/infrastructure/Atomic.js"; import { loggerNoop } from "../../common/MaybeLogger.js"; import { DrizzlePlayRepository, RepositoryCreatePlayOpts } from "../../common/database/drizzle/repositories/PlayRepository.js"; import { DrizzleQueueRepository } from "../../common/database/drizzle/repositories/QueueRepository.js"; -import { PlaySelectRel } from "../../common/database/drizzle/drizzleTypes.js"; +import { PlaySelect } from "../../common/database/drizzle/drizzleTypes.js"; import { loggerDebug } from "@foxxmd/logging"; export class TestScrobbler extends AbstractScrobbleClient { @@ -48,7 +48,7 @@ export class TestScrobbler extends AbstractScrobbleClient { return playObject; } - addScrobbled = async (plays: PlayObject[]): Promise => { + addScrobbled = async (plays: PlayObject[]): Promise => { const newPlayData: RepositoryCreatePlayOpts[] = plays.map(x => ({play: x, state: 'scrobbled', input: {}})); return await this.playRepoTest.createPlays(newPlayData); } diff --git a/src/core/Atomic.ts b/src/core/Atomic.ts index 812ef3e7..42927dd5 100644 --- a/src/core/Atomic.ts +++ b/src/core/Atomic.ts @@ -605,10 +605,10 @@ export const CLIENT_DEAD_QUEUE = 'dead'; /** * Useful TS type-only utility for testing type equality * - * Usage: type EQ = IfEquals; // "different" + * Usage: type EQ = TypesAreEqual; // "different" * * @see https://stackoverflow.com/a/53808212/1469797 */ -export type IfEquals = +export type TypesAreEqual = (() => G extends T ? 1 : 2) extends (() => G extends U ? 1 : 2) ? Y : N; \ No newline at end of file -- 2.51.2