Add Zod schemas and types for CurseForge and Modrinth APIs

This commit is contained in:
CPTProgrammer 2026-07-06 07:25:08 +08:00
parent 307b0cd4d4
commit 8071c2f8a7
No known key found for this signature in database
9 changed files with 614 additions and 0 deletions

View File

@ -0,0 +1,199 @@
import { z } from "zod";
// region FileStatus — 文件状态
export const FileStatusSchema = z.union([
z.literal(1), // Processing
z.literal(4), // Approved
z.literal(10), // Released
z.literal(12), // Deprecated
]);
export type FileStatus = z.infer<typeof FileStatusSchema>;
// endregion
// region FileReleaseType — 文件的发布类型(数值枚举)
export const FileReleaseTypeSchema = z.union([
z.literal(1), // Release
z.literal(2), // Beta
z.literal(3), // Alpha
]);
export type FileReleaseType = z.infer<typeof FileReleaseTypeSchema>;
// endregion
// region HashAlgo — 哈希算法
export const HashAlgoSchema = z.union([
z.literal(1), // Sha1
z.literal(2), // Md5
]);
export type HashAlgo = z.infer<typeof HashAlgoSchema>;
// endregion
// region FileRelationType — 依赖关系类型(数值枚举)
export const FileRelationTypeSchema = z.union([
z.literal(1), // EmbeddedLibrary
z.literal(2), // OptionalDependency
z.literal(3), // RequiredDependency
z.literal(4), // Tool
z.literal(5), // Incompatible
z.literal(6), // Include
]);
export type FileRelationType = z.infer<typeof FileRelationTypeSchema>;
// endregion
// region ModLoaderType — Mod Loader 类型(查询参数用)
export const ModLoaderTypeSchema = z.union([
z.literal(0), // Any
z.literal(1), // Forge
z.literal(2), // Cauldron
z.literal(3), // LiteLoader
z.literal(4), // Fabric
z.literal(5), // Quilt
z.literal(6), // NeoForge
]);
export type ModLoaderType = z.infer<typeof ModLoaderTypeSchema>;
// endregion
// region FileHash — 文件哈希
export const FileHashSchema = z.object({
/** 哈希值 */
value: z.string(),
/** 哈希算法 */
algo: HashAlgoSchema,
});
export type FileHash = z.infer<typeof FileHashSchema>;
// endregion
// region SortableGameVersion — 排序用版本信息
export const SortableGameVersionSchema = z.object({
/** 游戏版本名称,如 "1.21.5" */
gameVersionName: z.string(),
/** 补齐前缀的游戏版本号,用于排序,如 "0000000001.0000000021.0000000005" */
gameVersionPadded: z.string(),
/** 游戏版本,如 "1.21.5" */
gameVersion: z.string(),
/** 游戏版本发布日期ISO-8601 */
gameVersionReleaseDate: z.string(),
/** 版本类型 ID对应 Game Version Types API 中的 id */
gameVersionTypeId: z.number().int(),
});
export type SortableGameVersion = z.infer<typeof SortableGameVersionSchema>;
// endregion
// region FileDependency — 文件依赖
export const FileDependencySchema = z.object({
/** 依赖的项目 ID */
modId: z.number().int(),
/** 关系类型 */
relationType: FileRelationTypeSchema,
});
export type FileDependency = z.infer<typeof FileDependencySchema>;
// endregion
// region FileModule — 文件模块
export const FileModuleSchema = z.object({
/** 模块名称 */
name: z.string(),
/** 模块指纹 */
fingerprint: z.number().int(),
});
export type FileModule = z.infer<typeof FileModuleSchema>;
// endregion
// region CurseForgeFile — CurseForge 文件完整对象
export const CurseForgeFileSchema = z.object({
/** 文件 ID */
id: z.number().int(),
/** 游戏 ID */
gameId: z.number().int(),
/** 所属项目 ID */
modId: z.number().int(),
/** 是否可下载 */
isAvailable: z.boolean(),
/** 显示名称 */
displayName: z.string(),
/** 文件名 */
fileName: z.string(),
/** 发布类型 */
releaseType: FileReleaseTypeSchema,
/** 文件状态 */
fileStatus: FileStatusSchema,
/** 文件哈希列表 */
hashes: z.array(FileHashSchema),
/** 文件时间戳ISO-8601 */
fileDate: z.string(),
/** 文件大小(字节) */
fileLength: z.number().int(),
/** 下载次数 */
downloadCount: z.number().int(),
/** 磁盘占用(字节) */
fileSizeOnDisk: z.number().int(),
/** 下载地址 */
downloadUrl: z.string(),
/** 关联的游戏版本(字符串列表) */
gameVersions: z.array(z.string()),
/** 排序用版本信息 */
sortableGameVersions: z.array(SortableGameVersionSchema),
/** 依赖关系列表 */
dependencies: z.array(FileDependencySchema),
/** 是否作为替代文件暴露 */
exposeAsAlternative: z.boolean(),
/** 父项目文件 ID无则为 null */
parentProjectFileId: z.number().int().nullable(),
/** 替代文件 ID无则为 null */
alternateFileId: z.number().int().nullable(),
/** 是否为服务端整合包 */
isServerPack: z.boolean(),
/** 服务端整合包文件 ID无则为 null */
serverPackFileId: z.number().int().nullable(),
/** 是否为抢先体验内容 */
isEarlyAccessContent: z.boolean(),
/** 抢先体验截止日期ISO-8601无则为 null */
earlyAccessEndDate: z.string().nullable(),
/** 文件指纹 */
fileFingerprint: z.number().int(),
/** 文件模块列表 */
modules: z.array(FileModuleSchema),
});
export type CurseForgeFile = z.infer<typeof CurseForgeFileSchema>;
// endregion
// region Pagination — 分页信息
export const PaginationSchema = z.object({
/** 本次返回的起始索引 */
index: z.number().int(),
/** 请求的页大小 */
pageSize: z.number().int(),
/** 本次实际返回数量 */
resultCount: z.number().int(),
/** 总文件数 */
totalCount: z.number().int(),
});
export type Pagination = z.infer<typeof PaginationSchema>;
// endregion
// region GetModFilesParams — GET /v1/mods/{modId}/files 查询参数
export const GetModFilesParamsSchema = z.object({
/** 项目 ID路径参数 */
modId: z.number().int(),
/** 按游戏版本过滤 */
gameVersion: z.string().optional(),
/** 按 Mod Loader 类型过滤 */
modLoaderType: ModLoaderTypeSchema.optional(),
/** 按 gameVersionTypeId 过滤 */
gameVersionTypeId: z.number().int().optional(),
/** 分页起始索引(从 0 开始) */
index: z.number().int().optional(),
/** 每页数量,默认/最大 50 */
pageSize: z.number().int().optional(),
});
export type GetModFilesParams = z.infer<typeof GetModFilesParamsSchema>;
// endregion
// region GetModFilesResponse — GET /v1/mods/{modId}/files 成功响应体
export const GetModFilesResponseSchema = z.object({
/** 文件列表 */
data: z.array(CurseForgeFileSchema),
/** 分页信息 */
pagination: PaginationSchema,
});
export type GetModFilesResponse = z.infer<typeof GetModFilesResponseSchema>;
// endregion

