├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── aftman.toml ├── default.project.json ├── docs ├── .vitepress │ └── config.mjs ├── classes │ ├── connection.md │ └── signal.md ├── guide │ ├── features.md │ ├── performance.md │ └── what-is-lemonsignal.md ├── index.md └── public │ ├── benchmarks │ ├── all.png │ ├── benches.rbxm │ ├── connect.png │ ├── disconnect.png │ ├── fire.png │ ├── new.png │ ├── reconnect.png │ ├── recycling.png │ └── signals.rbxm │ ├── favicon.ico │ └── lemon.png ├── package-lock.json ├── package.json ├── place.project.json ├── src └── LemonSignal.lua ├── stylua.toml ├── tests └── test.server.lua └── wally.toml /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a VitePress site to GitHub Pages 2 | # 3 | name: Deploy VitePress site to Pages 4 | 5 | on: 6 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 7 | # using the `master` branch as the default branch. 8 | push: 9 | branches: [main] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22 | concurrency: 23 | group: pages 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | with: 34 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 35 | # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm 36 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 37 | - name: Setup Node 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: 18 41 | cache: npm # or pnpm / yarn 42 | - name: Setup Pages 43 | uses: actions/configure-pages@v3 44 | - name: Install dependencies 45 | run: npm ci # or pnpm install / yarn install / bun install 46 | - name: Build with VitePress 47 | run: | 48 | npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build 49 | touch docs/.vitepress/dist/.nojekyll 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v2 52 | with: 53 | path: docs/.vitepress/dist 54 | 55 | # Deployment job 56 | deploy: 57 | environment: 58 | name: github-pages 59 | url: ${{ steps.deployment.outputs.page_url }} 60 | needs: build 61 | runs-on: ubuntu-latest 62 | name: Deploy 63 | steps: 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/.vitepress/dist 2 | docs/.vitepress/cache 3 | node_modules 4 | sourcemap.json 5 | build 6 | profile.out 7 | profile.svg 8 | __pycache__ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "luau-lsp.sourcemap.rojoProjectFile": "place.project.json" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Data-Oriented-House 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LemonSignal 2 | 3 | A pure Luau signal implementation faster than most other implementations in Roblox. 4 | 5 | # [Documentation](https://data-oriented-house.github.io/LemonSignal/) 6 | -------------------------------------------------------------------------------- /aftman.toml: -------------------------------------------------------------------------------- 1 | # This file lists tools managed by Aftman, a cross-platform toolchain manager. 2 | # For more information, see https://github.com/LPGhatguy/aftman 3 | 4 | # To add a new tool, add an entry to this table. 5 | [tools] 6 | wally = "UpliftGames/wally@0.3.1" 7 | wally-package-types = "JohnnyMorganz/wally-package-types@1.3.0" 8 | stylua = "JohnnyMorganz/stylua@0.19.1" -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LemonSignal", 3 | "tree": { 4 | "$path": "src/LemonSignal.lua" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/.vitepress/config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | title: 'LemonSignal', 6 | base: '/LemonSignal/', 7 | description: 'A pure Luau signal implementation.', 8 | head: [['link', { rel: 'icon', href: '/favicon.ico' }]], 9 | themeConfig: { 10 | // https://vitepress.dev/reference/default-theme-config 11 | nav: [ 12 | { text: 'Guide', link: '/guide/what-is-lemonsignal' }, 13 | { text: 'Api Reference', link: '/classes/signal' } 14 | ], 15 | 16 | sidebar: { 17 | '/guide/': [ 18 | { 19 | text: 'Introduction', 20 | items: [ 21 | { text: 'What is LemonSignal?', link: '/guide/what-is-lemonsignal' }, 22 | { text: 'Features', link: '/guide/features' }, 23 | { text: 'Performance', link: '/guide/performance' }, 24 | ] 25 | } 26 | ], 27 | 'classes': [ 28 | { 29 | text: 'Classes', 30 | items: [ 31 | { text: 'Signal', link: '/classes/signal' }, 32 | { text: 'Connection', link: '/classes/connection' }, 33 | ] 34 | } 35 | ] 36 | }, 37 | 38 | socialLinks: [ 39 | { icon: 'github', link: 'https://github.com/Data-Oriented-House/LemonSignal' } 40 | ] 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /docs/classes/connection.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Connection 6 | ```lua 7 | type Connection = { 8 | Connected: boolean, 9 | 10 | Disconnect: (self: Connection) -> (), 11 | Reconnect: (self: Connection) -> (), 12 | } 13 | ``` 14 | 15 | ## Properties 16 | 17 | ### Connected 18 | Indicates whether the connection is connected or not. 19 | 20 | ```lua 21 | local signal = LemonSignal.new() 22 | 23 | local connection = signal:Connect(print, "Hello world!") 24 | 25 | print(connection.Connected) --> true 26 | 27 | connection:Disconnect() 28 | 29 | print(connection.Connected) --> false 30 | ``` 31 | 32 | ## Methods 33 | 34 | ### Disconnect 35 | Disconnects the connection from the signal. May be reconnected later using :Reconnect(). 36 | 37 | ```lua 38 | local signal = LemonSignal.new() 39 | 40 | local connection = signal:Connect(print, "Test:") 41 | 42 | signal:Fire("Hello world!") --> Test: Hello world! 43 | 44 | connection:Disconnect() 45 | 46 | signal:Fire("Goodbye world!") 47 | ``` 48 | 49 | ### Reconnect 50 | Reconnects the connection to the signal again. If it's a :Once connection it will be 51 | disconnected after the next signal fire. 52 | 53 | ```lua 54 | local signal = LemonSignal.new() 55 | 56 | local connection = signal:Connect(print, "Test:") 57 | 58 | signal:Fire("Hello world!") --> Test: Hello world! 59 | 60 | connection:Disconnect() 61 | 62 | signal:Fire("Goodbye world!") 63 | 64 | connection:Reconnect() 65 | 66 | signal:Fire("Hello again!") --> Test: Hello again! 67 | ``` -------------------------------------------------------------------------------- /docs/classes/signal.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Signal 6 | ```lua 7 | type Signal = { 8 | RBXScriptConnection: RBXScriptConnection?, 9 | 10 | Connect: (self: Signal, fn: (...unknown) -> (), U...) -> Connection, 11 | Once: (self: Signal, fn: (...unknown) -> (), U...) -> Connection, 12 | Wait: (self: Signal) -> T..., 13 | Fire: (self: Signal, T...) -> (), 14 | DisconnectAll: (self: Signal) -> (), 15 | Destroy: (self: Signal) -> (), 16 | } 17 | ``` 18 | 19 | ## Constructors 20 | 21 | ### new 22 | Returns a new signal instance which can be used to connect functions. 23 | 24 | ```lua 25 | local LemonSignal = require(path.to.LemonSignal) 26 | 27 | local signal = LemonSignal.new() 28 | ``` 29 | 30 | ### wrap 31 | Returns a new signal instance which fires along with the passed RBXScriptSignal. 32 | 33 | ```lua 34 | Players.PlayerAdded:Connect(function(player) 35 | print(player, "joined, from RBXScriptSignal") 36 | end) 37 | 38 | local playerAdded = LemonSignal.wrap(Players.PlayerAdded) 39 | local connection = playerAdded:Connect(function(player) 40 | print(player, "joined, from LemonSignal") 41 | end) 42 | 43 | playerAdded:Wait() 44 | -- Player1 joins after some time passes 45 | 46 | --> Player1 joined, from RBXScriptSignal 47 | --> Player1 joined, from LemonSignal 48 | 49 | connection:Disconnect() 50 | playerAdded:Wait() 51 | -- Player2 joins after some time passes 52 | 53 | --> Player2 joined, from RBXScriptSignal 54 | 55 | connection:Reconnect() -- Now we get our hands on more API! 56 | -- Player3 joins after some time passes 57 | 58 | --> Player3 joined, from RBXScriptSignal 59 | --> Player3 joined, from LemonSignal 60 | 61 | -- It can also be used to mock Roblox event fires. 62 | playerAdded:Fire(playerAdded, Players:FindFirstChildOfClass("Player")) --> Player1 joined, from LemonSignal 63 | ``` 64 | 65 | ## Properties 66 | 67 | ### RBXScriptConnection 68 | The RBXScriptConnection that gets made when using [.wrap](#wrap). 69 | It gets disconnected and removed when using [:Destroy](#Destroy). 70 | 71 | ```lua 72 | local bindable = Instance.new("BindableEvent") 73 | 74 | local signal = LemonSignal.wrap(bindable.Event) 75 | 76 | local scriptConnection = signal.RBXScriptConnection 77 | 78 | print(signal.RBXScriptConnection) --> Connection 79 | 80 | signal:Destroy() 81 | 82 | print(scriptConnection.Connected) --> false 83 | print(signal.RBXScriptConnection) --> nil 84 | ``` 85 | 86 | ## Methods 87 | 88 | ### Connect 89 | Connects a function to the signal and returns the [connection](../classes/connection.md). Passed variadic arguments will always be prepended to fired arguments. 90 | 91 | ```lua 92 | local signal = LemonSignal.new() 93 | 94 | local connection1 = signal:Connect(function(str: string) 95 | print(str) 96 | end) 97 | 98 | local connection2 = signal:Connect(print, "Hello") 99 | 100 | signal:Fire("world!") 101 | --> Hello world! 102 | --> world! 103 | ``` 104 | 105 | ### Once 106 | Connects a function to a signal and returns a [connection](../classes/connection.md) which disconnects on the first fire. Can be reconnected later. 107 | 108 | ```lua 109 | local signal = Signal.new() 110 | 111 | local connection = signal:Once(print, "Test:") 112 | 113 | signal:Fire("Hello world!") --> Test: Hello world! 114 | 115 | print(connection.Connected) --> false 116 | ``` 117 | 118 | ### Wait 119 | Yields the coroutine until the signal is fired and returns the fired arguments. 120 | 121 | ```lua 122 | local signal = LemonSignal.new() 123 | 124 | task.delay(1, function() 125 | signal:Fire("Hello", "world!") 126 | end) 127 | 128 | local str1, str2 = signal:Wait() 129 | print(str1) --> Hello 130 | print(str2) --> world! 131 | ``` 132 | 133 | ### Fire 134 | Fires the signal and calls all connected functions with the connection's bound arguments and the fired arguments. 135 | 136 | ```lua 137 | local signal = LemonSignal.new() 138 | 139 | local connection = signal:Connect(print, "Test:") 140 | 141 | signal:Fire("Hello world!") --> Test: Hello world! 142 | ``` 143 | 144 | ### DisconnectAll 145 | Disconnects all connections currently connected to the signal. They may be reconnected later. 146 | 147 | ```lua 148 | local signal = LemonSignal.new() 149 | 150 | local connection1 = signal:Connect(print, "First:") 151 | local connection2 = signal:Connect(print, "Second:") 152 | 153 | signal:Fire("Hello world!") 154 | --> Second: Hello world! 155 | --> First: Hello world! 156 | 157 | signal:DisconnectAll() 158 | 159 | signal:Fire("Goodbye World!") 160 | ``` 161 | 162 | ### Destroy 163 | Similar to DisconnectAll but also disconnects the RBXScriptConnection made using `.warp`. 164 | Contrary to the method's name, the signal remains usable. 165 | 166 | ```lua 167 | local signal = LemonSignal.new() 168 | 169 | local connection = signal:Connect(print, "Hello") 170 | 171 | signal:Fire("world!") 172 | --> Hello world! 173 | 174 | signal:Destroy() 175 | 176 | signal:Fire("Goodbye World!") 177 | ``` -------------------------------------------------------------------------------- /docs/guide/features.md: -------------------------------------------------------------------------------- 1 | ## Reconnect 2 | There are occasions where you `:Disconnect` and end up calling `:Connect` again with a new function, with `:Reconnect` that stops being a problem because reconnecting uses that exact function you passed in your connection and simply links it back to the signal. 3 | 4 | :::info 5 | *[:Once](../classes/signal#once) connections retain their behavior, as in they will get disconnected again on the next [:Fire](../classes/signal#fire)* 6 | ::: 7 | 8 | ```lua 9 | local signal = LemonSignal.new() 10 | 11 | local connection1 = signal:Connect(function() 12 | print("Used Connect") 13 | end) 14 | 15 | local connection2 = signal:Once(function() 16 | print("Used Once") 17 | end) 18 | 19 | signal:Fire() 20 | --> Used Once 21 | --> Used Connect 22 | 23 | connection1:Disconnect() 24 | 25 | signal:Fire() -- both connections are disconnected, nothing will fire 26 | 27 | connection1:Reconnect() 28 | connection2:Reconnect() 29 | 30 | signal:Fire() 31 | --> Used Once 32 | --> Used Connect 33 | 34 | print(connection2.Connected) -- prints false, the :Once connection retains its behavior 35 | 36 | signal:Fire() 37 | --> Used Connect 38 | ``` 39 | 40 | ## Variadic connections 41 | Another cool feature is the ability to connect a function while passing varargs which so you can use the same function for all your objects for a clean and [flyweight](https://en.wikipedia.org/wiki/Flyweight_pattern) code. 42 | 43 | :::info 44 | *[:Fire](../classes/signal#fire) varargs get appended to a connection's varargs, meaning that [:Connect](../classes/signal#connect) and [:Once](../classes/signal#once) varargs will always come before `:Fire`'s* 45 | 46 | ::: 47 | 48 | ```lua 49 | local signal = LemonSignal.new() 50 | 51 | local function foo(str1: string, str2: string) 52 | print(str1 .. " " .. str2) 53 | end 54 | 55 | signal:Connect(foo, "Hello") 56 | 57 | signal:Fire("world!") 58 | --> Hello world! 59 | ``` 60 | 61 | ## Signal wrapping 62 | This lets you "turn" an [RBXScriptSignal](https://create.roblox.com/docs/reference/engine/datatypes/RBXScriptSignal) into a `LemonSignal` by firing the latter signal every time the former fires, so you can use all the new API as if the `RBXScriptSignal` had it. 63 | 64 | ```lua 65 | local bindable = Instance.new("BindableEvent") 66 | 67 | local signal = LemonSignal.wrap(bindable.Event) 68 | 69 | signal:Connect(function(str: string) 70 | print("Wrapped", str) 71 | end) 72 | 73 | bindable:Fire("signal") 74 | --> Wrapped signal 75 | ``` -------------------------------------------------------------------------------- /docs/guide/performance.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | `LemonSignal` does two main things differently which gives it an edge in performance over similar implementations. 3 | 4 | ## Implementation 5 | `LemonSignal` uses a doubly linked list implementation because it offers the most features out of the others, to show you that I made a barebones version of [singly linked list](https://gist.github.com/Aspecky/9bc1daa8a17d2b698d127eff24e82bf3), [doubly linked list](https://gist.github.com/Aspecky/df557c8e2f486eeb5eee4690e67da312), [unordered array](https://gist.github.com/Aspecky/fa28639259f94ce4586a069b16cf44e3) and [dictionary](https://gist.github.com/Aspecky/4cd07bc64ed1016ee6c73baad24bfb80) signals and benched them using [boatbomber's bechmarker plugin](https://boatbomber.itch.io/benchmarker). 6 | 7 | :::info 8 | The execution times are not meant to measure the absolute time it takes for a method to run, they are only meant to be used relative to eachother to see which one runs faster. 9 | ::: 10 | 11 | ### .new 12 | ![new](/benchmarks/new.png) 13 | 14 | ### :Connect 15 | ![connect](/benchmarks/connect.png) 16 | 17 | ### :Fire 18 | ![fire](/benchmarks/fire.png) 19 | 20 | ### :Disconnect 21 | ![disconnect](/benchmarks/disconnect.png) 22 | 23 | ### :Reconnect 24 | ![reconnect](/benchmarks/reconnect.png) 25 | 26 | ### All of the above 27 | ![all](/benchmarks/all.png) 28 | 29 | ### Conclusion 30 | From the benchmarks above, we can conclude that a doubly linked list strikes the best balance out of the other 3 by having: 31 | * Ordered fire 32 | * Fast iteration making :Fire run as fast as an array 33 | * Solves singly's O(n) disconnect by making it O(1) which makes it as fast as the other 2 34 | 35 | All the signal implementations as ModuleScripts are here [signals.rbxm](https://github.com/Data-Oriented-House/LemonSignal/blob/main/docs/public/benchmarks/signals.rbxm)
36 | All the `.bench` ModuleScripts that the benchmarker plugin uses are here [benches.rbxm](https://github.com/Data-Oriented-House/LemonSignal/blob/main/docs/public/benchmarks/benches.rbxm) 37 | 38 | ## Thread recycling 39 | Recycling a thread aka a coroutine helps [task.spawn](https://create.roblox.com/docs/reference/engine/libraries/task#spawn) and [coroutine.resume](https://create.roblox.com/docs/reference/engine/libraries/coroutine#resume) run significantly faster, about 70%, because those functions wont need to go through the trouble of creating a thread. 40 | 41 | [GoodSignal](https://github.com/stravant/goodsignal/blob/b8f2cb7c4c989bb2a9b232cec8ca5b5863bcb7f4/src/init.lua#L27) popularized that pattern for signals but it can be improved, when you fire your signal and your connection's callback is asynchronous (yields), the next connection in queue will be forced to create a new thread and the when the previous one is finally free, it'll just get GC'ed. 42 | 43 | ```lua 44 | local signal = GoodSignal.new() 45 | 46 | signal:Connect(function() 47 | print("sync") 48 | end) 49 | 50 | signal:Connect(function() 51 | task.wait() 52 | print("async") 53 | end) 54 | 55 | signal:Connect(function() 56 | print("sync") 57 | end) 58 | 59 | signal:Fire() 60 | -- The signal will be forced to create two threads because 61 | -- the middle connection will not return the thread in time 62 | ``` 63 | 64 | So what can we do about this? `LemonSignal` simply caches every thread that gets created by the signal so that next time an async connection fires, it'll most likely find a free thread to run on therefor not wasting any work done, so the longer your game runs the higher the chances there's a free thread for the asyncs and the better performance, this benchmark simulates exactly that and shows the notable ~33% speed increase: 65 | 66 | ![recycling](/benchmarks/recycling.png) 67 | 68 | ### Memory 69 | Does this negatively affect memory you might wonder? To answer this, we first need to know when do we create and cache a thread? A thread gets created when a free one isnt available, and that can only happen when our connection is asynchronous because it keeps that free thread for itself until it's done with it, at which point it caches for another connection that needs it, and even if every connection was asynchronous you'll eventually reach an equilibrium where no new threads will be created and will be exclusively recycled. 70 | 71 | To give you a perspective on how little memory the caching uses, it'd take 100k cached threads to raise the heap by a measly ~100mb! And your connections will create nowhere near that amount, so you can rest assured that the memory is being used in a worthy manner. 72 | -------------------------------------------------------------------------------- /docs/guide/what-is-lemonsignal.md: -------------------------------------------------------------------------------- 1 | # What is LemonSignal? 2 | 3 | `LemonSignal` is a pure Luau signal implementation that works both outside and inside Roblox without sacrificing performance, for Roblox it is aimed at replacing [GoodSignal](https://github.com/stravant/goodsignal) and [BindableEvent](https://create.roblox.com/docs/reference/engine/classes/BindableEvent), it has performance improvements over them and houses new features like variadic connections and the ability to reconnect disconnected connections using `:Reconnect`. 4 | More on those features on the next page. 5 | 6 | ## API Design 7 | `LemonSignal` uses the conventional Roblox signal and connection so you can easily swap it with any other Luau signal that uses that API like `GoodSignal` and immediately enjoy the new features and optimizations. 8 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | text: "A faster and more equipped pure Luau signal." 7 | actions: 8 | - theme: brand 9 | text: Get Started 10 | link: /guide/what-is-lemonsignal 11 | - theme: alt 12 | text: API Reference 13 | link: /classes/signal 14 | image: 15 | src: /lemon.png 16 | 17 | features: 18 | - title: ♻ Better thread recycling 19 | details: Up to 40% speed increase when firing the signal with asynchronous connections. 20 | - title: 🔌 Reconnect 21 | details: Reconnect makes it a lot more convenient and efficient to reconnect your connections. 22 | - title: 💬 Variadic connections 23 | details: In addition to passing a function when connecting, you can pass variadics to it too. 24 | --- 25 | 26 | -------------------------------------------------------------------------------- /docs/public/benchmarks/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/all.png -------------------------------------------------------------------------------- /docs/public/benchmarks/benches.rbxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/benches.rbxm -------------------------------------------------------------------------------- /docs/public/benchmarks/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/connect.png -------------------------------------------------------------------------------- /docs/public/benchmarks/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/disconnect.png -------------------------------------------------------------------------------- /docs/public/benchmarks/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/fire.png -------------------------------------------------------------------------------- /docs/public/benchmarks/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/new.png -------------------------------------------------------------------------------- /docs/public/benchmarks/reconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/reconnect.png -------------------------------------------------------------------------------- /docs/public/benchmarks/recycling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/recycling.png -------------------------------------------------------------------------------- /docs/public/benchmarks/signals.rbxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/benchmarks/signals.rbxm -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/lemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Data-Oriented-House/LemonSignal/f9d1d4afedef1c512e49412d6c5cca5ece226826/docs/public/lemon.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LemonSignal", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "vitepress": "^1.0.0-rc.36" 9 | } 10 | }, 11 | "node_modules/@algolia/autocomplete-core": { 12 | "version": "1.9.3", 13 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", 14 | "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", 15 | "dev": true, 16 | "dependencies": { 17 | "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", 18 | "@algolia/autocomplete-shared": "1.9.3" 19 | } 20 | }, 21 | "node_modules/@algolia/autocomplete-plugin-algolia-insights": { 22 | "version": "1.9.3", 23 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", 24 | "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", 25 | "dev": true, 26 | "dependencies": { 27 | "@algolia/autocomplete-shared": "1.9.3" 28 | }, 29 | "peerDependencies": { 30 | "search-insights": ">= 1 < 3" 31 | } 32 | }, 33 | "node_modules/@algolia/autocomplete-preset-algolia": { 34 | "version": "1.9.3", 35 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", 36 | "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", 37 | "dev": true, 38 | "dependencies": { 39 | "@algolia/autocomplete-shared": "1.9.3" 40 | }, 41 | "peerDependencies": { 42 | "@algolia/client-search": ">= 4.9.1 < 6", 43 | "algoliasearch": ">= 4.9.1 < 6" 44 | } 45 | }, 46 | "node_modules/@algolia/autocomplete-shared": { 47 | "version": "1.9.3", 48 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", 49 | "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", 50 | "dev": true, 51 | "peerDependencies": { 52 | "@algolia/client-search": ">= 4.9.1 < 6", 53 | "algoliasearch": ">= 4.9.1 < 6" 54 | } 55 | }, 56 | "node_modules/@algolia/cache-browser-local-storage": { 57 | "version": "4.22.1", 58 | "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", 59 | "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", 60 | "dev": true, 61 | "dependencies": { 62 | "@algolia/cache-common": "4.22.1" 63 | } 64 | }, 65 | "node_modules/@algolia/cache-common": { 66 | "version": "4.22.1", 67 | "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", 68 | "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", 69 | "dev": true 70 | }, 71 | "node_modules/@algolia/cache-in-memory": { 72 | "version": "4.22.1", 73 | "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", 74 | "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", 75 | "dev": true, 76 | "dependencies": { 77 | "@algolia/cache-common": "4.22.1" 78 | } 79 | }, 80 | "node_modules/@algolia/client-account": { 81 | "version": "4.22.1", 82 | "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", 83 | "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", 84 | "dev": true, 85 | "dependencies": { 86 | "@algolia/client-common": "4.22.1", 87 | "@algolia/client-search": "4.22.1", 88 | "@algolia/transporter": "4.22.1" 89 | } 90 | }, 91 | "node_modules/@algolia/client-analytics": { 92 | "version": "4.22.1", 93 | "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", 94 | "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", 95 | "dev": true, 96 | "dependencies": { 97 | "@algolia/client-common": "4.22.1", 98 | "@algolia/client-search": "4.22.1", 99 | "@algolia/requester-common": "4.22.1", 100 | "@algolia/transporter": "4.22.1" 101 | } 102 | }, 103 | "node_modules/@algolia/client-common": { 104 | "version": "4.22.1", 105 | "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", 106 | "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", 107 | "dev": true, 108 | "dependencies": { 109 | "@algolia/requester-common": "4.22.1", 110 | "@algolia/transporter": "4.22.1" 111 | } 112 | }, 113 | "node_modules/@algolia/client-personalization": { 114 | "version": "4.22.1", 115 | "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", 116 | "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", 117 | "dev": true, 118 | "dependencies": { 119 | "@algolia/client-common": "4.22.1", 120 | "@algolia/requester-common": "4.22.1", 121 | "@algolia/transporter": "4.22.1" 122 | } 123 | }, 124 | "node_modules/@algolia/client-search": { 125 | "version": "4.22.1", 126 | "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", 127 | "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", 128 | "dev": true, 129 | "dependencies": { 130 | "@algolia/client-common": "4.22.1", 131 | "@algolia/requester-common": "4.22.1", 132 | "@algolia/transporter": "4.22.1" 133 | } 134 | }, 135 | "node_modules/@algolia/logger-common": { 136 | "version": "4.22.1", 137 | "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", 138 | "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", 139 | "dev": true 140 | }, 141 | "node_modules/@algolia/logger-console": { 142 | "version": "4.22.1", 143 | "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", 144 | "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", 145 | "dev": true, 146 | "dependencies": { 147 | "@algolia/logger-common": "4.22.1" 148 | } 149 | }, 150 | "node_modules/@algolia/requester-browser-xhr": { 151 | "version": "4.22.1", 152 | "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", 153 | "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", 154 | "dev": true, 155 | "dependencies": { 156 | "@algolia/requester-common": "4.22.1" 157 | } 158 | }, 159 | "node_modules/@algolia/requester-common": { 160 | "version": "4.22.1", 161 | "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", 162 | "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", 163 | "dev": true 164 | }, 165 | "node_modules/@algolia/requester-node-http": { 166 | "version": "4.22.1", 167 | "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", 168 | "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", 169 | "dev": true, 170 | "dependencies": { 171 | "@algolia/requester-common": "4.22.1" 172 | } 173 | }, 174 | "node_modules/@algolia/transporter": { 175 | "version": "4.22.1", 176 | "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", 177 | "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", 178 | "dev": true, 179 | "dependencies": { 180 | "@algolia/cache-common": "4.22.1", 181 | "@algolia/logger-common": "4.22.1", 182 | "@algolia/requester-common": "4.22.1" 183 | } 184 | }, 185 | "node_modules/@babel/parser": { 186 | "version": "7.23.6", 187 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", 188 | "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", 189 | "dev": true, 190 | "bin": { 191 | "parser": "bin/babel-parser.js" 192 | }, 193 | "engines": { 194 | "node": ">=6.0.0" 195 | } 196 | }, 197 | "node_modules/@docsearch/css": { 198 | "version": "3.5.2", 199 | "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", 200 | "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", 201 | "dev": true 202 | }, 203 | "node_modules/@docsearch/js": { 204 | "version": "3.5.2", 205 | "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", 206 | "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", 207 | "dev": true, 208 | "dependencies": { 209 | "@docsearch/react": "3.5.2", 210 | "preact": "^10.0.0" 211 | } 212 | }, 213 | "node_modules/@docsearch/react": { 214 | "version": "3.5.2", 215 | "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", 216 | "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", 217 | "dev": true, 218 | "dependencies": { 219 | "@algolia/autocomplete-core": "1.9.3", 220 | "@algolia/autocomplete-preset-algolia": "1.9.3", 221 | "@docsearch/css": "3.5.2", 222 | "algoliasearch": "^4.19.1" 223 | }, 224 | "peerDependencies": { 225 | "@types/react": ">= 16.8.0 < 19.0.0", 226 | "react": ">= 16.8.0 < 19.0.0", 227 | "react-dom": ">= 16.8.0 < 19.0.0", 228 | "search-insights": ">= 1 < 3" 229 | }, 230 | "peerDependenciesMeta": { 231 | "@types/react": { 232 | "optional": true 233 | }, 234 | "react": { 235 | "optional": true 236 | }, 237 | "react-dom": { 238 | "optional": true 239 | }, 240 | "search-insights": { 241 | "optional": true 242 | } 243 | } 244 | }, 245 | "node_modules/@esbuild/aix-ppc64": { 246 | "version": "0.19.11", 247 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", 248 | "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", 249 | "cpu": [ 250 | "ppc64" 251 | ], 252 | "dev": true, 253 | "optional": true, 254 | "os": [ 255 | "aix" 256 | ], 257 | "engines": { 258 | "node": ">=12" 259 | } 260 | }, 261 | "node_modules/@esbuild/android-arm": { 262 | "version": "0.19.11", 263 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", 264 | "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", 265 | "cpu": [ 266 | "arm" 267 | ], 268 | "dev": true, 269 | "optional": true, 270 | "os": [ 271 | "android" 272 | ], 273 | "engines": { 274 | "node": ">=12" 275 | } 276 | }, 277 | "node_modules/@esbuild/android-arm64": { 278 | "version": "0.19.11", 279 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", 280 | "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", 281 | "cpu": [ 282 | "arm64" 283 | ], 284 | "dev": true, 285 | "optional": true, 286 | "os": [ 287 | "android" 288 | ], 289 | "engines": { 290 | "node": ">=12" 291 | } 292 | }, 293 | "node_modules/@esbuild/android-x64": { 294 | "version": "0.19.11", 295 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", 296 | "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", 297 | "cpu": [ 298 | "x64" 299 | ], 300 | "dev": true, 301 | "optional": true, 302 | "os": [ 303 | "android" 304 | ], 305 | "engines": { 306 | "node": ">=12" 307 | } 308 | }, 309 | "node_modules/@esbuild/darwin-arm64": { 310 | "version": "0.19.11", 311 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", 312 | "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", 313 | "cpu": [ 314 | "arm64" 315 | ], 316 | "dev": true, 317 | "optional": true, 318 | "os": [ 319 | "darwin" 320 | ], 321 | "engines": { 322 | "node": ">=12" 323 | } 324 | }, 325 | "node_modules/@esbuild/darwin-x64": { 326 | "version": "0.19.11", 327 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", 328 | "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", 329 | "cpu": [ 330 | "x64" 331 | ], 332 | "dev": true, 333 | "optional": true, 334 | "os": [ 335 | "darwin" 336 | ], 337 | "engines": { 338 | "node": ">=12" 339 | } 340 | }, 341 | "node_modules/@esbuild/freebsd-arm64": { 342 | "version": "0.19.11", 343 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", 344 | "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", 345 | "cpu": [ 346 | "arm64" 347 | ], 348 | "dev": true, 349 | "optional": true, 350 | "os": [ 351 | "freebsd" 352 | ], 353 | "engines": { 354 | "node": ">=12" 355 | } 356 | }, 357 | "node_modules/@esbuild/freebsd-x64": { 358 | "version": "0.19.11", 359 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", 360 | "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", 361 | "cpu": [ 362 | "x64" 363 | ], 364 | "dev": true, 365 | "optional": true, 366 | "os": [ 367 | "freebsd" 368 | ], 369 | "engines": { 370 | "node": ">=12" 371 | } 372 | }, 373 | "node_modules/@esbuild/linux-arm": { 374 | "version": "0.19.11", 375 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", 376 | "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", 377 | "cpu": [ 378 | "arm" 379 | ], 380 | "dev": true, 381 | "optional": true, 382 | "os": [ 383 | "linux" 384 | ], 385 | "engines": { 386 | "node": ">=12" 387 | } 388 | }, 389 | "node_modules/@esbuild/linux-arm64": { 390 | "version": "0.19.11", 391 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", 392 | "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", 393 | "cpu": [ 394 | "arm64" 395 | ], 396 | "dev": true, 397 | "optional": true, 398 | "os": [ 399 | "linux" 400 | ], 401 | "engines": { 402 | "node": ">=12" 403 | } 404 | }, 405 | "node_modules/@esbuild/linux-ia32": { 406 | "version": "0.19.11", 407 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", 408 | "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", 409 | "cpu": [ 410 | "ia32" 411 | ], 412 | "dev": true, 413 | "optional": true, 414 | "os": [ 415 | "linux" 416 | ], 417 | "engines": { 418 | "node": ">=12" 419 | } 420 | }, 421 | "node_modules/@esbuild/linux-loong64": { 422 | "version": "0.19.11", 423 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", 424 | "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", 425 | "cpu": [ 426 | "loong64" 427 | ], 428 | "dev": true, 429 | "optional": true, 430 | "os": [ 431 | "linux" 432 | ], 433 | "engines": { 434 | "node": ">=12" 435 | } 436 | }, 437 | "node_modules/@esbuild/linux-mips64el": { 438 | "version": "0.19.11", 439 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", 440 | "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", 441 | "cpu": [ 442 | "mips64el" 443 | ], 444 | "dev": true, 445 | "optional": true, 446 | "os": [ 447 | "linux" 448 | ], 449 | "engines": { 450 | "node": ">=12" 451 | } 452 | }, 453 | "node_modules/@esbuild/linux-ppc64": { 454 | "version": "0.19.11", 455 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", 456 | "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", 457 | "cpu": [ 458 | "ppc64" 459 | ], 460 | "dev": true, 461 | "optional": true, 462 | "os": [ 463 | "linux" 464 | ], 465 | "engines": { 466 | "node": ">=12" 467 | } 468 | }, 469 | "node_modules/@esbuild/linux-riscv64": { 470 | "version": "0.19.11", 471 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", 472 | "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", 473 | "cpu": [ 474 | "riscv64" 475 | ], 476 | "dev": true, 477 | "optional": true, 478 | "os": [ 479 | "linux" 480 | ], 481 | "engines": { 482 | "node": ">=12" 483 | } 484 | }, 485 | "node_modules/@esbuild/linux-s390x": { 486 | "version": "0.19.11", 487 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", 488 | "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", 489 | "cpu": [ 490 | "s390x" 491 | ], 492 | "dev": true, 493 | "optional": true, 494 | "os": [ 495 | "linux" 496 | ], 497 | "engines": { 498 | "node": ">=12" 499 | } 500 | }, 501 | "node_modules/@esbuild/linux-x64": { 502 | "version": "0.19.11", 503 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", 504 | "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", 505 | "cpu": [ 506 | "x64" 507 | ], 508 | "dev": true, 509 | "optional": true, 510 | "os": [ 511 | "linux" 512 | ], 513 | "engines": { 514 | "node": ">=12" 515 | } 516 | }, 517 | "node_modules/@esbuild/netbsd-x64": { 518 | "version": "0.19.11", 519 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", 520 | "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", 521 | "cpu": [ 522 | "x64" 523 | ], 524 | "dev": true, 525 | "optional": true, 526 | "os": [ 527 | "netbsd" 528 | ], 529 | "engines": { 530 | "node": ">=12" 531 | } 532 | }, 533 | "node_modules/@esbuild/openbsd-x64": { 534 | "version": "0.19.11", 535 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", 536 | "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", 537 | "cpu": [ 538 | "x64" 539 | ], 540 | "dev": true, 541 | "optional": true, 542 | "os": [ 543 | "openbsd" 544 | ], 545 | "engines": { 546 | "node": ">=12" 547 | } 548 | }, 549 | "node_modules/@esbuild/sunos-x64": { 550 | "version": "0.19.11", 551 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", 552 | "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", 553 | "cpu": [ 554 | "x64" 555 | ], 556 | "dev": true, 557 | "optional": true, 558 | "os": [ 559 | "sunos" 560 | ], 561 | "engines": { 562 | "node": ">=12" 563 | } 564 | }, 565 | "node_modules/@esbuild/win32-arm64": { 566 | "version": "0.19.11", 567 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", 568 | "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", 569 | "cpu": [ 570 | "arm64" 571 | ], 572 | "dev": true, 573 | "optional": true, 574 | "os": [ 575 | "win32" 576 | ], 577 | "engines": { 578 | "node": ">=12" 579 | } 580 | }, 581 | "node_modules/@esbuild/win32-ia32": { 582 | "version": "0.19.11", 583 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", 584 | "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", 585 | "cpu": [ 586 | "ia32" 587 | ], 588 | "dev": true, 589 | "optional": true, 590 | "os": [ 591 | "win32" 592 | ], 593 | "engines": { 594 | "node": ">=12" 595 | } 596 | }, 597 | "node_modules/@esbuild/win32-x64": { 598 | "version": "0.19.11", 599 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", 600 | "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", 601 | "cpu": [ 602 | "x64" 603 | ], 604 | "dev": true, 605 | "optional": true, 606 | "os": [ 607 | "win32" 608 | ], 609 | "engines": { 610 | "node": ">=12" 611 | } 612 | }, 613 | "node_modules/@jridgewell/sourcemap-codec": { 614 | "version": "1.4.15", 615 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 616 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 617 | "dev": true 618 | }, 619 | "node_modules/@rollup/rollup-android-arm-eabi": { 620 | "version": "4.9.4", 621 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz", 622 | "integrity": "sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==", 623 | "cpu": [ 624 | "arm" 625 | ], 626 | "dev": true, 627 | "optional": true, 628 | "os": [ 629 | "android" 630 | ] 631 | }, 632 | "node_modules/@rollup/rollup-android-arm64": { 633 | "version": "4.9.4", 634 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz", 635 | "integrity": "sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==", 636 | "cpu": [ 637 | "arm64" 638 | ], 639 | "dev": true, 640 | "optional": true, 641 | "os": [ 642 | "android" 643 | ] 644 | }, 645 | "node_modules/@rollup/rollup-darwin-arm64": { 646 | "version": "4.9.4", 647 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz", 648 | "integrity": "sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==", 649 | "cpu": [ 650 | "arm64" 651 | ], 652 | "dev": true, 653 | "optional": true, 654 | "os": [ 655 | "darwin" 656 | ] 657 | }, 658 | "node_modules/@rollup/rollup-darwin-x64": { 659 | "version": "4.9.4", 660 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz", 661 | "integrity": "sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==", 662 | "cpu": [ 663 | "x64" 664 | ], 665 | "dev": true, 666 | "optional": true, 667 | "os": [ 668 | "darwin" 669 | ] 670 | }, 671 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 672 | "version": "4.9.4", 673 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz", 674 | "integrity": "sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==", 675 | "cpu": [ 676 | "arm" 677 | ], 678 | "dev": true, 679 | "optional": true, 680 | "os": [ 681 | "linux" 682 | ] 683 | }, 684 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 685 | "version": "4.9.4", 686 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz", 687 | "integrity": "sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==", 688 | "cpu": [ 689 | "arm64" 690 | ], 691 | "dev": true, 692 | "optional": true, 693 | "os": [ 694 | "linux" 695 | ] 696 | }, 697 | "node_modules/@rollup/rollup-linux-arm64-musl": { 698 | "version": "4.9.4", 699 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz", 700 | "integrity": "sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==", 701 | "cpu": [ 702 | "arm64" 703 | ], 704 | "dev": true, 705 | "optional": true, 706 | "os": [ 707 | "linux" 708 | ] 709 | }, 710 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 711 | "version": "4.9.4", 712 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz", 713 | "integrity": "sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==", 714 | "cpu": [ 715 | "riscv64" 716 | ], 717 | "dev": true, 718 | "optional": true, 719 | "os": [ 720 | "linux" 721 | ] 722 | }, 723 | "node_modules/@rollup/rollup-linux-x64-gnu": { 724 | "version": "4.9.4", 725 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz", 726 | "integrity": "sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw==", 727 | "cpu": [ 728 | "x64" 729 | ], 730 | "dev": true, 731 | "optional": true, 732 | "os": [ 733 | "linux" 734 | ] 735 | }, 736 | "node_modules/@rollup/rollup-linux-x64-musl": { 737 | "version": "4.9.4", 738 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz", 739 | "integrity": "sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==", 740 | "cpu": [ 741 | "x64" 742 | ], 743 | "dev": true, 744 | "optional": true, 745 | "os": [ 746 | "linux" 747 | ] 748 | }, 749 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 750 | "version": "4.9.4", 751 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz", 752 | "integrity": "sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==", 753 | "cpu": [ 754 | "arm64" 755 | ], 756 | "dev": true, 757 | "optional": true, 758 | "os": [ 759 | "win32" 760 | ] 761 | }, 762 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 763 | "version": "4.9.4", 764 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz", 765 | "integrity": "sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==", 766 | "cpu": [ 767 | "ia32" 768 | ], 769 | "dev": true, 770 | "optional": true, 771 | "os": [ 772 | "win32" 773 | ] 774 | }, 775 | "node_modules/@rollup/rollup-win32-x64-msvc": { 776 | "version": "4.9.4", 777 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz", 778 | "integrity": "sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==", 779 | "cpu": [ 780 | "x64" 781 | ], 782 | "dev": true, 783 | "optional": true, 784 | "os": [ 785 | "win32" 786 | ] 787 | }, 788 | "node_modules/@types/estree": { 789 | "version": "1.0.5", 790 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 791 | "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 792 | "dev": true 793 | }, 794 | "node_modules/@types/linkify-it": { 795 | "version": "3.0.5", 796 | "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", 797 | "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", 798 | "dev": true 799 | }, 800 | "node_modules/@types/markdown-it": { 801 | "version": "13.0.7", 802 | "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", 803 | "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", 804 | "dev": true, 805 | "dependencies": { 806 | "@types/linkify-it": "*", 807 | "@types/mdurl": "*" 808 | } 809 | }, 810 | "node_modules/@types/mdurl": { 811 | "version": "1.0.5", 812 | "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", 813 | "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", 814 | "dev": true 815 | }, 816 | "node_modules/@types/web-bluetooth": { 817 | "version": "0.0.20", 818 | "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", 819 | "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", 820 | "dev": true 821 | }, 822 | "node_modules/@vitejs/plugin-vue": { 823 | "version": "5.0.3", 824 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", 825 | "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", 826 | "dev": true, 827 | "engines": { 828 | "node": "^18.0.0 || >=20.0.0" 829 | }, 830 | "peerDependencies": { 831 | "vite": "^5.0.0", 832 | "vue": "^3.2.25" 833 | } 834 | }, 835 | "node_modules/@vue/compiler-core": { 836 | "version": "3.4.10", 837 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.10.tgz", 838 | "integrity": "sha512-53vxh7K9qbx+JILnGEhrFRyr7H7e4NdT8RuTNU3m6HhJKFvcAqFTNXpYMHnyuAzzRGdsbsYHBgQC3H6xEXTG6w==", 839 | "dev": true, 840 | "dependencies": { 841 | "@babel/parser": "^7.23.6", 842 | "@vue/shared": "3.4.10", 843 | "entities": "^4.5.0", 844 | "estree-walker": "^2.0.2", 845 | "source-map-js": "^1.0.2" 846 | } 847 | }, 848 | "node_modules/@vue/compiler-dom": { 849 | "version": "3.4.10", 850 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.10.tgz", 851 | "integrity": "sha512-QAALBJksIFpXGYuo74rtMgnwpVZDvd3kYbUa4gYX9s/5QiqEvZSgbKtOdUGydXcxKPt3ifC+0/bhPVHXN2694A==", 852 | "dev": true, 853 | "dependencies": { 854 | "@vue/compiler-core": "3.4.10", 855 | "@vue/shared": "3.4.10" 856 | } 857 | }, 858 | "node_modules/@vue/compiler-sfc": { 859 | "version": "3.4.10", 860 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.10.tgz", 861 | "integrity": "sha512-sTOssaQySgrMjrhZxmAqdp6n+E51VteIVIDaOR537H2P63DyzMmig21U0XXFxiXmMIfrK91lAInnc+bIAYemGw==", 862 | "dev": true, 863 | "dependencies": { 864 | "@babel/parser": "^7.23.6", 865 | "@vue/compiler-core": "3.4.10", 866 | "@vue/compiler-dom": "3.4.10", 867 | "@vue/compiler-ssr": "3.4.10", 868 | "@vue/shared": "3.4.10", 869 | "estree-walker": "^2.0.2", 870 | "magic-string": "^0.30.5", 871 | "postcss": "^8.4.32", 872 | "source-map-js": "^1.0.2" 873 | } 874 | }, 875 | "node_modules/@vue/compiler-ssr": { 876 | "version": "3.4.10", 877 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.10.tgz", 878 | "integrity": "sha512-Y90TL1abretWbUiK5rv+9smS1thCHE5sSuhZgiLh6cxgZ2Pcy3BEvDd3reID0iwNcTdMbTeE6NI3Aq4Mux6hqQ==", 879 | "dev": true, 880 | "dependencies": { 881 | "@vue/compiler-dom": "3.4.10", 882 | "@vue/shared": "3.4.10" 883 | } 884 | }, 885 | "node_modules/@vue/devtools-api": { 886 | "version": "6.5.1", 887 | "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", 888 | "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==", 889 | "dev": true 890 | }, 891 | "node_modules/@vue/reactivity": { 892 | "version": "3.4.10", 893 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.10.tgz", 894 | "integrity": "sha512-SmGGpo37LzPcAFTopHNIJRNVOQfma9YgyPkAzx9/TJ01lbCCYigS28hEcY1hjiJ1PRK8iVX62Ov5yzmUgYH/pQ==", 895 | "dev": true, 896 | "dependencies": { 897 | "@vue/shared": "3.4.10" 898 | } 899 | }, 900 | "node_modules/@vue/runtime-core": { 901 | "version": "3.4.10", 902 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.10.tgz", 903 | "integrity": "sha512-Ri2Cz9sFr66AEUewGUK8IXhIUAhshTHVUGuJR8pqMbtjIds+zPa8QPO5UZImGMQ8HTY7eEpKwztCct9V3+Iqug==", 904 | "dev": true, 905 | "dependencies": { 906 | "@vue/reactivity": "3.4.10", 907 | "@vue/shared": "3.4.10" 908 | } 909 | }, 910 | "node_modules/@vue/runtime-dom": { 911 | "version": "3.4.10", 912 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.10.tgz", 913 | "integrity": "sha512-ROsdi5M2niRDmjXJNZ8KKiGwXyG1FO8l9n6sCN0kaJEHbjWkuigu96YAI3fK/AWUZPSXXEcMEBVPC6rL3mmUuA==", 914 | "dev": true, 915 | "dependencies": { 916 | "@vue/runtime-core": "3.4.10", 917 | "@vue/shared": "3.4.10", 918 | "csstype": "^3.1.3" 919 | } 920 | }, 921 | "node_modules/@vue/server-renderer": { 922 | "version": "3.4.10", 923 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.10.tgz", 924 | "integrity": "sha512-WpCBAhesLq44JKWfdFqb+Bi4ACUW0d8x1z90GnE0spccsAlEDMXV5nm+pwXLyW0OdP2iPrO/n/QMJh4B1v9Ciw==", 925 | "dev": true, 926 | "dependencies": { 927 | "@vue/compiler-ssr": "3.4.10", 928 | "@vue/shared": "3.4.10" 929 | }, 930 | "peerDependencies": { 931 | "vue": "3.4.10" 932 | } 933 | }, 934 | "node_modules/@vue/shared": { 935 | "version": "3.4.10", 936 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.10.tgz", 937 | "integrity": "sha512-C0mIVhwW1xQLMFyqMJxnhq6fWyE02lCgcE+TDdtGpg6B3H6kh/0YcqS54qYc76UJNlWegf3VgsLqgk6D9hBmzQ==", 938 | "dev": true 939 | }, 940 | "node_modules/@vueuse/core": { 941 | "version": "10.7.1", 942 | "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.1.tgz", 943 | "integrity": "sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==", 944 | "dev": true, 945 | "dependencies": { 946 | "@types/web-bluetooth": "^0.0.20", 947 | "@vueuse/metadata": "10.7.1", 948 | "@vueuse/shared": "10.7.1", 949 | "vue-demi": ">=0.14.6" 950 | }, 951 | "funding": { 952 | "url": "https://github.com/sponsors/antfu" 953 | } 954 | }, 955 | "node_modules/@vueuse/core/node_modules/vue-demi": { 956 | "version": "0.14.6", 957 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", 958 | "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", 959 | "dev": true, 960 | "hasInstallScript": true, 961 | "bin": { 962 | "vue-demi-fix": "bin/vue-demi-fix.js", 963 | "vue-demi-switch": "bin/vue-demi-switch.js" 964 | }, 965 | "engines": { 966 | "node": ">=12" 967 | }, 968 | "funding": { 969 | "url": "https://github.com/sponsors/antfu" 970 | }, 971 | "peerDependencies": { 972 | "@vue/composition-api": "^1.0.0-rc.1", 973 | "vue": "^3.0.0-0 || ^2.6.0" 974 | }, 975 | "peerDependenciesMeta": { 976 | "@vue/composition-api": { 977 | "optional": true 978 | } 979 | } 980 | }, 981 | "node_modules/@vueuse/integrations": { 982 | "version": "10.7.1", 983 | "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.1.tgz", 984 | "integrity": "sha512-cKo5LEeKVHdBRBtMTOrDPdR0YNtrmN9IBfdcnY2P3m5LHVrsD0xiHUtAH1WKjHQRIErZG6rJUa6GA4tWZt89Og==", 985 | "dev": true, 986 | "dependencies": { 987 | "@vueuse/core": "10.7.1", 988 | "@vueuse/shared": "10.7.1", 989 | "vue-demi": ">=0.14.6" 990 | }, 991 | "funding": { 992 | "url": "https://github.com/sponsors/antfu" 993 | }, 994 | "peerDependencies": { 995 | "async-validator": "*", 996 | "axios": "*", 997 | "change-case": "*", 998 | "drauu": "*", 999 | "focus-trap": "*", 1000 | "fuse.js": "*", 1001 | "idb-keyval": "*", 1002 | "jwt-decode": "*", 1003 | "nprogress": "*", 1004 | "qrcode": "*", 1005 | "sortablejs": "*", 1006 | "universal-cookie": "*" 1007 | }, 1008 | "peerDependenciesMeta": { 1009 | "async-validator": { 1010 | "optional": true 1011 | }, 1012 | "axios": { 1013 | "optional": true 1014 | }, 1015 | "change-case": { 1016 | "optional": true 1017 | }, 1018 | "drauu": { 1019 | "optional": true 1020 | }, 1021 | "focus-trap": { 1022 | "optional": true 1023 | }, 1024 | "fuse.js": { 1025 | "optional": true 1026 | }, 1027 | "idb-keyval": { 1028 | "optional": true 1029 | }, 1030 | "jwt-decode": { 1031 | "optional": true 1032 | }, 1033 | "nprogress": { 1034 | "optional": true 1035 | }, 1036 | "qrcode": { 1037 | "optional": true 1038 | }, 1039 | "sortablejs": { 1040 | "optional": true 1041 | }, 1042 | "universal-cookie": { 1043 | "optional": true 1044 | } 1045 | } 1046 | }, 1047 | "node_modules/@vueuse/integrations/node_modules/vue-demi": { 1048 | "version": "0.14.6", 1049 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", 1050 | "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", 1051 | "dev": true, 1052 | "hasInstallScript": true, 1053 | "bin": { 1054 | "vue-demi-fix": "bin/vue-demi-fix.js", 1055 | "vue-demi-switch": "bin/vue-demi-switch.js" 1056 | }, 1057 | "engines": { 1058 | "node": ">=12" 1059 | }, 1060 | "funding": { 1061 | "url": "https://github.com/sponsors/antfu" 1062 | }, 1063 | "peerDependencies": { 1064 | "@vue/composition-api": "^1.0.0-rc.1", 1065 | "vue": "^3.0.0-0 || ^2.6.0" 1066 | }, 1067 | "peerDependenciesMeta": { 1068 | "@vue/composition-api": { 1069 | "optional": true 1070 | } 1071 | } 1072 | }, 1073 | "node_modules/@vueuse/metadata": { 1074 | "version": "10.7.1", 1075 | "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.1.tgz", 1076 | "integrity": "sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==", 1077 | "dev": true, 1078 | "funding": { 1079 | "url": "https://github.com/sponsors/antfu" 1080 | } 1081 | }, 1082 | "node_modules/@vueuse/shared": { 1083 | "version": "10.7.1", 1084 | "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.1.tgz", 1085 | "integrity": "sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==", 1086 | "dev": true, 1087 | "dependencies": { 1088 | "vue-demi": ">=0.14.6" 1089 | }, 1090 | "funding": { 1091 | "url": "https://github.com/sponsors/antfu" 1092 | } 1093 | }, 1094 | "node_modules/@vueuse/shared/node_modules/vue-demi": { 1095 | "version": "0.14.6", 1096 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", 1097 | "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", 1098 | "dev": true, 1099 | "hasInstallScript": true, 1100 | "bin": { 1101 | "vue-demi-fix": "bin/vue-demi-fix.js", 1102 | "vue-demi-switch": "bin/vue-demi-switch.js" 1103 | }, 1104 | "engines": { 1105 | "node": ">=12" 1106 | }, 1107 | "funding": { 1108 | "url": "https://github.com/sponsors/antfu" 1109 | }, 1110 | "peerDependencies": { 1111 | "@vue/composition-api": "^1.0.0-rc.1", 1112 | "vue": "^3.0.0-0 || ^2.6.0" 1113 | }, 1114 | "peerDependenciesMeta": { 1115 | "@vue/composition-api": { 1116 | "optional": true 1117 | } 1118 | } 1119 | }, 1120 | "node_modules/algoliasearch": { 1121 | "version": "4.22.1", 1122 | "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", 1123 | "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", 1124 | "dev": true, 1125 | "dependencies": { 1126 | "@algolia/cache-browser-local-storage": "4.22.1", 1127 | "@algolia/cache-common": "4.22.1", 1128 | "@algolia/cache-in-memory": "4.22.1", 1129 | "@algolia/client-account": "4.22.1", 1130 | "@algolia/client-analytics": "4.22.1", 1131 | "@algolia/client-common": "4.22.1", 1132 | "@algolia/client-personalization": "4.22.1", 1133 | "@algolia/client-search": "4.22.1", 1134 | "@algolia/logger-common": "4.22.1", 1135 | "@algolia/logger-console": "4.22.1", 1136 | "@algolia/requester-browser-xhr": "4.22.1", 1137 | "@algolia/requester-common": "4.22.1", 1138 | "@algolia/requester-node-http": "4.22.1", 1139 | "@algolia/transporter": "4.22.1" 1140 | } 1141 | }, 1142 | "node_modules/csstype": { 1143 | "version": "3.1.3", 1144 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 1145 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 1146 | "dev": true 1147 | }, 1148 | "node_modules/entities": { 1149 | "version": "4.5.0", 1150 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1151 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1152 | "dev": true, 1153 | "engines": { 1154 | "node": ">=0.12" 1155 | }, 1156 | "funding": { 1157 | "url": "https://github.com/fb55/entities?sponsor=1" 1158 | } 1159 | }, 1160 | "node_modules/esbuild": { 1161 | "version": "0.19.11", 1162 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", 1163 | "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", 1164 | "dev": true, 1165 | "hasInstallScript": true, 1166 | "bin": { 1167 | "esbuild": "bin/esbuild" 1168 | }, 1169 | "engines": { 1170 | "node": ">=12" 1171 | }, 1172 | "optionalDependencies": { 1173 | "@esbuild/aix-ppc64": "0.19.11", 1174 | "@esbuild/android-arm": "0.19.11", 1175 | "@esbuild/android-arm64": "0.19.11", 1176 | "@esbuild/android-x64": "0.19.11", 1177 | "@esbuild/darwin-arm64": "0.19.11", 1178 | "@esbuild/darwin-x64": "0.19.11", 1179 | "@esbuild/freebsd-arm64": "0.19.11", 1180 | "@esbuild/freebsd-x64": "0.19.11", 1181 | "@esbuild/linux-arm": "0.19.11", 1182 | "@esbuild/linux-arm64": "0.19.11", 1183 | "@esbuild/linux-ia32": "0.19.11", 1184 | "@esbuild/linux-loong64": "0.19.11", 1185 | "@esbuild/linux-mips64el": "0.19.11", 1186 | "@esbuild/linux-ppc64": "0.19.11", 1187 | "@esbuild/linux-riscv64": "0.19.11", 1188 | "@esbuild/linux-s390x": "0.19.11", 1189 | "@esbuild/linux-x64": "0.19.11", 1190 | "@esbuild/netbsd-x64": "0.19.11", 1191 | "@esbuild/openbsd-x64": "0.19.11", 1192 | "@esbuild/sunos-x64": "0.19.11", 1193 | "@esbuild/win32-arm64": "0.19.11", 1194 | "@esbuild/win32-ia32": "0.19.11", 1195 | "@esbuild/win32-x64": "0.19.11" 1196 | } 1197 | }, 1198 | "node_modules/estree-walker": { 1199 | "version": "2.0.2", 1200 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1201 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1202 | "dev": true 1203 | }, 1204 | "node_modules/focus-trap": { 1205 | "version": "7.5.4", 1206 | "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", 1207 | "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", 1208 | "dev": true, 1209 | "dependencies": { 1210 | "tabbable": "^6.2.0" 1211 | } 1212 | }, 1213 | "node_modules/fsevents": { 1214 | "version": "2.3.3", 1215 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1216 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1217 | "dev": true, 1218 | "hasInstallScript": true, 1219 | "optional": true, 1220 | "os": [ 1221 | "darwin" 1222 | ], 1223 | "engines": { 1224 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1225 | } 1226 | }, 1227 | "node_modules/magic-string": { 1228 | "version": "0.30.5", 1229 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", 1230 | "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", 1231 | "dev": true, 1232 | "dependencies": { 1233 | "@jridgewell/sourcemap-codec": "^1.4.15" 1234 | }, 1235 | "engines": { 1236 | "node": ">=12" 1237 | } 1238 | }, 1239 | "node_modules/mark.js": { 1240 | "version": "8.11.1", 1241 | "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", 1242 | "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", 1243 | "dev": true 1244 | }, 1245 | "node_modules/minisearch": { 1246 | "version": "6.3.0", 1247 | "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", 1248 | "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", 1249 | "dev": true 1250 | }, 1251 | "node_modules/nanoid": { 1252 | "version": "3.3.7", 1253 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 1254 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 1255 | "dev": true, 1256 | "funding": [ 1257 | { 1258 | "type": "github", 1259 | "url": "https://github.com/sponsors/ai" 1260 | } 1261 | ], 1262 | "bin": { 1263 | "nanoid": "bin/nanoid.cjs" 1264 | }, 1265 | "engines": { 1266 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1267 | } 1268 | }, 1269 | "node_modules/picocolors": { 1270 | "version": "1.0.0", 1271 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1272 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1273 | "dev": true 1274 | }, 1275 | "node_modules/postcss": { 1276 | "version": "8.4.33", 1277 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", 1278 | "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", 1279 | "dev": true, 1280 | "funding": [ 1281 | { 1282 | "type": "opencollective", 1283 | "url": "https://opencollective.com/postcss/" 1284 | }, 1285 | { 1286 | "type": "tidelift", 1287 | "url": "https://tidelift.com/funding/github/npm/postcss" 1288 | }, 1289 | { 1290 | "type": "github", 1291 | "url": "https://github.com/sponsors/ai" 1292 | } 1293 | ], 1294 | "dependencies": { 1295 | "nanoid": "^3.3.7", 1296 | "picocolors": "^1.0.0", 1297 | "source-map-js": "^1.0.2" 1298 | }, 1299 | "engines": { 1300 | "node": "^10 || ^12 || >=14" 1301 | } 1302 | }, 1303 | "node_modules/preact": { 1304 | "version": "10.19.3", 1305 | "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", 1306 | "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", 1307 | "dev": true, 1308 | "funding": { 1309 | "type": "opencollective", 1310 | "url": "https://opencollective.com/preact" 1311 | } 1312 | }, 1313 | "node_modules/rollup": { 1314 | "version": "4.9.4", 1315 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.4.tgz", 1316 | "integrity": "sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g==", 1317 | "dev": true, 1318 | "dependencies": { 1319 | "@types/estree": "1.0.5" 1320 | }, 1321 | "bin": { 1322 | "rollup": "dist/bin/rollup" 1323 | }, 1324 | "engines": { 1325 | "node": ">=18.0.0", 1326 | "npm": ">=8.0.0" 1327 | }, 1328 | "optionalDependencies": { 1329 | "@rollup/rollup-android-arm-eabi": "4.9.4", 1330 | "@rollup/rollup-android-arm64": "4.9.4", 1331 | "@rollup/rollup-darwin-arm64": "4.9.4", 1332 | "@rollup/rollup-darwin-x64": "4.9.4", 1333 | "@rollup/rollup-linux-arm-gnueabihf": "4.9.4", 1334 | "@rollup/rollup-linux-arm64-gnu": "4.9.4", 1335 | "@rollup/rollup-linux-arm64-musl": "4.9.4", 1336 | "@rollup/rollup-linux-riscv64-gnu": "4.9.4", 1337 | "@rollup/rollup-linux-x64-gnu": "4.9.4", 1338 | "@rollup/rollup-linux-x64-musl": "4.9.4", 1339 | "@rollup/rollup-win32-arm64-msvc": "4.9.4", 1340 | "@rollup/rollup-win32-ia32-msvc": "4.9.4", 1341 | "@rollup/rollup-win32-x64-msvc": "4.9.4", 1342 | "fsevents": "~2.3.2" 1343 | } 1344 | }, 1345 | "node_modules/search-insights": { 1346 | "version": "2.13.0", 1347 | "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", 1348 | "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", 1349 | "dev": true, 1350 | "peer": true 1351 | }, 1352 | "node_modules/shikiji": { 1353 | "version": "0.9.18", 1354 | "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.9.18.tgz", 1355 | "integrity": "sha512-/tFMIdV7UQklzN13VjF0/XFzmii6C606Jc878hNezvB8ZR8FG8FW9j0I4J9EJre0owlnPntgLVPpHqy27Gs+DQ==", 1356 | "dev": true, 1357 | "dependencies": { 1358 | "shikiji-core": "0.9.18" 1359 | } 1360 | }, 1361 | "node_modules/shikiji-core": { 1362 | "version": "0.9.18", 1363 | "resolved": "https://registry.npmjs.org/shikiji-core/-/shikiji-core-0.9.18.tgz", 1364 | "integrity": "sha512-PKTXptbrp/WEDjNHV8OFG9KkfhmR0pSd161kzlDDlgQ0HXAnqJYNDSjqsy1CYZMx5bSvLMy42yJj9oFTqmkNTQ==", 1365 | "dev": true 1366 | }, 1367 | "node_modules/shikiji-transformers": { 1368 | "version": "0.9.18", 1369 | "resolved": "https://registry.npmjs.org/shikiji-transformers/-/shikiji-transformers-0.9.18.tgz", 1370 | "integrity": "sha512-lvKVfgx1ETDqUNxqiUn+whlnjQiunsAg76DOpzjjxkHE/bLcwa+jrghcMxQhui86SLR1tzCdM4Imh+RxW0LI2Q==", 1371 | "dev": true, 1372 | "dependencies": { 1373 | "shikiji": "0.9.18" 1374 | } 1375 | }, 1376 | "node_modules/source-map-js": { 1377 | "version": "1.0.2", 1378 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1379 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 1380 | "dev": true, 1381 | "engines": { 1382 | "node": ">=0.10.0" 1383 | } 1384 | }, 1385 | "node_modules/tabbable": { 1386 | "version": "6.2.0", 1387 | "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", 1388 | "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", 1389 | "dev": true 1390 | }, 1391 | "node_modules/vite": { 1392 | "version": "5.0.11", 1393 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz", 1394 | "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==", 1395 | "dev": true, 1396 | "dependencies": { 1397 | "esbuild": "^0.19.3", 1398 | "postcss": "^8.4.32", 1399 | "rollup": "^4.2.0" 1400 | }, 1401 | "bin": { 1402 | "vite": "bin/vite.js" 1403 | }, 1404 | "engines": { 1405 | "node": "^18.0.0 || >=20.0.0" 1406 | }, 1407 | "funding": { 1408 | "url": "https://github.com/vitejs/vite?sponsor=1" 1409 | }, 1410 | "optionalDependencies": { 1411 | "fsevents": "~2.3.3" 1412 | }, 1413 | "peerDependencies": { 1414 | "@types/node": "^18.0.0 || >=20.0.0", 1415 | "less": "*", 1416 | "lightningcss": "^1.21.0", 1417 | "sass": "*", 1418 | "stylus": "*", 1419 | "sugarss": "*", 1420 | "terser": "^5.4.0" 1421 | }, 1422 | "peerDependenciesMeta": { 1423 | "@types/node": { 1424 | "optional": true 1425 | }, 1426 | "less": { 1427 | "optional": true 1428 | }, 1429 | "lightningcss": { 1430 | "optional": true 1431 | }, 1432 | "sass": { 1433 | "optional": true 1434 | }, 1435 | "stylus": { 1436 | "optional": true 1437 | }, 1438 | "sugarss": { 1439 | "optional": true 1440 | }, 1441 | "terser": { 1442 | "optional": true 1443 | } 1444 | } 1445 | }, 1446 | "node_modules/vitepress": { 1447 | "version": "1.0.0-rc.36", 1448 | "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.36.tgz", 1449 | "integrity": "sha512-2z4dpM9PplN/yvTifhavOIAazlCR6OJ5PvLoRbc+7LdcFeIlCsuDGENLX4HjMW18jQZF5/j7++PNqdBfeazxUA==", 1450 | "dev": true, 1451 | "dependencies": { 1452 | "@docsearch/css": "^3.5.2", 1453 | "@docsearch/js": "^3.5.2", 1454 | "@types/markdown-it": "^13.0.7", 1455 | "@vitejs/plugin-vue": "^5.0.2", 1456 | "@vue/devtools-api": "^6.5.1", 1457 | "@vueuse/core": "^10.7.1", 1458 | "@vueuse/integrations": "^10.7.1", 1459 | "focus-trap": "^7.5.4", 1460 | "mark.js": "8.11.1", 1461 | "minisearch": "^6.3.0", 1462 | "shikiji": "^0.9.17", 1463 | "shikiji-core": "^0.9.17", 1464 | "shikiji-transformers": "^0.9.17", 1465 | "vite": "^5.0.11", 1466 | "vue": "^3.4.5" 1467 | }, 1468 | "bin": { 1469 | "vitepress": "bin/vitepress.js" 1470 | }, 1471 | "peerDependencies": { 1472 | "markdown-it-mathjax3": "^4.3.2", 1473 | "postcss": "^8.4.33" 1474 | }, 1475 | "peerDependenciesMeta": { 1476 | "markdown-it-mathjax3": { 1477 | "optional": true 1478 | }, 1479 | "postcss": { 1480 | "optional": true 1481 | } 1482 | } 1483 | }, 1484 | "node_modules/vue": { 1485 | "version": "3.4.10", 1486 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.10.tgz", 1487 | "integrity": "sha512-c+O8qGqdWPF9joTCzMGeDDedViooh6c8RY3+eW5+6GCAIY8YjChmU06LsUu0PnMZbIk1oKUoJTqKzmghYtFypw==", 1488 | "dev": true, 1489 | "dependencies": { 1490 | "@vue/compiler-dom": "3.4.10", 1491 | "@vue/compiler-sfc": "3.4.10", 1492 | "@vue/runtime-dom": "3.4.10", 1493 | "@vue/server-renderer": "3.4.10", 1494 | "@vue/shared": "3.4.10" 1495 | }, 1496 | "peerDependencies": { 1497 | "typescript": "*" 1498 | }, 1499 | "peerDependenciesMeta": { 1500 | "typescript": { 1501 | "optional": true 1502 | } 1503 | } 1504 | } 1505 | } 1506 | } 1507 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "vitepress": "^1.0.0-rc.36" 4 | }, 5 | "scripts": { 6 | "docs:dev": "vitepress dev docs", 7 | "docs:build": "vitepress build docs", 8 | "docs:preview": "vitepress preview docs" 9 | } 10 | } -------------------------------------------------------------------------------- /place.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "tree": { 4 | "$className": "DataModel", 5 | "ReplicatedStorage": { 6 | "$ignoreUnknownInstances": true, 7 | "LemonSignal": { 8 | "$path": "default.project.json" 9 | } 10 | }, 11 | "ServerScriptService": { 12 | "$ignoreUnknownInstances": true, 13 | "tests": { 14 | "$path": "tests" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/LemonSignal.lua: -------------------------------------------------------------------------------- 1 | --!optimize 2 2 | --!nocheck 3 | --!native 4 | 5 | export type Connection = { 6 | Connected: boolean, 7 | 8 | Disconnect: (self: Connection) -> (), 9 | Reconnect: (self: Connection) -> (), 10 | } 11 | 12 | export type Signal = { 13 | RBXScriptConnection: RBXScriptConnection?, 14 | 15 | Connect: (self: Signal, fn: (...any) -> (), U...) -> Connection, 16 | Once: (self: Signal, fn: (...any) -> (), U...) -> Connection, 17 | Wait: (self: Signal) -> T..., 18 | Fire: (self: Signal, T...) -> (), 19 | DisconnectAll: (self: Signal) -> (), 20 | Destroy: (self: Signal) -> (), 21 | } 22 | 23 | local freeThreads: { thread } = {} 24 | 25 | local function runCallback(callback, thread, ...) 26 | callback(...) 27 | table.insert(freeThreads, thread) 28 | end 29 | 30 | local function yielder() 31 | while true do 32 | runCallback(coroutine.yield()) 33 | end 34 | end 35 | 36 | local Connection = {} 37 | Connection.__index = Connection 38 | 39 | local function disconnect(self: Connection) 40 | if not self.Connected then 41 | return 42 | end 43 | self.Connected = false 44 | 45 | local next = self._next 46 | local prev = self._prev 47 | 48 | if next then 49 | next._prev = prev 50 | end 51 | if prev then 52 | prev._next = next 53 | end 54 | 55 | local signal = self._signal 56 | if signal._head == self then 57 | signal._head = next 58 | end 59 | end 60 | 61 | local function reconnect(self: Connection) 62 | if self.Connected then 63 | return 64 | end 65 | self.Connected = true 66 | 67 | local signal = self._signal 68 | local head = signal._head 69 | if head then 70 | head._prev = self 71 | end 72 | signal._head = self 73 | 74 | self._next = head 75 | self._prev = false 76 | end 77 | 78 | Connection.Disconnect = disconnect 79 | Connection.Reconnect = reconnect 80 | 81 | --\\ Signal //-- 82 | local Signal = {} 83 | Signal.__index = Signal 84 | 85 | -- stylua: ignore 86 | local rbxConnect, rbxDisconnect do 87 | if task then 88 | local bindable = Instance.new("BindableEvent") 89 | rbxConnect = bindable.Event.Connect 90 | rbxDisconnect = bindable.Event:Connect(function() end).Disconnect 91 | bindable:Destroy() 92 | end 93 | end 94 | 95 | local function connect(self: Signal, fn: (...any) -> (), ...: U...): Connection 96 | local head = self._head 97 | local cn = setmetatable({ 98 | Connected = true, 99 | _signal = self, 100 | _fn = fn, 101 | _varargs = if not ... then false else { ... }, 102 | _next = head, 103 | _prev = false, 104 | }, Connection) 105 | 106 | if head then 107 | head._prev = cn 108 | end 109 | self._head = cn 110 | 111 | return cn 112 | end 113 | 114 | local function once(self: Signal, fn: (...any) -> (), ...: U...) 115 | local cn 116 | cn = connect(self, function(...) 117 | disconnect(cn) 118 | fn(...) 119 | end, ...) 120 | return cn 121 | end 122 | 123 | local wait = if task 124 | then function(self: Signal): ...any 125 | local thread = coroutine.running() 126 | local cn 127 | cn = connect(self, function(...) 128 | disconnect(cn) 129 | if coroutine.status(thread) == "suspended" then 130 | task.spawn(thread, ...) 131 | end 132 | end) 133 | return coroutine.yield() 134 | end 135 | else function(self: Signal): ...any 136 | local thread = coroutine.running() 137 | local cn 138 | cn = connect(self, function(...) 139 | disconnect(cn) 140 | local passed, message = coroutine.resume(thread, ...) 141 | if not passed then 142 | error(message, 0) 143 | end 144 | end) 145 | return coroutine.yield() 146 | end 147 | 148 | local fire = if task 149 | then function(self: Signal, ...: any) 150 | local cn = self._head 151 | while cn do 152 | local thread 153 | if #freeThreads > 0 then 154 | thread = freeThreads[#freeThreads] 155 | freeThreads[#freeThreads] = nil 156 | else 157 | thread = coroutine.create(yielder) 158 | coroutine.resume(thread) 159 | end 160 | 161 | if not cn._varargs then 162 | task.spawn(thread, cn._fn, thread, ...) 163 | else 164 | local args = cn._varargs 165 | local len = #args 166 | local count = len 167 | for _, value in { ... } do 168 | count += 1 169 | args[count] = value 170 | end 171 | 172 | task.spawn(thread, cn._fn, thread, table.unpack(args)) 173 | 174 | for i = count, len + 1, -1 do 175 | args[i] = nil 176 | end 177 | end 178 | 179 | cn = cn._next 180 | end 181 | end 182 | else function(self: Signal, ...: any) 183 | local cn = self._head 184 | while cn do 185 | local thread 186 | if #freeThreads > 0 then 187 | thread = freeThreads[#freeThreads] 188 | freeThreads[#freeThreads] = nil 189 | else 190 | thread = coroutine.create(yielder) 191 | coroutine.resume(thread) 192 | end 193 | 194 | if not cn._varargs then 195 | local passed, message = coroutine.resume(thread, cn._fn, thread, ...) 196 | if not passed then 197 | print(string.format("%s\nstacktrace:\n%s", message, debug.traceback())) 198 | end 199 | else 200 | local args = cn._varargs 201 | local len = #args 202 | local count = len 203 | for _, value in { ... } do 204 | count += 1 205 | args[count] = value 206 | end 207 | 208 | local passed, message = coroutine.resume(thread, cn._fn, thread, table.unpack(args)) 209 | if not passed then 210 | print(string.format("%s\nstacktrace:\n%s", message, debug.traceback())) 211 | end 212 | 213 | for i = count, len + 1, -1 do 214 | args[i] = nil 215 | end 216 | end 217 | 218 | cn = cn._next 219 | end 220 | end 221 | 222 | local function disconnectAll(self: Signal) 223 | local cn = self._head 224 | while cn do 225 | disconnect(cn) 226 | cn = cn._next 227 | end 228 | end 229 | 230 | local function destroy(self: Signal) 231 | disconnectAll(self) 232 | local cn = self.RBXScriptConnection 233 | if cn then 234 | rbxDisconnect(cn) 235 | self.RBXScriptConnection = nil 236 | end 237 | end 238 | 239 | --\\ Constructors 240 | function Signal.new(): Signal 241 | return setmetatable({ _head = false }, Signal) 242 | end 243 | 244 | function Signal.wrap(signal: RBXScriptSignal): Signal 245 | local wrapper = setmetatable({ _head = false }, Signal) 246 | wrapper.RBXScriptConnection = rbxConnect(signal, function(...) 247 | fire(wrapper, ...) 248 | end) 249 | return wrapper 250 | end 251 | 252 | --\\ Methods 253 | Signal.Connect = connect 254 | Signal.Once = once 255 | Signal.Wait = wait 256 | Signal.Fire = fire 257 | Signal.DisconnectAll = disconnectAll 258 | Signal.Destroy = destroy 259 | 260 | return { new = Signal.new, wrap = Signal.wrap } 261 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Tabs" -------------------------------------------------------------------------------- /tests/test.server.lua: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local LemonSignal = require(ReplicatedStorage.LemonSignal) 3 | local sig = LemonSignal.new() -------------------------------------------------------------------------------- /wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "data-oriented-house/lemonsignal" 3 | version = "1.10.0" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | licence = "MIT" 6 | authors = ["Aspecky", "Sona"] 7 | description = "A pure Luau signal implementation faster than most other implementations in Roblox." 8 | realm = "shared" 9 | exclude = ["**"] 10 | include = [ 11 | "default.project.json", 12 | "src", 13 | "src/**", 14 | "LICENSE", 15 | "wally.toml", 16 | ] 17 | 18 | [dependencies] --------------------------------------------------------------------------------