From 018e2e998b0738cd809fa13970fa4fc207eac0cc Mon Sep 17 00:00:00 2001 From: monoid Date: Tue, 30 Sep 2025 20:13:31 +0900 Subject: [PATCH] feat: (BREAKING!) migrate elysia js --- packages/server/package.json | 14 +- packages/server/src/app.ts | 4 +- packages/server/src/controller.ts | 18 + packages/server/src/diff/router.ts | 132 ++- .../server/src/diff/watcher/ComicConfig.ts | 2 +- packages/server/src/login.ts | 467 +++++----- packages/server/src/model/doc.ts | 1 - packages/server/src/permission/permission.ts | 54 +- packages/server/src/route/all.ts | 63 -- packages/server/src/route/comic.ts | 132 +-- packages/server/src/route/contents.ts | 423 ++++----- packages/server/src/route/error_handler.ts | 62 +- packages/server/src/route/tags.ts | 50 +- packages/server/src/route/util.ts | 37 - packages/server/src/route/video.ts | 67 -- packages/server/src/server.ts | 360 +++----- packages/server/src/setting.ts | 11 + packages/server/src/util/type_check.ts | 17 - pnpm-lock.yaml | 839 +++++------------- 19 files changed, 1034 insertions(+), 1719 deletions(-) create mode 100644 packages/server/src/controller.ts delete mode 100644 packages/server/src/route/all.ts delete mode 100644 packages/server/src/route/util.ts delete mode 100644 packages/server/src/route/video.ts create mode 100644 packages/server/src/setting.ts delete mode 100644 packages/server/src/util/type_check.ts diff --git a/packages/server/package.json b/packages/server/package.json index 1086e23..ee9a1ef 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,5 +1,5 @@ { - "name": "followed", + "name": "server", "version": "1.0.0", "description": "", "main": "build/app.js", @@ -12,28 +12,24 @@ "author": "", "license": "ISC", "dependencies": { + "@elysiajs/cors": "^1.3.3", + "@elysiajs/html": "^1.3.1", + "@elysiajs/static": "^1.3.0", "@std/async": "npm:@jsr/std__async@^1.0.13", "@zip.js/zip.js": "^2.7.62", "better-sqlite3": "^9.6.0", "chokidar": "^3.6.0", "dbtype": "workspace:dbtype", "dotenv": "^16.5.0", + "elysia": "^1.3.20", "jose": "^5.10.0", - "koa": "^2.16.1", - "koa-bodyparser": "^4.4.1", - "koa-compose": "^4.1.0", - "koa-router": "^12.0.1", "kysely": "^0.27.6", "natural-orderby": "^2.0.3", "tiny-async-pool": "^1.3.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", - "@types/jsonwebtoken": "^8.5.9", - "@types/koa": "^2.15.0", - "@types/koa-bodyparser": "^4.3.12", "@types/koa-compose": "^3.2.8", - "@types/koa-router": "^7.4.8", "@types/node": "^22.15.33", "@types/tiny-async-pool": "^1.0.5", "tsx": "^4.20.3", diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 88ebde1..e1158bb 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -1,7 +1,5 @@ import { create_server } from "./server.ts"; -create_server().then((server) => { - server.start_server(); -}).catch((err) => { +create_server().catch((err) => { console.error(err); }); \ No newline at end of file diff --git a/packages/server/src/controller.ts b/packages/server/src/controller.ts new file mode 100644 index 0000000..422edc2 --- /dev/null +++ b/packages/server/src/controller.ts @@ -0,0 +1,18 @@ +import Elysia from "elysia"; +import { connectDB } from "./database.ts"; +import { createSqliteDocumentAccessor, createSqliteTagController, createSqliteUserController } from "./db/mod.ts"; + +export async function createControllers() { + const db = await connectDB(); + + const userController = createSqliteUserController(db); + const documentController = createSqliteDocumentAccessor(db); + const tagController = createSqliteTagController(db); + + return { + userController, + documentController, + tagController + }; +} + diff --git a/packages/server/src/diff/router.ts b/packages/server/src/diff/router.ts index c3be4f2..bf76284 100644 --- a/packages/server/src/diff/router.ts +++ b/packages/server/src/diff/router.ts @@ -1,85 +1,57 @@ -import type Koa from "koa"; -import Router from "koa-router"; -import type { ContentFile } from "../content/mod.ts"; -import { AdminOnlyMiddleware } from "../permission/permission.ts"; -import { sendError } from "../route/error_handler.ts"; +import { Elysia, t } from "elysia"; import type { DiffManager } from "./diff.ts"; +import type { ContentFile } from "../content/mod.ts"; +import { AdminOnly } from "../permission/permission.ts"; +import { sendError } from "../route/error_handler.ts"; -function content_file_to_return(x: ContentFile) { - return { path: x.path, type: x.type }; -} +const toSerializableContent = (file: ContentFile) => ({ path: file.path, type: file.type }); -export const getAdded = (diffmgr: DiffManager) => (ctx: Koa.Context, next: Koa.Next) => { - const ret = diffmgr.getAdded(); - ctx.body = ret.map((x) => ({ - type: x.type, - value: x.value.map((x) => ({ path: x.path, type: x.type })), - })); - ctx.type = "json"; -}; +const CommitEntrySchema = t.Array(t.Object({ + type: t.String(), + path: t.String(), +})); -type PostAddedBody = { - type: string; - path: string; -}[]; +const CommitAllSchema = t.Object({ + type: t.String(), +}); -function checkPostAddedBody(body: unknown): body is PostAddedBody { - if (Array.isArray(body)) { - return body.map((x) => "type" in x && "path" in x).every((x) => x); - } - return false; -} - -export const postAdded = (diffmgr: DiffManager) => async (ctx: Router.IRouterContext, next: Koa.Next) => { - const reqbody = ctx.request.body; - if (!checkPostAddedBody(reqbody)) { - sendError(400, "format exception"); - return; - } - const allWork = reqbody.map((op) => diffmgr.commit(op.type, op.path)); - const results = await Promise.all(allWork); - ctx.body = { - ok: true, - docs: results, - }; - ctx.type = "json"; - await next(); -}; -export const postAddedAll = (diffmgr: DiffManager) => async (ctx: Router.IRouterContext, next: Koa.Next) => { - if (!ctx.is("json")) { - sendError(400, "format exception"); - return; - } - const reqbody = ctx.request.body as Record; - if (!("type" in reqbody)) { - sendError(400, 'format exception: there is no "type"'); - return; - } - const t = reqbody.type; - if (typeof t !== "string") { - sendError(400, 'format exception: invalid type of "type"'); - return; - } - await diffmgr.commitAll(t); - ctx.body = { - ok: true, - }; - ctx.type = "json"; - await next(); -}; -/* -export const getNotWatched = (diffmgr : DiffManager)=> (ctx:Router.IRouterContext,next:Koa.Next)=>{ - ctx.body = { - added: diffmgr.added.map(content_file_to_return), - deleted: diffmgr.deleted.map(content_file_to_return), - }; - ctx.type = 'json'; -}*/ - -export function createDiffRouter(diffmgr: DiffManager) { - const ret = new Router(); - ret.get("/list", AdminOnlyMiddleware, getAdded(diffmgr)); - ret.post("/commit", AdminOnlyMiddleware, postAdded(diffmgr)); - ret.post("/commitall", AdminOnlyMiddleware, postAddedAll(diffmgr)); - return ret; -} +export const createDiffRouter = (diffmgr: DiffManager) => + new Elysia({ name: "diff-router" }) + .group("/diff", (app) => + app + .get("/list", () => { + return diffmgr.getAdded().map((entry) => ({ + type: entry.type, + value: entry.value.map(toSerializableContent), + })); + }, { + beforeHandle: AdminOnly, + }) + .post("/commit", async ({ body }) => { + if (body.length === 0) { + return { ok: true, docs: [] as number[] }; + } + const results = await Promise.all(body.map(({ type, path }) => diffmgr.commit(type, path))); + return { + ok: true, + docs: results, + }; + }, { + beforeHandle: AdminOnly, + body: CommitEntrySchema, + }) + .post("/commitall", async ({ body }) => { + const { type } = body; + if (!type) { + sendError(400, 'format exception: there is no "type"'); + } + await diffmgr.commitAll(type); + return { ok: true }; + }, { + beforeHandle: AdminOnly, + body: CommitAllSchema, + }) + .get("/*", () => { + sendError(404); + }) + ); diff --git a/packages/server/src/diff/watcher/ComicConfig.ts b/packages/server/src/diff/watcher/ComicConfig.ts index da42a85..d2157b0 100644 --- a/packages/server/src/diff/watcher/ComicConfig.ts +++ b/packages/server/src/diff/watcher/ComicConfig.ts @@ -1,5 +1,5 @@ import { ConfigManager } from "../../util/configRW.ts"; -import ComicSchema from "./ComicConfig.schema.json" assert { type: "json" }; +import ComicSchema from "./ComicConfig.schema.json" with { type: "json" }; export interface ComicConfig { watch: string[]; } diff --git a/packages/server/src/login.ts b/packages/server/src/login.ts index 3374529..1b9aba6 100644 --- a/packages/server/src/login.ts +++ b/packages/server/src/login.ts @@ -1,19 +1,8 @@ +import { Elysia, t, type Context } from "elysia"; import { SignJWT, jwtVerify, errors } from "jose"; -import type Koa from "koa"; -import Router from "koa-router"; import type { IUser, UserAccessor } from "./model/mod.ts"; -import { sendError } from "./route/error_handler.ts"; +import { ClientRequestError } from "./route/error_handler.ts"; import { get_setting } from "./SettingConfig.ts"; -import { LoginRequestSchema, LoginResetRequestSchema } from "dbtype"; - -type LoginResponse = { - accessExpired: number; -} & PayloadInfo; - -type RefreshResponse = { - accessExpired: number; - refresh: boolean; -} & PayloadInfo; type PayloadInfo = { username: string; @@ -24,18 +13,41 @@ export type UserState = { user: PayloadInfo; }; -const isUserState = (obj: object | string): obj is PayloadInfo => { - if (typeof obj === "string") return false; - return "username" in obj && "permission" in obj && Array.isArray((obj as { permission: unknown }).permission); +type AuthStore = { + user: PayloadInfo; + refreshed: boolean; + authenticated: boolean; }; +type LoginResponse = { + accessExpired: number; +} & PayloadInfo; + +type RefreshResponse = { + accessExpired: number; + refresh: boolean; +} & PayloadInfo; + type RefreshPayloadInfo = { username: string }; -const isRefreshToken = (obj: object | string): obj is RefreshPayloadInfo => { - if (typeof obj === "string") return false; - return "username" in obj && typeof (obj as { username: unknown }).username === "string"; -}; -const accessExpiredTime = 60 * 60 * 2; // 2 hour +type CookieJar = Context["cookie"]; + +const LoginBodySchema = t.Object({ + username: t.String(), + password: t.String(), +}); + +const ResetBodySchema = t.Object({ + username: t.String(), + oldpassword: t.String(), + newpassword: t.String(), +}); + +const SettingsBodySchema = t.Record(t.String(), t.Unknown()); + +const accessExpiredTime = 60 * 60 * 2; // 2 hours +const refreshExpiredTime = 60 * 60 * 24 * 14; // 14 days + async function createAccessToken(payload: PayloadInfo, secret: string) { return await new SignJWT(payload) .setProtectedHeader({ alg: "HS256" }) @@ -43,7 +55,6 @@ async function createAccessToken(payload: PayloadInfo, secret: string) { .sign(new TextEncoder().encode(secret)); } -const refreshExpiredTime = 60 * 60 * 24 * 14; // 14 day; async function createRefreshToken(payload: RefreshPayloadInfo, secret: string) { return await new SignJWT(payload) .setProtectedHeader({ alg: "HS256" }) @@ -57,10 +68,10 @@ class TokenExpiredError extends Error { } } -async function verifyToken(token: string, secret: string) { +async function verifyToken(token: string, secret: string): Promise { try { const { payload } = await jwtVerify(token, new TextEncoder().encode(secret)); - return payload as PayloadInfo; + return payload as T; } catch (error) { if (error instanceof errors.JWTExpired) { throw new TokenExpiredError(); @@ -72,241 +83,245 @@ async function verifyToken(token: string, secret: string) { export const accessTokenName = "access_token"; export const refreshTokenName = "refresh_token"; -function setToken(ctx: Koa.Context, token_name: string, token_payload: string | null, expiredtime: number) { - const setting = get_setting(); - if (token_payload === null && !ctx.cookies.get(token_name)) { +function setToken(cookie: CookieJar, token_name: string, token_payload: string | null, expiredSeconds: number) { + if (token_payload === null) { + cookie[token_name]?.remove(); return; } - ctx.cookies.set(token_name, token_payload, { + const setting = get_setting(); + cookie[token_name].set({ + value: token_payload, httpOnly: true, secure: setting.secure, sameSite: "strict", - expires: new Date(Date.now() + expiredtime * 1000), + expires: new Date(Date.now() + expiredSeconds * 1000), }); } -export const createLoginHandler = (userController: UserAccessor) => async (ctx: Koa.Context, _next: Koa.Next) => { - const setting = get_setting(); - const secretKey = setting.jwt_secretkey; - const body = ctx.request.body; - const { - username, - password, - } = LoginRequestSchema.parse(body); - // if admin login is forbidden? - if (username === "admin" && setting.forbid_remote_admin_login) { - return sendError(403, "forbidden remote admin login"); +const isUserState = (obj: unknown): obj is PayloadInfo => { + if (typeof obj !== "object" || obj === null) { + return false; } - const user = await userController.findUser(username); - // username not exist - if (user === undefined) return sendError(401, "not authorized"); - // password not matched - if (!user.password.check_password(password)) { - return sendError(401, "not authorized"); - } - // create token - const userPermission = await user.get_permissions(); - const payload = await createAccessToken({ - username: user.username, - permission: userPermission, - }, secretKey); - const payload2 = await createRefreshToken({ - username: user.username, - }, secretKey); - setToken(ctx, accessTokenName, payload, accessExpiredTime); - setToken(ctx, refreshTokenName, payload2, refreshExpiredTime); - ctx.body = { - username: user.username, - permission: userPermission, - accessExpired: Math.floor(Date.now() / 1000) + accessExpiredTime, - } satisfies LoginResponse; - console.log(`${username} logined`); - return; + return "username" in obj && "permission" in obj && Array.isArray((obj as { permission: unknown }).permission); }; -export const LogoutHandler = (ctx: Koa.Context, _next: Koa.Next) => { +const isRefreshToken = (obj: unknown): obj is RefreshPayloadInfo => { + if (typeof obj !== "object" || obj === null) { + return false; + } + return "username" in obj && typeof (obj as { username: unknown }).username === "string"; +}; + +type AuthResult = { + user: PayloadInfo; + refreshed: boolean; + success: boolean; +}; + +async function authenticate( + cookie: CookieJar, + userController: UserAccessor, + options: { forceRefresh?: boolean } = {}, +): Promise { const setting = get_setting(); - ctx.cookies.set(accessTokenName, null); - ctx.cookies.set(refreshTokenName, null); - ctx.body = { - ok: true, + const secretKey = setting.jwt_secretkey; + const accessCookie = cookie[accessTokenName]; + const refreshCookie = cookie[refreshTokenName]; + const accessValue = accessCookie?.value; + const refreshValue = refreshCookie?.value; + + const guestUser: PayloadInfo = { username: "", permission: setting.guest, }; - return; -}; -export const createUserHandler = - (userController: UserAccessor) => async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { - const refreshToken = makeRefreshToken(userController); - const setting = get_setting(); - const setGuest = async () => { - setToken(ctx, accessTokenName, null, 0); - setToken(ctx, refreshTokenName, null, 0); - ctx.state.user = { username: "", permission: setting.guest }; - return await next(); + const setGuest = (): AuthResult => { + accessCookie?.remove(); + refreshCookie?.remove(); + return { user: guestUser, refreshed: false, success: false }; + }; + + const issueAccessForUser = async (username: string): Promise => { + const account = await userController.findUser(username); + if (!account) { + return setGuest(); + } + const permissions = await account.get_permissions(); + const payload: PayloadInfo = { + username: account.username, + permission: permissions, }; - return await refreshToken(ctx, setGuest, next); + const accessToken = await createAccessToken(payload, secretKey); + setToken(cookie, accessTokenName, accessToken, accessExpiredTime); + return { user: payload, refreshed: true, success: true }; }; -const makeRefreshToken = (cntr: UserAccessor) => async (ctx: Koa.Context, fail: Koa.Next, next: Koa.Next) => { - const accessPayload = ctx.cookies.get(accessTokenName); - const setting = get_setting(); - const secretKey = setting.jwt_secretkey; - - if (!accessPayload) { - return await checkRefreshAndUpdate(); - } - - try { - const payload = await verifyToken(accessPayload, secretKey); - if (isUserState(payload)) { - ctx.state.user = payload; - return await next(); + const tryRefresh = async (): Promise => { + if (!refreshValue) { + return setGuest(); } - console.error("Invalid token detected"); - throw new Error("Token form invalid"); - } catch (error) { - if (error instanceof TokenExpiredError) { - return await checkRefreshAndUpdate(); - } - throw error; - } - - async function checkRefreshAndUpdate() { - const refreshPayload = ctx.cookies.get(refreshTokenName); - if (!refreshPayload) { - return await fail(); // Refresh token doesn't exist - } - try { - const payload = await verifyToken(refreshPayload, secretKey); - if (isRefreshToken(payload)) { - const user = await cntr.findUser(payload.username); - if (!user) return await fail(); // User does not exist - - const permissions = await user.get_permissions(); - const newAccessToken = await createAccessToken({ - username: user.username, - permission: permissions, - }, secretKey); - - setToken(ctx, accessTokenName, newAccessToken, accessExpiredTime); - ctx.state.user = { username: payload.username, permission: permissions }; - } else { - console.error("Invalid token detected"); - throw new Error("Token form invalid"); + const payload = await verifyToken(refreshValue, secretKey); + if (!isRefreshToken(payload)) { + return setGuest(); } + return await issueAccessForUser(payload.username); + } catch { + return setGuest(); + } + }; + + if (options.forceRefresh) { + if (accessValue) { + try { + const payload = await verifyToken(accessValue, secretKey); + if (isUserState(payload)) { + const accessToken = await createAccessToken(payload, secretKey); + setToken(cookie, accessTokenName, accessToken, accessExpiredTime); + return { user: payload, refreshed: true, success: true }; + } + return setGuest(); + } catch (error) { + if (!(error instanceof TokenExpiredError)) { + return setGuest(); + } + } + } + return await tryRefresh(); + } + + if (accessValue) { + try { + const payload = await verifyToken(accessValue, secretKey); + if (isUserState(payload)) { + return { user: payload, refreshed: false, success: true }; + } + return setGuest(); } catch (error) { - if (error instanceof TokenExpiredError) { - // Refresh token is expired - return await fail(); + if (!(error instanceof TokenExpiredError)) { + return setGuest(); } - throw error; } - - return await next(); } -}; -export const createRefreshTokenMiddleware = (cntr: UserAccessor) => async (ctx: Koa.Context, next: Koa.Next) => { - const handler = makeRefreshToken(cntr); - await handler(ctx, fail, success); - async function fail() { - const user = ctx.state.user as PayloadInfo; - ctx.body = { - refresh: false, - accessExpired: 0, - ...user, - } satisfies RefreshResponse; - ctx.type = "json"; - }; - async function success() { - const user = ctx.state.user as PayloadInfo; - ctx.body = { - ...user, - refresh: true, - accessExpired: Math.floor(Date.now() / 1000 + accessExpiredTime), - } satisfies RefreshResponse; - ctx.type = "json"; - } -}; - - -export const resetPasswordMiddleware = (cntr: UserAccessor) => async (ctx: Koa.Context, next: Koa.Next) => { - const body = ctx.request.body; - const { - username, - oldpassword, - newpassword, - } = LoginResetRequestSchema.parse(body); - const user = await cntr.findUser(username); - if (user === undefined) { - return sendError(403, "not authorized"); - } - if (!user.password.check_password(oldpassword)) { - return sendError(403, "not authorized"); - } - user.reset_password(newpassword); - ctx.body = { ok: true }; - ctx.type = "json"; -}; - -export function getUserSettingHandler(userController: UserAccessor) { - return async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { - const username = ctx.state.user.username; - if (!username) { - return sendError(403, "not authorized"); - } - const user = await userController.findUser(username); - if (user === undefined) { - return sendError(403, "not authorized"); - } - const settings = await user.get_settings(); - if (settings === undefined) { - ctx.body = {}; - ctx.type = "json"; - return; - } - ctx.body = settings; - ctx.type = "json"; - await next(); - }; -} -export function setUserSettingHandler(userController: UserAccessor) { - return async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { - const username = ctx.state.user.username; - if (!username) { - return sendError(403, "not authorized"); - } - const user = await userController.findUser(username); - if (user === undefined) { - return sendError(403, "not authorized"); - } - const body = ctx.request.body; - const settings = body as Record; - await user.set_settings(settings); - ctx.body = { ok: true }; - ctx.type = "json"; - await next(); - }; + return await tryRefresh(); } -export function createLoginRouter(userController: UserAccessor) { - const router = new Router(); - router.post("/login", createLoginHandler(userController)); - router.post("/logout", LogoutHandler); - router.post("/refresh", createRefreshTokenMiddleware(userController)); - router.post("/reset", resetPasswordMiddleware(userController)); - router.get("/settings", getUserSettingHandler(userController)); - router.post("/settings", setUserSettingHandler(userController)); - return router; -} +export const createLoginRouter = (userController: UserAccessor) => { + return new Elysia({ name: "login-router" }) + .group("/user", (app) => + app + .post("/login", async ({ body, cookie, set }) => { + const setting = get_setting(); + const secretKey = setting.jwt_secretkey; + const { username, password } = body; + + if (username === "admin" && setting.forbid_remote_admin_login) { + throw new ClientRequestError(403, "forbidden remote admin login"); + } + + const user = await userController.findUser(username); + if (!user || !user.password.check_password(password)) { + throw new ClientRequestError(401, "not authorized"); + } + + const permission = await user.get_permissions(); + const accessToken = await createAccessToken({ username: user.username, permission }, secretKey); + const refreshToken = await createRefreshToken({ username: user.username }, secretKey); + + setToken(cookie, accessTokenName, accessToken, accessExpiredTime); + setToken(cookie, refreshTokenName, refreshToken, refreshExpiredTime); + + set.status = 200; + + return { + username: user.username, + permission, + accessExpired: Math.floor(Date.now() / 1000) + accessExpiredTime, + } satisfies LoginResponse; + }, { + body: LoginBodySchema, + }) + .post("/logout", ({ cookie, set }) => { + const setting = get_setting(); + setToken(cookie, accessTokenName, null, 0); + setToken(cookie, refreshTokenName, null, 0); + set.status = 200; + return { + ok: true, + username: "", + permission: setting.guest, + }; + }) + .post("/refresh", async ({ cookie }) => { + const auth = await authenticate(cookie, userController, { forceRefresh: true }); + if (!auth.success) { + throw new ClientRequestError(401, "not authorized"); + } + return { + ...auth.user, + refresh: true, + accessExpired: Math.floor(Date.now() / 1000) + accessExpiredTime, + } satisfies RefreshResponse; + }) + .post("/reset", async ({ body }) => { + const { username, oldpassword, newpassword } = body; + const account = await userController.findUser(username); + if (!account || !account.password.check_password(oldpassword)) { + throw new ClientRequestError(403, "not authorized"); + } + await account.reset_password(newpassword); + return { ok: true }; + }, { + body: ResetBodySchema, + }) + .get("/settings", async ({ store }) => { + const { user } = store as AuthStore; + if (!user.username) { + throw new ClientRequestError(403, "not authorized"); + } + const account = await userController.findUser(user.username); + if (!account) { + throw new ClientRequestError(403, "not authorized"); + } + return (await account.get_settings()) ?? {}; + }) + .post("/settings", async ({ body, store }) => { + const { user } = store as AuthStore; + if (!user.username) { + throw new ClientRequestError(403, "not authorized"); + } + const account = await userController.findUser(user.username); + if (!account) { + throw new ClientRequestError(403, "not authorized"); + } + await account.set_settings(body as Record); + return { ok: true }; + }, { + body: SettingsBodySchema, + }), + ); +}; + +export const createUserHandler = (userController: UserAccessor) => { + return new Elysia({ + name: "user-handler", + seed: "UserAccess", + }) + .derive({ as: "scoped" }, async ({ cookie }) => { + const auth = await authenticate(cookie, userController); + return { + user: auth.user, + refreshed: auth.refreshed, + authenticated: auth.success, + }; + }); +}; export const getAdmin = async (cntr: UserAccessor) => { const admin = await cntr.findUser("admin"); if (admin === undefined) { - throw new Error("initial process failed!"); // ??? + throw new Error("initial process failed!"); } return admin; }; diff --git a/packages/server/src/model/doc.ts b/packages/server/src/model/doc.ts index ec7ed75..47a651e 100644 --- a/packages/server/src/model/doc.ts +++ b/packages/server/src/model/doc.ts @@ -1,4 +1,3 @@ -import { check_type } from "../util/type_check.ts"; import type { DocumentBody, QueryListOption, diff --git a/packages/server/src/permission/permission.ts b/packages/server/src/permission/permission.ts index 04f96c7..ce79ef0 100644 --- a/packages/server/src/permission/permission.ts +++ b/packages/server/src/permission/permission.ts @@ -1,6 +1,5 @@ -import type Koa from "koa"; -import type { UserState } from "../login.ts"; import { sendError } from "../route/error_handler.ts"; +import type { UserState } from "../login.ts"; export enum Permission { // ======== @@ -34,27 +33,36 @@ export enum Permission { modifyTagDesc = "ModifyTagDesc", } -export const createPermissionCheckMiddleware = - (...permissions: string[]) => - async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { - const user = ctx.state.user; - if (user.username === "admin") { - return await next(); - } - const user_permission = user.permission; - // if permissions is not subset of user permission - if (!permissions.map((p) => user_permission.includes(p)).every((x) => x)) { - if (user.username === "") { - return sendError(401, "you are guest. login needed."); - } return sendError(403, "do not have permission"); - } - await next(); - }; +type PermissionCheckContext = { + user?: UserState["user"]; + store?: { user?: UserState["user"] }; +} & Record; -export const AdminOnlyMiddleware = async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { - const user = ctx.state.user; - if (user.username !== "admin") { - return sendError(403, "admin only"); +const resolveUser = (context: PermissionCheckContext): UserState["user"] => { + const user = context.user ?? context.store?.user; + if (!user) { + sendError(401, "you are guest. login needed."); } - await next(); + return user as UserState["user"]; +}; + +export const createPermissionCheck = (...permissions: string[]) => (context: PermissionCheckContext) => { + const user = resolveUser(context); + if (user.username === "admin") { + return; + } + const user_permission = user.permission; + if (!permissions.every((p) => user_permission.includes(p))) { + if (user.username === "") { + throw sendError(401, "you are guest. login needed."); + } + throw sendError(403, "do not have permission"); + } +}; + +export const AdminOnly = (context: PermissionCheckContext) => { + const user = resolveUser(context); + if (user.username !== "admin") { + throw sendError(403, "admin only"); + } }; diff --git a/packages/server/src/route/all.ts b/packages/server/src/route/all.ts deleted file mode 100644 index 34fbd09..0000000 --- a/packages/server/src/route/all.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { DefaultContext, Middleware, Next, ParameterizedContext } from "koa"; -import compose from "koa-compose"; -import Router from "koa-router"; -import ComicRouter from "./comic.ts"; -import type { ContentContext } from "./context.ts"; -import VideoRouter from "./video.ts"; - -const table: { [s: string]: Router | undefined } = { - comic: new ComicRouter(), - video: new VideoRouter(), -}; -const all_middleware = - (cont: string | undefined, restarg: string | undefined) => - async (ctx: ParameterizedContext, next: Next) => { - if (cont === undefined) { - ctx.status = 404; - return; - } - if (ctx.state.location === undefined) { - ctx.status = 404; - return; - } - - if (ctx.state.location.type !== cont) { - console.error("not matched"); - ctx.status = 404; - return; - } - const router = table[cont]; - if (router === undefined) { - ctx.status = 404; - return; - } - const rest = `/${restarg ?? ""}`; - const result = router.match(rest, "GET"); - if (!result.route) { - return await next(); - } - // biome-ignore lint/suspicious/noExplicitAny: - const chain = result.pathAndMethod.reduce((combination: Middleware[], cur) => { - combination.push(async (ctx, next) => { - const captures = cur.captures(rest); - ctx.params = cur.params(rest, captures); - ctx.request.params = ctx.params; - ctx.routerPath = cur.path; - return await next(); - }); - return combination.concat(cur.stack); - }, []); - return await compose(chain)(ctx, next); - }; -export class AllContentRouter extends Router { - constructor() { - super(); - this.get("/:content_type", async (ctx, next) => { - return await all_middleware(ctx.params.content_type, undefined)(ctx, next); - }); - this.get("/:content_type/:rest(.*)", async (ctx, next) => { - const cont = ctx.params.content_type as string; - return await all_middleware(cont, ctx.params.rest)(ctx, next); - }); - } -} diff --git a/packages/server/src/route/comic.ts b/packages/server/src/route/comic.ts index 6b30b0b..d10748b 100644 --- a/packages/server/src/route/comic.ts +++ b/packages/server/src/route/comic.ts @@ -1,72 +1,90 @@ -import type { Context } from "koa"; -import Router from "koa-router"; -import { createReadableStreamFromZip, entriesByNaturalOrder, readZip } from "../util/zipwrap.ts"; -import type { ContentContext } from "./context.ts"; -import { since_last_modified } from "./util.ts"; +import type { Context as ElysiaContext } from "elysia"; import { Readable } from "node:stream"; +import { createReadableStreamFromZip, entriesByNaturalOrder, readZip } from "../util/zipwrap.ts"; +const imageExtensions = new Set(["gif", "png", "jpeg", "bmp", "webp", "jpg", "avif"]); -async function renderZipImage(ctx: Context, path: string, page: number) { - const image_ext = ["gif", "png", "jpeg", "bmp", "webp", "jpg", "avif"]; +const extensionToMime = (ext: string) => { + if (ext === "jpg") return "image/jpeg"; + return `image/${ext}`; +}; + +type ResponseSet = Pick; + +type RenderOptions = { + path: string; + page: number; + reqHeaders: Headers; + set: ResponseSet; +}; + +export async function renderComicPage({ path, page, reqHeaders, set }: RenderOptions) { const zip = await readZip(path); - const entries = (await entriesByNaturalOrder(zip.reader)).filter((x) => { - const ext = x.filename.split(".").pop(); - return ext !== undefined && image_ext.includes(ext); - }); - if (0 <= page && page < entries.length) { - const entry = entries[page]; - const last_modified = entry.lastModDate; - if (since_last_modified(ctx, last_modified)) { - return; - } - const read_stream = await createReadableStreamFromZip(zip.reader, entry); - const nodeReadableStream = new Readable(); - nodeReadableStream._read = () => { }; - read_stream.pipeTo(new WritableStream({ + try { + const entries = (await entriesByNaturalOrder(zip.reader)).filter((entry) => { + const ext = entry.filename.split(".").pop()?.toLowerCase(); + return ext !== undefined && imageExtensions.has(ext); + }); + + if (page < 0 || page >= entries.length) { + set.status = 404; + zip.reader.close(); + return null; + } + + const entry = entries[page]; + const lastModified = entry.lastModDate ?? new Date(); + const ifModifiedSince = reqHeaders.get("if-modified-since"); + + const headers = (set.headers ??= {} as Record); + headers["Date"] = new Date().toUTCString(); + headers["Last-Modified"] = lastModified.toUTCString(); + + if (ifModifiedSince) { + const cachedDate = new Date(ifModifiedSince); + if (!Number.isNaN(cachedDate.valueOf()) && lastModified <= cachedDate) { + set.status = 304; + zip.reader.close(); + return null; + } + } + + const readStream = await createReadableStreamFromZip(zip.reader, entry); + const nodeReadable = new Readable({ + read() { + // noop + }, + }); + + readStream.pipeTo(new WritableStream({ write(chunk) { - nodeReadableStream.push(chunk); + nodeReadable.push(chunk); }, close() { - nodeReadableStream.push(null); + nodeReadable.push(null); }, - })); - nodeReadableStream.on("error", (err) => { - console.error("readalbe stream error",err); - ctx.status = 500; - ctx.body = "Internal Server Error"; - zip.reader.close(); - return; + })).catch((err) => { + nodeReadable.destroy(err); }); - nodeReadableStream.on("close", () => { + + nodeReadable.on("close", () => { + zip.reader.close(); + }); + nodeReadable.on("error", () => { zip.reader.close(); }); - ctx.body = nodeReadableStream; - ctx.response.length = entry.uncompressedSize; - ctx.response.type = entry.filename.split(".").pop() as string; - ctx.status = 200; - ctx.set("Date", new Date().toUTCString()); - ctx.set("Last-Modified", last_modified.toUTCString()); - } else { - ctx.status = 404; + const ext = entry.filename.split(".").pop()?.toLowerCase() ?? "jpeg"; + headers["Content-Type"] = extensionToMime(ext); + if (typeof entry.uncompressedSize === "number") { + headers["Content-Length"] = entry.uncompressedSize.toString(); + } + + set.status = 200; + return nodeReadable; + } catch (error) { + zip.reader.close(); + throw error; } } - -export class ComicRouter extends Router { - constructor() { - super(); - this.get("/", async (ctx, next) => { - await renderZipImage(ctx, ctx.state.location.path, 0); - }); - this.get("/:page(\\d+)", async (ctx, next) => { - const page = Number.parseInt(ctx.params.page); - await renderZipImage(ctx, ctx.state.location.path, page); - }); - this.get("/thumbnail", async (ctx, next) => { - await renderZipImage(ctx, ctx.state.location.path, 0); - }); - } -} - -export default ComicRouter; diff --git a/packages/server/src/route/contents.ts b/packages/server/src/route/contents.ts index 83d03f9..df1b67d 100644 --- a/packages/server/src/route/contents.ts +++ b/packages/server/src/route/contents.ts @@ -1,248 +1,193 @@ -import type { Context, Next } from "koa"; -import Router from "koa-router"; +import { Elysia, t } from "elysia"; import { join } from "node:path"; -import type { - Document, - QueryListOption, -} from "dbtype"; +import type { Document, QueryListOption } from "dbtype"; import type { DocumentAccessor } from "../model/doc.ts"; -import { - AdminOnlyMiddleware as AdminOnly, - createPermissionCheckMiddleware as PerCheck, - Permission as Per, -} from "../permission/permission.ts"; -import { AllContentRouter } from "./all.ts"; -import type { ContentLocation } from "./context.ts"; +import { AdminOnly, createPermissionCheck, Permission as Per } from "../permission/permission.ts"; import { sendError } from "./error_handler.ts"; -import { ParseQueryArgString, ParseQueryArray, ParseQueryBoolean, ParseQueryNumber } from "./util.ts"; import { oshash } from "src/util/oshash.ts"; - - -const ContentIDHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - const document = await controller.findById(num, true); - if (document === undefined) { - return sendError(404, "document does not exist."); - } - ctx.body = document; - ctx.type = "json"; -}; -const ContentTagIDHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - const document = await controller.findById(num, true); - if (document === undefined) { - return sendError(404, "document does not exist."); - } - ctx.body = document.tags; - ctx.type = "json"; -}; -const ContentQueryHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - - const query_limit = ctx.query.limit; - const query_cursor = ctx.query.cursor; - const query_word = ctx.query.word; - const query_content_type = ctx.query.content_type; - const query_offset = ctx.query.offset; - const query_use_offset = ctx.query.use_offset; - if ([ - query_limit, - query_cursor, - query_word, - query_content_type, - query_offset, - query_use_offset, - ].some((x) => Array.isArray(x))) { - return sendError(400, "paramter can not be array"); - } - const limit = Math.min(ParseQueryNumber(query_limit) ?? 20, 100); - const cursor = ParseQueryNumber(query_cursor); - const word = ParseQueryArgString(query_word); - const content_type = ParseQueryArgString(query_content_type); - const offset = ParseQueryNumber(query_offset); - if (Number.isNaN(limit) || Number.isNaN(cursor) || Number.isNaN(offset)) { - return sendError(400, "parameter limit, cursor or offset is not a number"); - } - const allow_tag = ParseQueryArray(ctx.query.allow_tag); - const [ok, use_offset] = ParseQueryBoolean(query_use_offset); - if (!ok) { - return sendError(400, "use_offset must be true or false."); - } - const option: QueryListOption = { - limit: limit, - allow_tag: allow_tag, - word: word, - cursor: cursor, - eager_loading: true, - offset: offset, - use_offset: use_offset ?? false, - content_type: content_type, - }; - const document = await controller.findList(option); - ctx.body = document; - ctx.type = "json"; -}; -const UpdateContentHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - - if (ctx.request.type !== "json") { - return sendError(400, "update fail. invalid document type: it is not json."); - } - if (typeof ctx.request.body !== "object") { - return sendError(400, "update fail. invalid argument: not"); - } - const content_desc: Partial & { id: number } = { - id: num, - ...ctx.request.body, - }; - const success = await controller.update(content_desc); - ctx.body = JSON.stringify(success); - ctx.type = "json"; -}; - -const AddTagHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - let tag_name = ctx.params.tag; - const num = Number.parseInt(ctx.params.num); - if (typeof tag_name === "undefined") { - return sendError(400, "??? Unreachable"); - } - tag_name = String(tag_name); - const c = await controller.findById(num); - if (c === undefined) { - return sendError(404); - } - const r = await controller.addTag(c, tag_name); - ctx.body = JSON.stringify(r); - ctx.type = "json"; -}; - -const DelTagHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - let tag_name = ctx.params.tag; - const num = Number.parseInt(ctx.params.num); - if (typeof tag_name === "undefined") { - return sendError(400, "?? Unreachable"); - } - tag_name = String(tag_name); - const c = await controller.findById(num); - if (c === undefined) { - return sendError(404); - } - const r = await controller.delTag(c, tag_name); - ctx.body = JSON.stringify(r); - ctx.type = "json"; -}; - -const DeleteContentHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - const r = await controller.del(num); - ctx.body = JSON.stringify(r); - ctx.type = "json"; -}; - -const ContentHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - const document = await controller.findById(num, true); - if (document === undefined) { - return sendError(404, "document does not exist."); - } - if (document.deleted_at !== null) { - return sendError(404, "document has been removed."); - } - const path = join(document.basepath, document.filename); - ctx.state.location = { - path: path, - type: document.content_type, - additional: document.additional, - } as ContentLocation; - await next(); -}; - -function RehashContentHandler(controller: DocumentAccessor) { - return async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - const c = await controller.findById(num); - if (c === undefined || c.deleted_at !== null) { - return sendError(404); - } - const filepath = join(c.basepath, c.filename); - let new_hash: string; - try { - new_hash = (await oshash(filepath)).toString(); - } - catch (e) { - // if file is not found, return 404 - if ( (e as NodeJS.ErrnoException).code === "ENOENT") { - return sendError(404, "file not found"); - } - throw e; - } - const r = await controller.update({ - id: num, - content_hash: new_hash, - }); - ctx.body = JSON.stringify(r); - ctx.type = "json"; - }; -} - -function getSimilarDocumentHandler(controller: DocumentAccessor) { - return async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - const c = await controller.findById(num, true); - if (c === undefined) { - return sendError(404); - } - const r = await controller.getSimilarDocument(c); - ctx.body = r; - ctx.type = "json"; - }; -} - -function getRescanDocumentHandler(controller: DocumentAccessor) { - return async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params.num); - const c = await controller.findById(num, true); - if (c === undefined) { - return sendError(404); - } - await controller.rescanDocument(c); - // 204 No Content - ctx.status = 204; - }; -} - -function ContentGidListHandler(controller: DocumentAccessor) { - return async (ctx: Context, next: Next) => { - const gid_list = ParseQueryArray(ctx.query.gid).map((x) => Number.parseInt(x)) - if (gid_list.some((x) => Number.isNaN(x))) { - return sendError(400, "gid is not a number"); - } - // size limit - if (gid_list.length > 100) { - return sendError(400, "gid list is too long"); - } - const r = await controller.findByGidList(gid_list); - ctx.body = r; - ctx.type = "json"; - }; -} +import { renderComicPage } from "./comic.ts"; export const getContentRouter = (controller: DocumentAccessor) => { - const ret = new Router(); - ret.get("/search", PerCheck(Per.QueryContent), ContentQueryHandler(controller)); - ret.get("/_gid", PerCheck(Per.QueryContent), ContentGidListHandler(controller)); - ret.get("/:num(\\d+)", PerCheck(Per.QueryContent), ContentIDHandler(controller)); - ret.all("/:num(\\d+)/(.*)", PerCheck(Per.QueryContent), ContentHandler(controller)); - ret.post("/:num(\\d+)", AdminOnly, UpdateContentHandler(controller)); - ret.del("/:num(\\d+)", AdminOnly, DeleteContentHandler(controller)); - // ret.post("/",AdminOnly,CreateContentHandler(controller)); - ret.get("/:num(\\d+)/similars", PerCheck(Per.QueryContent), getSimilarDocumentHandler(controller)); - ret.get("/:num(\\d+)/tags", PerCheck(Per.QueryContent), ContentTagIDHandler(controller)); - ret.post("/:num(\\d+)/tags/:tag", PerCheck(Per.ModifyTag), AddTagHandler(controller)); - ret.del("/:num(\\d+)/tags/:tag", PerCheck(Per.ModifyTag), DelTagHandler(controller)); - ret.use("/:num(\\d+)", PerCheck(Per.QueryContent), new AllContentRouter().routes()); - ret.post("/:num(\\d+)/_rehash", AdminOnly, RehashContentHandler(controller)); - ret.post("/:num(\\d+)/_rescan", AdminOnly, getRescanDocumentHandler(controller)); - return ret; + return new Elysia({ name: "content-router" }) + .get("/search", async ({ query }) => { + const limit = Math.min(Number(query.limit ?? 20), 100); + const option: QueryListOption = { + limit: limit, + allow_tag: query.allow_tag?.split(",") ?? [], + word: query.word, + cursor: Number(query.cursor), + eager_loading: true, + offset: Number(query.offset), + use_offset: query.use_offset === 'true', + content_type: query.content_type, + }; + return await controller.findList(option); + }, { + beforeHandle: createPermissionCheck(Per.QueryContent), + query: t.Object({ + limit: t.Optional(t.String()), + cursor: t.Optional(t.String()), + word: t.Optional(t.String()), + content_type: t.Optional(t.String()), + offset: t.Optional(t.String()), + use_offset: t.Optional(t.String()), + allow_tag: t.Optional(t.String()), + }) + }) + .get("/_gid", async ({ query }) => { + const gid_list = query.gid.split(",").map(x => Number.parseInt(x)); + if (gid_list.some(x => Number.isNaN(x)) || gid_list.length > 100) { + throw sendError(400, "Invalid GID list"); + } + return await controller.findByGidList(gid_list); + }, { + beforeHandle: createPermissionCheck(Per.QueryContent), + query: t.Object({ gid: t.String() }) + }) + .get("/:num", async ({ params: { num } }) => { + const document = await controller.findById(num, true); + if (document === undefined) { + throw sendError(404, "document does not exist."); + } + return document; + }, { + beforeHandle: createPermissionCheck(Per.QueryContent), + params: t.Object({ num: t.Numeric() }) + }) + .post("/:num", async ({ params: { num }, body }) => { + const content_desc: Partial & { id: number } = { + id: num, + ...body, + }; + return await controller.update(content_desc); + }, { + beforeHandle: AdminOnly, + params: t.Object({ num: t.Numeric() }), + body: t.Object({}, { additionalProperties: true }) + }) + .delete("/:num", async ({ params: { num } }) => { + return await controller.del(num); + }, { + beforeHandle: AdminOnly, + params: t.Object({ num: t.Numeric() }) + }) + .get("/:num/similars", async ({ params: { num } }) => { + const doc = await controller.findById(num, true); + if (doc === undefined) { + throw sendError(404); + } + return await controller.getSimilarDocument(doc); + }, { + beforeHandle: createPermissionCheck(Per.QueryContent), + params: t.Object({ num: t.Numeric() }) + }) + .get("/:num/tags", async ({ params: { num } }) => { + const document = await controller.findById(num, true); + if (document === undefined) { + throw sendError(404, "document does not exist."); + } + return document.tags; + }, { + beforeHandle: createPermissionCheck(Per.QueryContent), + params: t.Object({ num: t.Numeric() }) + }) + .post("/:num/tags/:tag", async ({ params: { num, tag } }) => { + const doc = await controller.findById(num); + if (doc === undefined) { + throw sendError(404); + } + return await controller.addTag(doc, tag); + }, { + beforeHandle: createPermissionCheck(Per.ModifyTag), + params: t.Object({ num: t.Numeric(), tag: t.String() }) + }) + .delete("/:num/tags/:tag", async ({ params: { num, tag } }) => { + const doc = await controller.findById(num); + if (doc === undefined) { + throw sendError(404); + } + return await controller.delTag(doc, tag); + }, { + beforeHandle: createPermissionCheck(Per.ModifyTag), + params: t.Object({ num: t.Numeric(), tag: t.String() }) + }) + .post("/:num/_rehash", async ({ params: { num } }) => { + const doc = await controller.findById(num); + if (doc === undefined || doc.deleted_at !== null) { + throw sendError(404); + } + const filepath = join(doc.basepath, doc.filename); + try { + const new_hash = (await oshash(filepath)).toString(); + return await controller.update({ id: num, content_hash: new_hash }); + } catch (e) { + if ((e as NodeJS.ErrnoException).code === "ENOENT") { + throw sendError(404, "file not found"); + } + throw e; + } + }, { + beforeHandle: AdminOnly, + params: t.Object({ num: t.Numeric() }) + }) + .post("/:num/_rescan", async ({ params: { num }, set }) => { + const doc = await controller.findById(num, true); + if (doc === undefined) { + throw sendError(404); + } + await controller.rescanDocument(doc); + set.status = 204; // No Content + }, { + beforeHandle: AdminOnly, + params: t.Object({ num: t.Numeric() }) + }) + .group("/:num", (app) => + app + .derive(async ({ params: { num } }) => { + const docId = typeof num === "number" ? num : Number.parseInt(String(num)); + if (Number.isNaN(docId)) { + throw sendError(400, "invalid document id"); + } + const document = await controller.findById(docId, true); + if (document === undefined) { + throw sendError(404, "document does not exist."); + } + return { document, docId }; + }) + .get("/comic/thumbnail", async ({ document, request, set }) => { + if (document.content_type !== "comic") { + throw sendError(404); + } + const path = join(document.basepath, document.filename); + const body = await renderComicPage({ + path, + page: 0, + reqHeaders: request.headers, + set, + }); + return body ?? undefined; + }, { + beforeHandle: createPermissionCheck(Per.QueryContent), + params: t.Object({ num: t.Numeric() }), + }) + .get("/comic/:page", async ({ document, params: { page }, request, set }) => { + if (document.content_type !== "comic") { + throw sendError(404); + } + const pageIndex = page; + const path = join(document.basepath, document.filename); + const body = await renderComicPage({ + path, + page: pageIndex, + reqHeaders: request.headers, + set, + }); + return body ?? undefined; + }, { + beforeHandle: createPermissionCheck(Per.QueryContent), + params: t.Object({ num: t.Numeric(), page: t.Numeric() }), + }) + ); }; export default getContentRouter; diff --git a/packages/server/src/route/error_handler.ts b/packages/server/src/route/error_handler.ts index 7677cb1..499f028 100644 --- a/packages/server/src/route/error_handler.ts +++ b/packages/server/src/route/error_handler.ts @@ -1,5 +1,4 @@ import { ZodError } from "dbtype"; -import type { Context, Next } from "koa"; export interface ErrorFormat { code: number; @@ -7,15 +6,12 @@ export interface ErrorFormat { detail?: string; } -class ClientRequestError implements Error { - name: string; - message: string; - stack?: string | undefined; +export class ClientRequestError extends Error { code: number; constructor(code: number, message: string) { + super(message); this.name = "client request error"; - this.message = message; this.code = code; } } @@ -25,36 +21,32 @@ const code_to_message_table: { [key: number]: string | undefined } = { 404: "NotFound", }; -export const error_handler = async (ctx: Context, next: Next) => { - try { - await next(); - } catch (err) { - if (err instanceof ClientRequestError) { - const body: ErrorFormat = { - code: err.code, - message: code_to_message_table[err.code] ?? "", - detail: err.message, - }; - ctx.status = err.code; - ctx.body = body; - } - else if (err instanceof ZodError) { - const body: ErrorFormat = { - code: 400, - message: "BadRequest", - detail: err.errors.map((x) => x.message).join(", "), - }; - ctx.status = 400; - ctx.body = body; - } - else { - throw err; - } - } +export const error_handler = ({ code, error, set }: { code: string, error: Error, set: { status?: number | string } }) => { + if (error instanceof ClientRequestError) { + set.status = error.code; + return { + code: error.code, + message: code_to_message_table[error.code] ?? "", + detail: error.message, + } satisfies ErrorFormat; + } + if (error instanceof ZodError) { + set.status = 400; + return { + code: 400, + message: "BadRequest", + detail: error.errors.map((x) => x.message).join(", "), + } satisfies ErrorFormat; + } + + set.status = 500; + return { + code: 500, + message: "Internal Server Error", + detail: error.message, + } }; -export const sendError = (code: number, message?: string) => { +export const sendError = (code: number, message?: string): never => { throw new ClientRequestError(code, message ?? ""); }; - -export default error_handler; diff --git a/packages/server/src/route/tags.ts b/packages/server/src/route/tags.ts index 5a1993d..d8fef1a 100644 --- a/packages/server/src/route/tags.ts +++ b/packages/server/src/route/tags.ts @@ -1,29 +1,31 @@ -import { type Context, Next } from "koa"; -import Router, { type RouterContext } from "koa-router"; +import { Elysia, t } from "elysia"; import type { TagAccessor } from "../model/tag.ts"; -import { createPermissionCheckMiddleware as PerCheck, Permission } from "../permission/permission.ts"; +import { createPermissionCheck, Permission } from "../permission/permission.ts"; import { sendError } from "./error_handler.ts"; export function getTagRounter(tagController: TagAccessor) { - const router = new Router(); - router.get("/", PerCheck(Permission.QueryContent), async (ctx: Context) => { - if (ctx.query.withCount) { - const c = await tagController.getAllTagCount(); - ctx.body = c; - } else { - const c = await tagController.getAllTagList(); - ctx.body = c; - } - ctx.type = "json"; - }); - router.get("/:tag_name", PerCheck(Permission.QueryContent), async (ctx: RouterContext) => { - const tag_name = ctx.params.tag_name; - const c = await tagController.getTagByName(tag_name); - if (!c) { - sendError(404, "tags not found"); - } - ctx.body = c; - ctx.type = "json"; - }); - return router; + return new Elysia({ name: "tags-router" }) + .get("/", async ({ query }) => { + if (query.withCount !== undefined) { + return await tagController.getAllTagCount(); + } + return await tagController.getAllTagList(); + }, { + beforeHandle: createPermissionCheck(Permission.QueryContent), + query: t.Object({ + withCount: t.Optional(t.String()), + }) + }) + .get("/:tag_name", async ({ params: { tag_name } }) => { + const tag = await tagController.getTagByName(tag_name); + if (!tag) { + sendError(404, "tags not found"); + } + return tag; + }, { + beforeHandle: createPermissionCheck(Permission.QueryContent), + params: t.Object({ + tag_name: t.String(), + }) + }); } diff --git a/packages/server/src/route/util.ts b/packages/server/src/route/util.ts deleted file mode 100644 index 8266aab..0000000 --- a/packages/server/src/route/util.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Context } from "koa"; - -export function ParseQueryNumber(s: string[] | string | undefined): number | undefined { - if (s === undefined) return undefined; - if (typeof s === "object") return undefined; - return Number.parseInt(s); -} -export function ParseQueryArray(s: string[] | string | undefined) { - const input = s ?? []; - const r = Array.isArray(input) ? input : input.split(","); - return r.map((x) => decodeURIComponent(x)); -} -export function ParseQueryArgString(s: string[] | string | undefined) { - if (typeof s === "object") return undefined; - return s === undefined ? s : decodeURIComponent(s); -} -export function ParseQueryBoolean(s: string[] | string | undefined): [boolean, boolean | undefined] { - let value: boolean | undefined; - - if (s === "true") { - value = true; - } else if (s === "false") { - value = false; - } else if (s === undefined) { - value = undefined; - } else return [false, undefined]; - return [true, value]; -} - -export function since_last_modified(ctx: Context, last_modified: Date): boolean { - const con = ctx.get("If-Modified-Since"); - if (con === "") return false; - const mdate = new Date(con); - if (last_modified > mdate) return false; - ctx.status = 304; - return true; -} diff --git a/packages/server/src/route/video.ts b/packages/server/src/route/video.ts deleted file mode 100644 index 65d1c91..0000000 --- a/packages/server/src/route/video.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { createReadStream, promises } from "node:fs"; -import type { Context } from "koa"; -import Router from "koa-router"; -import type { ContentContext } from "./context.ts"; - -export async function renderVideo(ctx: Context, path: string) { - const ext = path.trim().split(".").pop(); - if (ext === undefined) { - // ctx.status = 404; - console.error(`${path}:${ext}`); - return; - } - ctx.response.type = ext; - const range_text = ctx.request.get("range"); - const stat = await promises.stat(path); - let start = 0; - let end = 0; - ctx.set("Last-Modified", new Date(stat.mtime).toUTCString()); - ctx.set("Date", new Date().toUTCString()); - ctx.set("Accept-Ranges", "bytes"); - if (range_text === "") { - end = 1024 * 512; - end = Math.min(end, stat.size - 1); - if (start > end) { - ctx.status = 416; - return; - } - ctx.status = 200; - ctx.length = stat.size; - const stream = createReadStream(path); - ctx.body = stream; - } else { - const m = range_text.match(/^bytes=(\d+)-(\d*)/); - if (m === null) { - ctx.status = 416; - return; - } - start = Number.parseInt(m[1]); - end = m[2].length > 0 ? Number.parseInt(m[2]) : start + 1024 * 1024; - end = Math.min(end, stat.size - 1); - if (start > end) { - ctx.status = 416; - return; - } - ctx.status = 206; - ctx.length = end - start + 1; - ctx.response.set("Content-Range", `bytes ${start}-${end}/${stat.size}`); - ctx.body = createReadStream(path, { - start: start, - end: end, - }); // inclusive range. - } -} - -export class VideoRouter extends Router { - constructor() { - super(); - this.get("/", async (ctx, next) => { - await renderVideo(ctx, ctx.state.location.path); - }); - this.get("/thumbnail", async (ctx, next) => { - await renderVideo(ctx, ctx.state.location.path); - }); - } -} - -export default VideoRouter; diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 6ba4037..af8647b 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1,12 +1,13 @@ -import Koa from "koa"; -import Router from "koa-router"; +import { Elysia, t } from "elysia"; +import { cors } from "@elysiajs/cors"; +import { staticPlugin } from "@elysiajs/static"; +import { html } from "@elysiajs/html"; import { connectDB } from "./database.ts"; import { createDiffRouter, DiffManager } from "./diff/mod.ts"; -import { get_setting, SettingConfig } from "./SettingConfig.ts"; +import { get_setting } from "./SettingConfig.ts"; -import { createReadStream, readFileSync } from "node:fs"; -import bodyparser from "koa-bodyparser"; +import { readFileSync } from "node:fs"; import { createSqliteDocumentAccessor, createSqliteTagController, createSqliteUserController } from "./db/mod.ts"; import { createLoginRouter, createUserHandler, getAdmin, isAdminFirst } from "./login.ts"; import getContentRouter from "./route/contents.ts"; @@ -18,238 +19,129 @@ import type { DocumentAccessor, TagAccessor, UserAccessor } from "./model/mod.ts import { getTagRounter } from "./route/tags.ts"; import { config } from "dotenv"; -import { extname, join } from "node:path"; config(); -class ServerApplication { - readonly userController: UserAccessor; - readonly documentController: DocumentAccessor; - readonly tagController: TagAccessor; - readonly diffManger: DiffManager; - readonly app: Koa; - private index_html: string; - private constructor(controller: { - userController: UserAccessor; - documentController: DocumentAccessor; - tagController: TagAccessor; - }) { - this.userController = controller.userController; - this.documentController = controller.documentController; - this.tagController = controller.tagController; - - this.diffManger = new DiffManager(this.documentController); - this.app = new Koa(); - this.index_html = readFileSync("dist/index.html", "utf-8"); - } - private async setup() { - const setting = get_setting(); - const app = this.app; - - if (setting.cli) { - const userAdmin = await getAdmin(this.userController); - if (await isAdminFirst(userAdmin)) { - const rl = createReadlineInterface({ - input: process.stdin, - output: process.stdout, - }); - const pw = await new Promise((res: (data: string) => void, err) => { - rl.question("put admin password :", (data) => { - res(data); - }); - }); - rl.close(); - userAdmin.reset_password(pw); - } - } - app.use(bodyparser()); - app.use(error_handler); - app.use(createUserHandler(this.userController)); - - const diff_router = createDiffRouter(this.diffManger); - this.diffManger.register("comic", createComicWatcher()); - - console.log("setup router"); - - const router = new Router(); - router.use("/api/(.*)", async (ctx, next) => { - // For CORS - ctx.res.setHeader("access-control-allow-origin", "*"); - await next(); - }); - - router.use("/api/diff", diff_router.routes()); - router.use("/api/diff", diff_router.allowedMethods()); - - const content_router = getContentRouter(this.documentController); - router.use("/api/doc", content_router.routes()); - router.use("/api/doc", content_router.allowedMethods()); - - const tags_router = getTagRounter(this.tagController); - router.use("/api/tags", tags_router.allowedMethods()); - router.use("/api/tags", tags_router.routes()); - - this.serve_with_meta_index(router); - this.serve_index(router); - this.serve_static_file(router); - - const login_router = createLoginRouter(this.userController); - router.use("/api/user", login_router.routes()); - router.use("/api/user", login_router.allowedMethods()); - - if (setting.mode === "development") { - let mm_count = 0; - app.use(async (ctx, next) => { - console.log(`=== Request No ${mm_count++} \t===`); - const ip = ctx.get("X-Real-IP").length > 0 ? ctx.get("X-Real-IP") : ctx.ip; - const fromClient = ctx.state.user.username === "" ? ip : ctx.state.user.username; - console.log(`${mm_count} ${fromClient} : ${ctx.method} ${ctx.url}`); - const start = Date.now(); - await next(); - const end = Date.now(); - console.log(`${mm_count} ${fromClient} : ${ctx.method} ${ctx.url} ${ctx.status} ${end - start}ms`); - }); - } - app.use(router.routes()); - app.use(router.allowedMethods()); - console.log("setup done"); - } - private serve_index(router: Router) { - const serveindex = (url: string) => { - router.get(url, (ctx) => { - ctx.type = "html"; - ctx.body = this.index_html; - const setting = get_setting(); - ctx.set("x-content-type-options", "no-sniff"); - if (setting.mode === "development") { - ctx.set("cache-control", "no-cache"); - } else { - ctx.set("cache-control", "public, max-age=3600"); - } - }); - }; - serveindex("/"); - serveindex("/doc/:rest(.*)"); - serveindex("/search"); - serveindex("/login"); - serveindex("/profile"); - serveindex("/difference"); - serveindex("/setting"); - serveindex("/tags"); - } - private serve_with_meta_index(router: Router) { - const DocMiddleware = async (ctx: Koa.ParameterizedContext) => { - const docId = Number.parseInt(ctx.params.id); - const doc = await this.documentController.findById(docId, true); - // biome-ignore lint/suspicious/noImplicitAnyLet: - let meta; - if (doc === undefined) { - ctx.status = 404; - meta = NotFoundContent(); - } else { - ctx.status = 200; - meta = createOgTagContent( - doc.title, - doc.tags.join(", "), - `https://aeolian.monoid.top/api/doc/${docId}/comic/thumbnail`, - ); - } - const html = makeMetaTagInjectedHTML(this.index_html, meta); - serveHTML(ctx, html); - }; - router.get("/doc/:id(\\d+)", DocMiddleware); - - function NotFoundContent() { - return createOgTagContent("Not Found Doc", "Not Found", ""); - } - function makeMetaTagInjectedHTML(html: string, tagContent: string) { - return html.replace("", tagContent); - } - function serveHTML(ctx: Koa.Context, file: string) { - ctx.type = "html"; - ctx.body = file; - const setting = get_setting(); - ctx.set("x-content-type-options", "no-sniff"); - if (setting.mode === "development") { - ctx.set("cache-control", "no-cache"); - } else { - ctx.set("cache-control", "public, max-age=3600"); - } - } - - function createMetaTagContent(key: string, value: string) { - return ``; - } - function createOgTagContent(title: string, description: string, image: string) { - return [ - createMetaTagContent("og:title", title), - createMetaTagContent("og:type", "website"), - createMetaTagContent("og:description", description), - createMetaTagContent("og:image", image), - // createMetaTagContent("og:image:width","480"), - // createMetaTagContent("og:image","480"), - // createMetaTagContent("og:image:type","image/png"), - createMetaTagContent("twitter:card", "summary_large_image"), - createMetaTagContent("twitter:title", title), - createMetaTagContent("twitter:description", description), - createMetaTagContent("twitter:image", image), - ].join("\n"); - } - } - private serve_static_file(router: Router) { - router.get("/assets/(.*)", async (ctx, next) => { - const setting = get_setting(); - const ext = extname(ctx.path); - ctx.type = ext; - ctx.body = createReadStream(join("dist",`.${ctx.path}`)); - ctx.set("x-content-type-options", "no-sniff"); - if (setting.mode === "development") { - ctx.set("cache-control", "no-cache"); - } else { - ctx.set("cache-control", "public, max-age=3600"); - } - }); - // const static_file_server = (path: string, type: string) => { - // router.get(`/${path}`, async (ctx, next) => { - // const setting = get_setting(); - // ctx.type = type; - // ctx.body = createReadStream(path); - // ctx.set("x-content-type-options", "no-sniff"); - // if (setting.mode === "development") { - // ctx.set("cache-control", "no-cache"); - // } else { - // ctx.set("cache-control", "public, max-age=3600"); - // } - // }); - // }; - // const setting = get_setting(); - // static_file_server("dist/bundle.css", "css"); - // static_file_server("dist/bundle.js", "js"); - // if (setting.mode === "development") { - // static_file_server("dist/bundle.js.map", "text"); - // static_file_server("dist/bundle.css.map", "text"); - // } - } - start_server() { - const setting = get_setting(); - // todo : support https - console.log(`listen on http://${setting.localmode ? "localhost" : "0.0.0.0"}:${setting.port}`); - return this.app.listen(setting.port, setting.localmode ? "127.0.0.1" : "0.0.0.0"); - } - static async createServer() { - const db = await connectDB(); - - const app = new ServerApplication({ - userController: createSqliteUserController(db), - documentController: createSqliteDocumentAccessor(db), - tagController: createSqliteTagController(db), - }); - await app.setup(); - return app; - } +function createMetaTagContent(key: string, value: string) { + return ``; } +function createOgTagContent(title: string, description:string, image: string) { + return [ + createMetaTagContent("og:title", title), + createMetaTagContent("og:type", "website"), + createMetaTagContent("og:description", description), + createMetaTagContent("og:image", image), + createMetaTagContent("twitter:card", "summary_large_image"), + createMetaTagContent("twitter:title", title), + createMetaTagContent("twitter:description", description), + createMetaTagContent("twitter:image", image), + ].join("\n"); +} + +function makeMetaTagInjectedHTML(html: string, tagContent: string) { + return html.replace("", tagContent); +} + +const normalizeError = (error: unknown): Error => { + if (error instanceof Error) { + return error; + } + if (typeof error === "string") { + return new Error(error); + } + try { + return new Error(JSON.stringify(error)); + } catch (_err) { + return new Error("Unknown error"); + } +}; + + export async function create_server() { - return await ServerApplication.createServer(); -} + const setting = get_setting(); + const db = await connectDB(); -export default { create_server }; + const userController = createSqliteUserController(db); + const documentController = createSqliteDocumentAccessor(db); + const tagController = createSqliteTagController(db); + const diffManger = new DiffManager(documentController); + diffManger.register("comic", createComicWatcher()); + + if (setting.cli) { + const userAdmin = await getAdmin(userController); + if (await isAdminFirst(userAdmin)) { + const rl = createReadlineInterface({ + input: process.stdin, + output: process.stdout, + }); + const pw = await new Promise((res: (data: string) => void) => { + rl.question("put admin password :", (data) => { + res(data); + }); + }); + rl.close(); + await userAdmin.reset_password(pw); + } + } + + const index_html = readFileSync("dist/index.html", "utf-8"); + + const app = new Elysia() + .use(cors()) + .use(staticPlugin({ + assets: "dist/assets", + prefix: "/assets", + headers: { + "X-Content-Type-Options": "nosniff", + "Cache-Control": setting.mode === "development" ? "no-cache" : "public, max-age=3600", + } + })) + .use(html()) + .onError((context) => error_handler({ + code: typeof context.code === "number" ? String(context.code) : context.code, + error: normalizeError(context.error), + set: context.set, + })) + .use(createUserHandler(userController)) + .group("/api", (app) => app + .use(createDiffRouter(diffManger)) + .use(getContentRouter(documentController)) + .use(getTagRounter(tagController)) + .use(createLoginRouter(userController)) + ) + .get("/doc/:id", async ({ params: { id }, set }) => { + const docId = Number.parseInt(id, 10); + const doc = await documentController.findById(docId, true); + let meta; + if (doc === undefined) { + set.status = 404; + meta = createOgTagContent("Not Found Doc", "Not Found", ""); + } else { + set.status = 200; + meta = createOgTagContent( + doc.title, + doc.tags.join(", "), + `https://aeolian.monoid.top/api/doc/${docId}/comic/thumbnail`, + ); + } + return makeMetaTagInjectedHTML(index_html, meta); + }, { + params: t.Object({ id: t.String() }) + }) + .get("/", () => index_html) + .get("/doc/*", () => index_html) + .get("/search", () => index_html) + .get("/login", () => index_html) + .get("/profile", () => index_html) + .get("/difference", () => index_html) + .get("/setting", () => index_html) + .get("/tags", () => index_html) + .listen({ + hostname: setting.localmode ? "127.0.0.1" : "0.0.0.0", + port: setting.port, + }); + + console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`); + + return app; +} \ No newline at end of file diff --git a/packages/server/src/setting.ts b/packages/server/src/setting.ts new file mode 100644 index 0000000..9c89322 --- /dev/null +++ b/packages/server/src/setting.ts @@ -0,0 +1,11 @@ +import { Elysia } from "elysia"; +import { get_setting } from "./SettingConfig.ts"; +import { connectDB } from "./database.ts"; +import type { Kysely } from "kysely"; +import type { DB } from "dbtype/src/types.ts"; + +export const SettingPlugin = new Elysia({ + name: "setting", + seed: "ServerConfig" +}) + .state("setting", get_setting()); \ No newline at end of file diff --git a/packages/server/src/util/type_check.ts b/packages/server/src/util/type_check.ts deleted file mode 100644 index 6ee6d50..0000000 --- a/packages/server/src/util/type_check.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function check_type(obj: unknown, check_proto: Record): obj is T { - if (typeof obj !== "object" || obj === null) return false; - for (const it in check_proto) { - let defined = check_proto[it]; - if (defined === undefined) return false; - defined = defined.trim(); - if (defined.endsWith("[]")) { - if (!Array.isArray((obj as Record)[it])) { - return false; - } - // biome-ignore lint/suspicious/useValidTypeof: - } else if (defined !== typeof (obj as Record)[it]) { - return false; - } - } - return true; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b06c91..058b00b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,6 +169,15 @@ importers: packages/server: dependencies: + '@elysiajs/cors': + specifier: ^1.3.3 + version: 1.3.3(elysia@1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3)) + '@elysiajs/html': + specifier: ^1.3.1 + version: 1.3.1(elysia@1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3))(typescript@5.8.3) + '@elysiajs/static': + specifier: ^1.3.0 + version: 1.3.0(elysia@1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3)) '@std/async': specifier: npm:@jsr/std__async@^1.0.13 version: '@jsr/std__async@1.0.13' @@ -187,21 +196,12 @@ importers: dotenv: specifier: ^16.5.0 version: 16.5.0 + elysia: + specifier: ^1.3.20 + version: 1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3) jose: specifier: ^5.10.0 version: 5.10.0 - koa: - specifier: ^2.16.1 - version: 2.16.1 - koa-bodyparser: - specifier: ^4.4.1 - version: 4.4.1 - koa-compose: - specifier: ^4.1.0 - version: 4.1.0 - koa-router: - specifier: ^12.0.1 - version: 12.0.1 kysely: specifier: ^0.27.6 version: 0.27.6 @@ -215,21 +215,9 @@ importers: '@types/better-sqlite3': specifier: ^7.6.13 version: 7.6.13 - '@types/jsonwebtoken': - specifier: ^8.5.9 - version: 8.5.9 - '@types/koa': - specifier: ^2.15.0 - version: 2.15.0 - '@types/koa-bodyparser': - specifier: ^4.3.12 - version: 4.3.12 '@types/koa-compose': specifier: ^3.2.8 version: 3.2.8 - '@types/koa-router': - specifier: ^7.4.8 - version: 7.4.8 '@types/node': specifier: ^22.15.33 version: 22.15.33 @@ -421,6 +409,24 @@ packages: cpu: [x64] os: [win32] + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + + '@elysiajs/cors@1.3.3': + resolution: {integrity: sha512-mYIU6PyMM6xIJuj7d27Vt0/wuzVKIEnFPjcvlkyd7t/m9xspAG37cwNjFxVOnyvY43oOd2I/oW2DB85utXpA2Q==} + peerDependencies: + elysia: '>= 1.3.0' + + '@elysiajs/html@1.3.1': + resolution: {integrity: sha512-jOWUfvL9vZ2Gs3uCx2w4Po+jxOwRD/sXW3JgvOAD3rEjX0NuygwcvixtbONSzAH8lFhaDBbHAtmCfpue46X9IQ==} + peerDependencies: + elysia: '>= 1.3.0' + + '@elysiajs/static@1.3.0': + resolution: {integrity: sha512-7mWlj2U/AZvH27IfRKqpUjDP1W9ZRldF9NmdnatFEtx0AOy7YYgyk0rt5hXrH6wPcR//2gO2Qy+k5rwswpEhJA==} + peerDependencies: + elysia: '>= 1.3.0' + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -742,9 +748,6 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} - '@hapi/bourne@3.0.0': - resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} - '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -783,6 +786,17 @@ packages: '@jsr/std__async@1.0.13': resolution: {integrity: sha512-GEApyNtzauJ0kEZ/GxebSkdEN0t29qJtkw+WEvzYTwkL6fHX8cq3YWzRjCqHu+4jMl+rpHiwyr/lfitNInntzA==, tarball: https://npm.jsr.io/~/11/@jsr/std__async/1.0.13.tgz} + '@kitajs/html@4.2.9': + resolution: {integrity: sha512-FDHHf5Mi5nR0D+Btq86IV1O9XfsePVCiC5rwU4PXjw2aHja16FmIiwLZBO0CS16rJxKkibjMldyRLAW2ni2mzA==} + engines: {node: '>=12'} + + '@kitajs/ts-html-plugin@4.1.2': + resolution: {integrity: sha512-XE9iIe93TELBdQSvNC3xxXOPDhkcK7on4Oi2HUKhln3jAc5hzn1o33uzjHCYhLeW36r/LXCT70beoXRCFcuTxQ==} + hasBin: true + peerDependencies: + '@kitajs/html': ^4.2.5 + typescript: ^5.6.2 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1315,6 +1329,9 @@ packages: cpu: [x64] os: [win32] + '@sinclair/typebox@0.34.41': + resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + '@swc/core-darwin-arm64@1.11.31': resolution: {integrity: sha512-NTEaYOts0OGSbJZc0O74xsji+64JrF1stmBii6D5EevWEtrY4wlZhm8SiP/qPrOB+HqtAihxWIukWkP2aSdGSQ==} engines: {node: '>=10'} @@ -1399,6 +1416,13 @@ packages: '@tanstack/virtual-core@3.13.9': resolution: {integrity: sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==} + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@ts-morph/common@0.19.0': resolution: {integrity: sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==} @@ -1468,21 +1492,12 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - '@types/jsonwebtoken@8.5.9': - resolution: {integrity: sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==} - '@types/keygrip@1.0.6': resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} - '@types/koa-bodyparser@4.3.12': - resolution: {integrity: sha512-hKMmRMVP889gPIdLZmmtou/BijaU1tHPyMNmcK7FAHAdATnRcGQQy78EqTTxLH1D4FTsrxIzklAQCso9oGoebQ==} - '@types/koa-compose@3.2.8': resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} - '@types/koa-router@7.4.8': - resolution: {integrity: sha512-SkWlv4F9f+l3WqYNQHnWjYnyTxYthqt8W9az2RTdQW7Ay8bc00iRZcrb8MC75iEfPqnGcg2csEl8tTG1NQPD4A==} - '@types/koa@2.15.0': resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} @@ -1623,10 +1638,6 @@ packages: resolution: {integrity: sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA==} engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=16.5.0'} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1746,26 +1757,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cache-content-type@1.0.1: - resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} - engines: {node: '>= 6.0.0'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1815,22 +1810,22 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - co-body@6.2.0: - resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==} - engines: {node: '>=8.0.0'} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - code-block-writer@12.0.0: resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} @@ -1858,23 +1853,12 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookies@0.9.1: - resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} - engines: {node: '>= 0.8'} - - copy-to@2.0.1: - resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} @@ -1965,9 +1949,6 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - deep-equal@1.0.1: - resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} - deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -1978,25 +1959,10 @@ packages: defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - - depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -2041,29 +2007,25 @@ packages: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.165: resolution: {integrity: sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==} + elysia@1.3.20: + resolution: {integrity: sha512-7j2w/1NALKu1KOPDFEIKaSLZ2gcL92Ij/POssDOglZO04rFFQ3unvBjUVYTrMMKbDn+/tLd5OXuSUztK+pcEtQ==} + peerDependencies: + exact-mirror: '>= 0.0.9' + file-type: '>= 20.0.0' + typescript: '>= 5.0.0' + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -2073,21 +2035,9 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -2102,9 +2052,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -2169,6 +2116,14 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + exact-mirror@0.2.0: + resolution: {integrity: sha512-XnP8M3gIk6vLnpZY4A/RsAXwQLyqj7lCRJhiCZMt3NaIIXHsfzpJRsvG5DMSSYYrjm2xTBGCrPbG4Z9JublGBg==} + peerDependencies: + '@sinclair/typebox': ^0.34.15 + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + execa@7.2.0: resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} @@ -2181,6 +2136,9 @@ packages: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2205,10 +2163,17 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} + file-type@21.0.0: + resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} + engines: {node: '>=20'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -2238,10 +2203,6 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -2264,18 +2225,14 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -2318,10 +2275,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2336,30 +2289,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - http-assert@1.5.0: - resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} - engines: {node: '>= 0.8'} - - http-errors@1.8.1: - resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} - engines: {node: '>= 0.6'} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - https-proxy-agent@6.2.1: resolution: {integrity: sha512-ONsE3+yfZF2caH5+bJlcddtWqNI3Gvs5A38+ngvljxaBiRXRswym2c7yf8UAeFpRFKjFNHIFEHqR/OLAWJzyiA==} engines: {node: '>= 14'} @@ -2368,10 +2301,6 @@ packages: resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2387,10 +2316,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflation@2.1.0: - resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} - engines: {node: '>= 0.8.0'} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -2428,10 +2353,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} - engines: {node: '>= 0.4'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2448,10 +2369,6 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2517,10 +2434,6 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - keygrip@1.1.0: - resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} - engines: {node: '>= 0.6'} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2528,25 +2441,6 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - koa-bodyparser@4.4.1: - resolution: {integrity: sha512-kBH3IYPMb+iAXnrxIhXnW+gXV8OTzCu8VPDqvcDHW9SQrbkHmqPQtiZwrltNmSq6/lpipHnT7k7PsjlVD7kK0w==} - engines: {node: '>=8.0.0'} - - koa-compose@4.1.0: - resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} - - koa-convert@2.0.0: - resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} - engines: {node: '>= 10'} - - koa-router@12.0.1: - resolution: {integrity: sha512-gaDdj3GtzoLoeosacd50kBBTnnh3B9AYxDThQUo4sfUyXdOhY6ku1qyZKW88tQCRgc3Sw6ChXYXWZwwgjOxE0w==} - engines: {node: '>= 12'} - - koa@2.16.1: - resolution: {integrity: sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==} - engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} - kysely-codegen@0.14.1: resolution: {integrity: sha512-W6ULVWYnlBcGalHlKqw3ls0QjPl6n6z1xJ9ixPCh1lxDSbe98s4gjEbKC3hh8ZTCUTZSLSasi+QCii4AL/j7pw==} hasBin: true @@ -2649,14 +2543,6 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2664,10 +2550,6 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -2676,14 +2558,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2748,10 +2622,6 @@ packages: natural-orderby@2.0.3: resolution: {integrity: sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==} - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - node-abi@3.68.0: resolution: {integrity: sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==} engines: {node: '>=10'} @@ -2760,6 +2630,10 @@ packages: resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} engines: {node: '>=10'} + node-cache@5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -2792,14 +2666,6 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2811,8 +2677,8 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - only@0.0.2: - resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -2841,10 +2707,6 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -2871,9 +2733,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2972,17 +2831,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} - rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -3083,6 +2934,10 @@ packages: resolution: {integrity: sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==} engines: {node: '>=8'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3119,13 +2974,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -3142,9 +2990,6 @@ packages: engines: {node: '>=10'} hasBin: true - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shadcn-ui@0.8.0: resolution: {integrity: sha512-avqRgjJ6PIQQXdfvoCAWQpyLTLk6oHhtU5DQKmLeYcgu1ZIsgMqA9MKWAkr0HpEdCAenCCZvFbvJ2C2m5ZXRiA==} hasBin: true @@ -3166,22 +3011,6 @@ packages: engines: {node: '>=4'} hasBin: true - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3216,14 +3045,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -3266,6 +3087,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -3349,9 +3174,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} @@ -3372,10 +3197,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsscmp@1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - tsx@4.20.3: resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} engines: {node: '>=18.0.0'} @@ -3392,10 +3213,6 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - typescript@5.4.3: resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} engines: {node: '>=14.17'} @@ -3406,6 +3223,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3416,10 +3237,6 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -3463,10 +3280,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} @@ -3568,6 +3381,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3576,9 +3393,13 @@ packages: engines: {node: '>= 14.6'} hasBin: true - ylru@1.4.0: - resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} - engines: {node: '>= 4.0.0'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} @@ -3798,6 +3619,25 @@ snapshots: '@biomejs/cli-win32-x64@1.6.3': optional: true + '@borewit/text-codec@0.1.1': {} + + '@elysiajs/cors@1.3.3(elysia@1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3))': + dependencies: + elysia: 1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3) + + '@elysiajs/html@1.3.1(elysia@1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3))(typescript@5.8.3)': + dependencies: + '@kitajs/html': 4.2.9 + '@kitajs/ts-html-plugin': 4.1.2(@kitajs/html@4.2.9)(typescript@5.8.3) + elysia: 1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3) + transitivePeerDependencies: + - typescript + + '@elysiajs/static@1.3.0(elysia@1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3))': + dependencies: + elysia: 1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3) + node-cache: 5.1.2 + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -3982,8 +3822,6 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@hapi/bourne@3.0.0': {} - '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -4024,6 +3862,18 @@ snapshots: '@jsr/std__async@1.0.13': {} + '@kitajs/html@4.2.9': + dependencies: + csstype: 3.1.3 + + '@kitajs/ts-html-plugin@4.1.2(@kitajs/html@4.2.9)(typescript@5.8.3)': + dependencies: + '@kitajs/html': 4.2.9 + chalk: 4.1.2 + tslib: 2.8.1 + typescript: 5.8.3 + yargs: 17.7.2 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4513,6 +4363,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.42.0': optional: true + '@sinclair/typebox@0.34.41': + optional: true + '@swc/core-darwin-arm64@1.11.31': optional: true @@ -4573,6 +4426,16 @@ snapshots: '@tanstack/virtual-core@3.13.9': {} + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.1 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@ts-morph/common@0.19.0': dependencies: fast-glob: 3.3.3 @@ -4655,24 +4518,12 @@ snapshots: '@types/http-errors@2.0.5': {} - '@types/jsonwebtoken@8.5.9': - dependencies: - '@types/node': 22.15.33 - '@types/keygrip@1.0.6': {} - '@types/koa-bodyparser@4.3.12': - dependencies: - '@types/koa': 2.15.0 - '@types/koa-compose@3.2.8': dependencies: '@types/koa': 2.15.0 - '@types/koa-router@7.4.8': - dependencies: - '@types/koa': 2.15.0 - '@types/koa@2.15.0': dependencies: '@types/accepts': 1.3.7 @@ -4859,11 +4710,6 @@ snapshots: '@zip.js/zip.js@2.7.62': {} - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - acorn-jsx@5.3.2(acorn@8.14.1): dependencies: acorn: 8.14.1 @@ -4988,25 +4834,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bytes@3.1.2: {} - cac@6.7.14: {} - cache-content-type@1.0.1: - dependencies: - mime-types: 2.1.35 - ylru: 1.4.0 - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -5060,20 +4889,18 @@ snapshots: cli-spinners@2.9.2: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone@1.0.4: {} + clone@2.1.2: {} + clsx@2.1.1: {} - co-body@6.2.0: - dependencies: - '@hapi/bourne': 3.0.0 - inflation: 2.1.0 - qs: 6.14.0 - raw-body: 2.5.2 - type-is: 1.6.18 - - co@4.6.0: {} - code-block-writer@12.0.0: {} color-convert@1.9.3: @@ -5094,20 +4921,9 @@ snapshots: concat-map@0.0.1: {} - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - convert-source-map@2.0.0: {} - cookies@0.9.1: - dependencies: - depd: 2.0.0 - keygrip: 1.1.0 - - copy-to@2.0.1: {} + cookie@1.0.2: {} cosmiconfig@8.3.6(typescript@5.8.3): dependencies: @@ -5180,8 +4996,6 @@ snapshots: deep-eql@5.0.2: {} - deep-equal@1.0.1: {} - deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -5190,16 +5004,8 @@ snapshots: dependencies: clone: 1.0.4 - delegates@1.0.0: {} - - depd@1.1.2: {} - - depd@2.0.0: {} - dequal@2.0.3: {} - destroy@1.2.0: {} - detect-libc@2.0.3: {} detect-libc@2.0.4: {} @@ -5233,24 +5039,25 @@ snapshots: dotenv@16.5.0: {} - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - eastasianwidth@0.2.0: {} - ee-first@1.1.1: {} - electron-to-chromium@1.5.165: {} + elysia@1.3.20(exact-mirror@0.2.0(@sinclair/typebox@0.34.41))(file-type@21.0.0)(typescript@5.8.3): + dependencies: + cookie: 1.0.2 + exact-mirror: 0.2.0(@sinclair/typebox@0.34.41) + fast-decode-uri-component: 1.0.1 + file-type: 21.0.0 + typescript: 5.8.3 + optionalDependencies: + '@sinclair/typebox': 0.34.41 + openapi-types: 12.1.3 + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - encodeurl@1.0.2: {} - end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -5263,16 +5070,8 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -5329,8 +5128,6 @@ snapshots: escalade@3.2.0: {} - escape-html@1.0.3: {} - escape-string-regexp@1.0.5: {} escape-string-regexp@4.0.0: {} @@ -5419,6 +5216,10 @@ snapshots: eventemitter3@4.0.7: {} + exact-mirror@0.2.0(@sinclair/typebox@0.34.41): + optionalDependencies: + '@sinclair/typebox': 0.34.41 + execa@7.2.0: dependencies: cross-spawn: 7.0.6 @@ -5435,6 +5236,8 @@ snapshots: expect-type@1.2.1: {} + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} fast-equals@5.2.2: {} @@ -5460,10 +5263,21 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + fflate@0.8.2: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 + file-type@21.0.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -5494,8 +5308,6 @@ snapshots: fraction.js@4.3.7: {} - fresh@0.5.2: {} - fs-constants@1.0.0: {} fs-extra@11.3.0: @@ -5513,26 +5325,10 @@ snapshots: gensync@1.0.0-beta.2: {} - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 + get-caller-file@2.0.5: {} get-nonce@1.0.1: {} - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - get-stream@6.0.1: {} get-tsconfig@4.10.1: @@ -5590,8 +5386,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - gopd@1.2.0: {} - graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -5600,37 +5394,10 @@ snapshots: has-flag@4.0.0: {} - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - hasown@2.0.2: dependencies: function-bind: 1.1.2 - http-assert@1.5.0: - dependencies: - deep-equal: 1.0.1 - http-errors: 1.8.1 - - http-errors@1.8.1: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 1.5.0 - toidentifier: 1.0.1 - - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - https-proxy-agent@6.2.1: dependencies: agent-base: 7.1.3 @@ -5640,10 +5407,6 @@ snapshots: human-signals@4.3.1: {} - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - ieee754@1.2.1: {} ignore@5.3.2: {} @@ -5655,8 +5418,6 @@ snapshots: imurmurhash@0.1.4: {} - inflation@2.1.0: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -5684,13 +5445,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.0: - dependencies: - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -5701,13 +5455,6 @@ snapshots: is-path-inside@3.0.3: {} - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - is-stream@3.0.0: {} is-unicode-supported@1.3.0: {} @@ -5753,67 +5500,12 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - keygrip@1.1.0: - dependencies: - tsscmp: 1.0.6 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 kleur@3.0.3: {} - koa-bodyparser@4.4.1: - dependencies: - co-body: 6.2.0 - copy-to: 2.0.1 - type-is: 1.6.18 - - koa-compose@4.1.0: {} - - koa-convert@2.0.0: - dependencies: - co: 4.6.0 - koa-compose: 4.1.0 - - koa-router@12.0.1: - dependencies: - debug: 4.4.1 - http-errors: 2.0.0 - koa-compose: 4.1.0 - methods: 1.1.2 - path-to-regexp: 6.3.0 - transitivePeerDependencies: - - supports-color - - koa@2.16.1: - dependencies: - accepts: 1.3.8 - cache-content-type: 1.0.1 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookies: 0.9.1 - debug: 4.4.1 - delegates: 1.0.0 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - fresh: 0.5.2 - http-assert: 1.5.0 - http-errors: 1.8.1 - is-generator-function: 1.1.0 - koa-compose: 4.1.0 - koa-convert: 2.0.0 - on-finished: 2.4.1 - only: 0.0.2 - parseurl: 1.3.3 - statuses: 1.5.0 - type-is: 1.6.18 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - kysely-codegen@0.14.1(better-sqlite3@9.4.3)(kysely@0.27.3): dependencies: chalk: 4.1.2 @@ -5887,16 +5579,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - math-intrinsics@1.1.0: {} - - media-typer@0.3.0: {} - merge-stream@2.0.0: {} merge2@1.4.1: {} - methods@1.1.2: {} - micromatch@4.0.5: dependencies: braces: 3.0.3 @@ -5907,12 +5593,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mimic-fn@2.1.0: {} mimic-fn@4.0.0: {} @@ -5959,8 +5639,6 @@ snapshots: natural-orderby@2.0.3: {} - negotiator@0.6.3: {} - node-abi@3.68.0: dependencies: semver: 7.7.2 @@ -5969,6 +5647,10 @@ snapshots: dependencies: semver: 7.7.2 + node-cache@5.1.2: + dependencies: + clone: 2.1.2 + node-domexception@1.0.0: {} node-fetch@3.3.2: @@ -5991,12 +5673,6 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.13.4: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -6009,7 +5685,8 @@ snapshots: dependencies: mimic-fn: 4.0.0 - only@0.0.2: {} + openapi-types@12.1.3: + optional: true optionator@0.9.4: dependencies: @@ -6053,8 +5730,6 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parseurl@1.3.3: {} - path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -6072,8 +5747,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-to-regexp@6.3.0: {} - path-type@4.0.0: {} pathe@1.1.2: {} @@ -6180,19 +5853,8 @@ snapshots: punycode@2.3.1: {} - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - queue-microtask@1.2.3: {} - raw-body@2.5.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -6308,6 +5970,8 @@ snapshots: regexparam@3.0.0: {} + require-directory@2.1.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -6361,14 +6025,6 @@ snapshots: safe-buffer@5.2.1: {} - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safer-buffer@2.1.2: {} - scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -6379,8 +6035,6 @@ snapshots: semver@7.7.2: {} - setprototypeof@1.2.0: {} - shadcn-ui@0.8.0(typescript@5.8.3): dependencies: '@antfu/ni': 0.21.12 @@ -6421,34 +6075,6 @@ snapshots: interpret: 1.4.0 rechoir: 0.6.2 - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -6473,10 +6099,6 @@ snapshots: stackback@0.0.2: {} - statuses@1.5.0: {} - - statuses@2.0.1: {} - std-env@3.9.0: {} stdin-discarder@0.1.0: @@ -6515,6 +6137,10 @@ snapshots: strip-json-comments@3.1.1: {} + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -6626,7 +6252,11 @@ snapshots: dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 ts-api-utils@1.4.3(typescript@5.8.3): dependencies: @@ -6647,8 +6277,6 @@ snapshots: tslib@2.8.1: {} - tsscmp@1.0.6: {} - tsx@4.20.3: dependencies: esbuild: 0.25.5 @@ -6666,23 +6294,18 @@ snapshots: type-fest@0.20.2: {} - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - typescript@5.4.3: {} typescript@5.8.3: {} + uint8array-extras@1.5.0: {} + undici-types@6.21.0: {} undici-types@7.8.0: {} universalify@2.0.1: {} - unpipe@1.0.0: {} - update-browserslist-db@1.1.3(browserslist@4.25.0): dependencies: browserslist: 4.25.0 @@ -6719,8 +6342,6 @@ snapshots: util-deprecate@1.0.2: {} - vary@1.1.2: {} - victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.1 @@ -6838,11 +6459,23 @@ snapshots: wrappy@1.0.2: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yaml@2.8.0: {} - ylru@1.4.0: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 yocto-queue@0.1.0: {}