d1/sqlite支持

This commit is contained in:
mtvpls
2026-01-30 14:27:43 +08:00
parent c4198e0954
commit c86f9cdca7
11 changed files with 2110 additions and 11 deletions

7
.gitignore vendored
View File

@@ -57,4 +57,9 @@ public/workbox-*.js
public/workbox-*.js.map public/workbox-*.js.map
/.claude /.claude
/.vscode /.vscode
# SQLite 开发数据库
.data/
*.db
*.db-shm
*.db-wal

View File

@@ -0,0 +1,170 @@
-- ============================================
-- MoonTV Plus - Cloudflare D1 数据库结构
-- 版本: 1.0.0
-- 创建时间: 2026-01-30
-- ============================================
-- 1. 用户表
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
password_hash TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('owner', 'admin', 'user')),
banned INTEGER DEFAULT 0,
tags TEXT, -- JSON array: ["vip", "premium"]
oidc_sub TEXT UNIQUE,
enabled_apis TEXT, -- JSON array: ["api1", "api2"]
created_at INTEGER NOT NULL,
playrecord_migrated INTEGER DEFAULT 0,
favorite_migrated INTEGER DEFAULT 0,
skip_migrated INTEGER DEFAULT 0,
last_movie_request_time INTEGER,
email TEXT,
email_notifications INTEGER DEFAULT 1
);
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
CREATE INDEX IF NOT EXISTS idx_users_oidc_sub ON users(oidc_sub) WHERE oidc_sub IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
-- 2. 播放记录表
CREATE TABLE IF NOT EXISTS play_records (
username TEXT NOT NULL,
key TEXT NOT NULL, -- format: "source+id" (e.g., "tmdb+12345")
title TEXT NOT NULL,
source_name TEXT NOT NULL,
cover TEXT,
year TEXT,
episode_index INTEGER NOT NULL,
total_episodes INTEGER NOT NULL,
play_time INTEGER NOT NULL, -- 播放进度(秒)
total_time INTEGER NOT NULL, -- 总时长(秒)
save_time INTEGER NOT NULL, -- 保存时间戳
search_title TEXT,
PRIMARY KEY (username, key),
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_play_records_save_time ON play_records(username, save_time DESC);
CREATE INDEX IF NOT EXISTS idx_play_records_source ON play_records(username, source_name);
-- 3. 收藏表
CREATE TABLE IF NOT EXISTS favorites (
username TEXT NOT NULL,
key TEXT NOT NULL, -- format: "source+id"
source_name TEXT NOT NULL,
total_episodes INTEGER NOT NULL,
title TEXT NOT NULL,
year TEXT,
cover TEXT,
save_time INTEGER NOT NULL,
search_title TEXT,
origin TEXT CHECK(origin IN ('vod', 'live')),
is_completed INTEGER DEFAULT 0,
vod_remarks TEXT,
PRIMARY KEY (username, key),
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_favorites_save_time ON favorites(username, save_time DESC);
CREATE INDEX IF NOT EXISTS idx_favorites_source ON favorites(username, source_name);
-- 4. 搜索历史表
CREATE TABLE IF NOT EXISTS search_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
keyword TEXT NOT NULL,
timestamp INTEGER NOT NULL,
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE,
UNIQUE(username, keyword)
);
CREATE INDEX IF NOT EXISTS idx_search_history_user_time ON search_history(username, timestamp DESC);
-- 5. 跳过配置表(片头片尾)
CREATE TABLE IF NOT EXISTS skip_configs (
username TEXT NOT NULL,
key TEXT NOT NULL, -- format: "source+id"
enable INTEGER NOT NULL DEFAULT 1,
intro_time INTEGER NOT NULL DEFAULT 0, -- 片头时长(秒)
outro_time INTEGER NOT NULL DEFAULT 0, -- 片尾时长(秒)
PRIMARY KEY (username, key),
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);
-- 6. 弹幕过滤配置表
CREATE TABLE IF NOT EXISTS danmaku_filter_configs (
username TEXT PRIMARY KEY,
rules TEXT NOT NULL, -- JSON array: [{"keyword": "xxx", "type": "normal", "enabled": true}]
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);
-- 7. 通知表
CREATE TABLE IF NOT EXISTS notifications (
id TEXT PRIMARY KEY,
username TEXT NOT NULL,
type TEXT NOT NULL CHECK(type IN ('favorite_update', 'system', 'announcement', 'movie_request', 'request_fulfilled')),
title TEXT NOT NULL,
message TEXT NOT NULL,
timestamp INTEGER NOT NULL,
read INTEGER DEFAULT 0,
metadata TEXT, -- JSON object
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_notifications_user_time ON notifications(username, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_notifications_user_read ON notifications(username, read, timestamp DESC);
-- 8. 求片请求表
CREATE TABLE IF NOT EXISTS movie_requests (
id TEXT PRIMARY KEY,
tmdb_id INTEGER,
title TEXT NOT NULL,
year TEXT,
media_type TEXT NOT NULL CHECK(media_type IN ('movie', 'tv')),
season INTEGER,
poster TEXT,
overview TEXT,
requested_by TEXT NOT NULL, -- JSON array: ["user1", "user2"]
request_count INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL CHECK(status IN ('pending', 'fulfilled')) DEFAULT 'pending',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
fulfilled_at INTEGER,
fulfilled_source TEXT,
fulfilled_id TEXT
);
CREATE INDEX IF NOT EXISTS idx_movie_requests_status ON movie_requests(status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_movie_requests_tmdb ON movie_requests(tmdb_id) WHERE tmdb_id IS NOT NULL;
-- 9. 用户求片关联表(用于快速查询用户的求片记录)
CREATE TABLE IF NOT EXISTS user_movie_requests (
username TEXT NOT NULL,
request_id TEXT NOT NULL,
PRIMARY KEY (username, request_id),
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE,
FOREIGN KEY (request_id) REFERENCES movie_requests(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_user_movie_requests_user ON user_movie_requests(username);
-- 10. 全局配置表(键值对存储)
CREATE TABLE IF NOT EXISTS global_config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
-- 11. 管理员配置表(单例)
CREATE TABLE IF NOT EXISTS admin_config (
id INTEGER PRIMARY KEY CHECK(id = 1), -- 确保只有一条记录
config TEXT NOT NULL, -- JSON object
updated_at INTEGER NOT NULL
);
-- 12. 收藏更新检查时间表
CREATE TABLE IF NOT EXISTS favorite_check_times (
username TEXT PRIMARY KEY,
last_check_time INTEGER NOT NULL,
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);

View File

@@ -35,7 +35,7 @@ const nextConfig = {
], ],
}, },
webpack(config) { webpack(config, { isServer }) {
// Grab the existing rule that handles SVG imports // Grab the existing rule that handles SVG imports
const fileLoaderRule = config.module.rules.find((rule) => const fileLoaderRule = config.module.rules.find((rule) =>
rule.test?.test?.('.svg') rule.test?.test?.('.svg')
@@ -71,6 +71,21 @@ const nextConfig = {
crypto: false, crypto: false,
}; };
// Exclude better-sqlite3 and D1 modules from client-side bundle
if (!isServer) {
config.externals = config.externals || [];
config.externals.push({
'better-sqlite3': 'commonjs better-sqlite3',
});
config.resolve.alias = {
...config.resolve.alias,
'better-sqlite3': false,
'@/lib/d1.db': false,
'@/lib/d1-adapter': false,
};
}
return config; return config;
}, },
}; };

View File

@@ -20,7 +20,9 @@
"gen:manifest": "node scripts/generate-manifest.js", "gen:manifest": "node scripts/generate-manifest.js",
"postbuild": "echo 'Build completed - sitemap generation disabled'", "postbuild": "echo 'Build completed - sitemap generation disabled'",
"prepare": "husky install", "prepare": "husky install",
"watch-room:server": "node server/watch-room-standalone-server.js --port 3001 --auth YOUR_SECRET_KEY" "watch-room:server": "node server/watch-room-standalone-server.js --port 3001 --auth YOUR_SECRET_KEY",
"init:sqlite": "node scripts/init-sqlite.js",
"db:reset": "rm -f .data/moontv.db .data/moontv.db-shm .data/moontv.db-wal && node scripts/init-sqlite.js"
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
@@ -36,6 +38,7 @@
"anime4k-webgpu": "^1.0.0", "anime4k-webgpu": "^1.0.0",
"artplayer": "^5.3.0", "artplayer": "^5.3.0",
"artplayer-plugin-danmuku": "^5.2.0", "artplayer-plugin-danmuku": "^5.2.0",
"better-sqlite3": "^12.6.2",
"bs58": "^6.0.0", "bs58": "^6.0.0",
"cheerio": "^1.1.2", "cheerio": "^1.1.2",
"clsx": "^2.0.0", "clsx": "^2.0.0",
@@ -64,6 +67,7 @@
"react-window": "^2.2.3", "react-window": "^2.2.3",
"redis": "^4.6.7", "redis": "^4.6.7",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"server-only": "^0.0.1",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"swiper": "^11.2.8", "swiper": "^11.2.8",
@@ -81,6 +85,7 @@
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^15.0.7", "@testing-library/react": "^15.0.7",
"@types/better-sqlite3": "^7.6.11",
"@types/bs58": "^5.0.0", "@types/bs58": "^5.0.0",
"@types/he": "^1.2.3", "@types/he": "^1.2.3",
"@types/node": "24.0.3", "@types/node": "24.0.3",

196
pnpm-lock.yaml generated
View File

@@ -47,6 +47,9 @@ importers:
artplayer-plugin-danmuku: artplayer-plugin-danmuku:
specifier: ^5.2.0 specifier: ^5.2.0
version: 5.2.0 version: 5.2.0
better-sqlite3:
specifier: ^12.6.2
version: 12.6.2
bs58: bs58:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
@@ -131,6 +134,9 @@ importers:
remark-gfm: remark-gfm:
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1 version: 3.0.1
server-only:
specifier: ^0.0.1
version: 0.0.1
socket.io: socket.io:
specifier: ^4.8.1 specifier: ^4.8.1
version: 4.8.3 version: 4.8.3
@@ -177,6 +183,9 @@ importers:
'@testing-library/react': '@testing-library/react':
specifier: ^15.0.7 specifier: ^15.0.7
version: 15.0.7(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 15.0.7(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/better-sqlite3':
specifier: ^7.6.11
version: 7.6.13
'@types/bs58': '@types/bs58':
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.0.0 version: 5.0.0
@@ -3146,6 +3155,9 @@ packages:
'@types/babel__traverse@7.28.0': '@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/better-sqlite3@7.6.13':
resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==}
'@types/bs58@5.0.0': '@types/bs58@5.0.0':
resolution: {integrity: sha512-cAw/jKBzo98m6Xz1X5ETqymWfIMbXbu6nK15W4LQYjeHJkVqSmM5PO8Bd9KVHQJ/F4rHcSso9LcjtgCW6TGu2w==} resolution: {integrity: sha512-cAw/jKBzo98m6Xz1X5ETqymWfIMbXbu6nK15W4LQYjeHJkVqSmM5PO8Bd9KVHQJ/F4rHcSso9LcjtgCW6TGu2w==}
deprecated: This is a stub types definition. bs58 provides its own type definitions, so you do not need this installed. deprecated: This is a stub types definition. bs58 provides its own type definitions, so you do not need this installed.
@@ -3986,6 +3998,9 @@ packages:
base-x@5.0.1: base-x@5.0.1:
resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
base64id@2.0.0: base64id@2.0.0:
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
engines: {node: ^4.5.0 || >= 5.9} engines: {node: ^4.5.0 || >= 5.9}
@@ -3994,6 +4009,10 @@ packages:
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
hasBin: true hasBin: true
better-sqlite3@12.6.2:
resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==}
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x}
big.js@5.2.2: big.js@5.2.2:
resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
@@ -4004,6 +4023,9 @@ packages:
bindings@1.5.0: bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
blake3-wasm@2.1.5: blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
@@ -4047,6 +4069,9 @@ packages:
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
builtin-modules@3.3.0: builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -4150,6 +4175,9 @@ packages:
resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==} resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==}
engines: {node: '>= 14.16.0'} engines: {node: '>= 14.16.0'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
chownr@2.0.0: chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -4499,9 +4527,17 @@ packages:
decode-named-character-reference@1.2.0: decode-named-character-reference@1.2.0:
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
dedent@0.7.0: dedent@0.7.0:
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
deep-is@0.1.4: deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -4979,6 +5015,10 @@ packages:
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
expect@27.5.1: expect@27.5.1:
resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -5124,6 +5164,9 @@ packages:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
fs-extra@10.1.0: fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -5217,6 +5260,9 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
glob-parent@5.1.2: glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -5402,6 +5448,9 @@ packages:
idb@7.1.1: idb@7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@@ -6397,6 +6446,10 @@ packages:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'} engines: {node: '>=18'}
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
min-document@2.19.2: min-document@2.19.2:
resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==} resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==}
@@ -6463,6 +6516,9 @@ packages:
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mkdirp@1.0.4: mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -6520,6 +6576,9 @@ packages:
engines: {node: ^18 || >=20} engines: {node: ^18 || >=20}
hasBin: true hasBin: true
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
napi-postinstall@0.3.4: napi-postinstall@0.3.4:
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -6580,6 +6639,10 @@ packages:
no-case@3.0.4: no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
node-abi@3.87.0:
resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==}
engines: {node: '>=10'}
node-domexception@1.0.0: node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
@@ -6961,6 +7024,11 @@ packages:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'}
hasBin: true
prelude-ls@1.1.2: prelude-ls@1.1.2:
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -7110,6 +7178,10 @@ packages:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
react-dom@18.3.1: react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies: peerDependencies:
@@ -7384,6 +7456,9 @@ packages:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
set-function-length@1.2.2: set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -7441,6 +7516,12 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'} engines: {node: '>=14'}
simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
sisteransi@1.0.5: sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
@@ -7654,6 +7735,10 @@ packages:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
strip-json-comments@3.1.1: strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -7752,6 +7837,13 @@ packages:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'} engines: {node: '>=6'}
tar-fs@2.1.4:
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tar@6.2.1: tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -7944,6 +8036,9 @@ packages:
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
hasBin: true hasBin: true
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
type-check@0.3.2: type-check@0.3.2:
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -12807,6 +12902,10 @@ snapshots:
dependencies: dependencies:
'@babel/types': 7.28.5 '@babel/types': 7.28.5
'@types/better-sqlite3@7.6.13':
dependencies:
'@types/node': 24.0.3
'@types/bs58@5.0.0': '@types/bs58@5.0.0':
dependencies: dependencies:
bs58: 6.0.0 bs58: 6.0.0
@@ -13888,10 +13987,17 @@ snapshots:
base-x@5.0.1: {} base-x@5.0.1: {}
base64-js@1.5.1: {}
base64id@2.0.0: {} base64id@2.0.0: {}
baseline-browser-mapping@2.9.11: {} baseline-browser-mapping@2.9.11: {}
better-sqlite3@12.6.2:
dependencies:
bindings: 1.5.0
prebuild-install: 7.1.3
big.js@5.2.2: {} big.js@5.2.2: {}
binary-extensions@2.3.0: {} binary-extensions@2.3.0: {}
@@ -13900,6 +14006,12 @@ snapshots:
dependencies: dependencies:
file-uri-to-path: 1.0.0 file-uri-to-path: 1.0.0
bl@4.1.0:
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
blake3-wasm@2.1.5: {} blake3-wasm@2.1.5: {}
body-parser@2.2.2: body-parser@2.2.2:
@@ -13955,6 +14067,11 @@ snapshots:
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
builtin-modules@3.3.0: {} builtin-modules@3.3.0: {}
busboy@1.6.0: busboy@1.6.0:
@@ -14065,6 +14182,8 @@ snapshots:
dependencies: dependencies:
readdirp: 4.1.2 readdirp: 4.1.2
chownr@1.1.4: {}
chownr@2.0.0: {} chownr@2.0.0: {}
chownr@3.0.0: {} chownr@3.0.0: {}
@@ -14386,8 +14505,14 @@ snapshots:
dependencies: dependencies:
character-entities: 2.0.2 character-entities: 2.0.2
decompress-response@6.0.0:
dependencies:
mimic-response: 3.1.0
dedent@0.7.0: {} dedent@0.7.0: {}
deep-extend@0.6.0: {}
deep-is@0.1.4: {} deep-is@0.1.4: {}
deepmerge@4.3.1: {} deepmerge@4.3.1: {}
@@ -15059,6 +15184,8 @@ snapshots:
exit@0.1.2: {} exit@0.1.2: {}
expand-template@2.0.3: {}
expect@27.5.1: expect@27.5.1:
dependencies: dependencies:
'@jest/types': 27.5.1 '@jest/types': 27.5.1
@@ -15251,6 +15378,8 @@ snapshots:
fresh@2.0.0: {} fresh@2.0.0: {}
fs-constants@1.0.0: {}
fs-extra@10.1.0: fs-extra@10.1.0:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -15350,6 +15479,8 @@ snapshots:
split2: 3.2.2 split2: 3.2.2
through2: 4.0.2 through2: 4.0.2
github-from-package@0.0.0: {}
glob-parent@5.1.2: glob-parent@5.1.2:
dependencies: dependencies:
is-glob: 4.0.3 is-glob: 4.0.3
@@ -15579,6 +15710,8 @@ snapshots:
idb@7.1.1: {} idb@7.1.1: {}
ieee754@1.2.1: {}
ignore@5.3.2: {} ignore@5.3.2: {}
import-fresh@3.3.1: import-fresh@3.3.1:
@@ -17123,6 +17256,8 @@ snapshots:
mimic-function@5.0.1: {} mimic-function@5.0.1: {}
mimic-response@3.1.0: {}
min-document@2.19.2: min-document@2.19.2:
dependencies: dependencies:
dom-walk: 0.1.2 dom-walk: 0.1.2
@@ -17191,6 +17326,8 @@ snapshots:
dependencies: dependencies:
minipass: 7.1.2 minipass: 7.1.2
mkdirp-classic@0.5.3: {}
mkdirp@1.0.4: {} mkdirp@1.0.4: {}
mkdirp@3.0.1: {} mkdirp@3.0.1: {}
@@ -17240,6 +17377,8 @@ snapshots:
nanoid@5.1.6: {} nanoid@5.1.6: {}
napi-build-utils@2.0.0: {}
napi-postinstall@0.3.4: {} napi-postinstall@0.3.4: {}
natural-compare-lite@1.4.0: {} natural-compare-lite@1.4.0: {}
@@ -17310,6 +17449,10 @@ snapshots:
lower-case: 2.0.2 lower-case: 2.0.2
tslib: 2.8.1 tslib: 2.8.1
node-abi@3.87.0:
dependencies:
semver: 7.7.3
node-domexception@1.0.0: {} node-domexception@1.0.0: {}
node-fetch@2.6.7: node-fetch@2.6.7:
@@ -17649,6 +17792,21 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
prebuild-install@7.1.3:
dependencies:
detect-libc: 2.1.2
expand-template: 2.0.3
github-from-package: 0.0.0
minimist: 1.2.8
mkdirp-classic: 0.5.3
napi-build-utils: 2.0.0
node-abi: 3.87.0
pump: 3.0.3
rc: 1.2.8
simple-get: 4.0.1
tar-fs: 2.1.4
tunnel-agent: 0.6.0
prelude-ls@1.1.2: {} prelude-ls@1.1.2: {}
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
@@ -17742,6 +17900,13 @@ snapshots:
iconv-lite: 0.7.2 iconv-lite: 0.7.2
unpipe: 1.0.0 unpipe: 1.0.0
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.8
strip-json-comments: 2.0.1
react-dom@18.3.1(react@18.3.1): react-dom@18.3.1(react@18.3.1):
dependencies: dependencies:
loose-envify: 1.4.0 loose-envify: 1.4.0
@@ -18096,6 +18261,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
server-only@0.0.1: {}
set-function-length@1.2.2: set-function-length@1.2.2:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@@ -18193,6 +18360,14 @@ snapshots:
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
simple-concat@1.0.1: {}
simple-get@4.0.1:
dependencies:
decompress-response: 6.0.0
once: 1.4.0
simple-concat: 1.0.1
sisteransi@1.0.5: {} sisteransi@1.0.5: {}
slash@3.0.0: {} slash@3.0.0: {}
@@ -18454,6 +18629,8 @@ snapshots:
dependencies: dependencies:
min-indent: 1.0.1 min-indent: 1.0.1
strip-json-comments@2.0.1: {}
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
strnum@1.1.2: {} strnum@1.1.2: {}
@@ -18562,6 +18739,21 @@ snapshots:
tapable@2.3.0: {} tapable@2.3.0: {}
tar-fs@2.1.4:
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
pump: 3.0.3
tar-stream: 2.2.0
tar-stream@2.2.0:
dependencies:
bl: 4.1.0
end-of-stream: 1.4.5
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.2
tar@6.2.1: tar@6.2.1:
dependencies: dependencies:
chownr: 2.0.0 chownr: 2.0.0
@@ -18760,6 +18952,10 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
type-check@0.3.2: type-check@0.3.2:
dependencies: dependencies:
prelude-ls: 1.1.2 prelude-ls: 1.1.2

