From 865a259d494b8a80d39daa8b20f50be5a5e750bc Mon Sep 17 00:00:00 2001 From: SASAGAWA Kiyoshi Date: Thu, 14 May 2026 02:01:36 +0900 Subject: [PATCH] chore: replace better-sqlite3 with bun:sqlite (refs #166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminates the C++ native module `better-sqlite3` in favor of Bun's built-in `bun:sqlite`, removing the need for Node.js in CI and simplifying the toolchain. Driver swap (db/index.ts): - drizzle-orm/better-sqlite3 → drizzle-orm/bun-sqlite - better-sqlite3 → bun:sqlite (built-in) - sqlite.pragma("journal_mode = WAL") → sqlite.exec("PRAGMA ...") - Export SqliteDatabase type for shared use Type renames across 22 repositories: - BetterSQLite3Database → BunSQLiteDatabase Tests consolidated to Bun test runner: - Removed src/tests/node/sqlite-repositories.test.ts (vitest) - Migrated full coverage into src/tests/integration/sqlite-repositories.test.ts using bun:test + bun:sqlite - 28 SQLite integration tests pass under bun test Dependencies removed: - better-sqlite3 (native module) - @types/better-sqlite3 - vitest (only used for the SQLite tests above) CI simplification: - Drop actions/setup-node step from sqlite-tests job - bunx vitest → bun test Verification (local): - bun run typecheck (backend + frontend) — pass - bun run lint — 0 warnings, 0 errors - bun test src/tests/unit/ — 1012 pass / 0 fail - bun test src/tests/integration/ — 70 pass / 0 fail - bun run build (backend) — succeeds - node_modules/better-sqlite3 — not installed (optional peer only) --- .github/workflows/ci.yml | 7 +- bun.lock | 75 +- packages/backend/package.json | 7 +- packages/backend/src/db/index.ts | 15 +- packages/backend/src/db/schema/sqlite.ts | 2 +- .../sqlite/SqliteCustomEmojiRepository.ts | 4 +- .../sqlite/SqliteDriveFileRepository.ts | 4 +- .../sqlite/SqliteDriveFolderRepository.ts | 4 +- .../sqlite/SqliteFollowRepository.ts | 4 +- .../sqlite/SqliteInstanceBlockRepository.ts | 4 +- .../SqliteInstanceSettingsRepository.ts | 4 +- .../sqlite/SqliteInvitationCodeRepository.ts | 4 +- .../SqliteModerationAuditLogRepository.ts | 4 +- .../sqlite/SqliteNoteRepository.ts | 4 +- .../sqlite/SqliteNotificationRepository.ts | 4 +- .../sqlite/SqliteOAuthAccountRepository.ts | 4 +- .../SqlitePasskeyChallengeRepository.ts | 4 +- .../SqlitePasskeyCredentialRepository.ts | 4 +- .../sqlite/SqliteReactionRepository.ts | 4 +- .../sqlite/SqliteRemoteInstanceRepository.ts | 4 +- .../sqlite/SqliteRoleAssignmentRepository.ts | 4 +- .../sqlite/SqliteRoleRepository.ts | 4 +- .../sqlite/SqliteScheduledNoteRepository.ts | 4 +- .../sqlite/SqliteSessionRepository.ts | 4 +- .../sqlite/SqliteUserReportRepository.ts | 4 +- .../sqlite/SqliteUserRepository.ts | 6 +- .../sqlite/SqliteUserWarningRepository.ts | 4 +- .../integration/sqlite-repositories.test.ts | 833 ++++++++++++++++- .../tests/node/sqlite-repositories.test.ts | 834 ------------------ 29 files changed, 908 insertions(+), 955 deletions(-) delete mode 100644 packages/backend/src/tests/node/sqlite-repositories.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b57c9b9d..ab925621 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,16 +106,11 @@ jobs: with: bun-version: ${{ env.BUN_VERSION }} - - name: Setup Node.js (for native module compatibility) - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0 - with: - node-version: "24" - - name: Install dependencies run: bun install --frozen-lockfile - name: Run SQLite integration tests - run: bunx vitest run src/tests/node/sqlite-repositories.test.ts + run: bun test src/tests/integration/sqlite-repositories.test.ts working-directory: packages/backend env: DB_TYPE: sqlite diff --git a/bun.lock b/bun.lock index 7b004c7f..f21e6c65 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,6 @@ "@hono/zod-validator": "^0.7.6", "@simplewebauthn/server": "^13.3.0", "@simplewebauthn/types": "^12.0.0", - "better-sqlite3": "^12.10.0", "blurhash": "^2.0.5", "bullmq": "^5.76.8", "drizzle-orm": "^0.45.2", @@ -44,7 +43,6 @@ "zod": "^4.4.3", }, "devDependencies": { - "@types/better-sqlite3": "^7.6.13", "@types/eventsource": "^3.0.0", "@types/pg": "^8.20.0", "@types/pino": "^7.0.5", @@ -53,7 +51,6 @@ "bun-types": "^1.3.14", "drizzle-kit": "^0.31.10", "typescript": "^5.9.3", - "vitest": "^4.1.6", }, }, "packages/frontend": { @@ -110,10 +107,10 @@ "name": "shared", "version": "1.5.1", "dependencies": { - "vite-plus": "^0.1.16", + "vite-plus": "^0.1.21", }, "devDependencies": { - "bun-types": "^1.3.11", + "bun-types": "^1.3.14", "typescript": "^5.9.3", }, }, @@ -869,19 +866,13 @@ "@vitejs/plugin-rsc": ["@vitejs/plugin-rsc@0.5.26", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.18", "es-module-lexer": "^2.1.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.21", "srvx": "^0.11.15", "strip-literal": "^3.1.0", "turbo-stream": "^3.2.0", "vitefu": "^1.1.3" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-server-dom-webpack": "*", "vite": "*" }, "optionalPeers": ["react-server-dom-webpack"] }, "sha512-T8W8ODEutblw9qXQB512LDPyv1tAbJRD/Gf0QEGsAoydl4nxEtIrghnhoI9oLY9R+7aw+cLk1ZEltxWHWf4aHw=="], - "@vitest/expect": ["@vitest/expect@4.1.6", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.6", "@vitest/utils": "4.1.6", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg=="], - - "@vitest/mocker": ["@vitest/mocker@4.1.6", "", { "dependencies": { "@vitest/spy": "4.1.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ=="], - - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.6", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw=="], + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], - "@vitest/runner": ["@vitest/runner@4.1.6", "", { "dependencies": { "@vitest/utils": "4.1.6", "pathe": "^2.0.3" } }, "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA=="], + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - "@vitest/snapshot": ["@vitest/snapshot@4.1.6", "", { "dependencies": { "@vitest/pretty-format": "4.1.6", "@vitest/utils": "4.1.6", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw=="], + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], - "@vitest/spy": ["@vitest/spy@4.1.6", "", {}, "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg=="], - - "@vitest/utils": ["@vitest/utils@4.1.6", "", { "dependencies": { "@vitest/pretty-format": "4.1.6", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ=="], + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@voidzero-dev/vite-plus-core": ["@voidzero-dev/vite-plus-core@0.1.21", "", { "dependencies": { "@oxc-project/runtime": "=0.129.0", "@oxc-project/types": "=0.129.0", "lightningcss": "^1.30.2", "postcss": "^8.5.6" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.22.0", "@tsdown/exe": "0.22.0", "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "publint": "^0.3.8", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0", "unrun": "*", "yaml": "^2.4.2" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "publint", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "typescript", "unplugin-unused", "unrun", "yaml"] }, "sha512-BEnqw8h2vxgKkzBjmmW4e1kwPwzoWc+jXJQB+7e0Dm1/1AkdTaQ9FgUMFzDfYrwTDkGzCZHSSsHDOl3RERQFTA=="], @@ -1029,7 +1020,7 @@ "caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="], - "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -1137,7 +1128,7 @@ "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], @@ -1165,8 +1156,6 @@ "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - "fast-copy": ["fast-copy@4.0.2", "", {}, "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -1465,8 +1454,6 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="], @@ -1487,7 +1474,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], @@ -1629,8 +1616,6 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], @@ -1653,8 +1638,6 @@ "srvx": ["srvx@0.11.15", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg=="], - "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], @@ -1715,7 +1698,7 @@ "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], - "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], @@ -1759,8 +1742,6 @@ "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], - "vitest": ["vitest@4.1.6", "", { "dependencies": { "@vitest/expect": "4.1.6", "@vitest/mocker": "4.1.6", "@vitest/pretty-format": "4.1.6", "@vitest/runner": "4.1.6", "@vitest/snapshot": "4.1.6", "@vitest/spy": "4.1.6", "@vitest/utils": "4.1.6", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.6", "@vitest/browser-preview": "4.1.6", "@vitest/browser-webdriverio": "4.1.6", "@vitest/coverage-istanbul": "4.1.6", "@vitest/coverage-v8": "4.1.6", "@vitest/ui": "4.1.6", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ=="], - "waku": ["waku@1.0.0-beta.0", "", { "dependencies": { "@hono/node-server": "^2.0.2", "@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-rsc": "^0.5.26", "dotenv": "^17.4.2", "hono": "^4.12.14", "magic-string": "^0.30.21", "picocolors": "^1.1.1", "rsc-html-stream": "^0.0.7", "vite": "^8.0.0" }, "peerDependencies": { "react": "~19.2.4", "react-dom": "~19.2.4", "react-server-dom-webpack": "~19.2.4" }, "bin": { "waku": "cli.js" } }, "sha512-6UhIsxrN70g1nsACoEAmCriT15pnyH3CunhrckFZuARBkaFXUvN22dIQWdCTiA8JVeNXAnhFV85Tuko8uEqZKQ=="], "waku_rox": ["waku_rox@workspace:packages/frontend"], @@ -1779,8 +1760,6 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -1859,8 +1838,12 @@ "@peculiar/asn1-x509-attr/@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA=="], + "@rolldown/plugin-babel/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@rollup/pluginutils/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -1883,12 +1866,12 @@ "@vitejs/plugin-rsc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.18", "", {}, "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw=="], + "@vitejs/plugin-rsc/es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + "@voidzero-dev/vite-plus-core/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "@voidzero-dev/vite-plus-core/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], - "@voidzero-dev/vite-plus-test/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], - "@voidzero-dev/vite-plus-test/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -1945,16 +1928,10 @@ "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "rollup-plugin-visualizer/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], - - "storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], - "storybook/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], "storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], @@ -1965,6 +1942,8 @@ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "tsx/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], "tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -1973,16 +1952,18 @@ "tunnel-agent/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "unplugin/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "vite/rolldown": ["rolldown@1.0.0", "", { "dependencies": { "@oxc-project/types": "=0.129.0", "@rolldown/pluginutils": "1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0", "@rolldown/binding-darwin-arm64": "1.0.0", "@rolldown/binding-darwin-x64": "1.0.0", "@rolldown/binding-freebsd-x64": "1.0.0", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", "@rolldown/binding-linux-arm64-gnu": "1.0.0", "@rolldown/binding-linux-arm64-musl": "1.0.0", "@rolldown/binding-linux-ppc64-gnu": "1.0.0", "@rolldown/binding-linux-s390x-gnu": "1.0.0", "@rolldown/binding-linux-x64-gnu": "1.0.0", "@rolldown/binding-linux-x64-musl": "1.0.0", "@rolldown/binding-openharmony-arm64": "1.0.0", "@rolldown/binding-wasm32-wasi": "1.0.0", "@rolldown/binding-win32-arm64-msvc": "1.0.0", "@rolldown/binding-win32-x64-msvc": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA=="], "waku/vite": ["vite@8.0.7", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.13", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw=="], "webpack/enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], - "webpack/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], - "wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], "@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], @@ -2067,16 +2048,8 @@ "@types/pino/pino/thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], - "@voidzero-dev/vite-plus-test/tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "bl/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - "storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], - - "storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], - - "storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], - "storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], "storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], @@ -2219,6 +2192,8 @@ "waku/vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "waku/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "waku/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "waku/vite/rolldown": ["rolldown@1.0.0-rc.13", "", { "dependencies": { "@oxc-project/types": "=0.123.0", "@rolldown/pluginutils": "1.0.0-rc.13" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.13", "@rolldown/binding-darwin-arm64": "1.0.0-rc.13", "@rolldown/binding-darwin-x64": "1.0.0-rc.13", "@rolldown/binding-freebsd-x64": "1.0.0-rc.13", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw=="], @@ -2235,8 +2210,6 @@ "bl/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - "tar-stream/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], diff --git a/packages/backend/package.json b/packages/backend/package.json index 6b2bb3dd..05250b2c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -14,7 +14,7 @@ "test:integration": "bun test src/tests/integration/", "test:e2e": "bun test src/tests/e2e/", "test:all": "bun test", - "test:sqlite": "npx vitest run src/tests/node/sqlite-repositories.vitest.ts", + "test:sqlite": "bun test src/tests/integration/sqlite-repositories.test.ts", "db:generate": "drizzle-kit generate", "db:migrate": "bun run src/db/migrate.ts", "db:studio": "drizzle-kit studio" @@ -24,7 +24,6 @@ "@hono/zod-validator": "^0.7.6", "@simplewebauthn/server": "^13.3.0", "@simplewebauthn/types": "^12.0.0", - "better-sqlite3": "^12.10.0", "blurhash": "^2.0.5", "bullmq": "^5.76.8", "drizzle-orm": "^0.45.2", @@ -43,7 +42,6 @@ "zod": "^4.4.3" }, "devDependencies": { - "@types/better-sqlite3": "^7.6.13", "@types/eventsource": "^3.0.0", "@types/pg": "^8.20.0", "@types/pino": "^7.0.5", @@ -51,7 +49,6 @@ "@types/web-push": "^3.6.4", "bun-types": "^1.3.14", "drizzle-kit": "^0.31.10", - "typescript": "^5.9.3", - "vitest": "^4.1.6" + "typescript": "^5.9.3" } } diff --git a/packages/backend/src/db/index.ts b/packages/backend/src/db/index.ts index 3d3a022c..c7c9f59c 100644 --- a/packages/backend/src/db/index.ts +++ b/packages/backend/src/db/index.ts @@ -9,14 +9,19 @@ import { drizzle as drizzlePg, type NodePgDatabase } from "drizzle-orm/node-postgres"; import { drizzle as drizzleMysql } from "drizzle-orm/mysql2"; -import { drizzle as drizzleSqlite, type BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import { drizzle as drizzleSqlite, type BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { Pool } from "pg"; import mysql from "mysql2/promise"; -import BetterSqlite3 from "better-sqlite3"; +import { Database as BunSqlite } from "bun:sqlite"; import * as pgSchema from "./schema/pg.js"; import * as mysqlSchema from "./schema/mysql.js"; import * as sqliteSchema from "./schema/sqlite.js"; +/** + * SQLite Drizzle database instance type (shared by all SQLite repositories). + */ +export type SqliteDatabase = BunSQLiteDatabase; + /** * Supported Database Types */ @@ -92,9 +97,9 @@ export function createDatabase(): Database { } // Remove sqlite:// prefix if present const dbPath = databaseUrl.replace(/^sqlite:\/\//, ""); - const sqlite = new BetterSqlite3(dbPath); + const sqlite = new BunSqlite(dbPath); // Enable WAL mode for better concurrent access - sqlite.pragma("journal_mode = WAL"); + sqlite.exec("PRAGMA journal_mode = WAL"); return drizzleSqlite(sqlite, { schema: sqliteSchema }) as unknown as Database; } @@ -175,7 +180,7 @@ interface D1ExecResult { /** * D1 Database Instance Type */ -export type D1DatabaseInstance = BetterSQLite3Database; +export type D1DatabaseInstance = BunSQLiteDatabase; /** * Drizzle ORM Database Instance Type diff --git a/packages/backend/src/db/schema/sqlite.ts b/packages/backend/src/db/schema/sqlite.ts index 7aa12b6e..7179111f 100644 --- a/packages/backend/src/db/schema/sqlite.ts +++ b/packages/backend/src/db/schema/sqlite.ts @@ -1,7 +1,7 @@ /** * SQLite/D1 Database Schema * - * This schema is designed to work with both local SQLite (via better-sqlite3) + * This schema is designed to work with both local SQLite (via bun:sqlite) * and Cloudflare D1 (via drizzle-orm/d1). * * Key differences from PostgreSQL: diff --git a/packages/backend/src/repositories/sqlite/SqliteCustomEmojiRepository.ts b/packages/backend/src/repositories/sqlite/SqliteCustomEmojiRepository.ts index c644ebc4..c24d689d 100644 --- a/packages/backend/src/repositories/sqlite/SqliteCustomEmojiRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteCustomEmojiRepository.ts @@ -7,7 +7,7 @@ */ import { eq, and, isNull, like, inArray, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { customEmojis, type CustomEmoji, type NewCustomEmoji } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -15,7 +15,7 @@ import type { ListCustomEmojisOptions, } from "../../interfaces/repositories/ICustomEmojiRepository.js"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Custom Emoji Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteDriveFileRepository.ts b/packages/backend/src/repositories/sqlite/SqliteDriveFileRepository.ts index b4f5718f..b7146ab0 100644 --- a/packages/backend/src/repositories/sqlite/SqliteDriveFileRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteDriveFileRepository.ts @@ -7,13 +7,13 @@ */ import { eq, and, sql, desc, lt, gt, inArray, isNull } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { driveFiles } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IDriveFileRepository } from "../../interfaces/repositories/IDriveFileRepository.js"; import type { DriveFile } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of drive file repository diff --git a/packages/backend/src/repositories/sqlite/SqliteDriveFolderRepository.ts b/packages/backend/src/repositories/sqlite/SqliteDriveFolderRepository.ts index da1eee95..4776dcd3 100644 --- a/packages/backend/src/repositories/sqlite/SqliteDriveFolderRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteDriveFolderRepository.ts @@ -7,13 +7,13 @@ */ import { eq, and, sql, isNull, desc } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { driveFolders } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IDriveFolderRepository } from "../../interfaces/repositories/IDriveFolderRepository.js"; import type { DriveFolder } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of drive folder repository diff --git a/packages/backend/src/repositories/sqlite/SqliteFollowRepository.ts b/packages/backend/src/repositories/sqlite/SqliteFollowRepository.ts index 004606f6..af9d79a7 100644 --- a/packages/backend/src/repositories/sqlite/SqliteFollowRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteFollowRepository.ts @@ -7,13 +7,13 @@ */ import { eq, and, or, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { follows, users } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IFollowRepository } from "../../interfaces/repositories/IFollowRepository.js"; import type { Follow } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of follow repository diff --git a/packages/backend/src/repositories/sqlite/SqliteInstanceBlockRepository.ts b/packages/backend/src/repositories/sqlite/SqliteInstanceBlockRepository.ts index 220105a5..6012b822 100644 --- a/packages/backend/src/repositories/sqlite/SqliteInstanceBlockRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteInstanceBlockRepository.ts @@ -7,13 +7,13 @@ */ import { eq, sql, desc } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { instanceBlocks, type InstanceBlock } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IInstanceBlockRepository } from "../../interfaces/repositories/IInstanceBlockRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Instance Block Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteInstanceSettingsRepository.ts b/packages/backend/src/repositories/sqlite/SqliteInstanceSettingsRepository.ts index dbcf17e2..401eaba9 100644 --- a/packages/backend/src/repositories/sqlite/SqliteInstanceSettingsRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteInstanceSettingsRepository.ts @@ -7,7 +7,7 @@ */ import { eq, inArray } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { instanceSettings, type InstanceSetting } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -15,7 +15,7 @@ import type { InstanceSettingKey, } from "../../interfaces/repositories/IInstanceSettingsRepository.js"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Instance Settings Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteInvitationCodeRepository.ts b/packages/backend/src/repositories/sqlite/SqliteInvitationCodeRepository.ts index 66136ff7..fcf4fdf0 100644 --- a/packages/backend/src/repositories/sqlite/SqliteInvitationCodeRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteInvitationCodeRepository.ts @@ -7,13 +7,13 @@ */ import { eq, sql, desc, and, gt, or, isNull, lt } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { invitationCodes, type InvitationCode } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IInvitationCodeRepository } from "../../interfaces/repositories/IInvitationCodeRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Invitation Code Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteModerationAuditLogRepository.ts b/packages/backend/src/repositories/sqlite/SqliteModerationAuditLogRepository.ts index 8031f48c..f32ab806 100644 --- a/packages/backend/src/repositories/sqlite/SqliteModerationAuditLogRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteModerationAuditLogRepository.ts @@ -7,7 +7,7 @@ */ import { eq, sql, desc, and } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { moderationAuditLogs, type ModerationAuditLog } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -17,7 +17,7 @@ import type { } from "../../interfaces/repositories/IModerationAuditLogRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Moderation Audit Log Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteNoteRepository.ts b/packages/backend/src/repositories/sqlite/SqliteNoteRepository.ts index f61fa4d8..b39ba0f0 100644 --- a/packages/backend/src/repositories/sqlite/SqliteNoteRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteNoteRepository.ts @@ -7,7 +7,7 @@ */ import { eq, and, desc, lt, gt, inArray, isNull, sql, or } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { notes, users } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -17,7 +17,7 @@ import type { } from "../../interfaces/repositories/INoteRepository.js"; import type { Note } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of note repository diff --git a/packages/backend/src/repositories/sqlite/SqliteNotificationRepository.ts b/packages/backend/src/repositories/sqlite/SqliteNotificationRepository.ts index 66c91252..317a2c70 100644 --- a/packages/backend/src/repositories/sqlite/SqliteNotificationRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteNotificationRepository.ts @@ -7,13 +7,13 @@ */ import { eq, and, desc, lt, gt, inArray, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { notifications, type Notification, type NotificationType } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { INotificationRepository } from "../../interfaces/repositories/INotificationRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of notification repository diff --git a/packages/backend/src/repositories/sqlite/SqliteOAuthAccountRepository.ts b/packages/backend/src/repositories/sqlite/SqliteOAuthAccountRepository.ts index 93400ec2..66c9c839 100644 --- a/packages/backend/src/repositories/sqlite/SqliteOAuthAccountRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteOAuthAccountRepository.ts @@ -7,7 +7,7 @@ */ import { eq, and, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { oauthAccounts, type OAuthAccount, type NewOAuthAccount } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -15,7 +15,7 @@ import type { OAuthProvider, } from "../../interfaces/repositories/IOAuthAccountRepository.js"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of OAuth Account Repository diff --git a/packages/backend/src/repositories/sqlite/SqlitePasskeyChallengeRepository.ts b/packages/backend/src/repositories/sqlite/SqlitePasskeyChallengeRepository.ts index 1e50d461..b53f9bfd 100644 --- a/packages/backend/src/repositories/sqlite/SqlitePasskeyChallengeRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqlitePasskeyChallengeRepository.ts @@ -7,7 +7,7 @@ */ import { eq, lt, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { passkeyChallenges, type PasskeyChallenge, @@ -16,7 +16,7 @@ import { import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IPasskeyChallengeRepository } from "../../interfaces/repositories/IPasskeyChallengeRepository.js"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Passkey Challenge Repository diff --git a/packages/backend/src/repositories/sqlite/SqlitePasskeyCredentialRepository.ts b/packages/backend/src/repositories/sqlite/SqlitePasskeyCredentialRepository.ts index 3de0f441..093c33f3 100644 --- a/packages/backend/src/repositories/sqlite/SqlitePasskeyCredentialRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqlitePasskeyCredentialRepository.ts @@ -7,7 +7,7 @@ */ import { eq, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { passkeyCredentials, type PasskeyCredential, @@ -16,7 +16,7 @@ import { import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IPasskeyCredentialRepository } from "../../interfaces/repositories/IPasskeyCredentialRepository.js"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Passkey Credential Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteReactionRepository.ts b/packages/backend/src/repositories/sqlite/SqliteReactionRepository.ts index e96560e4..bb891102 100644 --- a/packages/backend/src/repositories/sqlite/SqliteReactionRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteReactionRepository.ts @@ -7,13 +7,13 @@ */ import { eq, and, sql, desc, inArray } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { reactions } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IReactionRepository } from "../../interfaces/repositories/IReactionRepository.js"; import type { Reaction } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of reaction repository diff --git a/packages/backend/src/repositories/sqlite/SqliteRemoteInstanceRepository.ts b/packages/backend/src/repositories/sqlite/SqliteRemoteInstanceRepository.ts index d20e048a..d3002af1 100644 --- a/packages/backend/src/repositories/sqlite/SqliteRemoteInstanceRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteRemoteInstanceRepository.ts @@ -7,13 +7,13 @@ */ import { eq, lt, inArray, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { remoteInstances } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IRemoteInstanceRepository } from "../../interfaces/repositories/IRemoteInstanceRepository.js"; import type { RemoteInstance } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; export class SqliteRemoteInstanceRepository implements IRemoteInstanceRepository { constructor(private db: SqliteDatabase) {} diff --git a/packages/backend/src/repositories/sqlite/SqliteRoleAssignmentRepository.ts b/packages/backend/src/repositories/sqlite/SqliteRoleAssignmentRepository.ts index cddfc709..2547e906 100644 --- a/packages/backend/src/repositories/sqlite/SqliteRoleAssignmentRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteRoleAssignmentRepository.ts @@ -7,7 +7,7 @@ */ import { eq, and, sql, lt } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { roleAssignments, roles, type Role, type RoleAssignment } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -16,7 +16,7 @@ import type { } from "../../interfaces/repositories/IRoleAssignmentRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Role Assignment Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteRoleRepository.ts b/packages/backend/src/repositories/sqlite/SqliteRoleRepository.ts index e501c11d..99c54578 100644 --- a/packages/backend/src/repositories/sqlite/SqliteRoleRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteRoleRepository.ts @@ -7,7 +7,7 @@ */ import { eq, asc, sql, desc } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { roles, type Role } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -17,7 +17,7 @@ import type { } from "../../interfaces/repositories/IRoleRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of Role Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteScheduledNoteRepository.ts b/packages/backend/src/repositories/sqlite/SqliteScheduledNoteRepository.ts index 7212daf9..cce74d06 100644 --- a/packages/backend/src/repositories/sqlite/SqliteScheduledNoteRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteScheduledNoteRepository.ts @@ -7,7 +7,7 @@ */ import { eq, and, desc, lt, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { scheduledNotes, type ScheduledNote, @@ -17,7 +17,7 @@ import { import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IScheduledNoteRepository } from "../../interfaces/repositories/IScheduledNoteRepository.js"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of scheduled note repository diff --git a/packages/backend/src/repositories/sqlite/SqliteSessionRepository.ts b/packages/backend/src/repositories/sqlite/SqliteSessionRepository.ts index 7d9607c8..d2782a10 100644 --- a/packages/backend/src/repositories/sqlite/SqliteSessionRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteSessionRepository.ts @@ -7,13 +7,13 @@ */ import { eq, lt, sql } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { sessions } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { ISessionRepository } from "../../interfaces/repositories/ISessionRepository.js"; import type { Session } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of session repository diff --git a/packages/backend/src/repositories/sqlite/SqliteUserReportRepository.ts b/packages/backend/src/repositories/sqlite/SqliteUserReportRepository.ts index 56ef971b..db5a98de 100644 --- a/packages/backend/src/repositories/sqlite/SqliteUserReportRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteUserReportRepository.ts @@ -7,7 +7,7 @@ */ import { eq, sql, desc, and } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { userReports, type UserReport } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -16,7 +16,7 @@ import type { } from "../../interfaces/repositories/IUserReportRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of User Report Repository diff --git a/packages/backend/src/repositories/sqlite/SqliteUserRepository.ts b/packages/backend/src/repositories/sqlite/SqliteUserRepository.ts index 66d09339..9cbc15f3 100644 --- a/packages/backend/src/repositories/sqlite/SqliteUserRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteUserRepository.ts @@ -7,7 +7,7 @@ */ import { eq, and, isNull, isNotNull, sql, desc, or } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { users, type User } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { @@ -16,7 +16,7 @@ import type { SearchUsersOptions, } from "../../interfaces/repositories/IUserRepository.js"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; export class SqliteUserRepository implements IUserRepository { constructor(private db: SqliteDatabase) {} @@ -32,7 +32,7 @@ export class SqliteUserRepository implements IUserRepository { }) .run(); - // SQLite with better-sqlite3 is synchronous, fetch the inserted record + // bun:sqlite is synchronous, fetch the inserted record const [result] = this.db.select().from(users).where(eq(users.id, user.id)).limit(1).all(); if (!result) { diff --git a/packages/backend/src/repositories/sqlite/SqliteUserWarningRepository.ts b/packages/backend/src/repositories/sqlite/SqliteUserWarningRepository.ts index d3b948cd..8a340344 100644 --- a/packages/backend/src/repositories/sqlite/SqliteUserWarningRepository.ts +++ b/packages/backend/src/repositories/sqlite/SqliteUserWarningRepository.ts @@ -7,13 +7,13 @@ */ import { eq, and, desc, isNull, sql, or } from "drizzle-orm"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { userWarnings, type UserWarning } from "../../db/schema/sqlite.js"; import type * as sqliteSchema from "../../db/schema/sqlite.js"; import type { IUserWarningRepository } from "../../interfaces/repositories/IUserWarningRepository.js"; import { generateId } from "shared"; -type SqliteDatabase = BetterSQLite3Database; +type SqliteDatabase = BunSQLiteDatabase; /** * SQLite implementation of user warning repository diff --git a/packages/backend/src/tests/integration/sqlite-repositories.test.ts b/packages/backend/src/tests/integration/sqlite-repositories.test.ts index 5cdd7ad8..897ff3ef 100644 --- a/packages/backend/src/tests/integration/sqlite-repositories.test.ts +++ b/packages/backend/src/tests/integration/sqlite-repositories.test.ts @@ -1,17 +1,834 @@ /** - * SQLite Repository Integration Tests (Bun version - skipped) + * SQLite Repository Integration Tests * - * This file is for Bun's test runner. Since Bun doesn't support better-sqlite3, - * all tests are skipped here. The actual tests run via Node.js/Vitest in: - * sqlite-repositories.node.test.ts + * Tests SQLite repository implementations with an actual in-memory SQLite database. + * Uses Bun's built-in `bun:sqlite` driver (no native module required). * - * Run SQLite tests with: bun run test:sqlite (uses Node.js + Vitest) + * Run with: bun test src/tests/integration/sqlite-repositories.test.ts */ -import { describe, test } from "bun:test"; +import { describe, test, expect, beforeAll, afterAll, beforeEach } from "bun:test"; +import { Database } from "bun:sqlite"; +import { drizzle } from "drizzle-orm/bun-sqlite"; +import * as schema from "../../db/schema/sqlite.js"; +import { SqliteNoteRepository } from "../../repositories/sqlite/SqliteNoteRepository.js"; +import { SqliteUserRepository } from "../../repositories/sqlite/SqliteUserRepository.js"; +import { SqliteSessionRepository } from "../../repositories/sqlite/SqliteSessionRepository.js"; +import { SqliteFollowRepository } from "../../repositories/sqlite/SqliteFollowRepository.js"; +import { SqliteInstanceBlockRepository } from "../../repositories/sqlite/SqliteInstanceBlockRepository.js"; +import { randomUUID } from "crypto"; +import type { User, Session, Follow, Note } from "shared"; + +const uuidv4 = randomUUID; + +// NoteCreateInput matches the type expected by SqliteNoteRepository.create +type NoteCreateInput = Omit & { + repliesCount?: number; + renoteCount?: number; +}; + +// Extended Note type returned by findById (includes user info) +interface NoteWithUser extends Note { + user?: { + id: string; + username: string; + name: string | null; + displayName: string | null; + avatarUrl: string | null; + host: string | null; + profileEmojis: unknown; + }; +} + +/** + * Creates a complete Note object with all required fields for testing + */ +function createTestNote(overrides: { + id: string; + userId: string; + text: string; + visibility?: "public" | "home" | "followers" | "specified"; + localOnly?: boolean; +}): NoteCreateInput { + return { + id: overrides.id, + userId: overrides.userId, + text: overrides.text, + cw: null, + visibility: overrides.visibility ?? "public", + localOnly: overrides.localOnly ?? false, + replyId: null, + renoteId: null, + fileIds: [], + mentions: [], + emojis: [], + tags: [], + uri: null, + isDeleted: false, + deletedAt: null, + deletedById: null, + deletionReason: null, + }; +} + +/** + * Creates a complete User object with all required fields for testing + */ +function createTestUser(overrides: { + id: string; + username: string; + email: string; + passwordHash: string; + isAdmin?: boolean; +}): Omit { + return { + id: overrides.id, + username: overrides.username, + email: overrides.email, + passwordHash: overrides.passwordHash, + displayName: null, + bio: null, + avatarUrl: null, + bannerUrl: null, + isAdmin: overrides.isAdmin ?? false, + isSuspended: false, + isDeleted: false, + deletedAt: null, + isSystemUser: false, + publicKey: null, + privateKey: null, + host: null, + inbox: null, + outbox: null, + followersUrl: null, + followingUrl: null, + uri: null, + sharedInbox: null, + customCss: null, + uiSettings: null, + alsoKnownAs: null, + movedTo: null, + movedAt: null, + profileEmojis: null, + storageQuotaMb: null, + goneDetectedAt: null, + fetchFailureCount: 0, + lastFetchAttemptAt: null, + lastFetchError: null, + followersCount: 0, + followingCount: 0, + }; +} + +/** + * Creates a complete Session object with all required fields for testing + */ +function createTestSession(overrides: { + id: string; + userId: string; + token: string; + expiresAt: Date; +}): Omit { + return { + id: overrides.id, + userId: overrides.userId, + token: overrides.token, + expiresAt: overrides.expiresAt, + userAgent: null, + ipAddress: null, + }; +} + +/** + * Creates a complete Follow object with all required fields for testing + */ +function createTestFollow(overrides: { + id: string; + followerId: string; + followeeId: string; +}): Omit { + return { + id: overrides.id, + followerId: overrides.followerId, + followeeId: overrides.followeeId, + }; +} describe("SQLite Repository Integration Tests", () => { - test.skip("SKIPPED: better-sqlite3 is not supported in Bun - run 'bun run test:sqlite' instead", () => { - // These tests run in Node.js via sqlite-repositories.node.test.ts + let sqlite: Database; + let db: ReturnType>; + + beforeAll(() => { + // Create in-memory SQLite database for testing + sqlite = new Database(":memory:"); + db = drizzle(sqlite, { schema }); + + // Create tables manually for testing (must match Drizzle schema column names exactly) + sqlite.exec(` + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL, + email TEXT NOT NULL, + password_hash TEXT NOT NULL, + display_name TEXT, + bio TEXT, + avatar_url TEXT, + banner_url TEXT, + is_admin INTEGER NOT NULL DEFAULT 0, + is_suspended INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, + deleted_at INTEGER, + is_system_user INTEGER NOT NULL DEFAULT 0, + public_key TEXT, + private_key TEXT, + host TEXT, + inbox TEXT, + outbox TEXT, + followers_url TEXT, + following_url TEXT, + uri TEXT, + shared_inbox TEXT, + also_known_as TEXT DEFAULT '[]', + moved_to TEXT, + moved_at INTEGER, + custom_css TEXT, + ui_settings TEXT, + profile_emojis TEXT DEFAULT '[]', + storage_quota_mb INTEGER, + gone_detected_at INTEGER, + fetch_failure_count INTEGER NOT NULL DEFAULT 0, + last_fetch_attempt_at INTEGER, + last_fetch_error TEXT, + followers_count INTEGER NOT NULL DEFAULT 0, + following_count INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS notes ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES users(id), + text TEXT, + cw TEXT, + visibility TEXT NOT NULL DEFAULT 'public', + local_only INTEGER NOT NULL DEFAULT 0, + reply_id TEXT REFERENCES notes(id), + renote_id TEXT REFERENCES notes(id), + file_ids TEXT DEFAULT '[]', + mentions TEXT DEFAULT '[]', + emojis TEXT DEFAULT '[]', + tags TEXT DEFAULT '[]', + uri TEXT UNIQUE, + is_deleted INTEGER NOT NULL DEFAULT 0, + deleted_at INTEGER, + deleted_by_id TEXT REFERENCES users(id), + deletion_reason TEXT, + replies_count INTEGER NOT NULL DEFAULT 0, + renote_count INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES users(id), + token TEXT NOT NULL UNIQUE, + expires_at INTEGER NOT NULL, + user_agent TEXT, + ip_address TEXT, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS follows ( + id TEXT PRIMARY KEY, + follower_id TEXT NOT NULL REFERENCES users(id), + followee_id TEXT NOT NULL REFERENCES users(id), + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + UNIQUE(follower_id, followee_id) + ); + + CREATE TABLE IF NOT EXISTS instance_blocks ( + id TEXT PRIMARY KEY, + host TEXT NOT NULL UNIQUE, + reason TEXT, + blocked_by_id TEXT NOT NULL REFERENCES users(id), + created_at INTEGER NOT NULL + ); + `); + }); + + afterAll(() => { + sqlite.close(); + }); + + describe("SqliteUserRepository", () => { + let userRepo: SqliteUserRepository; + + beforeAll(() => { + userRepo = new SqliteUserRepository(db); + }); + + beforeEach(() => { + // Clean up users table before each test + sqlite.exec("DELETE FROM notes"); + sqlite.exec("DELETE FROM sessions"); + sqlite.exec("DELETE FROM follows"); + sqlite.exec("DELETE FROM users"); + }); + + test("should create a user", async () => { + const userId = uuidv4(); + const user = await userRepo.create( + createTestUser({ + id: userId, + username: "testuser", + email: "test@example.com", + passwordHash: "hashedpassword", + }), + ); + + expect(user.id).toBe(userId); + expect(user.username).toBe("testuser"); + expect(user.email).toBe("test@example.com"); + }); + + test("should find user by id", async () => { + const userId = uuidv4(); + await userRepo.create( + createTestUser({ + id: userId, + username: "findbyid", + email: "findbyid@example.com", + passwordHash: "hash", + }), + ); + + const found = await userRepo.findById(userId); + expect(found).not.toBeNull(); + expect(found?.username).toBe("findbyid"); + }); + + test("should find user by username", async () => { + const userId = uuidv4(); + await userRepo.create( + createTestUser({ + id: userId, + username: "findbyusername", + email: "findbyusername@example.com", + passwordHash: "hash", + }), + ); + + const found = await userRepo.findByUsername("findbyusername"); + expect(found).not.toBeNull(); + expect(found?.id).toBe(userId); + }); + + test("should return null for non-existent user", async () => { + const found = await userRepo.findById("non-existent-id"); + expect(found).toBeNull(); + }); + + test("should update user", async () => { + const userId = uuidv4(); + await userRepo.create( + createTestUser({ + id: userId, + username: "updateuser", + email: "update@example.com", + passwordHash: "hash", + }), + ); + + const updated = await userRepo.update(userId, { + displayName: "Updated Display Name", + bio: "Updated bio", + }); + + expect(updated.displayName).toBe("Updated Display Name"); + expect(updated.bio).toBe("Updated bio"); + }); + + test("should count users", async () => { + const count1 = await userRepo.count(); + expect(count1).toBe(0); + + await userRepo.create( + createTestUser({ + id: uuidv4(), + username: "user1", + email: "user1@example.com", + passwordHash: "hash", + }), + ); + + const count2 = await userRepo.count(); + expect(count2).toBe(1); + }); + }); + + describe("SqliteNoteRepository", () => { + let noteRepo: SqliteNoteRepository; + let testUserId: string; + + beforeAll(async () => { + noteRepo = new SqliteNoteRepository(db); + const userRepo = new SqliteUserRepository(db); + + // Create a test user for notes + testUserId = uuidv4(); + await userRepo.create( + createTestUser({ + id: testUserId, + username: "noteuser", + email: "noteuser@example.com", + passwordHash: "hash", + }), + ); + }); + + beforeEach(() => { + // Clean up notes table before each test + sqlite.exec(`DELETE FROM notes WHERE user_id != '${testUserId}'`); + }); + + test("should create a note", async () => { + const noteId = uuidv4(); + const note = await noteRepo.create( + createTestNote({ + id: noteId, + userId: testUserId, + text: "Hello, world!", + }), + ); + + expect(note.id).toBe(noteId); + expect(note.text).toBe("Hello, world!"); + expect(note.userId).toBe(testUserId); + }); + + test("should find note by id with user", async () => { + const noteId = uuidv4(); + await noteRepo.create( + createTestNote({ + id: noteId, + userId: testUserId, + text: "Find me!", + }), + ); + + const found = (await noteRepo.findById(noteId)) as NoteWithUser | null; + expect(found).not.toBeNull(); + expect(found?.text).toBe("Find me!"); + expect(found?.user).toBeDefined(); + expect(found?.user?.username).toBe("noteuser"); + }); + + test("should return null for non-existent note", async () => { + const found = await noteRepo.findById("non-existent-note-id"); + expect(found).toBeNull(); + }); + + test("should soft delete a note", async () => { + const noteId = uuidv4(); + await noteRepo.create( + createTestNote({ + id: noteId, + userId: testUserId, + text: "Delete me!", + }), + ); + + const deleted = await noteRepo.softDelete(noteId, testUserId, "Test deletion"); + expect(deleted?.isDeleted).toBe(true); + expect(deleted?.deletedById).toBe(testUserId); + expect(deleted?.deletionReason).toBe("Test deletion"); + }); + + test("should restore a soft-deleted note", async () => { + const noteId = uuidv4(); + await noteRepo.create( + createTestNote({ + id: noteId, + userId: testUserId, + text: "Restore me!", + }), + ); + + await noteRepo.softDelete(noteId, testUserId); + const restored = await noteRepo.restore(noteId); + + expect(restored?.isDeleted).toBe(false); + expect(restored?.deletedById).toBeNull(); + }); + + test("should increment and decrement replies count", async () => { + const noteId = uuidv4(); + await noteRepo.create( + createTestNote({ + id: noteId, + userId: testUserId, + text: "Count replies!", + }), + ); + + await noteRepo.incrementRepliesCount(noteId); + await noteRepo.incrementRepliesCount(noteId); + + let note = await noteRepo.findById(noteId); + expect(note?.repliesCount).toBe(2); + + await noteRepo.decrementRepliesCount(noteId); + note = await noteRepo.findById(noteId); + expect(note?.repliesCount).toBe(1); + }); + + test("should count notes", async () => { + // Clear all notes first + sqlite.exec("DELETE FROM notes"); + + const noteId1 = uuidv4(); + const noteId2 = uuidv4(); + + await noteRepo.create( + createTestNote({ + id: noteId1, + userId: testUserId, + text: "Note 1", + }), + ); + + await noteRepo.create( + createTestNote({ + id: noteId2, + userId: testUserId, + text: "Note 2", + }), + ); + + const count = await noteRepo.count(); + expect(count).toBe(2); + }); + }); + + describe("SqliteSessionRepository", () => { + let sessionRepo: SqliteSessionRepository; + let testUserId: string; + + beforeAll(async () => { + sessionRepo = new SqliteSessionRepository(db); + const userRepo = new SqliteUserRepository(db); + + // Create a test user for sessions + testUserId = uuidv4(); + await userRepo.create( + createTestUser({ + id: testUserId, + username: "sessionuser", + email: "sessionuser@example.com", + passwordHash: "hash", + }), + ); + }); + + beforeEach(() => { + sqlite.exec("DELETE FROM sessions"); + }); + + test("should create a session", async () => { + const sessionId = uuidv4(); + const session = await sessionRepo.create( + createTestSession({ + id: sessionId, + userId: testUserId, + token: "test-token-" + sessionId, + expiresAt: new Date(Date.now() + 86400000), + }), + ); + + expect(session.id).toBe(sessionId); + expect(session.userId).toBe(testUserId); + }); + + test("should find session by token", async () => { + const sessionId = uuidv4(); + const token = "find-token-" + sessionId; + await sessionRepo.create( + createTestSession({ + id: sessionId, + userId: testUserId, + token, + expiresAt: new Date(Date.now() + 86400000), + }), + ); + + const found = await sessionRepo.findByToken(token); + expect(found).not.toBeNull(); + expect(found?.id).toBe(sessionId); + }); + + test("should delete session", async () => { + const sessionId = uuidv4(); + await sessionRepo.create( + createTestSession({ + id: sessionId, + userId: testUserId, + token: "delete-token-" + sessionId, + expiresAt: new Date(Date.now() + 86400000), + }), + ); + + await sessionRepo.delete(sessionId); + const found = await sessionRepo.findById(sessionId); + expect(found).toBeNull(); + }); + + test("should delete expired sessions", async () => { + const expiredId = uuidv4(); + const validId = uuidv4(); + + // Create expired session + await sessionRepo.create( + createTestSession({ + id: expiredId, + userId: testUserId, + token: "expired-token", + expiresAt: new Date(Date.now() - 86400000), // Yesterday + }), + ); + + // Create valid session + await sessionRepo.create( + createTestSession({ + id: validId, + userId: testUserId, + token: "valid-token", + expiresAt: new Date(Date.now() + 86400000), // Tomorrow + }), + ); + + const deletedCount = await sessionRepo.deleteExpired(); + expect(deletedCount).toBe(1); + + const expiredSession = await sessionRepo.findById(expiredId); + const validSession = await sessionRepo.findById(validId); + + expect(expiredSession).toBeNull(); + expect(validSession).not.toBeNull(); + }); + }); + + describe("SqliteFollowRepository", () => { + let followRepo: SqliteFollowRepository; + let user1Id: string; + let user2Id: string; + + beforeAll(async () => { + followRepo = new SqliteFollowRepository(db); + const userRepo = new SqliteUserRepository(db); + + user1Id = uuidv4(); + user2Id = uuidv4(); + + await userRepo.create( + createTestUser({ + id: user1Id, + username: "follower", + email: "follower@example.com", + passwordHash: "hash", + }), + ); + + await userRepo.create( + createTestUser({ + id: user2Id, + username: "followee", + email: "followee@example.com", + passwordHash: "hash", + }), + ); + }); + + beforeEach(() => { + sqlite.exec("DELETE FROM follows"); + }); + + test("should create a follow relationship", async () => { + const followId = uuidv4(); + const follow = await followRepo.create( + createTestFollow({ + id: followId, + followerId: user1Id, + followeeId: user2Id, + }), + ); + + expect(follow.id).toBe(followId); + expect(follow.followerId).toBe(user1Id); + expect(follow.followeeId).toBe(user2Id); + }); + + test("should check if following", async () => { + const followId = uuidv4(); + await followRepo.create( + createTestFollow({ + id: followId, + followerId: user1Id, + followeeId: user2Id, + }), + ); + + const isFollowing = await followRepo.exists(user1Id, user2Id); + expect(isFollowing).toBe(true); + + const notFollowing = await followRepo.exists(user2Id, user1Id); + expect(notFollowing).toBe(false); + }); + + test("should get followers", async () => { + const followId = uuidv4(); + await followRepo.create( + createTestFollow({ + id: followId, + followerId: user1Id, + followeeId: user2Id, + }), + ); + + const followers = await followRepo.findByFolloweeId(user2Id); + expect(followers.length).toBe(1); + expect(followers[0]?.followerId).toBe(user1Id); + }); + + test("should get following", async () => { + const followId = uuidv4(); + await followRepo.create( + createTestFollow({ + id: followId, + followerId: user1Id, + followeeId: user2Id, + }), + ); + + const following = await followRepo.findByFollowerId(user1Id); + expect(following.length).toBe(1); + expect(following[0]?.followeeId).toBe(user2Id); + }); + + test("should delete follow relationship", async () => { + const followId = uuidv4(); + await followRepo.create( + createTestFollow({ + id: followId, + followerId: user1Id, + followeeId: user2Id, + }), + ); + + await followRepo.delete(user1Id, user2Id); + const isFollowing = await followRepo.exists(user1Id, user2Id); + expect(isFollowing).toBe(false); + }); + }); + + describe("SqliteInstanceBlockRepository", () => { + let blockRepo: SqliteInstanceBlockRepository; + let adminUserId: string; + + beforeAll(async () => { + blockRepo = new SqliteInstanceBlockRepository(db); + const userRepo = new SqliteUserRepository(db); + + adminUserId = uuidv4(); + await userRepo.create( + createTestUser({ + id: adminUserId, + username: "admin", + email: "admin@example.com", + passwordHash: "hash", + isAdmin: true, + }), + ); + }); + + beforeEach(() => { + sqlite.exec("DELETE FROM instance_blocks"); + }); + + test("should create an instance block", async () => { + const block = await blockRepo.create({ + host: "spam.example.com", + reason: "Spam instance", + blockedById: adminUserId, + }); + + expect(block.host).toBe("spam.example.com"); + expect(block.reason).toBe("Spam instance"); + }); + + test("should check if host is blocked", async () => { + await blockRepo.create({ + host: "blocked.example.com", + reason: null, + blockedById: adminUserId, + }); + + const isBlocked = await blockRepo.isBlocked("blocked.example.com"); + expect(isBlocked).toBe(true); + + const notBlocked = await blockRepo.isBlocked("allowed.example.com"); + expect(notBlocked).toBe(false); + }); + + test("should find block by host", async () => { + await blockRepo.create({ + host: "findme.example.com", + reason: "Test reason", + blockedById: adminUserId, + }); + + const found = await blockRepo.findByHost("findme.example.com"); + expect(found).not.toBeNull(); + expect(found?.reason).toBe("Test reason"); + }); + + test("should delete block by host", async () => { + await blockRepo.create({ + host: "deleteme.example.com", + reason: null, + blockedById: adminUserId, + }); + + const deleted = await blockRepo.deleteByHost("deleteme.example.com"); + expect(deleted).toBe(true); + + const isBlocked = await blockRepo.isBlocked("deleteme.example.com"); + expect(isBlocked).toBe(false); + }); + + test("should return false when deleting non-existent block", async () => { + const deleted = await blockRepo.deleteByHost("nonexistent.example.com"); + expect(deleted).toBe(false); + }); + + test("should count blocks", async () => { + expect(await blockRepo.count()).toBe(0); + + await blockRepo.create({ + host: "spam1.example.com", + reason: null, + blockedById: adminUserId, + }); + + await blockRepo.create({ + host: "spam2.example.com", + reason: null, + blockedById: adminUserId, + }); + + expect(await blockRepo.count()).toBe(2); + }); }); }); diff --git a/packages/backend/src/tests/node/sqlite-repositories.test.ts b/packages/backend/src/tests/node/sqlite-repositories.test.ts deleted file mode 100644 index 8135d34c..00000000 --- a/packages/backend/src/tests/node/sqlite-repositories.test.ts +++ /dev/null @@ -1,834 +0,0 @@ -/** - * SQLite Repository Integration Tests (Node.js/Vitest) - * - * Tests SQLite repository implementations with an actual SQLite database. - * This file uses Vitest and runs in Node.js to support better-sqlite3. - * - * Run with: bun run test:sqlite - */ - -import { describe, test, expect, beforeAll, afterAll, beforeEach } from "vitest"; -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import * as schema from "../../db/schema/sqlite.js"; -import { SqliteNoteRepository } from "../../repositories/sqlite/SqliteNoteRepository.js"; -import { SqliteUserRepository } from "../../repositories/sqlite/SqliteUserRepository.js"; -import { SqliteSessionRepository } from "../../repositories/sqlite/SqliteSessionRepository.js"; -import { SqliteFollowRepository } from "../../repositories/sqlite/SqliteFollowRepository.js"; -import { SqliteInstanceBlockRepository } from "../../repositories/sqlite/SqliteInstanceBlockRepository.js"; -import { randomUUID } from "crypto"; -import type { User, Session, Follow, Note } from "shared"; - -const uuidv4 = randomUUID; - -// NoteCreateInput matches the type expected by SqliteNoteRepository.create -type NoteCreateInput = Omit & { - repliesCount?: number; - renoteCount?: number; -}; - -// Extended Note type returned by findById (includes user info) -interface NoteWithUser extends Note { - user?: { - id: string; - username: string; - name: string | null; - displayName: string | null; - avatarUrl: string | null; - host: string | null; - profileEmojis: unknown; - }; -} - -/** - * Creates a complete Note object with all required fields for testing - */ -function createTestNote(overrides: { - id: string; - userId: string; - text: string; - visibility?: "public" | "home" | "followers" | "specified"; - localOnly?: boolean; -}): NoteCreateInput { - return { - id: overrides.id, - userId: overrides.userId, - text: overrides.text, - cw: null, - visibility: overrides.visibility ?? "public", - localOnly: overrides.localOnly ?? false, - replyId: null, - renoteId: null, - fileIds: [], - mentions: [], - emojis: [], - tags: [], - uri: null, - isDeleted: false, - deletedAt: null, - deletedById: null, - deletionReason: null, - }; -} - -/** - * Creates a complete User object with all required fields for testing - */ -function createTestUser(overrides: { - id: string; - username: string; - email: string; - passwordHash: string; - isAdmin?: boolean; -}): Omit { - return { - id: overrides.id, - username: overrides.username, - email: overrides.email, - passwordHash: overrides.passwordHash, - displayName: null, - bio: null, - avatarUrl: null, - bannerUrl: null, - isAdmin: overrides.isAdmin ?? false, - isSuspended: false, - isDeleted: false, - deletedAt: null, - isSystemUser: false, - publicKey: null, - privateKey: null, - host: null, - inbox: null, - outbox: null, - followersUrl: null, - followingUrl: null, - uri: null, - sharedInbox: null, - customCss: null, - uiSettings: null, - alsoKnownAs: null, - movedTo: null, - movedAt: null, - profileEmojis: null, - storageQuotaMb: null, - goneDetectedAt: null, - fetchFailureCount: 0, - lastFetchAttemptAt: null, - lastFetchError: null, - followersCount: 0, - followingCount: 0, - }; -} - -/** - * Creates a complete Session object with all required fields for testing - */ -function createTestSession(overrides: { - id: string; - userId: string; - token: string; - expiresAt: Date; -}): Omit { - return { - id: overrides.id, - userId: overrides.userId, - token: overrides.token, - expiresAt: overrides.expiresAt, - userAgent: null, - ipAddress: null, - }; -} - -/** - * Creates a complete Follow object with all required fields for testing - */ -function createTestFollow(overrides: { - id: string; - followerId: string; - followeeId: string; -}): Omit { - return { - id: overrides.id, - followerId: overrides.followerId, - followeeId: overrides.followeeId, - }; -} - -describe("SQLite Repository Integration Tests", () => { - let sqlite: Database.Database; - let db: ReturnType>; - - beforeAll(() => { - // Create in-memory SQLite database for testing - sqlite = new Database(":memory:"); - db = drizzle(sqlite, { schema }); - - // Create tables manually for testing (must match Drizzle schema column names exactly) - sqlite.exec(` - CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT NOT NULL, - email TEXT NOT NULL, - password_hash TEXT NOT NULL, - display_name TEXT, - bio TEXT, - avatar_url TEXT, - banner_url TEXT, - is_admin INTEGER NOT NULL DEFAULT 0, - is_suspended INTEGER NOT NULL DEFAULT 0, - is_deleted INTEGER NOT NULL DEFAULT 0, - deleted_at INTEGER, - is_system_user INTEGER NOT NULL DEFAULT 0, - public_key TEXT, - private_key TEXT, - host TEXT, - inbox TEXT, - outbox TEXT, - followers_url TEXT, - following_url TEXT, - uri TEXT, - shared_inbox TEXT, - also_known_as TEXT DEFAULT '[]', - moved_to TEXT, - moved_at INTEGER, - custom_css TEXT, - ui_settings TEXT, - profile_emojis TEXT DEFAULT '[]', - storage_quota_mb INTEGER, - gone_detected_at INTEGER, - fetch_failure_count INTEGER NOT NULL DEFAULT 0, - last_fetch_attempt_at INTEGER, - last_fetch_error TEXT, - followers_count INTEGER NOT NULL DEFAULT 0, - following_count INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ); - - CREATE TABLE IF NOT EXISTS notes ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL REFERENCES users(id), - text TEXT, - cw TEXT, - visibility TEXT NOT NULL DEFAULT 'public', - local_only INTEGER NOT NULL DEFAULT 0, - reply_id TEXT REFERENCES notes(id), - renote_id TEXT REFERENCES notes(id), - file_ids TEXT DEFAULT '[]', - mentions TEXT DEFAULT '[]', - emojis TEXT DEFAULT '[]', - tags TEXT DEFAULT '[]', - uri TEXT UNIQUE, - is_deleted INTEGER NOT NULL DEFAULT 0, - deleted_at INTEGER, - deleted_by_id TEXT REFERENCES users(id), - deletion_reason TEXT, - replies_count INTEGER NOT NULL DEFAULT 0, - renote_count INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ); - - CREATE TABLE IF NOT EXISTS sessions ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL REFERENCES users(id), - token TEXT NOT NULL UNIQUE, - expires_at INTEGER NOT NULL, - user_agent TEXT, - ip_address TEXT, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ); - - CREATE TABLE IF NOT EXISTS follows ( - id TEXT PRIMARY KEY, - follower_id TEXT NOT NULL REFERENCES users(id), - followee_id TEXT NOT NULL REFERENCES users(id), - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - UNIQUE(follower_id, followee_id) - ); - - CREATE TABLE IF NOT EXISTS instance_blocks ( - id TEXT PRIMARY KEY, - host TEXT NOT NULL UNIQUE, - reason TEXT, - blocked_by_id TEXT NOT NULL REFERENCES users(id), - created_at INTEGER NOT NULL - ); - `); - }); - - afterAll(() => { - sqlite.close(); - }); - - describe("SqliteUserRepository", () => { - let userRepo: SqliteUserRepository; - - beforeAll(() => { - userRepo = new SqliteUserRepository(db); - }); - - beforeEach(() => { - // Clean up users table before each test - sqlite.exec("DELETE FROM notes"); - sqlite.exec("DELETE FROM sessions"); - sqlite.exec("DELETE FROM follows"); - sqlite.exec("DELETE FROM users"); - }); - - test("should create a user", async () => { - const userId = uuidv4(); - const user = await userRepo.create( - createTestUser({ - id: userId, - username: "testuser", - email: "test@example.com", - passwordHash: "hashedpassword", - }), - ); - - expect(user.id).toBe(userId); - expect(user.username).toBe("testuser"); - expect(user.email).toBe("test@example.com"); - }); - - test("should find user by id", async () => { - const userId = uuidv4(); - await userRepo.create( - createTestUser({ - id: userId, - username: "findbyid", - email: "findbyid@example.com", - passwordHash: "hash", - }), - ); - - const found = await userRepo.findById(userId); - expect(found).not.toBeNull(); - expect(found?.username).toBe("findbyid"); - }); - - test("should find user by username", async () => { - const userId = uuidv4(); - await userRepo.create( - createTestUser({ - id: userId, - username: "findbyusername", - email: "findbyusername@example.com", - passwordHash: "hash", - }), - ); - - const found = await userRepo.findByUsername("findbyusername"); - expect(found).not.toBeNull(); - expect(found?.id).toBe(userId); - }); - - test("should return null for non-existent user", async () => { - const found = await userRepo.findById("non-existent-id"); - expect(found).toBeNull(); - }); - - test("should update user", async () => { - const userId = uuidv4(); - await userRepo.create( - createTestUser({ - id: userId, - username: "updateuser", - email: "update@example.com", - passwordHash: "hash", - }), - ); - - const updated = await userRepo.update(userId, { - displayName: "Updated Display Name", - bio: "Updated bio", - }); - - expect(updated.displayName).toBe("Updated Display Name"); - expect(updated.bio).toBe("Updated bio"); - }); - - test("should count users", async () => { - const count1 = await userRepo.count(); - expect(count1).toBe(0); - - await userRepo.create( - createTestUser({ - id: uuidv4(), - username: "user1", - email: "user1@example.com", - passwordHash: "hash", - }), - ); - - const count2 = await userRepo.count(); - expect(count2).toBe(1); - }); - }); - - describe("SqliteNoteRepository", () => { - let noteRepo: SqliteNoteRepository; - let testUserId: string; - - beforeAll(async () => { - noteRepo = new SqliteNoteRepository(db); - const userRepo = new SqliteUserRepository(db); - - // Create a test user for notes - testUserId = uuidv4(); - await userRepo.create( - createTestUser({ - id: testUserId, - username: "noteuser", - email: "noteuser@example.com", - passwordHash: "hash", - }), - ); - }); - - beforeEach(() => { - // Clean up notes table before each test - sqlite.exec(`DELETE FROM notes WHERE user_id != '${testUserId}'`); - }); - - test("should create a note", async () => { - const noteId = uuidv4(); - const note = await noteRepo.create( - createTestNote({ - id: noteId, - userId: testUserId, - text: "Hello, world!", - }), - ); - - expect(note.id).toBe(noteId); - expect(note.text).toBe("Hello, world!"); - expect(note.userId).toBe(testUserId); - }); - - test("should find note by id with user", async () => { - const noteId = uuidv4(); - await noteRepo.create( - createTestNote({ - id: noteId, - userId: testUserId, - text: "Find me!", - }), - ); - - const found = (await noteRepo.findById(noteId)) as NoteWithUser | null; - expect(found).not.toBeNull(); - expect(found?.text).toBe("Find me!"); - expect(found?.user).toBeDefined(); - expect(found?.user?.username).toBe("noteuser"); - }); - - test("should return null for non-existent note", async () => { - const found = await noteRepo.findById("non-existent-note-id"); - expect(found).toBeNull(); - }); - - test("should soft delete a note", async () => { - const noteId = uuidv4(); - await noteRepo.create( - createTestNote({ - id: noteId, - userId: testUserId, - text: "Delete me!", - }), - ); - - const deleted = await noteRepo.softDelete(noteId, testUserId, "Test deletion"); - expect(deleted?.isDeleted).toBe(true); - expect(deleted?.deletedById).toBe(testUserId); - expect(deleted?.deletionReason).toBe("Test deletion"); - }); - - test("should restore a soft-deleted note", async () => { - const noteId = uuidv4(); - await noteRepo.create( - createTestNote({ - id: noteId, - userId: testUserId, - text: "Restore me!", - }), - ); - - await noteRepo.softDelete(noteId, testUserId); - const restored = await noteRepo.restore(noteId); - - expect(restored?.isDeleted).toBe(false); - expect(restored?.deletedById).toBeNull(); - }); - - test("should increment and decrement replies count", async () => { - const noteId = uuidv4(); - await noteRepo.create( - createTestNote({ - id: noteId, - userId: testUserId, - text: "Count replies!", - }), - ); - - await noteRepo.incrementRepliesCount(noteId); - await noteRepo.incrementRepliesCount(noteId); - - let note = await noteRepo.findById(noteId); - expect(note?.repliesCount).toBe(2); - - await noteRepo.decrementRepliesCount(noteId); - note = await noteRepo.findById(noteId); - expect(note?.repliesCount).toBe(1); - }); - - test("should count notes", async () => { - // Clear all notes first - sqlite.exec("DELETE FROM notes"); - - const noteId1 = uuidv4(); - const noteId2 = uuidv4(); - - await noteRepo.create( - createTestNote({ - id: noteId1, - userId: testUserId, - text: "Note 1", - }), - ); - - await noteRepo.create( - createTestNote({ - id: noteId2, - userId: testUserId, - text: "Note 2", - }), - ); - - const count = await noteRepo.count(); - expect(count).toBe(2); - }); - }); - - describe("SqliteSessionRepository", () => { - let sessionRepo: SqliteSessionRepository; - let testUserId: string; - - beforeAll(async () => { - sessionRepo = new SqliteSessionRepository(db); - const userRepo = new SqliteUserRepository(db); - - // Create a test user for sessions - testUserId = uuidv4(); - await userRepo.create( - createTestUser({ - id: testUserId, - username: "sessionuser", - email: "sessionuser@example.com", - passwordHash: "hash", - }), - ); - }); - - beforeEach(() => { - sqlite.exec("DELETE FROM sessions"); - }); - - test("should create a session", async () => { - const sessionId = uuidv4(); - const session = await sessionRepo.create( - createTestSession({ - id: sessionId, - userId: testUserId, - token: "test-token-" + sessionId, - expiresAt: new Date(Date.now() + 86400000), - }), - ); - - expect(session.id).toBe(sessionId); - expect(session.userId).toBe(testUserId); - }); - - test("should find session by token", async () => { - const sessionId = uuidv4(); - const token = "find-token-" + sessionId; - await sessionRepo.create( - createTestSession({ - id: sessionId, - userId: testUserId, - token, - expiresAt: new Date(Date.now() + 86400000), - }), - ); - - const found = await sessionRepo.findByToken(token); - expect(found).not.toBeNull(); - expect(found?.id).toBe(sessionId); - }); - - test("should delete session", async () => { - const sessionId = uuidv4(); - await sessionRepo.create( - createTestSession({ - id: sessionId, - userId: testUserId, - token: "delete-token-" + sessionId, - expiresAt: new Date(Date.now() + 86400000), - }), - ); - - await sessionRepo.delete(sessionId); - const found = await sessionRepo.findById(sessionId); - expect(found).toBeNull(); - }); - - test("should delete expired sessions", async () => { - const expiredId = uuidv4(); - const validId = uuidv4(); - - // Create expired session - await sessionRepo.create( - createTestSession({ - id: expiredId, - userId: testUserId, - token: "expired-token", - expiresAt: new Date(Date.now() - 86400000), // Yesterday - }), - ); - - // Create valid session - await sessionRepo.create( - createTestSession({ - id: validId, - userId: testUserId, - token: "valid-token", - expiresAt: new Date(Date.now() + 86400000), // Tomorrow - }), - ); - - const deletedCount = await sessionRepo.deleteExpired(); - expect(deletedCount).toBe(1); - - const expiredSession = await sessionRepo.findById(expiredId); - const validSession = await sessionRepo.findById(validId); - - expect(expiredSession).toBeNull(); - expect(validSession).not.toBeNull(); - }); - }); - - describe("SqliteFollowRepository", () => { - let followRepo: SqliteFollowRepository; - let user1Id: string; - let user2Id: string; - - beforeAll(async () => { - followRepo = new SqliteFollowRepository(db); - const userRepo = new SqliteUserRepository(db); - - user1Id = uuidv4(); - user2Id = uuidv4(); - - await userRepo.create( - createTestUser({ - id: user1Id, - username: "follower", - email: "follower@example.com", - passwordHash: "hash", - }), - ); - - await userRepo.create( - createTestUser({ - id: user2Id, - username: "followee", - email: "followee@example.com", - passwordHash: "hash", - }), - ); - }); - - beforeEach(() => { - sqlite.exec("DELETE FROM follows"); - }); - - test("should create a follow relationship", async () => { - const followId = uuidv4(); - const follow = await followRepo.create( - createTestFollow({ - id: followId, - followerId: user1Id, - followeeId: user2Id, - }), - ); - - expect(follow.id).toBe(followId); - expect(follow.followerId).toBe(user1Id); - expect(follow.followeeId).toBe(user2Id); - }); - - test("should check if following", async () => { - const followId = uuidv4(); - await followRepo.create( - createTestFollow({ - id: followId, - followerId: user1Id, - followeeId: user2Id, - }), - ); - - const isFollowing = await followRepo.exists(user1Id, user2Id); - expect(isFollowing).toBe(true); - - const notFollowing = await followRepo.exists(user2Id, user1Id); - expect(notFollowing).toBe(false); - }); - - test("should get followers", async () => { - const followId = uuidv4(); - await followRepo.create( - createTestFollow({ - id: followId, - followerId: user1Id, - followeeId: user2Id, - }), - ); - - const followers = await followRepo.findByFolloweeId(user2Id); - expect(followers.length).toBe(1); - expect(followers[0]?.followerId).toBe(user1Id); - }); - - test("should get following", async () => { - const followId = uuidv4(); - await followRepo.create( - createTestFollow({ - id: followId, - followerId: user1Id, - followeeId: user2Id, - }), - ); - - const following = await followRepo.findByFollowerId(user1Id); - expect(following.length).toBe(1); - expect(following[0]?.followeeId).toBe(user2Id); - }); - - test("should delete follow relationship", async () => { - const followId = uuidv4(); - await followRepo.create( - createTestFollow({ - id: followId, - followerId: user1Id, - followeeId: user2Id, - }), - ); - - await followRepo.delete(user1Id, user2Id); - const isFollowing = await followRepo.exists(user1Id, user2Id); - expect(isFollowing).toBe(false); - }); - }); - - describe("SqliteInstanceBlockRepository", () => { - let blockRepo: SqliteInstanceBlockRepository; - let adminUserId: string; - - beforeAll(async () => { - blockRepo = new SqliteInstanceBlockRepository(db); - const userRepo = new SqliteUserRepository(db); - - adminUserId = uuidv4(); - await userRepo.create( - createTestUser({ - id: adminUserId, - username: "admin", - email: "admin@example.com", - passwordHash: "hash", - isAdmin: true, - }), - ); - }); - - beforeEach(() => { - sqlite.exec("DELETE FROM instance_blocks"); - }); - - test("should create an instance block", async () => { - const block = await blockRepo.create({ - host: "spam.example.com", - reason: "Spam instance", - blockedById: adminUserId, - }); - - expect(block.host).toBe("spam.example.com"); - expect(block.reason).toBe("Spam instance"); - }); - - test("should check if host is blocked", async () => { - await blockRepo.create({ - host: "blocked.example.com", - reason: null, - blockedById: adminUserId, - }); - - const isBlocked = await blockRepo.isBlocked("blocked.example.com"); - expect(isBlocked).toBe(true); - - const notBlocked = await blockRepo.isBlocked("allowed.example.com"); - expect(notBlocked).toBe(false); - }); - - test("should find block by host", async () => { - await blockRepo.create({ - host: "findme.example.com", - reason: "Test reason", - blockedById: adminUserId, - }); - - const found = await blockRepo.findByHost("findme.example.com"); - expect(found).not.toBeNull(); - expect(found?.reason).toBe("Test reason"); - }); - - test("should delete block by host", async () => { - await blockRepo.create({ - host: "deleteme.example.com", - reason: null, - blockedById: adminUserId, - }); - - const deleted = await blockRepo.deleteByHost("deleteme.example.com"); - expect(deleted).toBe(true); - - const isBlocked = await blockRepo.isBlocked("deleteme.example.com"); - expect(isBlocked).toBe(false); - }); - - test("should return false when deleting non-existent block", async () => { - const deleted = await blockRepo.deleteByHost("nonexistent.example.com"); - expect(deleted).toBe(false); - }); - - test("should count blocks", async () => { - expect(await blockRepo.count()).toBe(0); - - await blockRepo.create({ - host: "spam1.example.com", - reason: null, - blockedById: adminUserId, - }); - - await blockRepo.create({ - host: "spam2.example.com", - reason: null, - blockedById: adminUserId, - }); - - expect(await blockRepo.count()).toBe(2); - }); - }); -});