feat: set up Drizzle ORM with SQLite database
Phase 1.1 Complete: Database & Auth Infrastructure - Configure Drizzle with SQLite and better-sqlite3 - Create schema for users, players, and user_stats tables - Set up database client with foreign keys and WAL mode enabled - Add migration runner and package.json scripts - Generate initial migration (0000_third_carnage.sql) Database Features: - Users table with guestId for guest sessions - Players table with userId FK (cascade delete) - UserStats table with userId FK (cascade delete) - Indexes on foreign keys for performance - Type-safe schema with Drizzle ORM Testing: - 20 unit + e2e tests all passing - Schema validation tests - Migration idempotency tests - Foreign key constraint tests - Cascade delete tests Scripts added: - pnpm db:generate - Generate migration from schema - pnpm db:migrate - Run pending migrations - pnpm db:push - Push schema directly (dev) - pnpm db:studio - Visual DB browser - pnpm db:drop - Drop migration (dev) User tests verified: ✅ Migration runs successfully ✅ Database tables created with correct schema ✅ Migration is idempotent (can run multiple times)
This commit is contained in:
parent
dd0df8c274
commit
5d5afd4e68
|
|
@ -0,0 +1,12 @@
|
|||
import type { Config } from 'drizzle-kit'
|
||||
|
||||
export default {
|
||||
schema: './src/db/schema/index.ts',
|
||||
out: './drizzle',
|
||||
dialect: 'sqlite',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL || './data/sqlite.db',
|
||||
},
|
||||
verbose: true,
|
||||
strict: true,
|
||||
} satisfies Config
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
CREATE TABLE `users` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`guest_id` text NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
`upgraded_at` integer,
|
||||
`email` text,
|
||||
`name` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_guest_id_unique` ON `users` (`guest_id`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);--> statement-breakpoint
|
||||
CREATE TABLE `players` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`emoji` text NOT NULL,
|
||||
`color` text NOT NULL,
|
||||
`is_active` integer DEFAULT false NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `players_user_id_idx` ON `players` (`user_id`);--> statement-breakpoint
|
||||
CREATE TABLE `user_stats` (
|
||||
`user_id` text PRIMARY KEY NOT NULL,
|
||||
`games_played` integer DEFAULT 0 NOT NULL,
|
||||
`total_wins` integer DEFAULT 0 NOT NULL,
|
||||
`favorite_game_type` text,
|
||||
`best_time` integer,
|
||||
`highest_accuracy` real DEFAULT 0 NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "949424bf-1933-497c-af2d-cab6ee81083d",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"guest_id": {
|
||||
"name": "guest_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"upgraded_at": {
|
||||
"name": "upgraded_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_guest_id_unique": {
|
||||
"name": "users_guest_id_unique",
|
||||
"columns": [
|
||||
"guest_id"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"players": {
|
||||
"name": "players",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"emoji": {
|
||||
"name": "emoji",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"color": {
|
||||
"name": "color",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"is_active": {
|
||||
"name": "is_active",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"players_user_id_idx": {
|
||||
"name": "players_user_id_idx",
|
||||
"columns": [
|
||||
"user_id"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"players_user_id_users_id_fk": {
|
||||
"name": "players_user_id_users_id_fk",
|
||||
"tableFrom": "players",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_stats": {
|
||||
"name": "user_stats",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"games_played": {
|
||||
"name": "games_played",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"total_wins": {
|
||||
"name": "total_wins",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"favorite_game_type": {
|
||||
"name": "favorite_game_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"best_time": {
|
||||
"name": "best_time",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"highest_accuracy": {
|
||||
"name": "highest_accuracy",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_stats_user_id_users_id_fk": {
|
||||
"name": "user_stats_user_id_users_id_fk",
|
||||
"tableFrom": "user_stats",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1759701472375,
|
||||
"tag": "0000_third_carnage",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -12,7 +12,12 @@
|
|||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf .next",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"build-storybook": "storybook build",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "tsx src/db/migrate.ts",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:drop": "drizzle-kit drop"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
|
@ -24,6 +29,7 @@
|
|||
"@myriaddreamin/typst.ts": "0.6.1-rc3",
|
||||
"@number-flow/react": "^0.5.10",
|
||||
"@pandacss/dev": "^0.20.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
|
|
@ -47,11 +53,15 @@
|
|||
"@tanstack/react-form": "^0.19.0",
|
||||
"@tanstack/react-query": "^5.90.2",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"emojibase-data": "^16.0.3",
|
||||
"jose": "^6.1.0",
|
||||
"lucide-react": "^0.294.0",
|
||||
"make-plural": "^7.4.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"next": "^14.2.32",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"python-bridge": "^1.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -64,17 +74,20 @@
|
|||
"@storybook/nextjs": "^9.1.7",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@vitejs/plugin-react": "^5.0.2",
|
||||
"concurrently": "^8.0.0",
|
||||
"drizzle-kit": "^0.31.5",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-config-next": "^14.0.0",
|
||||
"eslint-plugin-storybook": "^9.1.7",
|
||||
"happy-dom": "^18.0.1",
|
||||
"jsdom": "^27.0.0",
|
||||
"storybook": "^9.1.7",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^1.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import Database from 'better-sqlite3'
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
||||
|
||||
describe('Database connection', () => {
|
||||
it('connects to in-memory database', () => {
|
||||
const sqlite = new Database(':memory:')
|
||||
const db = drizzle(sqlite)
|
||||
|
||||
expect(db).toBeDefined()
|
||||
})
|
||||
|
||||
it('enables foreign keys', () => {
|
||||
const sqlite = new Database(':memory:')
|
||||
sqlite.pragma('foreign_keys = ON')
|
||||
|
||||
const result = sqlite.pragma('foreign_keys', { simple: true })
|
||||
|
||||
expect(result).toBe(1)
|
||||
})
|
||||
|
||||
it('can run simple queries', () => {
|
||||
const sqlite = new Database(':memory:')
|
||||
const db = drizzle(sqlite)
|
||||
|
||||
// Create simple table
|
||||
sqlite.exec(`
|
||||
CREATE TABLE test (
|
||||
id TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)
|
||||
`)
|
||||
|
||||
// Insert
|
||||
sqlite.prepare("INSERT INTO test (id, value) VALUES ('1', 'hello')").run()
|
||||
|
||||
// Query
|
||||
const result = sqlite.prepare("SELECT * FROM test WHERE id = '1'").get() as {
|
||||
id: string
|
||||
value: string
|
||||
}
|
||||
|
||||
expect(result).toEqual({ id: '1', value: 'hello' })
|
||||
})
|
||||
|
||||
it('supports WAL mode (file-based DB)', () => {
|
||||
// WAL mode doesn't work with in-memory databases
|
||||
// In-memory databases always use 'memory' journal mode
|
||||
// This test verifies the pragma can be set (will use WAL for file DBs)
|
||||
const sqlite = new Database(':memory:')
|
||||
|
||||
// Try to set WAL mode (will return 'memory' for in-memory DB)
|
||||
sqlite.pragma('journal_mode = WAL')
|
||||
const result = sqlite.pragma('journal_mode', { simple: true })
|
||||
|
||||
// In-memory databases can't use WAL, so expect 'memory'
|
||||
expect(result).toBe('memory')
|
||||
|
||||
// The actual app uses a file-based DB which will support WAL
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import Database from 'better-sqlite3'
|
||||
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
||||
|
||||
describe('Migrations E2E', () => {
|
||||
let sqlite: Database.Database
|
||||
|
||||
beforeEach(() => {
|
||||
// Fresh in-memory DB for each test
|
||||
sqlite = new Database(':memory:')
|
||||
sqlite.pragma('foreign_keys = ON')
|
||||
})
|
||||
|
||||
it('applies all migrations successfully', () => {
|
||||
const db = drizzle(sqlite)
|
||||
|
||||
// Should not throw
|
||||
expect(() => {
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it('creates all expected tables', () => {
|
||||
const db = drizzle(sqlite)
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
const tables = sqlite
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table'")
|
||||
.all() as Array<{ name: string }>
|
||||
|
||||
const tableNames = tables.map((t) => t.name)
|
||||
|
||||
expect(tableNames).toContain('users')
|
||||
expect(tableNames).toContain('players')
|
||||
expect(tableNames).toContain('user_stats')
|
||||
})
|
||||
|
||||
it('creates unique indexes on users', () => {
|
||||
const db = drizzle(sqlite)
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
const indexes = sqlite
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='users'")
|
||||
.all() as Array<{ name: string }>
|
||||
|
||||
const indexNames = indexes.map((i) => i.name)
|
||||
|
||||
expect(indexNames).toContain('users_guest_id_unique')
|
||||
expect(indexNames).toContain('users_email_unique')
|
||||
})
|
||||
|
||||
it('creates index on players userId', () => {
|
||||
const db = drizzle(sqlite)
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
const indexes = sqlite
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='players'")
|
||||
.all() as Array<{ name: string }>
|
||||
|
||||
const indexNames = indexes.map((i) => i.name)
|
||||
|
||||
expect(indexNames).toContain('players_user_id_idx')
|
||||
})
|
||||
|
||||
it('enforces foreign key constraints', () => {
|
||||
const db = drizzle(sqlite)
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
// Try to insert player with non-existent userId
|
||||
expect(() => {
|
||||
sqlite
|
||||
.prepare(
|
||||
"INSERT INTO players (id, user_id, name, emoji, color, is_active, created_at) VALUES ('p1', 'nonexistent', 'Test', '😀', '#000', 0, 0)"
|
||||
)
|
||||
.run()
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
it('cascades deletes from users to players', () => {
|
||||
const db = drizzle(sqlite)
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
// Insert user
|
||||
sqlite
|
||||
.prepare("INSERT INTO users (id, guest_id, created_at) VALUES ('u1', 'g1', 0)")
|
||||
.run()
|
||||
|
||||
// Insert player for that user
|
||||
sqlite
|
||||
.prepare(
|
||||
"INSERT INTO players (id, user_id, name, emoji, color, is_active, created_at) VALUES ('p1', 'u1', 'Test', '😀', '#000', 0, 0)"
|
||||
)
|
||||
.run()
|
||||
|
||||
// Verify player exists
|
||||
const playerBefore = sqlite.prepare("SELECT * FROM players WHERE id = 'p1'").get()
|
||||
expect(playerBefore).toBeDefined()
|
||||
|
||||
// Delete user
|
||||
sqlite.prepare("DELETE FROM users WHERE id = 'u1'").run()
|
||||
|
||||
// Verify player was cascade deleted
|
||||
const playerAfter = sqlite.prepare("SELECT * FROM players WHERE id = 'p1'").get()
|
||||
expect(playerAfter).toBeUndefined()
|
||||
})
|
||||
|
||||
it('cascades deletes from users to user_stats', () => {
|
||||
const db = drizzle(sqlite)
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
// Insert user
|
||||
sqlite
|
||||
.prepare("INSERT INTO users (id, guest_id, created_at) VALUES ('u1', 'g1', 0)")
|
||||
.run()
|
||||
|
||||
// Insert stats for that user
|
||||
sqlite
|
||||
.prepare(
|
||||
"INSERT INTO user_stats (user_id, games_played, total_wins, highest_accuracy) VALUES ('u1', 5, 2, 0.8)"
|
||||
)
|
||||
.run()
|
||||
|
||||
// Verify stats exist
|
||||
const statsBefore = sqlite.prepare("SELECT * FROM user_stats WHERE user_id = 'u1'").get()
|
||||
expect(statsBefore).toBeDefined()
|
||||
|
||||
// Delete user
|
||||
sqlite.prepare("DELETE FROM users WHERE id = 'u1'").run()
|
||||
|
||||
// Verify stats were cascade deleted
|
||||
const statsAfter = sqlite.prepare("SELECT * FROM user_stats WHERE user_id = 'u1'").get()
|
||||
expect(statsAfter).toBeUndefined()
|
||||
})
|
||||
|
||||
it('is idempotent (can run migrations twice)', () => {
|
||||
const db = drizzle(sqlite)
|
||||
|
||||
// Run migrations first time
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
// Run migrations second time (should not throw)
|
||||
expect(() => {
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { users, players, userStats } from '../schema'
|
||||
|
||||
describe('Schema validation', () => {
|
||||
describe('users table', () => {
|
||||
it('has correct structure', () => {
|
||||
expect(users.id).toBeDefined()
|
||||
expect(users.guestId).toBeDefined()
|
||||
expect(users.createdAt).toBeDefined()
|
||||
expect(users.upgradedAt).toBeDefined()
|
||||
expect(users.email).toBeDefined()
|
||||
expect(users.name).toBeDefined()
|
||||
})
|
||||
|
||||
it('has unique constraints on guestId and email', () => {
|
||||
expect(users.guestId.notNull).toBe(true)
|
||||
expect(users.email.notNull).toBe(false) // nullable until upgrade
|
||||
})
|
||||
})
|
||||
|
||||
describe('players table', () => {
|
||||
it('has correct structure', () => {
|
||||
expect(players.id).toBeDefined()
|
||||
expect(players.userId).toBeDefined()
|
||||
expect(players.name).toBeDefined()
|
||||
expect(players.emoji).toBeDefined()
|
||||
expect(players.color).toBeDefined()
|
||||
expect(players.isActive).toBeDefined()
|
||||
expect(players.createdAt).toBeDefined()
|
||||
})
|
||||
|
||||
it('has foreign key to users', () => {
|
||||
const userIdColumn = players.userId
|
||||
expect(userIdColumn).toBeDefined()
|
||||
expect(userIdColumn.notNull).toBe(true)
|
||||
})
|
||||
|
||||
it('has required fields as not null', () => {
|
||||
expect(players.name.notNull).toBe(true)
|
||||
expect(players.emoji.notNull).toBe(true)
|
||||
expect(players.color.notNull).toBe(true)
|
||||
expect(players.isActive.notNull).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user_stats table', () => {
|
||||
it('has correct structure', () => {
|
||||
expect(userStats.userId).toBeDefined()
|
||||
expect(userStats.gamesPlayed).toBeDefined()
|
||||
expect(userStats.totalWins).toBeDefined()
|
||||
expect(userStats.favoriteGameType).toBeDefined()
|
||||
expect(userStats.bestTime).toBeDefined()
|
||||
expect(userStats.highestAccuracy).toBeDefined()
|
||||
})
|
||||
|
||||
it('has foreign key to users', () => {
|
||||
const userIdColumn = userStats.userId
|
||||
expect(userIdColumn).toBeDefined()
|
||||
})
|
||||
|
||||
it('has correct defaults', () => {
|
||||
expect(userStats.gamesPlayed.notNull).toBe(true)
|
||||
expect(userStats.totalWins.notNull).toBe(true)
|
||||
expect(userStats.highestAccuracy.notNull).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import Database from 'better-sqlite3'
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
||||
import * as schema from './schema'
|
||||
|
||||
/**
|
||||
* Database connection and client
|
||||
*
|
||||
* Creates a singleton SQLite connection with Drizzle ORM.
|
||||
* Enables foreign key constraints (required for cascading deletes).
|
||||
*/
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL || './data/sqlite.db'
|
||||
|
||||
const sqlite = new Database(databaseUrl)
|
||||
|
||||
// Enable foreign keys (SQLite requires explicit enable)
|
||||
sqlite.pragma('foreign_keys = ON')
|
||||
|
||||
// Enable WAL mode for better concurrency
|
||||
sqlite.pragma('journal_mode = WAL')
|
||||
|
||||
export const db = drizzle(sqlite, { schema })
|
||||
export { schema }
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
|
||||
import { db } from './index'
|
||||
|
||||
/**
|
||||
* Migration runner
|
||||
*
|
||||
* Runs all pending migrations in the drizzle/ folder.
|
||||
* Safe to run multiple times (migrations are idempotent).
|
||||
*
|
||||
* Usage: pnpm db:migrate
|
||||
*/
|
||||
|
||||
try {
|
||||
console.log('🔄 Running migrations...')
|
||||
|
||||
migrate(db, { migrationsFolder: './drizzle' })
|
||||
|
||||
console.log('✅ Migrations complete')
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error('❌ Migration failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Database schema exports
|
||||
*
|
||||
* This is the single source of truth for the database schema.
|
||||
* All tables, relations, and types are exported from here.
|
||||
*/
|
||||
|
||||
export * from './users'
|
||||
export * from './players'
|
||||
export * from './user-stats'
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { sqliteTable, text, integer, index } from 'drizzle-orm/sqlite-core'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { users } from './users'
|
||||
|
||||
/**
|
||||
* Players table - user-created player profiles for games
|
||||
*
|
||||
* Each user can have multiple players (for multi-player modes).
|
||||
* Players are scoped to a user and deleted when user is deleted.
|
||||
*/
|
||||
export const players = sqliteTable('players', {
|
||||
id: text('id').primaryKey().$defaultFn(() => createId()),
|
||||
|
||||
/** Foreign key to users table - cascades on delete */
|
||||
userId: text('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
|
||||
/** Player display name */
|
||||
name: text('name').notNull(),
|
||||
|
||||
/** Player emoji avatar */
|
||||
emoji: text('emoji').notNull(),
|
||||
|
||||
/** Player color (hex) for UI theming */
|
||||
color: text('color').notNull(),
|
||||
|
||||
/** Whether this player is currently active in games */
|
||||
isActive: integer('is_active', { mode: 'boolean' })
|
||||
.notNull()
|
||||
.default(false),
|
||||
|
||||
/** When this player was created */
|
||||
createdAt: integer('created_at', { mode: 'timestamp' })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
}, (table) => ({
|
||||
/** Index for fast lookups by userId */
|
||||
userIdIdx: index('players_user_id_idx').on(table.userId),
|
||||
}))
|
||||
|
||||
export type Player = typeof players.$inferSelect
|
||||
export type NewPlayer = typeof players.$inferInsert
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'
|
||||
import { users } from './users'
|
||||
|
||||
/**
|
||||
* User stats table - game statistics per user
|
||||
*
|
||||
* One-to-one with users table. Tracks aggregate game performance.
|
||||
* Deleted when user is deleted (cascade).
|
||||
*/
|
||||
export const userStats = sqliteTable('user_stats', {
|
||||
/** Primary key and foreign key to users table */
|
||||
userId: text('user_id')
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
|
||||
/** Total number of games played */
|
||||
gamesPlayed: integer('games_played').notNull().default(0),
|
||||
|
||||
/** Total number of games won */
|
||||
totalWins: integer('total_wins').notNull().default(0),
|
||||
|
||||
/** User's most-played game type */
|
||||
favoriteGameType: text('favorite_game_type', {
|
||||
enum: ['abacus-numeral', 'complement-pairs'],
|
||||
}),
|
||||
|
||||
/** Best completion time in milliseconds */
|
||||
bestTime: integer('best_time'),
|
||||
|
||||
/** Highest accuracy percentage (0.0 - 1.0) */
|
||||
highestAccuracy: real('highest_accuracy').notNull().default(0),
|
||||
})
|
||||
|
||||
export type UserStats = typeof userStats.$inferSelect
|
||||
export type NewUserStats = typeof userStats.$inferInsert
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
|
||||
/**
|
||||
* Users table - stores both guest and authenticated users
|
||||
*
|
||||
* Guest users are created automatically on first visit via middleware.
|
||||
* They can upgrade to full accounts later while preserving their data.
|
||||
*/
|
||||
export const users = sqliteTable('users', {
|
||||
id: text('id').primaryKey().$defaultFn(() => createId()),
|
||||
|
||||
/** Stable guest ID from HttpOnly cookie - unique per browser session */
|
||||
guestId: text('guest_id').notNull().unique(),
|
||||
|
||||
/** When this user record was created */
|
||||
createdAt: integer('created_at', { mode: 'timestamp' })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
|
||||
/** When guest upgraded to full account (null for guests) */
|
||||
upgradedAt: integer('upgraded_at', { mode: 'timestamp' }),
|
||||
|
||||
/** Email (only set after upgrade) */
|
||||
email: text('email').unique(),
|
||||
|
||||
/** Display name (only set after upgrade) */
|
||||
name: text('name'),
|
||||
})
|
||||
|
||||
export type User = typeof users.$inferSelect
|
||||
export type NewUser = typeof users.$inferInsert
|
||||
350
pnpm-lock.yaml
350
pnpm-lock.yaml
|
|
@ -67,6 +67,9 @@ importers:
|
|||
'@pandacss/dev':
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.1(jsdom@27.0.0)(typescript@5.0.2)
|
||||
'@paralleldrive/cuid2':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(@types/react-dom@18.2.0)(@types/react@18.2.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
|
|
@ -136,9 +139,18 @@ importers:
|
|||
'@types/jsdom':
|
||||
specifier: ^21.1.7
|
||||
version: 21.1.7
|
||||
better-sqlite3:
|
||||
specifier: ^12.4.1
|
||||
version: 12.4.1
|
||||
drizzle-orm:
|
||||
specifier: ^0.44.6
|
||||
version: 0.44.6(@types/better-sqlite3@7.6.13)(better-sqlite3@12.4.1)
|
||||
emojibase-data:
|
||||
specifier: ^16.0.3
|
||||
version: 16.0.3(emojibase@16.0.0)
|
||||
jose:
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0
|
||||
lucide-react:
|
||||
specifier: ^0.294.0
|
||||
version: 0.294.0(react@18.2.0)
|
||||
|
|
@ -151,6 +163,9 @@ importers:
|
|||
next:
|
||||
specifier: ^14.2.32
|
||||
version: 14.2.32(@babel/core@7.28.4)(@playwright/test@1.55.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
next-auth:
|
||||
specifier: 5.0.0-beta.29
|
||||
version: 5.0.0-beta.29(next@14.2.32)(react@18.2.0)
|
||||
python-bridge:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
|
|
@ -182,6 +197,9 @@ importers:
|
|||
'@testing-library/react':
|
||||
specifier: ^16.3.0
|
||||
version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@18.2.0)(@types/react@18.2.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.13
|
||||
version: 7.6.13
|
||||
'@types/node':
|
||||
specifier: ^20.0.0
|
||||
version: 20.0.0
|
||||
|
|
@ -197,6 +215,9 @@ importers:
|
|||
concurrently:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
drizzle-kit:
|
||||
specifier: ^0.31.5
|
||||
version: 0.31.5
|
||||
eslint:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
|
|
@ -215,6 +236,9 @@ importers:
|
|||
storybook:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7(@testing-library/dom@10.4.1)(prettier@3.0.0)(vite@5.0.0)
|
||||
tsx:
|
||||
specifier: ^4.20.5
|
||||
version: 4.20.5
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.2
|
||||
|
|
@ -481,6 +505,27 @@ packages:
|
|||
/@asamuzakjp/nwsapi@2.3.9:
|
||||
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
||||
|
||||
/@auth/core@0.40.0:
|
||||
resolution: {integrity: sha512-n53uJE0RH5SqZ7N1xZoMKekbHfQgjd0sAEyUbE+IYJnmuQkbvuZnXItCU7d+i7Fj8VGOgqvNO7Mw4YfBTlZeQw==}
|
||||
peerDependencies:
|
||||
'@simplewebauthn/browser': ^9.0.1
|
||||
'@simplewebauthn/server': ^9.0.2
|
||||
nodemailer: ^6.8.0
|
||||
peerDependenciesMeta:
|
||||
'@simplewebauthn/browser':
|
||||
optional: true
|
||||
'@simplewebauthn/server':
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@panva/hkdf': 1.2.1
|
||||
jose: 6.1.0
|
||||
oauth4webapi: 3.8.2
|
||||
preact: 10.24.3
|
||||
preact-render-to-string: 6.5.11(preact@10.24.3)
|
||||
dev: false
|
||||
|
||||
/@aw-web-design/x-default-browser@1.4.126:
|
||||
resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==}
|
||||
hasBin: true
|
||||
|
|
@ -1872,6 +1917,10 @@ packages:
|
|||
tslib: 2.8.1
|
||||
dev: false
|
||||
|
||||
/@drizzle-team/brocli@0.10.2:
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
dev: true
|
||||
|
||||
/@emnapi/core@1.5.0:
|
||||
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
|
||||
requiresBuild: true
|
||||
|
|
@ -1905,6 +1954,22 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/@esbuild-kit/core-utils@3.3.2:
|
||||
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
dependencies:
|
||||
esbuild: 0.18.20
|
||||
source-map-support: 0.5.21
|
||||
dev: true
|
||||
|
||||
/@esbuild-kit/esm-loader@2.6.5:
|
||||
resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
dependencies:
|
||||
'@esbuild-kit/core-utils': 3.3.2
|
||||
get-tsconfig: 4.10.1
|
||||
dev: true
|
||||
|
||||
/@esbuild/aix-ppc64@0.19.12:
|
||||
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -2950,6 +3015,11 @@ packages:
|
|||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@noble/hashes@1.8.0:
|
||||
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir@2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -3295,6 +3365,16 @@ packages:
|
|||
resolution: {integrity: sha512-GWlGqNdv/ILa9iEGks7xZL966yKhCPtLSolPrOnAE6K/lBb4djtpBOHDgQ1kFJ4yVSwCMGkHd0G/NSqdl0t94A==}
|
||||
dev: false
|
||||
|
||||
/@panva/hkdf@1.2.1:
|
||||
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
|
||||
dev: false
|
||||
|
||||
/@paralleldrive/cuid2@2.2.2:
|
||||
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
||||
dependencies:
|
||||
'@noble/hashes': 1.8.0
|
||||
dev: false
|
||||
|
||||
/@pkgjs/parseargs@0.11.0:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
|
@ -6540,6 +6620,11 @@ packages:
|
|||
'@babel/types': 7.28.4
|
||||
dev: true
|
||||
|
||||
/@types/better-sqlite3@7.6.13:
|
||||
resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==}
|
||||
dependencies:
|
||||
'@types/node': 20.0.0
|
||||
|
||||
/@types/body-parser@1.19.6:
|
||||
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
|
||||
dependencies:
|
||||
|
|
@ -8018,7 +8103,6 @@ packages:
|
|||
|
||||
/base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
dev: true
|
||||
|
||||
/baseline-browser-mapping@2.8.3:
|
||||
resolution: {integrity: sha512-mcE+Wr2CAhHNWxXN/DdTI+n4gsPc5QpXpWnyCQWiQYIYZX+ZMJ8juXZgjRa/0/YPJo/NSsgW15/YgmI4nbysYw==}
|
||||
|
|
@ -8035,6 +8119,15 @@ packages:
|
|||
open: 8.4.2
|
||||
dev: true
|
||||
|
||||
/better-sqlite3@12.4.1:
|
||||
resolution: {integrity: sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==}
|
||||
engines: {node: 20.x || 22.x || 23.x || 24.x}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
prebuild-install: 7.1.3
|
||||
dev: false
|
||||
|
||||
/bidi-js@1.0.3:
|
||||
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||
dependencies:
|
||||
|
|
@ -8053,13 +8146,18 @@ packages:
|
|||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/bindings@1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
dev: false
|
||||
|
||||
/bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/bluebird@3.7.2:
|
||||
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
|
||||
|
|
@ -8233,7 +8331,6 @@ packages:
|
|||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
dev: true
|
||||
|
||||
/buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
|
@ -8419,7 +8516,6 @@ packages:
|
|||
|
||||
/chownr@1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
dev: true
|
||||
|
||||
/chownr@2.0.0:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
|
|
@ -9066,6 +9162,13 @@ packages:
|
|||
resolution: {integrity: sha512-/OMUlsRLrSgHPOWCwembsFFTT4DY7Ts9GGlwK8v9yeLOyYZSPKIfn/1oOuV9UmpQ9CZi5JeyT8edunRoBOOl5g==}
|
||||
dev: false
|
||||
|
||||
/decompress-response@6.0.0:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
mimic-response: 3.1.0
|
||||
dev: false
|
||||
|
||||
/dedent@0.7.0:
|
||||
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
|
||||
dev: true
|
||||
|
|
@ -9109,7 +9212,6 @@ packages:
|
|||
/deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dev: true
|
||||
|
||||
/deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
|
@ -9211,6 +9313,11 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
|
||||
|
|
@ -9336,6 +9443,114 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/drizzle-kit@0.31.5:
|
||||
resolution: {integrity: sha512-+CHgPFzuoTQTt7cOYCV6MOw2w8vqEn/ap1yv4bpZOWL03u7rlVRQhUY0WYT3rHsgVTXwYQDZaSUJSQrMBUKuWg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@drizzle-team/brocli': 0.10.2
|
||||
'@esbuild-kit/esm-loader': 2.6.5
|
||||
esbuild: 0.25.9
|
||||
esbuild-register: 3.6.0(esbuild@0.25.9)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/drizzle-orm@0.44.6(@types/better-sqlite3@7.6.13)(better-sqlite3@12.4.1):
|
||||
resolution: {integrity: sha512-uy6uarrrEOc9K1u5/uhBFJbdF5VJ5xQ/Yzbecw3eAYOunv5FDeYkR2m8iitocdHBOHbvorviKOW5GVw0U1j4LQ==}
|
||||
peerDependencies:
|
||||
'@aws-sdk/client-rds-data': '>=3'
|
||||
'@cloudflare/workers-types': '>=4'
|
||||
'@electric-sql/pglite': '>=0.2.0'
|
||||
'@libsql/client': '>=0.10.0'
|
||||
'@libsql/client-wasm': '>=0.10.0'
|
||||
'@neondatabase/serverless': '>=0.10.0'
|
||||
'@op-engineering/op-sqlite': '>=2'
|
||||
'@opentelemetry/api': ^1.4.1
|
||||
'@planetscale/database': '>=1.13'
|
||||
'@prisma/client': '*'
|
||||
'@tidbcloud/serverless': '*'
|
||||
'@types/better-sqlite3': '*'
|
||||
'@types/pg': '*'
|
||||
'@types/sql.js': '*'
|
||||
'@upstash/redis': '>=1.34.7'
|
||||
'@vercel/postgres': '>=0.8.0'
|
||||
'@xata.io/client': '*'
|
||||
better-sqlite3: '>=7'
|
||||
bun-types: '*'
|
||||
expo-sqlite: '>=14.0.0'
|
||||
gel: '>=2'
|
||||
knex: '*'
|
||||
kysely: '*'
|
||||
mysql2: '>=2'
|
||||
pg: '>=8'
|
||||
postgres: '>=3'
|
||||
prisma: '*'
|
||||
sql.js: '>=1'
|
||||
sqlite3: '>=5'
|
||||
peerDependenciesMeta:
|
||||
'@aws-sdk/client-rds-data':
|
||||
optional: true
|
||||
'@cloudflare/workers-types':
|
||||
optional: true
|
||||
'@electric-sql/pglite':
|
||||
optional: true
|
||||
'@libsql/client':
|
||||
optional: true
|
||||
'@libsql/client-wasm':
|
||||
optional: true
|
||||
'@neondatabase/serverless':
|
||||
optional: true
|
||||
'@op-engineering/op-sqlite':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@planetscale/database':
|
||||
optional: true
|
||||
'@prisma/client':
|
||||
optional: true
|
||||
'@tidbcloud/serverless':
|
||||
optional: true
|
||||
'@types/better-sqlite3':
|
||||
optional: true
|
||||
'@types/pg':
|
||||
optional: true
|
||||
'@types/sql.js':
|
||||
optional: true
|
||||
'@upstash/redis':
|
||||
optional: true
|
||||
'@vercel/postgres':
|
||||
optional: true
|
||||
'@xata.io/client':
|
||||
optional: true
|
||||
better-sqlite3:
|
||||
optional: true
|
||||
bun-types:
|
||||
optional: true
|
||||
expo-sqlite:
|
||||
optional: true
|
||||
gel:
|
||||
optional: true
|
||||
knex:
|
||||
optional: true
|
||||
kysely:
|
||||
optional: true
|
||||
mysql2:
|
||||
optional: true
|
||||
pg:
|
||||
optional: true
|
||||
postgres:
|
||||
optional: true
|
||||
prisma:
|
||||
optional: true
|
||||
sql.js:
|
||||
optional: true
|
||||
sqlite3:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/better-sqlite3': 7.6.13
|
||||
better-sqlite3: 12.4.1
|
||||
dev: false
|
||||
|
||||
/dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -9431,7 +9646,6 @@ packages:
|
|||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
dev: true
|
||||
|
||||
/endent@2.1.0:
|
||||
resolution: {integrity: sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==}
|
||||
|
|
@ -10229,6 +10443,11 @@ packages:
|
|||
strip-final-newline: 3.0.0
|
||||
dev: true
|
||||
|
||||
/expand-template@2.0.3:
|
||||
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/express@4.21.2:
|
||||
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
|
@ -10384,6 +10603,10 @@ packages:
|
|||
ramda: 0.29.0
|
||||
dev: true
|
||||
|
||||
/file-uri-to-path@1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
dev: false
|
||||
|
||||
/filelist@1.0.4:
|
||||
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
|
||||
dependencies:
|
||||
|
|
@ -10601,7 +10824,6 @@ packages:
|
|||
|
||||
/fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
dev: true
|
||||
|
||||
/fs-extra@10.1.0:
|
||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||
|
|
@ -10793,6 +11015,10 @@ packages:
|
|||
traverse: 0.6.8
|
||||
dev: true
|
||||
|
||||
/github-from-package@0.0.0:
|
||||
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||
dev: false
|
||||
|
||||
/github-slugger@1.5.0:
|
||||
resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
|
||||
dev: true
|
||||
|
|
@ -11188,7 +11414,6 @@ packages:
|
|||
|
||||
/ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: true
|
||||
|
||||
/ignore@4.0.6:
|
||||
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
|
||||
|
|
@ -11258,11 +11483,9 @@ packages:
|
|||
|
||||
/inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: true
|
||||
|
||||
/ini@1.3.8:
|
||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
dev: true
|
||||
|
||||
/internal-slot@1.1.0:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
|
|
@ -11819,6 +12042,10 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/jose@6.1.0:
|
||||
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
|
||||
dev: false
|
||||
|
||||
/joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -12501,6 +12728,11 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/mimic-response@3.1.0:
|
||||
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/min-indent@1.0.1:
|
||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -12550,7 +12782,6 @@ packages:
|
|||
|
||||
/minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
dev: true
|
||||
|
||||
/minipass@3.3.6:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
|
|
@ -12579,7 +12810,6 @@ packages:
|
|||
|
||||
/mkdirp-classic@0.5.3:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
dev: true
|
||||
|
||||
/mkdirp@0.5.6:
|
||||
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
|
||||
|
|
@ -12639,6 +12869,10 @@ packages:
|
|||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/napi-build-utils@2.0.0:
|
||||
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
|
||||
dev: false
|
||||
|
||||
/napi-postinstall@0.3.3:
|
||||
resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
|
@ -12667,6 +12901,27 @@ packages:
|
|||
resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==}
|
||||
dev: true
|
||||
|
||||
/next-auth@5.0.0-beta.29(next@14.2.32)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Ukpnuk3NMc/LiOl32njZPySk7pABEzbjhMUFd5/n10I0ZNC7NCuVv8IY2JgbDek2t/PUOifQEoUiOOTLy4os5A==}
|
||||
peerDependencies:
|
||||
'@simplewebauthn/browser': ^9.0.1
|
||||
'@simplewebauthn/server': ^9.0.2
|
||||
next: ^14.0.0-0 || ^15.0.0-0
|
||||
nodemailer: ^6.6.5
|
||||
react: ^18.2.0 || ^19.0.0-0
|
||||
peerDependenciesMeta:
|
||||
'@simplewebauthn/browser':
|
||||
optional: true
|
||||
'@simplewebauthn/server':
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@auth/core': 0.40.0
|
||||
next: 14.2.32(@babel/core@7.28.4)(@playwright/test@1.55.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/next@14.2.32(@babel/core@7.28.4)(@playwright/test@1.55.1)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-fg5g0GZ7/nFc09X8wLe6pNSU8cLWbLRG3TZzPJ1BJvi2s9m7eF991se67wliM9kR5yLHRkyGKU49MMx58s3LJg==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
|
|
@ -12716,6 +12971,13 @@ packages:
|
|||
tslib: 2.8.1
|
||||
dev: true
|
||||
|
||||
/node-abi@3.77.0:
|
||||
resolution: {integrity: sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
semver: 7.7.2
|
||||
dev: false
|
||||
|
||||
/node-abort-controller@3.1.1:
|
||||
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
|
||||
dev: true
|
||||
|
|
@ -12947,6 +13209,10 @@ packages:
|
|||
ufo: 1.6.1
|
||||
dev: true
|
||||
|
||||
/oauth4webapi@3.8.2:
|
||||
resolution: {integrity: sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==}
|
||||
dev: false
|
||||
|
||||
/object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -13046,7 +13312,6 @@ packages:
|
|||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
dev: true
|
||||
|
||||
/onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
|
|
@ -13701,6 +13966,37 @@ packages:
|
|||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
/preact-render-to-string@6.5.11(preact@10.24.3):
|
||||
resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==}
|
||||
peerDependencies:
|
||||
preact: '>=10'
|
||||
dependencies:
|
||||
preact: 10.24.3
|
||||
dev: false
|
||||
|
||||
/preact@10.24.3:
|
||||
resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==}
|
||||
dev: false
|
||||
|
||||
/prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
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.77.0
|
||||
pump: 3.0.3
|
||||
rc: 1.2.8
|
||||
simple-get: 4.0.1
|
||||
tar-fs: 2.1.4
|
||||
tunnel-agent: 0.6.0
|
||||
dev: false
|
||||
|
||||
/preferred-pm@3.1.4:
|
||||
resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -13835,7 +14131,6 @@ packages:
|
|||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
once: 1.4.0
|
||||
dev: true
|
||||
|
||||
/pumpify@1.5.1:
|
||||
resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==}
|
||||
|
|
@ -13946,7 +14241,6 @@ packages:
|
|||
ini: 1.3.8
|
||||
minimist: 1.2.8
|
||||
strip-json-comments: 2.0.1
|
||||
dev: true
|
||||
|
||||
/react-colorful@5.6.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
|
||||
|
|
@ -14193,7 +14487,6 @@ packages:
|
|||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
dev: true
|
||||
|
||||
/readable-stream@4.7.0:
|
||||
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
|
||||
|
|
@ -14534,7 +14827,6 @@ packages:
|
|||
|
||||
/safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
dev: true
|
||||
|
||||
/safe-push-apply@1.0.0:
|
||||
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
|
||||
|
|
@ -14675,7 +14967,6 @@ packages:
|
|||
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/send@0.19.0:
|
||||
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||
|
|
@ -14851,6 +15142,18 @@ packages:
|
|||
pkg-conf: 2.1.0
|
||||
dev: true
|
||||
|
||||
/simple-concat@1.0.1:
|
||||
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||
dev: false
|
||||
|
||||
/simple-get@4.0.1:
|
||||
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||
dependencies:
|
||||
decompress-response: 6.0.0
|
||||
once: 1.4.0
|
||||
simple-concat: 1.0.1
|
||||
dev: false
|
||||
|
||||
/simple-update-notifier@2.0.0:
|
||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -15166,7 +15469,6 @@ packages:
|
|||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
|
|
@ -15211,7 +15513,6 @@ packages:
|
|||
/strip-json-comments@2.0.1:
|
||||
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
|
|
@ -15335,7 +15636,6 @@ packages:
|
|||
mkdirp-classic: 0.5.3
|
||||
pump: 3.0.3
|
||||
tar-stream: 2.2.0
|
||||
dev: true
|
||||
|
||||
/tar-stream@2.2.0:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
|
|
@ -15346,7 +15646,6 @@ packages:
|
|||
fs-constants: 1.0.0
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/tar@6.2.1:
|
||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||
|
|
@ -15775,6 +16074,12 @@ packages:
|
|||
resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==}
|
||||
dev: true
|
||||
|
||||
/tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/turbo-darwin-64@1.10.0:
|
||||
resolution: {integrity: sha512-N0aVGFtBgOKd7pIdUiKREwnDhNHRIvpMJbmUw04c1AqEoTiKAKT6iuzcCozO5N/gYMVr0hxrXgLal5OLYXtcsw==}
|
||||
cpu: [x64]
|
||||
|
|
@ -16713,7 +17018,6 @@ packages:
|
|||
|
||||
/wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
/write-file-atomic@2.4.3:
|
||||
resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==}
|
||||
|
|
|
|||
Loading…
Reference in New Issue