62
scripts/init-sqlite.js Normal file
View File

@@ -0,0 +1,62 @@
const Database = require('better-sqlite3');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
// SHA-256 加密密码(与 Redis 保持一致)
function hashPassword(password) {
return crypto.createHash('sha256').update(password).digest('hex');
}
// 确保 .data 目录存在
const dataDir = path.join(__dirname, '../.data');
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
// 创建数据库
const dbPath = path.join(dataDir, 'moontv.db');
const db = new Database(dbPath);
console.log('📦 Initializing SQLite database for development...');
console.log('📍 Database location:', dbPath);
// 读取迁移脚本
const migrationPath = path.join(__dirname, '../migrations/001_initial_schema.sql');
if (!fs.existsSync(migrationPath)) {
console.error('❌ Migration file not found:', migrationPath);
process.exit(1);
}
const sql = fs.readFileSync(migrationPath, 'utf8');
// 执行迁移
try {
db.exec(sql);
console.log('✅ Database schema created successfully!');
// 创建默认管理员用户(可选)
const username = process.env.USERNAME || 'admin';
const password = process.env.PASSWORD || '123456789';
const passwordHash = hashPassword(password);
const stmt = db.prepare(`
INSERT OR IGNORE INTO users (username, password_hash, role, created_at, playrecord_migrated, favorite_migrated, skip_migrated)
VALUES (?, ?, 'owner', ?, 1, 1, 1)
`);
stmt.run(username, passwordHash, Date.now());
console.log(`✅ Default admin user created: ${username}`);
} catch (err) {
console.error('❌ Migration failed:', err);
process.exit(1);
} finally {
db.close();
}
console.log('');
console.log('🎉 SQLite database initialized successfully!');
console.log('');
console.log('Next steps:');
console.log('1. Set NEXT_PUBLIC_STORAGE_TYPE=d1 in .env');
console.log('2. Run: npm run dev');

