init: 初始化项目

This commit is contained in:
lsy committed 2026-02-15 02:12:01 +08:00
commit dd3fe3033b
60 files changed
+4514

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
node_modules/
dist/
.DS_Store
.env
.env.*
apps/admin/dist/
apps/worker/.wrangler/
tests/
helloagents/plan
helloagents/archive
+119
View File
@@ -0,0 +1,119 @@
# new-api-lite
简易版 new-api:Cloudflare Workers + D1 后端,Vite 静态管理台。
## 目录结构
- `apps/worker` Cloudflare Worker (Hono)
- `apps/admin` 管理台 (Vite)
- `tests` 基础单元测试
## 本地开发
### 1) 安装依赖
```bash
bun install
```
### 2) Worker
```bash
bun run dev:worker
```
环境变量(`apps/worker/wrangler.toml` 或 `wrangler secret put`):
- `ADMIN_PASSWORD` 管理员密码
- `SESSION_TTL_HOURS` 管理会话时长(小时)
- `LOG_RETENTION_DAYS` 日志保留天数
- `CORS_ORIGIN` 允许的管理台来源
- `PROXY_RETRY_ROUNDS` 代理失败轮询次数(默认 2)
- `PROXY_RETRY_DELAY_MS` 轮询间隔(毫秒,默认 200)
- `NEW_API_ADMIN_TOKEN` New API 兼容接口的管理员令牌
### 3) Admin UI
```bash
bun run dev:admin
```
前端配置(`apps/admin/.env` 可选):
- `VITE_API_BASE` 管理 API 基址(默认同域)
- `VITE_API_TARGET` 本地开发代理目标(默认 http://localhost:8787)
### 4) 常用脚本
```bash
bun run fix
```
用于自动修正 package.json 常见问题(等价于 `bun pm pkg fix`)。
## New API 兼容接口
为了支持 all-api-hub 等插件同步渠道,Worker 提供 New API 风格的渠道管理接口:
- 路径前缀:`/api/channel`
- 认证方式:`Authorization: Bearer {NEW_API_ADMIN_TOKEN}`(或管理台登录 token)
- 可选请求头:`New-Api-User: 1`
支持的常用接口:
- `GET /api/channel` 渠道列表
- `GET /api/channel/` 渠道列表(兼容尾斜杠)
- `GET /api/group` 渠道分组列表
- `GET /api/channel/search` 渠道搜索
- `POST /api/channel` 新增渠道(单条)
- `PUT /api/channel` 更新渠道
- `DELETE /api/channel/:id` 删除渠道
- `GET /api/channel/test/:id` 渠道连通性测试
- `GET /api/channel/fetch_models/:id` 拉取渠道模型
- `GET /api/user/models` 用户可用模型列表
## D1 数据库
在 `apps/worker/migrations/0001_init.sql` 定义表结构,使用 Wrangler 迁移:
```bash
bun run --filter new-api-lite-worker db:migrate
```
## 生产部署
### 1) 准备 Cloudflare 资源
1. 创建 D1 数据库并回填 `apps/worker/wrangler.toml` 的 `database_id`:
```bash
bunx wrangler d1 create new_api_lite
```
2. 设置 Worker 变量与密钥(推荐用 secret):
```bash
bunx wrangler secret put ADMIN_PASSWORD
bunx wrangler secret put NEW_API_ADMIN_TOKEN
```
其他变量可继续放在 `apps/worker/wrangler.toml` 的 `[vars]` 中,`CORS_ORIGIN` 需指向管理台域名。
### 2) 执行生产迁移
```bash
bunx wrangler d1 migrations apply DB
```
### 3) 部署 Worker
```bash
bun run --filter new-api-lite-worker deploy
```
### 4) 构建并部署 Admin
```bash
bun run --filter new-api-lite-admin build
```
将 `apps/admin/dist` 发布到 Cloudflare Pages 或任意静态托管;如需指定 API 基址,请在构建前设置 `VITE_API_BASE`(例如 `apps/admin/.env.production`)。
+18
View File
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>api-workers</title>
</head>
<body>
<div id="app"></div>
<script
type="module"
src="/src/main.ts"
></script>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
{
"name": "new-api-lite-admin",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "^5.4.5",
"vite": "^5.2.0"
}
}
+718
View File
@@ -0,0 +1,718 @@
import './styles.css';
type Channel = {
id: string;
name: string;
base_url: string;
api_key: string;
weight: number;
status: string;
rate_limit: number;
models_json?: string;
};
type Token = {
id: string;
name: string;
key_prefix: string;
quota_total: number | null;
quota_used: number;
status: string;
};
type UsageLog = {
id: string;
model: string;
channel_id: string;
channel_name?: string | null;
token_id: string;
token_name?: string | null;
total_tokens: number;
latency_ms: number;
status: string;
created_at: string;
};
type DashboardData = {
summary: {
total_requests: number;
total_tokens: number;
avg_latency: number;
total_errors: number;
};
byDay: Array<{ day: string; requests: number; tokens: number }>;
byModel: Array<{ model: string; requests: number; tokens: number }>;
byChannel: Array<{ channel_name: string; requests: number; tokens: number }>;
byToken: Array<{ token_name: string; requests: number; tokens: number }>;
};
type Settings = {
log_retention_days: number;
};
const root = document.querySelector<HTMLDivElement>('#app');
const apiBase = import.meta.env.VITE_API_BASE ?? '';
const tabs = [
{ id: 'dashboard', label: '数据面板' },
{ id: 'channels', label: '渠道管理' },
{ id: 'models', label: '模型广场' },
{ id: 'tokens', label: '令牌管理' },
{ id: 'usage', label: '使用日志' },
{ id: 'settings', label: '系统设置' }
];
const state = {
token: localStorage.getItem('admin_token'),
activeTab: 'dashboard',
loading: false,
notice: '',
data: {
channels: [] as Channel[],
tokens: [] as Token[],
models: [] as Array<{ id: string; channels: Array<{ id: string; name: string }> }>,
usage: [] as UsageLog[],
dashboard: null as DashboardData | null,
settings: null as Settings | null
}
};
if (!root) {
throw new Error('Missing #app root');
}
function setNotice(message: string) {
state.notice = message;
render();
}
function setToken(token: string | null) {
state.token = token;
if (token) {
localStorage.setItem('admin_token', token);
} else {
localStorage.removeItem('admin_token');
}
render();
}
async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
const headers = new Headers(options.headers ?? {});
headers.set('Content-Type', 'application/json');
if (state.token) {
headers.set('Authorization', `Bearer ${state.token}`);
}
const response = await fetch(`${apiBase}${path}`, { ...options, headers });
if (!response.ok) {
if (response.status === 401) {
setToken(null);
}
const payload = await response.json().catch(() => null);
throw new Error(payload?.error ?? `HTTP ${response.status}`);
}
return response.json() as Promise<T>;
}
async function loadDashboard() {
state.data.dashboard = await apiFetch<DashboardData>('/api/dashboard');
}
async function loadChannels() {
const result = await apiFetch<{ channels: Channel[] }>('/api/channels');
state.data.channels = result.channels;
}
async function loadModels() {
const result = await apiFetch<{ models: Array<{ id: string; channels: Array<{ id: string; name: string }> }> }>('/api/models');
state.data.models = result.models;
}
async function loadTokens() {
const result = await apiFetch<{ tokens: Token[] }>('/api/tokens');
state.data.tokens = result.tokens;
}
async function loadUsage() {
const result = await apiFetch<{ logs: UsageLog[] }>('/api/usage?limit=50');
state.data.usage = result.logs;
}
async function loadSettings() {
state.data.settings = await apiFetch<Settings>('/api/settings');
}
async function loadTab(tabId: string) {
state.loading = true;
state.notice = '';
render();
try {
if (tabId === 'dashboard') {
await loadDashboard();
}
if (tabId === 'channels') {
await loadChannels();
}
if (tabId === 'models') {
await loadModels();
}
if (tabId === 'tokens') {
await loadTokens();
}
if (tabId === 'usage') {
await loadUsage();
}
if (tabId === 'settings') {
await loadSettings();
}
} catch (error) {
setNotice((error as Error).message);
} finally {
state.loading = false;
render();
}
}
function renderLogin() {
return `
<div class="login-card">
<h1>api-workers</h1>
<p>请输入管理员密码登录管理台。</p>
<form id="login-form" class="form-grid">
<div>
<label for="password">管理员密码</label>
<input id="password" name="password" type="password" required />
</div>
<button class="button" type="submit">登录</button>
</form>
${state.notice ? `<div class="notice">${state.notice}</div>` : ''}
</div>
`;
}
function renderSidebar() {
return `
<aside class="sidebar">
<div class="brand">
<h2>api-workers</h2>
<span>console</span>
</div>
<nav class="nav-list">
${tabs
.map(
(tab) => `
<button class="nav-item ${state.activeTab === tab.id ? 'active' : ''}" data-tab="${tab.id}" type="button">
${tab.label}
</button>
`
)
.join('')}
</nav>
</aside>
`;
}
function renderDashboard() {
const data = state.data.dashboard;
if (!data) {
return '<div class="card">暂无数据</div>';
}
const errorRate = data.summary.total_requests
? Math.round((data.summary.total_errors / data.summary.total_requests) * 100)
: 0;
return `
<div class="grid three">
<div class="card kpi">
<span class="badge">总请求</span>
<div class="value">${data.summary.total_requests}</div>
<span class="mono">最近窗口</span>
</div>
<div class="card kpi">
<span class="badge">总 Tokens</span>
<div class="value">${data.summary.total_tokens}</div>
<span class="mono">累计消耗</span>
</div>
<div class="card kpi">
<span class="badge">错误率</span>
<div class="value">${errorRate}%</div>
<span class="mono">平均延迟 ${Math.round(data.summary.avg_latency)}ms</span>
</div>
</div>
<div class="grid two" style="margin-top: 20px;">
<div class="card">
<div class="section-header">
<h3>按日趋势</h3>
</div>
<table class="table">
<thead><tr><th>日期</th><th>请求</th><th>Tokens</th></tr></thead>
<tbody>
${data.byDay
.map((row) => `<tr><td>${row.day}</td><td>${row.requests}</td><td>${row.tokens}</td></tr>`)
.join('')}
</tbody>
</table>
</div>
<div class="card">
<div class="section-header">
<h3>模型排行</h3>
</div>
<table class="table">
<thead><tr><th>模型</th><th>请求</th><th>Tokens</th></tr></thead>
<tbody>
${data.byModel
.map((row) => `<tr><td>${row.model ?? '-'}</td><td>${row.requests}</td><td>${row.tokens}</td></tr>`)
.join('')}
</tbody>
</table>
</div>
</div>
<div class="grid two" style="margin-top: 20px;">
<div class="card">
<div class="section-header">
<h3>渠道贡献</h3>
</div>
<table class="table">
<thead><tr><th>渠道</th><th>请求</th><th>Tokens</th></tr></thead>
<tbody>
${data.byChannel
.map((row) => `<tr><td>${row.channel_name ?? '-'}</td><td>${row.requests}</td><td>${row.tokens}</td></tr>`)
.join('')}
</tbody>
</table>
</div>
<div class="card">
<div class="section-header">
<h3>令牌贡献</h3>
</div>
<table class="table">
<thead><tr><th>令牌</th><th>请求</th><th>Tokens</th></tr></thead>
<tbody>
${data.byToken
.map((row) => `<tr><td>${row.token_name ?? '-'}</td><td>${row.requests}</td><td>${row.tokens}</td></tr>`)
.join('')}
</tbody>
</table>
</div>
</div>
`;
}
function renderChannels() {
return `
<div class="grid two">
<div class="card">
<div class="section-header">
<h3>新增渠道</h3>
</div>
<form id="channel-form" class="form-grid">
<div>
<label for="channel-id">渠道 ID(可选)</label>
<input id="channel-id" name="id" placeholder="例如 ch_openai" />
</div>
<div>
<label for="channel-name">名称</label>
<input id="channel-name" name="name" required />
</div>
<div>
<label for="channel-base">Base URL</label>
<input id="channel-base" name="base_url" placeholder="https://api.openai.com" required />
</div>
<div>
<label for="channel-key">API Key</label>
<input id="channel-key" name="api_key" required />
</div>
<div class="form-grid two">
<div>
<label for="channel-weight">权重</label>
<input id="channel-weight" name="weight" type="number" min="1" value="1" />
</div>
<div>
<label for="channel-rate">限流</label>
<input id="channel-rate" name="rate_limit" type="number" min="0" value="0" />
</div>
</div>
<button class="button" type="submit">创建渠道</button>
</form>
</div>
<div class="card">
<div class="section-header">
<h3>渠道列表</h3>
</div>
<table class="table">
<thead>
<tr><th>ID</th><th>名称</th><th>状态</th><th>权重</th><th>操作</th></tr>
</thead>
<tbody>
${state.data.channels
.map(
(channel) => `
<tr>
<td class="mono">${channel.id}</td>
<td>${channel.name}</td>
<td>${channel.status}</td>
<td>${channel.weight}</td>
<td>
<button class="button secondary" type="button" data-action="channel-test" data-id="${channel.id}">连通测试</button>
<button class="button ghost" type="button" data-action="channel-toggle" data-id="${channel.id}" data-status="${channel.status}">切换状态</button>
<button class="button ghost" type="button" data-action="channel-delete" data-id="${channel.id}">删除</button>
</td>
</tr>
`
)
.join('')}
</tbody>
</table>
</div>
</div>
`;
}
function renderModels() {
return `
<div class="card">
<div class="section-header">
<h3>模型广场</h3>
<span class="badge">${state.data.models.length} 个模型</span>
</div>
<table class="table">
<thead><tr><th>模型</th><th>渠道</th></tr></thead>
<tbody>
${state.data.models
.map(
(model) => `
<tr>
<td>${model.id}</td>
<td>${model.channels.map((channel) => channel.name).join(' / ')}</td>
</tr>
`
)
.join('')}
</tbody>
</table>
</div>
`;
}
function renderTokens() {
return `
<div class="grid two">
<div class="card">
<div class="section-header">
<h3>生成令牌</h3>
</div>
<form id="token-form" class="form-grid">
<div>
<label for="token-name">名称</label>
<input id="token-name" name="name" required />
</div>
<div>
<label for="token-quota">额度(可选)</label>
<input id="token-quota" name="quota_total" type="number" min="0" placeholder="留空表示无限" />
</div>
<button class="button" type="submit">生成令牌</button>
</form>
</div>
<div class="card">
<div class="section-header">
<h3>令牌列表</h3>
</div>
<table class="table">
<thead>
<tr><th>名称</th><th>状态</th><th>已用/额度</th><th>操作</th></tr>
</thead>
<tbody>
${state.data.tokens
.map(
(token) => `
<tr>
<td>${token.name}</td>
<td>${token.status}</td>
<td>${token.quota_used} / ${token.quota_total ?? '∞'}</td>
<td>
<button class="button secondary" type="button" data-action="token-reveal" data-id="${token.id}">查看</button>
<button class="button secondary" type="button" data-action="token-toggle" data-id="${token.id}" data-status="${token.status}">切换</button>
<button class="button ghost" type="button" data-action="token-delete" data-id="${token.id}">删除</button>
</td>
</tr>
`
)
.join('')}
</tbody>
</table>
</div>
</div>
`;
}
function renderUsage() {
return `
<div class="card">
<div class="section-header">
<h3>使用日志</h3>
<button class="button secondary" type="button" data-action="usage-refresh">刷新</button>
</div>
<table class="table">
<thead><tr><th>时间</th><th>模型</th><th>渠道</th><th>Tokens</th><th>延迟</th><th>状态</th></tr></thead>
<tbody>
${state.data.usage
.map(
(log) => `
<tr>
<td>${log.created_at?.slice(0, 19).replace('T', ' ')}</td>
<td>${log.model ?? '-'}</td>
<td>${log.channel_name ?? log.channel_id ?? '-'}</td>
<td>${log.total_tokens ?? 0}</td>
<td>${log.latency_ms ?? 0} ms</td>
<td>${log.status}</td>
</tr>
`
)
.join('')}
</tbody>
</table>
</div>
`;
}
function renderSettings() {
const retention = state.data.settings?.log_retention_days ?? 30;
return `
<div class="card">
<div class="section-header">
<h3>系统设置</h3>
</div>
<form id="settings-form" class="form-grid two">
<div>
<label for="retention">日志保留天数</label>
<input id="retention" name="log_retention_days" type="number" min="1" value="${retention}" />
</div>
<div style="display: flex; align-items: flex-end;">
<button class="button" type="submit">保存设置</button>
</div>
</form>
</div>
`;
}
function renderContent() {
if (state.loading) {
return '<div class="card">加载中...</div>';
}
if (state.activeTab === 'dashboard') {
return renderDashboard();
}
if (state.activeTab === 'channels') {
return renderChannels();
}
if (state.activeTab === 'models') {
return renderModels();
}
if (state.activeTab === 'tokens') {
return renderTokens();
}
if (state.activeTab === 'usage') {
return renderUsage();
}
if (state.activeTab === 'settings') {
return renderSettings();
}
return '<div class="card">未知模块</div>';
}
function renderApp() {
return `
<div class="app-shell">
${renderSidebar()}
<main class="main">
<div class="top-bar">
<div>
<h1>${tabs.find((tab) => tab.id === state.activeTab)?.label ?? '管理台'}</h1>
<p>集中管理渠道、模型、令牌与使用情况。</p>
</div>
<div class="top-bar-actions">
<span class="badge">${state.token ? '已登录' : '未登录'}</span>
<button class="button ghost" type="button" data-action="logout">退出</button>
</div>
</div>
${state.notice ? `<div class="notice">${state.notice}</div>` : ''}
${renderContent()}
</main>
</div>
`;
}
function render() {
root!.innerHTML = state.token ? renderApp() : renderLogin();
bindEvents();
}
function bindEvents() {
const loginForm = document.querySelector<HTMLFormElement>('#login-form');
if (loginForm) {
loginForm.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(loginForm);
const password = String(formData.get('password') ?? '');
try {
const result = await apiFetch<{ token: string }>('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ password })
});
setToken(result.token);
state.notice = '';
await loadTab(state.activeTab);
} catch (error) {
setNotice((error as Error).message);
}
});
}
const channelForm = document.querySelector<HTMLFormElement>('#channel-form');
if (channelForm) {
channelForm.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(channelForm);
const payload = Object.fromEntries(formData.entries());
const channelId = String(payload.id ?? '').trim();
try {
const body = {
...payload,
weight: Number(payload.weight ?? 1),
rate_limit: Number(payload.rate_limit ?? 0)
} as Record<string, unknown>;
if (channelId) {
body.id = channelId;
} else {
delete body.id;
}
await apiFetch('/api/channels', {
method: 'POST',
body: JSON.stringify(body)
});
channelForm.reset();
await loadChannels();
render();
} catch (error) {
setNotice((error as Error).message);
}
});
}
const tokenForm = document.querySelector<HTMLFormElement>('#token-form');
if (tokenForm) {
tokenForm.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(tokenForm);
const payload = Object.fromEntries(formData.entries());
try {
const result = await apiFetch<{ token: string }>('/api/tokens', {
method: 'POST',
body: JSON.stringify({
name: payload.name,
quota_total: payload.quota_total ? Number(payload.quota_total) : null
})
});
setNotice(`新令牌: ${result.token}`);
tokenForm.reset();
await loadTokens();
render();
} catch (error) {
setNotice((error as Error).message);
}
});
}
const settingsForm = document.querySelector<HTMLFormElement>('#settings-form');
if (settingsForm) {
settingsForm.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(settingsForm);
const value = Number(formData.get('log_retention_days'));
try {
await apiFetch('/api/settings', {
method: 'PUT',
body: JSON.stringify({ log_retention_days: value })
});
await loadSettings();
setNotice('设置已更新');
} catch (error) {
setNotice((error as Error).message);
}
});
}
root!.querySelectorAll('[data-tab]').forEach((item) => {
item.addEventListener('click', () => {
const tab = (item as HTMLElement).dataset.tab ?? 'dashboard';
state.activeTab = tab;
loadTab(tab);
});
});
root!.querySelectorAll('[data-action]').forEach((item) => {
item.addEventListener('click', async () => {
const action = (item as HTMLElement).dataset.action ?? '';
const id = (item as HTMLElement).dataset.id ?? '';
try {
if (action === 'logout') {
await apiFetch('/api/auth/logout', { method: 'POST' }).catch(() => null);
setToken(null);
return;
}
if (action === 'channel-test') {
const result = await apiFetch<{ models: Array<{ id: string }> }>(`/api/channels/${id}/test`, { method: 'POST' });
await loadChannels();
setNotice(`连通测试完成,模型数 ${result.models?.length ?? 0}`);
render();
}
if (action === 'channel-delete') {
await apiFetch(`/api/channels/${id}`, { method: 'DELETE' });
await loadChannels();
setNotice('渠道已删除');
render();
}
if (action === 'channel-toggle') {
const status = (item as HTMLElement).dataset.status === 'active' ? 'disabled' : 'active';
await apiFetch(`/api/channels/${id}`, {
method: 'PATCH',
body: JSON.stringify({ status })
});
await loadChannels();
setNotice(`渠道已${status === 'active' ? '启用' : '停用'}`);
render();
}
if (action === 'token-delete') {
await apiFetch(`/api/tokens/${id}`, { method: 'DELETE' });
await loadTokens();
setNotice('令牌已删除');
render();
}
if (action === 'token-reveal') {
const result = await apiFetch<{ token: string | null }>(`/api/tokens/${id}/reveal`);
setNotice(result.token ? `令牌: ${result.token}` : '未找到令牌');
}
if (action === 'token-toggle') {
const status = (item as HTMLElement).dataset.status === 'active' ? 'disabled' : 'active';
await apiFetch(`/api/tokens/${id}`, {
method: 'PATCH',
body: JSON.stringify({ status })
});
await loadTokens();
setNotice(`令牌已${status === 'active' ? '启用' : '停用'}`);
render();
}
if (action === 'usage-refresh') {
await loadUsage();
setNotice('日志已刷新');
render();
}
} catch (error) {
setNotice((error as Error).message);
}
});
});
}
render();
if (state.token) {
loadTab(state.activeTab);
}
+353
View File
@@ -0,0 +1,353 @@
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap');
:root {
--bg: #f6f4ef;
--surface: #ffffff;
--surface-alt: #f0ede7;
--text: #1b1b19;
--muted: #6b6760;
--border: #e2ddd3;
--accent: #d4af37;
--accent-dark: #b9972a;
--shadow: 0 12px 30px rgba(27, 27, 25, 0.08);
--radius: 16px;
--radius-sm: 10px;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'IBM Plex Sans', sans-serif;
background: radial-gradient(circle at top, #ffffff 0%, #f3efe8 40%, #efeae2 100%);
color: var(--text);
min-height: 100vh;
}
h1,
h2,
h3,
h4 {
font-family: 'Space Grotesk', sans-serif;
margin: 0 0 0.75rem 0;
letter-spacing: -0.02em;
}
p {
margin: 0 0 1rem 0;
color: var(--muted);
line-height: 1.6;
}
a {
color: inherit;
text-decoration: none;
}
button,
input,
select,
textarea {
font-family: inherit;
}
#app {
min-height: 100vh;
}
.app-shell {
display: grid;
grid-template-columns: 260px 1fr;
min-height: 100vh;
}
.sidebar {
padding: 32px 20px;
background: var(--surface);
border-right: 1px solid var(--border);
position: sticky;
top: 0;
height: 100vh;
}
.brand {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 32px;
}
.brand span {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.24em;
color: var(--muted);
}
.nav-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.nav-item {
display: flex;
align-items: center;
padding: 10px 14px;
border-radius: 12px;
border: none;
background: transparent;
text-align: left;
width: 100%;
min-height: 44px;
color: var(--muted);
font-weight: 500;
transition: all 0.2s ease;
cursor: pointer;
}
.nav-item.active {
background: var(--surface-alt);
color: var(--text);
}
.nav-item:hover {
background: var(--surface-alt);
color: var(--text);
}
.main {
padding: 32px 40px 60px;
}
.top-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.top-bar-actions {
display: flex;
gap: 12px;
align-items: center;
}
.badge {
padding: 4px 10px;
border-radius: 999px;
background: var(--surface-alt);
font-size: 12px;
color: var(--muted);
}
.grid {
display: grid;
gap: 20px;
}
.grid.two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid.three {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.card {
background: var(--surface);
border-radius: var(--radius);
padding: 20px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.card h3 {
margin-bottom: 12px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.button {
background: var(--text);
color: #fff;
border: none;
padding: 10px 16px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
transition: all 0.2s ease;
min-height: 44px;
}
.button.secondary {
background: var(--surface-alt);
color: var(--text);
border: 1px solid var(--border);
}
.button.ghost {
background: transparent;
color: var(--muted);
border: 1px solid var(--border);
}
.button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 8px 18px rgba(27, 27, 25, 0.12);
}
.button:focus-visible,
.nav-item:focus-visible {
outline: 2px solid rgba(212, 175, 55, 0.6);
outline-offset: 2px;
}
.table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.table th,
.table td {
text-align: left;
padding: 10px 12px;
border-bottom: 1px solid var(--border);
}
.table th {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted);
}
.table tr:hover {
background: #faf8f3;
}
.form-grid {
display: grid;
gap: 14px;
}
.form-grid.two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
label {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted);
display: block;
margin-bottom: 6px;
}
input,
select,
textarea {
width: 100%;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid var(--border);
background: #fff;
color: var(--text);
}
input:focus,
select:focus,
textarea:focus {
outline: 2px solid rgba(212, 175, 55, 0.4);
border-color: var(--accent);
}
.notice {
padding: 12px 16px;
background: #fff8e1;
border: 1px solid #f1e0b3;
border-radius: 12px;
color: #7a5d00;
margin-top: 12px;
font-size: 14px;
}
.login-card {
max-width: 420px;
margin: 12vh auto 0;
background: var(--surface);
padding: 32px;
border-radius: 20px;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.login-card h1 {
margin-bottom: 8px;
}
.kpi {
display: flex;
flex-direction: column;
gap: 6px;
}
.kpi .value {
font-size: 24px;
font-weight: 600;
}
.mono {
font-family: 'Space Grotesk', sans-serif;
font-size: 13px;
}
@media (max-width: 1024px) {
.app-shell {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
height: auto;
border-right: none;
border-bottom: 1px solid var(--border);
}
.grid.two,
.grid.three,
.form-grid.two {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.main {
padding: 24px;
}
.top-bar {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
}
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"lib": ["ES2022", "DOM"],
"types": ["vite/client"],
"skipLibCheck": true
},
"include": ["src"]
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'vite';
const apiTarget = process.env.VITE_API_TARGET ?? 'http://localhost:8787';
export default defineConfig({
build: {
outDir: 'dist'
},
server: {
proxy: {
'/api': apiTarget,
'/v1': apiTarget
}
}
});
+55
View File
@@ -0,0 +1,55 @@
CREATE TABLE IF NOT EXISTS channels (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
base_url TEXT NOT NULL,
api_key TEXT NOT NULL,
weight INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'active',
rate_limit INTEGER DEFAULT 0,
models_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tokens (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
key_hash TEXT NOT NULL,
key_prefix TEXT NOT NULL,
quota_total INTEGER,
quota_used INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'active',
allowed_channels TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS usage_logs (
id TEXT PRIMARY KEY,
token_id TEXT,
channel_id TEXT,
model TEXT,
request_path TEXT,
total_tokens INTEGER,
prompt_tokens INTEGER,
completion_tokens INTEGER,
cost REAL,
latency_ms INTEGER,
status TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS usage_logs_created_at ON usage_logs(created_at);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS admin_sessions (
id TEXT PRIMARY KEY,
token_hash TEXT NOT NULL,
expires_at TEXT NOT NULL,
created_at TEXT NOT NULL
);
@@ -0,0 +1 @@
ALTER TABLE tokens ADD COLUMN token_plain TEXT;
@@ -0,0 +1,6 @@
ALTER TABLE channels ADD COLUMN type INTEGER NOT NULL DEFAULT 1;
ALTER TABLE channels ADD COLUMN group_name TEXT;
ALTER TABLE channels ADD COLUMN priority INTEGER NOT NULL DEFAULT 0;
ALTER TABLE channels ADD COLUMN metadata_json TEXT;
ALTER TABLE channels ADD COLUMN test_time INTEGER;
ALTER TABLE channels ADD COLUMN response_time_ms INTEGER;
+18
View File
@@ -0,0 +1,18 @@
{
"name": "new-api-lite-worker",
"private": true,
"type": "module",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"db:migrate": "wrangler d1 migrations apply DB --local"
},
"dependencies": {
"hono": "^4.2.7"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240222.0",
"typescript": "^5.4.5",
"wrangler": "^3.48.0"
}
}
+62
View File
@@ -0,0 +1,62 @@
CREATE TABLE IF NOT EXISTS channels (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
base_url TEXT NOT NULL,
api_key TEXT NOT NULL,
type INTEGER NOT NULL DEFAULT 1,
group_name TEXT,
priority INTEGER NOT NULL DEFAULT 0,
weight INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'active',
rate_limit INTEGER DEFAULT 0,
models_json TEXT,
metadata_json TEXT,
test_time INTEGER,
response_time_ms INTEGER,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tokens (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
key_hash TEXT NOT NULL,
key_prefix TEXT NOT NULL,
token_plain TEXT,
quota_total INTEGER,
quota_used INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'active',
allowed_channels TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS usage_logs (
id TEXT PRIMARY KEY,
token_id TEXT,
channel_id TEXT,
model TEXT,
request_path TEXT,
total_tokens INTEGER,
prompt_tokens INTEGER,
completion_tokens INTEGER,
cost REAL,
latency_ms INTEGER,
status TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS usage_logs_created_at ON usage_logs(created_at);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS admin_sessions (
id TEXT PRIMARY KEY,
token_hash TEXT NOT NULL,
expires_at TEXT NOT NULL,
created_at TEXT NOT NULL
);
+15
View File
@@ -0,0 +1,15 @@
export type Bindings = {
DB: D1Database;
ADMIN_PASSWORD: string;
SESSION_TTL_HOURS?: string;
LOG_RETENTION_DAYS?: string;
CORS_ORIGIN?: string;
PROXY_RETRY_ROUNDS?: string;
PROXY_RETRY_DELAY_MS?: string;
NEW_API_ADMIN_TOKEN?: string;
NEWAPI_ADMIN_TOKEN?: string;
};
export type AppEnv = {
Bindings: Bindings;
};
+68
View File
@@ -0,0 +1,68 @@
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import type { AppEnv } from './env';
import authRoutes from './routes/auth';
import channelRoutes from './routes/channels';
import modelRoutes from './routes/models';
import tokenRoutes from './routes/tokens';
import usageRoutes from './routes/usage';
import dashboardRoutes from './routes/dashboard';
import settingsRoutes from './routes/settings';
import proxyRoutes from './routes/proxy';
import newapiChannelRoutes from './routes/newapiChannels';
import newapiUserRoutes from './routes/newapiUsers';
import newapiGroupRoutes from './routes/newapiGroups';
import { adminAuth } from './middleware/adminAuth';
const app = new Hono<AppEnv>({ strict: false });
app.use('*', logger());
app.use(
'/api/*',
cors({
origin: (origin, c) => {
const allowed = c.env.CORS_ORIGIN ?? '*';
return allowed === '*' ? '*' : allowed.split(',').map((item) => item.trim());
},
allowHeaders: ['Content-Type', 'Authorization', 'x-api-key', 'x-admin-token', 'New-Api-User'],
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
})
);
app.use(
'/v1/*',
cors({
origin: '*',
allowHeaders: ['Content-Type', 'Authorization', 'x-api-key'],
allowMethods: ['GET', 'POST', 'OPTIONS']
})
);
app.use('/api/*', async (c, next) => {
if (
c.req.path === '/api/auth/login' ||
c.req.path.startsWith('/api/channel') ||
c.req.path.startsWith('/api/user') ||
c.req.path.startsWith('/api/group')
) {
return next();
}
return adminAuth(c, next);
});
app.get('/health', (c) => c.json({ ok: true }));
app.route('/api/auth', authRoutes);
app.route('/api/channels', channelRoutes);
app.route('/api/models', modelRoutes);
app.route('/api/tokens', tokenRoutes);
app.route('/api/usage', usageRoutes);
app.route('/api/dashboard', dashboardRoutes);
app.route('/api/settings', settingsRoutes);
app.route('/api/channel', newapiChannelRoutes);
app.route('/api/user', newapiUserRoutes);
app.route('/api/group', newapiGroupRoutes);
app.route('/v1', proxyRoutes);
export default app;
+36
View File
@@ -0,0 +1,36 @@
import { createMiddleware } from 'hono/factory';
import { sha256Hex } from '../utils/crypto';
import { getBearerToken } from '../utils/request';
import { jsonError } from '../utils/http';
import type { AppEnv } from '../env';
/**
* Validates admin session tokens for management APIs.
*/
export const adminAuth = createMiddleware<AppEnv>(async (c, next) => {
const token = getBearerToken(c);
if (!token) {
return jsonError(c, 401, 'admin_token_required', 'admin_token_required');
}
const tokenHash = await sha256Hex(token);
const session = await c.env.DB.prepare(
'SELECT id, expires_at FROM admin_sessions WHERE token_hash = ?'
)
.bind(tokenHash)
.first();
if (!session) {
return jsonError(c, 401, 'invalid_admin_token', 'invalid_admin_token');
}
if (new Date(String(session.expires_at)).getTime() <= Date.now()) {
await c.env.DB.prepare('DELETE FROM admin_sessions WHERE id = ?')
.bind(String(session.id))
.run();
return jsonError(c, 401, 'admin_session_expired', 'admin_session_expired');
}
c.set('adminSessionId', String(session.id));
await next();
});
+56
View File
@@ -0,0 +1,56 @@
import { createMiddleware } from 'hono/factory';
import { sha256Hex } from '../utils/crypto';
import { getBearerToken } from '../utils/request';
import type { AppEnv } from '../env';
function newApiError(c: any, status: number, message: string) {
return c.json(
{
success: false,
message
},
status
);
}
function readNewApiUserId(c: any): string | null {
return c.req.header('New-Api-User') ?? c.req.header('new-api-user') ?? null;
}
/**
* Validates New API admin tokens or admin sessions.
*/
export const newApiAuth = createMiddleware<AppEnv>(async (c, next) => {
const token = getBearerToken(c);
if (!token) {
return newApiError(c, 401, 'unauthorized');
}
const envToken = c.env.NEW_API_ADMIN_TOKEN ?? c.env.NEWAPI_ADMIN_TOKEN;
if (envToken && token === envToken) {
c.set('newApiUserId', readNewApiUserId(c));
await next();
return;
}
const tokenHash = await sha256Hex(token);
const session = await c.env.DB.prepare(
'SELECT id, expires_at FROM admin_sessions WHERE token_hash = ?'
)
.bind(tokenHash)
.first<{ id: string; expires_at: string }>();
if (!session) {
return newApiError(c, 401, 'unauthorized');
}
if (new Date(String(session.expires_at)).getTime() <= Date.now()) {
await c.env.DB.prepare('DELETE FROM admin_sessions WHERE id = ?')
.bind(String(session.id))
.run();
return newApiError(c, 401, 'session_expired');
}
c.set('newApiUserId', readNewApiUserId(c));
await next();
});
+56
View File
@@ -0,0 +1,56 @@
import { createMiddleware } from 'hono/factory';
import { sha256Hex } from '../utils/crypto';
import { getBearerToken } from '../utils/request';
import { jsonError } from '../utils/http';
import { canConsumeQuota, normalizeQuota } from '../services/quota';
import type { AppEnv } from '../env';
export type TokenRecord = {
id: string;
name: string;
quota_total: number | null;
quota_used: number;
status: string;
allowed_channels: string | null;
};
/**
* Validates API tokens for the OpenAI-compatible proxy.
*/
export const tokenAuth = createMiddleware<AppEnv>(async (c, next) => {
const token = getBearerToken(c);
if (!token) {
return jsonError(c, 401, 'token_required', 'token_required');
}
const tokenHash = await sha256Hex(token);
const record = await c.env.DB.prepare(
'SELECT id, name, quota_total, quota_used, status, allowed_channels FROM tokens WHERE key_hash = ?'
)
.bind(tokenHash)
.first<TokenRecord>();
if (!record) {
return jsonError(c, 401, 'invalid_token', 'invalid_token');
}
if (record.status !== 'active') {
return jsonError(c, 403, 'token_disabled', 'token_disabled');
}
const quotaTotal = record.quota_total === null || record.quota_total === undefined
? null
: Number(record.quota_total);
const quotaUsed = Number(record.quota_used ?? 0);
const normalized = normalizeQuota(Number.isNaN(quotaTotal) ? null : quotaTotal, Number.isNaN(quotaUsed) ? 0 : quotaUsed);
if (!canConsumeQuota(normalized.quotaTotal, normalized.quotaUsed, 1)) {
return jsonError(c, 402, 'quota_exceeded', 'quota_exceeded');
}
c.set('tokenRecord', {
...record,
quota_total: normalized.quotaTotal,
quota_used: normalized.quotaUsed
});
await next();
});
+53
View File
@@ -0,0 +1,53 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { addHours, nowIso } from '../utils/time';
import { generateToken, sha256Hex } from '../utils/crypto';
import { jsonError } from '../utils/http';
import { adminAuth } from '../middleware/adminAuth';
const auth = new Hono<AppEnv>();
/**
* Logs in the admin user with a password.
*/
auth.post('/login', async (c) => {
const body = await c.req.json().catch(() => null);
if (!body?.password) {
return jsonError(c, 400, 'password_required', 'password_required');
}
if (body.password !== c.env.ADMIN_PASSWORD) {
return jsonError(c, 401, 'invalid_password', 'invalid_password');
}
const rawToken = generateToken('admin_');
const tokenHash = await sha256Hex(rawToken);
const ttlHours = Number(c.env.SESSION_TTL_HOURS ?? '12');
const expiresAt = addHours(new Date(), Number.isNaN(ttlHours) ? 12 : ttlHours).toISOString();
await c.env.DB.prepare(
'INSERT INTO admin_sessions (id, token_hash, expires_at, created_at) VALUES (?, ?, ?, ?)'
)
.bind(crypto.randomUUID(), tokenHash, expiresAt, nowIso())
.run();
return c.json({
token: rawToken,
expires_at: expiresAt
});
});
/**
* Invalidates the current admin session token.
*/
auth.post('/logout', adminAuth, async (c) => {
const sessionId = c.get('adminSessionId');
if (sessionId) {
await c.env.DB.prepare('DELETE FROM admin_sessions WHERE id = ?')
.bind(String(sessionId))
.run();
}
return c.json({ ok: true });
});
export default auth;
+157
View File
@@ -0,0 +1,157 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { nowIso } from '../utils/time';
import { jsonError } from '../utils/http';
import { safeJsonParse } from '../utils/json';
import { generateToken } from '../utils/crypto';
import { normalizeBaseUrl } from '../utils/url';
const channels = new Hono<AppEnv>();
/**
* Resolves a channel id from request payload.
*
* Args:
* body: Request payload.
*
* Returns:
* Channel id if provided.
*/
function resolveChannelId(body: any): string | null {
const candidate = body?.id ?? body?.channel_id ?? body?.channelId;
if (!candidate) {
return null;
}
const normalized = String(candidate).trim();
return normalized.length > 0 ? normalized : null;
}
/**
* Lists all channels.
*/
channels.get('/', async (c) => {
const result = await c.env.DB.prepare('SELECT * FROM channels ORDER BY created_at DESC').all();
return c.json({ channels: result.results ?? [] });
});
/**
* Creates a new channel.
*/
channels.post('/', async (c) => {
const body = await c.req.json().catch(() => null);
if (!body?.name || !body?.base_url || !body?.api_key) {
return jsonError(c, 400, 'missing_fields', 'missing_fields');
}
const requestedId = resolveChannelId(body);
if (requestedId) {
const exists = await c.env.DB.prepare('SELECT id FROM channels WHERE id = ?').bind(requestedId).first();
if (exists) {
return jsonError(c, 409, 'channel_id_exists', 'channel_id_exists');
}
}
const id = requestedId ?? generateToken('ch_');
const now = nowIso();
await c.env.DB.prepare(
'INSERT INTO channels (id, name, base_url, api_key, weight, status, rate_limit, models_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
)
.bind(
id,
body.name,
normalizeBaseUrl(body.base_url),
body.api_key,
Number(body.weight ?? 1),
body.status ?? 'active',
body.rate_limit ?? 0,
JSON.stringify(body.models ?? []),
now,
now
)
.run();
return c.json({ id });
});
/**
* Updates a channel.
*/
channels.patch('/:id', async (c) => {
const body = await c.req.json().catch(() => null);
const id = c.req.param('id');
if (!body) {
return jsonError(c, 400, 'missing_body', 'missing_body');
}
const current = await c.env.DB.prepare('SELECT * FROM channels WHERE id = ?').bind(id).first();
if (!current) {
return jsonError(c, 404, 'channel_not_found', 'channel_not_found');
}
const models = body.models ?? safeJsonParse(current.models_json, []);
await c.env.DB.prepare(
'UPDATE channels SET name = ?, base_url = ?, api_key = ?, weight = ?, status = ?, rate_limit = ?, models_json = ?, updated_at = ? WHERE id = ?'
)
.bind(
body.name ?? current.name,
normalizeBaseUrl(body.base_url ?? current.base_url),
body.api_key ?? current.api_key,
Number(body.weight ?? current.weight ?? 1),
body.status ?? current.status,
body.rate_limit ?? current.rate_limit ?? 0,
JSON.stringify(models),
nowIso(),
id
)
.run();
return c.json({ ok: true });
});
/**
* Deletes a channel.
*/
channels.delete('/:id', async (c) => {
const id = c.req.param('id');
await c.env.DB.prepare('DELETE FROM channels WHERE id = ?').bind(id).run();
return c.json({ ok: true });
});
/**
* Tests channel connectivity and updates model list.
*/
channels.post('/:id/test', async (c) => {
const id = c.req.param('id');
const channel = await c.env.DB.prepare('SELECT * FROM channels WHERE id = ?').bind(id).first();
if (!channel) {
return jsonError(c, 404, 'channel_not_found', 'channel_not_found');
}
const target = `${normalizeBaseUrl(String(channel.base_url))}/v1/models`;
const response = await fetch(target, {
method: 'GET',
headers: {
Authorization: `Bearer ${channel.api_key}`,
'x-api-key': String(channel.api_key),
'Content-Type': 'application/json'
}
});
if (!response.ok) {
await c.env.DB.prepare('UPDATE channels SET status = ?, updated_at = ? WHERE id = ?')
.bind('error', nowIso(), id)
.run();
return jsonError(c, 502, 'channel_unreachable', 'channel_unreachable');
}
const data = await response.json().catch(() => ({ data: [] }));
await c.env.DB.prepare('UPDATE channels SET status = ?, models_json = ?, updated_at = ? WHERE id = ?')
.bind('active', JSON.stringify(data), nowIso(), id)
.run();
return c.json({ ok: true, models: data?.data ?? [] });
});
export default channels;
+66
View File
@@ -0,0 +1,66 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
const dashboard = new Hono<AppEnv>();
function buildDateFilters(query: Record<string, string>) {
let sql = ' WHERE 1=1';
const params: Array<string> = [];
if (query.from) {
sql += ' AND created_at >= ?';
params.push(query.from);
}
if (query.to) {
sql += ' AND created_at <= ?';
params.push(query.to);
}
return { sql, params };
}
/**
* Returns aggregated usage metrics.
*/
dashboard.get('/', async (c) => {
const query = c.req.query();
const { sql, params } = buildDateFilters(query);
const summary = await c.env.DB.prepare(
`SELECT COUNT(*) as total_requests, COALESCE(SUM(total_tokens), 0) as total_tokens, COALESCE(AVG(latency_ms), 0) as avg_latency, COALESCE(SUM(CASE WHEN status != 'ok' THEN 1 ELSE 0 END), 0) as total_errors FROM usage_logs${sql}`
)
.bind(...params)
.first();
const byDay = await c.env.DB.prepare(
`SELECT substr(created_at, 1, 10) as day, COUNT(*) as requests, COALESCE(SUM(total_tokens), 0) as tokens FROM usage_logs${sql} GROUP BY day ORDER BY day DESC LIMIT 30`
)
.bind(...params)
.all();
const byModel = await c.env.DB.prepare(
`SELECT model, COUNT(*) as requests, COALESCE(SUM(total_tokens), 0) as tokens FROM usage_logs${sql} GROUP BY model ORDER BY requests DESC LIMIT 20`
)
.bind(...params)
.all();
const byChannel = await c.env.DB.prepare(
`SELECT channels.id as channel_id, channels.name as channel_name, COUNT(usage_logs.id) as requests, COALESCE(SUM(usage_logs.total_tokens), 0) as tokens FROM usage_logs LEFT JOIN channels ON channels.id = usage_logs.channel_id${sql} GROUP BY channels.id, channels.name ORDER BY requests DESC LIMIT 20`
)
.bind(...params)
.all();
const byToken = await c.env.DB.prepare(
`SELECT tokens.id as token_id, tokens.name as token_name, COUNT(usage_logs.id) as requests, COALESCE(SUM(usage_logs.total_tokens), 0) as tokens FROM usage_logs LEFT JOIN tokens ON tokens.id = usage_logs.token_id${sql} GROUP BY tokens.id, tokens.name ORDER BY requests DESC LIMIT 20`
)
.bind(...params)
.all();
return c.json({
summary: summary ?? { total_requests: 0, total_tokens: 0, avg_latency: 0, total_errors: 0 },
byDay: byDay.results ?? [],
byModel: byModel.results ?? [],
byChannel: byChannel.results ?? [],
byToken: byToken.results ?? []
});
});
export default dashboard;
+26
View File
@@ -0,0 +1,26 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { extractModels } from '../services/channels';
const models = new Hono<AppEnv>();
/**
* Returns aggregated models from all channels.
*/
models.get('/', async (c) => {
const result = await c.env.DB.prepare('SELECT * FROM channels WHERE status = ?').bind('active').all();
const entries = (result.results ?? []).flatMap((channel) => extractModels(channel as any));
const map = new Map<string, { id: string; channels: { id: string; name: string }[] }>();
for (const entry of entries) {
const existing = map.get(entry.id) ?? { id: entry.id, channels: [] };
existing.channels.push({ id: entry.channelId, name: entry.channelName });
map.set(entry.id, existing);
}
return c.json({
models: Array.from(map.values())
});
});
export default models;
+466
View File
@@ -0,0 +1,466 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { newApiAuth } from '../middleware/newApiAuth';
import { nowIso } from '../utils/time';
import { generateToken } from '../utils/crypto';
import { normalizeBaseUrl } from '../utils/url';
import {
extractModelIds,
mergeMetadata,
modelsToJson,
normalizeChannelInput,
normalizeModelsInput,
normalizeBaseUrlInput,
toNewApiChannel
} from '../services/newapi';
import { safeJsonParse } from '../utils/json';
const newapi = new Hono<AppEnv>({ strict: false });
newapi.use('*', newApiAuth);
function success(c: any, data?: any, message = '') {
return c.json(
{
success: true,
message,
...(data !== undefined ? { data } : {})
},
200
);
}
function failure(c: any, status: number, message: string) {
return c.json(
{
success: false,
message
},
status
);
}
function normalizePage(value: string | undefined | null, fallback: number): number {
const parsed = Number(value);
if (Number.isNaN(parsed) || parsed <= 0) {
return fallback;
}
return Math.floor(parsed);
}
function normalizePageSize(value: string | undefined | null, fallback: number): number {
const parsed = Number(value);
if (Number.isNaN(parsed) || parsed <= 0) {
return fallback;
}
return Math.min(200, Math.floor(parsed));
}
function normalizeStatusFilter(value: string | undefined | null): string | null {
if (!value) {
return null;
}
const normalized = value.trim().toLowerCase();
if (normalized === 'all') {
return null;
}
if (['enabled', 'enable', '1', 'active'].includes(normalized)) {
return 'active';
}
if (['disabled', 'disable', '0', '2', 'inactive'].includes(normalized)) {
return 'disabled';
}
return null;
}
function normalizeBoolean(value: string | undefined | null): boolean {
if (!value) {
return false;
}
const normalized = value.trim().toLowerCase();
return normalized === '1' || normalized === 'true' || normalized === 'yes';
}
async function fetchModels(baseUrl: string, apiKey: string) {
const target = `${normalizeBaseUrl(baseUrl)}/v1/models`;
const start = Date.now();
const response = await fetch(target, {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
'x-api-key': apiKey,
'Content-Type': 'application/json'
}
});
const elapsed = Date.now() - start;
if (!response.ok) {
return { ok: false, elapsed, models: [] as string[] };
}
const data = await response.json().catch(() => ({ data: [] }));
const models = normalizeModelsInput(data?.data ?? data);
return { ok: true, elapsed, models };
}
async function updateChannelTestResult(
c: any,
id: string,
ok: boolean,
elapsed: number,
models?: string[]
) {
const now = Math.floor(Date.now() / 1000);
const status = ok ? 'active' : 'error';
const modelsJson = models ? modelsToJson(models) : undefined;
const sql = modelsJson
? 'UPDATE channels SET status = ?, models_json = ?, test_time = ?, response_time_ms = ?, updated_at = ? WHERE id = ?'
: 'UPDATE channels SET status = ?, test_time = ?, response_time_ms = ?, updated_at = ? WHERE id = ?';
const stmt = c.env.DB.prepare(sql);
if (modelsJson) {
await stmt.bind(status, modelsJson, now, elapsed, nowIso(), id).run();
} else {
await stmt.bind(status, now, elapsed, nowIso(), id).run();
}
}
async function loadChannels(c: any, where: string[], bindings: Array<string | number>) {
const whereSql = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
const rows = await c.env.DB.prepare(
`SELECT * FROM channels ${whereSql} ORDER BY priority DESC, created_at DESC`
)
.bind(...bindings)
.all();
return rows.results ?? [];
}
newapi.get('/', async (c) => {
const page = normalizePage(c.req.query('p') ?? c.req.query('page'), 1);
const pageSize = normalizePageSize(c.req.query('page_size') ?? c.req.query('limit'), 20);
const idSort = normalizeBoolean(c.req.query('id_sort'));
const statusFilter = normalizeStatusFilter(c.req.query('status'));
const typeFilter = c.req.query('type');
const where: string[] = [];
const bindings: Array<string | number> = [];
if (statusFilter) {
where.push('status = ?');
bindings.push(statusFilter);
}
if (typeFilter) {
where.push('type = ?');
bindings.push(Number(typeFilter));
}
const whereSql = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
const orderSql = idSort ? 'ORDER BY id ASC' : 'ORDER BY priority DESC, created_at DESC';
const offset = (page - 1) * pageSize;
const totalRow = await c.env.DB.prepare(
`SELECT COUNT(*) as count FROM channels ${whereSql}`
)
.bind(...bindings)
.first<{ count: number }>();
const rows = await c.env.DB.prepare(
`SELECT * FROM channels ${whereSql} ${orderSql} LIMIT ? OFFSET ?`
)
.bind(...bindings, pageSize, offset)
.all();
const counts = await c.env.DB.prepare(
`SELECT type, COUNT(*) as count FROM channels ${whereSql} GROUP BY type`
)
.bind(...bindings)
.all();
const typeCounts: Record<string, number> = { all: Number(totalRow?.count ?? 0) };
for (const entry of counts.results ?? []) {
typeCounts[String(entry.type)] = Number(entry.count ?? 0);
}
const items = (rows.results ?? []).map((row) => {
const channel = toNewApiChannel(row as any);
delete (channel as any).key;
return channel;
});
return success(c, {
items,
total: Number(totalRow?.count ?? 0),
page,
page_size: pageSize,
type_counts: typeCounts
});
});
newapi.get('/search', async (c) => {
const page = normalizePage(c.req.query('p') ?? c.req.query('page'), 1);
const pageSize = normalizePageSize(c.req.query('page_size') ?? c.req.query('limit'), 20);
const statusFilter = normalizeStatusFilter(c.req.query('status'));
const typeFilter = c.req.query('type');
const keyword = c.req.query('keyword') ?? '';
const group = c.req.query('group') ?? '';
const model = c.req.query('model') ?? '';
const where: string[] = [];
const bindings: Array<string | number> = [];
if (statusFilter) {
where.push('status = ?');
bindings.push(statusFilter);
}
if (typeFilter) {
where.push('type = ?');
bindings.push(Number(typeFilter));
}
const rows = await loadChannels(c, where, bindings);
const filtered = rows.filter((row) => {
const channel = row as any;
const models = extractModelIds(channel as any);
if (keyword && !String(channel.name).includes(keyword) && !String(channel.id).includes(keyword)) {
return false;
}
if (group && !String(channel.group_name ?? '').includes(group)) {
return false;
}
if (model && !models.includes(model)) {
return false;
}
return true;
});
const total = filtered.length;
const offset = (page - 1) * pageSize;
const items = filtered.slice(offset, offset + pageSize).map((row) => {
const channel = toNewApiChannel(row as any);
delete (channel as any).key;
return channel;
});
return success(c, {
items,
total,
page,
page_size: pageSize
});
});
newapi.get('/models', async (c) => {
const result = await c.env.DB.prepare('SELECT * FROM channels WHERE status = ?')
.bind('active')
.all();
const models = new Set<string>();
for (const row of result.results ?? []) {
for (const id of extractModelIds(row as any)) {
models.add(id);
}
}
const data = Array.from(models).map((id) => ({ id, name: id }));
return success(c, data);
});
newapi.get('/models_enabled', async (c) => {
const result = await c.env.DB.prepare('SELECT * FROM channels WHERE status = ?')
.bind('active')
.all();
const models = new Set<string>();
for (const row of result.results ?? []) {
for (const id of extractModelIds(row as any)) {
models.add(id);
}
}
const data = Array.from(models).map((id) => ({ id, name: id }));
return success(c, data);
});
newapi.post('/', async (c) => {
const body = await c.req.json().catch(() => null);
if (!body) {
return failure(c, 400, '请求体为空');
}
const mode = body.mode ?? 'single';
if (mode !== 'single') {
return failure(c, 400, '仅支持单渠道添加');
}
const payload = body.channel ?? body;
const parsed = normalizeChannelInput(payload);
if (!parsed.name || !parsed.base_url || !parsed.api_key) {
return failure(c, 400, '缺少必要参数');
}
const existingId = parsed.id ?? generateToken('ch_');
const exists = await c.env.DB.prepare('SELECT id FROM channels WHERE id = ?').bind(existingId).first();
if (exists) {
return failure(c, 409, '渠道已存在');
}
const now = nowIso();
const baseUrl = normalizeBaseUrlInput(parsed.base_url);
await c.env.DB.prepare(
'INSERT INTO channels (id, name, base_url, api_key, weight, status, rate_limit, models_json, type, group_name, priority, metadata_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
)
.bind(
existingId,
parsed.name,
baseUrl ?? normalizeBaseUrl(String(parsed.base_url)),
parsed.api_key,
parsed.weight ?? 1,
parsed.status ?? 'active',
parsed.rate_limit ?? 0,
parsed.models_json,
parsed.type ?? 1,
parsed.group_name,
parsed.priority ?? 0,
parsed.metadata_json,
now,
now
)
.run();
return success(c);
});
newapi.put('/', async (c) => {
const body = await c.req.json().catch(() => null);
if (!body) {
return failure(c, 400, '请求体为空');
}
const payload = body.channel ?? body;
const id = payload?.id ?? body?.id;
if (!id) {
return failure(c, 400, '缺少渠道ID');
}
const current = await c.env.DB.prepare('SELECT * FROM channels WHERE id = ?').bind(String(id)).first();
if (!current) {
return failure(c, 404, '渠道不存在');
}
const parsed = normalizeChannelInput(payload);
const models = parsed.models.length > 0 ? parsed.models : extractModelIds(current as any);
const mergedMetadata = mergeMetadata(current.metadata_json, parsed.metadata_json);
const nextBaseUrl = normalizeBaseUrlInput(parsed.base_url ?? current.base_url) ?? String(current.base_url);
await c.env.DB.prepare(
'UPDATE channels SET name = ?, base_url = ?, api_key = ?, weight = ?, status = ?, rate_limit = ?, models_json = ?, type = ?, group_name = ?, priority = ?, metadata_json = ?, updated_at = ? WHERE id = ?'
)
.bind(
parsed.name ?? current.name,
nextBaseUrl,
parsed.api_key ?? current.api_key,
parsed.weight ?? current.weight ?? 1,
parsed.status ?? current.status,
parsed.rate_limit ?? current.rate_limit ?? 0,
modelsToJson(models),
parsed.type ?? current.type ?? 1,
parsed.group_name ?? current.group_name,
parsed.priority ?? current.priority ?? 0,
mergedMetadata,
nowIso(),
String(id)
)
.run();
return success(c);
});
newapi.delete('/:id', async (c) => {
const id = c.req.param('id');
const existing = await c.env.DB.prepare('SELECT id FROM channels WHERE id = ?').bind(id).first();
if (!existing) {
return failure(c, 404, '渠道不存在');
}
await c.env.DB.prepare('DELETE FROM channels WHERE id = ?').bind(id).run();
return success(c);
});
newapi.get('/test/:id', async (c) => {
const id = c.req.param('id');
const channel = await c.env.DB.prepare('SELECT * FROM channels WHERE id = ?').bind(id).first();
if (!channel) {
return failure(c, 404, '渠道不存在');
}
const result = await fetchModels(String(channel.base_url), String(channel.api_key));
if (!result.ok) {
await updateChannelTestResult(c, id, false, result.elapsed);
return failure(c, 502, '渠道测试失败');
}
await updateChannelTestResult(c, id, true, result.elapsed);
return success(c, undefined, '测试成功');
});
newapi.post('/test', async (c) => {
const body = await c.req.json().catch(() => null);
const id = body?.id;
if (!id) {
return failure(c, 400, '缺少渠道ID');
}
const channel = await c.env.DB.prepare('SELECT * FROM channels WHERE id = ?').bind(String(id)).first();
if (!channel) {
return failure(c, 404, '渠道不存在');
}
const result = await fetchModels(String(channel.base_url), String(channel.api_key));
if (!result.ok) {
await updateChannelTestResult(c, String(id), false, result.elapsed);
return failure(c, 502, '渠道测试失败');
}
await updateChannelTestResult(c, String(id), true, result.elapsed);
return success(c, undefined, '测试成功');
});
newapi.get('/fetch_models/:id', async (c) => {
const id = c.req.param('id');
const channel = await c.env.DB.prepare('SELECT * FROM channels WHERE id = ?').bind(id).first();
if (!channel) {
return failure(c, 404, '渠道不存在');
}
const result = await fetchModels(String(channel.base_url), String(channel.api_key));
if (!result.ok) {
await updateChannelTestResult(c, id, false, result.elapsed);
return failure(c, 502, '获取模型失败');
}
await updateChannelTestResult(c, id, true, result.elapsed, result.models);
return success(c, result.models);
});
newapi.post('/fetch_models', async (c) => {
const body = await c.req.json().catch(() => null);
if (!body?.base_url || !body?.key) {
return failure(c, 400, '缺少必要参数');
}
const result = await fetchModels(String(body.base_url), String(body.key));
if (!result.ok) {
return failure(c, 502, '获取模型失败');
}
return success(c, result.models);
});
newapi.get('/:id', async (c) => {
const id = c.req.param('id');
const channel = await c.env.DB.prepare('SELECT * FROM channels WHERE id = ?').bind(id).first();
if (!channel) {
return failure(c, 404, '渠道不存在');
}
const metadata = safeJsonParse<Record<string, unknown>>(channel.metadata_json, {});
const output = toNewApiChannel(channel as any);
return success(c, {
...output,
model_mapping: metadata.model_mapping ?? '{}',
channel_info: metadata.channel_info ?? { is_multi_key: false, multi_key_mode: 'random' }
});
});
export default newapi;
+37
View File
@@ -0,0 +1,37 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { newApiAuth } from '../middleware/newApiAuth';
const groups = new Hono<AppEnv>({ strict: false });
groups.use('*', newApiAuth);
function collectGroups(rows: Array<{ group_name?: string | null }>): string[] {
const names = new Set<string>();
for (const row of rows) {
const raw = row.group_name;
if (!raw) {
continue;
}
raw
.split(',')
.map((item) => item.trim())
.filter((item) => item.length > 0)
.forEach((item) => names.add(item));
}
if (names.size === 0) {
names.add('default');
}
return Array.from(names).sort((a, b) => a.localeCompare(b));
}
groups.get('/', async (c) => {
const result = await c.env.DB.prepare('SELECT group_name FROM channels').all();
const data = collectGroups((result.results ?? []) as Array<{ group_name?: string | null }>);
return c.json({
success: true,
message: '',
data
});
});
export default groups;
+27
View File
@@ -0,0 +1,27 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { newApiAuth } from '../middleware/newApiAuth';
import { extractModelIds } from '../services/newapi';
const users = new Hono<AppEnv>({ strict: false });
users.use('*', newApiAuth);
users.get('/models', async (c) => {
const result = await c.env.DB.prepare('SELECT * FROM channels WHERE status = ?')
.bind('active')
.all();
const models = new Set<string>();
for (const row of result.results ?? []) {
for (const id of extractModelIds(row as any)) {
models.add(id);
}
}
const data = Array.from(models).map((id) => ({ id, name: id }));
return c.json({
success: true,
message: '',
data
});
});
export default users;
+172
View File
@@ -0,0 +1,172 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { tokenAuth, type TokenRecord } from '../middleware/tokenAuth';
import { createWeightedOrder, extractModels, type ChannelRecord } from '../services/channels';
import { recordUsage } from '../services/usage';
import { jsonError } from '../utils/http';
import { safeJsonParse } from '../utils/json';
import { normalizeBaseUrl } from '../utils/url';
const proxy = new Hono<AppEnv>();
function channelSupportsModel(channel: ChannelRecord, model?: string | null): boolean {
if (!model) {
return true;
}
const models = extractModels(channel);
return models.some((entry) => entry.id === model);
}
function filterAllowedChannels(channels: ChannelRecord[], tokenRecord: TokenRecord): ChannelRecord[] {
const allowed = safeJsonParse<string[] | null>(tokenRecord.allowed_channels, null);
if (!allowed || allowed.length === 0) {
return channels;
}
const allowedSet = new Set(allowed);
return channels.filter((channel) => allowedSet.has(channel.id));
}
/**
* Determines whether a response status should be retried.
*
* Args:
* status: HTTP response status code.
*
* Returns:
* True if the status is retryable.
*/
function isRetryableStatus(status: number): boolean {
return status === 408 || status === 429 || status >= 500;
}
/**
* Waits before the next retry round.
*
* Args:
* ms: Delay in milliseconds.
*
* Returns:
* Promise resolved after delay.
*/
async function sleep(ms: number): Promise<void> {
if (ms <= 0) {
return;
}
await new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* OpenAI-compatible proxy handler.
*/
proxy.all('/*', tokenAuth, async (c) => {
const tokenRecord = c.get('tokenRecord') as TokenRecord;
const requestText = await c.req.text();
const parsedBody = requestText ? safeJsonParse<any>(requestText, null) : null;
const model = parsedBody?.model ?? null;
const isStream = parsedBody?.stream === true;
const channelResult = await c.env.DB.prepare('SELECT * FROM channels WHERE status = ?')
.bind('active')
.all();
const activeChannels = (channelResult.results ?? []) as ChannelRecord[];
const allowedChannels = filterAllowedChannels(activeChannels, tokenRecord);
const modelChannels = allowedChannels.filter((channel) => channelSupportsModel(channel, model));
const candidates = modelChannels.length > 0 ? modelChannels : allowedChannels;
if (candidates.length === 0) {
return jsonError(c, 503, 'no_available_channels', 'no_available_channels');
}
const ordered = createWeightedOrder(candidates);
const targetPath = c.req.path;
const retryRounds = Math.max(1, Number(c.env.PROXY_RETRY_ROUNDS ?? '1'));
const retryDelayMs = Math.max(0, Number(c.env.PROXY_RETRY_DELAY_MS ?? '200'));
let lastResponse: Response | null = null;
let lastChannel: ChannelRecord | null = null;
const start = Date.now();
let selectedChannel: ChannelRecord | null = null;
let usagePayload: any = null;
let round = 0;
while (round < retryRounds && !selectedChannel) {
let shouldRetry = false;
for (const channel of ordered) {
lastChannel = channel;
const target = `${normalizeBaseUrl(channel.base_url)}${targetPath}${c.req.url.includes('?') ? `?${c.req.url.split('?')[1]}` : ''}`;
const headers = new Headers(c.req.header());
headers.set('Authorization', `Bearer ${channel.api_key}`);
headers.set('x-api-key', String(channel.api_key));
headers.delete('host');
headers.delete('content-length');
try {
const response = await fetch(target, {
method: c.req.method,
headers,
body: requestText || undefined
});
lastResponse = response;
if (response.ok) {
selectedChannel = channel;
if (!isStream && response.headers.get('content-type')?.includes('application/json')) {
const data = await response.clone().json().catch(() => null);
usagePayload = data?.usage ?? null;
}
break;
}
if (isRetryableStatus(response.status)) {
shouldRetry = true;
}
} catch {
lastResponse = null;
shouldRetry = true;
}
}
if (selectedChannel || !shouldRetry) {
break;
}
round += 1;
if (round < retryRounds) {
await sleep(retryDelayMs);
}
}
const latencyMs = Date.now() - start;
if (!lastResponse) {
await recordUsage(c.env.DB, {
tokenId: tokenRecord.id,
model,
requestPath: targetPath,
totalTokens: 0,
latencyMs,
status: 'error'
});
return jsonError(c, 502, 'upstream_unavailable', 'upstream_unavailable');
}
const channelForUsage = selectedChannel ?? lastChannel;
if (channelForUsage) {
const totalTokens = Number(usagePayload?.total_tokens ?? 0);
await recordUsage(c.env.DB, {
tokenId: tokenRecord.id,
channelId: channelForUsage.id,
model,
requestPath: targetPath,
totalTokens: Number.isNaN(totalTokens) ? 0 : totalTokens,
promptTokens: Number(usagePayload?.prompt_tokens ?? 0),
completionTokens: Number(usagePayload?.completion_tokens ?? 0),
cost: 0,
latencyMs,
status: lastResponse.ok ? 'ok' : 'error'
});
}
return lastResponse;
});
export default proxy;
+33
View File
@@ -0,0 +1,33 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { getRetentionDays, setRetentionDays } from '../services/settings';
import { jsonError } from '../utils/http';
const settings = new Hono<AppEnv>();
/**
* Returns settings values.
*/
settings.get('/', async (c) => {
const fallback = Number(c.env.LOG_RETENTION_DAYS ?? '30');
const retention = await getRetentionDays(c.env.DB, Number.isNaN(fallback) ? 30 : fallback);
return c.json({ log_retention_days: retention });
});
/**
* Updates settings values.
*/
settings.put('/', async (c) => {
const body = await c.req.json().catch(() => null);
if (!body?.log_retention_days) {
return jsonError(c, 400, 'log_retention_days_required', 'log_retention_days_required');
}
const days = Number(body.log_retention_days);
if (Number.isNaN(days) || days < 1) {
return jsonError(c, 400, 'invalid_log_retention_days', 'invalid_log_retention_days');
}
await setRetentionDays(c.env.DB, days);
return c.json({ ok: true });
});
export default settings;
+127
View File
@@ -0,0 +1,127 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { generateToken, sha256Hex } from '../utils/crypto';
import { nowIso } from '../utils/time';
import { jsonError } from '../utils/http';
import { safeJsonParse } from '../utils/json';
const tokens = new Hono<AppEnv>();
/**
* Lists API tokens.
*/
tokens.get('/', async (c) => {
const result = await c.env.DB
.prepare(
'SELECT id, name, key_prefix, quota_total, quota_used, status, allowed_channels, created_at, updated_at FROM tokens ORDER BY created_at DESC'
)
.all();
return c.json({ tokens: result.results ?? [] });
});
/**
* Creates a new API token.
*/
tokens.post('/', async (c) => {
const body = await c.req.json().catch(() => null);
if (!body?.name) {
return jsonError(c, 400, 'name_required', 'name_required');
}
const rawToken = generateToken('sk-');
const tokenHash = await sha256Hex(rawToken);
const id = crypto.randomUUID();
const now = nowIso();
const keyPrefix = rawToken.slice(0, 8);
const quotaTotal = body.quota_total === null || body.quota_total === undefined
? null
: Number(body.quota_total);
await c.env.DB
.prepare(
'INSERT INTO tokens (id, name, key_hash, key_prefix, token_plain, quota_total, quota_used, status, allowed_channels, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
)
.bind(
id,
body.name,
tokenHash,
keyPrefix,
rawToken,
Number.isNaN(quotaTotal) ? null : quotaTotal,
0,
body.status ?? 'active',
JSON.stringify(body.allowed_channels ?? null),
now,
now
)
.run();
return c.json({
id,
token: rawToken
});
});
/**
* Updates an API token.
*/
tokens.patch('/:id', async (c) => {
const id = c.req.param('id');
const body = await c.req.json().catch(() => null);
if (!body) {
return jsonError(c, 400, 'missing_body', 'missing_body');
}
const existing = await c.env.DB.prepare('SELECT * FROM tokens WHERE id = ?').bind(id).first();
if (!existing) {
return jsonError(c, 404, 'token_not_found', 'token_not_found');
}
const existingAllowed = safeJsonParse(existing.allowed_channels, null);
const quotaTotalUpdate = body.quota_total === null || body.quota_total === undefined
? existing.quota_total
: Number(body.quota_total);
const quotaUsedUpdate = body.quota_used === null || body.quota_used === undefined
? existing.quota_used
: Number(body.quota_used);
await c.env.DB
.prepare(
'UPDATE tokens SET name = ?, quota_total = ?, quota_used = ?, status = ?, allowed_channels = ?, updated_at = ? WHERE id = ?'
)
.bind(
body.name ?? existing.name,
Number.isNaN(quotaTotalUpdate) ? existing.quota_total : quotaTotalUpdate,
Number.isNaN(quotaUsedUpdate) ? existing.quota_used : quotaUsedUpdate,
body.status ?? existing.status,
JSON.stringify(body.allowed_channels ?? existingAllowed ?? null),
nowIso(),
id
)
.run();
return c.json({ ok: true });
});
/**
* Reveals a stored API token.
*/
tokens.get('/:id/reveal', async (c) => {
const id = c.req.param('id');
const record = await c.env.DB.prepare('SELECT token_plain FROM tokens WHERE id = ?').bind(id).first();
if (!record) {
return jsonError(c, 404, 'token_not_found', 'token_not_found');
}
return c.json({ token: record.token_plain ?? null });
});
/**
* Deletes an API token.
*/
tokens.delete('/:id', async (c) => {
const id = c.req.param('id');
await c.env.DB.prepare('DELETE FROM tokens WHERE id = ?').bind(id).run();
return c.json({ ok: true });
});
export default tokens;
+49
View File
@@ -0,0 +1,49 @@
import { Hono } from 'hono';
import type { AppEnv } from '../env';
import { getRetentionDays } from '../services/settings';
import { pruneUsageLogs } from '../services/usage';
const usage = new Hono<AppEnv>();
/**
* Lists usage logs with filters.
*/
usage.get('/', async (c) => {
const query = c.req.query();
const params: Array<string | number> = [];
let sql = 'SELECT usage_logs.*, channels.name as channel_name, tokens.name as token_name FROM usage_logs LEFT JOIN channels ON channels.id = usage_logs.channel_id LEFT JOIN tokens ON tokens.id = usage_logs.token_id WHERE 1=1';
if (query.from) {
sql += ' AND usage_logs.created_at >= ?';
params.push(query.from);
}
if (query.to) {
sql += ' AND usage_logs.created_at <= ?';
params.push(query.to);
}
if (query.model) {
sql += ' AND usage_logs.model = ?';
params.push(query.model);
}
if (query.channel_id) {
sql += ' AND usage_logs.channel_id = ?';
params.push(query.channel_id);
}
if (query.token_id) {
sql += ' AND usage_logs.token_id = ?';
params.push(query.token_id);
}
const limit = Math.min(Number(query.limit ?? 50), 200);
sql += ' ORDER BY usage_logs.created_at DESC LIMIT ?';
params.push(Number.isNaN(limit) ? 50 : limit);
const fallback = Number(c.env.LOG_RETENTION_DAYS ?? '30');
const retention = await getRetentionDays(c.env.DB, Number.isNaN(fallback) ? 30 : fallback);
await pruneUsageLogs(c.env.DB, retention);
const result = await c.env.DB.prepare(sql).bind(...params).all();
return c.json({ logs: result.results ?? [] });
});
export default usage;
+62
View File
@@ -0,0 +1,62 @@
import { safeJsonParse } from '../utils/json';
export type ChannelRecord = {
id: string;
name: string;
base_url: string;
api_key: string;
weight: number;
status: string;
rate_limit?: number | null;
models_json?: string | null;
};
export type ModelEntry = {
id: string;
label: string;
channelId: string;
channelName: string;
};
/**
* Returns channels in a weighted random order.
*/
export function createWeightedOrder(channels: ChannelRecord[]): ChannelRecord[] {
const pool = channels.map((channel) => ({
...channel,
weight: Math.max(1, Number(channel.weight) || 1)
}));
const ordered: ChannelRecord[] = [];
while (pool.length > 0) {
const total = pool.reduce((sum, channel) => sum + channel.weight, 0);
let roll = Math.random() * total;
const index = pool.findIndex((channel) => {
roll -= channel.weight;
return roll <= 0;
});
const [selected] = pool.splice(index < 0 ? 0 : index, 1);
ordered.push(selected);
}
return ordered;
}
/**
* Extracts model entries from a channel record.
*/
export function extractModels(channel: ChannelRecord): ModelEntry[] {
const raw = safeJsonParse<any>(channel.models_json, null);
const models = Array.isArray(raw)
? raw
: Array.isArray(raw?.data)
? raw.data
: [];
return models
.filter((model) => model?.id)
.map((model) => ({
id: String(model.id),
label: String(model.id),
channelId: channel.id,
channelName: channel.name
}));
}
+253
View File
@@ -0,0 +1,253 @@
import { safeJsonParse } from '../utils/json';
import { normalizeBaseUrl } from '../utils/url';
export type ChannelRow = {
id: string;
name: string;
base_url: string;
api_key: string;
weight: number;
status: string;
rate_limit?: number | null;
models_json?: string | null;
type?: number | null;
group_name?: string | null;
priority?: number | null;
metadata_json?: string | null;
test_time?: number | string | null;
response_time_ms?: number | null;
created_at?: string | null;
updated_at?: string | null;
};
export type NewApiChannel = {
id: string;
name: string;
type: number;
status: number;
priority: number;
weight: number;
models: string;
group: string;
response_time: number;
test_time: number;
base_url?: string;
key?: string;
created_time?: number;
updated_time?: number;
};
export type ParsedChannelInput = {
id?: string;
name?: string;
type?: number;
base_url?: string;
api_key?: string;
weight?: number;
status?: string;
rate_limit?: number;
models: string[];
models_json: string;
group_name?: string | null;
priority?: number;
metadata_json?: string | null;
};
const KNOWN_KEYS = new Set([
'id',
'name',
'type',
'key',
'api_key',
'base_url',
'baseUrl',
'weight',
'status',
'rate_limit',
'rateLimit',
'models',
'model',
'model_list',
'models_list',
'group',
'group_name',
'groups',
'priority'
]);
function toNumber(value: unknown, fallback: number | null = null): number | null {
if (value === null || value === undefined) {
return fallback;
}
const parsed = Number(value);
return Number.isNaN(parsed) ? fallback : parsed;
}
export function normalizeModelsInput(input: unknown): string[] {
if (!input) {
return [];
}
if (Array.isArray(input)) {
return input.map((item) => String(item)).filter((item) => item.length > 0);
}
if (typeof input === 'string') {
return input
.split(',')
.map((item) => item.trim())
.filter((item) => item.length > 0);
}
if (typeof input === 'object') {
const raw = input as { data?: unknown[] };
if (Array.isArray(raw.data)) {
return raw.data
.map((item: any) => String(item?.id ?? item))
.filter((item) => item.length > 0);
}
}
return [];
}
export function modelsToJson(models: string[]): string {
const normalized = models
.map((model) => String(model).trim())
.filter((model) => model.length > 0);
return JSON.stringify(normalized.map((id) => ({ id })));
}
export function extractModelIds(channel: ChannelRow): string[] {
const raw = safeJsonParse<any>(channel.models_json, null);
const models = Array.isArray(raw)
? raw
: Array.isArray(raw?.data)
? raw.data
: [];
return models
.map((model: any) => String(model?.id ?? model))
.filter((model: string) => model.length > 0);
}
export function toInternalStatus(status: unknown): string {
if (status === undefined || status === null) {
return 'active';
}
if (typeof status === 'string') {
const normalized = status.trim().toLowerCase();
if (['1', 'true', 'enabled', 'enable', 'active'].includes(normalized)) {
return 'active';
}
if (['0', '2', 'false', 'disabled', 'disable', 'inactive'].includes(normalized)) {
return 'disabled';
}
}
if (typeof status === 'number') {
return status === 1 ? 'active' : 'disabled';
}
if (typeof status === 'boolean') {
return status ? 'active' : 'disabled';
}
return 'active';
}
export function toNewApiStatus(status: string | null | undefined): number {
return status === 'active' ? 1 : 2;
}
export function normalizeChannelInput(body: any): ParsedChannelInput {
const hasModels =
body &&
(Object.prototype.hasOwnProperty.call(body, 'models') ||
Object.prototype.hasOwnProperty.call(body, 'model') ||
Object.prototype.hasOwnProperty.call(body, 'model_list') ||
Object.prototype.hasOwnProperty.call(body, 'models_list'));
const models = hasModels
? normalizeModelsInput(body?.models ?? body?.model ?? body?.model_list ?? body?.models_list)
: [];
const hasGroup =
body &&
(Object.prototype.hasOwnProperty.call(body, 'group') ||
Object.prototype.hasOwnProperty.call(body, 'group_name') ||
Object.prototype.hasOwnProperty.call(body, 'groups'));
const groupInput = body?.group ?? body?.group_name ?? null;
const groupsInput = Array.isArray(body?.groups) ? body.groups.map((item: any) => String(item)) : null;
const groupName = !hasGroup
? undefined
: groupInput
? String(groupInput)
: groupsInput && groupsInput.length > 0
? groupsInput.join(',')
: null;
const metadata: Record<string, unknown> = {};
if (body && typeof body === 'object') {
for (const [key, value] of Object.entries(body)) {
if (KNOWN_KEYS.has(key)) {
continue;
}
metadata[key] = value;
}
}
const hasType = body && Object.prototype.hasOwnProperty.call(body, 'type');
const hasWeight = body && Object.prototype.hasOwnProperty.call(body, 'weight');
const hasStatus = body && Object.prototype.hasOwnProperty.call(body, 'status');
const hasRateLimit =
body &&
(Object.prototype.hasOwnProperty.call(body, 'rate_limit') ||
Object.prototype.hasOwnProperty.call(body, 'rateLimit'));
const hasPriority = body && Object.prototype.hasOwnProperty.call(body, 'priority');
return {
id: body?.id !== undefined && body?.id !== null ? String(body.id) : undefined,
name: body?.name ? String(body.name) : undefined,
type: hasType ? toNumber(body?.type, 1) ?? 1 : undefined,
base_url: body?.base_url ?? body?.baseUrl,
api_key: body?.key ?? body?.api_key,
weight: hasWeight ? toNumber(body?.weight, 1) ?? 1 : undefined,
status: hasStatus ? toInternalStatus(body?.status) : undefined,
rate_limit: hasRateLimit ? toNumber(body?.rate_limit ?? body?.rateLimit, 0) ?? 0 : undefined,
models,
models_json: modelsToJson(models),
group_name: groupName,
priority: hasPriority ? toNumber(body?.priority, 0) ?? 0 : undefined,
metadata_json: Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null
};
}
export function mergeMetadata(existing: string | null | undefined, incoming: string | null | undefined): string | null {
const base = safeJsonParse<Record<string, unknown>>(existing, {});
const updates = safeJsonParse<Record<string, unknown>>(incoming, {});
const merged = { ...base, ...updates };
return Object.keys(merged).length > 0 ? JSON.stringify(merged) : null;
}
export function toNewApiChannel(channel: ChannelRow): NewApiChannel {
const models = extractModelIds(channel);
const metadata = safeJsonParse<Record<string, unknown>>(channel.metadata_json, {});
const createdTime = channel.created_at ? Date.parse(channel.created_at) : NaN;
const updatedTime = channel.updated_at ? Date.parse(channel.updated_at) : NaN;
return {
id: channel.id,
name: channel.name,
type: Number(channel.type ?? 1),
status: toNewApiStatus(channel.status),
priority: Number(channel.priority ?? 0),
weight: Number(channel.weight ?? 1),
models: models.join(','),
group: channel.group_name ?? '',
response_time: Number(channel.response_time_ms ?? 0),
test_time: toNumber(channel.test_time, 0) ?? 0,
base_url: channel.base_url,
key: channel.api_key,
created_time: Number.isNaN(createdTime) ? undefined : Math.floor(createdTime / 1000),
updated_time: Number.isNaN(updatedTime) ? undefined : Math.floor(updatedTime / 1000),
...metadata
};
}
export function normalizeBaseUrlInput(value: string | undefined | null): string | null {
if (!value) {
return null;
}
return normalizeBaseUrl(String(value));
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Determines whether a token can consume more quota.
*/
export function canConsumeQuota(quotaTotal: number | null, quotaUsed: number, increment: number): boolean {
if (quotaTotal === null || quotaTotal === undefined) {
return true;
}
return quotaUsed + increment <= quotaTotal;
}
/**
* Normalizes quota numbers to safe values.
*/
export function normalizeQuota(quotaTotal?: number | null, quotaUsed?: number | null) {
return {
quotaTotal: quotaTotal ?? null,
quotaUsed: quotaUsed ?? 0
};
}
+41
View File
@@ -0,0 +1,41 @@
import type { D1Database } from '@cloudflare/workers-types';
import { nowIso } from '../utils/time';
const RETENTION_KEY = 'log_retention_days';
/**
* Returns the log retention days from settings or env fallback.
*/
export async function getRetentionDays(db: D1Database, fallback: number): Promise<number> {
const setting = await db.prepare('SELECT value FROM settings WHERE key = ?').bind(RETENTION_KEY).first();
if (setting?.value) {
const parsed = Number(setting.value);
if (!Number.isNaN(parsed) && parsed > 0) {
return parsed;
}
}
return fallback;
}
/**
* Updates the log retention days setting.
*/
export async function setRetentionDays(db: D1Database, days: number): Promise<void> {
const value = Math.max(1, Math.floor(days)).toString();
await db
.prepare('INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at')
.bind(RETENTION_KEY, value, nowIso())
.run();
}
/**
* Loads generic settings as a key/value map.
*/
export async function listSettings(db: D1Database): Promise<Record<string, string>> {
const result = await db.prepare('SELECT key, value FROM settings').all();
const map: Record<string, string> = {};
for (const row of result.results ?? []) {
map[String(row.key)] = String(row.value);
}
return map;
}
+58
View File
@@ -0,0 +1,58 @@
import type { D1Database } from '@cloudflare/workers-types';
import { nowIso } from '../utils/time';
export type UsageInput = {
tokenId?: string | null;
channelId?: string | null;
model?: string | null;
requestPath?: string | null;
totalTokens?: number | null;
promptTokens?: number | null;
completionTokens?: number | null;
cost?: number | null;
latencyMs?: number | null;
status?: string | null;
};
/**
* Inserts a usage record and updates token quota.
*/
export async function recordUsage(db: D1Database, input: UsageInput): Promise<void> {
const id = crypto.randomUUID();
const createdAt = nowIso();
await db
.prepare(
'INSERT INTO usage_logs (id, token_id, channel_id, model, request_path, total_tokens, prompt_tokens, completion_tokens, cost, latency_ms, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
)
.bind(
id,
input.tokenId ?? null,
input.channelId ?? null,
input.model ?? null,
input.requestPath ?? null,
input.totalTokens ?? 0,
input.promptTokens ?? 0,
input.completionTokens ?? 0,
input.cost ?? 0,
input.latencyMs ?? 0,
input.status ?? 'ok',
createdAt
)
.run();
if (input.tokenId && input.totalTokens) {
await db
.prepare('UPDATE tokens SET quota_used = quota_used + ?, updated_at = ? WHERE id = ?')
.bind(input.totalTokens, createdAt, input.tokenId)
.run();
}
}
/**
* Deletes usage logs older than the retention window.
*/
export async function pruneUsageLogs(db: D1Database, retentionDays: number): Promise<void> {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - retentionDays);
await db.prepare('DELETE FROM usage_logs WHERE created_at < ?').bind(cutoff.toISOString()).run();
}
+23
View File
@@ -0,0 +1,23 @@
const encoder = new TextEncoder();
/**
* Creates a SHA-256 hex digest from input text.
*/
export async function sha256Hex(input: string): Promise<string> {
const data = encoder.encode(input);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
}
/**
* Generates a URL-safe random token with an optional prefix.
*/
export function generateToken(prefix = ''): string {
const bytes = crypto.getRandomValues(new Uint8Array(24));
const base = btoa(String.fromCharCode(...bytes))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
return `${prefix}${base}`;
}
+14
View File
@@ -0,0 +1,14 @@
import type { Context } from 'hono';
/**
* Sends a JSON error response with a consistent shape.
*/
export function jsonError(c: Context, status: number, message: string, code?: string) {
return c.json(
{
error: message,
code
},
status
);
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Safely parses JSON or returns a fallback value.
*/
export function safeJsonParse<T>(value: string | null | undefined, fallback: T): T {
if (!value) {
return fallback;
}
try {
return JSON.parse(value) as T;
} catch {
return fallback;
}
}
+12
View File
@@ -0,0 +1,12 @@
import type { Context } from 'hono';
/**
* Reads a bearer token from common headers.
*/
export function getBearerToken(c: Context): string | null {
const auth = c.req.header('Authorization');
if (auth && auth.toLowerCase().startsWith('bearer ')) {
return auth.slice(7).trim();
}
return c.req.header('x-api-key') ?? c.req.header('x-admin-token') ?? null;
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Returns the current ISO timestamp.
*/
export function nowIso(): string {
return new Date().toISOString();
}
/**
* Adds hours to a date and returns a new Date.
*/
export function addHours(date: Date, hours: number): Date {
const copy = new Date(date.getTime());
copy.setHours(copy.getHours() + hours);
return copy;
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Normalizes upstream base URLs and strips a trailing /v1 segment.
*
* Args:
* baseUrl: Raw upstream base URL.
*
* Returns:
* Normalized base URL without trailing slashes or /v1.
*/
export function normalizeBaseUrl(baseUrl: string): string {
const trimmed = baseUrl.trim().replace(/\/+$/, '');
return trimmed.replace(/\/v1$/i, '');
}
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["@cloudflare/workers-types"],
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}
+17
View File
@@ -0,0 +1,17 @@
name = "new-api-lite"
main = "src/index.ts"
compatibility_date = "2026-02-14"
[vars]
ADMIN_PASSWORD = "lsy"
CORS_ORIGIN = "http://localhost:5173"
SESSION_TTL_HOURS = "12"
LOG_RETENTION_DAYS = "30"
PROXY_RETRY_ROUNDS = "2"
PROXY_RETRY_DELAY_MS = "200"
NEW_API_ADMIN_TOKEN = "sk-newapi-admin"
[[d1_databases]]
binding = "DB"
database_name = "new_api_lite"
database_id = "00000000-0000-0000-0000-000000000000"
+652
View File
@@ -0,0 +1,652 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "new-api-lite",
"dependencies": {
"wrangler": "4",
},
"devDependencies": {
"@types/node": "^20.11.30",
"typescript": "^5.4.5",
"vitest": "^1.5.3",
},
},
"apps/admin": {
"name": "new-api-lite-admin",
"devDependencies": {
"typescript": "^5.4.5",
"vite": "^5.2.0",
},
},
"apps/worker": {
"name": "new-api-lite-worker",
"dependencies": {
"hono": "^4.2.7",
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240222.0",
"typescript": "^5.4.5",
"wrangler": "^3.48.0",
},
},
},
"packages": {
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "https://registry.npmmirror.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.12.1", "https://registry.npmmirror.com/@cloudflare/unenv-preset/-/unenv-preset-2.12.1.tgz", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20260115.0" }, "optionalPeers": ["workerd"] }, "sha512-tP/Wi+40aBJovonSNJSsS7aFJY0xjuckKplmzDs2Xat06BJ68B6iG7YDUWXJL8gNn0gqW7YC5WhlYhO3QbugQA=="],
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260212.0", "https://registry.npmmirror.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260212.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-kLxuYutk88Wlo7edp8mlkN68TgZZ9237SUnuX9kNaD5jcOdblUqiBctMRZeRcPsuoX/3g2t0vS4ga02NBEVRNg=="],
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260212.0", "https://registry.npmmirror.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260212.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-fqoqQWMA1D0ZzDOD8sp0allREM2M8GHdpxMXQ8EdZpZ70z5bJbJ9Vr4qe35++FNIZJspsDHfTw3Xm/M4ELm/dQ=="],
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260212.0", "https://registry.npmmirror.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260212.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-bCSQoZzDzV5MSh4ueWo1DgmOn4Hf3QBu4Yo3eQFXA2llYFIu/sZgRtkEehw1X2/SY5Sn6O0EMCqxJYRf82Wdeg=="],
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260212.0", "https://registry.npmmirror.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260212.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-GPvp1iiKQodtbUDi6OmR5I0vD75lawB54tdYGtmypuHC7ZOI2WhBmhb3wCxgnQNOG1z7mhCQrzRCoqrKwYbVWQ=="],
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260212.0", "https://registry.npmmirror.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260212.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-wHRI218Xn4ndgWJCUHH4Zx0YlU5q/o6OmcxXkcw95tJOsQn4lDrhppioPh4eScxJZALf2X+ODeZcyQTCq5exGw=="],
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260214.0", "https://registry.npmmirror.com/@cloudflare/workers-types/-/workers-types-4.20260214.0.tgz", {}, "sha512-qb8rgbAdJR4BAPXolXhFL/wuGtecHLh1veOyZ1mK6QqWuCdI3vK1biKC0i3lzmzdLR/DZvsN3mNtpUE8zpWGEg=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "https://registry.npmmirror.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="],
"@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "https://registry.npmmirror.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "https://registry.npmmirror.com/@fastify/busboy/-/busboy-2.1.1.tgz", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@img/colour": ["@img/colour@1.0.0", "https://registry.npmmirror.com/@img/colour/-/colour-1.0.0.tgz", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "https://registry.npmmirror.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "https://registry.npmmirror.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "https://registry.npmmirror.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"@jest/schemas": ["@jest/schemas@29.6.3", "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@poppinss/colors": ["@poppinss/colors@4.1.6", "https://registry.npmmirror.com/@poppinss/colors/-/colors-4.1.6.tgz", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "https://registry.npmmirror.com/@poppinss/dumper/-/dumper-0.6.5.tgz", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
"@poppinss/exception": ["@poppinss/exception@1.2.3", "https://registry.npmmirror.com/@poppinss/exception/-/exception-1.2.3.tgz", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
"@sinclair/typebox": ["@sinclair/typebox@0.27.10", "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.10.tgz", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "https://registry.npmmirror.com/@sindresorhus/is/-/is-7.2.0.tgz", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
"@speed-highlight/core": ["@speed-highlight/core@1.2.14", "https://registry.npmmirror.com/@speed-highlight/core/-/core-1.2.14.tgz", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
"@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/node": ["@types/node@20.19.33", "https://registry.npmmirror.com/@types/node/-/node-20.19.33.tgz", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="],
"@vitest/expect": ["@vitest/expect@1.6.1", "https://registry.npmmirror.com/@vitest/expect/-/expect-1.6.1.tgz", { "dependencies": { "@vitest/spy": "1.6.1", "@vitest/utils": "1.6.1", "chai": "^4.3.10" } }, "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog=="],
"@vitest/runner": ["@vitest/runner@1.6.1", "https://registry.npmmirror.com/@vitest/runner/-/runner-1.6.1.tgz", { "dependencies": { "@vitest/utils": "1.6.1", "p-limit": "^5.0.0", "pathe": "^1.1.1" } }, "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA=="],
"@vitest/snapshot": ["@vitest/snapshot@1.6.1", "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-1.6.1.tgz", { "dependencies": { "magic-string": "^0.30.5", "pathe": "^1.1.1", "pretty-format": "^29.7.0" } }, "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ=="],
"@vitest/spy": ["@vitest/spy@1.6.1", "https://registry.npmmirror.com/@vitest/spy/-/spy-1.6.1.tgz", { "dependencies": { "tinyspy": "^2.2.0" } }, "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw=="],
"@vitest/utils": ["@vitest/utils@1.6.1", "https://registry.npmmirror.com/@vitest/utils/-/utils-1.6.1.tgz", { "dependencies": { "diff-sequences": "^29.6.3", "estree-walker": "^3.0.3", "loupe": "^2.3.7", "pretty-format": "^29.7.0" } }, "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g=="],
"acorn": ["acorn@8.15.0", "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-walk": ["acorn-walk@8.3.4", "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.4.tgz", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
"ansi-styles": ["ansi-styles@5.2.0", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"as-table": ["as-table@1.0.55", "https://registry.npmmirror.com/as-table/-/as-table-1.0.55.tgz", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="],
"assertion-error": ["assertion-error@1.1.0", "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz", {}, "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="],
"blake3-wasm": ["blake3-wasm@2.1.5", "https://registry.npmmirror.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
"cac": ["cac@6.7.14", "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"chai": ["chai@4.5.0", "https://registry.npmmirror.com/chai/-/chai-4.5.0.tgz", { "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", "deep-eql": "^4.1.3", "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", "type-detect": "^4.1.0" } }, "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw=="],
"check-error": ["check-error@1.0.3", "https://registry.npmmirror.com/check-error/-/check-error-1.0.3.tgz", { "dependencies": { "get-func-name": "^2.0.2" } }, "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg=="],
"color": ["color@4.2.3", "https://registry.npmmirror.com/color/-/color-4.2.3.tgz", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"color-string": ["color-string@1.9.1", "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"confbox": ["confbox@0.1.8", "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"cookie": ["cookie@1.1.1", "https://registry.npmmirror.com/cookie/-/cookie-1.1.1.tgz", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="],
"debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-eql": ["deep-eql@4.1.4", "https://registry.npmmirror.com/deep-eql/-/deep-eql-4.1.4.tgz", { "dependencies": { "type-detect": "^4.0.0" } }, "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg=="],
"defu": ["defu@6.1.4", "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"diff-sequences": ["diff-sequences@29.6.3", "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.6.3.tgz", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
"esbuild": ["esbuild@0.27.3", "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"estree-walker": ["estree-walker@3.0.3", "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"execa": ["execa@8.0.1", "https://registry.npmmirror.com/execa/-/execa-8.0.1.tgz", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
"exit-hook": ["exit-hook@2.2.1", "https://registry.npmmirror.com/exit-hook/-/exit-hook-2.2.1.tgz", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
"exsolve": ["exsolve@1.0.8", "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-func-name": ["get-func-name@2.0.2", "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.2.tgz", {}, "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ=="],
"get-source": ["get-source@2.0.12", "https://registry.npmmirror.com/get-source/-/get-source-2.0.12.tgz", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="],
"get-stream": ["get-stream@8.0.1", "https://registry.npmmirror.com/get-stream/-/get-stream-8.0.1.tgz", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
"glob-to-regexp": ["glob-to-regexp@0.4.1", "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
"hono": ["hono@4.11.9", "https://registry.npmmirror.com/hono/-/hono-4.11.9.tgz", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="],
"human-signals": ["human-signals@5.0.0", "https://registry.npmmirror.com/human-signals/-/human-signals-5.0.0.tgz", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
"is-arrayish": ["is-arrayish@0.3.4", "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.4.tgz", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
"is-stream": ["is-stream@3.0.0", "https://registry.npmmirror.com/is-stream/-/is-stream-3.0.0.tgz", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"js-tokens": ["js-tokens@9.0.1", "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"kleur": ["kleur@4.1.5", "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"local-pkg": ["local-pkg@0.5.1", "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz", { "dependencies": { "mlly": "^1.7.3", "pkg-types": "^1.2.1" } }, "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ=="],
"loupe": ["loupe@2.3.7", "https://registry.npmmirror.com/loupe/-/loupe-2.3.7.tgz", { "dependencies": { "get-func-name": "^2.0.1" } }, "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA=="],
"magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"merge-stream": ["merge-stream@2.0.0", "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"mime": ["mime@3.0.0", "https://registry.npmmirror.com/mime/-/mime-3.0.0.tgz", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"mimic-fn": ["mimic-fn@4.0.0", "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
"miniflare": ["miniflare@4.20260212.0", "https://registry.npmmirror.com/miniflare/-/miniflare-4.20260212.0.tgz", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260212.0", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-Lgxq83EuR2q/0/DAVOSGXhXS1V7GDB04HVggoPsenQng8sqEDR3hO4FigIw5ZI2Sv2X7kIc30NCzGHJlCFIYWg=="],
"mlly": ["mlly@1.8.0", "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
"ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mustache": ["mustache@4.2.0", "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
"nanoid": ["nanoid@3.3.11", "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"new-api-lite-admin": ["new-api-lite-admin@workspace:apps/admin"],
"new-api-lite-worker": ["new-api-lite-worker@workspace:apps/worker"],
"npm-run-path": ["npm-run-path@5.3.0", "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-5.3.0.tgz", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="],
"ohash": ["ohash@2.0.11", "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"onetime": ["onetime@6.0.0", "https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
"p-limit": ["p-limit@5.0.0", "https://registry.npmmirror.com/p-limit/-/p-limit-5.0.0.tgz", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ=="],
"path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-to-regexp": ["path-to-regexp@6.3.0", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
"pathe": ["pathe@1.1.2", "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
"pathval": ["pathval@1.1.1", "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz", {}, "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="],
"picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"pkg-types": ["pkg-types@1.3.1", "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"postcss": ["postcss@8.5.6", "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"pretty-format": ["pretty-format@29.7.0", "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
"printable-characters": ["printable-characters@1.0.42", "https://registry.npmmirror.com/printable-characters/-/printable-characters-1.0.42.tgz", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="],
"react-is": ["react-is@18.3.1", "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"rollup": ["rollup@4.57.1", "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
"rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "https://registry.npmmirror.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="],
"rollup-plugin-node-polyfills": ["rollup-plugin-node-polyfills@0.2.1", "https://registry.npmmirror.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", { "dependencies": { "rollup-plugin-inject": "^3.0.0" } }, "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA=="],
"rollup-pluginutils": ["rollup-pluginutils@2.8.2", "https://registry.npmmirror.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="],
"semver": ["semver@7.7.4", "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"sharp": ["sharp@0.33.5", "https://registry.npmmirror.com/sharp/-/sharp-0.33.5.tgz", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
"shebang-command": ["shebang-command@2.0.0", "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"siginfo": ["siginfo@2.0.0", "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
"signal-exit": ["signal-exit@4.1.0", "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"simple-swizzle": ["simple-swizzle@0.2.4", "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
"source-map": ["source-map@0.6.1", "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"sourcemap-codec": ["sourcemap-codec@1.4.8", "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="],
"stackback": ["stackback@0.0.2", "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"stacktracey": ["stacktracey@2.1.8", "https://registry.npmmirror.com/stacktracey/-/stacktracey-2.1.8.tgz", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="],
"std-env": ["std-env@3.10.0", "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"stoppable": ["stoppable@1.1.0", "https://registry.npmmirror.com/stoppable/-/stoppable-1.1.0.tgz", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
"strip-final-newline": ["strip-final-newline@3.0.0", "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
"strip-literal": ["strip-literal@2.1.1", "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q=="],
"supports-color": ["supports-color@10.2.2", "https://registry.npmmirror.com/supports-color/-/supports-color-10.2.2.tgz", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
"tinybench": ["tinybench@2.9.0", "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinypool": ["tinypool@0.8.4", "https://registry.npmmirror.com/tinypool/-/tinypool-0.8.4.tgz", {}, "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ=="],
"tinyspy": ["tinyspy@2.2.1", "https://registry.npmmirror.com/tinyspy/-/tinyspy-2.2.1.tgz", {}, "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A=="],
"tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-detect": ["type-detect@4.1.0", "https://registry.npmmirror.com/type-detect/-/type-detect-4.1.0.tgz", {}, "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw=="],
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"ufo": ["ufo@1.6.3", "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
"undici": ["undici@7.18.2", "https://registry.npmmirror.com/undici/-/undici-7.18.2.tgz", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="],
"undici-types": ["undici-types@6.21.0", "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unenv": ["unenv@2.0.0-rc.24", "https://registry.npmmirror.com/unenv/-/unenv-2.0.0-rc.24.tgz", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
"vite": ["vite@5.4.21", "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
"vite-node": ["vite-node@1.6.1", "https://registry.npmmirror.com/vite-node/-/vite-node-1.6.1.tgz", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.4", "pathe": "^1.1.1", "picocolors": "^1.0.0", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA=="],
"vitest": ["vitest@1.6.1", "https://registry.npmmirror.com/vitest/-/vitest-1.6.1.tgz", { "dependencies": { "@vitest/expect": "1.6.1", "@vitest/runner": "1.6.1", "@vitest/snapshot": "1.6.1", "@vitest/spy": "1.6.1", "@vitest/utils": "1.6.1", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", "execa": "^8.0.1", "local-pkg": "^0.5.0", "magic-string": "^0.30.5", "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", "tinypool": "^0.8.3", "vite": "^5.0.0", "vite-node": "1.6.1", "why-is-node-running": "^2.2.2" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "1.6.1", "@vitest/ui": "1.6.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag=="],
"which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"why-is-node-running": ["why-is-node-running@2.3.0", "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"workerd": ["workerd@1.20260212.0", "https://registry.npmmirror.com/workerd/-/workerd-1.20260212.0.tgz", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260212.0", "@cloudflare/workerd-darwin-arm64": "1.20260212.0", "@cloudflare/workerd-linux-64": "1.20260212.0", "@cloudflare/workerd-linux-arm64": "1.20260212.0", "@cloudflare/workerd-windows-64": "1.20260212.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-4B9BoZUzKSRv3pVZGEPh7OX+Q817hpUqAUtz5O0TxJVqo4OsYJAUA/sY177Q5ha/twjT9KaJt2DtQzE+oyCOzw=="],
"wrangler": ["wrangler@4.65.0", "https://registry.npmmirror.com/wrangler/-/wrangler-4.65.0.tgz", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.12.1", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260212.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260212.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260212.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-R+n3o3tlGzLK9I4fGocPReOuvcnjhtOL2aCVKkHMeuEwt9pPbOO4FxJtx/ec5cIUG/otRyJnfQGCAr9DplBVng=="],
"ws": ["ws@8.18.0", "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
"yocto-queue": ["yocto-queue@1.2.2", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.2.2.tgz", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
"youch": ["youch@4.1.0-beta.10", "https://registry.npmmirror.com/youch/-/youch-4.1.0-beta.10.tgz", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
"youch-core": ["youch-core@0.3.3", "https://registry.npmmirror.com/youch-core/-/youch-core-0.3.3.tgz", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
"zod": ["zod@3.22.3", "https://registry.npmmirror.com/zod/-/zod-3.22.3.tgz", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
"miniflare/sharp": ["sharp@0.34.5", "https://registry.npmmirror.com/sharp/-/sharp-0.34.5.tgz", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"mlly/pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"new-api-lite-worker/wrangler": ["wrangler@3.114.17", "https://registry.npmmirror.com/wrangler/-/wrangler-3.114.17.tgz", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/unenv-preset": "2.0.2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250718.3", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.14", "workerd": "1.20250718.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250408.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA=="],
"npm-run-path/path-key": ["path-key@4.0.0", "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"pkg-types/pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"rollup-plugin-inject/estree-walker": ["estree-walker@0.6.1", "https://registry.npmmirror.com/estree-walker/-/estree-walker-0.6.1.tgz", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="],
"rollup-plugin-inject/magic-string": ["magic-string@0.25.9", "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
"rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "https://registry.npmmirror.com/estree-walker/-/estree-walker-0.6.1.tgz", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="],
"unenv/pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"vite/esbuild": ["esbuild@0.21.5", "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
"miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"miniflare/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"miniflare/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"miniflare/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"miniflare/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"miniflare/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"miniflare/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
"miniflare/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"miniflare/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"miniflare/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"miniflare/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"miniflare/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"miniflare/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
"miniflare/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"miniflare/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"miniflare/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"miniflare/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
"miniflare/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
"miniflare/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"new-api-lite-worker/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "https://registry.npmmirror.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="],
"new-api-lite-worker/wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.0.2", "https://registry.npmmirror.com/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", { "peerDependencies": { "unenv": "2.0.0-rc.14", "workerd": "^1.20250124.0" }, "optionalPeers": ["workerd"] }, "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg=="],
"new-api-lite-worker/wrangler/esbuild": ["esbuild@0.17.19", "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.19.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="],
"new-api-lite-worker/wrangler/miniflare": ["miniflare@3.20250718.3", "https://registry.npmmirror.com/miniflare/-/miniflare-3.20250718.3.tgz", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250718.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ=="],
"new-api-lite-worker/wrangler/unenv": ["unenv@2.0.0-rc.14", "https://registry.npmmirror.com/unenv/-/unenv-2.0.0-rc.14.tgz", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.1", "ohash": "^2.0.10", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q=="],
"new-api-lite-worker/wrangler/workerd": ["workerd@1.20250718.0", "https://registry.npmmirror.com/workerd/-/workerd-1.20250718.0.tgz", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250718.0", "@cloudflare/workerd-darwin-arm64": "1.20250718.0", "@cloudflare/workerd-linux-64": "1.20250718.0", "@cloudflare/workerd-linux-arm64": "1.20250718.0", "@cloudflare/workerd-windows-64": "1.20250718.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg=="],
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
"vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
"vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
"vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
"vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
"vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
"vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
"vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
"vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
"vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
"vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
"vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
"vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
"vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
"vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
"vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
"vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
"vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
"vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
"vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
"vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
"vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="],
"new-api-lite-worker/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="],
"new-api-lite-worker/wrangler/miniflare/acorn": ["acorn@8.14.0", "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"new-api-lite-worker/wrangler/miniflare/acorn-walk": ["acorn-walk@8.3.2", "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.2.tgz", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
"new-api-lite-worker/wrangler/miniflare/undici": ["undici@5.29.0", "https://registry.npmmirror.com/undici/-/undici-5.29.0.tgz", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"new-api-lite-worker/wrangler/miniflare/youch": ["youch@3.3.4", "https://registry.npmmirror.com/youch/-/youch-3.3.4.tgz", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
"new-api-lite-worker/wrangler/unenv/pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"new-api-lite-worker/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250718.0", "https://registry.npmmirror.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g=="],
"new-api-lite-worker/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250718.0", "https://registry.npmmirror.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q=="],
"new-api-lite-worker/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250718.0", "https://registry.npmmirror.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg=="],
"new-api-lite-worker/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250718.0", "https://registry.npmmirror.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog=="],
"new-api-lite-worker/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250718.0", "https://registry.npmmirror.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg=="],
"new-api-lite-worker/wrangler/miniflare/youch/cookie": ["cookie@0.7.2", "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
}
}
+72
View File
@@ -0,0 +1,72 @@
# 变更日志
## [Unreleased]
### 微调
- **[admin-ui]**: 本地开发增加 Vite proxy 解决前后端端口不一致
- 类型: 微调(无方案包)
- 文件: apps/admin/vite.config.ts, README.md
- **[worker]**: 补充 wrangler.toml 示例配置占位
- 类型: 微调(无方案包)
- 文件: apps/worker/wrangler.toml
- **[admin-ui]**: 渠道 ID 与日志渠道可见,操作反馈更清晰
- 类型: 微调(无方案包)
- 文件: apps/admin/src/main.ts
- **[worker]**: 使用日志关联渠道/令牌,base_url 自动纠正
- 类型: 微调(无方案包)
- 文件: apps/worker/src/routes/usage.ts, apps/worker/src/routes/channels.ts, apps/worker/src/routes/proxy.ts
- **[worker]**: 渠道 ID 支持自定义,令牌可二次查看
- 类型: 微调(无方案包)
- 文件: apps/worker/src/routes/channels.ts, apps/worker/src/routes/tokens.ts, apps/worker/src/db/schema.sql, apps/worker/migrations/0002_add_token_plain.sql
- **[admin-ui]**: 渠道 ID 可录入、令牌查看按钮
- 类型: 微调(无方案包)
- 文件: apps/admin/src/main.ts
- **[tests]**: 补充 URL 规范化单测
- 类型: 微调(无方案包)
- 文件: tests/worker/url.test.ts
- **[proxy]**: 增加失败轮询重试与相关配置
- 类型: 微调(无方案包)
- 文件: apps/worker/src/routes/proxy.ts, apps/worker/src/env.ts, apps/worker/wrangler.toml
- **[docs]**: 更新代理重试与本地配置说明
- 类型: 微调(无方案包)
- 文件: README.md, helloagents/modules/proxy.md
- **[worker]**: 放宽路由严格匹配以兼容 `/api/channel/` 尾斜杠
- 类型: 微调(无方案包)
- 文件: apps/worker/src/index.ts
- **[worker]**: 新增 `/api/group` 兼容接口并放行鉴权
- 类型: 微调(无方案包)
- 文件: apps/worker/src/index.ts, apps/worker/src/routes/newapiGroups.ts, tests/worker/newapi.test.ts
## [0.2.1] - 2026-02-15
### 变更
- **[tooling]**: 切换为 Bun 作为包管理器,补充部署说明与 fix 命令
- 方案: [202602150153_bun-tooling](archive/2026-02/202602150153_bun-tooling/)
## [0.2.0] - 2026-02-15
### 新增
- **[channels/auth/models]**: 新增 New API 兼容渠道管理接口、用户模型接口与管理员令牌鉴权
- 方案: [202602150127_newapi-channel-compat](archive/2026-02/202602150127_newapi-channel-compat/)
- 决策: newapi-channel-compat#D001(新增兼容层并保留扩展字段)
## [0.1.0] - 2026-02-14
### 新增
- **[核心服务]**: 初始化 Worker + D1 后端与 Vite 管理台,提供渠道/模型/令牌/日志/面板与 OpenAI 兼容代理
- 方案: [202602142217_new-api-lite](archive/2026-02/202602142217_new-api-lite/)
- 决策: new-api-lite#D001(单 Worker + Hono), new-api-lite#D002(Vite + Pages), new-api-lite#D003(Token 默认全渠道), new-api-lite#D004(日志保留可配置)
### 修复
- **[{模块名}]**: {修复描述}
- 方案: [{YYYYMMDDHHMM}_{fix}](archive/{YYYY-MM}/{YYYYMMDDHHMM}_{fix}/)
### 微调
- **[{模块名}]**: {微调描述}
- 类型: 微调(无方案包)
- 文件: {文件路径}:{行号范围}
### 回滚
- **[{模块名}]**: 回滚至 {版本/提交}
- 原因: {回滚原因}
- 方案: [{原方案包}](archive/{YYYY-MM}/{原方案包}/)
+36
View File
@@ -0,0 +1,36 @@
# new-api-lite 知识库
> 本文件是知识库的入口点
## 快速导航
| 需要了解 | 读取文件 |
|---------|---------|
| 项目概况、技术栈、开发约定 | [context.md](context.md) |
| 模块索引 | [modules/_index.md](modules/_index.md) |
| 某个模块的职责和接口 | [modules/{模块名}.md](modules/{模块名}.md) |
| 项目变更历史 | [CHANGELOG.md](CHANGELOG.md) |
| 历史方案索引 | [archive/_index.md](archive/_index.md) |
| 当前待执行的方案 | [plan/](plan/) |
## 知识库状态
```yaml
最后更新: 2026-02-15 02:03
模块数量: 9
待执行方案: 0
```
## 读取指引
```yaml
启动任务:
1. 读取本文件获取导航
2. 读取 context.md 获取项目上下文
3. 检查 plan/ 是否有进行中方案包
任务相关:
- 涉及特定模块: 读取 modules/{模块名}.md
- 需要历史决策: 搜索 CHANGELOG.md → 读取对应 archive/{YYYY-MM}/{方案包}/proposal.md
- 继续之前任务: 读取 plan/{方案包}/*
```
+17
View File
@@ -0,0 +1,17 @@
version = 1
[commands]
# mode:
# - "allowlist": 只允许 allow 里的命令
# - "denylist": 允许除 deny 以外的命令
mode = "denylist"
# 默认策略:允许全部命令(deny 为空)
allow = [
"~auto", "~plan", "~exec",
"~init", "~upgrade", "~clean",
"~commit", "~test", "~review",
"~validate", "~rollback", "~help"
]
deny = []
+89
View File
@@ -0,0 +1,89 @@
# 项目上下文
## 1. 基本信息
```yaml
名称: new-api-lite
描述: 基于 Cloudflare Workers + D1 的简易版 new-api
类型: Web服务
状态: 开发中
```
## 2. 技术上下文
```yaml
语言: TypeScript
框架: Hono + Vite
包管理器: bun
构建工具: Wrangler + Vite
```
### 主要依赖
| 依赖 | 版本 | 用途 |
|------|------|------|
| Hono | 待定 | Worker 路由与中间件 |
| Wrangler | 待定 | Workers 构建与部署 |
| Vite | 待定 | 管理台构建 |
## 3. 项目概述
### 核心功能
- OpenAI 兼容代理(/v1/*)
- 渠道管理、模型广场、令牌管理、使用日志、数据面板
- 管理员登录与权限
- New API 兼容渠道管理接口(/api/channel)
- 系统设置(日志保留天数)
### 项目边界
```yaml
范围内:
- 单管理员密码登录
- Token 默认全渠道
- 使用日志可配置保留天数
范围外:
- 多租户/复杂角色体系
- 复杂计费结算与对账
```
## 4. 开发约定
### 代码规范
```yaml
命名风格: camelCase
文件命名: kebab-case
目录组织: apps/worker + apps/admin
```
### 错误处理
```yaml
错误码格式: ERR_{模块}_{含义}
日志级别: info/warn/error
```
### 测试要求
```yaml
测试框架: vitest
覆盖率要求: 核心逻辑覆盖
测试文件位置: tests/
```
### Git规范
```yaml
分支策略: trunk-based
提交格式: conventional commits
```
## 5. 当前约束(源自历史决策)
| 约束 | 原因 | 决策来源 |
|------|------|---------|
| Cloudflare Workers + D1 | 技术栈约束 | new-api-lite#D001 |
| 登录密码来自环境变量 | 安全要求 | new-api-lite#D002 |
| Token 默认全渠道 | 产品规则 | new-api-lite#D003 |
| 使用日志保留天数可配置 | 合规/成本 | new-api-lite#D004 |
## 6. 已知技术债务(可选)
| 债务描述 | 优先级 | 来源 | 建议处理时机 |
|---------|--------|------|-------------|
| 暂无 | - | - | - |
+32
View File
@@ -0,0 +1,32 @@
# 模块索引
> 通过此文件快速定位模块文档
## 模块清单
| 模块 | 职责 | 状态 | 文档 |
|------|------|------|------|
| auth | 管理员登录与会话 | 🚧 | [auth.md](./auth.md) |
| proxy | OpenAI 兼容代理与转发 | 🚧 | [proxy.md](./proxy.md) |
| channels | 渠道管理与健康检查 | 🚧 | [channels.md](./channels.md) |
| models | 模型广场与聚合 | 🚧 | [models.md](./models.md) |
| tokens | 令牌管理与额度 | 🚧 | [tokens.md](./tokens.md) |
| usage | 使用日志与留存 | 🚧 | [usage.md](./usage.md) |
| dashboard | 数据面板统计 | 🚧 | [dashboard.md](./dashboard.md) |
| settings | 系统配置 | 🚧 | [settings.md](./settings.md) |
| admin-ui | 管理台前端 | 🚧 | [admin-ui.md](./admin-ui.md) |
## 模块依赖关系
```
admin-ui → auth, channels, models, tokens, usage, dashboard, settings
proxy → channels, tokens, usage
dashboard → usage, tokens
models → channels
usage → settings
```
## 状态说明
- ✅ 稳定
- 🚧 开发中
- 📝 规划中
+17
View File
@@ -0,0 +1,17 @@
# admin-ui 模块
## 职责
- 管理台前端展示与操作
- 覆盖渠道、模型、令牌、日志、面板与设置
## 接口定义
- 调用 `/api/*` 管理接口
- 登录后使用 Bearer token
## 行为规范
- Vite 构建静态文件
- 前端状态集中在单页
- Token 仅本地保存,不回显
## 依赖关系
- `auth` / `channels` / `models` / `tokens` / `usage` / `dashboard` / `settings` 模块
+20
View File
@@ -0,0 +1,20 @@
# auth 模块
## 职责
- 管理员密码登录与会话签发
- 管理员会话注销与失效处理
- New API 兼容接口鉴权(管理员令牌或会话)
## 接口定义
- `POST /api/auth/login` 管理员登录
- `POST /api/auth/logout` 管理员退出
## 行为规范
- 密码仅来自环境变量 `ADMIN_PASSWORD`
- 会话 token 使用哈希后存储
- 过期会话自动清理
- New API 兼容接口允许使用 `NEW_API_ADMIN_TOKEN` 直接鉴权
## 依赖关系
- `admin_sessions` 表
- `ADMIN_PASSWORD` / `SESSION_TTL_HOURS` / `NEW_API_ADMIN_TOKEN` 环境变量
+34
View File
@@ -0,0 +1,34 @@
# channels 模块
## 职责
- 渠道配置的增删改查
- 渠道连通性测试与模型同步
- 渠道权重与状态管理
## 接口定义
- `GET /api/channels` 渠道列表
- `POST /api/channels` 新建渠道
- `PATCH /api/channels/:id` 更新渠道
- `DELETE /api/channels/:id` 删除渠道
- `POST /api/channels/:id/test` 连通性测试
- `GET /api/channel` New API 兼容列表
- `GET /api/channel/search` New API 兼容搜索
- `POST /api/channel` New API 兼容新增
- `PUT /api/channel` New API 兼容更新
- `DELETE /api/channel/:id` New API 兼容删除
- `GET /api/channel/test/:id` New API 兼容连通性测试
- `GET /api/channel/fetch_models/:id` New API 兼容模型拉取
- `GET /api/group` New API 兼容分组列表
## 行为规范
- `base_url` 保存为无尾斜杠格式
- 创建渠道时可传入自定义 `id`,未提供则自动生成
- 连通性测试调用 `/v1/models`
- 测试成功后更新 `models_json`
- New API 兼容层支持 `type`/`group`/`priority` 等字段映射,并保留扩展字段到 `metadata_json`
- New API 分组列表从 `group_name` 字段解析,空时返回 `default`
## 依赖关系
- `channels` 表
- `models` 模块(聚合读取)
- `newApiAuth` 中间件
+16
View File
@@ -0,0 +1,16 @@
# dashboard 模块
## 职责
- 汇总调用统计指标
- 输出按时间、模型、渠道、令牌的聚合数据
## 接口定义
- `GET /api/dashboard` 数据面板汇总
## 行为规范
- 以 `usage_logs` 为统计来源
- 默认返回最近 30 条趋势数据
## 依赖关系
- `usage_logs` 表
- `tokens` / `channels` 表
+16
View File
@@ -0,0 +1,16 @@
# models 模块
## 职责
- 汇总各渠道模型列表
- 输出模型与渠道的映射关系
## 接口定义
- `GET /api/models` 模型广场列表
- `GET /api/user/models` New API 兼容用户模型列表
## 行为规范
- 仅聚合启用状态的渠道
- 优先使用渠道连通性测试返回的模型数据
## 依赖关系
- `channels` 表
+19
View File
@@ -0,0 +1,19 @@
# proxy 模块
## 职责
- 提供 OpenAI 兼容 API 代理
- 根据权重选择渠道并支持故障回退
- 记录用量日志并回写额度
## 接口定义
- `/v1/*` OpenAI 兼容代理路径
## 行为规范
- 基于令牌鉴权
- 按渠道权重随机排序
- 非流式响应解析 usage 字段
- 可配置失败重试轮询(响应 5xx/429 时触发)
## 依赖关系
- `channels` / `tokens` / `usage_logs` 表
- `tokenAuth` 中间件
+16
View File
@@ -0,0 +1,16 @@
# settings 模块
## 职责
- 维护系统级配置(如日志保留天数)
- 为使用日志与面板提供统一配置读取
## 接口定义
- `GET /api/settings` 读取设置
- `PUT /api/settings` 更新设置
## 行为规范
- 配置写入后即时生效
- 未配置时使用环境变量默认值
## 依赖关系
- `settings` 表
+22
View File
@@ -0,0 +1,22 @@
# tokens 模块
## 职责
- 管理 API 令牌的生成与生命周期
- 维护额度与已用额度统计
- 控制令牌启用/禁用状态
## 接口定义
- `GET /api/tokens` 令牌列表
- `POST /api/tokens` 生成令牌
- `PATCH /api/tokens/:id` 更新令牌
- `DELETE /api/tokens/:id` 删除令牌
- `GET /api/tokens/:id/reveal` 再次查看令牌
## 行为规范
- 令牌可通过 reveal 接口再次查看
- 默认允许所有渠道
- 额度消耗由代理调用记录
## 依赖关系
- `tokens` 表
- `usage` 模块(用量回写)
+18
View File
@@ -0,0 +1,18 @@
# usage 模块
## 职责
- 记录代理调用日志
- 提供日志查询与筛选
- 执行日志保留策略
## 接口定义
- `GET /api/usage` 使用日志查询
## 行为规范
- 代理调用统一写入 `usage_logs`
- 保留天数可配置,查询时触发清理
- 查询结果附带渠道与令牌名称
## 依赖关系
- `usage_logs` 表
- `settings` 模块
+22
View File
@@ -0,0 +1,22 @@
{
"name": "new-api-lite",
"private": true,
"packageManager": "bun@1.3.9",
"workspaces": [
"apps/*"
],
"scripts": {
"dev:worker": "npm --workspace apps/worker run dev",
"dev:admin": "npm --workspace apps/admin run dev",
"test": "vitest",
"fix": "bun pm pkg fix"
},
"devDependencies": {
"@types/node": "^20.11.30",
"typescript": "^5.4.5",
"vitest": "^1.5.3"
},
"dependencies": {
"wrangler": "4"
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"types": ["vitest/globals"],
"skipLibCheck": true
}
}
+8
View File
@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['tests/**/*.test.ts'],
environment: 'node'
}
});