From 267670af964d645be14d4b89c45e86ad48266cd7 Mon Sep 17 00:00:00 2001
From: Lhcfl <Lhcfl@outlook.com>
Date: Wed, 24 Apr 2024 02:00:34 +0800
Subject: [PATCH] move schema & langmap from backend to firefish-js

---
 packages/backend/package.json                 |   1 +
 packages/backend/src/misc/langmap.ts          | 382 +----------------
 packages/backend/src/misc/schema.ts           | 236 +----------
 .../client/src/scripts/extract-mentions.ts    |   5 +-
 packages/client/src/scripts/langmap.ts        | 383 +-----------------
 packages/firefish-js/package.json             |   5 +-
 packages/firefish-js/src/api.types.ts         |   4 +-
 packages/firefish-js/src/entities.ts          |   3 +-
 packages/firefish-js/src/index.ts             |   9 +-
 packages/firefish-js/src/misc/langmap.ts      | 382 +++++++++++++++++
 packages/firefish-js/src/misc/schema.ts       | 241 +++++++++++
 .../src}/schema/abuse-user-report.ts          |   0
 .../src}/schema/antenna.ts                    |   0
 .../models => firefish-js/src}/schema/app.ts  |   0
 .../src}/schema/blocking.ts                   |   0
 .../src}/schema/channel.ts                    |   0
 .../models => firefish-js/src}/schema/clip.ts |   0
 .../src}/schema/drive-file.ts                 |   0
 .../src}/schema/drive-folder.ts               |   0
 .../src}/schema/emoji.ts                      |   0
 .../src}/schema/federation-instance.ts        |   4 +-
 .../src}/schema/following.ts                  |   0
 .../src}/schema/gallery-post.ts               |   0
 .../src}/schema/hashtag.ts                    |   0
 .../src}/schema/messaging-message.ts          |   0
 .../src}/schema/muting.ts                     |   0
 .../src}/schema/note-edit.ts                  |   0
 .../src}/schema/note-favorite.ts              |   0
 .../src}/schema/note-file.ts                  |   0
 .../src}/schema/note-reaction.ts              |   0
 .../models => firefish-js/src}/schema/note.ts |  12 +-
 .../src}/schema/notification.ts               |   2 +-
 .../models => firefish-js/src}/schema/page.ts |   0
 .../src}/schema/queue.ts                      |   0
 .../src}/schema/renote-muting.ts              |   0
 .../src}/schema/reply-muting.ts               |   0
 .../src}/schema/user-group.ts                 |   0
 .../src}/schema/user-list.ts                  |   0
 .../models => firefish-js/src}/schema/user.ts |   0
 pnpm-lock.yaml                                |   3 +
 40 files changed, 656 insertions(+), 1016 deletions(-)
 create mode 100644 packages/firefish-js/src/misc/langmap.ts
 create mode 100644 packages/firefish-js/src/misc/schema.ts
 rename packages/{backend/src/models => firefish-js/src}/schema/abuse-user-report.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/antenna.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/app.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/blocking.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/channel.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/clip.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/drive-file.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/drive-folder.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/emoji.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/federation-instance.ts (97%)
 rename packages/{backend/src/models => firefish-js/src}/schema/following.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/gallery-post.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/hashtag.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/messaging-message.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/muting.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/note-edit.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/note-favorite.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/note-file.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/note-reaction.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/note.ts (94%)
 rename packages/{backend/src/models => firefish-js/src}/schema/notification.ts (96%)
 rename packages/{backend/src/models => firefish-js/src}/schema/page.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/queue.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/renote-muting.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/reply-muting.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/user-group.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/user-list.ts (100%)
 rename packages/{backend/src/models => firefish-js/src}/schema/user.ts (100%)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 0599a39d4b..7ac9205c58 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -39,6 +39,7 @@
 		"aws-sdk": "2.1599.0",
 		"axios": "^1.6.8",
 		"backend-rs": "workspace:*",
+		"firefish-js": "workspace:*",
 		"blurhash": "2.0.5",
 		"bull": "4.12.2",
 		"cacheable-lookup": "TheEssem/cacheable-lookup",
diff --git a/packages/backend/src/misc/langmap.ts b/packages/backend/src/misc/langmap.ts
index 2506a36151..0f71c8f81c 100644
--- a/packages/backend/src/misc/langmap.ts
+++ b/packages/backend/src/misc/langmap.ts
@@ -1,383 +1,5 @@
-// TODO: sharedに置いてバックエンドのと統合したい
-export const iso639Langs1 = {
-	af: {
-		nativeName: "Afrikaans",
-	},
-	ak: {
-		nativeName: "Tɕɥi",
-	},
-	ar: {
-		nativeName: "العربية",
-		rtl: true,
-	},
-	ay: {
-		nativeName: "Aymar aru",
-	},
-	az: {
-		nativeName: "Azərbaycan dili",
-	},
-	be: {
-		nativeName: "Беларуская",
-	},
-	bg: {
-		nativeName: "Български",
-	},
-	bn: {
-		nativeName: "বাংলা",
-	},
-	br: {
-		nativeName: "Brezhoneg",
-	},
-	bs: {
-		nativeName: "Bosanski",
-	},
-	ca: {
-		nativeName: "Català",
-	},
-	cs: {
-		nativeName: "Čeština",
-	},
-	cy: {
-		nativeName: "Cymraeg",
-	},
-	da: {
-		nativeName: "Dansk",
-	},
-	de: {
-		nativeName: "Deutsch",
-	},
-	el: {
-		nativeName: "Ελληνικά",
-	},
-	en: {
-		nativeName: "English",
-	},
-	eo: {
-		nativeName: "Esperanto",
-	},
-	es: {
-		nativeName: "Español",
-	},
-	et: {
-		nativeName: "eesti keel",
-	},
-	eu: {
-		nativeName: "Euskara",
-	},
-	fa: {
-		nativeName: "فارسی",
-		rtl: true,
-	},
-	ff: {
-		nativeName: "Fulah",
-	},
-	fi: {
-		nativeName: "Suomi",
-	},
-	fo: {
-		nativeName: "Føroyskt",
-	},
-	fr: {
-		nativeName: "Français",
-	},
-	fy: {
-		nativeName: "Frysk",
-	},
-	ga: {
-		nativeName: "Gaeilge",
-	},
-	gd: {
-		nativeName: "Gàidhlig",
-	},
-	gl: {
-		nativeName: "Galego",
-	},
-	gn: {
-		nativeName: "Avañe'ẽ",
-	},
-	gu: {
-		nativeName: "ગુજરાતી",
-	},
-	gv: {
-		nativeName: "Gaelg",
-	},
-	he: {
-		nativeName: "עברית‏",
-		rtl: true,
-	},
-	hi: {
-		nativeName: "हिन्दी",
-	},
-	hr: {
-		nativeName: "Hrvatski",
-	},
-	ht: {
-		nativeName: "Kreyòl",
-	},
-	hu: {
-		nativeName: "Magyar",
-	},
-	hy: {
-		nativeName: "Հայերեն",
-	},
-	id: {
-		nativeName: "Bahasa Indonesia",
-	},
-	is: {
-		nativeName: "Íslenska",
-	},
-	it: {
-		nativeName: "Italiano",
-	},
-	ja: {
-		nativeName: "日本語",
-	},
-	jv: {
-		nativeName: "Basa Jawa",
-	},
-	ka: {
-		nativeName: "ქართული",
-	},
-	kk: {
-		nativeName: "Қазақша",
-	},
-	kl: {
-		nativeName: "kalaallisut",
-	},
-	km: {
-		nativeName: "ភាសាខ្មែរ",
-	},
-	kn: {
-		nativeName: "ಕನ್ನಡ",
-	},
-	ko: {
-		nativeName: "한국어",
-	},
-	ku: {
-		nativeName: "Kurdî",
-	},
-	kw: {
-		nativeName: "Kernewek",
-	},
-	la: {
-		nativeName: "Latin",
-	},
-	lb: {
-		nativeName: "Lëtzebuergesch",
-	},
-	li: {
-		nativeName: "Lèmbörgs",
-	},
-	lt: {
-		nativeName: "Lietuvių",
-	},
-	lv: {
-		nativeName: "Latviešu",
-	},
-	mg: {
-		nativeName: "Malagasy",
-	},
-	mk: {
-		nativeName: "Македонски",
-	},
-	ml: {
-		nativeName: "മലയാളം",
-	},
-	mn: {
-		nativeName: "Монгол",
-	},
-	mr: {
-		nativeName: "मराठी",
-	},
-	ms: {
-		nativeName: "Bahasa Melayu",
-	},
-	mt: {
-		nativeName: "Malti",
-	},
-	my: {
-		nativeName: "ဗမာစကာ",
-	},
-	no: {
-		nativeName: "Norsk",
-	},
-	nb: {
-		nativeName: "Norsk (bokmål)",
-	},
-	ne: {
-		nativeName: "नेपाली",
-	},
-	nl: {
-		nativeName: "Nederlands",
-	},
-	nn: {
-		nativeName: "Norsk (nynorsk)",
-	},
-	oc: {
-		nativeName: "Occitan",
-	},
-	or: {
-		nativeName: "ଓଡ଼ିଆ",
-	},
-	pa: {
-		nativeName: "ਪੰਜਾਬੀ",
-	},
-	pl: {
-		nativeName: "Polski",
-	},
-	ps: {
-		nativeName: "پښتو",
-		rtl: true,
-	},
-	pt: {
-		nativeName: "Português",
-	},
-	qu: {
-		nativeName: "Qhichwa",
-	},
-	rm: {
-		nativeName: "Rumantsch",
-	},
-	ro: {
-		nativeName: "Română",
-	},
-	ru: {
-		nativeName: "Русский",
-	},
-	sa: {
-		nativeName: "संस्कृतम्",
-	},
-	se: {
-		nativeName: "Davvisámegiella",
-	},
-	sh: {
-		nativeName: "српскохрватски",
-	},
-	si: {
-		nativeName: "සිංහල",
-	},
-	sk: {
-		nativeName: "Slovenčina",
-	},
-	sl: {
-		nativeName: "Slovenščina",
-	},
-	so: {
-		nativeName: "Soomaaliga",
-	},
-	sq: {
-		nativeName: "Shqip",
-	},
-	sr: {
-		nativeName: "Српски",
-	},
-	su: {
-		nativeName: "Basa Sunda",
-	},
-	sv: {
-		nativeName: "Svenska",
-	},
-	sw: {
-		nativeName: "Kiswahili",
-	},
-	ta: {
-		nativeName: "தமிழ்",
-	},
-	te: {
-		nativeName: "తెలుగు",
-	},
-	tg: {
-		nativeName: "забо́ни тоҷикӣ́",
-	},
-	th: {
-		nativeName: "ภาษาไทย",
-	},
-	tr: {
-		nativeName: "Türkçe",
-	},
-	tt: {
-		nativeName: "татарча",
-	},
-	uk: {
-		nativeName: "Українська",
-	},
-	ur: {
-		nativeName: "اردو",
-		rtl: true,
-	},
-	uz: {
-		nativeName: "O'zbek",
-	},
-	vi: {
-		nativeName: "Tiếng Việt",
-	},
-	xh: {
-		nativeName: "isiXhosa",
-	},
-	yi: {
-		nativeName: "ייִדיש",
-		rtl: true,
-	},
-	zh: {
-		nativeName: "中文",
-	},
-	zu: {
-		nativeName: "isiZulu",
-	},
-};
+import LangMap from "firefish-js/built/misc/langmap.js";
 
