UNPKG

@trieb.work/nextjs-turbo-redis-cache

Version:

The ultimate Redis caching solution for Next.js. Built for production-ready, large-scale projects, it delivers unparalleled performance and efficiency with features tailored for high-traffic applications.

1 lines 34.3 kB
{"version":3,"sources":["../src/RedisStringsHandler.ts","../src/SyncedMap.ts","../src/DeduplicatedRequestHandler.ts","../src/CachedHandler.ts","../src/index.ts"],"sourcesContent":["import { commandOptions, createClient } from 'redis';\nimport { SyncedMap } from './SyncedMap';\nimport { DeduplicatedRequestHandler } from './DeduplicatedRequestHandler';\nimport {\n CacheHandler,\n CacheHandlerValue,\n IncrementalCache,\n} from 'next/dist/server/lib/incremental-cache';\n\nexport type CommandOptions = ReturnType<typeof commandOptions>;\ntype GetParams = Parameters<IncrementalCache['get']>;\ntype SetParams = Parameters<IncrementalCache['set']>;\ntype RevalidateParams = Parameters<IncrementalCache['revalidateTag']>;\nexport type Client = ReturnType<typeof createClient>;\n\nexport type CreateRedisStringsHandlerOptions = {\n database?: number;\n keyPrefix?: string;\n timeoutMs?: number;\n revalidateTagQuerySize?: number;\n sharedTagsKey?: string;\n avgResyncIntervalMs?: number;\n redisGetDeduplication?: boolean;\n inMemoryCachingTime?: number;\n defaultStaleAge?: number;\n estimateExpireAge?: (staleAge: number) => number;\n};\n\nconst NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_';\nconst REVALIDATED_TAGS_KEY = '__revalidated_tags__';\n\nfunction isImplicitTag(tag: string): boolean {\n return tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID);\n}\n\nexport function getTimeoutRedisCommandOptions(\n timeoutMs: number,\n): CommandOptions {\n return commandOptions({ signal: AbortSignal.timeout(timeoutMs) });\n}\n\nexport default class RedisStringsHandler implements CacheHandler {\n private client: Client;\n private sharedTagsMap: SyncedMap<string[]>;\n private revalidatedTagsMap: SyncedMap<number>;\n private inMemoryDeduplicationCache: SyncedMap<\n Promise<ReturnType<Client['get']>>\n >;\n private redisGet: Client['get'];\n private redisDeduplicationHandler: DeduplicatedRequestHandler<\n Client['get'],\n string | Buffer | null\n >;\n private deduplicatedRedisGet: (key: string) => Client['get'];\n private timeoutMs: number;\n private keyPrefix: string;\n private redisGetDeduplication: boolean;\n private inMemoryCachingTime: number;\n private defaultStaleAge: number;\n private estimateExpireAge: (staleAge: number) => number;\n\n constructor({\n database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n sharedTagsKey = '__sharedTags__',\n timeoutMs = 5000,\n revalidateTagQuerySize = 250,\n avgResyncIntervalMs = 60 * 60 * 1000,\n redisGetDeduplication = true,\n inMemoryCachingTime = 10_000,\n defaultStaleAge = 60 * 60 * 24 * 14,\n estimateExpireAge = (staleAge) =>\n process.env.VERCEL_ENV === 'preview' ? staleAge * 1.2 : staleAge * 2,\n }: CreateRedisStringsHandlerOptions) {\n this.keyPrefix = keyPrefix;\n this.timeoutMs = timeoutMs;\n this.redisGetDeduplication = redisGetDeduplication;\n this.inMemoryCachingTime = inMemoryCachingTime;\n this.defaultStaleAge = defaultStaleAge;\n this.estimateExpireAge = estimateExpireAge;\n\n try {\n this.client = createClient({\n ...(database !== 0 ? { database } : {}),\n url: process.env.REDISHOST\n ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n : 'redis://localhost:6379',\n });\n\n this.client.on('error', (error) => {\n console.error('Redis client error', error);\n });\n\n this.client\n .connect()\n .then(() => {\n console.info('Redis client connected.');\n })\n .catch((error) => {\n console.error('Failed to connect Redis client:', error);\n this.client.disconnect();\n });\n } catch (error: unknown) {\n console.error('Failed to initialize Redis client');\n throw error;\n }\n\n const filterKeys = (key: string): boolean =>\n key !== REVALIDATED_TAGS_KEY && key !== sharedTagsKey;\n\n this.sharedTagsMap = new SyncedMap<string[]>({\n client: this.client,\n keyPrefix,\n redisKey: sharedTagsKey,\n database,\n timeoutMs,\n querySize: revalidateTagQuerySize,\n filterKeys,\n resyncIntervalMs:\n avgResyncIntervalMs -\n avgResyncIntervalMs / 10 +\n Math.random() * (avgResyncIntervalMs / 10),\n });\n\n this.revalidatedTagsMap = new SyncedMap<number>({\n client: this.client,\n keyPrefix,\n redisKey: REVALIDATED_TAGS_KEY,\n database,\n timeoutMs,\n querySize: revalidateTagQuerySize,\n filterKeys,\n resyncIntervalMs:\n avgResyncIntervalMs +\n avgResyncIntervalMs / 10 +\n Math.random() * (avgResyncIntervalMs / 10),\n });\n\n this.inMemoryDeduplicationCache = new SyncedMap({\n client: this.client,\n keyPrefix,\n redisKey: 'inMemoryDeduplicationCache',\n database,\n timeoutMs,\n querySize: revalidateTagQuerySize,\n filterKeys,\n customizedSync: {\n withoutRedisHashmap: true,\n withoutSetSync: true,\n },\n });\n\n const redisGet: Client['get'] = this.client.get.bind(this.client);\n this.redisDeduplicationHandler = new DeduplicatedRequestHandler(\n redisGet,\n inMemoryCachingTime,\n this.inMemoryDeduplicationCache,\n );\n this.redisGet = redisGet;\n this.deduplicatedRedisGet =\n this.redisDeduplicationHandler.deduplicatedFunction;\n }\n resetRequestCache(...args: never[]): void {\n console.warn('WARNING resetRequestCache() was called', args);\n }\n\n private async assertClientIsReady(): Promise<void> {\n await Promise.all([\n this.sharedTagsMap.waitUntilReady(),\n this.revalidatedTagsMap.waitUntilReady(),\n ]);\n if (!this.client.isReady) {\n throw new Error('Redis client is not ready yet or connection is lost.');\n }\n }\n\n public async get(key: GetParams[0], ctx: GetParams[1]) {\n await this.assertClientIsReady();\n\n const clientGet = this.redisGetDeduplication\n ? this.deduplicatedRedisGet(key)\n : this.redisGet;\n const result = await clientGet(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n this.keyPrefix + key,\n );\n\n if (!result) {\n return null;\n }\n\n const cacheValue = JSON.parse(result) as\n | (CacheHandlerValue & { lastModified: number })\n | null;\n\n if (!cacheValue) {\n return null;\n }\n\n if (cacheValue.value?.kind === 'FETCH') {\n cacheValue.value.data.body = Buffer.from(\n cacheValue.value.data.body,\n ).toString('base64');\n }\n\n const combinedTags = new Set([\n ...(ctx?.softTags || []),\n ...(ctx?.tags || []),\n ]);\n\n if (combinedTags.size === 0) {\n return cacheValue;\n }\n\n for (const tag of combinedTags) {\n // TODO: check how this revalidatedTagsMap is used or if it can be deleted\n const revalidationTime = this.revalidatedTagsMap.get(tag);\n if (revalidationTime && revalidationTime > cacheValue.lastModified) {\n const redisKey = this.keyPrefix + key;\n // Do not await here as this can happen in the background while we can already serve the cacheValue\n this.client\n .unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey)\n .catch((err) => {\n console.error(\n 'Error occurred while unlinking stale data. Retrying now. Error was:',\n err,\n );\n this.client.unlink(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n redisKey,\n );\n })\n .finally(async () => {\n await this.sharedTagsMap.delete(key);\n await this.revalidatedTagsMap.delete(tag);\n });\n return null;\n }\n }\n\n return cacheValue;\n }\n public async set(\n key: SetParams[0],\n data: SetParams[1] & { lastModified: number },\n ctx: SetParams[2],\n ) {\n if (data.kind === 'FETCH') {\n console.time('encoding' + key);\n data.data.body = Buffer.from(data.data.body, 'base64').toString();\n console.timeEnd('encoding' + key);\n }\n await this.assertClientIsReady();\n\n data.lastModified = Date.now();\n\n const value = JSON.stringify(data);\n\n // pre seed data into deduplicated get client. This will reduce redis load by not requesting\n // the same value from redis which was just set.\n if (this.redisGetDeduplication) {\n this.redisDeduplicationHandler.seedRequestReturn(key, value);\n }\n\n const expireAt =\n ctx.revalidate &&\n Number.isSafeInteger(ctx.revalidate) &&\n ctx.revalidate > 0\n ? this.estimateExpireAge(ctx.revalidate)\n : this.estimateExpireAge(this.defaultStaleAge);\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n const setOperation: Promise<string | null> = this.client.set(\n options,\n this.keyPrefix + key,\n value,\n {\n EX: expireAt,\n },\n );\n\n let setTagsOperation: Promise<void> | undefined;\n if (ctx.tags && ctx.tags.length > 0) {\n const currentTags = this.sharedTagsMap.get(key);\n const currentIsSameAsNew =\n currentTags?.length === ctx.tags.length &&\n currentTags.every((v) => ctx.tags!.includes(v)) &&\n ctx.tags.every((v) => currentTags.includes(v));\n\n if (!currentIsSameAsNew) {\n setTagsOperation = this.sharedTagsMap.set(\n key,\n structuredClone(ctx.tags) as string[],\n );\n }\n }\n\n await Promise.all([setOperation, setTagsOperation]);\n }\n public async revalidateTag(tagOrTags: RevalidateParams[0]) {\n const tags = new Set([tagOrTags || []].flat());\n await this.assertClientIsReady();\n\n // TODO: check how this revalidatedTagsMap is used or if it can be deleted\n for (const tag of tags) {\n if (isImplicitTag(tag)) {\n const now = Date.now();\n await this.revalidatedTagsMap.set(tag, now);\n }\n }\n\n const keysToDelete: string[] = [];\n\n for (const [key, sharedTags] of this.sharedTagsMap.entries()) {\n if (sharedTags.some((tag) => tags.has(tag))) {\n keysToDelete.push(key);\n }\n }\n\n if (keysToDelete.length === 0) {\n return;\n }\n\n const fullRedisKeys = keysToDelete.map((key) => this.keyPrefix + key);\n\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n\n const deleteKeysOperation = this.client.unlink(options, fullRedisKeys);\n\n // delete entries from in-memory deduplication cache\n if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {\n for (const key of keysToDelete) {\n this.inMemoryDeduplicationCache.delete(key);\n }\n }\n\n const deleteTagsOperation = this.sharedTagsMap.delete(keysToDelete);\n\n await Promise.all([deleteKeysOperation, deleteTagsOperation]);\n }\n}\n","// SyncedMap.ts\nimport { Client, getTimeoutRedisCommandOptions } from './RedisStringsHandler';\n\ntype CustomizedSync = {\n withoutRedisHashmap?: boolean;\n withoutSetSync?: boolean;\n};\n\ntype SyncedMapOptions = {\n client: Client;\n keyPrefix: string;\n redisKey: string; // Redis Hash key\n database: number;\n timeoutMs: number;\n querySize: number;\n filterKeys: (key: string) => boolean;\n resyncIntervalMs?: number;\n customizedSync?: CustomizedSync;\n};\n\nexport type SyncMessage<V> = {\n type: 'insert' | 'delete';\n key?: string;\n value?: V;\n keys?: string[];\n};\n\nconst SYNC_CHANNEL_SUFFIX = ':sync-channel:';\nexport class SyncedMap<V> {\n private client: Client;\n private subscriberClient: Client;\n private map: Map<string, V>;\n private keyPrefix: string;\n private syncChannel: string;\n private redisKey: string;\n private database: number;\n private timeoutMs: number;\n private querySize: number;\n private filterKeys: (key: string) => boolean;\n private resyncIntervalMs?: number;\n private customizedSync?: CustomizedSync;\n\n private setupLock: Promise<void>;\n private setupLockResolve!: () => void;\n\n constructor(options: SyncedMapOptions) {\n this.client = options.client;\n this.keyPrefix = options.keyPrefix;\n this.redisKey = options.redisKey;\n this.syncChannel = `${options.keyPrefix}${SYNC_CHANNEL_SUFFIX}${options.redisKey}`;\n this.database = options.database;\n this.timeoutMs = options.timeoutMs;\n this.querySize = options.querySize;\n this.filterKeys = options.filterKeys;\n this.resyncIntervalMs = options.resyncIntervalMs;\n this.customizedSync = options.customizedSync;\n\n this.map = new Map<string, V>();\n this.subscriberClient = this.client.duplicate();\n this.setupLock = new Promise<void>((resolve) => {\n this.setupLockResolve = resolve;\n });\n\n this.setup().catch((error) => {\n console.error('Failed to setup SyncedMap:', error);\n throw error;\n });\n }\n\n private async setup() {\n let setupPromises: Promise<void>[] = [];\n if (!this.customizedSync?.withoutRedisHashmap) {\n setupPromises.push(this.initialSync());\n this.setupPeriodicResync();\n }\n setupPromises.push(this.setupPubSub());\n await Promise.all(setupPromises);\n this.setupLockResolve();\n }\n\n private async initialSync() {\n let cursor = 0;\n const hScanOptions = { COUNT: this.querySize };\n\n try {\n do {\n const remoteItems = await this.client.hScan(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n this.keyPrefix + this.redisKey,\n cursor,\n hScanOptions,\n );\n for (const { field, value } of remoteItems.tuples) {\n if (this.filterKeys(field)) {\n const parsedValue = JSON.parse(value);\n this.map.set(field, parsedValue);\n }\n }\n cursor = remoteItems.cursor;\n } while (cursor !== 0);\n\n // Clean up keys not in Redis\n await this.cleanupKeysNotInRedis();\n } catch (error) {\n console.error('Error during initial sync:', error);\n throw error;\n }\n }\n\n private async cleanupKeysNotInRedis() {\n let cursor = 0;\n const scanOptions = { COUNT: this.querySize, MATCH: `${this.keyPrefix}*` };\n let remoteKeys: string[] = [];\n try {\n do {\n const remoteKeysPortion = await this.client.scan(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n cursor,\n scanOptions,\n );\n remoteKeys = remoteKeys.concat(remoteKeysPortion.keys);\n cursor = remoteKeysPortion.cursor;\n } while (cursor !== 0);\n\n const remoteKeysSet = new Set(\n remoteKeys.map((key) => key.substring(this.keyPrefix.length)),\n );\n\n const keysToDelete: string[] = [];\n for (const key of this.map.keys()) {\n const keyStr = key as unknown as string;\n if (!remoteKeysSet.has(keyStr) && this.filterKeys(keyStr)) {\n keysToDelete.push(keyStr);\n }\n }\n\n if (keysToDelete.length > 0) {\n await this.delete(keysToDelete);\n }\n } catch (error) {\n console.error('Error during cleanup of keys not in Redis:', error);\n throw error;\n }\n }\n\n private setupPeriodicResync() {\n if (this.resyncIntervalMs && this.resyncIntervalMs > 0) {\n setInterval(() => {\n this.initialSync().catch((error) => {\n console.error('Error during periodic resync:', error);\n });\n }, this.resyncIntervalMs);\n }\n }\n\n private async setupPubSub() {\n const syncHandler = async (message: string) => {\n const syncMessage: SyncMessage<V> = JSON.parse(message);\n if (syncMessage.type === 'insert') {\n if (syncMessage.key !== undefined && syncMessage.value !== undefined) {\n this.map.set(syncMessage.key, syncMessage.value);\n }\n } else if (syncMessage.type === 'delete') {\n if (syncMessage.keys) {\n for (const key of syncMessage.keys) {\n this.map.delete(key);\n }\n }\n }\n };\n\n const keyEventHandler = async (_channel: string, message: string) => {\n const key = message;\n if (key.startsWith(this.keyPrefix)) {\n const keyInMap = key.substring(this.keyPrefix.length);\n if (this.filterKeys(keyInMap)) {\n await this.delete(keyInMap, true);\n }\n }\n };\n\n try {\n await this.subscriberClient.connect();\n\n await Promise.all([\n // We use a custom channel for insert/delete For the following reason:\n // With custom channel we can delete multiple entries in one message. If we would listen to unlink / del we\n // could get thousands of messages for one revalidateTag (For example revalidateTag(\"algolia\") would send an enormous amount of network packages)\n // Also we can send the value in the message for insert\n this.subscriberClient.subscribe(this.syncChannel, syncHandler),\n // Subscribe to Redis keyspace notifications for evicted and expired keys\n this.subscriberClient.subscribe(\n `__keyevent@${this.database}__:evicted`,\n keyEventHandler,\n ),\n this.subscriberClient.subscribe(\n `__keyevent@${this.database}__:expired`,\n keyEventHandler,\n ),\n ]);\n\n // Error handling for reconnection\n this.subscriberClient.on('error', async (err) => {\n console.error('Subscriber client error:', err);\n try {\n await this.subscriberClient.quit();\n this.subscriberClient = this.client.duplicate();\n await this.setupPubSub();\n } catch (reconnectError) {\n console.error(\n 'Failed to reconnect subscriber client:',\n reconnectError,\n );\n }\n });\n } catch (error) {\n console.error('Error setting up pub/sub client:', error);\n throw error;\n }\n }\n\n public async waitUntilReady() {\n await this.setupLock;\n }\n\n public get(key: string): V | undefined {\n return this.map.get(key);\n }\n\n public async set(key: string, value: V): Promise<void> {\n this.map.set(key, value);\n const operations = [];\n\n // This is needed if we only want to sync delete commands. This is especially useful for non serializable data like a promise map\n if (this.customizedSync?.withoutSetSync) {\n return;\n }\n if (!this.customizedSync?.withoutRedisHashmap) {\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n operations.push(\n this.client.hSet(\n options,\n this.keyPrefix + this.redisKey,\n key as unknown as string,\n JSON.stringify(value),\n ),\n );\n }\n\n const insertMessage: SyncMessage<V> = {\n type: 'insert',\n key: key as unknown as string,\n value,\n };\n operations.push(\n this.client.publish(this.syncChannel, JSON.stringify(insertMessage)),\n );\n await Promise.all(operations);\n }\n\n public async delete(\n keys: string[] | string,\n withoutSyncMessage = false,\n ): Promise<void> {\n const keysArray = Array.isArray(keys) ? keys : [keys];\n const operations = [];\n\n for (const key of keysArray) {\n this.map.delete(key);\n }\n\n if (!this.customizedSync?.withoutRedisHashmap) {\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n operations.push(\n this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray),\n );\n }\n\n if (!withoutSyncMessage) {\n const deletionMessage: SyncMessage<V> = {\n type: 'delete',\n keys: keysArray,\n };\n operations.push(\n this.client.publish(this.syncChannel, JSON.stringify(deletionMessage)),\n );\n }\n await Promise.all(operations);\n }\n\n public has(key: string): boolean {\n return this.map.has(key);\n }\n\n public entries(): IterableIterator<[string, V]> {\n return this.map.entries();\n }\n}\n","import { SyncedMap } from './SyncedMap';\nexport class DeduplicatedRequestHandler<\n T extends (...args: [never, never]) => Promise<K>,\n K,\n> {\n private inMemoryDeduplicationCache: SyncedMap<Promise<K>>;\n private cachingTimeMs: number;\n private fn: T;\n\n constructor(\n fn: T,\n cachingTimeMs: number,\n inMemoryDeduplicationCache: SyncedMap<Promise<K>>,\n ) {\n this.fn = fn;\n this.cachingTimeMs = cachingTimeMs;\n this.inMemoryDeduplicationCache = inMemoryDeduplicationCache;\n }\n\n // Method to manually seed a result into the cache\n seedRequestReturn(key: string, value: K): void {\n const resultPromise = new Promise<K>((res) => res(value));\n this.inMemoryDeduplicationCache.set(key, resultPromise);\n setTimeout(() => {\n this.inMemoryDeduplicationCache.delete(key);\n }, this.cachingTimeMs);\n }\n\n // Method to handle deduplicated requests\n deduplicatedFunction = (key: string): T => {\n //eslint-disable-next-line @typescript-eslint/no-this-alias\n const self = this;\n const dedupedFn = async (...args: [never, never]): Promise<K> => {\n // If there's already a pending request with the same key, return it\n if (\n self.inMemoryDeduplicationCache &&\n self.inMemoryDeduplicationCache.has(key)\n ) {\n const res = await self.inMemoryDeduplicationCache\n .get(key)!\n .then((v) => structuredClone(v));\n return res;\n }\n\n // If no pending request, call the original function and store the promise\n const promise = self.fn(...args);\n self.inMemoryDeduplicationCache.set(key, promise);\n\n try {\n const result = await promise;\n return structuredClone(result);\n } finally {\n // Once the promise is resolved/rejected, remove it from the map\n setTimeout(() => {\n self.inMemoryDeduplicationCache.delete(key);\n }, self.cachingTimeMs);\n }\n };\n return dedupedFn as T;\n };\n}\n","import { CacheHandler } from \"next/dist/server/lib/incremental-cache\";\nimport RedisStringsHandler, { CreateRedisStringsHandlerOptions } from \"./RedisStringsHandler\";\n\nlet cachedHandler: RedisStringsHandler;\n\nexport default class CachedHandler implements CacheHandler {\n constructor(options: CreateRedisStringsHandlerOptions) {\n if (!cachedHandler) {\n console.log(\"created cached handler\");\n cachedHandler = new RedisStringsHandler(options);\n }\n }\n get(...args: Parameters<RedisStringsHandler[\"get\"]>): ReturnType<RedisStringsHandler[\"get\"]> {\n return cachedHandler.get(...args);\n }\n set(...args: Parameters<RedisStringsHandler[\"set\"]>): ReturnType<RedisStringsHandler[\"set\"]> {\n return cachedHandler.set(...args);\n }\n revalidateTag(...args: Parameters<RedisStringsHandler[\"revalidateTag\"]>): ReturnType<RedisStringsHandler[\"revalidateTag\"]> {\n return cachedHandler.revalidateTag(...args);\n }\n resetRequestCache(...args: Parameters<RedisStringsHandler[\"resetRequestCache\"]>): ReturnType<RedisStringsHandler[\"resetRequestCache\"]> {\n return cachedHandler.resetRequestCache(...args);\n }\n}","import CachedHandler from \"./CachedHandler\";\nexport default CachedHandler;"],"mappings":";AAAA,SAAS,gBAAgB,oBAAoB;;;AC2B7C,IAAM,sBAAsB;AACrB,IAAM,YAAN,MAAmB;AAAA,EAiBxB,YAAY,SAA2B;AACrC,SAAK,SAAS,QAAQ;AACtB,SAAK,YAAY,QAAQ;AACzB,SAAK,WAAW,QAAQ;AACxB,SAAK,cAAc,GAAG,QAAQ,SAAS,GAAG,mBAAmB,GAAG,QAAQ,QAAQ;AAChF,SAAK,WAAW,QAAQ;AACxB,SAAK,YAAY,QAAQ;AACzB,SAAK,YAAY,QAAQ;AACzB,SAAK,aAAa,QAAQ;AAC1B,SAAK,mBAAmB,QAAQ;AAChC,SAAK,iBAAiB,QAAQ;AAE9B,SAAK,MAAM,oBAAI,IAAe;AAC9B,SAAK,mBAAmB,KAAK,OAAO,UAAU;AAC9C,SAAK,YAAY,IAAI,QAAc,CAAC,YAAY;AAC9C,WAAK,mBAAmB;AAAA,IAC1B,CAAC;AAED,SAAK,MAAM,EAAE,MAAM,CAAC,UAAU;AAC5B,cAAQ,MAAM,8BAA8B,KAAK;AACjD,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,QAAQ;AACpB,QAAI,gBAAiC,CAAC;AACtC,QAAI,CAAC,KAAK,gBAAgB,qBAAqB;AAC7C,oBAAc,KAAK,KAAK,YAAY,CAAC;AACrC,WAAK,oBAAoB;AAAA,IAC3B;AACA,kBAAc,KAAK,KAAK,YAAY,CAAC;AACrC,UAAM,QAAQ,IAAI,aAAa;AAC/B,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,MAAc,cAAc;AAC1B,QAAI,SAAS;AACb,UAAM,eAAe,EAAE,OAAO,KAAK,UAAU;AAE7C,QAAI;AACF,SAAG;AACD,cAAM,cAAc,MAAM,KAAK,OAAO;AAAA,UACpC,8BAA8B,KAAK,SAAS;AAAA,UAC5C,KAAK,YAAY,KAAK;AAAA,UACtB;AAAA,UACA;AAAA,QACF;AACA,mBAAW,EAAE,OAAO,MAAM,KAAK,YAAY,QAAQ;AACjD,cAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,kBAAM,cAAc,KAAK,MAAM,KAAK;AACpC,iBAAK,IAAI,IAAI,OAAO,WAAW;AAAA,UACjC;AAAA,QACF;AACA,iBAAS,YAAY;AAAA,MACvB,SAAS,WAAW;AAGpB,YAAM,KAAK,sBAAsB;AAAA,IACnC,SAAS,OAAO;AACd,cAAQ,MAAM,8BAA8B,KAAK;AACjD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,wBAAwB;AACpC,QAAI,SAAS;AACb,UAAM,cAAc,EAAE,OAAO,KAAK,WAAW,OAAO,GAAG,KAAK,SAAS,IAAI;AACzE,QAAI,aAAuB,CAAC;AAC5B,QAAI;AACF,SAAG;AACD,cAAM,oBAAoB,MAAM,KAAK,OAAO;AAAA,UAC1C,8BAA8B,KAAK,SAAS;AAAA,UAC5C;AAAA,UACA;AAAA,QACF;AACA,qBAAa,WAAW,OAAO,kBAAkB,IAAI;AACrD,iBAAS,kBAAkB;AAAA,MAC7B,SAAS,WAAW;AAEpB,YAAM,gBAAgB,IAAI;AAAA,QACxB,WAAW,IAAI,CAAC,QAAQ,IAAI,UAAU,KAAK,UAAU,MAAM,CAAC;AAAA,MAC9D;AAEA,YAAM,eAAyB,CAAC;AAChC,iBAAW,OAAO,KAAK,IAAI,KAAK,GAAG;AACjC,cAAM,SAAS;AACf,YAAI,CAAC,cAAc,IAAI,MAAM,KAAK,KAAK,WAAW,MAAM,GAAG;AACzD,uBAAa,KAAK,MAAM;AAAA,QAC1B;AAAA,MACF;AAEA,UAAI,aAAa,SAAS,GAAG;AAC3B,cAAM,KAAK,OAAO,YAAY;AAAA,MAChC;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,8CAA8C,KAAK;AACjE,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,sBAAsB;AAC5B,QAAI,KAAK,oBAAoB,KAAK,mBAAmB,GAAG;AACtD,kBAAY,MAAM;AAChB,aAAK,YAAY,EAAE,MAAM,CAAC,UAAU;AAClC,kBAAQ,MAAM,iCAAiC,KAAK;AAAA,QACtD,CAAC;AAAA,MACH,GAAG,KAAK,gBAAgB;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,MAAc,cAAc;AAC1B,UAAM,cAAc,OAAO,YAAoB;AAC7C,YAAM,cAA8B,KAAK,MAAM,OAAO;AACtD,UAAI,YAAY,SAAS,UAAU;AACjC,YAAI,YAAY,QAAQ,UAAa,YAAY,UAAU,QAAW;AACpE,eAAK,IAAI,IAAI,YAAY,KAAK,YAAY,KAAK;AAAA,QACjD;AAAA,MACF,WAAW,YAAY,SAAS,UAAU;AACxC,YAAI,YAAY,MAAM;AACpB,qBAAW,OAAO,YAAY,MAAM;AAClC,iBAAK,IAAI,OAAO,GAAG;AAAA,UACrB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,kBAAkB,OAAO,UAAkB,YAAoB;AACnE,YAAM,MAAM;AACZ,UAAI,IAAI,WAAW,KAAK,SAAS,GAAG;AAClC,cAAM,WAAW,IAAI,UAAU,KAAK,UAAU,MAAM;AACpD,YAAI,KAAK,WAAW,QAAQ,GAAG;AAC7B,gBAAM,KAAK,OAAO,UAAU,IAAI;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,YAAM,KAAK,iBAAiB,QAAQ;AAEpC,YAAM,QAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,KAAK,iBAAiB,UAAU,KAAK,aAAa,WAAW;AAAA;AAAA,QAE7D,KAAK,iBAAiB;AAAA,UACpB,cAAc,KAAK,QAAQ;AAAA,UAC3B;AAAA,QACF;AAAA,QACA,KAAK,iBAAiB;AAAA,UACpB,cAAc,KAAK,QAAQ;AAAA,UAC3B;AAAA,QACF;AAAA,MACF,CAAC;AAGD,WAAK,iBAAiB,GAAG,SAAS,OAAO,QAAQ;AAC/C,gBAAQ,MAAM,4BAA4B,GAAG;AAC7C,YAAI;AACF,gBAAM,KAAK,iBAAiB,KAAK;AACjC,eAAK,mBAAmB,KAAK,OAAO,UAAU;AAC9C,gBAAM,KAAK,YAAY;AAAA,QACzB,SAAS,gBAAgB;AACvB,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,oCAAoC,KAAK;AACvD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAa,iBAAiB;AAC5B,UAAM,KAAK;AAAA,EACb;AAAA,EAEO,IAAI,KAA4B;AACrC,WAAO,KAAK,IAAI,IAAI,GAAG;AAAA,EACzB;AAAA,EAEA,MAAa,IAAI,KAAa,OAAyB;AACrD,SAAK,IAAI,IAAI,KAAK,KAAK;AACvB,UAAM,aAAa,CAAC;AAGpB,QAAI,KAAK,gBAAgB,gBAAgB;AACvC;AAAA,IACF;AACA,QAAI,CAAC,KAAK,gBAAgB,qBAAqB;AAC7C,YAAM,UAAU,8BAA8B,KAAK,SAAS;AAC5D,iBAAW;AAAA,QACT,KAAK,OAAO;AAAA,UACV;AAAA,UACA,KAAK,YAAY,KAAK;AAAA,UACtB;AAAA,UACA,KAAK,UAAU,KAAK;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,gBAAgC;AAAA,MACpC,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AACA,eAAW;AAAA,MACT,KAAK,OAAO,QAAQ,KAAK,aAAa,KAAK,UAAU,aAAa,CAAC;AAAA,IACrE;AACA,UAAM,QAAQ,IAAI,UAAU;AAAA,EAC9B;AAAA,EAEA,MAAa,OACX,MACA,qBAAqB,OACN;AACf,UAAM,YAAY,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AACpD,UAAM,aAAa,CAAC;AAEpB,eAAW,OAAO,WAAW;AAC3B,WAAK,IAAI,OAAO,GAAG;AAAA,IACrB;AAEA,QAAI,CAAC,KAAK,gBAAgB,qBAAqB;AAC7C,YAAM,UAAU,8BAA8B,KAAK,SAAS;AAC5D,iBAAW;AAAA,QACT,KAAK,OAAO,KAAK,SAAS,KAAK,YAAY,KAAK,UAAU,SAAS;AAAA,MACrE;AAAA,IACF;AAEA,QAAI,CAAC,oBAAoB;AACvB,YAAM,kBAAkC;AAAA,QACtC,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AACA,iBAAW;AAAA,QACT,KAAK,OAAO,QAAQ,KAAK,aAAa,KAAK,UAAU,eAAe,CAAC;AAAA,MACvE;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,UAAU;AAAA,EAC9B;AAAA,EAEO,IAAI,KAAsB;AAC/B,WAAO,KAAK,IAAI,IAAI,GAAG;AAAA,EACzB;AAAA,EAEO,UAAyC;AAC9C,WAAO,KAAK,IAAI,QAAQ;AAAA,EAC1B;AACF;;;ACxSO,IAAM,6BAAN,MAGL;AAAA,EAKA,YACE,IACA,eACA,4BACA;AAgBF;AAAA,gCAAuB,CAAC,QAAmB;AAEzC,YAAM,OAAO;AACb,YAAM,YAAY,UAAU,SAAqC;AAE/D,YACE,KAAK,8BACL,KAAK,2BAA2B,IAAI,GAAG,GACvC;AACA,gBAAM,MAAM,MAAM,KAAK,2BACpB,IAAI,GAAG,EACP,KAAK,CAAC,MAAM,gBAAgB,CAAC,CAAC;AACjC,iBAAO;AAAA,QACT;AAGA,cAAM,UAAU,KAAK,GAAG,GAAG,IAAI;AAC/B,aAAK,2BAA2B,IAAI,KAAK,OAAO;AAEhD,YAAI;AACF,gBAAM,SAAS,MAAM;AACrB,iBAAO,gBAAgB,MAAM;AAAA,QAC/B,UAAE;AAEA,qBAAW,MAAM;AACf,iBAAK,2BAA2B,OAAO,GAAG;AAAA,UAC5C,GAAG,KAAK,aAAa;AAAA,QACvB;AAAA,MACF;AACA,aAAO;AAAA,IACT;AA7CE,SAAK,KAAK;AACV,SAAK,gBAAgB;AACrB,SAAK,6BAA6B;AAAA,EACpC;AAAA;AAAA,EAGA,kBAAkB,KAAa,OAAgB;AAC7C,UAAM,gBAAgB,IAAI,QAAW,CAAC,QAAQ,IAAI,KAAK,CAAC;AACxD,SAAK,2BAA2B,IAAI,KAAK,aAAa;AACtD,eAAW,MAAM;AACf,WAAK,2BAA2B,OAAO,GAAG;AAAA,IAC5C,GAAG,KAAK,aAAa;AAAA,EACvB;AAkCF;;;AFhCA,IAAM,6BAA6B;AACnC,IAAM,uBAAuB;AAE7B,SAAS,cAAc,KAAsB;AAC3C,SAAO,IAAI,WAAW,0BAA0B;AAClD;AAEO,SAAS,8BACd,WACgB;AAChB,SAAO,eAAe,EAAE,QAAQ,YAAY,QAAQ,SAAS,EAAE,CAAC;AAClE;AAEA,IAAqB,sBAArB,MAAiE;AAAA,EAoB/D,YAAY;AAAA,IACV,WAAW,QAAQ,IAAI,eAAe,eAAe,IAAI;AAAA,IACzD,YAAY,QAAQ,IAAI,cAAc;AAAA,IACtC,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,yBAAyB;AAAA,IACzB,sBAAsB,KAAK,KAAK;AAAA,IAChC,wBAAwB;AAAA,IACxB,sBAAsB;AAAA,IACtB,kBAAkB,KAAK,KAAK,KAAK;AAAA,IACjC,oBAAoB,CAAC,aACnB,QAAQ,IAAI,eAAe,YAAY,WAAW,MAAM,WAAW;AAAA,EACvE,GAAqC;AACnC,SAAK,YAAY;AACjB,SAAK,YAAY;AACjB,SAAK,wBAAwB;AAC7B,SAAK,sBAAsB;AAC3B,SAAK,kBAAkB;AACvB,SAAK,oBAAoB;AAEzB,QAAI;AACF,WAAK,SAAS,aAAa;AAAA,QACzB,GAAI,aAAa,IAAI,EAAE,SAAS,IAAI,CAAC;AAAA,QACrC,KAAK,QAAQ,IAAI,YACb,WAAW,QAAQ,IAAI,SAAS,IAAI,QAAQ,IAAI,SAAS,KACzD;AAAA,MACN,CAAC;AAED,WAAK,OAAO,GAAG,SAAS,CAAC,UAAU;AACjC,gBAAQ,MAAM,sBAAsB,KAAK;AAAA,MAC3C,CAAC;AAED,WAAK,OACF,QAAQ,EACR,KAAK,MAAM;AACV,gBAAQ,KAAK,yBAAyB;AAAA,MACxC,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,gBAAQ,MAAM,mCAAmC,KAAK;AACtD,aAAK,OAAO,WAAW;AAAA,MACzB,CAAC;AAAA,IACL,SAAS,OAAgB;AACvB,cAAQ,MAAM,mCAAmC;AACjD,YAAM;AAAA,IACR;AAEA,UAAM,aAAa,CAAC,QAClB,QAAQ,wBAAwB,QAAQ;AAE1C,SAAK,gBAAgB,IAAI,UAAoB;AAAA,MAC3C,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,kBACE,sBACA,sBAAsB,KACtB,KAAK,OAAO,KAAK,sBAAsB;AAAA,IAC3C,CAAC;AAED,SAAK,qBAAqB,IAAI,UAAkB;AAAA,MAC9C,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,kBACE,sBACA,sBAAsB,KACtB,KAAK,OAAO,KAAK,sBAAsB;AAAA,IAC3C,CAAC;AAED,SAAK,6BAA6B,IAAI,UAAU;AAAA,MAC9C,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,gBAAgB;AAAA,QACd,qBAAqB;AAAA,QACrB,gBAAgB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,WAA0B,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM;AAChE,SAAK,4BAA4B,IAAI;AAAA,MACnC;AAAA,MACA;AAAA,MACA,KAAK;AAAA,IACP;AACA,SAAK,WAAW;AAChB,SAAK,uBACH,KAAK,0BAA0B;AAAA,EACnC;AAAA,EACA,qBAAqB,MAAqB;AACxC,YAAQ,KAAK,0CAA0C,IAAI;AAAA,EAC7D;AAAA,EAEA,MAAc,sBAAqC;AACjD,UAAM,QAAQ,IAAI;AAAA,MAChB,KAAK,cAAc,eAAe;AAAA,MAClC,KAAK,mBAAmB,eAAe;AAAA,IACzC,CAAC;AACD,QAAI,CAAC,KAAK,OAAO,SAAS;AACxB,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AAAA,EACF;AAAA,EAEA,MAAa,IAAI,KAAmB,KAAmB;AACrD,UAAM,KAAK,oBAAoB;AAE/B,UAAM,YAAY,KAAK,wBACnB,KAAK,qBAAqB,GAAG,IAC7B,KAAK;AACT,UAAM,SAAS,MAAM;AAAA,MACnB,8BAA8B,KAAK,SAAS;AAAA,MAC5C,KAAK,YAAY;AAAA,IACnB;AAEA,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,MAAM,MAAM;AAIpC,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AAEA,QAAI,WAAW,OAAO,SAAS,SAAS;AACtC,iBAAW,MAAM,KAAK,OAAO,OAAO;AAAA,QAClC,WAAW,MAAM,KAAK;AAAA,MACxB,EAAE,SAAS,QAAQ;AAAA,IACrB;AAEA,UAAM,eAAe,oBAAI,IAAI;AAAA,MAC3B,GAAI,KAAK,YAAY,CAAC;AAAA,MACtB,GAAI,KAAK,QAAQ,CAAC;AAAA,IACpB,CAAC;AAED,QAAI,aAAa,SAAS,GAAG;AAC3B,aAAO;AAAA,IACT;AAEA,eAAW,OAAO,cAAc;AAE9B,YAAM,mBAAmB,KAAK,mBAAmB,IAAI,GAAG;AACxD,UAAI,oBAAoB,mBAAmB,WAAW,cAAc;AAClE,cAAM,WAAW,KAAK,YAAY;AAElC,aAAK,OACF,OAAO,8BAA8B,KAAK,SAAS,GAAG,QAAQ,EAC9D,MAAM,CAAC,QAAQ;AACd,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,UACF;AACA,eAAK,OAAO;AAAA,YACV,8BAA8B,KAAK,SAAS;AAAA,YAC5C;AAAA,UACF;AAAA,QACF,CAAC,EACA,QAAQ,YAAY;AACnB,gBAAM,KAAK,cAAc,OAAO,GAAG;AACnC,gBAAM,KAAK,mBAAmB,OAAO,GAAG;AAAA,QAC1C,CAAC;AACH,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EACA,MAAa,IACX,KACA,MACA,KACA;AACA,QAAI,KAAK,SAAS,SAAS;AACzB,cAAQ,KAAK,aAAa,GAAG;AAC7B,WAAK,KAAK,OAAO,OAAO,KAAK,KAAK,KAAK,MAAM,QAAQ,EAAE,SAAS;AAChE,cAAQ,QAAQ,aAAa,GAAG;AAAA,IAClC;AACA,UAAM,KAAK,oBAAoB;AAE/B,SAAK,eAAe,KAAK,IAAI;AAE7B,UAAM,QAAQ,KAAK,UAAU,IAAI;AAIjC,QAAI,KAAK,uBAAuB;AAC9B,WAAK,0BAA0B,kBAAkB,KAAK,KAAK;AAAA,IAC7D;AAEA,UAAM,WACJ,IAAI,cACJ,OAAO,cAAc,IAAI,UAAU,KACnC,IAAI,aAAa,IACb,KAAK,kBAAkB,IAAI,UAAU,IACrC,KAAK,kBAAkB,KAAK,eAAe;AACjD,UAAM,UAAU,8BAA8B,KAAK,SAAS;AAC5D,UAAM,eAAuC,KAAK,OAAO;AAAA,MACvD;AAAA,MACA,KAAK,YAAY;AAAA,MACjB;AAAA,MACA;AAAA,QACE,IAAI;AAAA,MACN;AAAA,IACF;AAEA,QAAI;AACJ,QAAI,IAAI,QAAQ,IAAI,KAAK,SAAS,GAAG;AACnC,YAAM,cAAc,KAAK,cAAc,IAAI,GAAG;AAC9C,YAAM,qBACJ,aAAa,WAAW,IAAI,KAAK,UACjC,YAAY,MAAM,CAAC,MAAM,IAAI,KAAM,SAAS,CAAC,CAAC,KAC9C,IAAI,KAAK,MAAM,CAAC,MAAM,YAAY,SAAS,CAAC,CAAC;AAE/C,UAAI,CAAC,oBAAoB;AACvB,2BAAmB,KAAK,cAAc;AAAA,UACpC;AAAA,UACA,gBAAgB,IAAI,IAAI;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,IAAI,CAAC,cAAc,gBAAgB,CAAC;AAAA,EACpD;AAAA,EACA,MAAa,cAAc,WAAgC;AACzD,UAAM,OAAO,IAAI,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,KAAK,CAAC;AAC7C,UAAM,KAAK,oBAAoB;AAG/B,eAAW,OAAO,MAAM;AACtB,UAAI,cAAc,GAAG,GAAG;AACtB,cAAM,MAAM,KAAK,IAAI;AACrB,cAAM,KAAK,mBAAmB,IAAI,KAAK,GAAG;AAAA,MAC5C;AAAA,IACF;AAEA,UAAM,eAAyB,CAAC;AAEhC,eAAW,CAAC,KAAK,UAAU,KAAK,KAAK,cAAc,QAAQ,GAAG;AAC5D,UAAI,WAAW,KAAK,CAAC,QAAQ,KAAK,IAAI,GAAG,CAAC,GAAG;AAC3C,qBAAa,KAAK,GAAG;AAAA,MACvB;AAAA,IACF;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B;AAAA,IACF;AAEA,UAAM,gBAAgB,aAAa,IAAI,CAAC,QAAQ,KAAK,YAAY,GAAG;AAEpE,UAAM,UAAU,8BAA8B,KAAK,SAAS;AAE5D,UAAM,sBAAsB,KAAK,OAAO,OAAO,SAAS,aAAa;AAGrE,QAAI,KAAK,yBAAyB,KAAK,sBAAsB,GAAG;AAC9D,iBAAW,OAAO,cAAc;AAC9B,aAAK,2BAA2B,OAAO,GAAG;AAAA,MAC5C;AAAA,IACF;AAEA,UAAM,sBAAsB,KAAK,cAAc,OAAO,YAAY;AAElE,UAAM,QAAQ,IAAI,CAAC,qBAAqB,mBAAmB,CAAC;AAAA,EAC9D;AACF;;;AGhVA,IAAI;AAEJ,IAAqB,gBAArB,MAA2D;AAAA,EACzD,YAAY,SAA2C;AACrD,QAAI,CAAC,eAAe;AAClB,cAAQ,IAAI,wBAAwB;AACpC,sBAAgB,IAAI,oBAAoB,OAAO;AAAA,IACjD;AAAA,EACF;AAAA,EACA,OAAO,MAAsF;AAC3F,WAAO,cAAc,IAAI,GAAG,IAAI;AAAA,EAClC;AAAA,EACA,OAAO,MAAsF;AAC3F,WAAO,cAAc,IAAI,GAAG,IAAI;AAAA,EAClC;AAAA,EACA,iBAAiB,MAA0G;AACzH,WAAO,cAAc,cAAc,GAAG,IAAI;AAAA,EAC5C;AAAA,EACA,qBAAqB,MAAkH;AACrI,WAAO,cAAc,kBAAkB,GAAG,IAAI;AAAA,EAChD;AACF;;;ACvBA,IAAO,gBAAQ;","names":[]}