refactor(data): replace schema classes with readonly models

This commit is contained in:
2026-07-17 13:21:40 +08:00
parent 3437312167
commit ae97366a4a
103 changed files with 1220 additions and 2117 deletions
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { Character } from "@/data/schemas/character";
import { CharacterSchema } from "@/data/schemas/character";
describe("Character", () => {
it("keeps only the four public character fields", () => {
const character = Character.fromJson({
const character = CharacterSchema.parse({
id: "character_elio",
slug: "elio",
displayName: " Elio Silvestri ",
@@ -13,7 +13,7 @@ describe("Character", () => {
sortOrder: 1,
});
expect(character.toJson()).toEqual({
expect(character).toEqual({
id: "character_elio",
slug: "elio",
displayName: "Elio Silvestri",
@@ -23,7 +23,7 @@ describe("Character", () => {
it("rejects unsafe slugs and non-HTTPS avatars", () => {
expect(() =>
Character.from({
CharacterSchema.parse({
id: "character_aria",
slug: "Aria Profile",
displayName: "Aria",
@@ -31,7 +31,7 @@ describe("Character", () => {
}),
).toThrow();
expect(() =>
Character.from({
CharacterSchema.parse({
id: "character_aria",
slug: "aria",
displayName: "Aria",
+11 -31
View File
@@ -1,37 +1,17 @@
import { z } from "zod";
export const CharacterSchema = z.object({
id: z.string().min(1).max(64),
slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
displayName: z.string().trim().min(1).max(80),
avatarUrl: z.url().refine((url) => url.startsWith("https://"), {
message: "avatarUrl must use HTTPS",
}),
});
export const CharacterSchema = z
.object({
id: z.string().min(1).max(64),
slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
displayName: z.string().trim().min(1).max(80),
avatarUrl: z.url().refine((url) => url.startsWith("https://"), {
message: "avatarUrl must use HTTPS",
}),
})
.readonly();
export type CharacterInput = z.input<typeof CharacterSchema>;
export type CharacterData = z.output<typeof CharacterSchema>;
export class Character {
declare readonly id: string;
declare readonly slug: string;
declare readonly displayName: string;
declare readonly avatarUrl: string;
private constructor(input: CharacterInput) {
Object.assign(this, CharacterSchema.parse(input));
Object.freeze(this);
}
static from(input: CharacterInput): Character {
return new Character(input);
}
static fromJson(json: unknown): Character {
return Character.from(json as CharacterInput);
}
toJson(): CharacterData {
return CharacterSchema.parse(this);
}
}
export type Character = CharacterData;
@@ -1,36 +1,17 @@
import { z } from "zod";
import { Character, CharacterSchema } from "./character";
import { CharacterSchema } from "./character";
export const CharactersResponseSchema = z.object({
items: z.array(CharacterSchema).default([]),
});
export const CharactersResponseSchema = z
.object({
items: z
.array(CharacterSchema)
.default(() => [])
.readonly(),
})
.readonly();
export type CharactersResponseInput = z.input<typeof CharactersResponseSchema>;
export type CharactersResponseData = z.output<typeof CharactersResponseSchema>;
export class CharactersResponse {
declare readonly items: Character[];
private constructor(input: CharactersResponseInput) {
const data = CharactersResponseSchema.parse(input);
Object.assign(this, {
items: data.items.map((item) => Character.from(item)),
});
Object.freeze(this);
}
static from(input: CharactersResponseInput): CharactersResponse {
return new CharactersResponse(input);
}
static fromJson(json: unknown): CharactersResponse {
return CharactersResponse.from(json as CharactersResponseInput);
}
toJson(): CharactersResponseData {
return CharactersResponseSchema.parse({
items: this.items.map((item) => item.toJson()),
});
}
}
export type CharactersResponse = CharactersResponseData;