feat: Implemented emulator installation

feat: Updated romm API version
feat: Updated es-de rules
feat: Added tabs to game details
refactor: returned to global query definitions to help with typescript performance
This commit is contained in:
Simeon Radivoev 2026-03-22 01:11:21 +02:00
parent cf6fff6fac
commit 3750e9ed8f
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
103 changed files with 4888 additions and 1632 deletions

View file

@ -1,13 +1,21 @@
import Elysia from "elysia";
import z, { } from "zod";
import z, { _ZodType, ZodAny, ZodObject, ZodTypeAny } from "zod";
import { taskQueue } from "../app";
import { LoginJob } from "./login-job";
import TwitchLoginJob from "./twitch-login-job";
import UpdateStoreJob from "./update-store";
import { EmulatorDownloadJob } from "./emulator-download-job";
import { getErrorMessage } from "@/bun/utils";
import { IJob } from "../task-queue";
function registerJob<const Path extends string, TS, T extends { id: Path, dataSchema?: TS; }> (_job: T, path: Path, dataSchema: TS)
function registerJob<
const Path extends string,
const Schema extends ZodTypeAny,
const States extends string,
T extends IJob<z.infer<Schema>, States>
> (_job: { id: Path; dataSchema: Schema; } & (new (...args: any[]) => T))
{
return new Elysia().ws(path, {
return new Elysia().ws(_job.id, {
body: z.discriminatedUnion('type', [
z.object({ type: z.literal('cancel') })
]),
@ -16,14 +24,14 @@ function registerJob<const Path extends string, TS, T extends { id: Path, dataSc
type: z.literal(['data', 'started', 'progress']),
status: z.string(),
progress: z.number(),
data: dataSchema
data: _job.dataSchema
}),
z.object({ type: z.literal(['completed', 'ended']) }),
z.object({ type: z.literal('error'), error: z.unknown() })
z.object({ type: z.literal(['completed', 'ended']), data: _job.dataSchema }),
z.object({ type: z.literal('error'), error: z.string() })
]),
open (ws)
{
const job = taskQueue.findJob(path);
const job = taskQueue.findJob(_job.id, _job);
if (job)
{
ws.send({ type: 'data', status: job.status, progress: job.progress, data: job.job.exposeData?.() });
@ -32,30 +40,37 @@ function registerJob<const Path extends string, TS, T extends { id: Path, dataSc
(ws.data as any).cleanup = [
taskQueue.on('started', ({ id, job }) =>
{
if (id === path)
if (id === _job.id)
{
ws.send({ type: 'started', status: job.status, progress: job.progress, data: job.job.exposeData?.() });
}
}),
taskQueue.on('progress', ({ id, job }) =>
{
if (id === path)
if (id === _job.id)
{
ws.send({ type: 'started', status: job.status, progress: job.progress, data: job.job.exposeData?.() });
}
}),
taskQueue.on('completed', ({ id }) =>
taskQueue.on('completed', ({ id, job }) =>
{
if (id === path)
if (id === _job.id)
{
ws.send({ type: 'completed' });
ws.send({ type: 'completed', data: job.job.exposeData?.() });
}
}),
taskQueue.on('ended', ({ id, job }) =>
{
if (id === _job.id)
{
ws.send({ type: 'ended', data: job.job.exposeData?.() });
}
}),
taskQueue.on('error', ({ id, error }) =>
{
if (id === path)
if (id === _job.id)
{
ws.send({ type: 'error', error: error });
ws.send({ type: 'error', error: getErrorMessage(error) });
}
})
];
@ -68,13 +83,14 @@ function registerJob<const Path extends string, TS, T extends { id: Path, dataSc
{
if (message.type === 'cancel')
{
taskQueue.findJob(path)?.abort('cancel');
taskQueue.findJob(_job.id, _job)?.abort('cancel');
}
},
});
}
export const jobs = new Elysia({ prefix: '/api/jobs' })
.use(registerJob(LoginJob, LoginJob.id, LoginJob.dataSchema))
.use(registerJob(TwitchLoginJob, TwitchLoginJob.id, TwitchLoginJob.dataSchema))
.use(registerJob(UpdateStoreJob, UpdateStoreJob.id, undefined));
.use(registerJob(LoginJob))
.use(registerJob(TwitchLoginJob))
.use(registerJob(UpdateStoreJob))
.use(registerJob(EmulatorDownloadJob));