db layer implemented

using sqlite
This commit is contained in:
echo 2025-11-07 23:46:46 +01:00
parent e287e55f1f
commit df668927f5
14 changed files with 2541 additions and 70 deletions

BIN
apps/admin/data/fitai.db Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,11 @@
"@hookform/resolvers": "^3.3.0",
"@tanstack/react-query": "^5.0.0",
"@types/bcryptjs": "^2.4.6",
"@types/sqlite3": "^3.1.11",
"ag-charts-community": "^9.0.0",
"ag-charts-react": "^9.0.0",
"ag-grid-community": "^32.0.0",
"ag-grid-react": "^32.0.0",
"autoprefixer": "^10.4.0",
"axios": "^1.6.0",
"bcryptjs": "^2.4.3",
@ -25,12 +30,10 @@
"react-dom": "^18.0.0",
"react-hook-form": "^7.47.0",
"recharts": "^2.8.0",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
"tailwindcss": "^3.3.0",
"zod": "^3.22.0",
"ag-grid-community": "^32.0.0",
"ag-grid-react": "^32.0.0",
"ag-charts-community": "^9.0.0",
"ag-charts-react": "^9.0.0"
"zod": "^3.22.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.0",

View File

@ -1,9 +1,10 @@
import { NextRequest, NextResponse } from 'next/server'
import bcrypt from 'bcryptjs'
import { users } from '../../../../lib/database'
import { getDatabase } from '../../../../lib/database/index'
export async function POST(request: NextRequest) {
try {
const db = await getDatabase()
const { email, password } = await request.json()
if (!email || !password) {
@ -13,7 +14,7 @@ export async function POST(request: NextRequest) {
)
}
const user = users.find(u => u.email === email)
const user = await db.getUserByEmail(email)
if (!user) {
return NextResponse.json(

View File

@ -1,9 +1,10 @@
import { NextRequest, NextResponse } from 'next/server'
import bcrypt from 'bcryptjs'
import { users, clients } from '../../../../lib/database'
import { getDatabase } from '../../../../lib/database/index'
export async function POST(request: NextRequest) {
try {
const db = await getDatabase()
const { email, password, firstName, lastName, phone } = await request.json()
if (!email || !password || !firstName || !lastName) {
@ -13,7 +14,7 @@ export async function POST(request: NextRequest) {
)
}
const existingUser = users.find(u => u.email === email)
const existingUser = await db.getUserByEmail(email)
if (existingUser) {
return NextResponse.json(
@ -23,31 +24,22 @@ export async function POST(request: NextRequest) {
}
const hashedPassword = await bcrypt.hash(password, 10)
const userId = Math.random().toString(36).substr(2, 9)
const newUser = {
id: userId,
const newUser = await db.createUser({
email,
firstName,
lastName,
password: hashedPassword,
phone,
role: 'client',
createdAt: new Date(),
updatedAt: new Date(),
}
role: 'client'
})
users.push(newUser)
const newClient = {
id: Math.random().toString(36).substr(2, 9),
userId,
const newClient = await db.createClient({
userId: newUser.id,
membershipType: 'basic',
membershipStatus: 'active',
joinDate: new Date(),
}
clients.push(newClient)
joinDate: new Date()
})
const { password: _, ...userWithoutPassword } = newUser
@ -68,6 +60,16 @@ export async function POST(request: NextRequest) {
}
export async function GET() {
const usersWithoutPassword = users.map(({ password: _, ...user }) => user)
return NextResponse.json({ users: usersWithoutPassword })
try {
const db = await getDatabase()
const allUsers = await db.getAllUsers()
const usersWithoutPassword = allUsers.map(({ password: _, ...user }) => user)
return NextResponse.json({ users: usersWithoutPassword })
} catch (error) {
console.error('Get users error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

View File

@ -1,9 +1,10 @@
import { NextRequest, NextResponse } from 'next/server'
import { users, fitnessProfiles } from '../../../../lib/database'
import { getDatabase } from '../../../../lib/database/index'
export async function POST(request: NextRequest) {
try {
const profileData: FitnessProfile = await request.json()
const db = await getDatabase()
const profileData = await request.json()
if (!profileData.userId || !profileData.height || !profileData.weight || !profileData.age) {
return NextResponse.json(
@ -12,21 +13,29 @@ export async function POST(request: NextRequest) {
)
}
// For demo purposes, we'll allow profile creation without strict user validation
// In production, you'd validate against a persistent database
console.log('Creating fitness profile for user ID:', profileData.userId)
// Check if user exists
const user = await db.getUserById(profileData.userId)
if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
)
}
const existingProfile = fitnessProfiles.find(p => p.userId === profileData.userId)
// Check if profile already exists
const existingProfile = await db.getFitnessProfileByUserId(profileData.userId)
let profile
if (existingProfile) {
Object.assign(existingProfile, profileData)
profile = await db.updateFitnessProfile(profileData.userId, profileData)
} else {
fitnessProfiles.push(profileData)
profile = await db.createFitnessProfile(profileData)
}
return NextResponse.json(
{
message: 'Fitness profile saved successfully',
profile: profileData
profile
},
{ status: 201 }
)
@ -41,11 +50,12 @@ export async function POST(request: NextRequest) {
export async function GET(request: NextRequest) {
try {
const db = await getDatabase()
const { searchParams } = new URL(request.url)
const userId = searchParams.get('userId')
if (userId) {
const profile = fitnessProfiles.find(p => p.userId === userId)
const profile = await db.getFitnessProfileByUserId(userId)
if (!profile) {
return NextResponse.json(
{ error: 'Profile not found' },
@ -55,7 +65,8 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ profile })
}
return NextResponse.json({ profiles: fitnessProfiles })
const profiles = await db.getAllFitnessProfiles()
return NextResponse.json({ profiles })
} catch (error) {
console.error('Get fitness profiles error:', error)
return NextResponse.json(

View File

@ -1,21 +1,25 @@
import { NextRequest, NextResponse } from 'next/server'
import { users, clients } from '../../../lib/database'
import { getDatabase } from '../../../lib/database/index'
export async function GET(request: NextRequest) {
try {
const db = await getDatabase()
const { searchParams } = new URL(request.url)
const role = searchParams.get('role')
let filteredUsers = users
let users = await db.getAllUsers()
if (role) {
filteredUsers = users.filter(user => user.role === role)
users = users.filter(user => user.role === role)
}
const usersWithClients = filteredUsers.map(({ password: _, ...user }) => {
const client = clients.find(c => c.userId === user.id)
return { ...user, client }
})
const usersWithClients = await Promise.all(
users.map(async (user) => {
const { password: _, ...userWithoutPassword } = user
const client = await db.getClientByUserId(user.id)
return { ...userWithoutPassword, client }
})
)
return NextResponse.json({ users: usersWithClients })
} catch (error) {

View File

@ -0,0 +1,87 @@
import { IDatabase, DatabaseConfig } from './types'
import { SQLiteDatabase } from './sqlite'
// Database factory - creates appropriate database instance based on config
export class DatabaseFactory {
private static instance: IDatabase | null = null
private static config: DatabaseConfig | null = null
static async initialize(config: DatabaseConfig): Promise<IDatabase> {
if (this.instance && this.config && this.isSameConfig(config)) {
return this.instance
}
// Close existing connection if different config
if (this.instance) {
await this.instance.disconnect()
this.instance = null
}
// Create new database instance based on type
switch (config.type) {
case 'sqlite':
this.instance = new SQLiteDatabase(config)
break
case 'postgresql':
// TODO: Implement PostgreSQLDatabase
throw new Error('PostgreSQL implementation not yet available')
case 'mysql':
// TODO: Implement MySQLDatabase
throw new Error('MySQL implementation not yet available')
case 'mongodb':
// TODO: Implement MongoDBDatabase
throw new Error('MongoDB implementation not yet available')
default:
throw new Error(`Unsupported database type: ${config.type}`)
}
await this.instance.connect()
this.config = config
return this.instance
}
static getInstance(): IDatabase {
if (!this.instance) {
throw new Error('Database not initialized. Call initialize() first.')
}
return this.instance
}
static async disconnect(): Promise<void> {
if (this.instance) {
await this.instance.disconnect()
this.instance = null
this.config = null
}
}
private static isSameConfig(config: DatabaseConfig): boolean {
if (!this.config) return false
return (
this.config.type === config.type &&
JSON.stringify(this.config.connection) === JSON.stringify(config.connection)
)
}
}
// Default configuration
export const defaultDatabaseConfig: DatabaseConfig = {
type: 'sqlite',
connection: {
filename: './data/fitai.db'
},
options: {
logging: process.env.NODE_ENV === 'development'
}
}
// Convenience function to get database instance
export async function getDatabase(): Promise<IDatabase> {
return await DatabaseFactory.initialize(defaultDatabaseConfig)
}

View File

@ -0,0 +1,369 @@
import Database from 'better-sqlite3'
import path from 'path'
import fs from 'fs'
import { IDatabase, User, Client, FitnessProfile, DatabaseConfig } from './types'
export class SQLiteDatabase implements IDatabase {
private db: Database.Database | null = null
private config: DatabaseConfig
constructor(config: DatabaseConfig) {
this.config = config
}
async connect(): Promise<void> {
try {
const dbPath = this.config.connection.filename || path.join(process.cwd(), 'data', 'fitai.db')
// Ensure data directory exists
const dataDir = path.dirname(dbPath)
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true })
}
this.db = new Database(dbPath)
// Enable foreign keys
this.db.exec('PRAGMA foreign_keys = ON')
// Create tables
await this.createTables()
if (this.config.options?.logging) {
console.log('SQLite database connected successfully at:', dbPath)
}
} catch (error) {
console.error('Failed to connect to SQLite database:', error)
throw error
}
}
async disconnect(): Promise<void> {
if (this.db) {
this.db.close()
this.db = null
if (this.config.options?.logging) {
console.log('SQLite database disconnected')
}
}
}
private async createTables(): Promise<void> {
if (!this.db) throw new Error('Database not connected')
// Users table
this.db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
firstName TEXT NOT NULL,
lastName TEXT NOT NULL,
password TEXT NOT NULL,
phone TEXT,
role TEXT NOT NULL CHECK (role IN ('admin', 'client')),
createdAt DATETIME NOT NULL,
updatedAt DATETIME NOT NULL
)
`)
// Clients table
this.db.exec(`
CREATE TABLE IF NOT EXISTS clients (
id TEXT PRIMARY KEY,
userId TEXT NOT NULL,
membershipType TEXT NOT NULL CHECK (membershipType IN ('basic', 'premium', 'vip')),
membershipStatus TEXT NOT NULL CHECK (membershipStatus IN ('active', 'inactive', 'expired')),
joinDate DATETIME NOT NULL,
FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE
)
`)
// Fitness profiles table
this.db.exec(`
CREATE TABLE IF NOT EXISTS fitness_profiles (
userId TEXT PRIMARY KEY,
height TEXT NOT NULL,
weight TEXT NOT NULL,
age TEXT NOT NULL,
gender TEXT NOT NULL CHECK (gender IN ('male', 'female', 'other')),
activityLevel TEXT NOT NULL CHECK (activityLevel IN ('sedentary', 'light', 'moderate', 'active', 'very_active')),
fitnessGoals TEXT NOT NULL, -- JSON array
exerciseHabits TEXT,
dietHabits TEXT,
medicalConditions TEXT,
createdAt DATETIME NOT NULL,
updatedAt DATETIME NOT NULL,
FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE
)
`)
// Create indexes for better performance
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_clients_userId ON clients(userId);
CREATE INDEX IF NOT EXISTS idx_fitness_profiles_userId ON fitness_profiles(userId);
`)
}
// User operations
async createUser(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User> {
if (!this.db) throw new Error('Database not connected')
const id = Math.random().toString(36).substr(2, 9)
const now = new Date()
const user: User = {
id,
...userData,
createdAt: now,
updatedAt: now
}
const stmt = this.db.prepare(
`INSERT INTO users (id, email, firstName, lastName, password, phone, role, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
stmt.run(
user.id, user.email, user.firstName, user.lastName, user.password,
user.phone, user.role, user.createdAt.toISOString(), user.updatedAt.toISOString()
)
return user
}
async getUserById(id: string): Promise<User | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM users WHERE id = ?')
const row = stmt.get(id)
return row ? this.mapRowToUser(row) : null
}
async getUserByEmail(email: string): Promise<User | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM users WHERE email = ?')
const row = stmt.get(email)
return row ? this.mapRowToUser(row) : null
}
async getAllUsers(): Promise<User[]> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM users ORDER BY createdAt DESC')
const rows = stmt.all()
return rows.map(row => this.mapRowToUser(row))
}
async updateUser(id: string, updates: Partial<User>): Promise<User | null> {
if (!this.db) throw new Error('Database not connected')
const fields = Object.keys(updates).filter(key => key !== 'id')
if (fields.length === 0) return this.getUserById(id)
const setClause = fields.map(field => `${field} = ?`).join(', ')
const values = fields.map(field => (updates as any)[field])
values.push(new Date().toISOString()) // updatedAt
values.push(id)
const stmt = this.db.prepare(`UPDATE users SET ${setClause}, updatedAt = ? WHERE id = ?`)
stmt.run(values)
return this.getUserById(id)
}
async deleteUser(id: string): Promise<boolean> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('DELETE FROM users WHERE id = ?')
const result = stmt.run(id)
return (result.changes || 0) > 0
}
// Client operations
async createClient(clientData: Omit<Client, 'id'>): Promise<Client> {
if (!this.db) throw new Error('Database not connected')
const id = Math.random().toString(36).substr(2, 9)
const client: Client = { id, ...clientData }
const stmt = this.db.prepare(
`INSERT INTO clients (id, userId, membershipType, membershipStatus, joinDate)
VALUES (?, ?, ?, ?, ?)`
)
stmt.run(
client.id, client.userId, client.membershipType,
client.membershipStatus, client.joinDate.toISOString()
)
return client
}
async getClientById(id: string): Promise<Client | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM clients WHERE id = ?')
const row = stmt.get(id)
return row ? this.mapRowToClient(row) : null
}
async getClientByUserId(userId: string): Promise<Client | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM clients WHERE userId = ?')
const row = stmt.get(userId)
return row ? this.mapRowToClient(row) : null
}
async getAllClients(): Promise<Client[]> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM clients ORDER BY joinDate DESC')
const rows = stmt.all()
return rows.map(row => this.mapRowToClient(row))
}
async updateClient(id: string, updates: Partial<Client>): Promise<Client | null> {
if (!this.db) throw new Error('Database not connected')
const fields = Object.keys(updates).filter(key => key !== 'id')
if (fields.length === 0) return this.getClientById(id)
const setClause = fields.map(field => `${field} = ?`).join(', ')
const values = fields.map(field => (updates as any)[field])
values.push(id)
const stmt = this.db.prepare(`UPDATE clients SET ${setClause} WHERE id = ?`)
stmt.run(values)
return this.getClientById(id)
}
async deleteClient(id: string): Promise<boolean> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('DELETE FROM clients WHERE id = ?')
const result = stmt.run(id)
return (result.changes || 0) > 0
}
// Fitness Profile operations
async createFitnessProfile(profileData: Omit<FitnessProfile, 'createdAt' | 'updatedAt'>): Promise<FitnessProfile> {
if (!this.db) throw new Error('Database not connected')
const now = new Date()
const profile: FitnessProfile = {
...profileData,
createdAt: now,
updatedAt: now
}
const stmt = this.db.prepare(
`INSERT INTO fitness_profiles
(userId, height, weight, age, gender, activityLevel, fitnessGoals,
exerciseHabits, dietHabits, medicalConditions, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
stmt.run(
profile.userId, profile.height, profile.weight, profile.age, profile.gender,
profile.activityLevel, JSON.stringify(profile.fitnessGoals), profile.exerciseHabits,
profile.dietHabits, profile.medicalConditions, profile.createdAt.toISOString(),
profile.updatedAt.toISOString()
)
return profile
}
async getFitnessProfileByUserId(userId: string): Promise<FitnessProfile | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM fitness_profiles WHERE userId = ?')
const row = stmt.get(userId)
return row ? this.mapRowToFitnessProfile(row) : null
}
async getAllFitnessProfiles(): Promise<FitnessProfile[]> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM fitness_profiles ORDER BY createdAt DESC')
const rows = stmt.all()
return rows.map(row => this.mapRowToFitnessProfile(row))
}
async updateFitnessProfile(userId: string, updates: Partial<FitnessProfile>): Promise<FitnessProfile | null> {
if (!this.db) throw new Error('Database not connected')
const fields = Object.keys(updates).filter(key => key !== 'userId' && key !== 'createdAt')
if (fields.length === 0) return this.getFitnessProfileByUserId(userId)
const setClause = fields.map(field => `${field} = ?`).join(', ')
const values = fields.map(field => {
const value = (updates as any)[field]
return field === 'fitnessGoals' ? JSON.stringify(value) : value
})
values.push(new Date().toISOString()) // updatedAt
values.push(userId)
const stmt = this.db.prepare(`UPDATE fitness_profiles SET ${setClause}, updatedAt = ? WHERE userId = ?`)
stmt.run(values)
return this.getFitnessProfileByUserId(userId)
}
async deleteFitnessProfile(userId: string): Promise<boolean> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('DELETE FROM fitness_profiles WHERE userId = ?')
const result = stmt.run(userId)
return (result.changes || 0) > 0
}
// Helper methods to map database rows to entities
private mapRowToUser(row: any): User {
return {
id: row.id,
email: row.email,
firstName: row.firstName,
lastName: row.lastName,
password: row.password,
phone: row.phone,
role: row.role,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt)
}
}
private mapRowToClient(row: any): Client {
return {
id: row.id,
userId: row.userId,
membershipType: row.membershipType,
membershipStatus: row.membershipStatus,
joinDate: new Date(row.joinDate)
}
}
private mapRowToFitnessProfile(row: any): FitnessProfile {
return {
userId: row.userId,
height: row.height,
weight: row.weight,
age: row.age,
gender: row.gender,
activityLevel: row.activityLevel,
fitnessGoals: JSON.parse(row.fitnessGoals || '[]'),
exerciseHabits: row.exerciseHabits,
dietHabits: row.dietHabits,
medicalConditions: row.medicalConditions,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt)
}
}
}

View File

@ -0,0 +1,83 @@
// Database Entity Types
export interface User {
id: string
email: string
firstName: string
lastName: string
password: string
phone?: string
role: 'admin' | 'client'
createdAt: Date
updatedAt: Date
}
export interface Client {
id: string
userId: string
membershipType: 'basic' | 'premium' | 'vip'
membershipStatus: 'active' | 'inactive' | 'expired'
joinDate: Date
}
export interface FitnessProfile {
userId: string
height: string
weight: string
age: string
gender: 'male' | 'female' | 'other'
activityLevel: 'sedentary' | 'light' | 'moderate' | 'active' | 'very_active'
fitnessGoals: string[]
exerciseHabits: string
dietHabits: string
medicalConditions: string
createdAt: Date
updatedAt: Date
}
// Database Interface - allows us to swap implementations
export interface IDatabase {
// Connection management
connect(): Promise<void>
disconnect(): Promise<void>
// User operations
createUser(user: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User>
getUserById(id: string): Promise<User | null>
getUserByEmail(email: string): Promise<User | null>
getAllUsers(): Promise<User[]>
updateUser(id: string, updates: Partial<User>): Promise<User | null>
deleteUser(id: string): Promise<boolean>
// Client operations
createClient(client: Omit<Client, 'id'>): Promise<Client>
getClientById(id: string): Promise<Client | null>
getClientByUserId(userId: string): Promise<Client | null>
getAllClients(): Promise<Client[]>
updateClient(id: string, updates: Partial<Client>): Promise<Client | null>
deleteClient(id: string): Promise<boolean>
// Fitness Profile operations
createFitnessProfile(profile: Omit<FitnessProfile, 'createdAt' | 'updatedAt'>): Promise<FitnessProfile>
getFitnessProfileByUserId(userId: string): Promise<FitnessProfile | null>
getAllFitnessProfiles(): Promise<FitnessProfile[]>
updateFitnessProfile(userId: string, updates: Partial<FitnessProfile>): Promise<FitnessProfile | null>
deleteFitnessProfile(userId: string): Promise<boolean>
}
// Database configuration
export interface DatabaseConfig {
type: 'sqlite' | 'postgresql' | 'mysql' | 'mongodb'
connection: {
filename?: string // for SQLite
host?: string // for SQL databases
port?: number
database?: string
username?: string
password?: string
}
options?: {
logging?: boolean
poolSize?: number
timeout?: number
}
}

29
apps/admin/test-db.js Normal file
View File

@ -0,0 +1,29 @@
import { getDatabase } from './src/lib/database/index.js'
async function testDatabase() {
try {
console.log('Testing database connection...')
const db = await getDatabase()
console.log('Creating test user...')
const user = await db.createUser({
email: 'test@example.com',
firstName: 'Test',
lastName: 'User',
password: 'hashedpassword',
role: 'client'
})
console.log('Created user:', user)
console.log('Getting user by email...')
const foundUser = await db.getUserByEmail('test@example.com')
console.log('Found user:', foundUser)
console.log('Database test completed successfully!')
} catch (error) {
console.error('Database test failed:', error)
}
}
testDatabase()

View File

@ -1,5 +1,5 @@
export const API_BASE_URL = __DEV__
? 'http://192.168.0.100:3000'
? 'http://192.168.1.24:3000'
: 'https://your-production-url.com'
export const API_ENDPOINTS = {

440
package-lock.json generated
View File

@ -8,6 +8,8 @@
"name": "fitai",
"version": "1.0.0",
"dependencies": {
"@types/better-sqlite3": "^7.6.13",
"better-sqlite3": "^12.4.1",
"next": "^16.0.1"
},
"devDependencies": {
@ -839,6 +841,15 @@
"tslib": "^2.8.0"
}
},
"node_modules/@types/better-sqlite3": {
"version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@ -846,6 +857,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.10.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
"integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
@ -1148,6 +1168,60 @@
"dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/better-sqlite3": {
"version": "12.4.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.1.tgz",
"integrity": "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
},
"engines": {
"node": "20.x || 22.x || 23.x || 24.x"
}
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@ -1171,6 +1245,30 @@
"node": ">=8"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -1231,6 +1329,12 @@
"node": ">=8"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@ -1357,6 +1461,30 @@
}
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -1369,7 +1497,6 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
@ -1407,6 +1534,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@ -1605,6 +1741,15 @@
"node": ">=0.10.0"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -1679,6 +1824,12 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT"
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -1731,6 +1882,12 @@
"dev": true,
"license": "ISC"
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -1748,6 +1905,12 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -1861,6 +2024,26 @@
"node": ">=8"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -1914,7 +2097,12 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/is-extglob": {
@ -2089,6 +2277,18 @@
"node": ">=8.6"
}
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
@ -2105,6 +2305,21 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -2130,6 +2345,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@ -2189,11 +2410,22 @@
}
}
},
"node_modules/node-abi": {
"version": "3.80.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.80.0.tgz",
"integrity": "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@ -2349,6 +2581,32 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -2375,6 +2633,16 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -2406,6 +2674,30 @@
],
"license": "MIT"
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/rc/node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react": {
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
@ -2429,6 +2721,20 @@
"react": "^19.2.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -2511,6 +2817,26 @@
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@ -2522,7 +2848,6 @@
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"devOptional": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -2612,6 +2937,51 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -2637,6 +3007,15 @@
"integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
"dev": true
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@ -2717,6 +3096,34 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -2766,6 +3173,18 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -2806,6 +3225,12 @@
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@ -2816,6 +3241,12 @@
"punycode": "^2.1.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -2864,7 +3295,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/y18n": {

View File

@ -33,6 +33,8 @@
"npm": ">=9.0.0"
},
"dependencies": {
"@types/better-sqlite3": "^7.6.13",
"better-sqlite3": "^12.4.1",
"next": "^16.0.1"
}
}