-export const iso639Langs3 = {
-	ach: {
-		nativeName: "Lwo",
-	},
-	ady: {
-		nativeName: "Адыгэбзэ",
-	},
-	cak: {
-		nativeName: "Maya Kaqchikel",
-	},
-	chr: {
-		nativeName: "ᏣᎳᎩ (tsalagi)",
-	},
-	dsb: {
-		nativeName: "Dolnoserbšćina",
-	},
-	fil: {
-		nativeName: "Filipino",
-	},
-	hsb: {
-		nativeName: "Hornjoserbšćina",
-	},
-	kab: {
-		nativeName: "Taqbaylit",
-	},
-	mai: {
-		nativeName: "मैथिली, মৈথিলী",
-	},
-	tlh: {
-		nativeName: "tlhIngan-Hol",
-	},
-	tok: {
-		nativeName: "Toki Pona",
-	},
-	yue: {
-		nativeName: "粵語",
-	},
-	nan: {
-		nativeName: "閩南語",
-	},
-};
+export const langmap = LangMap.langmap;
 
-export const langmapNoRegion = Object.assign({}, iso639Langs1, iso639Langs3);
-
-export const iso639Regional = {
-	"zh-hans": {
-		nativeName: "中文(简体)",
-	},
-	"zh-hant": {
-		nativeName: "中文(繁體)",
-	},
-};
-
-export const langmap = Object.assign({}, langmapNoRegion, iso639Regional);
 export type PostLanguage = keyof typeof langmap;
diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index 73600832ce..8d073e4832 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -1,234 +1,4 @@
-import {
-	packedUserLiteSchema,
-	packedUserDetailedNotMeOnlySchema,
-	packedMeDetailedOnlySchema,
-	packedUserDetailedNotMeSchema,
-	packedMeDetailedSchema,
-	packedUserDetailedSchema,
-	packedUserSchema,
-} from "@/models/schema/user.js";
-import { packedNoteSchema } from "@/models/schema/note.js";
-import { packedUserListSchema } from "@/models/schema/user-list.js";
-import { packedAppSchema } from "@/models/schema/app.js";
-import { packedMessagingMessageSchema } from "@/models/schema/messaging-message.js";
-import { packedNotificationSchema } from "@/models/schema/notification.js";
-import { packedDriveFileSchema } from "@/models/schema/drive-file.js";
-import { packedDriveFolderSchema } from "@/models/schema/drive-folder.js";
-import { packedFollowingSchema } from "@/models/schema/following.js";
-import { packedMutingSchema } from "@/models/schema/muting.js";
-import { packedRenoteMutingSchema } from "@/models/schema/renote-muting.js";
-import { packedReplyMutingSchema } from "@/models/schema/reply-muting.js";
-import { packedBlockingSchema } from "@/models/schema/blocking.js";
-import { packedNoteReactionSchema } from "@/models/schema/note-reaction.js";
-import { packedHashtagSchema } from "@/models/schema/hashtag.js";
-import { packedPageSchema } from "@/models/schema/page.js";
-import { packedUserGroupSchema } from "@/models/schema/user-group.js";
-import { packedNoteFavoriteSchema } from "@/models/schema/note-favorite.js";
-import { packedChannelSchema } from "@/models/schema/channel.js";
-import { packedAntennaSchema } from "@/models/schema/antenna.js";
-import { packedClipSchema } from "@/models/schema/clip.js";
-import { packedFederationInstanceSchema } from "@/models/schema/federation-instance.js";
-import { packedQueueCountSchema } from "@/models/schema/queue.js";
-import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
-import { packedEmojiSchema } from "@/models/schema/emoji.js";
-import { packedNoteEdit } from "@/models/schema/note-edit.js";
-import { packedNoteFileSchema } from "@/models/schema/note-file.js";
-import { packedAbuseUserReportSchema } from "@/models/schema/abuse-user-report.js";
+// TODO: use firefish-js
 