170
src/lib/d1-adapter.ts Normal file
View File

@@ -0,0 +1,170 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* 统一的数据库适配器接口
* 兼容 Cloudflare D1 和 better-sqlite3
*
* 注意:此模块仅在服务端使用,通过 webpack 配置排除客户端打包
*/
// D1 PreparedStatement 接口
export interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = any>(colName?: string): Promise<T | null>;
run<T = any>(): Promise<D1Result<T>>;
all<T = any>(): Promise<D1Result<T>>;
}
export interface D1Result<T = any> {
results?: T[];
success: boolean;
meta?: any;
error?: string;
}
// 统一的数据库接口
export interface DatabaseAdapter {
prepare(query: string): D1PreparedStatement;
batch?(statements: D1PreparedStatement[]): Promise<D1Result[]>;
exec?(query: string): void;
}
/**
* Cloudflare D1 适配器(生产环境)
*/
export class CloudflareD1Adapter implements DatabaseAdapter {
constructor(private db: D1Database) {}
prepare(query: string): D1PreparedStatement {
return this.db.prepare(query);
}
async batch(statements: D1PreparedStatement[]): Promise<D1Result[]> {
return this.db.batch(statements as any);
}
}
/**
* SQLite 适配器(开发环境)
* 包装 better-sqlite3 以兼容 D1 API
*/
export class SQLiteAdapter implements DatabaseAdapter {
private db: any; // better-sqlite3 Database
constructor(db: any) {
this.db = db;
}
prepare(query: string): D1PreparedStatement {
const stmt = this.db.prepare(query);
return new SQLitePreparedStatement(stmt);
}
batch(statements: D1PreparedStatement[]): Promise<D1Result[]> {
// SQLite 使用事务模拟 batch
return new Promise((resolve, reject) => {
try {
const results: D1Result[] = [];
const transaction = this.db.transaction(() => {
for (const stmt of statements) {
const result = (stmt as any).runSync();
results.push(result);
}
});
transaction();
resolve(results);
} catch (err) {
reject(err);
}
});
}
exec(query: string): void {
this.db.exec(query);
}
}
/**
* SQLite PreparedStatement 包装器
* 将 better-sqlite3 API 转换为 D1 兼容 API
*/
class SQLitePreparedStatement implements D1PreparedStatement {
private stmt: any;
private params: any[] = [];
constructor(stmt: any) {
this.stmt = stmt;
}
bind(...values: any[]): D1PreparedStatement {
this.params = values;
return this;
}
async first<T = any>(colName?: string): Promise<T | null> {
try {
const result = this.stmt.get(...this.params);
if (!result) return null;
if (colName) return result[colName] ?? null;
return result;
} catch (err) {
console.error('SQLite first() error:', err);
return null;
}
}
async run<T = any>(): Promise<D1Result<T>> {
try {
const info = this.stmt.run(...this.params);
return {
success: true,
meta: {
changes: info.changes,
last_row_id: info.lastInsertRowid,
},
};
} catch (err: any) {
console.error('SQLite run() error:', err);
return {
success: false,
error: err.message,
};
}
}
async all<T = any>(): Promise<D1Result<T>> {
try {
const results = this.stmt.all(...this.params);
return {
success: true,
results: results || [],
};
} catch (err: any) {
console.error('SQLite all() error:', err);
return {
success: false,
error: err.message,
results: [],
};
}
}
// 同步版本(用于 batch
runSync(): D1Result {
try {
const info = this.stmt.run(...this.params);
return {
success: true,
meta: {
changes: info.changes,
last_row_id: info.lastInsertRowid,
},
};
} catch (err: any) {
return {
success: false,
error: err.message,
};
}
}
}

