refactor(database): Replace relation select type with simpler built type

This commit is contained in:
FoxxMD
2026-05-06 16:04:54 +00:00
parent 102f9795f8
commit 0bf9d5e786
6 changed files with 22 additions and 14 deletions
@@ -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<typeof plays>;
export type PlaySelectWithQueueStates = GenericRelationResult<'plays', 'queueStates'>;
export type PlayNew = typeof plays.$inferInsert;
@@ -31,6 +33,9 @@ export type QueryConfig<T extends TableName> = DBQueryConfig<"many", TSchema, TS
export type FindMany<T extends TableName> = Pick<KnownKeysOnly<QueryConfig<T>, DBQueryConfig<"many", TSchema, TSchema[T]>>, 'where' | 'orderBy' | 'limit' | 'offset' | 'extras'> & {with?: FindWith<T>}
export type FindOne<T extends TableName> = Pick<KnownKeysOnly<QueryConfig<T>, DBQueryConfig<"one", TSchema, TSchema[T]>>, 'where' | 'orderBy' | 'limit' | 'offset' | 'extras'> & {with?: FindWith<T>}
export type FindWhere<T extends TableName> = QueryConfig<T>['where'];
// https://github.com/drizzle-team/drizzle-orm/issues/5218#issuecomment-4154686086
export type WhereClause<T extends keyof typeof relations> = RelationsFilter<typeof relations[T], typeof relations>
export type CompareOp<T> = Pick<RelationFieldsFilterInternals<T>, 'gt' | 'gte' | 'eq' | 'lt' | 'lte' | 'ne'>
export type CompareOpKey<T> = keyof CompareOp<T>;
@@ -89,4 +94,7 @@ export type ModelWithRelations<TTable extends Schema[keyof Schema]> =
// all relations are are now fully typed and optional
//type FullPlay = ModelWithRelations<typeof plays>;
//type FullPlay = ModelWithRelations<typeof plays>;
// https://github.com/drizzle-team/drizzle-orm/issues/695#issuecomment-4389296482
type GenericRelationResult<T extends keyof TSchema, K extends keyof TSchema[T]['relations']> = BuildQueryResult<TSchema, TSchema[T], { with: Record<K, true> }>;
@@ -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<PlaySelectRel, 'queueStates'>): DeadLetterScrobble<PlayObject> => {
export const playSelectToDeadScrobble = (select: PlaySelectWithQueueStates): DeadLetterScrobble<PlayObject> => {
const deadQueue = select.queueStates.find(x => x.queueName === CLIENT_DEAD_QUEUE);
return {
play: select.play,
@@ -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<MarkRequired<PlaySelectRel, 'queueStates'> | undefined> => {
public getQueueNext = async (queueName: string, opts: {order?: 'asc' | 'desc', retries?: number} & ComponentConstrainedRepoOpts = {}): Promise<PlaySelectWithQueueStates | undefined> => {
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<MarkRequired<PlaySelectRel, 'queueStates'> | undefined> => {
public checkExisting = async (play: PlayObject, opts: {queueName?: string, states?: PlaySelect['state'][], taAccuracy?: TemporalAccuracy[]} & ComponentConstrainedRepoOpts = {}): Promise<PlaySelectWithQueueStates | undefined> => {
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<typeof getDb>)['query']['plays']['findMany']>[0]['where'] = {
// };
@@ -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<PlaySelectRel, "queueStates">, signal: AbortSignal) => {
protected processQueueCurrentScrobble = async (currQueuedPlay: PlaySelectWithQueueStates, signal: AbortSignal) => {
signal.throwIfAborted();
//const currQueuedPlay = await this.playRepo.getQueueNext(CLIENT_INGRESS_QUEUE);
// if (currQueuedPlay === undefined) {
+2 -2
View File
@@ -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<PlaySelectRel[]> => {
addScrobbled = async (plays: PlayObject[]): Promise<PlaySelect[]> => {
const newPlayData: RepositoryCreatePlayOpts[] = plays.map(x => ({play: x, state: 'scrobbled', input: {}}));
return await this.playRepoTest.createPlays(newPlayData);
}
+2 -2
View File
@@ -605,10 +605,10 @@ export const CLIENT_DEAD_QUEUE = 'dead';
/**
* Useful TS type-only utility for testing type equality
*
* Usage: type EQ = IfEquals<any[], [number][], "same", "different">; // "different"
* Usage: type EQ = TypesAreEqual<any[], [number][], "same", "different">; // "different"
*
* @see https://stackoverflow.com/a/53808212/1469797
*/
export type IfEquals<T, U, Y=unknown, N=never> =
export type TypesAreEqual<T, U, Y=unknown, N=never> =
(<G>() => G extends T ? 1 : 2) extends
(<G>() => G extends U ? 1 : 2) ? Y : N;