View File

@ -0,0 +1,29 @@
import { z } from "zod";
// region GameVersionType — GET /api/game/version-types 响应元素
export const GameVersionTypeSchema = z.object({
/** 版本类型 ID即 gameVersionTypeID */
id: z.number().int(),
/** 类型名称,如 "Modloader"、"Environment" */
name: z.string(),
/** 类型标识 */
slug: z.string(),
});
export type GameVersionType = z.infer<typeof GameVersionTypeSchema>;
// endregion
// region GameVersion — GET /api/game/versions 响应元素
export const GameVersionSchema = z.object({
/** 版本 ID用于上传时的 gameVersions 字段) */
id: z.number().int(),
/** 版本类型 ID对应 GameVersionType.id */
gameVersionTypeID: z.number().int(),
/** 版本名称,如 "1.21.5" */
name: z.string(),
/** 版本标识,如 "1-21-5" */
slug: z.string(),
/** API 版本号,无则为 null */
apiVersion: z.number().int().nullable(),
});
export type GameVersion = z.infer<typeof GameVersionSchema>;
// endregion

View File

@ -0,0 +1,54 @@
export {
FileStatusSchema,
FileReleaseTypeSchema,
HashAlgoSchema,
FileRelationTypeSchema,
ModLoaderTypeSchema,
FileHashSchema,
SortableGameVersionSchema,
FileDependencySchema,
FileModuleSchema,
CurseForgeFileSchema,
PaginationSchema,
GetModFilesParamsSchema,
GetModFilesResponseSchema,
type FileStatus,
type FileReleaseType,
type HashAlgo,
type FileRelationType,
type ModLoaderType,
type FileHash,
type SortableGameVersion,
type FileDependency,
type FileModule,
type CurseForgeFile,
type Pagination,
type GetModFilesParams,
type GetModFilesResponse,
} from "./files";
export {
GameVersionTypeSchema,
GameVersionSchema,
type GameVersionType,
type GameVersion,
} from "./game";
export {
UploadReleaseTypeSchema,
ChangelogTypeSchema,
UploadRelationTypeSchema,
RelationProjectSchema,
RelationsSchema,
UploadMetadataSchema,
UpdateMetadataSchema,
UploadResponseSchema,
type UploadReleaseType,
type ChangelogType,
type UploadRelationType,
type RelationProject,
type Relations,
type UploadMetadata,
type UpdateMetadata,
type UploadResponse,
} from "./upload";

