├── .gitignore ├── src ├── shared │ ├── index.ts │ └── types.ts ├── server │ ├── index.ts │ ├── utils.ts │ ├── handler.ts │ └── realtime.ts └── client │ ├── index.ts │ ├── use-realtime.ts │ └── provider.tsx ├── public └── thumbnail.png ├── context7.json ├── tsup.config.ts ├── tsconfig.json ├── README.md ├── LICENSE ├── package.json └── bun.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types" 2 | -------------------------------------------------------------------------------- /src/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./handler.js" 2 | export * from "./realtime.js" 3 | -------------------------------------------------------------------------------- /public/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/realtime/main/public/thumbnail.png -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./use-realtime.js" 2 | export * from "./provider.js" 3 | -------------------------------------------------------------------------------- /context7.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://context7.com/upstash/realtime", 3 | "public_key": "pk_xGyFaAYFooMoYbTLzPgYf" 4 | } 5 | -------------------------------------------------------------------------------- /src/server/utils.ts: -------------------------------------------------------------------------------- 1 | export function compareStreamIds(a: string, b: string): number { 2 | const [aTime = 0, aSeq = 0] = a.split("-").map(Number) 3 | const [bTime = 0, bSeq = 0] = b.split("-").map(Number) 4 | 5 | if (aTime !== bTime) return aTime - bTime 6 | return aSeq - bSeq 7 | } -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/client", "src/server", "src/shared"], 5 | format: ["cjs", "esm"], 6 | splitting: false, 7 | clean: true, 8 | bundle: false, 9 | dts: true, 10 | treeshake: true, 11 | minify: false, 12 | sourcemap: false, 13 | }) 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "bundler", 4 | "module": "esnext", 5 | 6 | "target": "esnext", 7 | "types": ["node"], 8 | "sourceMap": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "noUncheckedIndexedAccess": true, 12 | "exactOptionalPropertyTypes": true, 13 | "strict": true, 14 | "stripInternal": true, 15 | "jsx": "react-jsx", 16 | "isolatedModules": true, 17 | "noUncheckedSideEffectImports": true, 18 | "moduleDetection": "force", 19 | "skipLibCheck": true, 20 | "noEmit": true, 21 | 22 | "allowSyntheticDefaultImports": true, 23 | "esModuleInterop": true, 24 | 25 | "verbatimModuleSyntax": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Upstash Realtime 2 | 3 | The easiest way to add realtime features to any Next.js project. 4 | 5 | ![Project Image](https://github.com/upstash/realtime/blob/main/public/thumbnail.png) 6 | 7 | ## Features 8 | 9 | - ⏰ Setup takes 60 seconds 10 | - 🧨 Clean APIs & first-class TypeScript support 11 | - ⚡ Extremely fast, zero dependencies, 1.9kB gzipped 12 | - 💻 Deploy anywhere: Vercel, Netlify, etc. 13 | - 💎 100% type-safe with zod 4 or zod mini 14 | - ⏱️ Built-in message histories 15 | - 🔌 Automatic connection management w/ delivery guarantee 16 | - 🔋 Built-in middleware and authentication helpers 17 | - 📶 100% HTTP-based: Redis streams & SSE 18 | 19 | --- 20 | 21 | ## Quickstart 22 | 23 | Upstash Realtime quickstart, documentation & code examples 👇 24 | 25 | [https://upstash.com/docs/realtime/overall/quickstart](https://upstash.com/docs/realtime/overall/quickstart) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 Upstash, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@upstash/realtime", 3 | "version": "1.0.0", 4 | "author": "Joscha Neske", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/upstash/realtime" 8 | }, 9 | "main": "dist/server/index.js", 10 | "module": "dist/server/index.js", 11 | "devDependencies": { 12 | "@types/node": "^24.6.0", 13 | "@types/react": "^19.1.16", 14 | "@types/react-dom": "^19.1.9", 15 | "tsup": "^8.5.0", 16 | "typescript": "^5.9.2", 17 | "zod": "^4.1.11" 18 | }, 19 | "peerDependencies": { 20 | "@upstash/redis": ">=1.35.4", 21 | "zod": "^3.25.0 || ^4.0.0", 22 | "react": ">=16.8.0", 23 | "react-dom": ">=16.8.0" 24 | }, 25 | "peerDependenciesMeta": { 26 | "react": { 27 | "optional": true 28 | }, 29 | "react-dom": { 30 | "optional": true 31 | } 32 | }, 33 | "exports": { 34 | ".": { 35 | "import": { 36 | "types": "./dist/server/index.d.ts", 37 | "default": "./dist/server/index.js" 38 | }, 39 | "require": { 40 | "types": "./dist/server/index.d.ts", 41 | "default": "./dist/server/index.js" 42 | } 43 | }, 44 | "./client": { 45 | "import": { 46 | "types": "./dist/client/index.d.ts", 47 | "default": "./dist/client/index.js" 48 | }, 49 | "require": { 50 | "types": "./dist/client/index.d.ts", 51 | "default": "./dist/client/index.js" 52 | } 53 | } 54 | }, 55 | "access": "public", 56 | "description": "An HTTP-based realtime client powered by Redis Streams.", 57 | "files": [ 58 | "dist" 59 | ], 60 | "homepage": "https://github.com/upstash/realtime", 61 | "keywords": [ 62 | "realtime", 63 | "serverless", 64 | "upstash" 65 | ], 66 | "license": "MIT", 67 | "scripts": { 68 | "build": "tsup", 69 | "lint": "tsc" 70 | }, 71 | "type": "module", 72 | "types": "dist/server/index.d.ts" 73 | } 74 | -------------------------------------------------------------------------------- /src/client/use-realtime.ts: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useRef } from "react" 2 | import { 3 | EventPaths, 4 | EventPayloadUnion, 5 | userEvent 6 | } from "../shared/types.js" 7 | import { RealtimeContext } from "./provider.js" 8 | 9 | export interface UseRealtimeOpts, E extends string> { 10 | events?: readonly E[] 11 | onData?: (arg: EventPayloadUnion) => void 12 | channels?: readonly (string | undefined)[] 13 | enabled?: boolean 14 | } 15 | 16 | export function useRealtime, const E extends EventPaths>( 17 | opts: UseRealtimeOpts 18 | ) { 19 | const { channels = ["default"], events, onData, enabled } = opts 20 | 21 | const context = useContext(RealtimeContext) 22 | 23 | if (!context) { 24 | throw new Error( 25 | "useRealtime: No RealtimeProvider found. Wrap your app in to use Upstash Realtime." 26 | ) 27 | } 28 | 29 | const registrationId = useRef(Math.random().toString(36).substring(2)).current 30 | const onDataRef = useRef(onData) 31 | onDataRef.current = onData 32 | 33 | useEffect(() => { 34 | if (enabled === false) { 35 | context.unregister(registrationId) 36 | return 37 | } 38 | 39 | const validChannels = channels.filter(Boolean) as string[] 40 | if (validChannels.length === 0) return 41 | 42 | context.register(registrationId, validChannels, (msg) => { 43 | const result = userEvent.safeParse(msg) 44 | 45 | if (result.success) { 46 | const { event, channel, data } = result.data 47 | 48 | if (events && events.length > 0 && !events.includes(event as E)) { 49 | return 50 | } 51 | 52 | const payload: EventPayloadUnion = { event, data, channel } 53 | 54 | // @ts-expect-error EventPayloadUnion generic mismatch 55 | onDataRef.current?.(payload) 56 | } 57 | }) 58 | 59 | return () => { 60 | context.unregister(registrationId) 61 | } 62 | }, [JSON.stringify(channels), enabled, JSON.stringify(events)]) 63 | 64 | return { status: context.status } 65 | } 66 | -------------------------------------------------------------------------------- /src/shared/types.ts: -------------------------------------------------------------------------------- 1 | import z from "zod/v4" 2 | import core from "zod/v4/core" 3 | 4 | export const systemEvent = z.discriminatedUnion("type", [ 5 | z.object({ 6 | type: z.literal("connected"), 7 | channel: z.string(), 8 | cursor: z.string().optional(), 9 | }), 10 | z.object({ type: z.literal("reconnect"), timestamp: z.number() }), 11 | z.object({ type: z.literal("error"), error: z.string() }), 12 | z.object({ type: z.literal("disconnected"), channels: z.array(z.string()) }), 13 | z.object({ type: z.literal("ping"), timestamp: z.number() }), 14 | ]) 15 | 16 | export type SystemEvent = z.infer 17 | 18 | export const userEvent = z.object({ 19 | id: z.string(), 20 | data: z.unknown(), 21 | event: z.string(), 22 | channel: z.string(), 23 | }) 24 | 25 | export type UserEvent = z.infer 26 | 27 | export type RealtimeMessage = SystemEvent | UserEvent 28 | 29 | export type ConnectionStatus = "connected" | "disconnected" | "error" | "connecting" 30 | 31 | export type EventPaths< 32 | T, 33 | Prefix extends string = "", 34 | Depth extends readonly number[] = [] 35 | > = Depth["length"] extends 10 36 | ? never 37 | : { 38 | [K in keyof T & string]: T[K] extends core.$ZodType 39 | ? `${Prefix}${K}` 40 | : T[K] extends Record 41 | ? EventPaths 42 | : `${Prefix}${K}` 43 | }[keyof T & string] 44 | 45 | type EventData< 46 | T, 47 | K extends string, 48 | Depth extends readonly number[] = [] 49 | > = Depth["length"] extends 10 50 | ? never 51 | : K extends `${infer A}.${infer Rest}` 52 | ? A extends keyof T 53 | ? T[A] extends core.$ZodType 54 | ? never 55 | : EventData 56 | : never 57 | : K extends keyof T 58 | ? T[K] extends core.$ZodType 59 | ? T[K] 60 | : never 61 | : never 62 | 63 | export type EventPayloadUnion = E extends any 64 | ? { event: E; data: core.infer>; channel: string } 65 | : never 66 | 67 | export type HistoryArgs = { limit?: number; start?: number; end?: number } 68 | -------------------------------------------------------------------------------- /src/client/provider.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | createContext, 3 | useCallback, 4 | useContext, 5 | useEffect, 6 | useRef, 7 | useState, 8 | } from "react" 9 | import { 10 | userEvent, 11 | systemEvent, 12 | type ConnectionStatus, 13 | type RealtimeMessage, 14 | EventPaths, 15 | } from "../shared/types" 16 | import { useRealtime, UseRealtimeOpts } from "./use-realtime" 17 | 18 | type RealtimeContextValue = { 19 | status: ConnectionStatus 20 | register: (id: string, channels: string[], cb: (msg: RealtimeMessage) => void) => void 21 | unregister: (id: string) => void 22 | } 23 | 24 | export const RealtimeContext = createContext(null) 25 | 26 | const PING_TIMEOUT_MS = 75_000 27 | 28 | export interface RealtimeProviderProps { 29 | children: React.ReactNode 30 | api?: { url?: string; withCredentials?: boolean } 31 | maxReconnectAttempts?: number 32 | } 33 | 34 | export function RealtimeProvider({ 35 | children, 36 | api = { url: "/api/realtime", withCredentials: false }, 37 | maxReconnectAttempts = 3, 38 | }: RealtimeProviderProps) { 39 | const [status, setStatus] = useState("disconnected") 40 | 41 | const localSubsRef = useRef< 42 | Map; cb: (msg: RealtimeMessage) => void }> 43 | >(new Map()) 44 | 45 | const eventSourceRef = useRef(null) 46 | const reconnectTimeoutRef = useRef(null) 47 | const pingTimeoutRef = useRef(null) 48 | const reconnectAttemptsRef = useRef(0) 49 | const lastAckRef = useRef>(new Map()) 50 | const connectTimeoutRef = useRef(null) 51 | const debounceTimeoutRef = useRef(null) 52 | 53 | const getAllNeededChannels = useCallback(() => { 54 | const channels = new Set() 55 | localSubsRef.current.forEach((sub) => { 56 | sub.channels.forEach((ch) => channels.add(ch)) 57 | }) 58 | return channels 59 | }, []) 60 | 61 | const cleanup = () => { 62 | if (eventSourceRef.current) { 63 | eventSourceRef.current.close() 64 | eventSourceRef.current = null 65 | } 66 | if (reconnectTimeoutRef.current) { 67 | clearTimeout(reconnectTimeoutRef.current) 68 | reconnectTimeoutRef.current = null 69 | } 70 | if (pingTimeoutRef.current) { 71 | clearTimeout(pingTimeoutRef.current) 72 | pingTimeoutRef.current = null 73 | } 74 | if (connectTimeoutRef.current) { 75 | clearTimeout(connectTimeoutRef.current) 76 | connectTimeoutRef.current = null 77 | } 78 | 79 | reconnectAttemptsRef.current = 0 80 | 81 | setStatus("disconnected") 82 | } 83 | 84 | const resetPingTimeout = useCallback(() => { 85 | if (pingTimeoutRef.current) { 86 | clearTimeout(pingTimeoutRef.current) 87 | } 88 | 89 | pingTimeoutRef.current = setTimeout(() => { 90 | console.warn("Connection timed out, reconnecting...") 91 | connect() 92 | }, PING_TIMEOUT_MS) 93 | }, []) 94 | 95 | type ConnectOpts = { 96 | replayEventsSince?: number 97 | } 98 | 99 | const connect = (opts?: ConnectOpts) => { 100 | const { replayEventsSince } = opts ?? { replayEventsSince: Date.now() } 101 | const channels = Array.from(getAllNeededChannels()) 102 | 103 | if (channels.length === 0) return 104 | 105 | if (reconnectAttemptsRef.current >= maxReconnectAttempts) { 106 | console.log("Max reconnection attempts reached.") 107 | setStatus("error") 108 | return 109 | } 110 | 111 | cleanup() 112 | 113 | setStatus("connecting") 114 | 115 | try { 116 | const channelsParam = channels 117 | .map((ch) => `channel=${encodeURIComponent(ch)}`) 118 | .join("&") 119 | 120 | const lastAckParam = channels 121 | .map((c) => { 122 | const lastAck = lastAckRef.current.get(c) ?? String(replayEventsSince) 123 | return `last_ack_${encodeURIComponent(c)}=${encodeURIComponent(lastAck)}` 124 | }) 125 | .join("&") 126 | 127 | const url = api.url + "?" + channelsParam + "&" + lastAckParam 128 | 129 | const eventSource = new EventSource(url, { 130 | withCredentials: api.withCredentials ?? false, 131 | }) 132 | eventSourceRef.current = eventSource 133 | 134 | eventSource.onopen = () => { 135 | reconnectAttemptsRef.current = 0 136 | setStatus("connected") 137 | resetPingTimeout() 138 | } 139 | 140 | eventSource.onmessage = (evt) => { 141 | try { 142 | const payload: RealtimeMessage = JSON.parse(evt.data) 143 | resetPingTimeout() 144 | 145 | handleMessage(payload) 146 | 147 | const systemResult = systemEvent.safeParse(payload) 148 | 149 | if (systemResult.success) { 150 | if (systemResult.data.type === "reconnect") { 151 | connect({ replayEventsSince: systemResult.data.timestamp }) 152 | } 153 | } 154 | } catch (error) { 155 | console.warn("Error parsing message:", error) 156 | } 157 | } 158 | 159 | eventSource.onerror = () => { 160 | if (eventSource !== eventSourceRef.current) return 161 | 162 | const readyState = eventSourceRef.current?.readyState 163 | if (readyState === EventSource.CONNECTING) return 164 | 165 | if (readyState === EventSource.CLOSED) { 166 | console.log("Connection closed, reconnecting...") 167 | } 168 | 169 | setStatus("disconnected") 170 | 171 | if (reconnectAttemptsRef.current < maxReconnectAttempts) { 172 | reconnectAttemptsRef.current++ 173 | reconnectTimeoutRef.current = setTimeout(() => { 174 | connect() 175 | }, Math.min(1000 * reconnectAttemptsRef.current, 10000)) 176 | } else { 177 | setStatus("error") 178 | } 179 | } 180 | } catch (error) { 181 | setStatus("error") 182 | } 183 | } 184 | 185 | const debouncedConnect = useCallback(() => { 186 | if (debounceTimeoutRef.current) { 187 | clearTimeout(debounceTimeoutRef.current) 188 | } 189 | 190 | debounceTimeoutRef.current = setTimeout(() => { 191 | connect() 192 | debounceTimeoutRef.current = null 193 | }, 25) 194 | }, [connect]) 195 | 196 | const handleMessage = (payload: RealtimeMessage) => { 197 | const systemResult = systemEvent.safeParse(payload) 198 | 199 | if (systemResult.success) { 200 | const event = systemResult.data 201 | if (event.type === "connected") { 202 | if (event.cursor) { 203 | lastAckRef.current.set(event.channel, event.cursor) 204 | } 205 | } 206 | 207 | return 208 | } 209 | 210 | const event = userEvent.safeParse(payload) 211 | 212 | if (event.success) { 213 | lastAckRef.current.set(event.data.channel, event.data.id) 214 | 215 | localSubsRef.current.forEach((sub) => { 216 | if (sub.channels.has(event.data.channel)) { 217 | sub.cb(payload) 218 | } 219 | }) 220 | } 221 | } 222 | 223 | useEffect(() => { 224 | return () => cleanup() 225 | }, []) 226 | 227 | const register = ( 228 | id: string, 229 | channels: string[], 230 | cb: (msg: RealtimeMessage) => void 231 | ) => { 232 | localSubsRef.current.set(id, { channels: new Set(channels), cb }) 233 | debouncedConnect() 234 | } 235 | 236 | const unregister = (id: string) => { 237 | const channels = Array.from(localSubsRef.current.get(id)?.channels ?? []) 238 | 239 | channels.forEach((channel) => { 240 | lastAckRef.current.delete(channel) 241 | }) 242 | 243 | localSubsRef.current.delete(id) 244 | 245 | if (localSubsRef.current.size === 0) { 246 | cleanup() 247 | 248 | if (debounceTimeoutRef.current) { 249 | clearTimeout(debounceTimeoutRef.current) 250 | debounceTimeoutRef.current = null 251 | } 252 | 253 | return 254 | } 255 | 256 | debouncedConnect() 257 | } 258 | 259 | return ( 260 | 261 | {children} 262 | 263 | ) 264 | } 265 | 266 | export function useRealtimeContext() { 267 | const context = useContext(RealtimeContext) 268 | if (!context) { 269 | throw new Error("useRealtimeContext must be used within a RealtimeProvider") 270 | } 271 | return context 272 | } 273 | 274 | export const createRealtime = >() => ({ 275 | useRealtime: >(opts: UseRealtimeOpts) => 276 | useRealtime(opts), 277 | }) 278 | -------------------------------------------------------------------------------- /src/server/handler.ts: -------------------------------------------------------------------------------- 1 | import type { Opts, Realtime } from "./realtime.js" 2 | import { 3 | userEvent, 4 | systemEvent, 5 | type SystemEvent, 6 | type UserEvent, 7 | } from "../shared/types.js" 8 | import { compareStreamIds } from "./utils.js" 9 | 10 | export function handle(config: { 11 | realtime: Realtime 12 | middleware?: ({ 13 | request, 14 | channels, 15 | }: { 16 | request: Request 17 | channels: string[] 18 | }) => Response | void | Promise 19 | }): (request: Request) => Promise { 20 | return async (request: Request) => { 21 | const requestStartTime = Date.now() 22 | const { searchParams } = new URL(request.url) 23 | const rawChannels = 24 | searchParams.getAll("channel").length > 0 25 | ? searchParams.getAll("channel") 26 | : ["default"] 27 | const channels = [...new Set(rawChannels)] 28 | 29 | const redis = config.realtime._redis 30 | const logger = config.realtime._logger 31 | 32 | if (config.middleware) { 33 | const result = await config.middleware({ request, channels }) 34 | if (result) return result 35 | } 36 | 37 | if (!redis) { 38 | logger.error("No Redis instance provided to Realtime") 39 | return new Response(JSON.stringify({ error: "Redis not configured" }), { 40 | status: 500, 41 | headers: { "Content-Type": "application/json" }, 42 | }) 43 | } 44 | 45 | let cleanup: (() => Promise) | undefined 46 | let subscriber: ReturnType 47 | let subCount: number = 0 48 | let reconnectTimeout: NodeJS.Timeout | undefined 49 | let keepaliveInterval: NodeJS.Timeout | undefined 50 | let isClosed = false 51 | let handleAbort: (() => Promise) | undefined 52 | let onSubscribe: (() => Promise) | undefined 53 | let onError: ((err: Error) => void) | undefined 54 | let onUnsubscribe: (() => void) | undefined 55 | let onMessage: 56 | | (({ message, channel }: { message: unknown; channel: string }) => Promise) 57 | | undefined 58 | 59 | const stream = new ReadableStream({ 60 | async start(controller) { 61 | if (request.signal.aborted) { 62 | controller.close() 63 | return 64 | } 65 | 66 | cleanup = async () => { 67 | if (isClosed) return 68 | isClosed = true 69 | 70 | clearTimeout(reconnectTimeout) 71 | clearInterval(keepaliveInterval) 72 | 73 | if (handleAbort) { 74 | request.signal.removeEventListener("abort", handleAbort) 75 | } 76 | 77 | await subscriber?.unsubscribe().catch((err) => { 78 | logger.error("⚠️ Error closing connection:", err) 79 | }) 80 | 81 | try { 82 | if (!request.signal.aborted) controller.close() 83 | logger.log("✅ Connection closed successfully.") 84 | } catch (err) { 85 | logger.error("⚠️ Error closing controller:", err) 86 | } 87 | } 88 | 89 | handleAbort = async () => { 90 | await cleanup?.() 91 | } 92 | 93 | request.signal.addEventListener("abort", handleAbort) 94 | 95 | subscriber = redis.subscribe(channels) 96 | 97 | const safeEnqueue = (data: Uint8Array) => { 98 | if (isClosed) return 99 | 100 | try { 101 | controller.enqueue(data) 102 | } catch (err) { 103 | logger.error("⚠️ Error closing controller:", err) 104 | } 105 | } 106 | 107 | const elapsedMs = Date.now() - requestStartTime 108 | const remainingMs = config.realtime._maxDurationSecs * 1000 - elapsedMs 109 | const streamDurationMs = Math.max(remainingMs - 2000, 1000) 110 | 111 | reconnectTimeout = setTimeout(async () => { 112 | const reconnectEvent: SystemEvent = { 113 | type: "reconnect", 114 | timestamp: Date.now(), 115 | } 116 | 117 | safeEnqueue(json(reconnectEvent)) 118 | 119 | await cleanup?.() 120 | }, streamDurationMs) 121 | 122 | let buffer: UserEvent[] = [] 123 | let isHistoryReplayed = false 124 | const lastHistoryIds = new Map() 125 | 126 | onSubscribe = async () => { 127 | await Promise.all( 128 | channels.map(async (channel) => { 129 | const connectedEvent: SystemEvent = { 130 | type: "connected", 131 | channel, 132 | } 133 | 134 | safeEnqueue(json(connectedEvent)) 135 | 136 | const lastAck = 137 | searchParams.get(`last_ack_${channel}`) ?? String(Date.now()) 138 | 139 | const missingMessages = await redis.xrange(channel, `(${lastAck}`, "+") 140 | 141 | const entries = Object.entries(missingMessages) 142 | if (entries.length > 0) { 143 | entries.forEach(([id, value]) => { 144 | const eventWithId = { ...value, id } 145 | const event = userEvent.safeParse(eventWithId) 146 | if (event.success) safeEnqueue(json(event.data)) 147 | }) 148 | lastHistoryIds.set(channel, entries[entries.length - 1]?.[0] ?? "") 149 | } 150 | }) 151 | ) 152 | 153 | for (const msg of buffer) { 154 | const channelLastId = lastHistoryIds.get(msg.channel) 155 | if (channelLastId && compareStreamIds(msg.id, channelLastId) <= 0) continue 156 | safeEnqueue(json(msg)) 157 | } 158 | 159 | buffer = [] 160 | isHistoryReplayed = true 161 | 162 | logger.log("✅ Subscription established:", { channels }) 163 | } 164 | 165 | onError = (err) => { 166 | logger.error("⚠️ Redis subscriber error:", err) 167 | 168 | const errorEvent: SystemEvent = { 169 | type: "error", 170 | error: err.message, 171 | } 172 | 173 | safeEnqueue(json(errorEvent)) 174 | } 175 | 176 | onUnsubscribe = async () => { 177 | logger.log("⬅️ Client unsubscribed from channels:", channels) 178 | 179 | await cleanup?.() 180 | } 181 | 182 | onMessage = async ({ message }) => { 183 | let payload: Record 184 | 185 | if (typeof message === "string") { 186 | try { 187 | payload = JSON.parse(message) 188 | } catch { 189 | payload = { data: message } 190 | } 191 | } else if (typeof message === "object" && message !== null) { 192 | payload = message as Record 193 | } else { 194 | payload = { data: message } 195 | } 196 | 197 | const systemResult = systemEvent.safeParse(payload) 198 | 199 | if (systemResult.success) { 200 | safeEnqueue(json(systemResult.data)) 201 | return 202 | } 203 | 204 | logger.log("⬇️ Received event:", payload) 205 | 206 | const result = userEvent.safeParse(payload) 207 | 208 | if (result.success) { 209 | if (!isHistoryReplayed) { 210 | buffer.push(result.data) 211 | } else { 212 | safeEnqueue(json(result.data)) 213 | } 214 | } 215 | } 216 | 217 | subscriber.on("subscribe", () => { 218 | subCount = subCount + 1 219 | if (subCount === channels.length) onSubscribe?.() 220 | }) 221 | subscriber.on("error", onError) 222 | subscriber.on("unsubscribe", onUnsubscribe) 223 | subscriber.on("message", onMessage) 224 | 225 | keepaliveInterval = setInterval(async () => { 226 | const channel = channels[0] 227 | if (channel) { 228 | await redis.publish(channel, { 229 | type: "ping", 230 | timestamp: Date.now(), 231 | }) 232 | } 233 | }, 60_000) 234 | }, 235 | 236 | async cancel() { 237 | if (isClosed) return 238 | await cleanup?.() 239 | }, 240 | }) 241 | 242 | return new StreamingResponse(stream) 243 | } 244 | } 245 | 246 | export function json(data: SystemEvent | UserEvent) { 247 | return new TextEncoder().encode(`data: ${JSON.stringify(data)}\n\n`) 248 | } 249 | 250 | export class StreamingResponse extends Response { 251 | constructor(res: ReadableStream, init?: ResponseInit) { 252 | super(res as any, { 253 | ...init, 254 | status: 200, 255 | headers: { 256 | "Content-Type": "text/event-stream", 257 | "Cache-Control": "no-cache", 258 | Connection: "keep-alive", 259 | "Access-Control-Allow-Origin": "*", 260 | "Access-Control-Allow-Headers": "Cache-Control", 261 | ...init?.headers, 262 | }, 263 | }) 264 | } 265 | } 266 | 267 | 268 | -------------------------------------------------------------------------------- /src/server/realtime.ts: -------------------------------------------------------------------------------- 1 | import { type Redis } from "@upstash/redis" 2 | import * as z from "zod/v4/core" 3 | import { 4 | EventPaths, 5 | EventPayloadUnion, 6 | HistoryArgs, 7 | userEvent, 8 | type UserEvent 9 | } from "../shared/types.js" 10 | import { compareStreamIds } from "./utils.js" 11 | 12 | const DEFAULT_VERCEL_FLUID_TIMEOUT = 300 13 | 14 | type Schema = Record> 15 | 16 | export type Opts = { 17 | schema?: Schema 18 | redis?: Redis | undefined 19 | maxDurationSecs?: number 20 | verbose?: boolean 21 | history?: 22 | | { 23 | maxLength?: number 24 | expireAfterSecs?: number 25 | } 26 | | boolean 27 | } 28 | 29 | class RealtimeBase { 30 | private channels: Record = {} 31 | private _schema: Schema 32 | private _verbose: boolean 33 | private _history: { 34 | maxLength?: number 35 | expireAfterSecs?: number 36 | } 37 | private _trimConfig?: NonNullable[3]>["trim"] 38 | 39 | /** @internal */ 40 | public readonly _redis?: Redis | undefined 41 | 42 | /** @internal */ 43 | public readonly _maxDurationSecs: number 44 | 45 | /** @internal */ 46 | public readonly _logger = { 47 | log: (...args: any[]) => { 48 | if (this._verbose) console.log(...args) 49 | }, 50 | warn: (...args: any[]) => { 51 | if (this._verbose) console.warn(...args) 52 | }, 53 | error: (...args: any[]) => { 54 | if (this._verbose) console.error(...args) 55 | }, 56 | } 57 | 58 | constructor(data: T) { 59 | Object.assign(this, data) 60 | this._schema = data.schema || {} 61 | this._redis = data.redis 62 | this._maxDurationSecs = data.maxDurationSecs ?? DEFAULT_VERCEL_FLUID_TIMEOUT 63 | this._verbose = data.verbose ?? false 64 | this._history = typeof data.history === "boolean" ? {} : data.history ?? {} 65 | 66 | this._trimConfig = this._history.maxLength 67 | ? { 68 | type: "MAXLEN", 69 | threshold: this._history.maxLength, 70 | comparison: "=", 71 | } 72 | : undefined 73 | 74 | Object.assign(this, this.createEventHandlers("default")) 75 | } 76 | 77 | private createEventHandlers(channel: string): any { 78 | const handlers: any = {} 79 | let unsubscribe: undefined | (() => void) = undefined 80 | let pingInterval: undefined | NodeJS.Timeout = undefined 81 | 82 | const startPingInterval = () => { 83 | pingInterval = setInterval(() => { 84 | this._redis?.publish(channel, { type: "ping", timestamp: Date.now() }) 85 | }, 60_000) 86 | } 87 | 88 | const stopPingInterval = () => { 89 | if (pingInterval) clearInterval(pingInterval) 90 | } 91 | 92 | handlers.history = async (args?: HistoryArgs) => { 93 | const redis = this._redis 94 | if (!redis) throw new Error("Redis not configured.") 95 | 96 | const start = args?.start ? String(args.start) : "-" 97 | const end = args?.end ? String(args.end) : "+" 98 | const limit = Math.min(args?.limit ?? 1000, 1000) 99 | 100 | const history = (await redis.xrange(channel, start, end, limit)) as Record< 101 | string, 102 | UserEvent 103 | > 104 | 105 | const messages = Object.entries(history) 106 | 107 | return messages 108 | .map(([_, value]) => { 109 | if (typeof value === "object" && value !== null) { 110 | const { id, channel, event, data } = value 111 | return { data, event, id, channel } 112 | } 113 | return null 114 | }) 115 | .filter(Boolean) 116 | } 117 | 118 | handlers.unsubscribe = () => { 119 | if (unsubscribe) { 120 | unsubscribe() 121 | this._logger.log("✅ Connection closed successfully.") 122 | } 123 | } 124 | 125 | handlers.subscribe = async ({ 126 | events, 127 | onData, 128 | history, 129 | }: SubscribeArgs): Promise<() => void> => { 130 | const redis = this._redis 131 | if (!redis) throw new Error("Redis not configured.") 132 | 133 | const buffer: UserEvent[] = [] 134 | let isHistoryReplayed = false 135 | let lastHistoryId: string | null = null 136 | 137 | const sub = redis.subscribe(channel) 138 | 139 | await new Promise((resolve) => { 140 | sub.on("subscribe", async () => { 141 | if (history) { 142 | const start = 143 | typeof history === "object" && history.start ? String(history.start) : "-" 144 | const end = 145 | typeof history === "object" && history.end ? String(history.end) : "+" 146 | const limit = typeof history === "object" ? history.limit : undefined 147 | 148 | const messages = await redis.xrange(channel, start, end, limit) 149 | 150 | const entries = Object.entries(messages) 151 | for (const [id, message] of entries) { 152 | if (!message.event || !events.includes(message.event)) continue 153 | 154 | const result = userEvent.safeParse(message) 155 | if (result.success) onData(result.data) 156 | } 157 | 158 | if (entries.length > 0) { 159 | lastHistoryId = entries[entries.length - 1]?.[0] ?? null 160 | } 161 | } 162 | 163 | for (const message of buffer) { 164 | if (lastHistoryId && compareStreamIds(message.id, lastHistoryId) <= 0) 165 | continue 166 | onData(message) 167 | } 168 | 169 | buffer.length = 0 170 | isHistoryReplayed = true 171 | startPingInterval() 172 | resolve() 173 | }) 174 | }) 175 | 176 | sub.on("message", ({ message }) => { 177 | if (!message.event || !events.includes(message.event)) return 178 | 179 | const result = userEvent.safeParse(message) 180 | if (!result.success) return 181 | 182 | if (!isHistoryReplayed) { 183 | buffer.push(result.data) 184 | } else { 185 | onData(result.data) 186 | } 187 | }) 188 | 189 | sub.on("unsubscribe", () => { 190 | stopPingInterval() 191 | }) 192 | 193 | unsubscribe = () => sub.unsubscribe() 194 | return () => sub.unsubscribe() 195 | } 196 | 197 | const findSchema = (path: string[]): z.$ZodType | undefined => { 198 | let current: any = this._schema 199 | 200 | for (const key of path) { 201 | if (!current || typeof current !== "object") return undefined 202 | current = current[key] 203 | } 204 | 205 | return current?._zod || current?._def ? current : undefined 206 | } 207 | 208 | handlers.emit = async (event: string, data: any) => { 209 | const pathParts = event.split(".") 210 | const schema = findSchema(pathParts) 211 | 212 | if (schema) { 213 | z.parse(schema, data) 214 | } 215 | 216 | if (!this._redis) { 217 | this._logger.warn("No Redis instance provided to Realtime.") 218 | return 219 | } 220 | 221 | const id = await this._redis.xadd( 222 | channel, 223 | "*", 224 | { data, event, channel } as Record, 225 | { 226 | ...(this._trimConfig && { trim: this._trimConfig }), 227 | } 228 | ) 229 | 230 | const payload: UserEvent = { 231 | data, 232 | event, 233 | channel, 234 | id, 235 | } 236 | 237 | const pipeline = this._redis.pipeline() 238 | 239 | if (this._history.expireAfterSecs) { 240 | pipeline.expire(channel, this._history.expireAfterSecs) 241 | } 242 | 243 | pipeline.publish(channel, payload) 244 | 245 | await pipeline.exec() 246 | 247 | this._logger.log(`⬆️ Emitted event:`, { 248 | id, 249 | data, 250 | event, 251 | channel, 252 | }) 253 | } 254 | 255 | return handlers 256 | } 257 | 258 | channel(channel: N): RealtimeChannel { 259 | if (!this.channels[channel]) { 260 | this.channels[channel] = this.createEventHandlers(channel) 261 | } 262 | 263 | return this.channels[channel] 264 | } 265 | } 266 | 267 | type SchemaPaths = { 268 | [K in keyof T]: K extends string 269 | ? T[K] extends z.$ZodType 270 | ? Prefix extends "" 271 | ? K 272 | : `${Prefix}${K}` 273 | : T[K] extends object 274 | ? SchemaPaths 275 | : never 276 | : never 277 | }[keyof T] 278 | 279 | export type EventPath = T["schema"] extends Schema 280 | ? SchemaPaths 281 | : never 282 | 283 | type SchemaValue = Path extends `${infer First}.${infer Rest}` 284 | ? First extends keyof T 285 | ? SchemaValue 286 | : never 287 | : Path extends keyof T 288 | ? T[Path] 289 | : never 290 | 291 | export type EventData = T["schema"] extends Schema 292 | ? SchemaValue extends z.$ZodType 293 | ? z.infer> 294 | : never 295 | : never 296 | 297 | export type HistoryMessage = { 298 | id: string 299 | event: string 300 | channel: string 301 | data: unknown 302 | } 303 | 304 | type SubscribeArgs> = { 305 | events: readonly E[] 306 | onData: (arg: EventPayloadUnion) => void 307 | history?: boolean | HistoryArgs 308 | } 309 | 310 | type RealtimeChannel = { 311 | subscribe: >( 312 | args: SubscribeArgs 313 | ) => Promise<() => void> 314 | unsubscribe: () => void 315 | emit: >(event: K, data: EventData) => Promise 316 | history: (params?: HistoryArgs) => Promise 317 | } 318 | 319 | export type Realtime = RealtimeBase & { 320 | channel: (name: string) => RealtimeChannel 321 | } & RealtimeChannel 322 | 323 | type InferSchemaRecursive = { 324 | [K in keyof T]: T[K] extends z.$ZodType 325 | ? z.infer 326 | : T[K] extends object 327 | ? InferSchemaRecursive 328 | : never 329 | } 330 | 331 | export type InferSchema = InferSchemaRecursive 332 | 333 | export type InferRealtimeEvents = T extends Realtime 334 | ? NonNullable 335 | : never 336 | 337 | export const Realtime = RealtimeBase as new (data?: T) => Realtime 338 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "@upstash/realtime", 6 | "devDependencies": { 7 | "@types/node": "^24.6.0", 8 | "@types/react": "^19.1.16", 9 | "@types/react-dom": "^19.1.9", 10 | "tsup": "^8.5.0", 11 | "typescript": "^5.9.2", 12 | "zod": "^4.1.11", 13 | }, 14 | "peerDependencies": { 15 | "@upstash/redis": ">=1.35.4", 16 | "react": ">=16.8.0", 17 | "react-dom": ">=16.8.0", 18 | "zod": "^3.25.0 || ^4.0.0", 19 | }, 20 | }, 21 | }, 22 | "packages": { 23 | "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], 24 | 25 | "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], 26 | 27 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="], 28 | 29 | "@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="], 30 | 31 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="], 32 | 33 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="], 34 | 35 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="], 36 | 37 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="], 38 | 39 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="], 40 | 41 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="], 42 | 43 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="], 44 | 45 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="], 46 | 47 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="], 48 | 49 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="], 50 | 51 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="], 52 | 53 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="], 54 | 55 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="], 56 | 57 | "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="], 58 | 59 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="], 60 | 61 | "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="], 62 | 63 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="], 64 | 65 | "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="], 66 | 67 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="], 68 | 69 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="], 70 | 71 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="], 72 | 73 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], 74 | 75 | "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], 76 | 77 | "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 78 | 79 | "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 80 | 81 | "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 82 | 83 | "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 84 | 85 | "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], 86 | 87 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.3", "", { "os": "android", "cpu": "arm" }, "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw=="], 88 | 89 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.3", "", { "os": "android", "cpu": "arm64" }, "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw=="], 90 | 91 | "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg=="], 92 | 93 | "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A=="], 94 | 95 | "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ=="], 96 | 97 | "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A=="], 98 | 99 | "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.3", "", { "os": "linux", "cpu": "arm" }, "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA=="], 100 | 101 | "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.3", "", { "os": "linux", "cpu": "arm" }, "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA=="], 102 | 103 | "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ=="], 104 | 105 | "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw=="], 106 | 107 | "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg=="], 108 | 109 | "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw=="], 110 | 111 | "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg=="], 112 | 113 | "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg=="], 114 | 115 | "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg=="], 116 | 117 | "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA=="], 118 | 119 | "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.3", "", { "os": "linux", "cpu": "x64" }, "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw=="], 120 | 121 | "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.3", "", { "os": "none", "cpu": "arm64" }, "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA=="], 122 | 123 | "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA=="], 124 | 125 | "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g=="], 126 | 127 | "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ=="], 128 | 129 | "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA=="], 130 | 131 | "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 132 | 133 | "@types/node": ["@types/node@24.6.0", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA=="], 134 | 135 | "@types/react": ["@types/react@19.1.16", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog=="], 136 | 137 | "@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="], 138 | 139 | "@upstash/redis": ["@upstash/redis@1.35.6", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-aSEIGJgJ7XUfTYvhQcQbq835re7e/BXjs8Janq6Pvr6LlmTZnyqwT97RziZLO/8AVUL037RLXqqiQC6kCt+5pA=="], 140 | 141 | "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 142 | 143 | "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], 144 | 145 | "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], 146 | 147 | "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], 148 | 149 | "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 150 | 151 | "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 152 | 153 | "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], 154 | 155 | "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], 156 | 157 | "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], 158 | 159 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 160 | 161 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 162 | 163 | "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], 164 | 165 | "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], 166 | 167 | "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 168 | 169 | "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 170 | 171 | "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], 172 | 173 | "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 174 | 175 | "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], 176 | 177 | "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], 178 | 179 | "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], 180 | 181 | "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 182 | 183 | "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], 184 | 185 | "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], 186 | 187 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 188 | 189 | "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], 190 | 191 | "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 192 | 193 | "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 194 | 195 | "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], 196 | 197 | "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], 198 | 199 | "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], 200 | 201 | "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], 202 | 203 | "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], 204 | 205 | "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], 206 | 207 | "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], 208 | 209 | "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], 210 | 211 | "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 212 | 213 | "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], 214 | 215 | "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], 216 | 217 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 218 | 219 | "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], 220 | 221 | "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 222 | 223 | "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], 224 | 225 | "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 226 | 227 | "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], 228 | 229 | "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 230 | 231 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 232 | 233 | "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 234 | 235 | "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], 236 | 237 | "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], 238 | 239 | "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], 240 | 241 | "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 242 | 243 | "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="], 244 | 245 | "react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="], 246 | 247 | "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], 248 | 249 | "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], 250 | 251 | "rollup": ["rollup@4.52.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.3", "@rollup/rollup-android-arm64": "4.52.3", "@rollup/rollup-darwin-arm64": "4.52.3", "@rollup/rollup-darwin-x64": "4.52.3", "@rollup/rollup-freebsd-arm64": "4.52.3", "@rollup/rollup-freebsd-x64": "4.52.3", "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", "@rollup/rollup-linux-arm-musleabihf": "4.52.3", "@rollup/rollup-linux-arm64-gnu": "4.52.3", "@rollup/rollup-linux-arm64-musl": "4.52.3", "@rollup/rollup-linux-loong64-gnu": "4.52.3", "@rollup/rollup-linux-ppc64-gnu": "4.52.3", "@rollup/rollup-linux-riscv64-gnu": "4.52.3", "@rollup/rollup-linux-riscv64-musl": "4.52.3", "@rollup/rollup-linux-s390x-gnu": "4.52.3", "@rollup/rollup-linux-x64-gnu": "4.52.3", "@rollup/rollup-linux-x64-musl": "4.52.3", "@rollup/rollup-openharmony-arm64": "4.52.3", "@rollup/rollup-win32-arm64-msvc": "4.52.3", "@rollup/rollup-win32-ia32-msvc": "4.52.3", "@rollup/rollup-win32-x64-gnu": "4.52.3", "@rollup/rollup-win32-x64-msvc": "4.52.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A=="], 252 | 253 | "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], 254 | 255 | "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 256 | 257 | "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 258 | 259 | "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], 260 | 261 | "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], 262 | 263 | "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], 264 | 265 | "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 266 | 267 | "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], 268 | 269 | "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 270 | 271 | "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], 272 | 273 | "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], 274 | 275 | "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], 276 | 277 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], 278 | 279 | "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 280 | 281 | "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], 282 | 283 | "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], 284 | 285 | "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], 286 | 287 | "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], 288 | 289 | "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], 290 | 291 | "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], 292 | 293 | "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], 294 | 295 | "undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="], 296 | 297 | "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], 298 | 299 | "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], 300 | 301 | "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 302 | 303 | "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], 304 | 305 | "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=="], 306 | 307 | "zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="], 308 | 309 | "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 310 | 311 | "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 312 | 313 | "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 314 | 315 | "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 316 | 317 | "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 318 | 319 | "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 320 | 321 | "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 322 | 323 | "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 324 | 325 | "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 326 | } 327 | } 328 | --------------------------------------------------------------------------------