mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
refactor(main): normalize runtime composer contracts
This commit is contained in:
@@ -8,27 +8,28 @@ import {
|
||||
createNotifyAnilistSetupHandler,
|
||||
createRegisterSubminerProtocolClientHandler,
|
||||
} from '../domains/anilist';
|
||||
import type { ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
type NotifyHandler = ReturnType<typeof createNotifyAnilistSetupHandler>;
|
||||
type ConsumeHandler = ReturnType<typeof createConsumeAnilistSetupTokenFromUrlHandler>;
|
||||
type HandleProtocolHandler = ReturnType<typeof createHandleAnilistSetupProtocolUrlHandler>;
|
||||
type RegisterClientHandler = ReturnType<typeof createRegisterSubminerProtocolClientHandler>;
|
||||
|
||||
export type AnilistSetupComposerOptions = {
|
||||
export type AnilistSetupComposerOptions = ComposerInputs<{
|
||||
notifyDeps: Parameters<typeof createBuildNotifyAnilistSetupMainDepsHandler>[0];
|
||||
consumeTokenDeps: Parameters<typeof createBuildConsumeAnilistSetupTokenFromUrlMainDepsHandler>[0];
|
||||
handleProtocolDeps: Parameters<typeof createBuildHandleAnilistSetupProtocolUrlMainDepsHandler>[0];
|
||||
registerProtocolClientDeps: Parameters<
|
||||
typeof createBuildRegisterSubminerProtocolClientMainDepsHandler
|
||||
>[0];
|
||||
};
|
||||
}>;
|
||||
|
||||
export type AnilistSetupComposerResult = {
|
||||
export type AnilistSetupComposerResult = ComposerOutputs<{
|
||||
notifyAnilistSetup: NotifyHandler;
|
||||
consumeAnilistSetupTokenFromUrl: ConsumeHandler;
|
||||
handleAnilistSetupProtocolUrl: HandleProtocolHandler;
|
||||
registerSubminerProtocolClient: RegisterClientHandler;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function composeAnilistSetupHandlers(
|
||||
options: AnilistSetupComposerOptions,
|
||||
|
||||
@@ -20,8 +20,9 @@ import {
|
||||
createResetAnilistMediaTrackingHandler,
|
||||
createSetAnilistMediaGuessRuntimeStateHandler,
|
||||
} from '../domains/anilist';
|
||||
import type { ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
export type AnilistTrackingComposerOptions = {
|
||||
export type AnilistTrackingComposerOptions = ComposerInputs<{
|
||||
refreshClientSecretMainDeps: Parameters<
|
||||
typeof createBuildRefreshAnilistClientSecretStateMainDepsHandler
|
||||
>[0];
|
||||
@@ -50,9 +51,9 @@ export type AnilistTrackingComposerOptions = {
|
||||
maybeRunPostWatchUpdateMainDeps: Parameters<
|
||||
typeof createBuildMaybeRunAnilistPostWatchUpdateMainDepsHandler
|
||||
>[0];
|
||||
};
|
||||
}>;
|
||||
|
||||
export type AnilistTrackingComposerResult = {
|
||||
export type AnilistTrackingComposerResult = ComposerOutputs<{
|
||||
refreshAnilistClientSecretState: ReturnType<typeof createRefreshAnilistClientSecretStateHandler>;
|
||||
getCurrentAnilistMediaKey: ReturnType<typeof createGetCurrentAnilistMediaKeyHandler>;
|
||||
resetAnilistMediaTracking: ReturnType<typeof createResetAnilistMediaTrackingHandler>;
|
||||
@@ -67,7 +68,7 @@ export type AnilistTrackingComposerResult = {
|
||||
ensureAnilistMediaGuess: ReturnType<typeof createEnsureAnilistMediaGuessHandler>;
|
||||
processNextAnilistRetryUpdate: ReturnType<typeof createProcessNextAnilistRetryUpdateHandler>;
|
||||
maybeRunAnilistPostWatchUpdate: ReturnType<typeof createMaybeRunAnilistPostWatchUpdateHandler>;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function composeAnilistTrackingHandlers(
|
||||
options: AnilistTrackingComposerOptions,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { createCriticalConfigErrorHandler, createReloadConfigHandler } from '../startup-config';
|
||||
import { createBuildImmersionTrackerStartupMainDepsHandler } from '../immersion-startup-main-deps';
|
||||
import { createImmersionTrackerStartupHandler } from '../immersion-startup';
|
||||
import type { ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
type ReloadConfigMainDeps = Parameters<typeof createBuildReloadConfigMainDepsHandler>[0];
|
||||
type CriticalConfigErrorMainDeps = Parameters<
|
||||
@@ -14,20 +15,20 @@ type CriticalConfigErrorMainDeps = Parameters<
|
||||
>[0];
|
||||
type AppReadyRuntimeMainDeps = Parameters<typeof createBuildAppReadyRuntimeMainDepsHandler>[0];
|
||||
|
||||
export type AppReadyComposerOptions = {
|
||||
export type AppReadyComposerOptions = ComposerInputs<{
|
||||
reloadConfigMainDeps: ReloadConfigMainDeps;
|
||||
criticalConfigErrorMainDeps: CriticalConfigErrorMainDeps;
|
||||
appReadyRuntimeMainDeps: Omit<AppReadyRuntimeMainDeps, 'reloadConfig' | 'onCriticalConfigErrors'>;
|
||||
immersionTrackerStartupMainDeps: Parameters<
|
||||
typeof createBuildImmersionTrackerStartupMainDepsHandler
|
||||
>[0];
|
||||
};
|
||||
}>;
|
||||
|
||||
export type AppReadyComposerResult = {
|
||||
export type AppReadyComposerResult = ComposerOutputs<{
|
||||
reloadConfig: ReturnType<typeof createReloadConfigHandler>;
|
||||
criticalConfigError: ReturnType<typeof createCriticalConfigErrorHandler>;
|
||||
appReadyRuntimeRunner: ReturnType<typeof createAppReadyRuntimeRunner>;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function composeAppReadyRuntime(options: AppReadyComposerOptions): AppReadyComposerResult {
|
||||
const reloadConfig = createReloadConfigHandler(
|
||||
|
||||
95
src/main/runtime/composers/composer-contracts.type-test.ts
Normal file
95
src/main/runtime/composers/composer-contracts.type-test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { ComposerInputs } from './contracts';
|
||||
import type { IpcRuntimeComposerOptions } from './ipc-runtime-composer';
|
||||
import type { JellyfinRemoteComposerOptions } from './jellyfin-remote-composer';
|
||||
import type { MpvRuntimeComposerOptions } from './mpv-runtime-composer';
|
||||
import type { AnilistSetupComposerOptions } from './anilist-setup-composer';
|
||||
|
||||
type Assert<T extends true> = T;
|
||||
type IsAssignable<From, To> = [From] extends [To] ? true : false;
|
||||
|
||||
type FakeMpvClient = {
|
||||
on: (...args: unknown[]) => unknown;
|
||||
connect: () => void;
|
||||
};
|
||||
|
||||
type FakeTokenizerDeps = { isKnownWord: (text: string) => boolean };
|
||||
type FakeTokenizedSubtitle = { text: string };
|
||||
|
||||
type RequiredAnilistSetupInputKeys = keyof ComposerInputs<AnilistSetupComposerOptions>;
|
||||
type RequiredJellyfinInputKeys = keyof ComposerInputs<JellyfinRemoteComposerOptions>;
|
||||
type RequiredIpcInputKeys = keyof ComposerInputs<IpcRuntimeComposerOptions>;
|
||||
type RequiredMpvInputKeys = keyof ComposerInputs<
|
||||
MpvRuntimeComposerOptions<FakeMpvClient, FakeTokenizerDeps, FakeTokenizedSubtitle>
|
||||
>;
|
||||
|
||||
type _anilistHasNotifyDeps = Assert<IsAssignable<'notifyDeps', RequiredAnilistSetupInputKeys>>;
|
||||
type _jellyfinHasGetMpvClient = Assert<IsAssignable<'getMpvClient', RequiredJellyfinInputKeys>>;
|
||||
type _ipcHasRegistration = Assert<IsAssignable<'registration', RequiredIpcInputKeys>>;
|
||||
type _mpvHasTokenizer = Assert<IsAssignable<'tokenizer', RequiredMpvInputKeys>>;
|
||||
|
||||
// @ts-expect-error missing required notifyDeps should fail compile-time contract
|
||||
const anilistMissingRequired: AnilistSetupComposerOptions = {
|
||||
consumeTokenDeps: {} as AnilistSetupComposerOptions['consumeTokenDeps'],
|
||||
handleProtocolDeps: {} as AnilistSetupComposerOptions['handleProtocolDeps'],
|
||||
registerProtocolClientDeps: {} as AnilistSetupComposerOptions['registerProtocolClientDeps'],
|
||||
};
|
||||
|
||||
// @ts-expect-error missing required getMpvClient should fail compile-time contract
|
||||
const jellyfinMissingRequired: JellyfinRemoteComposerOptions = {
|
||||
getConfiguredSession: {} as JellyfinRemoteComposerOptions['getConfiguredSession'],
|
||||
getClientInfo: {} as JellyfinRemoteComposerOptions['getClientInfo'],
|
||||
getJellyfinConfig: {} as JellyfinRemoteComposerOptions['getJellyfinConfig'],
|
||||
playJellyfinItem: {} as JellyfinRemoteComposerOptions['playJellyfinItem'],
|
||||
logWarn: {} as JellyfinRemoteComposerOptions['logWarn'],
|
||||
sendMpvCommand: {} as JellyfinRemoteComposerOptions['sendMpvCommand'],
|
||||
jellyfinTicksToSeconds: {} as JellyfinRemoteComposerOptions['jellyfinTicksToSeconds'],
|
||||
getActivePlayback: {} as JellyfinRemoteComposerOptions['getActivePlayback'],
|
||||
clearActivePlayback: {} as JellyfinRemoteComposerOptions['clearActivePlayback'],
|
||||
getSession: {} as JellyfinRemoteComposerOptions['getSession'],
|
||||
getNow: {} as JellyfinRemoteComposerOptions['getNow'],
|
||||
getLastProgressAtMs: {} as JellyfinRemoteComposerOptions['getLastProgressAtMs'],
|
||||
setLastProgressAtMs: {} as JellyfinRemoteComposerOptions['setLastProgressAtMs'],
|
||||
progressIntervalMs: 3000,
|
||||
ticksPerSecond: 10_000_000,
|
||||
logDebug: {} as JellyfinRemoteComposerOptions['logDebug'],
|
||||
};
|
||||
|
||||
// @ts-expect-error missing required registration should fail compile-time contract
|
||||
const ipcMissingRequired: IpcRuntimeComposerOptions = {
|
||||
mpvCommandMainDeps: {} as IpcRuntimeComposerOptions['mpvCommandMainDeps'],
|
||||
handleMpvCommandFromIpcRuntime: {} as IpcRuntimeComposerOptions['handleMpvCommandFromIpcRuntime'],
|
||||
runSubsyncManualFromIpc: {} as IpcRuntimeComposerOptions['runSubsyncManualFromIpc'],
|
||||
};
|
||||
|
||||
// @ts-expect-error missing required tokenizer should fail compile-time contract
|
||||
const mpvMissingRequired: MpvRuntimeComposerOptions<
|
||||
FakeMpvClient,
|
||||
FakeTokenizerDeps,
|
||||
FakeTokenizedSubtitle
|
||||
> = {
|
||||
bindMpvMainEventHandlersMainDeps: {} as MpvRuntimeComposerOptions<
|
||||
FakeMpvClient,
|
||||
FakeTokenizerDeps,
|
||||
FakeTokenizedSubtitle
|
||||
>['bindMpvMainEventHandlersMainDeps'],
|
||||
mpvClientRuntimeServiceFactoryMainDeps: {} as MpvRuntimeComposerOptions<
|
||||
FakeMpvClient,
|
||||
FakeTokenizerDeps,
|
||||
FakeTokenizedSubtitle
|
||||
>['mpvClientRuntimeServiceFactoryMainDeps'],
|
||||
updateMpvSubtitleRenderMetricsMainDeps: {} as MpvRuntimeComposerOptions<
|
||||
FakeMpvClient,
|
||||
FakeTokenizerDeps,
|
||||
FakeTokenizedSubtitle
|
||||
>['updateMpvSubtitleRenderMetricsMainDeps'],
|
||||
warmups: {} as MpvRuntimeComposerOptions<
|
||||
FakeMpvClient,
|
||||
FakeTokenizerDeps,
|
||||
FakeTokenizedSubtitle
|
||||
>['warmups'],
|
||||
};
|
||||
|
||||
void anilistMissingRequired;
|
||||
void jellyfinMissingRequired;
|
||||
void ipcMissingRequired;
|
||||
void mpvMissingRequired;
|
||||
13
src/main/runtime/composers/contracts.ts
Normal file
13
src/main/runtime/composers/contracts.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
type ComposerShape = Record<string, unknown>;
|
||||
|
||||
export type ComposerInputs<T extends ComposerShape> = Readonly<Required<T>>;
|
||||
|
||||
export type ComposerOutputs<T extends ComposerShape> = Readonly<T>;
|
||||
|
||||
export type BuiltMainDeps<TFactory> = TFactory extends (
|
||||
...args: infer _TFactoryArgs
|
||||
) => infer TBuilder
|
||||
? TBuilder extends (...args: infer _TBuilderArgs) => infer TDeps
|
||||
? TDeps
|
||||
: never
|
||||
: never;
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './anilist-setup-composer';
|
||||
export * from './anilist-tracking-composer';
|
||||
export * from './app-ready-composer';
|
||||
export * from './contracts';
|
||||
export * from './ipc-runtime-composer';
|
||||
export * from './jellyfin-remote-composer';
|
||||
export * from './mpv-runtime-composer';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { composeIpcRuntimeHandlers } from './ipc-runtime-composer';
|
||||
test('composeIpcRuntimeHandlers returns callable IPC handlers and registration bridge', async () => {
|
||||
let registered = false;
|
||||
|
||||
const composed = composeIpcRuntimeHandlers<{ value: number }, { ok: boolean; received: number }>({
|
||||
const composed = composeIpcRuntimeHandlers({
|
||||
mpvCommandMainDeps: {
|
||||
triggerSubsyncFromConfig: async () => {},
|
||||
openRuntimeOptionsPalette: () => {},
|
||||
@@ -18,7 +18,10 @@ test('composeIpcRuntimeHandlers returns callable IPC handlers and registration b
|
||||
hasRuntimeOptionsManager: () => true,
|
||||
},
|
||||
handleMpvCommandFromIpcRuntime: () => {},
|
||||
runSubsyncManualFromIpc: async (request) => ({ ok: true, received: request.value }),
|
||||
runSubsyncManualFromIpc: async (request) => ({
|
||||
ok: true,
|
||||
received: (request as { value: number }).value,
|
||||
}),
|
||||
registration: {
|
||||
runtimeOptions: {
|
||||
getRuntimeOptionsManager: () => null,
|
||||
@@ -89,7 +92,10 @@ test('composeIpcRuntimeHandlers returns callable IPC handlers and registration b
|
||||
assert.equal(typeof composed.runSubsyncManualFromIpc, 'function');
|
||||
assert.equal(typeof composed.registerIpcRuntimeHandlers, 'function');
|
||||
|
||||
const result = await composed.runSubsyncManualFromIpc({ value: 7 });
|
||||
const result = (await composed.runSubsyncManualFromIpc({ value: 7 })) as {
|
||||
ok: boolean;
|
||||
received: number;
|
||||
};
|
||||
assert.deepEqual(result, { ok: true, received: 7 });
|
||||
|
||||
composed.registerIpcRuntimeHandlers();
|
||||
|
||||
@@ -3,48 +3,43 @@ import {
|
||||
createBuildMpvCommandFromIpcRuntimeMainDepsHandler,
|
||||
createIpcRuntimeHandlers,
|
||||
} from '../domains/ipc';
|
||||
import type { ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
type MpvCommand = (string | number)[];
|
||||
|
||||
type IpcMainDepsWithoutHandlers = Omit<
|
||||
RegisterIpcRuntimeServicesParams['mainDeps'],
|
||||
'handleMpvCommand' | 'runSubsyncManual'
|
||||
>;
|
||||
type IpcMainDeps = RegisterIpcRuntimeServicesParams['mainDeps'];
|
||||
type IpcMainDepsWithoutHandlers = Omit<IpcMainDeps, 'handleMpvCommand' | 'runSubsyncManual'>;
|
||||
type RunSubsyncManual = IpcMainDeps['runSubsyncManual'];
|
||||
|
||||
type IpcRuntimeDeps<TRequest, TResult> = Parameters<
|
||||
typeof createIpcRuntimeHandlers<TRequest, TResult>
|
||||
>[0];
|
||||
type IpcRuntimeDeps = Parameters<typeof createIpcRuntimeHandlers<unknown, unknown>>[0];
|
||||
|
||||
export type IpcRuntimeComposerOptions<TRequest, TResult> = {
|
||||
export type IpcRuntimeComposerOptions = ComposerInputs<{
|
||||
mpvCommandMainDeps: Parameters<typeof createBuildMpvCommandFromIpcRuntimeMainDepsHandler>[0];
|
||||
handleMpvCommandFromIpcRuntime: IpcRuntimeDeps<
|
||||
TRequest,
|
||||
TResult
|
||||
>['handleMpvCommandFromIpcDeps']['handleMpvCommandFromIpcRuntime'];
|
||||
runSubsyncManualFromIpc: (request: TRequest) => Promise<TResult>;
|
||||
handleMpvCommandFromIpcRuntime: IpcRuntimeDeps['handleMpvCommandFromIpcDeps']['handleMpvCommandFromIpcRuntime'];
|
||||
runSubsyncManualFromIpc: RunSubsyncManual;
|
||||
registration: {
|
||||
runtimeOptions: RegisterIpcRuntimeServicesParams['runtimeOptions'];
|
||||
mainDeps: IpcMainDepsWithoutHandlers;
|
||||
ankiJimakuDeps: RegisterIpcRuntimeServicesParams['ankiJimakuDeps'];
|
||||
registerIpcRuntimeServices: (params: RegisterIpcRuntimeServicesParams) => void;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
export type IpcRuntimeComposerResult<TRequest, TResult> = {
|
||||
export type IpcRuntimeComposerResult = ComposerOutputs<{
|
||||
handleMpvCommandFromIpc: (command: MpvCommand) => void;
|
||||
runSubsyncManualFromIpc: (request: TRequest) => Promise<TResult>;
|
||||
runSubsyncManualFromIpc: RunSubsyncManual;
|
||||
registerIpcRuntimeHandlers: () => void;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function composeIpcRuntimeHandlers<TRequest, TResult>(
|
||||
options: IpcRuntimeComposerOptions<TRequest, TResult>,
|
||||
): IpcRuntimeComposerResult<TRequest, TResult> {
|
||||
export function composeIpcRuntimeHandlers(
|
||||
options: IpcRuntimeComposerOptions,
|
||||
): IpcRuntimeComposerResult {
|
||||
const mpvCommandFromIpcRuntimeMainDeps = createBuildMpvCommandFromIpcRuntimeMainDepsHandler(
|
||||
options.mpvCommandMainDeps,
|
||||
)();
|
||||
const { handleMpvCommandFromIpc, runSubsyncManualFromIpc } = createIpcRuntimeHandlers<
|
||||
TRequest,
|
||||
TResult
|
||||
unknown,
|
||||
unknown
|
||||
>({
|
||||
handleMpvCommandFromIpcDeps: {
|
||||
handleMpvCommandFromIpcRuntime: options.handleMpvCommandFromIpcRuntime,
|
||||
@@ -61,7 +56,7 @@ export function composeIpcRuntimeHandlers<TRequest, TResult>(
|
||||
mainDeps: {
|
||||
...options.registration.mainDeps,
|
||||
handleMpvCommand: (command) => handleMpvCommandFromIpc(command),
|
||||
runSubsyncManual: (request) => runSubsyncManualFromIpc(request as TRequest),
|
||||
runSubsyncManual: (request: unknown) => runSubsyncManualFromIpc(request),
|
||||
},
|
||||
ankiJimakuDeps: options.registration.ankiJimakuDeps,
|
||||
});
|
||||
|
||||
@@ -10,26 +10,41 @@ import {
|
||||
createReportJellyfinRemoteProgressHandler,
|
||||
createReportJellyfinRemoteStoppedHandler,
|
||||
} from '../domains/jellyfin';
|
||||
import type { ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
type RemotePlayPayload = Parameters<ReturnType<typeof createHandleJellyfinRemotePlay>>[0];
|
||||
type RemotePlaystatePayload = Parameters<ReturnType<typeof createHandleJellyfinRemotePlaystate>>[0];
|
||||
type RemoteGeneralPayload = Parameters<ReturnType<typeof createHandleJellyfinRemoteGeneralCommand>>[0];
|
||||
type RemoteGeneralPayload = Parameters<
|
||||
ReturnType<typeof createHandleJellyfinRemoteGeneralCommand>
|
||||
>[0];
|
||||
type JellyfinRemotePlayMainDeps = Parameters<
|
||||
typeof createBuildHandleJellyfinRemotePlayMainDepsHandler
|
||||
>[0];
|
||||
type JellyfinRemotePlaystateMainDeps = Parameters<
|
||||
typeof createBuildHandleJellyfinRemotePlaystateMainDepsHandler
|
||||
>[0];
|
||||
type JellyfinRemoteGeneralMainDeps = Parameters<
|
||||
typeof createBuildHandleJellyfinRemoteGeneralCommandMainDepsHandler
|
||||
>[0];
|
||||
type JellyfinRemoteProgressMainDeps = Parameters<
|
||||
typeof createBuildReportJellyfinRemoteProgressMainDepsHandler
|
||||
>[0];
|
||||
|
||||
export type JellyfinRemoteComposerOptions = {
|
||||
getConfiguredSession: Parameters<typeof createBuildHandleJellyfinRemotePlayMainDepsHandler>[0]['getConfiguredSession'];
|
||||
getClientInfo: Parameters<typeof createBuildHandleJellyfinRemotePlayMainDepsHandler>[0]['getClientInfo'];
|
||||
getJellyfinConfig: Parameters<typeof createBuildHandleJellyfinRemotePlayMainDepsHandler>[0]['getJellyfinConfig'];
|
||||
playJellyfinItem: Parameters<typeof createBuildHandleJellyfinRemotePlayMainDepsHandler>[0]['playJellyfinItem'];
|
||||
logWarn: Parameters<typeof createBuildHandleJellyfinRemotePlayMainDepsHandler>[0]['logWarn'];
|
||||
getMpvClient: Parameters<typeof createBuildReportJellyfinRemoteProgressMainDepsHandler>[0]['getMpvClient'];
|
||||
sendMpvCommand: Parameters<typeof createBuildHandleJellyfinRemotePlaystateMainDepsHandler>[0]['sendMpvCommand'];
|
||||
export type JellyfinRemoteComposerOptions = ComposerInputs<{
|
||||
getConfiguredSession: JellyfinRemotePlayMainDeps['getConfiguredSession'];
|
||||
getClientInfo: JellyfinRemotePlayMainDeps['getClientInfo'];
|
||||
getJellyfinConfig: JellyfinRemotePlayMainDeps['getJellyfinConfig'];
|
||||
playJellyfinItem: JellyfinRemotePlayMainDeps['playJellyfinItem'];
|
||||
logWarn: JellyfinRemotePlayMainDeps['logWarn'];
|
||||
getMpvClient: JellyfinRemoteProgressMainDeps['getMpvClient'];
|
||||
sendMpvCommand: JellyfinRemotePlaystateMainDeps['sendMpvCommand'];
|
||||
jellyfinTicksToSeconds: Parameters<
|
||||
typeof createBuildHandleJellyfinRemotePlaystateMainDepsHandler
|
||||
>[0]['jellyfinTicksToSeconds'];
|
||||
getActivePlayback: Parameters<typeof createBuildHandleJellyfinRemoteGeneralCommandMainDepsHandler>[0]['getActivePlayback'];
|
||||
clearActivePlayback: Parameters<typeof createBuildReportJellyfinRemoteProgressMainDepsHandler>[0]['clearActivePlayback'];
|
||||
getSession: Parameters<typeof createBuildReportJellyfinRemoteProgressMainDepsHandler>[0]['getSession'];
|
||||
getNow: Parameters<typeof createBuildReportJellyfinRemoteProgressMainDepsHandler>[0]['getNow'];
|
||||
getActivePlayback: JellyfinRemoteGeneralMainDeps['getActivePlayback'];
|
||||
clearActivePlayback: JellyfinRemoteProgressMainDeps['clearActivePlayback'];
|
||||
getSession: JellyfinRemoteProgressMainDeps['getSession'];
|
||||
getNow: JellyfinRemoteProgressMainDeps['getNow'];
|
||||
getLastProgressAtMs: Parameters<
|
||||
typeof createBuildReportJellyfinRemoteProgressMainDepsHandler
|
||||
>[0]['getLastProgressAtMs'];
|
||||
@@ -38,16 +53,18 @@ export type JellyfinRemoteComposerOptions = {
|
||||
>[0]['setLastProgressAtMs'];
|
||||
progressIntervalMs: number;
|
||||
ticksPerSecond: number;
|
||||
logDebug: Parameters<typeof createBuildReportJellyfinRemoteProgressMainDepsHandler>[0]['logDebug'];
|
||||
};
|
||||
logDebug: Parameters<
|
||||
typeof createBuildReportJellyfinRemoteProgressMainDepsHandler
|
||||
>[0]['logDebug'];
|
||||
}>;
|
||||
|
||||
export type JellyfinRemoteComposerResult = {
|
||||
export type JellyfinRemoteComposerResult = ComposerOutputs<{
|
||||
reportJellyfinRemoteProgress: ReturnType<typeof createReportJellyfinRemoteProgressHandler>;
|
||||
reportJellyfinRemoteStopped: ReturnType<typeof createReportJellyfinRemoteStoppedHandler>;
|
||||
handleJellyfinRemotePlay: (payload: RemotePlayPayload) => Promise<void>;
|
||||
handleJellyfinRemotePlaystate: (payload: RemotePlaystatePayload) => Promise<void>;
|
||||
handleJellyfinRemoteGeneralCommand: (payload: RemoteGeneralPayload) => Promise<void>;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function composeJellyfinRemoteHandlers(
|
||||
options: JellyfinRemoteComposerOptions,
|
||||
@@ -89,9 +106,7 @@ export function composeJellyfinRemoteHandlers(
|
||||
});
|
||||
const buildHandleJellyfinRemotePlaystateMainDepsHandler =
|
||||
createBuildHandleJellyfinRemotePlaystateMainDepsHandler({
|
||||
getMpvClient: options.getMpvClient as Parameters<
|
||||
typeof createBuildHandleJellyfinRemotePlaystateMainDepsHandler
|
||||
>[0]['getMpvClient'],
|
||||
getMpvClient: options.getMpvClient,
|
||||
sendMpvCommand: options.sendMpvCommand,
|
||||
reportJellyfinRemoteProgress: (force) => reportJellyfinRemoteProgress(force),
|
||||
reportJellyfinRemoteStopped: () => reportJellyfinRemoteStopped(),
|
||||
@@ -99,9 +114,7 @@ export function composeJellyfinRemoteHandlers(
|
||||
});
|
||||
const buildHandleJellyfinRemoteGeneralCommandMainDepsHandler =
|
||||
createBuildHandleJellyfinRemoteGeneralCommandMainDepsHandler({
|
||||
getMpvClient: options.getMpvClient as Parameters<
|
||||
typeof createBuildHandleJellyfinRemoteGeneralCommandMainDepsHandler
|
||||
>[0]['getMpvClient'],
|
||||
getMpvClient: options.getMpvClient,
|
||||
sendMpvCommand: options.sendMpvCommand,
|
||||
getActivePlayback: options.getActivePlayback,
|
||||
reportJellyfinRemoteProgress: (force) => reportJellyfinRemoteProgress(force),
|
||||
|
||||
@@ -48,6 +48,7 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
||||
}
|
||||
|
||||
const composed = composeMpvRuntimeHandlers<
|
||||
FakeMpvClient,
|
||||
{ isKnownWord: (text: string) => boolean },
|
||||
{ text: string }
|
||||
>({
|
||||
@@ -189,7 +190,7 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
||||
assert.equal(typeof composed.launchBackgroundWarmupTask, 'function');
|
||||
assert.equal(typeof composed.startBackgroundWarmups, 'function');
|
||||
|
||||
const client = composed.createMpvClientRuntimeService() as FakeMpvClient;
|
||||
const client = composed.createMpvClientRuntimeService();
|
||||
assert.equal(client.connected, true);
|
||||
|
||||
composed.updateMpvSubtitleRenderMetrics({ subPos: 90 });
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createBindMpvMainEventHandlersHandler } from '../mpv-main-event-binding
|
||||
import { createBuildBindMpvMainEventHandlersMainDepsHandler } from '../mpv-main-event-main-deps';
|
||||
import { createBuildMpvClientRuntimeServiceFactoryDepsHandler } from '../mpv-client-runtime-service-main-deps';
|
||||
import { createMpvClientRuntimeServiceFactory } from '../mpv-client-runtime-service';
|
||||
import type { MpvClientRuntimeServiceOptions } from '../mpv-client-runtime-service';
|
||||
import { createBuildUpdateMpvSubtitleRenderMetricsMainDepsHandler } from '../mpv-subtitle-render-metrics-main-deps';
|
||||
import { createUpdateMpvSubtitleRenderMetricsHandler } from '../mpv-subtitle-render-metrics';
|
||||
import {
|
||||
@@ -17,19 +18,29 @@ import {
|
||||
createLaunchBackgroundWarmupTaskHandler as createLaunchBackgroundWarmupTaskFromStartup,
|
||||
createStartBackgroundWarmupsHandler as createStartBackgroundWarmupsFromStartup,
|
||||
} from '../startup-warmups';
|
||||
import type { BuiltMainDeps, ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
type BindMpvMainEventHandlersMainDeps = Parameters<
|
||||
typeof createBuildBindMpvMainEventHandlersMainDepsHandler
|
||||
>[0];
|
||||
type MpvClientRuntimeServiceFactoryMainDeps = Omit<
|
||||
Parameters<typeof createBuildMpvClientRuntimeServiceFactoryDepsHandler>[0],
|
||||
type BindMpvMainEventHandlers = ReturnType<typeof createBindMpvMainEventHandlersHandler>;
|
||||
type BoundMpvClient = Parameters<BindMpvMainEventHandlers>[0];
|
||||
type RuntimeMpvClient = BoundMpvClient & { connect: () => void };
|
||||
type MpvClientRuntimeServiceFactoryMainDeps<TMpvClient extends RuntimeMpvClient> = Omit<
|
||||
Parameters<
|
||||
typeof createBuildMpvClientRuntimeServiceFactoryDepsHandler<
|
||||
TMpvClient,
|
||||
unknown,
|
||||
MpvClientRuntimeServiceOptions
|
||||
>
|
||||
>[0],
|
||||
'bindEventHandlers'
|
||||
>;
|
||||
type UpdateMpvSubtitleRenderMetricsMainDeps = Parameters<
|
||||
typeof createBuildUpdateMpvSubtitleRenderMetricsMainDepsHandler
|
||||
>[0];
|
||||
type BuildTokenizerDepsMainDeps = Parameters<typeof createBuildTokenizerDepsMainHandler>[0];
|
||||
type TokenizerMainDeps = ReturnType<ReturnType<typeof createBuildTokenizerDepsMainHandler>>;
|
||||
type TokenizerMainDeps = BuiltMainDeps<typeof createBuildTokenizerDepsMainHandler>;
|
||||
type CreateMecabTokenizerAndCheckMainDeps = Parameters<
|
||||
typeof createCreateMecabTokenizerAndCheckMainHandler
|
||||
>[0];
|
||||
@@ -44,9 +55,13 @@ type StartBackgroundWarmupsMainDeps = Omit<
|
||||
'launchTask' | 'createMecabTokenizerAndCheck' | 'prewarmSubtitleDictionaries'
|
||||
>;
|
||||
|
||||
export type MpvRuntimeComposerOptions<TTokenizerRuntimeDeps, TTokenizedSubtitle> = {
|
||||
export type MpvRuntimeComposerOptions<
|
||||
TMpvClient extends RuntimeMpvClient,
|
||||
TTokenizerRuntimeDeps,
|
||||
TTokenizedSubtitle,
|
||||
> = ComposerInputs<{
|
||||
bindMpvMainEventHandlersMainDeps: BindMpvMainEventHandlersMainDeps;
|
||||
mpvClientRuntimeServiceFactoryMainDeps: MpvClientRuntimeServiceFactoryMainDeps;
|
||||
mpvClientRuntimeServiceFactoryMainDeps: MpvClientRuntimeServiceFactoryMainDeps<TMpvClient>;
|
||||
updateMpvSubtitleRenderMetricsMainDeps: UpdateMpvSubtitleRenderMetricsMainDeps;
|
||||
tokenizer: {
|
||||
buildTokenizerDepsMainDeps: BuildTokenizerDepsMainDeps;
|
||||
@@ -59,22 +74,29 @@ export type MpvRuntimeComposerOptions<TTokenizerRuntimeDeps, TTokenizedSubtitle>
|
||||
launchBackgroundWarmupTaskMainDeps: LaunchBackgroundWarmupTaskMainDeps;
|
||||
startBackgroundWarmupsMainDeps: StartBackgroundWarmupsMainDeps;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
export type MpvRuntimeComposerResult<TTokenizedSubtitle> = {
|
||||
bindMpvClientEventHandlers: ReturnType<typeof createBindMpvMainEventHandlersHandler>;
|
||||
createMpvClientRuntimeService: () => unknown;
|
||||
export type MpvRuntimeComposerResult<
|
||||
TMpvClient extends RuntimeMpvClient,
|
||||
TTokenizedSubtitle,
|
||||
> = ComposerOutputs<{
|
||||
bindMpvClientEventHandlers: BindMpvMainEventHandlers;
|
||||
createMpvClientRuntimeService: () => TMpvClient;
|
||||
updateMpvSubtitleRenderMetrics: ReturnType<typeof createUpdateMpvSubtitleRenderMetricsHandler>;
|
||||
tokenizeSubtitle: (text: string) => Promise<TTokenizedSubtitle>;
|
||||
createMecabTokenizerAndCheck: () => Promise<void>;
|
||||
prewarmSubtitleDictionaries: () => Promise<void>;
|
||||
launchBackgroundWarmupTask: ReturnType<typeof createLaunchBackgroundWarmupTaskFromStartup>;
|
||||
startBackgroundWarmups: ReturnType<typeof createStartBackgroundWarmupsFromStartup>;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function composeMpvRuntimeHandlers<TTokenizerRuntimeDeps, TTokenizedSubtitle>(
|
||||
options: MpvRuntimeComposerOptions<TTokenizerRuntimeDeps, TTokenizedSubtitle>,
|
||||
): MpvRuntimeComposerResult<TTokenizedSubtitle> {
|
||||
export function composeMpvRuntimeHandlers<
|
||||
TMpvClient extends RuntimeMpvClient,
|
||||
TTokenizerRuntimeDeps,
|
||||
TTokenizedSubtitle,
|
||||
>(
|
||||
options: MpvRuntimeComposerOptions<TMpvClient, TTokenizerRuntimeDeps, TTokenizedSubtitle>,
|
||||
): MpvRuntimeComposerResult<TMpvClient, TTokenizedSubtitle> {
|
||||
const bindMpvMainEventHandlersMainDeps = createBuildBindMpvMainEventHandlersMainDepsHandler(
|
||||
options.bindMpvMainEventHandlersMainDeps,
|
||||
)();
|
||||
@@ -83,14 +105,16 @@ export function composeMpvRuntimeHandlers<TTokenizerRuntimeDeps, TTokenizedSubti
|
||||
);
|
||||
|
||||
const buildMpvClientRuntimeServiceFactoryMainDepsHandler =
|
||||
createBuildMpvClientRuntimeServiceFactoryDepsHandler({
|
||||
createBuildMpvClientRuntimeServiceFactoryDepsHandler<
|
||||
TMpvClient,
|
||||
unknown,
|
||||
MpvClientRuntimeServiceOptions
|
||||
>({
|
||||
...options.mpvClientRuntimeServiceFactoryMainDeps,
|
||||
bindEventHandlers: (client) => bindMpvClientEventHandlers(client as never),
|
||||
bindEventHandlers: (client) => bindMpvClientEventHandlers(client),
|
||||
});
|
||||
const createMpvClientRuntimeService = (): unknown =>
|
||||
createMpvClientRuntimeServiceFactory(
|
||||
buildMpvClientRuntimeServiceFactoryMainDepsHandler() as never,
|
||||
)();
|
||||
const createMpvClientRuntimeService = (): TMpvClient =>
|
||||
createMpvClientRuntimeServiceFactory(buildMpvClientRuntimeServiceFactoryMainDepsHandler())();
|
||||
|
||||
const updateMpvSubtitleRenderMetrics = createUpdateMpvSubtitleRenderMetricsHandler(
|
||||
createBuildUpdateMpvSubtitleRenderMetricsMainDepsHandler(
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
createNumericShortcutSessionRuntimeHandlers,
|
||||
createOverlayShortcutsRuntimeHandlers,
|
||||
} from '../domains/shortcuts';
|
||||
import type { ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
type GlobalShortcutsOptions = Parameters<typeof createGlobalShortcutsRuntimeHandlers>[0];
|
||||
type NumericShortcutRuntimeMainDeps = Parameters<
|
||||
@@ -18,18 +19,18 @@ type OverlayShortcutsMainDeps = Parameters<
|
||||
typeof createOverlayShortcutsRuntimeHandlers
|
||||
>[0]['overlayShortcutsRuntimeMainDeps'];
|
||||
|
||||
export type ShortcutsRuntimeComposerOptions = {
|
||||
export type ShortcutsRuntimeComposerOptions = ComposerInputs<{
|
||||
globalShortcuts: GlobalShortcutsOptions;
|
||||
numericShortcutRuntimeMainDeps: NumericShortcutRuntimeMainDeps;
|
||||
numericSessions: NumericSessionOptions;
|
||||
overlayShortcutsRuntimeMainDeps: OverlayShortcutsMainDeps;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type ShortcutsRuntimeComposerResult = ReturnType<
|
||||
typeof createGlobalShortcutsRuntimeHandlers
|
||||
> &
|
||||
ReturnType<typeof createNumericShortcutSessionRuntimeHandlers> &
|
||||
ReturnType<typeof createOverlayShortcutsRuntimeHandlers>;
|
||||
export type ShortcutsRuntimeComposerResult = ComposerOutputs<
|
||||
ReturnType<typeof createGlobalShortcutsRuntimeHandlers> &
|
||||
ReturnType<typeof createNumericShortcutSessionRuntimeHandlers> &
|
||||
ReturnType<typeof createOverlayShortcutsRuntimeHandlers>
|
||||
>;
|
||||
|
||||
export function composeShortcutRuntimes(
|
||||
options: ShortcutsRuntimeComposerOptions,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../app-lifecycle-main-activate';
|
||||
import { createBuildRegisterProtocolUrlHandlersMainDepsHandler } from '../protocol-url-handlers-main-deps';
|
||||
import { registerProtocolUrlHandlers } from '../protocol-url-handlers';
|
||||
import type { ComposerInputs, ComposerOutputs } from './contracts';
|
||||
|
||||
type RegisterProtocolUrlHandlersMainDeps = Parameters<
|
||||
typeof createBuildRegisterProtocolUrlHandlersMainDepsHandler
|
||||
@@ -22,19 +23,19 @@ type RestoreWindowsOnActivateMainDeps = Parameters<
|
||||
typeof createBuildRestoreWindowsOnActivateMainDepsHandler
|
||||
>[0];
|
||||
|
||||
export type StartupLifecycleComposerOptions = {
|
||||
export type StartupLifecycleComposerOptions = ComposerInputs<{
|
||||
registerProtocolUrlHandlersMainDeps: RegisterProtocolUrlHandlersMainDeps;
|
||||
onWillQuitCleanupMainDeps: OnWillQuitCleanupDeps;
|
||||
shouldRestoreWindowsOnActivateMainDeps: ShouldRestoreWindowsOnActivateMainDeps;
|
||||
restoreWindowsOnActivateMainDeps: RestoreWindowsOnActivateMainDeps;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type StartupLifecycleComposerResult = {
|
||||
export type StartupLifecycleComposerResult = ComposerOutputs<{
|
||||
registerProtocolUrlHandlers: () => void;
|
||||
onWillQuitCleanup: () => void;
|
||||
shouldRestoreWindowsOnActivate: () => boolean;
|
||||
restoreWindowsOnActivate: () => void;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function composeStartupLifecycleHandlers(
|
||||
options: StartupLifecycleComposerOptions,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type MpvClientCtorBaseOptions = {
|
||||
export type MpvClientRuntimeServiceOptions = {
|
||||
getResolvedConfig: () => unknown;
|
||||
autoStartOverlay: boolean;
|
||||
setOverlayVisible: (visible: boolean) => void;
|
||||
@@ -12,14 +12,14 @@ type MpvClientLike = {
|
||||
connect: () => void;
|
||||
};
|
||||
|
||||
type MpvClientCtor<TClient extends MpvClientLike, TOptions extends MpvClientCtorBaseOptions> = new (
|
||||
socketPath: string,
|
||||
options: TOptions,
|
||||
) => TClient;
|
||||
type MpvClientCtor<
|
||||
TClient extends MpvClientLike,
|
||||
TOptions extends MpvClientRuntimeServiceOptions,
|
||||
> = new (socketPath: string, options: TOptions) => TClient;
|
||||
|
||||
export function createMpvClientRuntimeServiceFactory<
|
||||
TClient extends MpvClientLike,
|
||||
TOptions extends MpvClientCtorBaseOptions,
|
||||
TOptions extends MpvClientRuntimeServiceOptions,
|
||||
>(deps: {
|
||||
createClient: MpvClientCtor<TClient, TOptions>;
|
||||
socketPath: string;
|
||||
|
||||
Reference in New Issue
Block a user