View File

@ -0,0 +1,103 @@
import { z } from "zod";
// region UploadReleaseType — 上传时的发布类型(字符串枚举)
export const UploadReleaseTypeSchema = z.enum([
"alpha",
"beta",
"release",
]);
export type UploadReleaseType = z.infer<typeof UploadReleaseTypeSchema>;
// endregion
// region ChangelogType — 更新日志格式
export const ChangelogTypeSchema = z.enum([
"text",
"html",
"markdown",
]);
export type ChangelogType = z.infer<typeof ChangelogTypeSchema>;
// endregion
// region UploadRelationType — 上传时的依赖关系类型(字符串枚举)
export const UploadRelationTypeSchema = z.enum([
"embeddedLibrary",
"incompatible",
"optionalDependency",
"requiredDependency",
"tool",
]);
export type UploadRelationType = z.infer<typeof UploadRelationTypeSchema>;
// endregion
// region RelationProject — 关联项目
export const RelationProjectSchema = z.object({
/** 关联项目的 slug */
slug: z.string(),
/** 关联项目的 ID精确匹配可选 */
projectID: z.string().optional(),
/** 关联类型 */
type: UploadRelationTypeSchema,
});
export type RelationProject = z.infer<typeof RelationProjectSchema>;
// endregion
// region Relations — 关联项目集合
export const RelationsSchema = z.object({
projects: z.array(RelationProjectSchema),
});
export type Relations = z.infer<typeof RelationsSchema>;
// endregion
// region UploadMetadata — POST /api/projects/{projectId}/upload-file 的 metadata JSON
export const UploadMetadataSchema = z.object({
/** 更新日志内容,必填 */
changelog: z.string(),
/** 更新日志格式,默认 "text" */
changelogType: ChangelogTypeSchema.optional(),
/** 站点上显示的文件友好名称 */
displayName: z.string().optional(),
/** 父文件 ID */
parentFileID: z.number().int().optional(),
/**
* Game Version ID Game Versions API
* parentFileID
*/
gameVersions: z.array(z.number().int()).optional(),
/** 发布类型,必填 */
releaseType: UploadReleaseTypeSchema,
/** 若为 true审核通过后不会立即发布可手动选择发布时间 */
isMarkedForManualRelease: z.boolean().optional(),
/** 关联项目 */
relations: RelationsSchema.optional(),
});
export type UploadMetadata = z.infer<typeof UploadMetadataSchema>;
// endregion
// region UpdateMetadata — POST /api/projects/{projectId}/update-file 的 metadata JSON
export const UpdateMetadataSchema = z.object({
/** 要更新的文件 ID必填 */
fileID: z.number().int(),
/** 更新日志内容 */
changelog: z.string().optional(),
/** 更新日志格式 */
changelogType: ChangelogTypeSchema.optional(),
/** 站点上显示的文件友好名称 */
displayName: z.string().optional(),
/** 支持的 Game Version ID 列表 */
gameVersions: z.array(z.number().int()).optional(),
/** 发布类型 */
releaseType: UploadReleaseTypeSchema.optional(),
/** 关联项目 */
relations: RelationsSchema.optional(),
});
export type UpdateMetadata = z.infer<typeof UpdateMetadataSchema>;
// endregion
// region UploadResponse — 上传 / 更新文件的成功响应体
export const UploadResponseSchema = z.object({
/** 新建 / 被更新文件的 ID */
id: z.number().int(),
});
export type UploadResponse = z.infer<typeof UploadResponseSchema>;
// endregion