-export const refs = {
-	AbuseUserReport: packedAbuseUserReportSchema,
-	UserLite: packedUserLiteSchema,
-	UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
-	MeDetailedOnly: packedMeDetailedOnlySchema,
-	UserDetailedNotMe: packedUserDetailedNotMeSchema,
-	MeDetailed: packedMeDetailedSchema,
-	UserDetailed: packedUserDetailedSchema,
-	User: packedUserSchema,
-
-	UserList: packedUserListSchema,
-	UserGroup: packedUserGroupSchema,
-	App: packedAppSchema,
-	MessagingMessage: packedMessagingMessageSchema,
-	Note: packedNoteSchema,
-	NoteFile: packedNoteFileSchema,
-	NoteEdit: packedNoteEdit,
-	NoteReaction: packedNoteReactionSchema,
-	NoteFavorite: packedNoteFavoriteSchema,
-	Notification: packedNotificationSchema,
-	DriveFile: packedDriveFileSchema,
-	DriveFolder: packedDriveFolderSchema,
-	Following: packedFollowingSchema,
-	Muting: packedMutingSchema,
-	RenoteMuting: packedRenoteMutingSchema,
-	ReplyMuting: packedReplyMutingSchema,
-	Blocking: packedBlockingSchema,
-	Hashtag: packedHashtagSchema,
-	Page: packedPageSchema,
-	Channel: packedChannelSchema,
-	QueueCount: packedQueueCountSchema,
-	Antenna: packedAntennaSchema,
-	Clip: packedClipSchema,
-	FederationInstance: packedFederationInstanceSchema,
-	GalleryPost: packedGalleryPostSchema,
-	Emoji: packedEmojiSchema,
-};
-
-export type Packed<x extends keyof typeof refs> = SchemaType<(typeof refs)[x]>;
-
-type TypeStringef =
-	| "null"
-	| "boolean"
-	| "integer"
-	| "number"
-	| "string"
-	| "array"
-	| "object"
-	| "any";
-type StringDefToType<T extends TypeStringef> = T extends "null"
-	? null
-	: T extends "boolean"
-		? boolean
-		: T extends "integer"
-			? number
-			: T extends "number"
-				? number
-				: T extends "string"
-					? string | Date
-					: T extends "array"
-						? ReadonlyArray<any>
-						: T extends "object"
-							? Record<string, any>
-							: any;
-
-// https://swagger.io/specification/?sbsearch=optional#schema-object
-type OfSchema = {
-	readonly anyOf?: ReadonlyArray<Schema>;
-	readonly oneOf?: ReadonlyArray<Schema>;
-	readonly allOf?: ReadonlyArray<Schema>;
-};
-
-export interface Schema extends OfSchema {
-	readonly type?: TypeStringef;
-	readonly nullable?: boolean;
-	readonly optional?: boolean;
-	readonly items?: Schema;
-	readonly properties?: Obj;
-	readonly required?: ReadonlyArray<
-		Extract<keyof NonNullable<this["properties"]>, string>
-	>;
-	readonly description?: string;
-	readonly example?: any;
-	readonly format?: string;
-	readonly ref?: keyof typeof refs;
-	readonly enum?: ReadonlyArray<string>;
-	readonly default?:
-		| (this["type"] extends TypeStringef ? StringDefToType<this["type"]> : any)
-		| null;
-	readonly maxLength?: number;
-	readonly minLength?: number;
-	readonly maximum?: number;
-	readonly minimum?: number;
-	readonly pattern?: string;
-}
-
-type RequiredPropertyNames<s extends Obj> = {
-	[K in keyof s]: // K is not optional
-	s[K]["optional"] extends false
-		? K
-		: // K has default value
-			s[K]["default"] extends
-					| null
-					| string
-					| number
-					| boolean
-					| Record<string, unknown>
-			? K
-			: never;
-}[keyof s];
-
-export type Obj = Record<string, Schema>;
-
-// https://github.com/misskey-dev/misskey/issues/8535
-// To avoid excessive stack depth error,
-// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
-export type ObjType<
-	s extends Obj,
-	RequiredProps extends keyof s,
-> = UnionToIntersection<
-	{
-		-readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]>;
-	} & {
-		-readonly [R in RequiredProps]-?: SchemaType<s[R]>;
-	} & {
-		-readonly [P in keyof s]?: SchemaType<s[P]>;
-	}
->;
-
-type NullOrUndefined<p extends Schema, T> =
-	| (p["nullable"] extends true ? null : never)
-	| (p["optional"] extends true ? undefined : never)
-	| T;
-
-// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
-// Get intersection from union
-type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
-	k: infer I,
-) => void
-	? I
-	: never;
-
-// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
-// To get union, we use `Foo extends any ? Hoge<Foo> : never`
-type UnionSchemaType<
-	a extends readonly any[],
-	X extends Schema = a[number],
-> = X extends any ? SchemaType<X> : never;
-type ArrayUnion<T> = T extends any ? Array<T> : never;
-
-export type SchemaTypeDef<p extends Schema> = p["type"] extends "null"
-	? null
-	: p["type"] extends "integer"
-		? number
-		: p["type"] extends "number"
-			? number
-			: p["type"] extends "string"
-				? p["enum"] extends readonly string[]
-					? p["enum"][number]
-					: p["format"] extends "date-time"
-						? string
-						: // Dateにする??
-							string
-				: p["type"] extends "boolean"
-					? boolean
-					: p["type"] extends "object"
-						? p["ref"] extends keyof typeof refs
-							? Packed<p["ref"]>
-							: p["properties"] extends NonNullable<Obj>
-								? ObjType<p["properties"], NonNullable<p["required"]>[number]>
-								: p["anyOf"] extends ReadonlyArray<Schema>
-									? UnionSchemaType<p["anyOf"]> &
-											Partial<UnionToIntersection<UnionSchemaType<p["anyOf"]>>>
-									: p["allOf"] extends ReadonlyArray<Schema>
-										? UnionToIntersection<UnionSchemaType<p["allOf"]>>
-										: any
-						: p["type"] extends "array"
-							? p["items"] extends OfSchema
-								? p["items"]["anyOf"] extends ReadonlyArray<Schema>
-									? UnionSchemaType<NonNullable<p["items"]["anyOf"]>>[]
-									: p["items"]["oneOf"] extends ReadonlyArray<Schema>
-										? ArrayUnion<
-												UnionSchemaType<NonNullable<p["items"]["oneOf"]>>
-											>
-										: p["items"]["allOf"] extends ReadonlyArray<Schema>
-											? UnionToIntersection<
-													UnionSchemaType<NonNullable<p["items"]["allOf"]>>
-												>[]
-											: never
-								: p["items"] extends NonNullable<Schema>
-									? SchemaTypeDef<p["items"]>[]
-									: any[]
-							: p["oneOf"] extends ReadonlyArray<Schema>
-								? UnionSchemaType<p["oneOf"]>
-								: any;
-
-export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;
+export * from "firefish-js/built/misc/schema.js";
+// export Schema from "firefish-js";
diff --git a/packages/client/src/scripts/extract-mentions.ts b/packages/client/src/scripts/extract-mentions.ts
index 2f238dc15e..cdf04c1106 100644
--- a/packages/client/src/scripts/extract-mentions.ts
+++ b/packages/client/src/scripts/extract-mentions.ts
@@ -6,7 +6,10 @@ export function extractMentions(
 	nodes: mfm.MfmNode[],
 ): mfm.MfmMention["props"][] {
 	// TODO: 重複を削除
-	const mentionNodes = mfm.extract(nodes, (node) => node.type === "mention") as mfm.MfmMention[];
+	const mentionNodes = mfm.extract(
+		nodes,
+		(node) => node.type === "mention",
+	) as mfm.MfmMention[];
 	const mentions = mentionNodes.map((x) => x.props);
 
 	return mentions;
diff --git a/packages/client/src/scripts/langmap.ts b/packages/client/src/scripts/langmap.ts
index bfb6bec00d..ae82146bec 100644
--- a/packages/client/src/scripts/langmap.ts
+++ b/packages/client/src/scripts/langmap.ts
@@ -1,385 +1,6 @@
-// TODO: sharedに置いてバックエンドのと統合したい
-export const iso639Langs1 = {
-	af: {
-		nativeName: "Afrikaans",
-	},
-	ak: {
-		nativeName: "Tɕɥi",
-	},
-	ar: {
-		nativeName: "العربية",
-		rtl: true,
-	},
-	ay: {
-		nativeName: "Aymar aru",
-	},
-	az: {
-		nativeName: "Azərbaycan dili",
-	},
-	be: {
-		nativeName: "Беларуская",
-	},
-	bg: {
-		nativeName: "Български",
-	},
-	bn: {
-		nativeName: "বাংলা",
-	},
-	br: {
-		nativeName: "Brezhoneg",
-	},
-	bs: {
-		nativeName: "Bosanski",
-	},
-	ca: {
-		nativeName: "Català",
-	},
-	cs: {
-		nativeName: "Čeština",
-	},
-	cy: {
-		nativeName: "Cymraeg",
-	},
-	da: {
-		nativeName: "Dansk",
-	},
-	de: {
-		nativeName: "Deutsch",
-	},
-	el: {
-		nativeName: "Ελληνικά",
-	},
-	en: {
-		nativeName: "English",
-	},
-	eo: {
-		nativeName: "Esperanto",
-	},
-	es: {
-		nativeName: "Español",
-	},
-	et: {
-		nativeName: "eesti keel",
-	},
-	eu: {
-		nativeName: "Euskara",
-	},
-	fa: {
-		nativeName: "فارسی",
-		rtl: true,
-	},
-	ff: {
-		nativeName: "Fulah",
-	},
-	fi: {
-		nativeName: "Suomi",
-	},
-	fo: {
-		nativeName: "Føroyskt",
-	},
-	fr: {
-		nativeName: "Français",
-	},
-	fy: {
-		nativeName: "Frysk",
-	},
-	ga: {
-		nativeName: "Gaeilge",
-	},
-	gd: {
-		nativeName: "Gàidhlig",
-	},
-	gl: {
-		nativeName: "Galego",
-	},
-	gn: {
-		nativeName: "Avañe'ẽ",
-	},
-	gu: {
-		nativeName: "ગુજરાતી",
-	},
-	gv: {
-		nativeName: "Gaelg",
-	},
-	he: {
-		nativeName: "עברית‏",
-		rtl: true,
-	},
-	hi: {
-		nativeName: "हिन्दी",
-	},
-	hr: {
-		nativeName: "Hrvatski",
-	},
-	ht: {
-		nativeName: "Kreyòl",
-	},
-	hu: {
-		nativeName: "Magyar",
-	},
-	hy: {
-		nativeName: "Հայերեն",
-	},
-	id: {
-		nativeName: "Bahasa Indonesia",
-	},
-	is: {
-		nativeName: "Íslenska",
-	},
-	it: {
-		nativeName: "Italiano",
-	},
-	ja: {
-		nativeName: "日本語",
-	},
-	jv: {
-		nativeName: "Basa Jawa",
-	},
-	ka: {
-		nativeName: "ქართული",
-	},
-	kk: {
-		nativeName: "Қазақша",
-	},
-	kl: {
-		nativeName: "kalaallisut",
-	},
-	km: {
-		nativeName: "ភាសាខ្មែរ",
-	},
-	kn: {
-		nativeName: "ಕನ್ನಡ",
-	},
-	ko: {
-		nativeName: "한국어",
-	},
-	ku: {
-		nativeName: "Kurdî",
-	},
-	kw: {
-		nativeName: "Kernewek",
-	},
-	la: {
-		nativeName: "Latin",
-	},
-	lb: {
-		nativeName: "Lëtzebuergesch",
-	},
-	li: {
-		nativeName: "Lèmbörgs",
-	},
-	lt: {
-		nativeName: "Lietuvių",
-	},
-	lv: {
-		nativeName: "Latviešu",
-	},
-	mg: {
-		nativeName: "Malagasy",
-	},
-	mk: {
-		nativeName: "Македонски",
-	},
-	ml: {
-		nativeName: "മലയാളം",
-	},
-	mn: {
-		nativeName: "Монгол",
-	},
-	mr: {
-		nativeName: "मराठी",
-	},
-	ms: {
-		nativeName: "Bahasa Melayu",
-	},
-	mt: {
-		nativeName: "Malti",
-	},
-	my: {
-		nativeName: "ဗမာစကာ",
-	},
-	no: {
-		nativeName: "Norsk",
-	},
-	nb: {
-		nativeName: "Norsk (bokmål)",
-	},
-	ne: {
-		nativeName: "नेपाली",
-	},
-	nl: {
-		nativeName: "Nederlands",
-	},
-	nn: {
-		nativeName: "Norsk (nynorsk)",
-	},
-	oc: {
-		nativeName: "Occitan",
-	},
-	or: {
-		nativeName: "ଓଡ଼ିଆ",
-	},
-	pa: {
-		nativeName: "ਪੰਜਾਬੀ",
-	},
-	pl: {
-		nativeName: "Polski",
-	},
-	ps: {
-		nativeName: "پښتو",
-		rtl: true,
-	},
-	pt: {
-		nativeName: "Português",
-	},
-	qu: {
-		nativeName: "Qhichwa",
-	},
-	rm: {
-		nativeName: "Rumantsch",
-	},
-	ro: {
-		nativeName: "Română",
-	},
-	ru: {
-		nativeName: "Русский",
-	},
-	sa: {
-		nativeName: "संस्कृतम्",
-	},
-	se: {
-		nativeName: "Davvisámegiella",
-	},
-	sh: {
-		nativeName: "српскохрватски",
-	},
-	si: {
-		nativeName: "සිංහල",
-	},
-	sk: {
-		nativeName: "Slovenčina",
-	},
-	sl: {
-		nativeName: "Slovenščina",
-	},
-	so: {
-		nativeName: "Soomaaliga",
-	},
-	sq: {
-		nativeName: "Shqip",
-	},
-	sr: {
-		nativeName: "Српски",
-	},
-	su: {
-		nativeName: "Basa Sunda",
-	},
-	sv: {
-		nativeName: "Svenska",
-	},
-	sw: {
-		nativeName: "Kiswahili",
-	},
-	ta: {
-		nativeName: "தமிழ்",
-	},
-	te: {
-		nativeName: "తెలుగు",
-	},
-	tg: {
-		nativeName: "забо́ни тоҷикӣ́",
-	},
-	th: {
-		nativeName: "ภาษาไทย",
-	},
-	tr: {
-		nativeName: "Türkçe",
-	},
-	tt: {
-		nativeName: "татарча",
-	},
-	uk: {
-		nativeName: "Українська",
-	},
-	ur: {
-		nativeName: "اردو",
-		rtl: true,
-	},
-	uz: {
-		nativeName: "O'zbek",
-	},
-	vi: {
-		nativeName: "Tiếng Việt",
-	},
-	xh: {
-		nativeName: "isiXhosa",
-	},
-	yi: {
-		nativeName: "ייִדיש",
-		rtl: true,
-	},
-	zh: {
-		nativeName: "中文",
-	},
-	zu: {
-		nativeName: "isiZulu",
-	},
-};
+import { langmap as _langmap } from "firefish-js";
 
-export const iso639Langs3 = {
-	ach: {
-		nativeName: "Lwo",
-	},
-	ady: {
-		nativeName: "Адыгэбзэ",
-	},
-	cak: {
-		nativeName: "Maya Kaqchikel",
-	},
-	chr: {
-		nativeName: "ᏣᎳᎩ (tsalagi)",
-	},
-	dsb: {
-		nativeName: "Dolnoserbšćina",
-	},
-	fil: {
-		nativeName: "Filipino",
-	},
-	hsb: {
-		nativeName: "Hornjoserbšćina",
-	},
-	kab: {
-		nativeName: "Taqbaylit",
-	},
-	mai: {
-		nativeName: "मैथिली, মৈথিলী",
-	},
-	tlh: {
-		nativeName: "tlhIngan-Hol",
-	},
-	tok: {
-		nativeName: "Toki Pona",
-	},
-	yue: {
-		nativeName: "粵語",
-	},
-	nan: {
-		nativeName: "閩南語",
-	},
-};
-
-export const langmapNoRegion = Object.assign({}, iso639Langs1, iso639Langs3);
-
-export const iso639Regional = {
-	"zh-hans": {
-		nativeName: "中文(简体)",
-	},
-	"zh-hant": {
-		nativeName: "中文(繁體)",
-	},
-};
-
-export const langmap = Object.assign({}, langmapNoRegion, iso639Regional);
+export const langmap = _langmap;
 
 /**
  * @see https://github.com/komodojp/tinyld/blob/develop/docs/langs.md
diff --git a/packages/firefish-js/package.json b/packages/firefish-js/package.json
index a29c4d5876..50228712e1 100644
--- a/packages/firefish-js/package.json
+++ b/packages/firefish-js/package.json
@@ -7,8 +7,9 @@
 	"types": "./src/index.ts",
 	"license": "MIT",
 	"scripts": {
-		"build": "pnpm swc src --out-dir built --source-maps false --copy-files --strip-leading-paths",
-		"build:debug": "pnpm swc src --out-dir built --source-maps true --copy-files --strip-leading-paths",
+		"build": "pnpm swc src --out-dir built --source-maps false --copy-files --strip-leading-paths && pnpm run build:types",
+		"build:debug": "pnpm swc src --out-dir built --source-maps true --copy-files --strip-leading-paths && pnpm run build:types",
+		"build:types": "pnpm tsc --emitDeclarationOnly",
 		"watch": "pnpm swc src --out-dir built --source-maps true --copy-files --strip-leading-paths --watch",
 		"lint": "pnpm biome check --apply src",
 		"format": "pnpm biome format --write src",
diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts
index 065de49025..a153a96962 100644
--- a/packages/firefish-js/src/api.types.ts
+++ b/packages/firefish-js/src/api.types.ts
@@ -265,7 +265,7 @@ export type Endpoints = {
 	"clips/add-note": { req: TODO; res: TODO };
 	"clips/create": { req: TODO; res: Clip };
 	"clips/delete": { req: { clipId: Clip["id"] }; res: null };
-	"clips/list": { req: TODO; res: TODO };
+	"clips/list": { req: TODO; res: Clip[] };
 	"clips/notes": { req: TODO; res: TODO };
 	"clips/show": { req: TODO; res: TODO };
 	"clips/update": { req: TODO; res: TODO };
@@ -785,7 +785,7 @@ export type Endpoints = {
 			noteId: Note["id"];
 		};
 		res: null;
-	}
+	};
 	"notes/mentions": {
 		req: {
 			following?: boolean;
diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts
index db9e18a741..e622e98df4 100644
--- a/packages/firefish-js/src/entities.ts
+++ b/packages/firefish-js/src/entities.ts
@@ -1,4 +1,5 @@
 import type * as consts from "./consts";
+import type { Packed } from "./misc/schema";
 
 export type ID = string;
 export type DateString = string;
@@ -513,7 +514,7 @@ export type AuthSession = {
 
 export type Ad = TODO;
 
-export type Clip = TODO;
+export type Clip = Packed<"Clip">;
 
 export type NoteFavorite = {
 	id: ID;
diff --git a/packages/firefish-js/src/index.ts b/packages/firefish-js/src/index.ts
index 3398ed8a2e..efb625258e 100644
--- a/packages/firefish-js/src/index.ts
+++ b/packages/firefish-js/src/index.ts
@@ -7,6 +7,9 @@ import Stream, { Connection } from "./streaming";
 import * as StreamTypes from "./streaming.types";
 import type * as TypeUtils from "./type-utils";
 
+import type * as SchemaTypes from "./misc/schema";
+import * as Schema from "./misc/schema";
+
 export {
 	Endpoints,
 	type ApiTypes,
@@ -16,6 +19,8 @@ export {
 	acct,
 	type Acct,
 	type TypeUtils,
+	Schema,
+	type SchemaTypes,
 };
 
 export const permissions = consts.permissions;
@@ -26,9 +31,11 @@ export const languages = consts.languages;
 export const ffVisibility = consts.ffVisibility;
 export const instanceSortParam = consts.instanceSortParam;
 
+import { langmap } from "./misc/langmap";
+
 // api extractor not supported yet
 //export * as api from './api';
 //export * as entities from './entities';
 import * as api from "./api";
 import * as entities from "./entities";
-export { api, entities };
+export { api, entities, langmap };
diff --git a/packages/firefish-js/src/misc/langmap.ts b/packages/firefish-js/src/misc/langmap.ts
new file mode 100644
index 0000000000..16d169d914
--- /dev/null
+++ b/packages/firefish-js/src/misc/langmap.ts
@@ -0,0 +1,382 @@
+export const iso639Langs1 = {
+	af: {
+		nativeName: "Afrikaans",
+	},
+	ak: {
+		nativeName: "Tɕɥi",
+	},
+	ar: {
+		nativeName: "العربية",
+		rtl: true,
+	},
+	ay: {
+		nativeName: "Aymar aru",
+	},
+	az: {
+		nativeName: "Azərbaycan dili",
+	},
+	be: {
+		nativeName: "Беларуская",
+	},
+	bg: {
+		nativeName: "Български",
+	},
+	bn: {
+		nativeName: "বাংলা",
+	},
+	br: {
+		nativeName: "Brezhoneg",
+	},
+	bs: {
+		nativeName: "Bosanski",
+	},
+	ca: {
+		nativeName: "Català",
+	},
+	cs: {
+		nativeName: "Čeština",
+	},
+	cy: {
+		nativeName: "Cymraeg",
+	},
+	da: {
+		nativeName: "Dansk",
+	},
+	de: {
+		nativeName: "Deutsch",
+	},
+	el: {
+		nativeName: "Ελληνικά",
+	},
+	en: {
+		nativeName: "English",
+	},
+	eo: {
+		nativeName: "Esperanto",
+	},
+	es: {
+		nativeName: "Español",
+	},
+	et: {
+		nativeName: "eesti keel",
+	},
+	eu: {
+		nativeName: "Euskara",
+	},
+	fa: {
+		nativeName: "فارسی",
+		rtl: true,
+	},
+	ff: {
+		nativeName: "Fulah",
+	},
+	fi: {
+		nativeName: "Suomi",
+	},
+	fo: {
+		nativeName: "Føroyskt",
+	},
+	fr: {
+		nativeName: "Français",
+	},
+	fy: {
+		nativeName: "Frysk",
+	},
+	ga: {
+		nativeName: "Gaeilge",
+	},
+	gd: {
+		nativeName: "Gàidhlig",
+	},
+	gl: {
+		nativeName: "Galego",
+	},
+	gn: {
+		nativeName: "Avañe'ẽ",
+	},
+	gu: {
+		nativeName: "ગુજરાતી",
+	},
+	gv: {
+		nativeName: "Gaelg",
+	},
+	he: {
+		nativeName: "עברית‏",
+		rtl: true,
+	},
+	hi: {
+		nativeName: "हिन्दी",
+	},
+	hr: {
+		nativeName: "Hrvatski",
+	},
+	ht: {
+		nativeName: "Kreyòl",
+	},
+	hu: {
+		nativeName: "Magyar",
+	},
+	hy: {
+		nativeName: "Հայերեն",
+	},
+	id: {
+		nativeName: "Bahasa Indonesia",
+	},
+	is: {
+		nativeName: "Íslenska",
+	},
+	it: {
+		nativeName: "Italiano",
+	},
+	ja: {
+		nativeName: "日本語",
+	},
+	jv: {
+		nativeName: "Basa Jawa",
+	},
+	ka: {
+		nativeName: "ქართული",
+	},
+	kk: {
+		nativeName: "Қазақша",
+	},
+	kl: {
+		nativeName: "kalaallisut",
+	},
+	km: {
+		nativeName: "ភាសាខ្មែរ",
+	},
+	kn: {
+		nativeName: "ಕನ್ನಡ",
+	},
+	ko: {
+		nativeName: "한국어",
+	},
+	ku: {
+		nativeName: "Kurdî",
+	},
+	kw: {
+		nativeName: "Kernewek",
+	},
+	la: {
+		nativeName: "Latin",
+	},
+	lb: {
+		nativeName: "Lëtzebuergesch",
+	},
+	li: {
+		nativeName: "Lèmbörgs",
+	},
+	lt: {
+		nativeName: "Lietuvių",
+	},
+	lv: {
+		nativeName: "Latviešu",
+	},
+	mg: {
+		nativeName: "Malagasy",
+	},
+	mk: {
+		nativeName: "Македонски",
+	},
+	ml: {
+		nativeName: "മലയാളം",
+	},
+	mn: {
+		nativeName: "Монгол",
+	},
+	mr: {
+		nativeName: "मराठी",
+	},
+	ms: {
+		nativeName: "Bahasa Melayu",
+	},
+	mt: {
+		nativeName: "Malti",
+	},
+	my: {
+		nativeName: "ဗမာစကာ",
+	},
+	no: {
+		nativeName: "Norsk",
+	},
+	nb: {
+		nativeName: "Norsk (bokmål)",
+	},
+	ne: {
+		nativeName: "नेपाली",
+	},
+	nl: {
+		nativeName: "Nederlands",
+	},
+	nn: {
+		nativeName: "Norsk (nynorsk)",
+	},
+	oc: {
+		nativeName: "Occitan",
+	},
+	or: {
+		nativeName: "ଓଡ଼ିଆ",
+	},
+	pa: {
+		nativeName: "ਪੰਜਾਬੀ",
+	},
+	pl: {
+		nativeName: "Polski",
+	},
+	ps: {
+		nativeName: "پښتو",
+		rtl: true,
+	},
+	pt: {
+		nativeName: "Português",
+	},
+	qu: {
+		nativeName: "Qhichwa",
+	},
+	rm: {
+		nativeName: "Rumantsch",
+	},
+	ro: {
+		nativeName: "Română",
+	},
+	ru: {
+		nativeName: "Русский",
+	},
+	sa: {
+		nativeName: "संस्कृतम्",
+	},
+	se: {
+		nativeName: "Davvisámegiella",
+	},
+	sh: {
+		nativeName: "српскохрватски",
+	},
+	si: {
+		nativeName: "සිංහල",
+	},
+	sk: {
+		nativeName: "Slovenčina",
+	},
+	sl: {
+		nativeName: "Slovenščina",
+	},
+	so: {
+		nativeName: "Soomaaliga",
+	},
+	sq: {
+		nativeName: "Shqip",
+	},
+	sr: {
+		nativeName: "Српски",
+	},
+	su: {
+		nativeName: "Basa Sunda",
+	},
+	sv: {
+		nativeName: "Svenska",
+	},
+	sw: {
+		nativeName: "Kiswahili",
+	},
+	ta: {
+		nativeName: "தமிழ்",
+	},
+	te: {
+		nativeName: "తెలుగు",
+	},
+	tg: {
+		nativeName: "забо́ни тоҷикӣ́",
+	},
+	th: {
+		nativeName: "ภาษาไทย",
+	},
+	tr: {
+		nativeName: "Türkçe",
+	},
+	tt: {
+		nativeName: "татарча",
+	},
+	uk: {
+		nativeName: "Українська",
+	},
+	ur: {
+		nativeName: "اردو",
+		rtl: true,
+	},
+	uz: {
+		nativeName: "O'zbek",
+	},
+	vi: {
+		nativeName: "Tiếng Việt",
+	},
+	xh: {
+		nativeName: "isiXhosa",
+	},
+	yi: {
+		nativeName: "ייִדיש",
+		rtl: true,
+	},
+	zh: {
+		nativeName: "中文",
+	},
+	zu: {
+		nativeName: "isiZulu",
+	},
+};
+
+export const iso639Langs3 = {
+	ach: {
+		nativeName: "Lwo",
+	},
+	ady: {
+		nativeName: "Адыгэбзэ",
+	},
+	cak: {
+		nativeName: "Maya Kaqchikel",
+	},
+	chr: {
+		nativeName: "ᏣᎳᎩ (tsalagi)",
+	},
+	dsb: {
+		nativeName: "Dolnoserbšćina",
+	},
+	fil: {
+		nativeName: "Filipino",
+	},
+	hsb: {
+		nativeName: "Hornjoserbšćina",
+	},
+	kab: {
+		nativeName: "Taqbaylit",
+	},
+	mai: {
+		nativeName: "मैथिली, মৈথিলী",
+	},
+	tlh: {
+		nativeName: "tlhIngan-Hol",
+	},
+	tok: {
+		nativeName: "Toki Pona",
+	},
+	yue: {
+		nativeName: "粵語",
+	},
+	nan: {
+		nativeName: "閩南語",
+	},
+};
+
+export const langmapNoRegion = Object.assign({}, iso639Langs1, iso639Langs3);
+
+export const iso639Regional = {
+	"zh-hans": {
+		nativeName: "中文(简体)",
+	},
+	"zh-hant": {
+		nativeName: "中文(繁體)",
+	},
+};
+
+export const langmap = Object.assign({}, langmapNoRegion, iso639Regional);
+export type PostLanguage = keyof typeof langmap;
diff --git a/packages/firefish-js/src/misc/schema.ts b/packages/firefish-js/src/misc/schema.ts
new file mode 100644
index 0000000000..811190e3e1
--- /dev/null
+++ b/packages/firefish-js/src/misc/schema.ts
@@ -0,0 +1,241 @@
+import {
+	packedUserLiteSchema,
+	packedUserDetailedNotMeOnlySchema,
+	packedMeDetailedOnlySchema,
+	packedUserDetailedNotMeSchema,
+	packedMeDetailedSchema,
+	packedUserDetailedSchema,
+	packedUserSchema,
+} from "../schema/user.js";
+import { packedNoteSchema } from "../schema/note.js";
+import { packedUserListSchema } from "../schema/user-list.js";
+import { packedAppSchema } from "../schema/app.js";
+import { packedMessagingMessageSchema } from "../schema/messaging-message.js";
+import { packedNotificationSchema } from "../schema/notification.js";
+import { packedDriveFileSchema } from "../schema/drive-file.js";
+import { packedDriveFolderSchema } from "../schema/drive-folder.js";
+import { packedFollowingSchema } from "../schema/following.js";
+import { packedMutingSchema } from "../schema/muting.js";
+import { packedRenoteMutingSchema } from "../schema/renote-muting.js";
+import { packedReplyMutingSchema } from "../schema/reply-muting.js";
+import { packedBlockingSchema } from "../schema/blocking.js";
+import { packedNoteReactionSchema } from "../schema/note-reaction.js";
+import { packedHashtagSchema } from "../schema/hashtag.js";
+import { packedPageSchema } from "../schema/page.js";
+import { packedUserGroupSchema } from "../schema/user-group.js";
+import { packedNoteFavoriteSchema } from "../schema/note-favorite.js";
+import { packedChannelSchema } from "../schema/channel.js";
+import { packedAntennaSchema } from "../schema/antenna.js";
+import { packedClipSchema } from "../schema/clip.js";
+import { packedFederationInstanceSchema } from "../schema/federation-instance.js";
+import { packedQueueCountSchema } from "../schema/queue.js";
+import { packedGalleryPostSchema } from "../schema/gallery-post.js";
+import { packedEmojiSchema } from "../schema/emoji.js";
+import { packedNoteEdit } from "../schema/note-edit.js";
+import { packedNoteFileSchema } from "../schema/note-file.js";
+import { packedAbuseUserReportSchema } from "../schema/abuse-user-report.js";
+
+export const refs = {
+	AbuseUserReport: packedAbuseUserReportSchema,
+	UserLite: packedUserLiteSchema,
+	UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
+	MeDetailedOnly: packedMeDetailedOnlySchema,
+	UserDetailedNotMe: packedUserDetailedNotMeSchema,
+	MeDetailed: packedMeDetailedSchema,
+	UserDetailed: packedUserDetailedSchema,
+	User: packedUserSchema,
+
+	UserList: packedUserListSchema,
+	UserGroup: packedUserGroupSchema,
+	App: packedAppSchema,
+	MessagingMessage: packedMessagingMessageSchema,
+	Note: packedNoteSchema,
+	NoteFile: packedNoteFileSchema,
+	NoteEdit: packedNoteEdit,
+	NoteReaction: packedNoteReactionSchema,
+	NoteFavorite: packedNoteFavoriteSchema,
+	Notification: packedNotificationSchema,
+	DriveFile: packedDriveFileSchema,
+	DriveFolder: packedDriveFolderSchema,
+	Following: packedFollowingSchema,
+	Muting: packedMutingSchema,
+	RenoteMuting: packedRenoteMutingSchema,
+	ReplyMuting: packedReplyMutingSchema,
+	Blocking: packedBlockingSchema,
+	Hashtag: packedHashtagSchema,
+	Page: packedPageSchema,
+	Channel: packedChannelSchema,
+	QueueCount: packedQueueCountSchema,
+	Antenna: packedAntennaSchema,
+	Clip: packedClipSchema,
+	FederationInstance: packedFederationInstanceSchema,
+	GalleryPost: packedGalleryPostSchema,
+	Emoji: packedEmojiSchema,
+};
+
+// biome-ignore lint/suspicious/noExplicitAny: used it intentially
+type ExplicitlyUsedAny = any;
+
+export type Packed<x extends keyof typeof refs> = SchemaType<(typeof refs)[x]>;
+
+type TypeStringef =
+	| "null"
+	| "boolean"
+	| "integer"
+	| "number"
+	| "string"
+	| "array"
+	| "object"
+	| "any";
+type StringDefToType<T extends TypeStringef> = T extends "null"
+	? null
+	: T extends "boolean"
+		? boolean
+		: T extends "integer"
+			? number
+			: T extends "number"
+				? number
+				: T extends "string"
+					? string | Date
+					: T extends "array"
+						? ReadonlyArray<ExplicitlyUsedAny>
+						: T extends "object"
+							? Record<string, ExplicitlyUsedAny>
+							: ExplicitlyUsedAny;
+
+// https://swagger.io/specification/?sbsearch=optional#schema-object
+type OfSchema = {
+	readonly anyOf?: ReadonlyArray<Schema>;
+	readonly oneOf?: ReadonlyArray<Schema>;
+	readonly allOf?: ReadonlyArray<Schema>;
+};
+
+export interface Schema extends OfSchema {
+	readonly type?: TypeStringef;
+	readonly nullable?: boolean;
+	readonly optional?: boolean;
+	readonly items?: Schema;
+	readonly properties?: Obj;
+	readonly required?: ReadonlyArray<
+		Extract<keyof NonNullable<this["properties"]>, string>
+	>;
+	readonly description?: string;
+	readonly example?: ExplicitlyUsedAny;
+	readonly format?: string;
+	readonly ref?: keyof typeof refs;
+	readonly enum?: ReadonlyArray<string>;
+	readonly default?:
+		| (this["type"] extends TypeStringef
+				? StringDefToType<this["type"]>
+				: ExplicitlyUsedAny)
+		| null;
+	readonly maxLength?: number;
+	readonly minLength?: number;
+	readonly maximum?: number;
+	readonly minimum?: number;
+	readonly pattern?: string;
+}
+
+type RequiredPropertyNames<s extends Obj> = {
+	[K in keyof s]: // K is not optional
+	s[K]["optional"] extends false
+		? K
+		: // K has default value
+			s[K]["default"] extends
+					| null
+					| string
+					| number
+					| boolean
+					| Record<string, unknown>
+			? K
+			: never;
+}[keyof s];
+
+export type Obj = Record<string, Schema>;
+
+// https://github.com/misskey-dev/misskey/issues/8535
+// To avoid excessive stack depth error,
+// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
+export type ObjType<
+	s extends Obj,
+	RequiredProps extends keyof s,
+> = UnionToIntersection<
+	{
+		-readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]>;
+	} & {
+		-readonly [R in RequiredProps]-?: SchemaType<s[R]>;
+	} & {
+		-readonly [P in keyof s]?: SchemaType<s[P]>;
+	}
+>;
+
+type NullOrUndefined<p extends Schema, T> =
+	| (p["nullable"] extends true ? null : never)
+	| (p["optional"] extends true ? undefined : never)
+	| T;
+
+// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
+// Get intersection from union
+type UnionToIntersection<U> = (
+	U extends ExplicitlyUsedAny
+		? (k: U) => void
+		: never
+) extends (k: infer I) => void
+	? I
+	: never;
+
+// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
+// To get union, we use `Foo extends ExplicitlyUsedAny ? Hoge<Foo> : never`
+type UnionSchemaType<
+	a extends readonly ExplicitlyUsedAny[],
+	X extends Schema = a[number],
+> = X extends ExplicitlyUsedAny ? SchemaType<X> : never;
+type ArrayUnion<T> = T extends ExplicitlyUsedAny ? Array<T> : never;
+
+export type SchemaTypeDef<p extends Schema> = p["type"] extends "null"
+	? null
+	: p["type"] extends "integer"
+		? number
+		: p["type"] extends "number"
+			? number
+			: p["type"] extends "string"
+				? p["enum"] extends readonly string[]
+					? p["enum"][number]
+					: p["format"] extends "date-time"
+						? string
+						: // Dateにする??
+							string
+				: p["type"] extends "boolean"
+					? boolean
+					: p["type"] extends "object"
+						? p["ref"] extends keyof typeof refs
+							? Packed<p["ref"]>
+							: p["properties"] extends NonNullable<Obj>
+								? ObjType<p["properties"], NonNullable<p["required"]>[number]>
+								: p["anyOf"] extends ReadonlyArray<Schema>
+									? UnionSchemaType<p["anyOf"]> &
+											Partial<UnionToIntersection<UnionSchemaType<p["anyOf"]>>>
+									: p["allOf"] extends ReadonlyArray<Schema>
+										? UnionToIntersection<UnionSchemaType<p["allOf"]>>
+										: ExplicitlyUsedAny
+						: p["type"] extends "array"
+							? p["items"] extends OfSchema
+								? p["items"]["anyOf"] extends ReadonlyArray<Schema>
+									? UnionSchemaType<NonNullable<p["items"]["anyOf"]>>[]
+									: p["items"]["oneOf"] extends ReadonlyArray<Schema>
+										? ArrayUnion<
+												UnionSchemaType<NonNullable<p["items"]["oneOf"]>>
+											>
+										: p["items"]["allOf"] extends ReadonlyArray<Schema>
+											? UnionToIntersection<
+													UnionSchemaType<NonNullable<p["items"]["allOf"]>>
+												>[]
+											: never
+								: p["items"] extends NonNullable<Schema>
+									? SchemaTypeDef<p["items"]>[]
+									: ExplicitlyUsedAny[]
+							: p["oneOf"] extends ReadonlyArray<Schema>
+								? UnionSchemaType<p["oneOf"]>
+								: ExplicitlyUsedAny;
+
+export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;
diff --git a/packages/backend/src/models/schema/abuse-user-report.ts b/packages/firefish-js/src/schema/abuse-user-report.ts
similarity index 100%
rename from packages/backend/src/models/schema/abuse-user-report.ts
rename to packages/firefish-js/src/schema/abuse-user-report.ts
diff --git a/packages/backend/src/models/schema/antenna.ts b/packages/firefish-js/src/schema/antenna.ts
similarity index 100%
rename from packages/backend/src/models/schema/antenna.ts
rename to packages/firefish-js/src/schema/antenna.ts
diff --git a/packages/backend/src/models/schema/app.ts b/packages/firefish-js/src/schema/app.ts
similarity index 100%
rename from packages/backend/src/models/schema/app.ts
rename to packages/firefish-js/src/schema/app.ts
diff --git a/packages/backend/src/models/schema/blocking.ts b/packages/firefish-js/src/schema/blocking.ts
similarity index 100%
rename from packages/backend/src/models/schema/blocking.ts
rename to packages/firefish-js/src/schema/blocking.ts
diff --git a/packages/backend/src/models/schema/channel.ts b/packages/firefish-js/src/schema/channel.ts
similarity index 100%
rename from packages/backend/src/models/schema/channel.ts
rename to packages/firefish-js/src/schema/channel.ts
diff --git a/packages/backend/src/models/schema/clip.ts b/packages/firefish-js/src/schema/clip.ts
similarity index 100%
rename from packages/backend/src/models/schema/clip.ts
rename to packages/firefish-js/src/schema/clip.ts
diff --git a/packages/backend/src/models/schema/drive-file.ts b/packages/firefish-js/src/schema/drive-file.ts
similarity index 100%
rename from packages/backend/src/models/schema/drive-file.ts
rename to packages/firefish-js/src/schema/drive-file.ts
diff --git a/packages/backend/src/models/schema/drive-folder.ts b/packages/firefish-js/src/schema/drive-folder.ts
similarity index 100%
rename from packages/backend/src/models/schema/drive-folder.ts
rename to packages/firefish-js/src/schema/drive-folder.ts
diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/firefish-js/src/schema/emoji.ts
similarity index 100%
rename from packages/backend/src/models/schema/emoji.ts
rename to packages/firefish-js/src/schema/emoji.ts
diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/firefish-js/src/schema/federation-instance.ts
similarity index 97%
rename from packages/backend/src/models/schema/federation-instance.ts
rename to packages/firefish-js/src/schema/federation-instance.ts
index 338e079e28..9ef0d337b5 100644
--- a/packages/backend/src/models/schema/federation-instance.ts
+++ b/packages/firefish-js/src/schema/federation-instance.ts
@@ -1,5 +1,3 @@
-import { config } from "@/config.js";
-
 export const packedFederationInstanceSchema = {
 	type: "object",
 	properties: {
@@ -83,7 +81,7 @@ export const packedFederationInstanceSchema = {
 			type: "string",
 			optional: false,
 			nullable: true,
-			example: config.version,
+			example: "20240424",
 		},
 		openRegistrations: {
 			type: "boolean",
diff --git a/packages/backend/src/models/schema/following.ts b/packages/firefish-js/src/schema/following.ts
similarity index 100%
rename from packages/backend/src/models/schema/following.ts
rename to packages/firefish-js/src/schema/following.ts
diff --git a/packages/backend/src/models/schema/gallery-post.ts b/packages/firefish-js/src/schema/gallery-post.ts
similarity index 100%
rename from packages/backend/src/models/schema/gallery-post.ts
rename to packages/firefish-js/src/schema/gallery-post.ts
diff --git a/packages/backend/src/models/schema/hashtag.ts b/packages/firefish-js/src/schema/hashtag.ts
similarity index 100%
rename from packages/backend/src/models/schema/hashtag.ts
rename to packages/firefish-js/src/schema/hashtag.ts
diff --git a/packages/backend/src/models/schema/messaging-message.ts b/packages/firefish-js/src/schema/messaging-message.ts
similarity index 100%
rename from packages/backend/src/models/schema/messaging-message.ts
rename to packages/firefish-js/src/schema/messaging-message.ts
diff --git a/packages/backend/src/models/schema/muting.ts b/packages/firefish-js/src/schema/muting.ts
similarity index 100%
rename from packages/backend/src/models/schema/muting.ts
rename to packages/firefish-js/src/schema/muting.ts
diff --git a/packages/backend/src/models/schema/note-edit.ts b/packages/firefish-js/src/schema/note-edit.ts
similarity index 100%
rename from packages/backend/src/models/schema/note-edit.ts
rename to packages/firefish-js/src/schema/note-edit.ts
diff --git a/packages/backend/src/models/schema/note-favorite.ts b/packages/firefish-js/src/schema/note-favorite.ts
similarity index 100%
rename from packages/backend/src/models/schema/note-favorite.ts
rename to packages/firefish-js/src/schema/note-favorite.ts
diff --git a/packages/backend/src/models/schema/note-file.ts b/packages/firefish-js/src/schema/note-file.ts
similarity index 100%
rename from packages/backend/src/models/schema/note-file.ts
rename to packages/firefish-js/src/schema/note-file.ts
diff --git a/packages/backend/src/models/schema/note-reaction.ts b/packages/firefish-js/src/schema/note-reaction.ts
similarity index 100%
rename from packages/backend/src/models/schema/note-reaction.ts
rename to packages/firefish-js/src/schema/note-reaction.ts
diff --git a/packages/backend/src/models/schema/note.ts b/packages/firefish-js/src/schema/note.ts
similarity index 94%
rename from packages/backend/src/models/schema/note.ts
rename to packages/firefish-js/src/schema/note.ts
index 6064919960..73e85e6f0d 100644
--- a/packages/backend/src/models/schema/note.ts
+++ b/packages/firefish-js/src/schema/note.ts
@@ -1,4 +1,4 @@
-import { langmap } from "@/misc/langmap.js";
+import { langmap } from "../misc/langmap.js";
 
 export const packedNoteSchema = {
 	type: "object",
@@ -208,15 +208,5 @@ export const packedNoteSchema = {
 			optional: true,
 			nullable: true,
 		},
-		myRenoteCount: {
-			type: "number",
-			optional: true,
-			nullable: false,
-		},
-		quoteCount: {
-			type: "number",
-			optional: false,
-			nullable: false,
-		},
 	},
 } as const;
diff --git a/packages/backend/src/models/schema/notification.ts b/packages/firefish-js/src/schema/notification.ts
similarity index 96%
rename from packages/backend/src/models/schema/notification.ts
rename to packages/firefish-js/src/schema/notification.ts
index 97fd16339c..85d7d33970 100644
--- a/packages/backend/src/models/schema/notification.ts
+++ b/packages/firefish-js/src/schema/notification.ts
@@ -1,4 +1,4 @@
-import { notificationTypes } from "@/types.js";
+import { notificationTypes } from "../consts";
 
 export const packedNotificationSchema = {
 	type: "object",
diff --git a/packages/backend/src/models/schema/page.ts b/packages/firefish-js/src/schema/page.ts
similarity index 100%
rename from packages/backend/src/models/schema/page.ts
rename to packages/firefish-js/src/schema/page.ts
diff --git a/packages/backend/src/models/schema/queue.ts b/packages/firefish-js/src/schema/queue.ts
similarity index 100%
rename from packages/backend/src/models/schema/queue.ts
rename to packages/firefish-js/src/schema/queue.ts
diff --git a/packages/backend/src/models/schema/renote-muting.ts b/packages/firefish-js/src/schema/renote-muting.ts
similarity index 100%
rename from packages/backend/src/models/schema/renote-muting.ts
rename to packages/firefish-js/src/schema/renote-muting.ts
diff --git a/packages/backend/src/models/schema/reply-muting.ts b/packages/firefish-js/src/schema/reply-muting.ts
similarity index 100%
rename from packages/backend/src/models/schema/reply-muting.ts
rename to packages/firefish-js/src/schema/reply-muting.ts
diff --git a/packages/backend/src/models/schema/user-group.ts b/packages/firefish-js/src/schema/user-group.ts
similarity index 100%
rename from packages/backend/src/models/schema/user-group.ts
rename to packages/firefish-js/src/schema/user-group.ts
diff --git a/packages/backend/src/models/schema/user-list.ts b/packages/firefish-js/src/schema/user-list.ts
similarity index 100%
rename from packages/backend/src/models/schema/user-list.ts
rename to packages/firefish-js/src/schema/user-list.ts
diff --git a/packages/backend/src/models/schema/user.ts b/packages/firefish-js/src/schema/user.ts
similarity index 100%
rename from packages/backend/src/models/schema/user.ts
rename to packages/firefish-js/src/schema/user.ts
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 16a4ad6ad4..929d19ef20 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -153,6 +153,9 @@ importers:
       file-type:
         specifier: 19.0.0
         version: 19.0.0
+      firefish-js:
+        specifier: workspace:*
+        version: link:../firefish-js
       fluent-ffmpeg:
         specifier: 2.1.2
         version: 2.1.2