From b2bb1439ee4e3ea3655be435ffa5505b3df605cc Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Thu, 5 Feb 2026 14:26:27 -0800 Subject: [PATCH] mal api schema impl --- src/lib/types/mal/index.ts | 154 ++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 80 deletions(-) diff --git a/src/lib/types/mal/index.ts b/src/lib/types/mal/index.ts index 3279d57..59ee958 100644 --- a/src/lib/types/mal/index.ts +++ b/src/lib/types/mal/index.ts @@ -1,87 +1,81 @@ -/* +import { z } from "zod"; -query Parameters -status -string +export const MalAnimeListStatusEnum = z.enum([ + "watching", + "completed", + "on_hold", + "dropped", + "plan_to_watch", +]); +export type MalAnimeListStatus = z.infer; -Filters returned anime list by these statuses. +export const MalAnimeListSortEnum = z.enum([ + "list_score", + "list_updated_at", + "anime_title", + "anime_start_date", + "anime_id", +]); +export type MalAnimeListSort = z.infer; -To return all anime, don't specify this field. +const NumericQueryParamSchema = z + .union([z.number(), z.string()]) + .transform((v) => (typeof v === "string" ? Number(v) : v)); -Valid values: +export const MalAnimeListQuerySchema = z + .object({ + status: MalAnimeListStatusEnum.optional(), + sort: MalAnimeListSortEnum.optional(), + limit: NumericQueryParamSchema.pipe( + z.number().int().min(1).max(1000), + ).optional(), + offset: NumericQueryParamSchema.pipe(z.number().int().min(0)).optional(), + }) + .strict(); +export type MalAnimeListQuery = z.infer; - watching - completed - on_hold - dropped - plan_to_watch +export const MalAnimeEntry = z + .object({ + id: z.number().int().positive(), + title: z.string(), + main_picture: z + .object({ + medium: z.string(), + large: z.string(), + }) + .strict() + .optional(), + }) + .strict(); -sort -string +export const MalAnimeListEntry = z + .object({ + status: MalAnimeListStatusEnum, + score: z.number().int().min(0).max(10), + num_episodes_watched: z.number().int().min(0), + is_rewatching: z.boolean(), + updated_at: z.iso.datetime(), + start_date: z.iso.date().optional(), + finish_date: z.iso.date().optional(), + }) + .strict(); -Valid values: -Value Order -list_score Descending -list_updated_at Descending -anime_title Ascending -anime_start_date Descending -anime_id (Under Development) Ascending -limit -integer -Default: 100 - -The maximum value is 1000. -offset -integer -Default: 0 - -*/ - -/* -sample response - -{ - "data": [ - { - "node": { - "id": 31646, - "title": "3-gatsu no Lion", - "main_picture": { - "medium": "https://cdn.myanimelist.net/images/anime/3/82899.jpg", - "large": "https://cdn.myanimelist.net/images/anime/3/82899l.jpg" - } - }, - "list_status": { - "status": "watching", - "score": 0, - "num_episodes_watched": 2, - "is_rewatching": false, - "updated_at": "2025-07-17T00:57:26+00:00", - "start_date": "2025-07-16" - } - }, - { - "node": { - "id": 38101, - "title": "5-toubun no Hanayome", - "main_picture": { - "medium": "https://cdn.myanimelist.net/images/anime/1819/97947.jpg", - "large": "https://cdn.myanimelist.net/images/anime/1819/97947l.jpg" - } - }, - "list_status": { - "status": "completed", - "score": 0, - "num_episodes_watched": 12, - "is_rewatching": false, - "updated_at": "2019-06-02T09:12:42+00:00", - "start_date": "2019-05-31", - "finish_date": "2019-06-02" - } - } - ], - "paging": { - "next": "https://api.myanimelist.net/v2/users/CaZzzer/animelist?offset=2&fields=list_status&limit=2" - } -} -*/ +export const MalAnimeListResponseSchema = z + .object({ + data: z.array( + z + .object({ + node: MalAnimeEntry, + list_status: MalAnimeListEntry, + }) + .strict(), + ), + paging: z + .object({ + next: z.string().optional(), + previous: z.string().optional(), + }) + .strict(), + }) + .strict(); +export type MalAnimeListResponse = z.infer;