3
src/types/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from "./modrinth";
export * from "./curseforge";
// export * from "./config"; // TODO

View File

@ -0,0 +1,49 @@
import { z } from "zod";
import { BaseVersionSchema, FileTypeEnumSchema } from "./version";
// region CreatableVersion — 创建版本请求中的 data JSON
export const CreatableVersionSchema = BaseVersionSchema.extend({
/** 目标项目的 ID8 位 base62 */
project_id: z.string().length(8),
/** 所有承载文件的 multipart 字段名列表 */
file_parts: z.array(z.string()),
/** 主文件的 multipart 字段名;不提供时推断第一个文件为主文件 */
primary_file: z.string().optional(),
/** 目标环境 */
environment: z
.enum([
"unknown",
"client_only",
"server_only",
"dedicated_server_only",
"client_and_server",
"server_only_client_optional",
"client_only_server_optional",
"client_or_server_prefers_both",
"client_or_server",
"singleplayer_only",
])
.optional(),
/** multipart 字段名 → 文件类型的映射,用于标记 sources-jar 等附属文件 */
file_types: z.record(FileTypeEnumSchema).optional(),
});
export type CreatableVersion = z.infer<typeof CreatableVersionSchema>;
// endregion
// region CreateVersionBody — multipart/form-data 请求的最外层包装
export const CreateVersionBodySchema = z.object({
/** JSON 字符串形式的 CreatableVersion 元数据 */
data: CreatableVersionSchema,
});
export type CreateVersionBody = z.infer<typeof CreateVersionBodySchema>;
// endregion
/**
* POST /version multipart/form-data
*
* - `data`JSON {@link CreatableVersion}
* - `file_parts` / `primary_file` / `file_types`
*
* Zod schema `data`
*
*/

View File

@ -0,0 +1,31 @@
export {
LoaderTagSchema,
GameVersionTagSchema,
CategoryTagSchema,
type LoaderTag,
type GameVersionTag,
type CategoryTag,
type ProjectTypeTag,
} from "./tags";
export {
VersionDependencySchema,
FileTypeEnumSchema,
VersionFileHashesSchema,
VersionFileSchema,
BaseVersionSchema,
VersionSchema,
type VersionDependency,
type FileTypeEnum,
type VersionFileHashes,
type VersionFile,
type BaseVersion,
type Version,
} from "./version";
export {
CreatableVersionSchema,
CreateVersionBodySchema,
type CreatableVersion,
type CreateVersionBody,
} from "./create-version";

View File

@ -0,0 +1,46 @@
import { z } from "zod";
// region /tag/loader — 加载器列表
export const LoaderTagSchema = z.object({
/** 加载器的 SVG 图标 */
icon: z.string(),
/** 加载器名称,如 "fabric"、"forge" */
name: z.string(),
/** 该加载器适用的项目类型列表 */
supported_project_types: z.array(z.string()),
});
export type LoaderTag = z.infer<typeof LoaderTagSchema>;
// endregion
// region /tag/game_version — 游戏版本列表
export const GameVersionTagSchema = z.object({
/** 游戏版本号,如 "1.18.1" */
version: z.string(),
/** 版本类型release | snapshot | alpha | beta */
version_type: z.enum(["release", "snapshot", "alpha", "beta"]),
/** 版本发布日期ISO-8601 */
date: z.string().datetime(),
/** 是否为主要版本,用于 Featured Versions 标记 */
major: z.boolean(),
});
export type GameVersionTag = z.infer<typeof GameVersionTagSchema>;
// endregion
// region /tag/category — 分类标签列表
export const CategoryTagSchema = z.object({
/** 分类的 SVG 图标 */
icon: z.string(),
/** 分类名称 */
name: z.string(),
/** 该分类适用的项目类型 */
project_type: z.string(),
/** 该分类所属的标题分组 */
header: z.string(),
});
export type CategoryTag = z.infer<typeof CategoryTagSchema>;
// endregion
// region /tag/project_type — 项目类型(纯字符串数组)
/** GET /tag/project_type 的响应类型 */
export type ProjectTypeTag = string[];
// endregion

