From ab8aeb157e7eac60dd192209a3aed941446b1ae6 Mon Sep 17 00:00:00 2001 From: "suxiaobin.sxb" Date: Tue, 21 May 2024 11:46:42 +0800 Subject: [PATCH 1/2] feat: improve performance of scan mode --- internal/reader/scan_standalone_reader.go | 86 ++++++++++++++++------- shake.toml | 2 +- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/internal/reader/scan_standalone_reader.go b/internal/reader/scan_standalone_reader.go index c9a21ab3..c1061d13 100644 --- a/internal/reader/scan_standalone_reader.go +++ b/internal/reader/scan_standalone_reader.go @@ -2,6 +2,7 @@ package reader import ( "context" + "errors" "fmt" "math/bits" "regexp" @@ -26,7 +27,7 @@ type ScanReaderOptions struct { KSN bool `mapstructure:"ksn" default:"false"` DBS []int `mapstructure:"dbs"` PreferReplica bool `mapstructure:"prefer_replica" default:"false"` - Count int `mapstructure:"count" default:"2048"` + Count int `mapstructure:"count" default:"1"` } type dbKey struct { @@ -34,12 +35,19 @@ type dbKey struct { key string } +type needRestoreItem struct { + dbId int + key string +} + type scanStandaloneReader struct { - ctx context.Context - dbs []int - opts *ScanReaderOptions - ch chan *entry.Entry - keyQueue *utils.UniqueQueue + ctx context.Context + dbs []int + opts *ScanReaderOptions + ch chan *entry.Entry + needDumpQueue *utils.UniqueQueue + needRestoreChan chan *needRestoreItem + dumpClient *client.Redis stat struct { Name string `json:"name"` @@ -72,7 +80,8 @@ func NewScanStandaloneReader(ctx context.Context, opts *ScanReaderOptions) Reade r.opts = opts r.ch = make(chan *entry.Entry, 1024) r.stat.Name = "reader_" + strings.Replace(opts.Address, ":", "_", -1) - r.keyQueue = utils.NewUniqueQueue(100000) // cache 100000 keys + r.needDumpQueue = utils.NewUniqueQueue(100000) // cache 100000 keys + r.needRestoreChan = make(chan *needRestoreItem, 1024) // inflight 1024 keys return r } @@ -80,7 +89,8 @@ func (r *scanStandaloneReader) StartRead(ctx context.Context) chan *entry.Entry r.ctx = ctx r.subscript() go r.scan() - go r.fetch() + go r.dump() + go r.restore() return r.ch } @@ -104,7 +114,8 @@ func (r *scanStandaloneReader) subscript() { for { select { case <-r.ctx.Done(): - close(r.keyQueue.Ch) + log.Infof("[%s] scanStandaloneReader subscript finished.", r.stat.Name) + r.needDumpQueue.Close() return default: resp, err := c.Receive() @@ -120,7 +131,7 @@ func (r *scanStandaloneReader) subscript() { } // if the db is not in the dbs, ignore it if _, ok := dbIDmap[dbIdInt]; ok { - r.keyQueue.Put(dbKey{db: dbIdInt, key: key}) + r.needDumpQueue.Put(dbKey{db: dbIdInt, key: key}) } } } @@ -141,10 +152,18 @@ func (r *scanStandaloneReader) scan() { var cursor uint64 = 0 count := r.opts.Count for { + select { + case <-r.ctx.Done(): + log.Infof("[%s] scanStandaloneReader scan finished.", r.stat.Name) + r.needDumpQueue.Close() + return + default: + } + var keys []string cursor, keys = c.Scan(cursor, count) for _, key := range keys { - r.keyQueue.Put(dbKey{dbId, key}) // pass value not pointer + r.needDumpQueue.Put(dbKey{dbId, key}) // pass value not pointer } // stat @@ -159,31 +178,45 @@ func (r *scanStandaloneReader) scan() { } r.stat.ScanFinished = true if !r.opts.KSN { - r.keyQueue.Close() + r.needDumpQueue.Close() } } -func (r *scanStandaloneReader) fetch() { +func (r *scanStandaloneReader) dump() { nowDbId := 0 - c := client.NewRedisClient(r.ctx, r.opts.Address, r.opts.Username, r.opts.Password, r.opts.Tls) - defer c.Close() - for item := range r.keyQueue.Ch { - r.stat.NeedUpdateCount = int64(r.keyQueue.Len()) + r.dumpClient = client.NewRedisClient(r.ctx, r.opts.Address, r.opts.Username, r.opts.Password, r.opts.Tls) + for item := range r.needDumpQueue.Ch { + r.stat.NeedUpdateCount = int64(r.needDumpQueue.Len()) dbId := item.(dbKey).db key := item.(dbKey).key if nowDbId != dbId { - reply := c.DoWithStringReply("SELECT", strconv.Itoa(dbId)) - if reply != "OK" { + r.dumpClient.Send("SELECT", strconv.Itoa(dbId)) + nowDbId = dbId + } + // dump + r.dumpClient.Send("DUMP", key) + r.dumpClient.Send("PTTL", key) + r.needRestoreChan <- &needRestoreItem{dbId, key} + } + close(r.needRestoreChan) + log.Infof("[%s] scanStandaloneReader dump finished.", r.stat.Name) +} + +func (r *scanStandaloneReader) restore() { + nowDbId := 0 + for item := range r.needRestoreChan { + dbId := item.dbId + key := item.key + if nowDbId != dbId { + reply, err := r.dumpClient.Receive() + if err != nil || reply != "OK" { log.Panicf("scanStandaloneReader select db failed. db=[%d]", dbId) } nowDbId = dbId } - // dump - c.Send("DUMP", key) - c.Send("PTTL", key) - iDump, err1 := c.Receive() - iPttl, err2 := c.Receive() - if err1 == proto.Nil { + iDump, err1 := r.dumpClient.Receive() + iPttl, err2 := r.dumpClient.Receive() + if errors.Is(err1, proto.Nil) { continue // key not exist } else if err1 != nil { log.Panicf(err1.Error()) @@ -227,8 +260,7 @@ func (r *scanStandaloneReader) fetch() { } } } - - log.Infof("[%s] scanStandaloneReader fetch finished.", r.stat.Name) + log.Infof("[%s] scanStandaloneReader restore finished.", r.stat.Name) close(r.ch) } diff --git a/shake.toml b/shake.toml index 99d52c61..93dc793f 100644 --- a/shake.toml +++ b/shake.toml @@ -9,7 +9,7 @@ password = "" # keep empty if no authentication is required tls = false sync_rdb = true # set to false if you don't want to sync rdb sync_aof = true # set to false if you don't want to sync aof -prefer_replica = true # set to true if you want to sync from replica node +count = 1 # number of keys to scan per iteration # [scan_reader] # cluster = false # set to true if source is a redis cluster From c5d84e61850e3d9cf8dadffb70c275a0261455ea Mon Sep 17 00:00:00 2001 From: "suxiaobin.sxb" Date: Wed, 15 May 2024 11:30:31 +0800 Subject: [PATCH 2/2] docs: add more docs and refine doc website --- docs/.vitepress/config.ts | 140 +-- docs/.vitepress/en.ts | 64 ++ docs/.vitepress/zh.ts | 63 ++ docs/package-lock.json | 1167 ++++++++++++++------- docs/src/en/index.md | 3 +- docs/src/index.md | 16 +- docs/src/zh/index.md | 26 + docs/src/zh/others/consistent.md | 16 + docs/src/zh/reader/scan_reader.md | 55 +- docs/src/zh/reader/sync_reader.md | 8 + internal/reader/scan_standalone_reader.go | 68 +- shake.toml | 21 +- 12 files changed, 1057 insertions(+), 590 deletions(-) create mode 100644 docs/.vitepress/en.ts create mode 100644 docs/.vitepress/zh.ts create mode 100644 docs/src/zh/index.md create mode 100644 docs/src/zh/others/consistent.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index cea18636..a1c9cef3 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,126 +1,36 @@ -import { defineConfig } from 'vitepress' +import { defineConfigWithTheme } from "vitepress" +import { en } from "./en" +import { zh } from "./zh" // https://vitepress.dev/reference/site-config -export default defineConfig({ - head: [['link', { rel: 'icon', href: '/RedisShake/favicon.ico' }]], +export default defineConfigWithTheme({ + head: [["link", { rel: "icon", href: "/RedisShake/favicon.ico" }]], base: "/RedisShake/", title: "RedisShake", - description: "RedisShake is a tool for processing and migrating Redis data.", - srcDir: './src', - locales: { - root: { - label: '中文', - lang: 'zh', // optional, will be added as `lang` attribute on `html` tag - themeConfig: { - // https://vitepress.dev/reference/default-theme-config - nav: [ - { text: '主页', link: '/' }, - { text: '使用文档', link: '/zh/guide/getting-started' }, - { text: '云原生内存数据库 Tair', link: 'https://www.aliyun.com/product/apsaradb/kvstore/tair' } - ], - sidebar: [ - { - text: '介绍', - items: [ - { text: '什么是 RedisShake', link: '/zh/guide/introduction' }, - { text: '快速上手', link: '/zh/guide/getting-started' }, - { text: '配置', link: '/zh/guide/config' }, - { text: '迁移模式选择', link: '/zh/guide/mode' }, - ] - }, - { - text: 'Reader', - items: [ - { text: 'Sync Reader', link: '/zh/reader/sync_reader' }, - { text: 'Scan Reader', link: '/zh/reader/scan_reader' }, - { text: 'RDB Reader', link: '/zh/reader/rdb_reader' }, - ] - }, - { - text: 'Writer', - items: [ - { text: 'Redis Writer', link: '/zh/writer/redis_writer' }, - ] - }, - { - text: 'Function', - items: [ - { text: '什么是 function', link: '/zh/function/introduction' }, - { text: '最佳实践', link: '/zh/function/best_practices' } - ] - }, - { - text: 'Others', - items: [ - { text: 'Redis Modules', link: '/zh/others/modules' }, - ] - }, - ], - footer: { - message: 'Released under the MIT License.', - copyright: 'Copyright © 2019-present Tair' - } - } - }, - en: { - label: 'English', - lang: 'en', // optional, will be added as `lang` attribute on `html` tag - themeConfig: { - // https://vitepress.dev/reference/default-theme-config - nav: [ - { text: 'Home', link: '/en/' }, - { text: 'User Guide', link: '/en/guide/getting-started' }, - { text: 'Tair', link: 'https://www.alibabacloud.com/product/tair' } - ], - sidebar: [ - { - text: 'Introduction', - items: [ - { text: 'What is RedisShake', link: '/en/guide/introduction' }, - { text: 'Getting Started', link: '/en/guide/getting-started' }, - { text: 'Configuration', link: '/en/guide/config' }, - { text: 'Migration Mode Selection', link: '/en/guide/mode' }, - ] - }, - { - text: 'Reader', - items: [ - { text: 'Sync Reader', link: '/en/reader/sync_reader' }, - { text: 'Scan Reader', link: '/en/reader/scan_reader' }, - { text: 'RDB Reader', link: '/en/reader/rdb_reader' }, - ] - }, - { - text: 'Writer', - items: [ - { text: 'Redis Writer', link: '/en/writer/redis_writer' }, - ] - }, - { - text: 'Function', - items: [ - { text: 'What is function', link: '/en/function/introduction' }, - { text: 'Best Practices', link: '/en/function/best_practices' } - ] - }, - { - text: 'Others', - items: [ - { text: 'Redis Modules', link: '/en/others/modules' }, - ] - }, - ], - footer: { - message: 'Released under the MIT License.', - copyright: 'Copyright © 2019-present Tair' - } - } - }, + srcDir: "./src", + locales: { + zh: { label: "简体中文", ...zh }, + en: { label: "English", ...en }, }, themeConfig: { + nav: zh.themeConfig?.nav, socialLinks: [ - { icon: 'github', link: 'https://github.com/tair-opensource/RedisShake' } + { icon: "github", link: "https://github.com/tair-opensource/RedisShake" } ], + editLink: { + pattern: "https://github.com/tair-opensource/RedisShake/tree/v4/docs/src/:path" + }, + lastUpdated: { + text: "Updated at", + formatOptions: { + dateStyle: "full", + timeStyle: "medium" + } + }, + footer: { + message: "Released under the MIT License.", + copyright: "Copyright © 2019-present Tair" + } } }) diff --git a/docs/.vitepress/en.ts b/docs/.vitepress/en.ts new file mode 100644 index 00000000..6d3f8d2a --- /dev/null +++ b/docs/.vitepress/en.ts @@ -0,0 +1,64 @@ +import { createRequire } from 'module' +import { defineConfig, type DefaultTheme } from 'vitepress' + +const require = createRequire(import.meta.url) +const pkg = require('vitepress/package.json') + +export const en = defineConfig({ + lang: 'en-US', + description: 'Vite & Vue powered static site generator.', + + themeConfig: { + nav: nav(), + sidebar: sidebar(), + } +}) + +function nav(): DefaultTheme.NavItem[] { + return [ + { text: 'Home', link: '/en/' }, + { text: 'User Guide', link: '/en/guide/getting-started' }, + { text: 'Tair', link: 'https://www.alibabacloud.com/product/tair' } + ] +} + +function sidebar(): DefaultTheme.SidebarItem[] { + return [ + { + text: 'Introduction', + items: [ + { text: 'What is RedisShake', link: '/en/guide/introduction' }, + { text: 'Getting Started', link: '/en/guide/getting-started' }, + { text: 'Configuration', link: '/en/guide/config' }, + { text: 'Migration Mode Selection', link: '/en/guide/mode' }, + ] + }, + { + text: 'Reader', + items: [ + { text: 'Sync Reader', link: '/en/reader/sync_reader' }, + { text: 'Scan Reader', link: '/en/reader/scan_reader' }, + { text: 'RDB Reader', link: '/en/reader/rdb_reader' }, + ] + }, + { + text: 'Writer', + items: [ + { text: 'Redis Writer', link: '/en/writer/redis_writer' }, + ] + }, + { + text: 'Function', + items: [ + { text: 'What is function', link: '/en/function/introduction' }, + { text: 'Best Practices', link: '/en/function/best_practices' } + ] + }, + { + text: 'Others', + items: [ + { text: 'Redis Modules', link: '/en/others/modules' }, + ] + }, + ] +} diff --git a/docs/.vitepress/zh.ts b/docs/.vitepress/zh.ts new file mode 100644 index 00000000..2ed56de1 --- /dev/null +++ b/docs/.vitepress/zh.ts @@ -0,0 +1,63 @@ +import { createRequire } from 'module' +import { defineConfig, type DefaultTheme } from 'vitepress' + +const require = createRequire(import.meta.url) +const pkg = require('vitepress/package.json') + +export const zh = defineConfig({ + lang: 'zh-Hans', + themeConfig: { + nav: nav(), + sidebar: sidebar(), + } +}) + +function nav(): DefaultTheme.NavItem[] { + return [ + { text: '主页', link: '/' }, + { text: '使用文档', link: '/zh/guide/getting-started' }, + { text: '云原生内存数据库 Tair', link: 'https://www.aliyun.com/product/apsaradb/kvstore/tair' } + ] +} + +function sidebar(): DefaultTheme.SidebarItem[] { + return [ + { + text: '介绍', + items: [ + { text: '什么是 RedisShake', link: '/zh/guide/introduction' }, + { text: '快速上手', link: '/zh/guide/getting-started' }, + { text: '配置', link: '/zh/guide/config' }, + { text: '迁移模式选择', link: '/zh/guide/mode' }, + ] + }, + { + text: 'Reader', + items: [ + { text: 'Sync Reader', link: '/zh/reader/sync_reader' }, + { text: 'Scan Reader', link: '/zh/reader/scan_reader' }, + { text: 'RDB Reader', link: '/zh/reader/rdb_reader' }, + ] + }, + { + text: 'Writer', + items: [ + { text: 'Redis Writer', link: '/zh/writer/redis_writer' }, + ] + }, + { + text: 'Function', + items: [ + { text: '什么是 function', link: '/zh/function/introduction' }, + { text: '最佳实践', link: '/zh/function/best_practices' } + ] + }, + { + text: 'Others', + items: [ + { text: 'Redis Modules', link: '/zh/others/modules' }, + { text: '如何判断数据一致', link: '/zh/others/consistent' }, + ] + }, + ] +} diff --git a/docs/package-lock.json b/docs/package-lock.json index 017ce21d..0ab285e4 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -54,138 +54,157 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.19.1.tgz", - "integrity": "sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", + "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.19.1" + "@algolia/cache-common": "4.23.3" } }, "node_modules/@algolia/cache-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.19.1.tgz", - "integrity": "sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", + "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==", "dev": true }, "node_modules/@algolia/cache-in-memory": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.19.1.tgz", - "integrity": "sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", + "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.19.1" + "@algolia/cache-common": "4.23.3" } }, "node_modules/@algolia/client-account": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.19.1.tgz", - "integrity": "sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", + "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/client-search": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-analytics": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.19.1.tgz", - "integrity": "sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", + "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/client-search": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.19.1.tgz", - "integrity": "sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", + "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-personalization": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.19.1.tgz", - "integrity": "sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", + "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-search": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.19.1.tgz", - "integrity": "sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", + "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/logger-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.19.1.tgz", - "integrity": "sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", + "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==", "dev": true }, "node_modules/@algolia/logger-console": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.19.1.tgz", - "integrity": "sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", + "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", "dev": true, "dependencies": { - "@algolia/logger-common": "4.19.1" + "@algolia/logger-common": "4.23.3" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", + "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.19.1.tgz", - "integrity": "sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", + "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.19.1" + "@algolia/requester-common": "4.23.3" } }, "node_modules/@algolia/requester-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.19.1.tgz", - "integrity": "sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", + "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==", "dev": true }, "node_modules/@algolia/requester-node-http": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.19.1.tgz", - "integrity": "sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", + "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.19.1" + "@algolia/requester-common": "4.23.3" } }, "node_modules/@algolia/transporter": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.19.1.tgz", - "integrity": "sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", + "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.19.1", - "@algolia/logger-common": "4.19.1", - "@algolia/requester-common": "4.19.1" + "@algolia/cache-common": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/requester-common": "4.23.3" } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -195,36 +214,37 @@ } }, "node_modules/@docsearch/css": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.1.tgz", - "integrity": "sha512-2Pu9HDg/uP/IT10rbQ+4OrTQuxIWdKVUEdcw9/w7kZJv9NeHS6skJx1xuRiFyoGKwAzcHXnLp7csE99sj+O1YA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", + "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", "dev": true }, "node_modules/@docsearch/js": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.1.tgz", - "integrity": "sha512-EXi8de5njxgP6TV3N9ytnGRLG9zmBNTEZjR4VzwPcpPLbZxxTLG2gaFyJyKiFVQxHW/DPlMrDJA3qoRRGEkgZw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz", + "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==", "dev": true, "dependencies": { - "@docsearch/react": "3.5.1", + "@docsearch/react": "3.6.0", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.1.tgz", - "integrity": "sha512-t5mEODdLzZq4PTFAm/dvqcvZFdPDMdfPE5rJS5SC8OUq9mPzxEy6b+9THIqNM9P0ocCb4UC5jqBrxKclnuIbzQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", + "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", "dev": true, "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.1", - "algoliasearch": "^4.0.0" + "@docsearch/css": "3.6.0", + "algoliasearch": "^4.19.1" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 19.0.0", "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0" + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" }, "peerDependenciesMeta": { "@types/react": { @@ -235,13 +255,32 @@ }, "react-dom": { "optional": true + }, + "search-insights": { + "optional": true } } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.19.tgz", - "integrity": "sha512-1uOoDurJYh5MNqPqpj3l/TQCI1V25BXgChEldCB7D6iryBYqYKrbZIhYO5AI9fulf66sM8UJpc3UcCly2Tv28w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -255,9 +294,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.19.tgz", - "integrity": "sha512-4+jkUFQxZkQfQOOxfGVZB38YUWHMJX2ihZwF+2nh8m7bHdWXpixiurgGRN3c/KMSwlltbYI0/i929jwBRMFzbA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -271,9 +310,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.19.tgz", - "integrity": "sha512-ae5sHYiP/Ogj2YNrLZbWkBmyHIDOhPgpkGvFnke7XFGQldBDWvc/AyYwSLpNuKw9UNkgnLlB/jPpnBmlF3G9Bg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -287,9 +326,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.19.tgz", - "integrity": "sha512-HIpQvNQWFYROmWDANMRL+jZvvTQGOiTuwWBIuAsMaQrnStedM+nEKJBzKQ6bfT9RFKH2wZ+ej+DY7+9xHBTFPg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -303,9 +342,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.19.tgz", - "integrity": "sha512-m6JdvXJQt0thNLIcWOeG079h2ivhYH4B5sVCgqb/B29zTcFd7EE8/J1nIUHhdtwGeItdUeqKaqqb4towwxvglQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -319,9 +358,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.19.tgz", - "integrity": "sha512-G0p4EFMPZhGn/xVNspUyMQbORH3nlKTV0bFNHPIwLraBuAkTeMyxNviTe0ZXUbIXQrR1lrwniFjNFU4s+x7veQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -335,9 +374,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.19.tgz", - "integrity": "sha512-hBxgRlG42+W+j/1/cvlnSa+3+OBKeDCyO7OG2ICya1YJaSCYfSpuG30KfOnQHI7Ytgu4bRqCgrYXxQEzy0zM5Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -351,9 +390,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.19.tgz", - "integrity": "sha512-qtWyoQskfJlb9MD45mvzCEKeO4uCnDZ7lPFeNqbfaaJHqBiH9qA5Vu2EuckqYZuFMJWy1l4dxTf9NOulCVfUjg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -367,9 +406,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.19.tgz", - "integrity": "sha512-X8g33tczY0GsJq3lhyBrjnFtaKjWVpp1gMq5IlF9BQJ3TUfSK74nQnz9mRIEejmcV+OIYn6bkOJeUaU1Knrljg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -383,9 +422,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.19.tgz", - "integrity": "sha512-SAkRWJgb+KN+gOhmbiE6/wu23D6HRcGQi15cB13IVtBZZgXxygTV5GJlUAKLQ5Gcx0gtlmt+XIxEmSqA6sZTOw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -399,9 +438,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.19.tgz", - "integrity": "sha512-YLAslaO8NsB9UOxBchos82AOMRDbIAWChwDKfjlGrHSzS3v1kxce7dGlSTsrb0PJwo1KYccypN3VNjQVLtz7LA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -415,9 +454,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.19.tgz", - "integrity": "sha512-vSYFtlYds/oTI8aflEP65xo3MXChMwBOG1eWPGGKs/ev9zkTeXVvciU+nifq8J1JYMz+eQ4J9JDN0O2RKF8+1Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -431,9 +470,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.19.tgz", - "integrity": "sha512-tgG41lRVwlzqO9tv9l7aXYVw35BxKXLtPam1qALScwSqPivI8hjkZLNH0deaaSCYCFT9cBIdB+hUjWFlFFLL9A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -447,9 +486,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.19.tgz", - "integrity": "sha512-EgBZFLoN1S5RuB4cCJI31pBPsjE1nZ+3+fHRjguq9Ibrzo29bOLSBcH1KZJvRNh5qtd+fcYIGiIUia8Jw5r1lQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -463,9 +502,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.19.tgz", - "integrity": "sha512-q1V1rtHRojAzjSigZEqrcLkpfh5K09ShCoIsdTakozVBnM5rgV58PLFticqDp5UJ9uE0HScov9QNbbl8HBo6QQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -479,9 +518,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.19.tgz", - "integrity": "sha512-D0IiYjpZRXxGZLQfsydeAD7ZWqdGyFLBj5f2UshJpy09WPs3qizDCsEr8zyzcym6Woj/UI9ZzMIXwvoXVtyt0A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -495,9 +534,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.19.tgz", - "integrity": "sha512-3tt3SOS8L3D54R8oER41UdDshlBIAjYhdWRPiZCTZ1E41+shIZBpTjaW5UaN/jD1ENE/Ok5lkeqhoNMbxstyxw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -511,9 +550,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.19.tgz", - "integrity": "sha512-MxbhcuAYQPlfln1EMc4T26OUoeg/YQc6wNoEV8xvktDKZhLtBxjkoeESSo9BbPaGKhAPzusXYj5n8n5A8iZSrA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -527,9 +566,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.19.tgz", - "integrity": "sha512-m0/UOq1wj25JpWqOJxoWBRM9VWc3c32xiNzd+ERlYstUZ6uwx5SZsQUtkiFHaYmcaoj+f6+Tfcl7atuAz3idwQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -543,9 +582,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.19.tgz", - "integrity": "sha512-L4vb6pcoB1cEcXUHU6EPnUhUc4+/tcz4OqlXTWPcSQWxegfmcOprhmIleKKwmMNQVc4wrx/+jB7tGkjjDmiupg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -559,9 +598,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.19.tgz", - "integrity": "sha512-rQng7LXSKdrDlNDb7/v0fujob6X0GAazoK/IPd9C3oShr642ri8uIBkgM37/l8B3Rd5sBQcqUXoDdEy75XC/jg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -575,9 +614,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.19.tgz", - "integrity": "sha512-z69jhyG20Gq4QL5JKPLqUT+eREuqnDAFItLbza4JCmpvUnIlY73YNjd5djlO7kBiiZnvTnJuAbOjIoZIOa1GjA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -596,162 +635,428 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", + "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", + "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", + "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", + "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", + "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", + "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", + "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", + "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", + "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", + "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", + "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", + "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", + "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", + "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", + "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", + "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.0.tgz", + "integrity": "sha512-NIEAi5U5R7BLkbW1pG/ZKu3eb1lzc3/+jD0lFsuxMT7zjaf9bbNwdNyMr7zh/Zl8EXQtQ+MYBAt5G+JLu+5DlA==", + "dev": true + }, + "node_modules/@shikijs/transformers": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.6.0.tgz", + "integrity": "sha512-qGfHe1ECiqfE2STPWvfogIj/9Q0SK+MCRJdoITkW7AmFuB7DmbFnBT2US84+zklJOB51MzNO8RUXZiauWssJlQ==", + "dev": true, + "dependencies": { + "shiki": "1.6.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dev": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "node_modules/@types/web-bluetooth": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", - "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", "dev": true }, "node_modules/@vitejs/plugin-vue": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", - "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", "dev": true, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^4.0.0", + "vite": "^5.0.0", "vue": "^3.2.25" } }, "node_modules/@vue/compiler-core": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", - "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", "dev": true, "dependencies": { - "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.4", + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", + "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", - "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", - "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", "dev": true, "dependencies": { - "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.4", - "@vue/compiler-dom": "3.3.4", - "@vue/compiler-ssr": "3.3.4", - "@vue/reactivity-transform": "3.3.4", - "@vue/shared": "3.3.4", + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", "estree-walker": "^2.0.2", - "magic-string": "^0.30.0", - "postcss": "^8.1.10", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", - "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", - "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==", - "dev": true + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.2.1.tgz", + "integrity": "sha512-6oNCtyFOrNdqm6GUkFujsCgFlpbsHLnZqq7edeM/+cxAbMyCWvsaCsIMUaz7AiluKLccCGEM8fhOsjaKgBvb7g==", + "dev": true, + "dependencies": { + "@vue/devtools-kit": "^7.2.1" + } }, - "node_modules/@vue/reactivity": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", - "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "node_modules/@vue/devtools-kit": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.2.1.tgz", + "integrity": "sha512-Wak/fin1X0Q8LLIfCAHBrdaaB+R6IdpSXsDByPHbQ3BmkCP0/cIo/oEGp9i0U2+gEqD4L3V9RDjNf1S34DTzQQ==", + "dev": true, + "dependencies": { + "@vue/devtools-shared": "^7.2.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.2.1.tgz", + "integrity": "sha512-PCJF4UknJmOal68+X9XHyVeQ+idv0LFujkTOIW30+GaMJqwFVN9LkQKX4gLqn61KkGMdJTzQ1bt7EJag3TI6AA==", "dev": true, "dependencies": { - "@vue/shared": "3.3.4" + "rfdc": "^1.3.1" } }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", - "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "node_modules/@vue/reactivity": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", + "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", "dev": true, "dependencies": { - "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.4", - "@vue/shared": "3.3.4", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.0" + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-core": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", - "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", + "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", "dev": true, "dependencies": { - "@vue/reactivity": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/reactivity": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-dom": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", - "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", + "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", "dev": true, "dependencies": { - "@vue/runtime-core": "3.3.4", - "@vue/shared": "3.3.4", - "csstype": "^3.1.1" + "@vue/runtime-core": "3.4.27", + "@vue/shared": "3.4.27", + "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", - "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", + "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", "dev": true, "dependencies": { - "@vue/compiler-ssr": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27" }, "peerDependencies": { - "vue": "3.3.4" + "vue": "3.4.27" } }, "node_modules/@vue/shared": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", - "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vueuse/core": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.3.0.tgz", - "integrity": "sha512-BEM5yxcFKb5btFjTSAFjTu5jmwoW66fyV9uJIP4wUXXU8aR5Hl44gndaaXp7dC5HSObmgbnR2RN+Un1p68Mf5Q==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz", + "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==", "dev": true, "dependencies": { - "@types/web-bluetooth": "^0.0.17", - "@vueuse/metadata": "10.3.0", - "@vueuse/shared": "10.3.0", - "vue-demi": ">=0.14.5" + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.9.0", + "@vueuse/shared": "10.9.0", + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", - "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "dev": true, "hasInstallScript": true, "bin": { @@ -775,14 +1080,14 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.3.0.tgz", - "integrity": "sha512-Jgiv7oFyIgC6BxmDtiyG/fxyGysIds00YaY7sefwbhCZ2/tjEx1W/1WcsISSJPNI30in28+HC2J4uuU8184ekg==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.9.0.tgz", + "integrity": "sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==", "dev": true, "dependencies": { - "@vueuse/core": "10.3.0", - "@vueuse/shared": "10.3.0", - "vue-demi": ">=0.14.5" + "@vueuse/core": "10.9.0", + "@vueuse/shared": "10.9.0", + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -841,9 +1146,9 @@ } }, "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", - "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "dev": true, "hasInstallScript": true, "bin": { @@ -867,30 +1172,30 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.3.0.tgz", - "integrity": "sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz", + "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.3.0.tgz", - "integrity": "sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz", + "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==", "dev": true, "dependencies": { - "vue-demi": ">=0.14.5" + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", - "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "dev": true, "hasInstallScript": true, "bin": { @@ -914,49 +1219,50 @@ } }, "node_modules/algoliasearch": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.19.1.tgz", - "integrity": "sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", + "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", "dev": true, "dependencies": { - "@algolia/cache-browser-local-storage": "4.19.1", - "@algolia/cache-common": "4.19.1", - "@algolia/cache-in-memory": "4.19.1", - "@algolia/client-account": "4.19.1", - "@algolia/client-analytics": "4.19.1", - "@algolia/client-common": "4.19.1", - "@algolia/client-personalization": "4.19.1", - "@algolia/client-search": "4.19.1", - "@algolia/logger-common": "4.19.1", - "@algolia/logger-console": "4.19.1", - "@algolia/requester-browser-xhr": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/requester-node-http": "4.19.1", - "@algolia/transporter": "4.19.1" - } - }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, - "node_modules/body-scroll-lock": { - "version": "4.0.0-beta.0", - "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-4.0.0-beta.0.tgz", - "integrity": "sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==", - "dev": true + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-account": "4.23.3", + "@algolia/client-analytics": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-personalization": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/recommend": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" + } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { - "version": "0.18.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.19.tgz", - "integrity": "sha512-ra3CaIKCzJp5bU5BDfrCc0FRqKj71fQi+gbld0aj6lN0ifuX2fWJYPgLVLGwPfA+ruKna+OWwOvf/yHj6n+i0g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -966,28 +1272,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.19", - "@esbuild/android-arm64": "0.18.19", - "@esbuild/android-x64": "0.18.19", - "@esbuild/darwin-arm64": "0.18.19", - "@esbuild/darwin-x64": "0.18.19", - "@esbuild/freebsd-arm64": "0.18.19", - "@esbuild/freebsd-x64": "0.18.19", - "@esbuild/linux-arm": "0.18.19", - "@esbuild/linux-arm64": "0.18.19", - "@esbuild/linux-ia32": "0.18.19", - "@esbuild/linux-loong64": "0.18.19", - "@esbuild/linux-mips64el": "0.18.19", - "@esbuild/linux-ppc64": "0.18.19", - "@esbuild/linux-riscv64": "0.18.19", - "@esbuild/linux-s390x": "0.18.19", - "@esbuild/linux-x64": "0.18.19", - "@esbuild/netbsd-x64": "0.18.19", - "@esbuild/openbsd-x64": "0.18.19", - "@esbuild/sunos-x64": "0.18.19", - "@esbuild/win32-arm64": "0.18.19", - "@esbuild/win32-ia32": "0.18.19", - "@esbuild/win32-x64": "0.18.19" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/estree-walker": { @@ -997,18 +1304,18 @@ "dev": true }, "node_modules/focus-trap": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", - "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", "dev": true, "dependencies": { "tabbable": "^6.2.0" } }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -1019,22 +1326,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "dev": true }, "node_modules/magic-string": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", - "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/mark.js": { @@ -1044,15 +1348,21 @@ "dev": true }, "node_modules/minisearch": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.1.0.tgz", - "integrity": "sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", + "dev": true + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -1067,16 +1377,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/picocolors": { + "node_modules/perfect-debounce": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/postcss": { - "version": "8.4.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", - "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -1093,66 +1409,94 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/preact": { - "version": "10.16.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.16.0.tgz", - "integrity": "sha512-XTSj3dJ4roKIC93pald6rWuB2qQJO9gO2iLLyTe87MrjQN+HklueLsmskbywEWqCHlclgz3/M4YLL2iBr9UmMA==", + "version": "10.22.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz", + "integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==", "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, "node_modules/rollup": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.2.tgz", - "integrity": "sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", + "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.17.2", + "@rollup/rollup-android-arm64": "4.17.2", + "@rollup/rollup-darwin-arm64": "4.17.2", + "@rollup/rollup-darwin-x64": "4.17.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", + "@rollup/rollup-linux-arm-musleabihf": "4.17.2", + "@rollup/rollup-linux-arm64-gnu": "4.17.2", + "@rollup/rollup-linux-arm64-musl": "4.17.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", + "@rollup/rollup-linux-riscv64-gnu": "4.17.2", + "@rollup/rollup-linux-s390x-gnu": "4.17.2", + "@rollup/rollup-linux-x64-gnu": "4.17.2", + "@rollup/rollup-linux-x64-musl": "4.17.2", + "@rollup/rollup-win32-arm64-msvc": "4.17.2", + "@rollup/rollup-win32-ia32-msvc": "4.17.2", + "@rollup/rollup-win32-x64-msvc": "4.17.2", "fsevents": "~2.3.2" } }, "node_modules/search-insights": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.7.0.tgz", - "integrity": "sha512-GLbVaGgzYEKMvuJbHRhLi1qoBFnjXZGZ6l4LxOYPCp4lI2jDRB3jPU9/XNhMwv6kvnA9slTreq6pvK+b3o3aqg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", + "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", "dev": true, - "peer": true, - "engines": { - "node": ">=8.16.0" - } + "peer": true }, "node_modules/shiki": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz", - "integrity": "sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.0.tgz", + "integrity": "sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==", "dev": true, "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" + "@shikijs/core": "1.6.0" } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1165,29 +1509,29 @@ "dev": true }, "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", "dev": true, "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", @@ -1220,52 +1564,63 @@ } }, "node_modules/vitepress": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.4.tgz", - "integrity": "sha512-JCQ89Bm6ECUTnyzyas3JENo00UDJeK8q1SUQyJYou+4Yz5BKEc/F3O21cu++DnUT2zXc0kvQ2Aj4BZCc/nioXQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.2.2.tgz", + "integrity": "sha512-uZ3nXR5NY4nYj3RJWCo5jev9qlNZAQo5SUXu1U0QSUx84cUm/o7hCTDVjZ4njVSVui+PsV1oAbdQOg8ygbaf4w==", "dev": true, "dependencies": { - "@docsearch/css": "^3.5.1", - "@docsearch/js": "^3.5.1", - "@vitejs/plugin-vue": "^4.2.3", - "@vue/devtools-api": "^6.5.0", - "@vueuse/core": "^10.3.0", - "@vueuse/integrations": "^10.3.0", - "body-scroll-lock": "4.0.0-beta.0", - "focus-trap": "^7.5.2", + "@docsearch/css": "^3.6.0", + "@docsearch/js": "^3.6.0", + "@shikijs/core": "^1.5.2", + "@shikijs/transformers": "^1.5.2", + "@types/markdown-it": "^14.1.1", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/devtools-api": "^7.2.0", + "@vue/shared": "^3.4.27", + "@vueuse/core": "^10.9.0", + "@vueuse/integrations": "^10.9.0", + "focus-trap": "^7.5.4", "mark.js": "8.11.1", - "minisearch": "^6.1.0", - "shiki": "^0.14.3", - "vite": "^4.4.9", - "vue": "^3.3.4" + "minisearch": "^6.3.0", + "shiki": "^1.5.2", + "vite": "^5.2.11", + "vue": "^3.4.27" }, "bin": { "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, "node_modules/vue": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", - "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", + "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.3.4", - "@vue/compiler-sfc": "3.3.4", - "@vue/runtime-dom": "3.3.4", - "@vue/server-renderer": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-sfc": "3.4.27", + "@vue/runtime-dom": "3.4.27", + "@vue/server-renderer": "3.4.27", + "@vue/shared": "3.4.27" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } } } diff --git a/docs/src/en/index.md b/docs/src/en/index.md index cff1513b..5ea49e66 100644 --- a/docs/src/en/index.md +++ b/docs/src/en/index.md @@ -4,8 +4,7 @@ layout: home hero: name: "RedisShake" - # text: "用于 Redis-like 数据库的数据迁移与处理服务" - tagline: 用于 Redis-like 数据库的数据迁移与处理服务 + tagline: Data migration and processing for Redis-like databases actions: - theme: brand text: Get Started diff --git a/docs/src/index.md b/docs/src/index.md index 07072da1..bd511e9e 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,7 +4,6 @@ layout: home hero: name: "RedisShake" - # text: "用于 Redis-like 数据库的数据迁移与处理服务" tagline: 用于 Redis-like 数据库的数据迁移与处理服务 actions: - theme: brand @@ -14,11 +13,14 @@ hero: text: 什么是 RedisShake link: /zh/guide/introduction features: - - title: 数据迁移 - details: 支持 sync、scan 和 restore 三种数据迁移模式 - - title: 数据加工 - details: 支持使用 lua 脚本对数据进行过滤与修改 - - title: 兼容 - details: 兼容多种 Redis 部署形态,兼容主流云厂商的 Redis-like 数据库 + - icon: ☁️ + title: 兼容性 + details: 兼容自建 Redis 与主流云厂商的 Redis-like 数据库 + - icon: 🔍 + title: 数据过滤与修改 + details: 提供强大的数据过滤和修改功能 + - icon: 🔄 + title: 增量同步 + details: 支持实时增量数据同步,用于不停机迁移 --- diff --git a/docs/src/zh/index.md b/docs/src/zh/index.md new file mode 100644 index 00000000..2380388f --- /dev/null +++ b/docs/src/zh/index.md @@ -0,0 +1,26 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "RedisShake" + tagline: 用于 Redis-like 数据库的数据迁移与处理服务 + actions: + - theme: brand + text: 快速上手 + link: /zh/guide/getting-started + - theme: alt + text: 什么是 RedisShake + link: /zh/guide/introduction +features: + - icon: ☁️ + title: 云厂商兼容性 + details: 兼容自建 Redis 与主流云厂商的 Redis-like 数据库 + - icon: 🔍 + title: 数据过滤与修改 + details: 提供强大的数据过滤和修改功能 + - icon: 🔄 + title: 增量同步 + details: 支持实时增量数据同步,用于不停机迁移 +--- + diff --git a/docs/src/zh/others/consistent.md b/docs/src/zh/others/consistent.md new file mode 100644 index 00000000..146a9948 --- /dev/null +++ b/docs/src/zh/others/consistent.md @@ -0,0 +1,16 @@ +# 如何判断数据一致 + +在增量同步阶段经常需要判断源端与目标端数据是否一致,介绍两种经验方法: +1. 通过日志或者监控,观察到 RedisShake 的同步流量为 0,认为同步结束,两端一致。 +2. 对比源端与目的端 Key 数量是否相等,如果相等,认为一致。 + +如果 Key 带有过期时间,会导致 Key 数量不一致。原因: +1. 因为过期算法限制,源端中可能存在一些 Key 已经过期但实际上没有被删除,这批 Key 在目的端可能会被删除,导致目的端 Key 数量少于源端。 +2. 源端和目的端独立运行,各自的过期算法独立运行,过期算法具有随机性,会导致源端和目的端删除的 Key 不一致,导致 Key 数量不一致。 + +在实践中,带有过期时间的 Key 一般认为是允许不一致的,不会影响业务,所以可以仅校验不带有过期时间的 Key 数量是否一致。如下所示,应当分别计算源端和目的端的 `$keys-$expires` 的值是否一样。[795](https://github.com/tair-opensource/RedisShake/issues/795) [791](https://github.com/tair-opensource/RedisShake/issues/791) +``` +127.0.0.1:6379> info keyspace +# Keyspace +db0:keys=4463175,expires=2,avg_ttl=333486 +``` \ No newline at end of file diff --git a/docs/src/zh/reader/scan_reader.md b/docs/src/zh/reader/scan_reader.md index 7131b411..3020f340 100644 --- a/docs/src/zh/reader/scan_reader.md +++ b/docs/src/zh/reader/scan_reader.md @@ -1,30 +1,56 @@ -# Scan Reader +--- +outline: deep +--- -## 介绍 +# Scan Reader ::: tip -本方案为次选方案,当可以使用 [`sync_reader`](sync_reader.md) 时,请优选 [`sync_reader`](sync_reader.md)。 +`scan_reader` 的性能与数据一致性均不如 [`sync_reader`](sync_reader.md),应尽可能选择 `sync_reader`。 ::: -`scan_reader` 通过 `SCAN` 命令遍历源端数据库中的所有 Key,并使用 `DUMP` 与 `RESTORE` 命令来读取与写入 Key 的内容。 +## 原理介绍 + +Scan Reader 有 SCAN 和 KSN 两个阶段,SCAN 阶段是全量同步,KSN 阶段是增量同步。 + +### 全量数据 + +**SCAN 阶段**:默认开启,可通过 `scan` 配置关闭。`scan_reader` 通过 `SCAN` 命令遍历源端数据库中的所有 Key,之后使用 `DUMP` 获取 Key 对应的 Value,并通过 `RESTORE` 命令写入目标端,完成数据的全量同步。 + +1. Redis 的 `SCAN` 命令只保证 SCAN 操作开始至结束期间均存在的 Key 一定会被返回,但是新写入的 Key 有可能会被遗漏,期间删除的 Key 也可能已经被写入目的端。 +2. SCAN 阶段期间 RedisShake 会通过 `SCAN 命令` 返回的游标来计算出当前同步进度。该进度有较大误差,仅供参考。对于非 Redis 数据库游标计算方式与 Redis 不同,所以可能会看到进度显示错误的情况,忽略即可。 + +### 增量数据 -注意: -1. Redis 的 `SCAN` 命令只保证 `SCAN` 的开始与结束之前均存在的 Key 一定会被返回,但是新写入的 Key 有可能会被遗漏,期间删除的 Key 也可能已经被写入目的端。可以通过 `ksn` 配置解决 -2. `SCAN` 命令与 `DUMP` 命令会占用源端数据库较多的 CPU 资源。 +**KSN 阶段**:默认关闭,可通过 `ksn` 开启,可以解决 SCAN` 阶段期间遗漏 Key 问题。增量数据同步并不是在 SCAN 阶段结束后才开始,而是与其一同进行,并在 SCAN 阶段结束后持续进行,直到退出 RedisShake。 +`ksn` 使用 [Redis keyspace notifications](https://redis.io/docs/manual/keyspace-notifications/) +能力来订阅 Key 的变化。具体来说,RedisShake 会使用 `psubscribe` 命令订阅 `__keyevent@*__:*`,当 Key 发生变化时,RedisShake 会收到发生修改的 Key,之后使用 `DUMP` 与 `RESTORE` 命令来从源端读取 Key 的内容,并写入目标端。 +1. Redis 在默认情况下不会开启 `notify-keyspace-events` 配置,需要手动开启,保证值中含有 `E`。 +2. 如果在 KSN 阶段出现源端将连接断开,考虑适当调高 `client-output-buffer-limit pubsub` 的值。[802](https://github.com/tair-opensource/RedisShake/issues/802) +3. `Redis keyspace notifications` 不会感知到 `FLUSHALL` 与 `FLUSHDB` 命令,因此在使用 `ksn` 参数时,需要确保源端数据库不会执行这两个命令。 + + +### 性能影响 + +SCAN 与 KSN 阶段均使用 DUMP 命令来获取数据,DUMP 命令是 CPU 密集命令,会对源端造成较高压力。需要小心使用,避免影响源端实例的可用性。 +* 对于 SCAN 阶段,可以通过调整 `count` 参数来减轻源端压力,建议从 1 开始,逐步增加。 +* 对于 KSN 阶段,暂无参数可调整,需要根据源端写请求量来评估是否开启。 + +性能影响参考数据:源端实例写 QPS 为 15 万左右时,源端 CPU 使用率为 47%,开启 RedisShake 后,源端 CPU 使用率为 91%。 ## 配置 ```toml -[scan_reader] cluster = false # set to true if source is a redis cluster address = "127.0.0.1:6379" # when cluster is true, set address to one of the cluster node username = "" # keep empty if not using ACL password = "" # keep empty if no authentication is required tls = false +dbs = [] # set you want to scan dbs such as [1,5,7], if you don't want to scan all +scan = true # set to false if you don't want to scan keys ksn = false # set to true to enabled Redis keyspace notifications (KSN) subscription -dbs = [] # set you want to scan dbs, if you don't want to scan all +count = 1 # number of keys to scan per iteration ``` * `cluster`:源端是否为集群 @@ -34,10 +60,7 @@ dbs = [] # set you want to scan dbs, if you don't want to scan * 当源端使用传统账号时,仅配置 `password` * 当源端无鉴权时,不配置 `username` 和 `password` * `tls`:源端是否开启 TLS/SSL,不需要配置证书因为 RedisShake 没有校验服务器证书 -* `ksn`:开启 `ksn` 参数后 RedisShake 会在 `SCAN` 之前使用 [Redis keyspace notifications](https://redis.io/docs/manual/keyspace-notifications/) -能力来订阅 Key 的变化。当 Key 发生变化时,RedisShake 会使用 `DUMP` 与 `RESTORE` 命令来从源端读取 Key 的内容,并写入目标端。 -* `dbs`:源端为非集群模式时,支持指定DB库 - -::: warning -Redis keyspace notifications 不会感知到 `FLUSHALL` 与 `FLUSHDB` 命令,因此在使用 `ksn` 参数时,需要确保源端数据库不会执行这两个命令。 -::: +* `dbs`:源端为非集群模式时,支持仅同步指定 DB 库。 +* `scan`:是否开启 SCAN 阶段,设置为 false 时,RedisShake 会跳过全量同步阶段 +* `ksn`:开启 `ksn` 参数后,RedisShake 会订阅源端的 Key 变化,实现增量同步 +* `count`:全量同步时每次从源端拉取的 key 的个数,默认为 1,改为较大值可以显著提升同步效率,同时也会提升源端压力。 diff --git a/docs/src/zh/reader/sync_reader.md b/docs/src/zh/reader/sync_reader.md index 5b624a3a..ef45eb20 100644 --- a/docs/src/zh/reader/sync_reader.md +++ b/docs/src/zh/reader/sync_reader.md @@ -1,3 +1,7 @@ +--- +outline: deep +--- + # Sync Reader ## 介绍 @@ -13,6 +17,10 @@ 原理:RedisShake 模拟 Slave 连接到 Master 节点,Master 会向 RedisShake 发送数据,数据包含全量与增量两部分。全量是一个 RDB 文件,增量是 AOF 数据流,RedisShake 会接受全量与增量将其暂存到硬盘上。全量同步阶段:RedisShake 首先会将 RDB 文件解析为一条条的 Redis 命令,然后将这些命令发送至目的端。增量同步阶段:RedisShake 会持续将 AOF 数据流同步至目的端。 +### 常见问题 + +**连接断开**:如果遇到 RedisShake 至源端节点的连接被断开,可通过源端对应节点的运行日志确认,多是因为源端 `client-output-buffer-limit replica` 参数设置过小,建议适当调高。 + ## 配置 ```toml diff --git a/internal/reader/scan_standalone_reader.go b/internal/reader/scan_standalone_reader.go index c1061d13..a8ad5837 100644 --- a/internal/reader/scan_standalone_reader.go +++ b/internal/reader/scan_standalone_reader.go @@ -24,6 +24,7 @@ type ScanReaderOptions struct { Username string `mapstructure:"username" default:""` Password string `mapstructure:"password" default:""` Tls bool `mapstructure:"tls" default:"false"` + Scan bool `mapstructure:"scan" default:"true"` KSN bool `mapstructure:"ksn" default:"false"` DBS []int `mapstructure:"dbs"` PreferReplica bool `mapstructure:"prefer_replica" default:"false"` @@ -87,17 +88,18 @@ func NewScanStandaloneReader(ctx context.Context, opts *ScanReaderOptions) Reade func (r *scanStandaloneReader) StartRead(ctx context.Context) chan *entry.Entry { r.ctx = ctx - r.subscript() - go r.scan() + if r.opts.Scan { + go r.scan() + } + if r.opts.KSN { + go r.subscript() + } go r.dump() go r.restore() return r.ch } func (r *scanStandaloneReader) subscript() { - if !r.opts.KSN { - return - } c := client.NewRedisClient(r.ctx, r.opts.Address, r.opts.Username, r.opts.Password, r.opts.Tls) c.Send("psubscribe", "__keyevent@*__:*") // filter dbs @@ -105,37 +107,35 @@ func (r *scanStandaloneReader) subscript() { for _, db := range r.dbs { dbIDmap[db] = struct{}{} } - go func() { - _, err := c.Receive() - if err != nil { - log.Panicf(err.Error()) - } - regex := regexp.MustCompile(`\d+`) - for { - select { - case <-r.ctx.Done(): - log.Infof("[%s] scanStandaloneReader subscript finished.", r.stat.Name) - r.needDumpQueue.Close() - return - default: - resp, err := c.Receive() - if err != nil { - log.Panicf(err.Error()) - } - respSlice := resp.([]interface{}) - key := respSlice[3].(string) - dbId := regex.FindString(respSlice[2].(string)) - dbIdInt, err := strconv.Atoi(dbId) - if err != nil { - log.Panicf(err.Error()) - } - // if the db is not in the dbs, ignore it - if _, ok := dbIDmap[dbIdInt]; ok { - r.needDumpQueue.Put(dbKey{db: dbIdInt, key: key}) - } + _, err := c.Receive() + if err != nil { + log.Panicf(err.Error()) + } + regex := regexp.MustCompile(`\d+`) + for { + select { + case <-r.ctx.Done(): + log.Infof("[%s] scanStandaloneReader subscript finished.", r.stat.Name) + r.needDumpQueue.Close() + return + default: + resp, err := c.Receive() + if err != nil { + log.Panicf(err.Error()) + } + respSlice := resp.([]interface{}) + key := respSlice[3].(string) + dbId := regex.FindString(respSlice[2].(string)) + dbIdInt, err := strconv.Atoi(dbId) + if err != nil { + log.Panicf(err.Error()) + } + // if the db is not in the dbs, ignore it + if _, ok := dbIDmap[dbIdInt]; ok { + r.needDumpQueue.Put(dbKey{db: dbIdInt, key: key}) } } - }() + } } func (r *scanStandaloneReader) scan() { diff --git a/shake.toml b/shake.toml index 93dc793f..36ad5cf6 100644 --- a/shake.toml +++ b/shake.toml @@ -9,17 +9,18 @@ password = "" # keep empty if no authentication is required tls = false sync_rdb = true # set to false if you don't want to sync rdb sync_aof = true # set to false if you don't want to sync aof -count = 1 # number of keys to scan per iteration +prefer_replica = true # set to true if you want to sync from replica node -# [scan_reader] -# cluster = false # set to true if source is a redis cluster -# address = "127.0.0.1:6379" # when cluster is true, set address to one of the cluster node -# username = "" # keep empty if not using ACL -# password = "" # keep empty if no authentication is required -# ksn = false # set to true to enabled Redis keyspace notifications (KSN) subscription -# tls = false -# dbs = [] # set you want to scan dbs such as [1,5,7], if you don't want to scan all -# prefer_replica = true # set to true if you want to sync from replica node +#[scan_reader] +#cluster = false # set to true if source is a redis cluster +#address = "127.0.0.1:6379" # when cluster is true, set address to one of the cluster node +#username = "" # keep empty if not using ACL +#password = "" # keep empty if no authentication is required +#tls = false +#dbs = [] # set you want to scan dbs such as [1,5,7], if you don't want to scan all +#scan = true # set to false if you don't want to scan keys +#ksn = false # set to true to enabled Redis keyspace notifications (KSN) subscription +#count = 1 # number of keys to scan per iteration # [rdb_reader] # filepath = "/tmp/dump.rdb"