From ef58db01de326b72f07fc4d6ae96a2e742e8fa2b Mon Sep 17 00:00:00 2001
From: Izan Gil <66965250+SrIzan10@users.noreply.github.com>
Date: Wed, 8 Jan 2025 19:43:40 +0100
Subject: [PATCH] feat: very barebones search
---
dev/docker-compose.yml | 12 +-
package.json | 2 +
src/app/(public)/api/autocomplete/route.ts | 29 +++
src/app/(public)/api/posts/route.ts | 10 +
src/app/(public)/page.tsx | 4 -
.../app/PostBrowser/PostBrowser.tsx | 77 +++++---
src/components/app/Search/Search.tsx | 108 +++++++++++
src/instrumentation.ts | 43 ++--
src/lib/services/ephemeralStorage.ts | 12 ++
yarn.lock | 183 +++++++++++++++++-
10 files changed, 429 insertions(+), 51 deletions(-)
create mode 100644 src/app/(public)/api/autocomplete/route.ts
create mode 100644 src/components/app/Search/Search.tsx
create mode 100644 src/lib/services/ephemeralStorage.ts
diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml
index e79af75..130e396 100644
--- a/dev/docker-compose.yml
+++ b/dev/docker-compose.yml
@@ -8,4 +8,14 @@ services:
volumes:
- ./psql:/var/lib/postgresql/data
ports:
- - 5555:5432
\ No newline at end of file
+ - 5555:5432
+ dragonfly:
+ image: 'docker.dragonflydb.io/dragonflydb/dragonfly'
+ ulimits:
+ memlock: -1
+ ports:
+ - "6379:6379"
+ environment:
+ DRAGONFLY_PASSWORD: dfsjhkdswkjntelsmldbfvsgknl5t
+ volumes:
+ - ./draognfly:/data
\ No newline at end of file
diff --git a/package.json b/package.json
index a8f75d8..1b86cb1 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.0",
"cron": "^3.3.2",
+ "ioredis": "^5.4.2",
"lucia": "^3.1.1",
"lucide-react": "^0.368.0",
"minio": "^8.0.3",
@@ -37,6 +38,7 @@
"sonner": "^1.4.41",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
+ "unstorage": "^1.14.4",
"zod": "^3.24.1"
},
"devDependencies": {
diff --git a/src/app/(public)/api/autocomplete/route.ts b/src/app/(public)/api/autocomplete/route.ts
new file mode 100644
index 0000000..4d53ef8
--- /dev/null
+++ b/src/app/(public)/api/autocomplete/route.ts
@@ -0,0 +1,29 @@
+import ephemeralStorage from '@/lib/services/ephemeralStorage';
+import type { NextRequest } from 'next/server';
+
+export async function GET(request: NextRequest) {
+ const searchParams = request.nextUrl.searchParams;
+ const query = searchParams.get('q')!.toLowerCase().split(' ').at(-1);
+ if (query !== '' && !query) {
+ return new Response('Invalid query', { status: 400 });
+ }
+
+ const tags = await ephemeralStorage.keys('tag');
+ const filteredTags = tags.filter((tag) => tag.replace('tag:', '').includes(query));
+
+ const mappedTags = await Promise.all(
+ filteredTags.map(async (t) => {
+ const getTag = parseInt((await ephemeralStorage.get(t)) as string);
+ return { tag: t.replace('tag:', ''), count: getTag };
+ })
+ );
+ mappedTags.sort((a, b) => {
+ return b.count - a.count;
+ });
+
+ return new Response(JSON.stringify(mappedTags), {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+}
diff --git a/src/app/(public)/api/posts/route.ts b/src/app/(public)/api/posts/route.ts
index 7855818..57d90c9 100644
--- a/src/app/(public)/api/posts/route.ts
+++ b/src/app/(public)/api/posts/route.ts
@@ -4,14 +4,24 @@ import { type NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const page = parseInt(searchParams.get('page')!);
+ const query = searchParams.get('q')?.trim().split(' ')!;
if (page && isNaN(page)) {
return new Response('Invalid page number', { status: 400 });
}
+ if (!query.length) {
+ return new Response('Invalid query', { status: 400 });
+ }
+ // TODO: negative tags
const queryPosts = await prisma.post.findMany({
take: 30,
skip: page * 30,
orderBy: { createdAt: 'desc' },
+ where: {
+ tags: {
+ hasEvery: query[0] !== '' ? query : [],
+ },
+ },
});
return new Response(JSON.stringify(queryPosts), {
diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx
index f2a5dae..51b9815 100644
--- a/src/app/(public)/page.tsx
+++ b/src/app/(public)/page.tsx
@@ -1,10 +1,6 @@
import PostBrowser from '@/components/app/PostBrowser/PostBrowser';
-import prisma from '@/lib/db';
-import Image from 'next/image';
-import Link from 'next/link';
export default async function Home() {
- const posts = await prisma.post.findMany({ take: 30, orderBy: { createdAt: 'desc' } });
return (
This is nextbooru
diff --git a/src/components/app/PostBrowser/PostBrowser.tsx b/src/components/app/PostBrowser/PostBrowser.tsx
index f6105dd..aac847d 100644
--- a/src/components/app/PostBrowser/PostBrowser.tsx
+++ b/src/components/app/PostBrowser/PostBrowser.tsx
@@ -6,16 +6,24 @@ import { Skeleton } from '@/components/ui/skeleton';
import { Post } from '@prisma/client';
import Image from 'next/image';
import Link from 'next/link';
+import Search from '../Search/Search';
export default function PostBrowser() {
const [page, setPage] = React.useState(0);
const [loading, setLoading] = React.useState(false);
const [hasMore, setHasMore] = React.useState(true);
const [posts, setPosts] = React.useState
([]);
+ const [search, setSearch] = React.useState('');
- const next = async () => {
+ const next = async (reset = false, query = search) => {
setLoading(true);
- const res = await fetch(`/api/posts?page=${page}`);
+ const currentPage = reset ? 0 : page;
+ if (reset) {
+ setPosts([]);
+ setPage(0);
+ setHasMore(true);
+ }
+ const res = await fetch(`/api/posts?page=${currentPage}&q=${query}`);
const newImages = await res.json();
if (!newImages.length) {
setHasMore(false);
@@ -28,37 +36,42 @@ export default function PostBrowser() {
};
return (
-
-
- {posts.map((post) => (
-
-
-
-
- ))}
- {loading &&
- Array.from({ length: page === 0 ? 12 : 2 }).map((_, i) => (
-
-
+ <>
+
{
+ setSearch(q);
+ next(true, q);
+ }} />
+
+
+ {posts.map((post) => (
+
+
+
))}
-
+ {loading &&
+ Array.from({ length: page === 0 ? 12 : 2 }).map((_, i) => (
+
+
+
+ ))}
+
- {/* interactionobserver sentinel */}
-
-
+ {/* interactionobserver sentinel */}
+
+
+ >
);
-}
-
+}
\ No newline at end of file
diff --git a/src/components/app/Search/Search.tsx b/src/components/app/Search/Search.tsx
new file mode 100644
index 0000000..ad55915
--- /dev/null
+++ b/src/components/app/Search/Search.tsx
@@ -0,0 +1,108 @@
+'use client'
+
+import React, { useState, useEffect, useRef } from 'react'
+import { Input } from "@/components/ui/input"
+import { Button } from "@/components/ui/button"
+import { Search as SearchIcon } from 'lucide-react'
+
+export default function Search(props: Props) {
+ const [input, setInput] = useState('')
+ const [suggestions, setSuggestions] = useState<{ tag: string, count: string }[]>([])
+ const [selectedIndex, setSelectedIndex] = useState(-1)
+ const inputRef = useRef
(null)
+
+ useEffect(() => {
+ const fetchSuggestions = async () => {
+ const lastTag = input.split(' ').pop() || ''
+ if (lastTag.length > 0) {
+ try {
+ const response = await fetch(`/api/autocomplete?q=${encodeURIComponent(lastTag)}`)
+ if (response.ok) {
+ const data = await response.json()
+ setSuggestions(data.slice(0, 5))
+ } else {
+ setSuggestions([])
+ }
+ } catch (error) {
+ setSuggestions([])
+ }
+ } else {
+ setSuggestions([])
+ }
+ setSelectedIndex(-1)
+ }
+
+ fetchSuggestions()
+ }, [input])
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'ArrowDown') {
+ setSelectedIndex(prev => (prev < suggestions.length - 1 ? prev + 1 : prev))
+ e.preventDefault()
+ } else if (e.key === 'ArrowUp') {
+ setSelectedIndex(prev => (prev > 0 ? prev - 1 : -1))
+ e.preventDefault()
+ } else if (e.key === 'Enter' && selectedIndex >= 0) {
+ const tags = input.split(' ').slice(0, -1)
+ tags.push(suggestions[selectedIndex].tag)
+ setInput(tags.join(' ') + ' ')
+ setSuggestions([])
+ e.preventDefault()
+ } else if (e.key === 'Escape') {
+ setSuggestions([])
+ } else if (e.key === 'Enter') {
+ props.onSearch(input)
+ }
+ }
+
+ const handleSuggestionClick = (suggestion: string) => {
+ const tags = input.split(' ').slice(0, -1)
+ tags.push(suggestion)
+ setInput(tags.join(' ') + ' ')
+ setSuggestions([])
+ inputRef.current?.focus()
+ }
+
+ return (
+
+
+ setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ className="pr-10 font-mono"
+ />
+
+
+ {suggestions.length > 0 && (
+
+ {suggestions.map((suggestion, index) => (
+ - handleSuggestionClick(suggestion.tag)}
+ >
+ {suggestion.tag} ({suggestion.count})
+
+ ))}
+
+ )}
+
+ )
+}
+
+interface Props {
+ onSearch: (q: string) => void
+}
\ No newline at end of file
diff --git a/src/instrumentation.ts b/src/instrumentation.ts
index 39114ef..cc88a19 100644
--- a/src/instrumentation.ts
+++ b/src/instrumentation.ts
@@ -1,16 +1,16 @@
import prisma from './lib/db';
+import ephemeralStorage from './lib/services/ephemeralStorage';
export async function register() {
- if (process.env.SAFEBOORU_PULL !== 'true') return;
if (process.env.NEXT_RUNTIME === 'nodejs') {
- const { CronJob } = await import('cron');
const crypto = await import('crypto');
const { generateId } = await import('lucia');
- const fs = await import('fs/promises');
const { default: hashImage } = await import('@/lib/hashImage');
const minio = (await import('@/lib/services/minio')).default;
-
- const job = async () => {
+ const { performance } = await import('perf_hooks');
+
+ const safebooruJob = async () => {
+ if (process.env.SAFEBOORU_PULL !== 'true') return;
console.log('Deleting prior safebooru posts and accounts...');
await prisma.post.deleteMany({
where: { author: { username: { startsWith: 'safebooru-' } } },
@@ -28,14 +28,13 @@ export async function register() {
console.log('Pulling safebooru images...');
console.log('Fetching...');
const res = await fetch(
- 'https://safebooru.org/index.php?page=dapi&s=post&q=index&json=1&limit=40',
+ 'https://safebooru.org/index.php?page=dapi&s=post&q=index&json=1&limit=300',
{ headers: { 'User-Agent': 'nextbooru' } }
);
const posts = await res.json();
-
- const genId = generateId(6);
-
+
console.log('Creating account...');
+ const genId = generateId(6);
const account = await prisma.user.create({
data: {
username: `safebooru-${genId}`,
@@ -49,7 +48,6 @@ export async function register() {
const savedFilename = `http${minioIsSSL ? 's' : ''}://${process.env.MINIO_ENDPOINT}/${
process.env.MINIO_BUCKET
}/safebooru-${genId}-${post.id}.jpg`;
- //await fs.writeFile(savedFilename, new Uint8Array(imageUrl));
await minio.putObject(
process.env.MINIO_BUCKET!,
`safebooru-${genId}-${post.id}.jpg`,
@@ -68,9 +66,30 @@ export async function register() {
console.log(`Downloaded id ${post.id}`);
}
};
+ // await safebooruJob();
- await job();
+ const writeTagsToEphemeral = async () => {
+ // TODO: move tags to another table. this is a temporary and inefficient solution.
+ const perfStart = performance.now();
+ const posts = await prisma.post.findMany({ select: { tags: true } });
+ const tags = posts.flatMap((post) => post.tags);
- // new CronJob('0 */2 * * *', async () => await job(), null, true);
+ const occurrences: Record = {};
+ for (const tag of tags) {
+ if (occurrences[tag]) {
+ occurrences[tag]++;
+ } else {
+ occurrences[tag] = 1;
+ }
+ }
+
+ for (const [tag, count] of Object.entries(occurrences)) {
+ await ephemeralStorage.set(`tag:${tag}`, count);
+ }
+ const perfEnd = performance.now();
+
+ console.log(`Writing tags to ephemeral took ${Math.round(perfEnd - perfStart)}ms`);
+ }
+ await writeTagsToEphemeral();
}
}
diff --git a/src/lib/services/ephemeralStorage.ts b/src/lib/services/ephemeralStorage.ts
new file mode 100644
index 0000000..bab0a6e
--- /dev/null
+++ b/src/lib/services/ephemeralStorage.ts
@@ -0,0 +1,12 @@
+import { createStorage } from "unstorage";
+import redisDriver from 'unstorage/drivers/redis';
+
+const ephemeralStorage = createStorage({
+ driver: redisDriver({
+ host: process.env.REDIS_HOST,
+ port: Number(process.env.REDIS_PORT),
+ password: process.env.REDIS_PASSWORD,
+ }),
+});
+
+export default ephemeralStorage;
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 9505742..477486c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -473,6 +473,11 @@
resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342"
integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==
+"@ioredis/commands@^1.1.1":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11"
+ integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==
+
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -1430,7 +1435,7 @@ any-promise@^1.0.0:
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
-anymatch@~3.1.2:
+anymatch@^3.1.3, anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
@@ -1760,7 +1765,7 @@ chalk@^5.0.0:
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
-chokidar@^3.5.3:
+chokidar@^3.5.3, chokidar@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@@ -1809,6 +1814,11 @@ clsx@^2.1.0, clsx@^2.1.1:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+cluster-key-slot@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
+ integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
+
code-block-writer@^12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770"
@@ -1857,11 +1867,21 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+consola@^3.2.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-3.3.3.tgz#0dd8a2314b0f7bf18a49064138ad685f3346543d"
+ integrity sha512-Qil5KwghMzlqd51UXM0b6fyaGHtOC22scxrwrz4A2882LyUMwQjnvaedN1HAeXzphspQ6CpHkzMAWxBTUruDLg==
+
convert-source-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+cookie-es@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.2.2.tgz#18ceef9eb513cac1cb6c14bcbf8bdb2679b34821"
+ integrity sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==
+
cosmiconfig@^8.1.3:
version "8.3.6"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
@@ -1898,6 +1918,13 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
+"crossws@>=0.2.0 <0.4.0":
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.3.1.tgz#7980e0b6688fe23286661c3ab8deeccbaa05ca86"
+ integrity sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==
+ dependencies:
+ uncrypto "^0.1.3"
+
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@@ -2006,11 +2033,26 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
+defu@^6.1.4:
+ version "6.1.4"
+ resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479"
+ integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==
+
+denque@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
+ integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
+
dequal@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+destr@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449"
+ integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==
+
detect-libc@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
@@ -2792,6 +2834,22 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+h3@^1.13.0:
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/h3/-/h3-1.13.0.tgz#b5347a8936529794b6754b440e26c0ab8a60dceb"
+ integrity sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==
+ dependencies:
+ cookie-es "^1.2.2"
+ crossws ">=0.2.0 <0.4.0"
+ defu "^6.1.4"
+ destr "^2.0.3"
+ iron-webcrypto "^1.2.1"
+ ohash "^1.1.4"
+ radix3 "^1.1.2"
+ ufo "^1.5.4"
+ uncrypto "^0.1.3"
+ unenv "^1.10.0"
+
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
@@ -2903,11 +2961,31 @@ invariant@^2.2.4:
dependencies:
loose-envify "^1.0.0"
+ioredis@^5.4.2:
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.2.tgz#ebb6f1a10b825b2c0fb114763d7e82114a0bee6c"
+ integrity sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg==
+ dependencies:
+ "@ioredis/commands" "^1.1.1"
+ cluster-key-slot "^1.1.0"
+ debug "^4.3.4"
+ denque "^2.1.0"
+ lodash.defaults "^4.2.0"
+ lodash.isarguments "^3.1.0"
+ redis-errors "^1.2.0"
+ redis-parser "^3.0.0"
+ standard-as-callback "^2.1.0"
+
ipaddr.js@^2.0.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
+iron-webcrypto@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz#aa60ff2aa10550630f4c0b11fd2442becdb35a6f"
+ integrity sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==
+
is-arguments@^1.0.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b"
@@ -3327,6 +3405,16 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==
+lodash.defaults@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
+ integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
+
+lodash.isarguments@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+ integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
+
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -3372,6 +3460,11 @@ lru-cache@^10.2.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.1.tgz#e8d901141f22937968e45a6533d52824070151e4"
integrity sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==
+lru-cache@^10.4.3:
+ version "10.4.3"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
+ integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -3452,6 +3545,11 @@ mime-types@^2.1.35:
dependencies:
mime-db "1.52.0"
+mime@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
+ integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
+
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@@ -3587,6 +3685,11 @@ node-domexception@^1.0.0:
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
+node-fetch-native@^1.6.4:
+ version "1.6.4"
+ resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e"
+ integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==
+
node-fetch@^3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"
@@ -3689,6 +3792,20 @@ object.values@^1.1.6, object.values@^1.1.7:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
+ofetch@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.4.1.tgz#b6bf6b0d75ba616cef6519dd8b6385a8bae480ec"
+ integrity sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==
+ dependencies:
+ destr "^2.0.3"
+ node-fetch-native "^1.6.4"
+ ufo "^1.5.4"
+
+ohash@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72"
+ integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==
+
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -3819,6 +3936,11 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+pathe@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
+ integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
+
picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@@ -3957,6 +4079,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+radix3@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.2.tgz#fd27d2af3896c6bf4bcdfab6427c69c2afc69ec0"
+ integrity sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==
+
react-dom@19:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
@@ -4041,6 +4168,18 @@ recast@^0.23.2:
tiny-invariant "^1.3.3"
tslib "^2.0.1"
+redis-errors@^1.0.0, redis-errors@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
+ integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==
+
+redis-parser@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
+ integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==
+ dependencies:
+ redis-errors "^1.0.0"
+
reflect.getprototypeof@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859"
@@ -4333,6 +4472,11 @@ split-on-first@^1.0.0:
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
+standard-as-callback@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
+ integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
+
stdin-discarder@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.1.0.tgz#22b3e400393a8e28ebf53f9958f3880622efde21"
@@ -4715,6 +4859,11 @@ typescript@^5:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
+ufo@^1.5.4:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"
+ integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==
+
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
@@ -4725,16 +4874,46 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
+uncrypto@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b"
+ integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==
+
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+unenv@^1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.10.0.tgz#c3394a6c6e4cfe68d699f87af456fe3f0db39571"
+ integrity sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==
+ dependencies:
+ consola "^3.2.3"
+ defu "^6.1.4"
+ mime "^3.0.0"
+ node-fetch-native "^1.6.4"
+ pathe "^1.1.2"
+
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
+unstorage@^1.14.4:
+ version "1.14.4"
+ resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.14.4.tgz#620dd68997a3245fca1e04c0171335817525bc3d"
+ integrity sha512-1SYeamwuYeQJtJ/USE1x4l17LkmQBzg7deBJ+U9qOBoHo15d1cDxG4jM31zKRgF7pG0kirZy4wVMX6WL6Zoscg==
+ dependencies:
+ anymatch "^3.1.3"
+ chokidar "^3.6.0"
+ destr "^2.0.3"
+ h3 "^1.13.0"
+ lru-cache "^10.4.3"
+ node-fetch-native "^1.6.4"
+ ofetch "^1.4.1"
+ ufo "^1.5.4"
+
update-browserslist-db@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5"