View File

@ -0,0 +1,100 @@
import { z } from "zod";
// region VersionDependency — 依赖项
export const VersionDependencySchema = z.object({
/** 依赖的具体版本 ID为 null 时匹配最新版 */
version_id: z.string().nullable(),
/** 依赖的项目 ID */
project_id: z.string().nullable(),
/** 依赖文件名,主要用于整合包中的外部依赖 */
file_name: z.string().nullable(),
/** 依赖类型 */
dependency_type: z.enum(["required", "optional", "incompatible", "embedded"]),
});
export type VersionDependency = z.infer<typeof VersionDependencySchema>;
// endregion
// region FileTypeEnum — 文件类型枚举
export const FileTypeEnumSchema = z.enum([
"required-resource-pack",
"optional-resource-pack",
"sources-jar",
"dev-jar",
"javadoc-jar",
"unknown",
"signature",
]);
export type FileTypeEnum = z.infer<typeof FileTypeEnumSchema>;
// endregion
// region VersionFileHashes — 文件哈希
export const VersionFileHashesSchema = z.object({
sha512: z.string(),
sha1: z.string(),
});
export type VersionFileHashes = z.infer<typeof VersionFileHashesSchema>;
// endregion
// region VersionFile — 版本文件
export const VersionFileSchema = z.object({
/** 文件哈希,包含 sha512 和 sha1 */
hashes: VersionFileHashesSchema,
/** 文件直链下载地址 */
url: z.string().url(),
/** 文件名 */
filename: z.string(),
/** 是否为主文件。每版本最多一个主文件;若无则为第一个文件 */
primary: z.boolean(),
/** 文件大小(字节) */
size: z.number().int().min(0),
/** 文件类型;普通主 jar 为 null */
file_type: FileTypeEnumSchema.nullable(),
});
export type VersionFile = z.infer<typeof VersionFileSchema>;
// endregion
// region BaseVersion — 版本基础字段Version 和 CreatableVersion 的公共基类)
export const BaseVersionSchema = z.object({
/** 版本名称 */
name: z.string(),
/** 版本号,建议遵循语义化版本 */
version_number: z.string(),
/** 更新日志Markdown/纯文本);使用 include_changelog=false 时为 null */
changelog: z.string().nullable(),
/** 依赖列表 */
dependencies: z.array(VersionDependencySchema),
/** 支持的 Minecraft 版本列表 */
game_versions: z.array(z.string()),
/** 发布渠道 */
version_type: z.enum(["release", "beta", "alpha"]),
/** 支持的模组加载器列表 */
loaders: z.array(z.string()),
/** 是否为精选版本 */
featured: z.boolean(),
/** 版本状态 */
status: z.enum(["listed", "archived", "draft", "unlisted", "scheduled"]),
/** 请求的状态(用于审核流程) */
requested_status: z.enum(["listed", "archived", "draft", "unlisted"]).nullable(),
});
export type BaseVersion = z.infer<typeof BaseVersionSchema>;
// endregion
// region Version — 版本完整响应体GET /version/{id}、GET /project/{id}/version 等返回)
export const VersionSchema = BaseVersionSchema.extend({
/** 版本唯一 ID8 位 base62 */
id: z.string().length(8),
/** 所属项目 ID8 位 base62 */
project_id: z.string().length(8),
/** 发布者用户 ID */
author_id: z.string(),
/** 发布时间ISO-8601 */
date_published: z.string().datetime(),
/** 下载次数 */
downloads: z.number().int().min(0),
/** changelog URL已废弃始终为 null */
changelog_url: z.string().nullable(),
/** 该版本的所有文件列表 */
files: z.array(VersionFileSchema),
});
export type Version = z.infer<typeof VersionSchema>;
// endregion