1414
src/lib/d1.db.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,14 @@ import { RedisStorage } from './redis.db';
import { DanmakuFilterConfig,Favorite, IStorage, PlayRecord, SkipConfig } from './types'; import { DanmakuFilterConfig,Favorite, IStorage, PlayRecord, SkipConfig } from './types';
import { UpstashRedisStorage } from './upstash.db'; import { UpstashRedisStorage } from './upstash.db';
// storage type 常量: 'localstorage' | 'redis' | 'upstash',默认 'localstorage' // storage type 常量: 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | 'd1',默认 'localstorage'
const STORAGE_TYPE = const STORAGE_TYPE =
(process.env.NEXT_PUBLIC_STORAGE_TYPE as (process.env.NEXT_PUBLIC_STORAGE_TYPE as
| 'localstorage' | 'localstorage'
| 'redis' | 'redis'
| 'upstash' | 'upstash'
| 'kvrocks' | 'kvrocks'
| 'd1'
| undefined) || 'localstorage'; | undefined) || 'localstorage';
// 创建存储实例 // 创建存储实例
@@ -24,12 +25,67 @@ function createStorage(): IStorage {
return new UpstashRedisStorage(); return new UpstashRedisStorage();
case 'kvrocks': case 'kvrocks':
return new KvrocksStorage(); return new KvrocksStorage();
case 'd1':
// D1Storage 只能在服务端使用,客户端会报错
if (typeof window !== 'undefined') {
throw new Error('D1Storage can only be used on the server side');
}
const adapter = getD1Adapter();
// 动态导入 D1Storage 以避免客户端打包
const { D1Storage } = require('./d1.db');
return new D1Storage(adapter);
case 'localstorage': case 'localstorage':
default: default:
return null as unknown as IStorage; return null as unknown as IStorage;
} }
} }
/**
* 获取 D1 适配器
* 开发环境:使用 better-sqlite3
* 生产环境:使用 Cloudflare D1
*/
function getD1Adapter(): any {
// 动态导入适配器以避免客户端打包
const { CloudflareD1Adapter, SQLiteAdapter } = require('./d1-adapter');
// 生产环境Cloudflare Workers/Pages
if (typeof process !== 'undefined' && (process as any).env?.DB) {
console.log('Using Cloudflare D1 database');
return new CloudflareD1Adapter((process as any).env.DB);
}
// 开发环境better-sqlite3
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
try {
const Database = require('better-sqlite3');
const path = require('path');
const fs = require('fs');
const dbPath = path.join(process.cwd(), '.data', 'moontv.db');
// 检查数据库文件是否存在
if (!fs.existsSync(dbPath)) {
console.error('❌ SQLite database not found. Please run: npm run init:sqlite');
throw new Error('SQLite database not initialized');
}
const db = new Database(dbPath);
db.pragma('journal_mode = WAL'); // 启用 WAL 模式提升性能
console.log('Using SQLite database (development mode)');
console.log('Database location:', dbPath);
return new SQLiteAdapter(db);
} catch (err) {
console.error('Failed to initialize SQLite:', err);
throw err;
}
}
throw new Error('D1 database not available. Set NEXT_PUBLIC_STORAGE_TYPE to another option or configure D1.');
}
// 单例存储实例 // 单例存储实例
let storageInstance: IStorage | null = null; let storageInstance: IStorage | null = null;

