diff --git a/.gitignore b/.gitignore index 7338ee4..96612c7 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ next-env.d.ts certificates dev/psql +dev/redis .turbo packages/db/generated/client diff --git a/apps/web/package.json b/apps/web/package.json index 5ecb3d4..a606cdc 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -32,11 +32,13 @@ "@slack/web-api": "^7.9.1", "@uidotdev/usehooks": "^2.4.1", "arctic": "^3.1.1", + "bullmq": "^5.45.2", "cheerio": "^1.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", "cmdk": "1.0.0", "hls-video-element": "^1.5.0", + "ioredis": "^5.6.0", "livekit-client": "^2.8.0", "livekit-server-sdk": "^2.9.7", "lucia": "^3.2.2", diff --git a/apps/web/src/app/(protected)/api/stream/follow/route.ts b/apps/web/src/app/(protected)/api/stream/follow/route.ts index 7d55d70..3e29265 100644 --- a/apps/web/src/app/(protected)/api/stream/follow/route.ts +++ b/apps/web/src/app/(protected)/api/stream/follow/route.ts @@ -1,5 +1,5 @@ import { validateRequest } from '@/lib/auth/validate'; -import { getPgBoss } from '@/lib/workers'; +import { getNotificationQueue } from '@/lib/workers'; import { prisma } from '@hctv/db'; import { NextRequest } from 'next/server'; @@ -42,7 +42,7 @@ export async function GET(request: NextRequest) { export async function POST(request: NextRequest) { const { user } = await validateRequest(); - const boss = await getPgBoss(); + const queue = getNotificationQueue(); const searchParams = new URL(request.url).searchParams; const username = searchParams.get('username'); if (!user) { @@ -102,7 +102,7 @@ export async function POST(request: NextRequest) { }, }); - const jobId = await boss.send('notifier:sendMsg', { + const jobId = await queue.add('notifier:sendMsg', { text: `You started following \`${username}\`!\n_Stream notifications are enabled by default. If you want to disable them, you can do so in \`Profile > Notifications\`._`, channel: user.slack_id, }); diff --git a/apps/web/src/instrumentation.ts b/apps/web/src/instrumentation.ts index 7f49062..64ed9f7 100644 --- a/apps/web/src/instrumentation.ts +++ b/apps/web/src/instrumentation.ts @@ -3,11 +3,8 @@ export async function register() { await (await import('@/lib/instrumentation/streamInfo')).default(); } if (process.env.NEXT_RUNTIME === 'nodejs') { - const { getPgBoss } = await import('@/lib/workers'); const { registerWorkers } = await import('@/lib/workers/register'); - await getPgBoss(); - await registerWorkers(); console.log('pgboss workers registered'); diff --git a/apps/web/src/lib/services/redis.ts b/apps/web/src/lib/services/redis.ts new file mode 100644 index 0000000..a18c020 --- /dev/null +++ b/apps/web/src/lib/services/redis.ts @@ -0,0 +1,30 @@ +import Redis from 'ioredis'; + +const createRedisConnection = () => { + return new Redis(process.env.REDIS_URL || 'redis://localhost:6379', { maxRetriesPerRequest: null }); +}; + +const globalForQueue = global as unknown as { + redisConnection: Redis | null; +}; + +if (!globalForQueue.redisConnection) { + globalForQueue.redisConnection = null; +} + +export function getRedisConnection(): Redis { + if (!globalForQueue.redisConnection) { + console.log('Creating new Redis connection...'); + globalForQueue.redisConnection = createRedisConnection(); + } + return globalForQueue.redisConnection; +} + +export async function closeRedisConnection(): Promise { + // Close Redis connection + if (globalForQueue.redisConnection) { + await globalForQueue.redisConnection.quit(); + globalForQueue.redisConnection = null; + console.log('Redis connection closed'); + } +} \ No newline at end of file diff --git a/apps/web/src/lib/workers/index.ts b/apps/web/src/lib/workers/index.ts index 19b9ac0..e508ba2 100644 --- a/apps/web/src/lib/workers/index.ts +++ b/apps/web/src/lib/workers/index.ts @@ -1,112 +1,50 @@ import type { ChatPostMessageArguments } from '@slack/web-api'; -import PgBoss from 'pg-boss'; +import { Queue, Worker } from 'bullmq'; +import { getRedisConnection } from '@/lib/services/redis'; +import snClient from '../services/slackNotifier'; -export type JobName = - | 'notifier:sendMsg'; +// Singleton instances for notifier +const globalForNotifier = global as unknown as { + notificationQueue: Queue | null; + notificationWorker: Worker | null; +}; -export interface JobDefinitions { - 'notifier:sendMsg': { - payload: ChatPostMessageArguments; - result: { - success: boolean; - error?: string; - }; - }; +// Initialize if they don't exist +if (!globalForNotifier.notificationQueue) { + globalForNotifier.notificationQueue = null; + globalForNotifier.notificationWorker = null; } -export type PayloadFor = JobDefinitions[T]['payload']; -export type ResultFor = JobDefinitions[T]['result']; - -export class TypedPgBoss { - private instance: PgBoss; - - constructor(connectionString: string) { - this.instance = new PgBoss(connectionString); - } - - async start(): Promise { - return this.instance.start(); - } - - async stop(): Promise { - return this.instance.stop(); - } - - async send( - name: T, - payload: PayloadFor, - options?: PgBoss.SendOptions - ): Promise { - await this.instance.createQueue(name); - return this.instance.send(name, payload, options!); - } - - async schedule( - name: T, - payload: PayloadFor, - cron: string, - options?: PgBoss.ScheduleOptions - ): Promise { - return this.instance.schedule(name, cron, payload, options); - } - - async work( - name: T, - handler: (job: PgBoss.Job>) => Promise> | void, - options?: PgBoss.WorkOptions - ): Promise { - const wrappedHandler: PgBoss.WorkHandler = async (job: PgBoss.Job | PgBoss.Job[]) => { - const singleJob = Array.isArray(job) ? job[0] : job; - const processedJob = {...singleJob}; - if (Array.isArray(singleJob.data) && singleJob.data.length === 1) { - processedJob.data = singleJob.data[0]; +// Get or create the notification queue +export function getNotificationQueue(): Queue { + if (!globalForNotifier.notificationQueue) { + globalForNotifier.notificationQueue = new Queue('notifications', { + connection: getRedisConnection(), + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + delay: 5000, + }, } - - return await handler(processedJob as PgBoss.Job>); - }; - - return this.instance.work(name, options || {}, wrappedHandler); + }); + } + return globalForNotifier.notificationQueue; +} + +// Cleanup function for notification resources +export async function closeNotificationResources(): Promise { + // Close worker + if (globalForNotifier.notificationWorker) { + await globalForNotifier.notificationWorker.close(); + globalForNotifier.notificationWorker = null; + console.log('Notification worker closed'); } - getInstance(): PgBoss { - return this.instance; + // Close queue + if (globalForNotifier.notificationQueue) { + await globalForNotifier.notificationQueue.close(); + globalForNotifier.notificationQueue = null; + console.log('Notification queue closed'); } -} - -const globalForPgBoss = global as unknown as { pgBoss: TypedPgBoss | null }; - -// Initialize if it doesn't exist yet -if (!globalForPgBoss.pgBoss) { - globalForPgBoss.pgBoss = null; -} - -// Get or create the singleton instance -export async function getPgBoss(): Promise { - if (!globalForPgBoss.pgBoss) { - if (!process.env.DATABASE_URL) { - throw new Error('DATABASE_URL environment variable is not set'); - } - - console.log('Creating new PgBoss instance...'); - const newBoss = new TypedPgBoss(process.env.DATABASE_URL); - - try { - await newBoss.start(); - console.log('PgBoss started successfully'); - globalForPgBoss.pgBoss = newBoss; - } catch (error) { - console.error('Failed to start PgBoss:', error); - throw error; - } - } - - return globalForPgBoss.pgBoss; -} - -export async function closePgBoss(): Promise { - if (globalForPgBoss.pgBoss) { - await globalForPgBoss.pgBoss.stop(); - globalForPgBoss.pgBoss = null; - console.log('PgBoss stopped successfully'); - } -} +} \ No newline at end of file diff --git a/apps/web/src/lib/workers/register.ts b/apps/web/src/lib/workers/register.ts index 2e275ce..8020905 100644 --- a/apps/web/src/lib/workers/register.ts +++ b/apps/web/src/lib/workers/register.ts @@ -1,17 +1,7 @@ -import { getPgBoss } from '@/lib/workers'; -import snClient from '../services/slackNotifier'; +import { registerNotificationWorker } from './worker/notification'; -export async function registerWorkers() { - const boss = await getPgBoss(); - - await boss.work('notifier:sendMsg', async (job) => { - console.log('Processing job:', job.id); - - await snClient.chat.postMessage(job.data).catch(e => { - return { success: false, error: e.message }; - }); - return { success: true }; - }); - +// Register all workers in one place +export async function registerWorkers(): Promise { + await registerNotificationWorker(); console.log('All workers registered successfully'); } \ No newline at end of file diff --git a/apps/web/src/lib/workers/worker/notification.ts b/apps/web/src/lib/workers/worker/notification.ts new file mode 100644 index 0000000..dfe8764 --- /dev/null +++ b/apps/web/src/lib/workers/worker/notification.ts @@ -0,0 +1,63 @@ +import { Worker } from 'bullmq'; +import { getRedisConnection } from '@/lib/services/redis'; +import snClient from '@/lib/services/slackNotifier'; + +const globalForWorker = global as unknown as { + notificationWorker: Worker | null; +}; + +if (!globalForWorker.notificationWorker) { + globalForWorker.notificationWorker = null; +} + +// Register the Slack notification worker +export async function registerNotificationWorker(): Promise { + if (globalForWorker.notificationWorker) { + console.log('Notification worker already registered'); + return; + } + + console.log('Registering notification worker...'); + + const worker = new Worker('notifications', async (job) => { + console.log('Processing job:', job.id); + console.log('Job data:', job.data); + + try { + await snClient.chat.postMessage(job.data); + return { success: true }; + } catch (e) { + console.error('Slack notification failed:', e); + // @ts-ignore e is unknown + return { success: false, error: e.message }; + } + }, { + connection: getRedisConnection(), + concurrency: 1, + limiter: { + max: 45, + duration: 60000 + } + }); + + // Set up event handlers + worker.on('completed', job => { + console.log(`Job ${job.id} completed successfully`); + }); + + worker.on('failed', (job, error) => { + console.error(`Job ${job?.id} failed:`, error); + }); + + globalForWorker.notificationWorker = worker; + console.log('Notification worker registered successfully'); +} + +// Close the worker +export async function closeNotificationWorker(): Promise { + if (globalForWorker.notificationWorker) { + await globalForWorker.notificationWorker.close(); + globalForWorker.notificationWorker = null; + console.log('Notification worker closed'); + } +} \ No newline at end of file diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index 0ae1791..d093337 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -10,6 +10,12 @@ services: - ./psql:/var/lib/postgresql/data ports: - 5555:5432 + redis: + image: redis:7.4-alpine + volumes: + - ./redis:/data + ports: + - 6379:6379 nginx-rtmp: # ports: # - 1935:1935 diff --git a/yarn.lock b/yarn.lock index 17772aa..37c34d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -604,6 +604,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" @@ -690,6 +695,36 @@ resolved "https://registry.yarnpkg.com/@lucia-auth/adapter-prisma/-/adapter-prisma-4.0.1.tgz#8f8aa95bf0ac266fd2fb8ac3dc6dd9faec53342f" integrity sha512-3SztRhj1RAHbbhI/0aB7YC5zl6Z6aktPhkWpn2CHhiB03B9x/+A+M6pqJuAt1usU8PzkjVilgRPhrPymMar66A== +"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz#9edec61b22c3082018a79f6d1c30289ddf3d9d11" + integrity sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw== + +"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz#33677a275204898ad8acbf62734fc4dc0b6a4855" + integrity sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw== + +"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz#19edf7cdc2e7063ee328403c1d895a86dd28f4bb" + integrity sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg== + +"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz#94fb0543ba2e28766c3fc439cabbe0440ae70159" + integrity sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw== + +"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz#4a0609ab5fe44d07c9c60a11e4484d3c38bbd6e3" + integrity sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg== + +"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz#0aa5502d547b57abfc4ac492de68e2006e417242" + integrity sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ== + "@napi-rs/wasm-runtime@^0.2.5", "@napi-rs/wasm-runtime@^0.2.7": version "0.2.7" resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz#288f03812a408bc53c2c3686c65f38fe90f295eb" @@ -2109,6 +2144,19 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bullmq@^5.45.2: + version "5.45.2" + resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-5.45.2.tgz#88e19db218e5f7eceb5885ffac9334c87586701a" + integrity sha512-wHZfcD4z4aLolxREmwNNDSbfh7USeq2e3yu5W2VGkzHMUcrH0fzZuRuCMsjD0XKS9ViK1U854oM9yWR6ftPeDA== + dependencies: + cron-parser "^4.9.0" + ioredis "^5.4.1" + msgpackr "^1.11.2" + node-abort-controller "^3.1.1" + semver "^7.5.4" + tslib "^2.0.0" + uuid "^9.0.0" + bundle-require@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee" @@ -2287,6 +2335,11 @@ clsx@2.1.1, 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== + cmdk@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-1.0.0.tgz#0a095fdafca3dfabed82d1db78a6262fb163ded9" @@ -2508,12 +2561,17 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +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== -detect-libc@^2.0.3: +detect-libc@^2.0.1, detect-libc@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== @@ -3492,6 +3550,21 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" +ioredis@^5.4.1, ioredis@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.6.0.tgz#faa2a27132f8a05c0ddfef400b01d1326df211a0" + integrity sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg== + 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" + is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" @@ -3941,6 +4014,16 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +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" @@ -4109,6 +4192,27 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msgpackr-extract@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz#e9d87023de39ce714872f9e9504e3c1996d61012" + integrity sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA== + dependencies: + node-gyp-build-optional-packages "5.2.2" + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.3" + "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.3" + +msgpackr@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.11.2.tgz#4463b7f7d68f2e24865c395664973562ad24473d" + integrity sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g== + optionalDependencies: + msgpackr-extract "^3.0.2" + mz@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -4156,6 +4260,11 @@ next@^15.2.3: "@next/swc-win32-x64-msvc" "15.2.3" sharp "^0.33.5" +node-abort-controller@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" @@ -4170,6 +4279,13 @@ node-fetch@^3.3.0: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" +node-gyp-build-optional-packages@5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz#522f50c2d53134d7f3a76cd7255de4ab6c96a3a4" + integrity sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw== + dependencies: + detect-libc "^2.0.1" + node-releases@^2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" @@ -4793,6 +4909,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.6, reflect.getprototypeof@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" @@ -5006,7 +5134,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.6.0, semver@^7.6.3: +semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: version "7.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== @@ -5212,6 +5340,11 @@ stable-hash@^0.0.5: resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269" integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA== +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" @@ -5782,6 +5915,11 @@ util-utils@^1.0.3: resolved "https://registry.yarnpkg.com/util-utils/-/util-utils-1.0.3.tgz#abde6c79d373eb7fae42f28933273eab6d895dd3" integrity sha512-KXQzb5Y1cmQOubnYn2TMSJDwX+cLrFNi3CRp0bm+yTUdR4tV4LDkk2RAzGSweucwvTdhuoDdZrY67ds7QDRYXQ== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + valtio@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/valtio/-/valtio-2.1.4.tgz#6f0978b1d6a0a5eed70a1c55981a80c7e2b6e76a"