View File

@@ -1,5 +1,10 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { TOKEN_CONFIG } from './token-config';
// Re-export TOKEN_CONFIG for backward compatibility
export { TOKEN_CONFIG };
// Lazy import to avoid Edge Runtime issues in middleware // Lazy import to avoid Edge Runtime issues in middleware
let getStorage: (() => any) | null = null; let getStorage: (() => any) | null = null;
@@ -11,13 +16,6 @@ async function loadStorage() {
return getStorage(); return getStorage();
} }
// Token 配置
export const TOKEN_CONFIG = {
ACCESS_TOKEN_AGE: 4 * 60 * 60 * 1000, // 4 小时
REFRESH_TOKEN_AGE: 60 * 24 * 60 * 60 * 1000, // 60 天
RENEWAL_THRESHOLD: 10 * 60 * 1000, // 剩余 10 分钟时自动续期
};
interface TokenData { interface TokenData {
token: string; token: string;
deviceInfo: string; deviceInfo: string;

8
src/lib/token-config.ts Normal file
View File

@@ -0,0 +1,8 @@
// Token 配置常量
// 这个文件不依赖任何服务端模块,可以在客户端安全使用
export const TOKEN_CONFIG = {
ACCESS_TOKEN_AGE: 4 * 60 * 60 * 1000, // 4 小时
REFRESH_TOKEN_AGE: 60 * 24 * 60 * 60 * 1000, // 60 天
RENEWAL_THRESHOLD: 10 * 60 * 1000, // 剩余 10 分钟时自动续期
};